ADDED .dockerignore Index: .dockerignore ================================================================== --- /dev/null +++ .dockerignore @@ -0,0 +1,26 @@ +_FOSSIL_ +.fslckout +ajax +art +autosetup +bld +compat +debian +fossil +fossil.exe +setup +src +test +tools +win +wbld +win +www +*.a +*.lib +*.log +*.manifest +*.o +*.obj +*.pdb +*.res ADDED .fossil-settings/encoding-glob Index: .fossil-settings/encoding-glob ================================================================== --- /dev/null +++ .fossil-settings/encoding-glob @@ -0,0 +1,2 @@ +compat/zlib/contrib/dotzlib/DotZLib/*.cs +win/fossil.rc Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -1,4 +1,5 @@ compat/openssl* compat/tcl* fossil fossil.exe +win/fossil.exe Index: .fossil-settings/keep-glob ================================================================== --- .fossil-settings/keep-glob +++ .fossil-settings/keep-glob @@ -1,4 +1,5 @@ compat/openssl* compat/tcl* fossil fossil.exe +win/fossil.exe Index: BUILD.txt ================================================================== --- BUILD.txt +++ BUILD.txt @@ -22,11 +22,11 @@ cd win; nmake /f Makefile.msc If you have trouble, or you want to do something fancy, just look at Makefile.classic. There are 6 configuration options that are all well -commented. Instead of editing the Makefile.classic, consider copying +commented. Instead of editing the Makefile.classic, consider copying Makefile.classic to an alternative name such as "GNUMakefile", "BSDMakefile", or "makefile" and editing the copy. BUILDING OUTSIDE THE SOURCE TREE @@ -61,11 +61,11 @@ Do not edit src/main.mk directly. Update src/makemake.tcl and then rerun it. * The *.h header files are automatically generated using a program called "makeheaders". Source code to the makeheaders program is - found in src/makeheaders.c. Documentation is found in + found in src/makeheaders.c. Documentation is found in src/makeheaders.html. * Most *.c source files are preprocessed using a program called "translate". The sources to translate are found in src/translate.c. A header comment in src/translate.c explains in detail what it does. Index: COPYRIGHT-BSD2.txt ================================================================== --- COPYRIGHT-BSD2.txt +++ COPYRIGHT-BSD2.txt @@ -1,31 +1,31 @@ Copyright 2007 D. Richard Hipp. All rights reserved. -Redistribution and use in source and binary forms, with or -without modification, are permitted provided that the +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above - copyright notice, this list of conditions and the + copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS -OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -The views and conclusions contained in the software and documentation -are those of the authors and contributors and should not be interpreted +The views and conclusions contained in the software and documentation +are those of the authors and contributors and should not be interpreted as representing official policies, either expressed or implied, of anybody else. ADDED Dockerfile Index: Dockerfile ================================================================== --- /dev/null +++ Dockerfile @@ -0,0 +1,27 @@ +### +# Dockerfile for Fossil +### +FROM fedora:21 + +### Now install some additional parts we will need for the build +RUN yum update -y && yum install -y gcc make zlib-devel openssl-devel tar && yum clean all && groupadd -r fossil -g 433 && useradd -u 431 -r -g fossil -d /opt/fossil -s /sbin/nologin -c "Fossil user" fossil + +### If you want to build "release", change the next line accordingly. +ENV FOSSIL_INSTALL_VERSION trunk + +RUN curl "http://core.tcl.tk/tcl/tarball/tcl-src.tar.gz?name=tcl-src&uuid=release" | tar zx +RUN cd tcl-src/unix && ./configure --prefix=/usr --disable-shared --disable-threads --disable-load && make && make install +RUN curl "http://www.fossil-scm.org/index.html/tarball/fossil-src.tar.gz?name=fossil-src&uuid=${FOSSIL_INSTALL_VERSION}" | tar zx +RUN cd fossil-src && ./configure --disable-lineedit --disable-fusefs --json --with-th1-docs --with-th1-hooks --with-tcl +RUN cd fossil-src && make && strip fossil && cp fossil /usr/bin && cd .. && rm -rf fossil-src && chmod a+rx /usr/bin/fossil && mkdir -p /opt/fossil && chown fossil:fossil /opt/fossil + +### Build is done, remove modules no longer needed +RUN yum remove -y gcc make zlib-devel openssl-devel tar && yum clean all + +USER fossil + +ENV HOME /opt/fossil + +EXPOSE 8080 + +CMD ["/usr/bin/fossil", "server", "--create", "--user", "admin", "/opt/fossil/repository.fossil"] Index: Makefile.classic ================================================================== --- Makefile.classic +++ Makefile.classic @@ -25,30 +25,40 @@ #### The suffix to add to final executable file. When cross-compiling # to windows, make this ".exe". Otherwise leave it blank. # E = -#### C Compile and options for use in building executables that +#### C Compile and options for use in building executables that # will run on the target platform. This is usually the same # as BCC, unless you are cross-compiling. This C compiler builds # the finished binary for fossil. The BCC compiler above is used # for building intermediate code-generator tools. # #TCC = gcc -O6 #TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage TCC = gcc -g -Os -Wall +# To use the included miniz library +# FOSSIL_ENABLE_MINIZ = 1 +# TCC += -DFOSSIL_ENABLE_MINIZ + # To add support for HTTPS TCC += -DFOSSIL_ENABLE_SSL #### Extra arguments for linking the finished binary. Fossil needs -# to link against the Z-Lib compression library. There are no -# other dependencies. We sometimes add the -static option here -# so that we can build a static executable that will run in a -# chroot jail. +# to link against the Z-Lib compression library unless the miniz +# library in the source tree is being used. There are no other +# required dependencies. We sometimes add the -static option +# here so that we can build a static executable that will run in +# a chroot jail. # -LIB = -lz $(LDFLAGS) +ZLIB_LIB.0 = -lz +ZLIB_LIB.1 = +ZLIB_LIB. = $(ZLIB_LIB.0) + +# If using zlib: +LIB = $(ZLIB_LIB.$(FOSSIL_ENABLE_MINIZ)) $(LDFLAGS) # If using HTTPS: LIB += -lcrypto -lssl #### Tcl shell for use in running the fossil testsuite. If you do not Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -37,16 +37,14 @@ # care about testing the end result, this can be blank. # TCLSH = tclsh LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@ -TCC += @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H +TCCFLAGS = @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H -D_HAVE_SQLITE_CONFIG_H INSTALLDIR = $(DESTDIR)@prefix@/bin USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@ -FOSSIL_ENABLE_TCL = @FOSSIL_ENABLE_TCL@ -FOSSIL_ENABLE_TCL_STUBS = @FOSSIL_ENABLE_TCL_STUBS@ -FOSSIL_ENABLE_TCL_PRIVATE_STUBS = @FOSSIL_ENABLE_TCL_PRIVATE_STUBS@ +FOSSIL_ENABLE_MINIZ = @FOSSIL_ENABLE_MINIZ@ include $(SRCDIR)/main.mk distclean: clean rm -f autoconfig.h config.log Makefile Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -1,1 +1,1 @@ -1.27 +1.32 Index: auto.def ================================================================== --- auto.def +++ auto.def @@ -2,19 +2,23 @@ use cc cc-lib options { with-openssl:path|auto|none - => {Look for openssl in the given path, or auto or none} + => {Look for OpenSSL in the given path, or auto or none} + with-miniz=0 => {Use miniz from the source tree} with-zlib:path => {Look for zlib in the given path} + with-th1-docs=0 => {Enable TH1 for embedded documentation pages} + with-th1-hooks=0 => {Enable TH1 hooks for commands and web pages} with-tcl:path => {Enable Tcl integration, with Tcl in the specified path} with-tcl-stubs=0 => {Enable Tcl integration via stubs library mechanism} with-tcl-private-stubs=0 => {Enable Tcl integration via private stubs mechanism} internal-sqlite=1 => {Don't use the internal SQLite, use the system one} static=0 => {Link a static executable} lineedit=1 => {Disable line editing} + fusefs=1 => {Disable the Fuse Filesystem} fossil-debug=0 => {Build with fossil debugging enabled} json=0 => {Build with fossil JSON API enabled} } # sqlite wants these types if possible @@ -28,11 +32,11 @@ # Find tclsh for the test suite. Can't yet use jimsh for this. cc-check-progs tclsh define EXTRA_CFLAGS "" define EXTRA_LDFLAGS "" -define USE_SYSTEM_SQLITE "" +define USE_SYSTEM_SQLITE 0 if {![opt-bool internal-sqlite]} { proc find_internal_sqlite {} { # On some systems (slackware), libsqlite3 requires -ldl to link. So @@ -41,17 +45,17 @@ # the code below will append -ldl to LIBS. # foreach extralibs {{} {-ldl}} { # Locate the system SQLite by searching for sqlite3_open(). Then check - # if sqlite3_wal_checkpoint() can be found as well. If we can find - # open() but not wal_checkpoint(), then the system SQLite is too old - # to link against fossil. + # if sqlite3_strglob() can be found as well. If we can find open() but + # not strglob(), then the system SQLite is too old to link against + # fossil. # if {[cc-check-function-in-lib sqlite3_open sqlite3 $extralibs]} { - if {![cc-check-function-in-lib sqlite3_wal_checkpoint sqlite3 $extralibs]} { - user-error "system sqlite3 too old (require >= 3.7.0)" + if {![cc-check-function-in-lib sqlite3_strglob sqlite3 $extralibs]} { + user-error "system sqlite3 too old (require >= 3.7.17)" } # Success. Update symbols and return. # define USE_SYSTEM_SQLITE 1 @@ -64,44 +68,49 @@ find_internal_sqlite } if {[string match *-solaris* [get-define host]]} { - define-append EXTRA_CFLAGS -D_XOPEN_SOURCE=500 + define-append EXTRA_CFLAGS {-D_XOPEN_SOURCE=500 -D__EXTENSIONS__} } if {[opt-bool fossil-debug]} { define-append EXTRA_CFLAGS -DFOSSIL_DEBUG + msg-result "Debugging support enabled" } if {[opt-bool json]} { # Reminder/FIXME (stephan): FOSSIL_ENABLE_JSON # is required in the CFLAGS because json*.c # have #ifdef guards around the whole file without # reading config.h first. define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_JSON define FOSSIL_ENABLE_JSON + msg-result "JSON support enabled" +} + +if {[opt-bool with-th1-docs]} { + define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_TH1_DOCS + define FOSSIL_ENABLE_TH1_DOCS + msg-result "TH1 embedded documentation support enabled" +} + +if {[opt-bool with-th1-hooks]} { + define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_TH1_HOOKS + define FOSSIL_ENABLE_TH1_HOOKS + msg-result "TH1 hooks support enabled" } #if {[opt-bool markdown]} { # # no-op. Markdown is now enabled by default. +# msg-result "Markdown support enabled" #} if {[opt-bool static]} { # XXX: This will not work on all systems. define-append EXTRA_LDFLAGS -static -} - -# Check for zlib, using the given location if specified -set zlibpath [opt-val with-zlib] -if {$zlibpath ne ""} { - cc-with [list -cflags "-I$zlibpath -L$zlibpath"] - define-append EXTRA_CFLAGS -I$zlibpath - define-append EXTRA_LDFLAGS -L$zlibpath -} -if {![cc-check-includes zlib.h] || ![cc-check-function-in-lib inflateEnd z]} { - user-error "zlib not found please install it or specify the location with --with-zlib" + msg-result "Trying to link statically" } set tclpath [opt-val with-tcl] if {$tclpath ne ""} { set tclprivatestubs [opt-bool with-tcl-private-stubs] @@ -160,11 +169,11 @@ define-append EXTRA_CFLAGS $cflags define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS) define FOSSIL_ENABLE_TCL } -# Helper for openssl checking +# Helper for OpenSSL checking proc check-for-openssl {msg {cflags {}}} { msg-checking "Checking for $msg..." set rc 0 msg-quiet cc-with [list -cflags $cflags -libs {-lssl -lcrypto}] { if {[cc-check-includes openssl/ssl.h] && [cc-check-functions SSL_new]} { @@ -226,18 +235,37 @@ } } else { user-error "OpenSSL not found. Consider --with-openssl=none to disable HTTPS support" } } + +if {[opt-bool with-miniz]} { + define FOSSIL_ENABLE_MINIZ 1 + msg-result "Using miniz for compression" +} else { + # Check for zlib, using the given location if specified + set zlibpath [opt-val with-zlib] + if {$zlibpath ne ""} { + cc-with [list -cflags "-I$zlibpath -L$zlibpath"] + define-append EXTRA_CFLAGS -I$zlibpath + define-append EXTRA_LDFLAGS -L$zlibpath + msg-result "Using zlib from $zlibpath" + } + if {![cc-check-includes zlib.h] || ![cc-check-function-in-lib inflateEnd z]} { + user-error "zlib not found please install it or specify the location with --with-zlib" + } +} if {[opt-bool lineedit]} { # Need readline-compatible line editing cc-with {-includes stdio.h} { if {[cc-check-includes readline/readline.h] && [cc-check-function-in-lib readline readline]} { + define-append EXTRA_CFLAGS -DHAVE_READLINE msg-result "Using readline for line editing" } elseif {[cc-check-includes editline/readline.h] && [cc-check-function-in-lib readline edit]} { define-feature editline + define-append EXTRA_CFLAGS -DHAVE_EDITLINE msg-result "Using editline for line editing" } } } @@ -248,15 +276,33 @@ if {[string match *mingw* [get-define host]]} { define-append LIBS -lwsock32 } } cc-check-function-in-lib iconv iconv +cc-check-functions utime +cc-check-functions usleep +cc-check-functions strchrnul + +# Check for getloadavg(), and if it doesn't exist, define FOSSIL_OMIT_LOAD_AVERAGE +if {![cc-check-functions getloadavg]} { + define FOSSIL_OMIT_LOAD_AVERAGE 1 + msg-result "Load average support unavailable" +} # Check for getpassphrase() for Solaris 10 where getpass() truncates to 10 chars if {![cc-check-functions getpassphrase]} { # Haiku needs this cc-check-function-in-lib getpass bsd } cc-check-function-in-lib dlopen dl + +# Check for the FuseFS library +if {[opt-bool fusefs]} { + if {[cc-check-function-in-lib fuse_mount fuse]} { + define FOSSIL_HAVE_FUSEFS 1 + define-append LIBS -lfuse + msg-result "FuseFS support enabled" + } +} make-template Makefile.in make-config-header autoconfig.h -auto {USE_* FOSSIL_*} Index: autosetup/autosetup ================================================================== --- autosetup/autosetup +++ autosetup/autosetup @@ -925,11 +925,11 @@ } # Load module source in the global scope by executing the given command proc automf_load {args} { if {[catch [list uplevel #0 $args] msg opts] ni {0 2 3}} { - autosetup-full-error [error-dump $msg $opts] + autosetup-full-error [error-dump $msg $opts $::autosetup(debug)] } } # Initial settings set autosetup(exe) $::argv0 @@ -1706,18 +1706,35 @@ return $msg } # Given the return from [catch {...} msg opts], returns an appropriate # error message. A nice one for Jim and a less-nice one for Tcl. +# If 'fulltrace' is set, a full stack trace is provided. +# Otherwise a simple message is provided. # -# This is designed for developer errors, e.g. in module code +# This is designed for developer errors, e.g. in module code or auto.def code +# # -proc error-dump {msg opts} { +proc error-dump {msg opts fulltrace} { if {$::autosetup(istcl)} { - return "Error: [dict get $opts -errorinfo]" + if {$fulltrace} { + return "Error: [dict get $opts -errorinfo]" + } else { + return "Error: $msg" + } } else { - return "Error: $msg\n[stackdump $opts(-errorinfo)]" + lassign $opts(-errorinfo) p f l + if {$f ne ""} { + set result "$f:$l: Error: " + } + append result "$msg\n" + if {$fulltrace} { + append result [stackdump $opts(-errorinfo)] + } + + # Remove the trailing newline + string trim $result } } } # ----- module text-formatting ----- @@ -1886,13 +1903,13 @@ # Entry/Exit # if {$autosetup(debug)} { main $argv } -if {[catch {main $argv} msg] == 1} { +if {[catch {main $argv} msg opts] == 1} { show-notices - puts stderr [error-stacktrace $msg] - if {!$autosetup(debug) && !$autosetup(istcl)} { + autosetup-full-error [error-dump $msg $opts $::autosetup(debug)] + if {!$autosetup(debug)} { puts stderr "Try: '[file tail $autosetup(exe)] --debug' for a full stack trace" } exit 1 } Index: autosetup/cc-shared.tcl ================================================================== --- autosetup/cc-shared.tcl +++ autosetup/cc-shared.tcl @@ -94,10 +94,19 @@ define SHOBJ_LDFLAGS -b define SH_CFLAGS +z define SH_LINKFLAGS -Wl,+s define LD_LIBRARY_PATH SHLIB_PATH } + *-*-haiku { + define SHOBJ_CFLAGS "" + define SHOBJ_LDFLAGS -shared + define SH_CFLAGS "" + define SH_LDFLAGS -shared + define SH_LINKFLAGS "" + define SH_SOPREFIX "" + define LD_LIBRARY_PATH LIBRARY_PATH + } } if {![is-defined SHOBJ_LDFLAGS_R]} { define SHOBJ_LDFLAGS_R [get-define SHOBJ_LDFLAGS] } Index: autosetup/cc.tcl ================================================================== --- autosetup/cc.tcl +++ autosetup/cc.tcl @@ -333,11 +333,11 @@ } -libs { # Note that new libraries are added before previous libraries set new($name) [list {*}$value {*}$new($name)] } - -link - -lang { + -link - -lang - -nooutput { set new($name) $value } -source - -sourcefile - -code { # XXX: These probably are only valid directly from cctest set new($name) $value @@ -428,10 +428,11 @@ ## -lang c|c++ Use the C (default) or C++ compiler ## -libs liblist List of libraries to link, e.g. {-ldl -lm} ## -code code Code to compile in the body of main() ## -source code Compile a complete program. Ignore -includes, -declare and -code ## -sourcefile file Shorthand for -source [readfile [get-define srcdir]/$file] +## -nooutput 1 Treat any compiler output (e.g. a warning) as an error # # Unless -source or -sourcefile is specified, the C program looks like: # ## #include /* same for remaining includes in the list */ ## @@ -521,11 +522,12 @@ } writefile $src $lines\n set ok 1 - if {[catch {exec-with-stderr {*}$cmdline} result errinfo]} { + set err [catch {exec-with-stderr {*}$cmdline} result errinfo] + if {$err || ($opts(-nooutput) && [string length $result])} { configlog "Failed: [join $cmdline]" configlog $result configlog "============" configlog "The failed code was:" configlog $lines @@ -671,27 +673,27 @@ } define CCACHE [find-an-executable [get-env CCACHE ccache]] # Initial cctest settings -cc-store-settings {-cflags {} -includes {} -declare {} -link 0 -lang c -libs {} -code {}} +cc-store-settings {-cflags {} -includes {} -declare {} -link 0 -lang c -libs {} -code {} -nooutput 0} set autosetup(cc-include-deps) {} msg-result "C compiler...[get-define CCACHE] [get-define CC] [get-define CFLAGS]" if {[get-define CXX] ne "false"} { msg-result "C++ compiler...[get-define CCACHE] [get-define CXX] [get-define CXXFLAGS]" } msg-result "Build C compiler...[get-define CC_FOR_BUILD]" -# On Darwin, we prefer to use -gstabs to avoid creating .dSYM directories -# but some compilers don't support -gstabs, so test for it here. +# On Darwin, we prefer to use -g0 to avoid creating .dSYM directories +# but some compilers may not support it, so test here. switch -glob -- [get-define host] { *-*-darwin* { - if {[cctest -cflags {-gstabs}]} { - define cc-default-debug -gstabs + if {[cctest -cflags {-g0}]} { + define cc-default-debug -g0 } } } if {![cc-check-includes stdlib.h]} { user-error "Compiler does not work. See config.log" } Index: autosetup/config.guess ================================================================== --- autosetup/config.guess +++ autosetup/config.guess @@ -1,44 +1,38 @@ #! /bin/sh # Attempt to guess a canonical system name. -# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, -# 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 -# Free Software Foundation, Inc. +# Copyright 1992-2014 Free Software Foundation, Inc. -timestamp='2010-09-24' +timestamp='2014-03-23' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or +# the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA -# 02110-1301, USA. +# along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - - -# Originally written by Per Bothner. Please send patches (context -# diff format) to and include a ChangeLog -# entry. -# -# This script attempts to guess a canonical system name similar to -# config.sub. If it succeeds, it prints the system name on stdout, and -# exits with 0. Otherwise, it exits with 1. +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). +# +# Originally written by Per Bothner. # # You can get the latest version of this script from: # http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD +# +# Please send patches with a ChangeLog entry to config-patches@gnu.org. + me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] @@ -54,13 +48,11 @@ version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. -Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, -2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free -Software Foundation, Inc. +Copyright 1992-2014 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" @@ -90,11 +82,11 @@ if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi -trap 'exit 1' HUP INT TERM +trap 'exit 1' 1 2 15 # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. @@ -104,11 +96,11 @@ # Portable tmp directory creation inspired by the Autoconf team. set_cc_for_build=' trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; -trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" HUP INT PIPE TERM ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; : ${TMPDIR=/tmp} ; { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; @@ -137,17 +129,38 @@ UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case "${UNAME_SYSTEM}" in +Linux|GNU|GNU/*) + # If the system lacks a compiler, then just pick glibc. + # We could probably try harder. + LIBC=gnu + + eval $set_cc_for_build + cat <<-EOF > $dummy.c + #include + #if defined(__UCLIBC__) + LIBC=uclibc + #elif defined(__dietlibc__) + LIBC=dietlibc + #else + LIBC=gnu + #endif + EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` + ;; +esac # Note: order is significant - the case branches are not exclusive. case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or - # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. @@ -179,11 +192,11 @@ else os=netbsdelf fi ;; *) - os=netbsd + os=netbsd ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need @@ -200,10 +213,14 @@ # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. echo "${machine}-${os}${release}" exit ;; + *:Bitrig:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-bitrig${UNAME_RELEASE} + exit ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} exit ;; *:ekkoBSD:*:*) @@ -222,11 +239,11 @@ case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) - UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU @@ -268,11 +285,14 @@ # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` - exit ;; + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + exitcode=$? + trap '' 0 + exit $exitcode ;; Alpha\ *:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # Should we change UNAME_MACHINE based on the output of uname instead # of the specific Alpha model? echo alpha-pc-interix @@ -294,16 +314,16 @@ exit ;; *:z/VM:*:*) echo s390-ibm-zvmoe exit ;; *:OS400:*:*) - echo powerpc-ibm-os400 + echo powerpc-ibm-os400 exit ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) echo arm-acorn-riscix${UNAME_RELEASE} exit ;; - arm:riscos:*:*|arm:RISCOS:*:*) + arm*:riscos:*:*|arm*:RISCOS:*:*) echo arm-unknown-riscos exit ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) echo hppa1.1-hitachi-hiuxmpp exit ;; @@ -393,27 +413,27 @@ # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) - echo m68k-atari-mint${UNAME_RELEASE} + echo m68k-atari-mint${UNAME_RELEASE} exit ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} - exit ;; + exit ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) - echo m68k-atari-mint${UNAME_RELEASE} + echo m68k-atari-mint${UNAME_RELEASE} exit ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) - echo m68k-milan-mint${UNAME_RELEASE} - exit ;; + echo m68k-milan-mint${UNAME_RELEASE} + exit ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) - echo m68k-hades-mint${UNAME_RELEASE} - exit ;; + echo m68k-hades-mint${UNAME_RELEASE} + exit ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) - echo m68k-unknown-mint${UNAME_RELEASE} - exit ;; + echo m68k-unknown-mint${UNAME_RELEASE} + exit ;; m68k:machten:*:*) echo m68k-apple-machten${UNAME_RELEASE} exit ;; powerpc:machten:*:*) echo powerpc-apple-machten${UNAME_RELEASE} @@ -479,12 +499,12 @@ exit ;; m88k:*:3*:R3*) echo m88k-motorola-sysv3 exit ;; AViiON:dgux:*:*) - # DG/UX returns AViiON for all architectures - UNAME_PROCESSOR=`/usr/bin/uname -p` + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] then if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ [ ${TARGET_BINARY_INTERFACE}x = x ] then @@ -493,11 +513,11 @@ echo m88k-dg-dguxbcs${UNAME_RELEASE} fi else echo i586-dg-dgux${UNAME_RELEASE} fi - exit ;; + exit ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) echo m88k-dolphin-sysv3 exit ;; M88*:*:R3*:*) # Delta 88k system running SVR3 @@ -593,56 +613,56 @@ 9000/31? ) HP_ARCH=m68000 ;; 9000/[34]?? ) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if [ -x /usr/bin/getconf ]; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` - sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` - case "${sc_cpu_version}" in - 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 - 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 - 532) # CPU_PA_RISC2_0 - case "${sc_kernel_bits}" in - 32) HP_ARCH="hppa2.0n" ;; - 64) HP_ARCH="hppa2.0w" ;; + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 - esac ;; - esac + esac ;; + esac fi if [ "${HP_ARCH}" = "" ]; then eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - - #define _HPUX_SOURCE - #include - #include - - int main () - { - #if defined(_SC_KERNEL_BITS) - long bits = sysconf(_SC_KERNEL_BITS); - #endif - long cpu = sysconf (_SC_CPU_VERSION); - - switch (cpu) - { - case CPU_PA_RISC1_0: puts ("hppa1.0"); break; - case CPU_PA_RISC1_1: puts ("hppa1.1"); break; - case CPU_PA_RISC2_0: - #if defined(_SC_KERNEL_BITS) - switch (bits) - { - case 64: puts ("hppa2.0w"); break; - case 32: puts ("hppa2.0n"); break; - default: puts ("hppa2.0"); break; - } break; - #else /* !defined(_SC_KERNEL_BITS) */ - puts ("hppa2.0"); break; - #endif - default: puts ("hppa1.0"); break; - } - exit (0); - } + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } EOF (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac @@ -729,26 +749,26 @@ parisc*:Lites*:*:*) echo hppa1.1-hp-lites exit ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) echo c1-convex-bsd - exit ;; + exit ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi - exit ;; + exit ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) echo c34-convex-bsd - exit ;; + exit ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) echo c38-convex-bsd - exit ;; + exit ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) echo c4-convex-bsd - exit ;; + exit ;; CRAY*Y-MP:*:*:*) echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*[A-Z]90:*:*:*) echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ @@ -768,18 +788,18 @@ *:UNICOS/mp:*:*) echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` - FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` - FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` - echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" - exit ;; + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; 5000:UNIX_System_V:4.*:*) - FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` - FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` - echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} exit ;; sparc*:BSD/OS:*:*) @@ -787,37 +807,39 @@ exit ;; *:BSD/OS:*:*) echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} exit ;; *:FreeBSD:*:*) - case ${UNAME_MACHINE} in - pc98) - echo i386-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + UNAME_PROCESSOR=`/usr/bin/uname -p` + case ${UNAME_PROCESSOR} in amd64) echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; *) - echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; esac exit ;; i*:CYGWIN*:*) echo ${UNAME_MACHINE}-pc-cygwin exit ;; + *:MINGW64*:*) + echo ${UNAME_MACHINE}-pc-mingw64 + exit ;; *:MINGW*:*) echo ${UNAME_MACHINE}-pc-mingw32 exit ;; - i*:MSYS*:*) - echo ${UNAME_MACHINE}-pc-msys - exit ;; + *:MSYS*:*) + echo ${UNAME_MACHINE}-pc-msys + exit ;; i*:windows32*:*) - # uname -m includes "-pc" on this system. - echo ${UNAME_MACHINE}-mingw32 + # uname -m includes "-pc" on this system. + echo ${UNAME_MACHINE}-mingw32 exit ;; i*:PW*:*) echo ${UNAME_MACHINE}-pc-pw32 exit ;; *:Interix*:*) - case ${UNAME_MACHINE} in + case ${UNAME_MACHINE} in x86) echo i586-pc-interix${UNAME_RELEASE} exit ;; authenticamd | genuineintel | EM64T) echo x86_64-unknown-interix${UNAME_RELEASE} @@ -850,74 +872,85 @@ prep*:SunOS:5.*:*) echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; *:GNU:*:*) # the GNU system - echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-${LIBC}`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` exit ;; *:GNU/*:*:*) # other systems with GNU libc and userland - echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC} exit ;; i*86:Minix:*:*) echo ${UNAME_MACHINE}-pc-minix exit ;; + aarch64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; - esac + esac objdump --private-headers /bin/sh | grep -q ld.so.1 - if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi - echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} + if test "$?" = 0 ; then LIBC="gnulibc1" ; fi + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + arc:Linux:*:* | arceb:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; arm*:Linux:*:*) eval $set_cc_for_build if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_EABI__ then - echo ${UNAME_MACHINE}-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} else - echo ${UNAME_MACHINE}-unknown-linux-gnueabi + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabi + else + echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabihf + fi fi exit ;; avr32*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; cris:Linux:*:*) - echo cris-axis-linux-gnu + echo ${UNAME_MACHINE}-axis-linux-${LIBC} exit ;; crisv32:Linux:*:*) - echo crisv32-axis-linux-gnu + echo ${UNAME_MACHINE}-axis-linux-${LIBC} exit ;; frv:Linux:*:*) - echo frv-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + hexagon:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; i*86:Linux:*:*) - LIBC=gnu - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - #ifdef __dietlibc__ - LIBC=dietlibc - #endif -EOF - eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC'` - echo "${UNAME_MACHINE}-pc-linux-${LIBC}" + echo ${UNAME_MACHINE}-pc-linux-${LIBC} exit ;; ia64:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; m32r*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; m68*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; mips:Linux:*:* | mips64:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU @@ -932,71 +965,80 @@ CPU= #endif #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'` - test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-${LIBC}"; exit; } ;; - or32:Linux:*:*) - echo or32-unknown-linux-gnu + openrisc*:Linux:*:*) + echo or1k-unknown-linux-${LIBC} + exit ;; + or32:Linux:*:* | or1k*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; padre:Linux:*:*) - echo sparc-unknown-linux-gnu + echo sparc-unknown-linux-${LIBC} exit ;; parisc64:Linux:*:* | hppa64:Linux:*:*) - echo hppa64-unknown-linux-gnu + echo hppa64-unknown-linux-${LIBC} exit ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in - PA7*) echo hppa1.1-unknown-linux-gnu ;; - PA8*) echo hppa2.0-unknown-linux-gnu ;; - *) echo hppa-unknown-linux-gnu ;; + PA7*) echo hppa1.1-unknown-linux-${LIBC} ;; + PA8*) echo hppa2.0-unknown-linux-${LIBC} ;; + *) echo hppa-unknown-linux-${LIBC} ;; esac exit ;; ppc64:Linux:*:*) - echo powerpc64-unknown-linux-gnu + echo powerpc64-unknown-linux-${LIBC} exit ;; ppc:Linux:*:*) - echo powerpc-unknown-linux-gnu + echo powerpc-unknown-linux-${LIBC} + exit ;; + ppc64le:Linux:*:*) + echo powerpc64le-unknown-linux-${LIBC} + exit ;; + ppcle:Linux:*:*) + echo powerpcle-unknown-linux-${LIBC} exit ;; s390:Linux:*:* | s390x:Linux:*:*) - echo ${UNAME_MACHINE}-ibm-linux + echo ${UNAME_MACHINE}-ibm-linux-${LIBC} exit ;; sh64*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; sh*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; sparc:Linux:*:* | sparc64:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; tile*:Linux:*:*) - echo ${UNAME_MACHINE}-tilera-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; vax:Linux:*:*) - echo ${UNAME_MACHINE}-dec-linux-gnu + echo ${UNAME_MACHINE}-dec-linux-${LIBC} exit ;; x86_64:Linux:*:*) - echo x86_64-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; xtensa*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. echo i386-sequent-sysv4 exit ;; i*86:UNIX_SV:4.2MP:2.*) - # Unixware is an offshoot of SVR4, but it has its own version - # number series starting with 2... - # I am not positive that other SVR4 systems won't match this, + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. - # Use sysv4.2uw... so that sysv4* matches it. + # Use sysv4.2uw... so that sysv4* matches it. echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} exit ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. @@ -1024,11 +1066,11 @@ else echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} fi exit ;; i*86:*:5:[678]*) - # UnixWare 7.x, OpenUNIX and OpenServer 6. + # UnixWare 7.x, OpenUNIX and OpenServer 6. case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac @@ -1052,17 +1094,17 @@ echo ${UNAME_MACHINE}-pc-sysv32 fi exit ;; pc:*:*:*) # Left here for compatibility: - # uname -m prints for DJGPP always 'pc', but it prints nothing about - # the processor, so we play safe by assuming i586. + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i586. # Note: whatever this is, it MUST be the same as what config.sub # prints for the "djgpp" host, or else GDB configury will decide that # this is a cross-build. echo i586-pc-msdosdjgpp - exit ;; + exit ;; Intel:Mach:3*:*) echo i386-pc-mach3 exit ;; paragon:*:*:*) echo i860-intel-osf1 @@ -1093,12 +1135,12 @@ /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3${OS_REL}; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) - /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ - && { echo i486-ncr-sysv4; exit; } ;; + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; NCR*:*:4.2:* | MPRAS*:*:4.2:*) OS_REL='.3' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ @@ -1137,14 +1179,14 @@ echo ${UNAME_MACHINE}-sni-sysv4 else echo ns32k-sni-sysv fi exit ;; - PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort - # says - echo i586-unisys-sysv4 - exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm echo hppa1.1-stratus-sysv4 exit ;; @@ -1166,15 +1208,15 @@ news*:NEWS-OS:6*:*) echo mips-sony-newsos6 exit ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if [ -d /usr/nec ]; then - echo mips-nec-sysv${UNAME_RELEASE} + echo mips-nec-sysv${UNAME_RELEASE} else - echo mips-unknown-sysv${UNAME_RELEASE} + echo mips-unknown-sysv${UNAME_RELEASE} fi - exit ;; + exit ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. echo powerpc-be-beos exit ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. echo powerpc-apple-beos @@ -1183,10 +1225,13 @@ echo i586-pc-beos exit ;; BePC:Haiku:*:*) # Haiku running on Intel PC compatible. echo i586-pc-haiku exit ;; + x86_64:Haiku:*:*) + echo x86_64-unknown-haiku + exit ;; SX-4:SUPER-UX:*:*) echo sx4-nec-superux${UNAME_RELEASE} exit ;; SX-5:SUPER-UX:*:*) echo sx5-nec-superux${UNAME_RELEASE} @@ -1209,23 +1254,35 @@ *:Rhapsody:*:*) echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} exit ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown - case $UNAME_PROCESSOR in - i386) - eval $set_cc_for_build - if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then - if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ - (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ - grep IS_64BIT_ARCH >/dev/null - then - UNAME_PROCESSOR="x86_64" - fi - fi ;; - unknown) UNAME_PROCESSOR=powerpc ;; - esac + eval $set_cc_for_build + if test "$UNAME_PROCESSOR" = unknown ; then + UNAME_PROCESSOR=powerpc + fi + if test `echo "$UNAME_RELEASE" | sed -e 's/\..*//'` -le 10 ; then + if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then + if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + case $UNAME_PROCESSOR in + i386) UNAME_PROCESSOR=x86_64 ;; + powerpc) UNAME_PROCESSOR=powerpc64 ;; + esac + fi + fi + elif test "$UNAME_PROCESSOR" = i386 ; then + # Avoid executing cc on OS X 10.9, as it ships with a stub + # that puts up a graphical alert prompting to install + # developer tools. Any system running Mac OS X 10.7 or + # later (Darwin 11 and later) is required to have a 64-bit + # processor. This is not true of the ARM version of Darwin + # that Apple uses in portable devices. + UNAME_PROCESSOR=x86_64 + fi echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} exit ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = "x86"; then @@ -1238,11 +1295,11 @@ echo i386-pc-qnx exit ;; NEO-?:NONSTOP_KERNEL:*:*) echo neo-tandem-nsk${UNAME_RELEASE} exit ;; - NSE-?:NONSTOP_KERNEL:*:*) + NSE-*:NONSTOP_KERNEL:*:*) echo nse-tandem-nsk${UNAME_RELEASE} exit ;; NSR-?:NONSTOP_KERNEL:*:*) echo nsr-tandem-nsk${UNAME_RELEASE} exit ;; @@ -1283,17 +1340,17 @@ exit ;; *:ITS:*:*) echo pdp10-unknown-its exit ;; SEI:*:*:SEIUX) - echo mips-sei-seiux${UNAME_RELEASE} + echo mips-sei-seiux${UNAME_RELEASE} exit ;; *:DragonFly:*:*) echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` exit ;; *:*VMS:*:*) - UNAME_MACHINE=`(uname -p) 2>/dev/null` + UNAME_MACHINE=`(uname -p) 2>/dev/null` case "${UNAME_MACHINE}" in A*) echo alpha-dec-vms ; exit ;; I*) echo ia64-dec-vms ; exit ;; V*) echo vax-dec-vms ; exit ;; esac ;; @@ -1307,162 +1364,14 @@ echo ${UNAME_MACHINE}-pc-rdos exit ;; i*86:AROS:*:*) echo ${UNAME_MACHINE}-pc-aros exit ;; -esac - -#echo '(No uname command or uname output not recognized.)' 1>&2 -#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 - -eval $set_cc_for_build -cat >$dummy.c < -# include -#endif -main () -{ -#if defined (sony) -#if defined (MIPSEB) - /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, - I don't know.... */ - printf ("mips-sony-bsd\n"); exit (0); -#else -#include - printf ("m68k-sony-newsos%s\n", -#ifdef NEWSOS4 - "4" -#else - "" -#endif - ); exit (0); -#endif -#endif - -#if defined (__arm) && defined (__acorn) && defined (__unix) - printf ("arm-acorn-riscix\n"); exit (0); -#endif - -#if defined (hp300) && !defined (hpux) - printf ("m68k-hp-bsd\n"); exit (0); -#endif - -#if defined (NeXT) -#if !defined (__ARCHITECTURE__) -#define __ARCHITECTURE__ "m68k" -#endif - int version; - version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; - if (version < 4) - printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); - else - printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); - exit (0); -#endif - -#if defined (MULTIMAX) || defined (n16) -#if defined (UMAXV) - printf ("ns32k-encore-sysv\n"); exit (0); -#else -#if defined (CMU) - printf ("ns32k-encore-mach\n"); exit (0); -#else - printf ("ns32k-encore-bsd\n"); exit (0); -#endif -#endif -#endif - -#if defined (__386BSD__) - printf ("i386-pc-bsd\n"); exit (0); -#endif - -#if defined (sequent) -#if defined (i386) - printf ("i386-sequent-dynix\n"); exit (0); -#endif -#if defined (ns32000) - printf ("ns32k-sequent-dynix\n"); exit (0); -#endif -#endif - -#if defined (_SEQUENT_) - struct utsname un; - - uname(&un); - - if (strncmp(un.version, "V2", 2) == 0) { - printf ("i386-sequent-ptx2\n"); exit (0); - } - if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ - printf ("i386-sequent-ptx1\n"); exit (0); - } - printf ("i386-sequent-ptx\n"); exit (0); - -#endif - -#if defined (vax) -# if !defined (ultrix) -# include -# if defined (BSD) -# if BSD == 43 - printf ("vax-dec-bsd4.3\n"); exit (0); -# else -# if BSD == 199006 - printf ("vax-dec-bsd4.3reno\n"); exit (0); -# else - printf ("vax-dec-bsd\n"); exit (0); -# endif -# endif -# else - printf ("vax-dec-bsd\n"); exit (0); -# endif -# else - printf ("vax-dec-ultrix\n"); exit (0); -# endif -#endif - -#if defined (alliant) && defined (i860) - printf ("i860-alliant-bsd\n"); exit (0); -#endif - - exit (1); -} -EOF - -$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` && - { echo "$SYSTEM_NAME"; exit; } - -# Apollos put the system type in the environment. - -test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; } - -# Convex versions that predate uname can use getsysinfo(1) - -if [ -x /usr/convex/getsysinfo ] -then - case `getsysinfo -f cpu_type` in - c1*) - echo c1-convex-bsd - exit ;; - c2*) - if getsysinfo -f scalar_acc - then echo c32-convex-bsd - else echo c2-convex-bsd - fi - exit ;; - c34*) - echo c34-convex-bsd - exit ;; - c38*) - echo c38-convex-bsd - exit ;; - c4*) - echo c4-convex-bsd - exit ;; - esac -fi + x86_64:VMkernel:*:*) + echo ${UNAME_MACHINE}-unknown-esx + exit ;; +esac cat >&2 <. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). -# Please send patches to . Submit a context -# diff and a properly formatted GNU ChangeLog entry. +# Please send patches with a ChangeLog entry to config-patches@gnu.org. # # Configuration subroutine to validate and canonicalize a configuration type. # Supply the specified configuration type as an argument. # If it is invalid, we print an error message on stderr and exit with code 1. # Otherwise, we print the canonical config type on stdout and succeed. @@ -73,13 +66,11 @@ Report bugs and patches to ." version="\ GNU config.sub ($timestamp) -Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, -2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free -Software Foundation, Inc. +Copyright 1992-2014 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" @@ -123,17 +114,21 @@ # Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). # Here we must recognize all the valid KERNEL-OS combinations. maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` case $maybe_os in nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \ - linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ + linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ knetbsd*-gnu* | netbsd*-gnu* | \ kopensolaris*-gnu* | \ storm-chaos* | os2-emx* | rtmk-nova*) os=-$maybe_os basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` ;; + android-linux) + os=-linux-android + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown + ;; *) basic_machine=`echo $1 | sed 's/-[^-]*$//'` if [ $basic_machine != $1 ] then os=`echo $1 | sed 's/.*-/-/'` else os=; fi @@ -152,16 +147,16 @@ -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ - -apple | -axis | -knuth | -cray | -microblaze) + -apple | -axis | -knuth | -cray | -microblaze*) os= basic_machine=$1 ;; - -bluegene*) - os=-cnk + -bluegene*) + os=-cnk ;; -sim | -cisco | -oki | -wec | -winbond) os= basic_machine=$1 ;; @@ -173,14 +168,14 @@ ;; -chorusos*) os=-chorusos basic_machine=$1 ;; - -chorusrdb) - os=-chorusrdb + -chorusrdb) + os=-chorusrdb basic_machine=$1 - ;; + ;; -hiux*) os=-hiuxwe2 ;; -sco6) os=-sco5v6 @@ -221,10 +216,16 @@ basic_machine=clipper-intergraph ;; -isc*) basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; + -lynx*178) + os=-lynxos178 + ;; + -lynx*5) + os=-lynxos5 + ;; -lynx*) os=-lynxos ;; -ptx*) basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` @@ -245,24 +246,32 @@ case $basic_machine in # Recognize the basic CPU types without company name. # Some are omitted here because they have special meanings below. 1750a | 580 \ | a29k \ + | aarch64 | aarch64_be \ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ | am33_2.0 \ - | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr | avr32 \ + | arc | arceb \ + | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ + | avr | avr32 \ + | be32 | be64 \ | bfin \ - | c4x | clipper \ + | c4x | c8051 | clipper \ | d10v | d30v | dlx | dsp16xx \ + | epiphany \ | fido | fr30 | frv \ | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | hexagon \ | i370 | i860 | i960 | ia64 \ | ip2k | iq2000 \ + | k1om \ + | le32 | le64 \ | lm32 \ | m32c | m32r | m32rle | m68000 | m68k | m88k \ - | maxq | mb | microblaze | mcore | mep | metag \ + | maxq | mb | microblaze | microblazeel | mcore | mep | metag \ | mips | mipsbe | mipseb | mipsel | mipsle \ | mips16 \ | mips64 | mips64el \ | mips64octeon | mips64octeonel \ | mips64orion | mips64orionel \ @@ -272,38 +281,41 @@ | mips64vr4300 | mips64vr4300el \ | mips64vr5000 | mips64vr5000el \ | mips64vr5900 | mips64vr5900el \ | mipsisa32 | mipsisa32el \ | mipsisa32r2 | mipsisa32r2el \ + | mipsisa32r6 | mipsisa32r6el \ | mipsisa64 | mipsisa64el \ | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64r6 | mipsisa64r6el \ | mipsisa64sb1 | mipsisa64sb1el \ | mipsisa64sr71k | mipsisa64sr71kel \ + | mipsr5900 | mipsr5900el \ | mipstx39 | mipstx39el \ | mn10200 | mn10300 \ | moxie \ | mt \ | msp430 \ | nds32 | nds32le | nds32be \ - | nios | nios2 \ + | nios | nios2 | nios2eb | nios2el \ | ns16k | ns32k \ - | or32 \ + | open8 | or1k | or1knd | or32 \ | pdp10 | pdp11 | pj | pjl \ - | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ + | powerpc | powerpc64 | powerpc64le | powerpcle \ | pyramid \ - | rx \ + | rl78 | rx \ | score \ | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ | sh64 | sh64le \ | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \ | sparcv8 | sparcv9 | sparcv9b | sparcv9v \ - | spu | strongarm \ - | tahoe | thumb | tic4x | tic54x | tic55x | tic6x | tic80 | tron \ + | spu \ + | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \ | ubicom32 \ - | v850 | v850e \ + | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \ | we32k \ - | x86 | xc16x | xscale | xscalee[bl] | xstormy16 | xtensa \ + | x86 | xc16x | xstormy16 | xtensa \ | z8k | z80) basic_machine=$basic_machine-unknown ;; c54x) basic_machine=tic54x-unknown @@ -312,20 +324,34 @@ basic_machine=tic55x-unknown ;; c6x) basic_machine=tic6x-unknown ;; - m6811 | m68hc11 | m6812 | m68hc12 | picochip) - # Motorola 68HC11/12. + m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip) basic_machine=$basic_machine-unknown os=-none ;; m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) ;; ms1) basic_machine=mt-unknown ;; + + strongarm | thumb | xscale) + basic_machine=arm-unknown + ;; + xgate) + basic_machine=$basic_machine-unknown + os=-none + ;; + xscaleeb) + basic_machine=armeb-unknown + ;; + + xscaleel) + basic_machine=armel-unknown + ;; # We use `pc' rather than `unknown' # because (1) that's what they normally are, and # (2) the word "unknown" tends to confuse beginning users. i*86 | x86_64) @@ -337,29 +363,35 @@ exit 1 ;; # Recognize the basic CPU types with company name. 580-* \ | a29k-* \ + | aarch64-* | aarch64_be-* \ | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ - | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \ | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ | avr-* | avr32-* \ + | be32-* | be64-* \ | bfin-* | bs2000-* \ | c[123]* | c30-* | [cjt]90-* | c4x-* \ - | clipper-* | craynv-* | cydra-* \ + | c8051-* | clipper-* | craynv-* | cydra-* \ | d10v-* | d30v-* | dlx-* \ | elxsi-* \ | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ | h8300-* | h8500-* \ | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | hexagon-* \ | i*86-* | i860-* | i960-* | ia64-* \ | ip2k-* | iq2000-* \ + | k1om-* \ + | le32-* | le64-* \ | lm32-* \ | m32c-* | m32r-* | m32rle-* \ | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ - | m88110-* | m88k-* | maxq-* | mcore-* | metag-* | microblaze-* \ + | m88110-* | m88k-* | maxq-* | mcore-* | metag-* \ + | microblaze-* | microblazeel-* \ | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ | mips16-* \ | mips64-* | mips64el-* \ | mips64octeon-* | mips64octeonel-* \ | mips64orion-* | mips64orionel-* \ @@ -369,39 +401,45 @@ | mips64vr4300-* | mips64vr4300el-* \ | mips64vr5000-* | mips64vr5000el-* \ | mips64vr5900-* | mips64vr5900el-* \ | mipsisa32-* | mipsisa32el-* \ | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa32r6-* | mipsisa32r6el-* \ | mipsisa64-* | mipsisa64el-* \ | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64r6-* | mipsisa64r6el-* \ | mipsisa64sb1-* | mipsisa64sb1el-* \ | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipsr5900-* | mipsr5900el-* \ | mipstx39-* | mipstx39el-* \ | mmix-* \ | mt-* \ | msp430-* \ | nds32-* | nds32le-* | nds32be-* \ - | nios-* | nios2-* \ + | nios-* | nios2-* | nios2eb-* | nios2el-* \ | none-* | np1-* | ns16k-* | ns32k-* \ + | open8-* \ + | or1k*-* \ | orion-* \ | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ - | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \ | pyramid-* \ - | romp-* | rs6000-* | rx-* \ + | rl78-* | romp-* | rs6000-* | rx-* \ | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ | sparclite-* \ - | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | strongarm-* | sv1-* | sx?-* \ - | tahoe-* | thumb-* \ + | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx?-* \ + | tahoe-* \ | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ - | tile-* | tilegx-* \ + | tile*-* \ | tron-* \ | ubicom32-* \ - | v850-* | v850e-* | vax-* \ + | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \ + | vax-* \ | we32k-* \ - | x86-* | x86_64-* | xc16x-* | xps100-* | xscale-* | xscalee[bl]-* \ + | x86-* | x86_64-* | xc16x-* | xps100-* \ | xstormy16-* | xtensa*-* \ | ymp-* \ | z8k-* | z80-*) ;; # Recognize the basic CPU types without company name, with glob match. @@ -422,11 +460,11 @@ ;; a29khif) basic_machine=a29k-amd os=-udi ;; - abacus) + abacus) basic_machine=abacus-unknown ;; adobe68k) basic_machine=m68010-adobe os=-scout @@ -505,11 +543,11 @@ ;; c90) basic_machine=c90-cray os=-unicos ;; - cegcc) + cegcc) basic_machine=arm-unknown os=-cegcc ;; convex-c1) basic_machine=c1-convex @@ -537,11 +575,11 @@ ;; craynv) basic_machine=craynv-cray os=-unicosmp ;; - cr16) + cr16 | cr16-*) basic_machine=cr16-unknown os=-elf ;; crds | unos) basic_machine=m68k-crds @@ -695,11 +733,10 @@ os=-proelf ;; i370-ibm* | ibm*) basic_machine=i370-ibm ;; -# I'm not sure what "Sysv32" means. Should this be sysv3.2? i*86v32) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv32 ;; i*86v4*) @@ -753,15 +790,19 @@ ;; merlin) basic_machine=ns32k-utek os=-sysv ;; - microblaze) + microblaze*) basic_machine=microblaze-xilinx ;; + mingw64) + basic_machine=x86_64-pc + os=-mingw64 + ;; mingw32) - basic_machine=i386-pc + basic_machine=i686-pc os=-mingw32 ;; mingw32ce) basic_machine=arm-unknown os=-mingw32ce @@ -792,18 +833,22 @@ os=-msdos ;; ms1-*) basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'` ;; + msys) + basic_machine=i686-pc + os=-msys + ;; mvs) basic_machine=i370-ibm os=-mvs ;; - msys) - basic_machine=i386-pc - os=-msys - ;; + nacl) + basic_machine=le32-unknown + os=-nacl + ;; ncr3000) basic_machine=i486-ncr os=-sysv4 ;; netbsd386) @@ -864,14 +909,14 @@ os=-nonstopux ;; np1) basic_machine=np1-gould ;; - neo-tandem) + neo-tandem) basic_machine=neo-tandem ;; - nse-tandem) + nse-tandem) basic_machine=nse-tandem ;; nsr-tandem) basic_machine=nsr-tandem ;; @@ -952,13 +997,14 @@ pn) basic_machine=pn-gould ;; power) basic_machine=power-ibm ;; - ppc) basic_machine=powerpc-unknown + ppc | ppcbe) basic_machine=powerpc-unknown ;; - ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ppc-* | ppcbe-*) + basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppcle | powerpclittle | ppc-le | powerpc-little) basic_machine=powerpcle-unknown ;; ppcle-* | powerpclittle-*) @@ -979,11 +1025,15 @@ ;; pw32) basic_machine=i586-unknown os=-pw32 ;; - rdos) + rdos | rdos64) + basic_machine=x86_64-pc + os=-rdos + ;; + rdos32) basic_machine=i386-pc os=-rdos ;; rom68k) basic_machine=m68k-rom68k @@ -1048,10 +1098,13 @@ ;; stratus) basic_machine=i860-stratus os=-sysv4 ;; + strongarm-* | thumb-*) + basic_machine=arm-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; sun2) basic_machine=m68000-sun ;; sun2os3) basic_machine=m68000-sun @@ -1104,17 +1157,12 @@ ;; t90) basic_machine=t90-cray os=-unicos ;; - # This must be matched before tile*. - tilegx*) - basic_machine=tilegx-unknown - os=-linux-gnu - ;; tile*) - basic_machine=tile-unknown + basic_machine=$basic_machine-unknown os=-linux-gnu ;; tx39) basic_machine=mipstx39-unknown ;; @@ -1180,10 +1228,13 @@ os=-mingw32 ;; xps | xps100) basic_machine=xps100-honeywell ;; + xscale-* | xscalee[bl]-*) + basic_machine=`echo $basic_machine | sed 's/^xscale/arm/'` + ;; ymp) basic_machine=ymp-cray os=-unicos ;; z8k-*-coff) @@ -1277,15 +1328,15 @@ # Decode manufacturer-specific aliases for certain operating systems. if [ x"$os" != x"" ] then case $os in - # First match some system type aliases - # that might get confused with valid system types. + # First match some system type aliases + # that might get confused with valid system types. # -solaris* is a basic system type, with this one exception. - -auroraux) - os=-auroraux + -auroraux) + os=-auroraux ;; -solaris1 | -solaris1.*) os=`echo $os | sed -e 's|solaris1|sunos4|'` ;; -solaris) @@ -1305,33 +1356,33 @@ # Each alternative MUST END IN A *, to match a version number. # -sysv* is not here because it comes later, after sysvr4. -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\ | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \ - | -sym* | -kopensolaris* \ + | -sym* | -kopensolaris* | -plan9* \ | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ | -aos* | -aros* \ | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \ - | -openbsd* | -solidbsd* \ + | -bitrig* | -openbsd* | -solidbsd* \ | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ | -chorusos* | -chorusrdb* | -cegcc* \ | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ - | -mingw32* | -linux-gnu* | -linux-android* \ - | -linux-newlib* | -linux-uclibc* \ + | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \ + | -linux-newlib* | -linux-musl* | -linux-uclibc* \ | -uxpv* | -beos* | -mpeix* | -udk* \ | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ - | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es*) + | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* | -tirtos*) # Remember, each alternative MUST END IN *, to match a version number. ;; -qnx*) case $basic_machine in x86-* | i*86-*) @@ -1366,11 +1417,11 @@ os=`echo $os | sed -e 's|sunos6|solaris3|'` ;; -opened*) os=-openedition ;; - -os400*) + -os400*) os=-os400 ;; -wince*) os=-wince ;; @@ -1415,11 +1466,11 @@ os=`echo $os | sed -e 's|sinix|sysv|'` ;; -sinix*) os=-sysv4 ;; - -tpf*) + -tpf*) os=-tpf ;; -triton*) os=-sysv3 ;; @@ -1451,21 +1502,18 @@ os=-mint ;; -aros*) os=-aros ;; - -kaos*) - os=-kaos - ;; -zvmoe) os=-zvmoe ;; -dicos*) os=-dicos ;; - -nacl*) - ;; + -nacl*) + ;; -none) ;; *) # Get rid of the `-' at the beginning of $os. os=`echo $os | sed 's/[^-]*-//'` @@ -1484,14 +1532,14 @@ # that MANUFACTURER isn't an operating system. Otherwise, code above # will signal an error saying that MANUFACTURER isn't an operating # system, and we'll never get to this point. case $basic_machine in - score-*) + score-*) os=-elf ;; - spu-*) + spu-*) os=-elf ;; *-acorn) os=-riscix1.2 ;; @@ -1499,12 +1547,18 @@ os=-linux ;; arm*-semi) os=-aout ;; - c4x-* | tic4x-*) - os=-coff + c4x-* | tic4x-*) + os=-coff + ;; + c8051-*) + os=-elf + ;; + hexagon-*) + os=-elf ;; tic54x-*) os=-coff ;; tic55x-*) @@ -1529,18 +1583,15 @@ i386-sun) os=-sunos4.0.2 ;; m68000-sun) os=-sunos3 - # This also exists in the configure program, but was not the - # default. - # os=-sunos4 ;; m68*-cisco) os=-aout ;; - mep-*) + mep-*) os=-elf ;; mips*-cisco) os=-elf ;; @@ -1563,11 +1614,11 @@ os=-haiku ;; *-ibm) os=-aix ;; - *-knuth) + *-knuth) os=-mmixware ;; *-wec) os=-proelf ;; Index: autosetup/jimsh0.c ================================================================== --- autosetup/jimsh0.c +++ autosetup/jimsh0.c @@ -1,6 +1,6 @@ -/* This is single source file, bootstrap version of Jim Tcl. See http://jim.berlios.de/ */ +/* This is single source file, bootstrap version of Jim Tcl. See http://jim.tcl.tk/ */ #define _GNU_SOURCE #define JIM_TCL_COMPAT #define JIM_REFERENCES #define JIM_ANSIC #define JIM_REGEXP @@ -38,18 +38,25 @@ #define TCL_PLATFORM_PLATFORM "unix" #define TCL_PLATFORM_PATH_SEPARATOR ":" #define HAVE_VFORK #define HAVE_WAITPID #define HAVE_ISATTY +#define HAVE_MKSTEMP +#define HAVE_LINK #define HAVE_SYS_TIME_H #define HAVE_DIRENT_H #define HAVE_UNISTD_H #endif +#define JIM_VERSION 76 #ifndef JIM_WIN32COMPAT_H #define JIM_WIN32COMPAT_H + +#ifdef __cplusplus +extern "C" { +#endif #if defined(_WIN32) || defined(WIN32) #define HAVE_DLOPEN @@ -105,17 +112,32 @@ } DIR; DIR *opendir(const char *name); int closedir(DIR *dir); struct dirent *readdir(DIR *dir); -#endif + +#elif defined(__MINGW32__) + +#include +#define strtod __strtod + +#endif #endif + +#ifdef __cplusplus +} +#endif #endif #ifndef UTF8_UTIL_H #define UTF8_UTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + #define MAX_UTF8_LEN 4 int utf8_fromunicode(char *p, unsigned uc); @@ -124,10 +146,11 @@ #include #define utf8_strlen(S, B) ((B) < 0 ? strlen(S) : (B)) #define utf8_tounicode(S, CP) (*(CP) = (unsigned char)*(S), 1) +#define utf8_getchars(CP, C) (*(CP) = (C), 1) #define utf8_upper(C) toupper(C) #define utf8_title(C) toupper(C) #define utf8_lower(C) tolower(C) #define utf8_index(C, I) (I) #define utf8_charlen(C) 1 @@ -134,10 +157,14 @@ #define utf8_prev_len(S, L) 1 #else #endif + +#ifdef __cplusplus +} +#endif #endif #ifndef __JIM__H #define __JIM__H @@ -185,12 +212,10 @@ #endif #define UCHAR(c) ((unsigned char)(c)) -#define JIM_VERSION 74 - #define JIM_OK 0 #define JIM_ERR 1 #define JIM_RETURN 2 #define JIM_BREAK 3 #define JIM_CONTINUE 4 @@ -200,44 +225,34 @@ #define JIM_EVAL 7 #define JIM_MAX_CALLFRAME_DEPTH 1000 #define JIM_MAX_EVAL_DEPTH 2000 -#define JIM_NONE 0 -#define JIM_ERRMSG 1 - -#define JIM_UNSHARED 4 -#define JIM_MUSTEXIST 8 - - -#define JIM_GLOBAL_ONLY 0x100 + +#define JIM_PRIV_FLAG_SHIFT 20 + +#define JIM_NONE 0 +#define JIM_ERRMSG 1 +#define JIM_ENUM_ABBREV 2 +#define JIM_UNSHARED 4 +#define JIM_MUSTEXIST 8 #define JIM_SUBST_NOVAR 1 #define JIM_SUBST_NOCMD 2 #define JIM_SUBST_NOESC 4 #define JIM_SUBST_FLAG 128 - -#define JIM_NOTUSED(V) ((void) V) - - -#define JIM_ENUM_ABBREV 2 - #define JIM_CASESENS 0 #define JIM_NOCASE 1 #define JIM_PATH_LEN 1024 -#ifdef JIM_CRLF -#define JIM_NL "\r\n" -#else -#define JIM_NL "\n" -#endif +#define JIM_NOTUSED(V) ((void) V) #define JIM_LIBPATH "auto_path" #define JIM_INTERACTIVE "tcl_interactive" @@ -267,21 +282,22 @@ } Jim_HashTableType; typedef struct Jim_HashTable { Jim_HashEntry **table; const Jim_HashTableType *type; + void *privdata; unsigned int size; unsigned int sizemask; unsigned int used; unsigned int collisions; - void *privdata; + unsigned int uniq; } Jim_HashTable; typedef struct Jim_HashTableIterator { Jim_HashTable *ht; - int index; Jim_HashEntry *entry, *nextEntry; + int index; } Jim_HashTableIterator; #define JIM_HT_INITIAL_SIZE 16 @@ -290,35 +306,35 @@ if ((ht)->type->valDestructor) \ (ht)->type->valDestructor((ht)->privdata, (entry)->u.val) #define Jim_SetHashVal(ht, entry, _val_) do { \ if ((ht)->type->valDup) \ - entry->u.val = (ht)->type->valDup((ht)->privdata, _val_); \ + (entry)->u.val = (ht)->type->valDup((ht)->privdata, (_val_)); \ else \ - entry->u.val = (_val_); \ + (entry)->u.val = (_val_); \ } while(0) #define Jim_FreeEntryKey(ht, entry) \ if ((ht)->type->keyDestructor) \ (ht)->type->keyDestructor((ht)->privdata, (entry)->key) #define Jim_SetHashKey(ht, entry, _key_) do { \ if ((ht)->type->keyDup) \ - entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ + (entry)->key = (ht)->type->keyDup((ht)->privdata, (_key_)); \ else \ - entry->key = (void *)(_key_); \ + (entry)->key = (void *)(_key_); \ } while(0) #define Jim_CompareHashKeys(ht, key1, key2) \ (((ht)->type->keyCompare) ? \ - (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ + (ht)->type->keyCompare((ht)->privdata, (key1), (key2)) : \ (key1) == (key2)) -#define Jim_HashKey(ht, key) (ht)->type->hashFunction(key) +#define Jim_HashKey(ht, key) ((ht)->type->hashFunction(key) + (ht)->uniq) #define Jim_GetHashEntryKey(he) ((he)->key) -#define Jim_GetHashEntryVal(he) ((he)->val) +#define Jim_GetHashEntryVal(he) ((he)->u.val) #define Jim_GetHashTableCollisions(ht) ((ht)->collisions) #define Jim_GetHashTableSize(ht) ((ht)->size) #define Jim_GetHashTableUsed(ht) ((ht)->used) @@ -342,19 +358,19 @@ void *ptr1; void *ptr2; } twoPtrValue; struct { - unsigned long callFrameId; struct Jim_Var *varPtr; + unsigned long callFrameId; int global; } varValue; struct { - unsigned long procEpoch; struct Jim_Obj *nsObj; struct Jim_Cmd *cmdPtr; + unsigned long procEpoch; } cmdValue; struct { struct Jim_Obj **ele; int len; @@ -380,12 +396,12 @@ struct Jim_Obj *varNameObjPtr; struct Jim_Obj *indexObjPtr; } dictSubstValue; struct { - unsigned flags; void *compre; + unsigned flags; } regexpValue; struct { int line; int argc; } scriptLineValue; @@ -454,21 +470,24 @@ struct Jim_CallFrame *next; Jim_Obj *nsObj; Jim_Obj *fileNameObj; int line; Jim_Stack *localCommands; + int tailcall; + struct Jim_Obj *tailcallObj; + struct Jim_Cmd *tailcallCmd; } Jim_CallFrame; typedef struct Jim_Var { Jim_Obj *objPtr; struct Jim_CallFrame *linkFramePtr; } Jim_Var; -typedef int (*Jim_CmdProc)(struct Jim_Interp *interp, int argc, +typedef int Jim_CmdProc(struct Jim_Interp *interp, int argc, Jim_Obj *const *argv); -typedef void (*Jim_DelCmdProc)(struct Jim_Interp *interp, void *privData); +typedef void Jim_DelCmdProc(struct Jim_Interp *interp, void *privData); typedef struct Jim_Cmd { int inUse; @@ -475,12 +494,12 @@ int isproc; struct Jim_Cmd *prevCmd; union { struct { - Jim_CmdProc cmdProc; - Jim_DelCmdProc delProc; + Jim_CmdProc *cmdProc; + Jim_DelCmdProc *delProc; void *privData; } native; struct { Jim_Obj *argListObjPtr; @@ -590,15 +609,11 @@ Jim_Obj *finalizerCmdNamePtr; char tag[JIM_REFERENCE_TAGLEN+1]; } Jim_Reference; - #define Jim_NewEmptyStringObj(i) Jim_NewStringObj(i, "", 0) - - - #define Jim_FreeHashTableIterator(iter) Jim_Free(iter) #define JIM_EXPORT @@ -609,10 +624,11 @@ JIM_EXPORT char *Jim_StrDupLen(const char *s, int l); JIM_EXPORT char **Jim_GetEnviron(void); JIM_EXPORT void Jim_SetEnviron(char **env); +JIM_EXPORT int Jim_MakeTempFile(Jim_Interp *interp, const char *template); JIM_EXPORT int Jim_Eval(Jim_Interp *interp, const char *script); @@ -740,13 +756,12 @@ JIM_EXPORT int Jim_SetVariableStrWithStr (Jim_Interp *interp, const char *name, const char *val); JIM_EXPORT int Jim_SetVariableLink (Jim_Interp *interp, Jim_Obj *nameObjPtr, Jim_Obj *targetNameObjPtr, Jim_CallFrame *targetCallFrame); -JIM_EXPORT int Jim_CreateNamespaceVariable(Jim_Interp *interp, - Jim_Obj *varNameObj, Jim_Obj *targetNameObj); -JIM_EXPORT int Jim_DiscardNamespaceVars(Jim_Interp *interp); +JIM_EXPORT Jim_Obj * Jim_MakeGlobalNamespaceName(Jim_Interp *interp, + Jim_Obj *nameObjPtr); JIM_EXPORT Jim_Obj * Jim_GetVariable (Jim_Interp *interp, Jim_Obj *nameObjPtr, int flags); JIM_EXPORT Jim_Obj * Jim_GetGlobalVariable (Jim_Interp *interp, Jim_Obj *nameObjPtr, int flags); JIM_EXPORT Jim_Obj * Jim_GetVariableStr (Jim_Interp *interp, @@ -805,10 +820,11 @@ JIM_EXPORT int Jim_DictAddElement(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *keyObjPtr, Jim_Obj *valueObjPtr); JIM_EXPORT int Jim_DictKeys(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *patternObj); JIM_EXPORT int Jim_DictValues(Jim_Interp *interp, Jim_Obj *dictObjPtr, Jim_Obj *patternObjPtr); JIM_EXPORT int Jim_DictSize(Jim_Interp *interp, Jim_Obj *objPtr); +JIM_EXPORT int Jim_DictInfo(Jim_Interp *interp, Jim_Obj *objPtr); JIM_EXPORT int Jim_GetReturnCode (Jim_Interp *interp, Jim_Obj *objPtr, int *intPtr); @@ -832,16 +848,10 @@ double *doublePtr); JIM_EXPORT void Jim_SetDouble(Jim_Interp *interp, Jim_Obj *objPtr, double doubleValue); JIM_EXPORT Jim_Obj * Jim_NewDoubleObj(Jim_Interp *interp, double doubleValue); - -JIM_EXPORT const char * Jim_GetSharedString (Jim_Interp *interp, - const char *str); -JIM_EXPORT void Jim_ReleaseSharedString (Jim_Interp *interp, - const char *str); - JIM_EXPORT void Jim_WrongNumArgs (Jim_Interp *interp, int argc, Jim_Obj *const *argv, const char *msg); JIM_EXPORT int Jim_GetEnum (Jim_Interp *interp, Jim_Obj *objPtr, const char * const *tablePtr, int *indexPtr, const char *name, int flags); @@ -875,20 +885,20 @@ JIM_EXPORT void Jim_HistoryShow(void); JIM_EXPORT int Jim_InitStaticExtensions(Jim_Interp *interp); JIM_EXPORT int Jim_StringToWide(const char *str, jim_wide *widePtr, int base); -JIM_EXPORT int Jim_CheckSignal(Jim_Interp *interp); +JIM_EXPORT int Jim_IsBigEndian(void); + #define Jim_CheckSignal(i) ((i)->signal_level && (i)->sigmask) JIM_EXPORT int Jim_LoadLibrary(Jim_Interp *interp, const char *pathName); JIM_EXPORT void Jim_FreeLoadHandles(Jim_Interp *interp); JIM_EXPORT FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command); - JIM_EXPORT int Jim_IsDict(Jim_Obj *objPtr); JIM_EXPORT int Jim_IsList(Jim_Obj *objPtr); @@ -910,16 +920,16 @@ #define JIM_MODFLAG_HIDDEN 0x0001 #define JIM_MODFLAG_FULLARGV 0x0002 -typedef int tclmod_cmd_function(Jim_Interp *interp, int argc, Jim_Obj *const *argv); +typedef int jim_subcmd_function(Jim_Interp *interp, int argc, Jim_Obj *const *argv); typedef struct { const char *cmd; const char *args; - tclmod_cmd_function *function; + jim_subcmd_function *function; short minargs; short maxargs; unsigned short flags; } jim_subcmd_type; @@ -936,22 +946,16 @@ #endif #ifndef JIMREGEXP_H #define JIMREGEXP_H -#ifndef _JIMAUTOCONF_H -#error Need jimautoconf.h + +#ifdef __cplusplus +extern "C" { #endif -#if defined(HAVE_REGCOMP) && !defined(JIM_REGEXP) - -#include - -#else - #include - typedef struct { int rm_so; int rm_eo; } regmatch_t; @@ -1020,10 +1024,12 @@ int regcomp(regex_t *preg, const char *regex, int cflags); int regexec(regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags); size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size); void regfree(regex_t *preg); +#ifdef __cplusplus +} #endif #endif int Jim_bootstrapInit(Jim_Interp *interp) { @@ -1045,29 +1051,48 @@ "\n" "\n" "\n" "proc _jimsh_init {} {\n" " rename _jimsh_init {}\n" +" global jim::exe jim::argv0 tcl_interactive auto_path tcl_platform\n" "\n" "\n" -" lappend p {*}[split [env JIMLIB {}] $::tcl_platform(pathSeparator)]\n" -" lappend p {*}$::auto_path\n" -" lappend p [file dirname [info nameofexecutable]]\n" -" set ::auto_path $p\n" +" if {[exists jim::argv0]} {\n" +" if {[string match \"*/*\" $jim::argv0]} {\n" +" set jim::exe [file join [pwd] $jim::argv0]\n" +" } else {\n" +" foreach path [split [env PATH \"\"] $tcl_platform(pathSeparator)] {\n" +" set exec [file join [pwd] [string map {\\\\ /} $path] $jim::argv0]\n" +" if {[file executable $exec]} {\n" +" set jim::exe $exec\n" +" break\n" +" }\n" +" }\n" +" }\n" +" }\n" "\n" -" if {$::tcl_interactive && [env HOME {}] ne \"\"} {\n" +"\n" +" lappend p {*}[split [env JIMLIB {}] $tcl_platform(pathSeparator)]\n" +" if {[exists jim::exe]} {\n" +" lappend p [file dirname $jim::exe]\n" +" }\n" +" lappend p {*}$auto_path\n" +" set auto_path $p\n" +"\n" +" if {$tcl_interactive && [env HOME {}] ne \"\"} {\n" " foreach src {.jimrc jimrc.tcl} {\n" " if {[file exists [env HOME]/$src]} {\n" " uplevel #0 source [env HOME]/$src\n" " break\n" " }\n" " }\n" " }\n" +" return \"\"\n" "}\n" "\n" "if {$tcl_platform(platform) eq \"windows\"} {\n" -" set jim_argv0 [string map {\\\\ /} $jim_argv0]\n" +" set jim::argv0 [string map {\\\\ /} $jim::argv0]\n" "}\n" "\n" "_jimsh_init\n" ); } @@ -1086,10 +1111,15 @@ "\n" "package require readdir\n" "\n" "\n" "proc glob.globdir {dir pattern} {\n" +" if {[file exists $dir/$pattern]} {\n" +"\n" +" return [list $pattern]\n" +" }\n" +"\n" " set result {}\n" " set files [readdir $dir]\n" " lappend files . ..\n" "\n" " foreach name $files {\n" @@ -1145,11 +1175,11 @@ " }\n" "\n" " foreach old $oldexp {\n" " lappend newexp $old$suf\n" " }\n" -" linsert $newexp 0 $rest\n" +" list $rest {*}$newexp\n" "}\n" "\n" "\n" "\n" "proc glob.glob {base pattern} {\n" @@ -1192,10 +1222,11 @@ "\n" "\n" "proc glob {args} {\n" " set nocomplain 0\n" " set base \"\"\n" +" set tails 0\n" "\n" " set n 0\n" " foreach arg $args {\n" " if {[info exists param]} {\n" " set $param $arg\n" @@ -1209,21 +1240,20 @@ " set param base\n" " }\n" " -n* {\n" " set nocomplain 1\n" " }\n" -" -t* {\n" -"\n" -" }\n" -"\n" -" -* {\n" -" return -code error \"bad option \\\"$switch\\\": must be -directory, -nocomplain, -tails, or --\"\n" +" -ta* {\n" +" set tails 1\n" " }\n" " -- {\n" " incr n\n" " break\n" " }\n" +" -* {\n" +" return -code error \"bad option \\\"$arg\\\": must be -directory, -nocomplain, -tails, or --\"\n" +" }\n" " * {\n" " break\n" " }\n" " }\n" " incr n\n" @@ -1237,29 +1267,35 @@ "\n" " set args [lrange $args $n end]\n" "\n" " set result {}\n" " foreach pattern $args {\n" -" set pattern [string map {\n" +" set escpattern [string map {\n" " \\\\\\\\ \\x01 \\\\\\{ \\x02 \\\\\\} \\x03 \\\\, \\x04\n" " } $pattern]\n" -" set patexps [lassign [glob.explode $pattern] rest]\n" +" set patexps [lassign [glob.explode $escpattern] rest]\n" " if {$rest ne \"\"} {\n" " return -code error \"unmatched close brace in glob pattern\"\n" " }\n" " foreach patexp $patexps {\n" " set patexp [string map {\n" " \\x01 \\\\\\\\ \\x02 \\{ \\x03 \\} \\x04 ,\n" " } $patexp]\n" " foreach {realname name} [glob.glob $base $patexp] {\n" -" lappend result $name\n" +" incr n\n" +" if {$tails} {\n" +" lappend result $name\n" +" } else {\n" +" lappend result [file join $base $name]\n" +" }\n" " }\n" " }\n" " }\n" "\n" " if {!$nocomplain && [llength $result] == 0} {\n" -" return -code error \"no files matched glob patterns\"\n" +" set s $(([llength $args] > 1) ? \"s\" : \"\")\n" +" return -code error \"no files matched glob pattern$s \\\"[join $args]\\\"\"\n" " }\n" "\n" " return $result\n" "}\n" ); @@ -1268,10 +1304,12 @@ { if (Jim_PackageProvide(interp, "stdlib", "1.0", JIM_ERRMSG)) return JIM_ERR; return Jim_EvalSource(interp, "stdlib.tcl", 1, +"\n" +"\n" "\n" "proc lambda {arglist args} {\n" " tailcall proc [ref {} function lambda.finalizer] $arglist {*}$args\n" "}\n" "\n" @@ -1297,50 +1335,52 @@ "}\n" "\n" "\n" "\n" "\n" -"proc stacktrace {} {\n" +"proc stacktrace {{skip 0}} {\n" " set trace {}\n" -" foreach level [range 1 [info level]] {\n" -" lassign [info frame -$level] p f l\n" -" lappend trace $p $f $l\n" +" incr skip\n" +" foreach level [range $skip [info level]] {\n" +" lappend trace {*}[info frame -$level]\n" " }\n" " return $trace\n" "}\n" "\n" "\n" "proc stackdump {stacktrace} {\n" -" set result {}\n" -" set count 0\n" +" set lines {}\n" " foreach {l f p} [lreverse $stacktrace] {\n" -" if {$count} {\n" -" append result \\n\n" -" }\n" -" incr count\n" +" set line {}\n" " if {$p ne \"\"} {\n" -" append result \"in procedure '$p' \"\n" +" append line \"in procedure '$p' \"\n" " if {$f ne \"\"} {\n" -" append result \"called \"\n" +" append line \"called \"\n" " }\n" " }\n" " if {$f ne \"\"} {\n" -" append result \"at file \\\"$f\\\", line $l\"\n" +" append line \"at file \\\"$f\\\", line $l\"\n" +" }\n" +" if {$line ne \"\"} {\n" +" lappend lines $line\n" " }\n" " }\n" -" return $result\n" +" join $lines \\n\n" "}\n" "\n" "\n" "\n" "proc errorInfo {msg {stacktrace \"\"}} {\n" " if {$stacktrace eq \"\"} {\n" +"\n" " set stacktrace [info stacktrace]\n" +"\n" +" lappend stacktrace {*}[stacktrace 1]\n" " }\n" " lassign $stacktrace p f l\n" " if {$f ne \"\"} {\n" -" set result \"Runtime Error: $f:$l: \"\n" +" set result \"$f:$l: Error: \"\n" " }\n" " append result \"$msg\\n\"\n" " append result [stackdump $stacktrace]\n" "\n" "\n" @@ -1348,40 +1388,52 @@ "}\n" "\n" "\n" "\n" "proc {info nameofexecutable} {} {\n" -" if {[info exists ::jim_argv0]} {\n" -" if {[string match \"*/*\" $::jim_argv0]} {\n" -" return [file join [pwd] $::jim_argv0]\n" -" }\n" -" foreach path [split [env PATH \"\"] $::tcl_platform(pathSeparator)] {\n" -" set exec [file join [pwd] [string map {\\\\ /} $path] $::jim_argv0]\n" -" if {[file executable $exec]} {\n" -" return $exec\n" -" }\n" -" }\n" +" if {[exists ::jim::exe]} {\n" +" return $::jim::exe\n" " }\n" -" return \"\"\n" "}\n" "\n" "\n" -"proc {dict with} {dictVar args script} {\n" -" upvar $dictVar dict\n" +"proc {dict with} {&dictVar {args key} script} {\n" " set keys {}\n" -" foreach {n v} [dict get $dict {*}$args] {\n" +" foreach {n v} [dict get $dictVar {*}$key] {\n" " upvar $n var_$n\n" " set var_$n $v\n" " lappend keys $n\n" " }\n" " catch {uplevel 1 $script} msg opts\n" -" if {[info exists dict] && [dict exists $dict {*}$args]} {\n" +" if {[info exists dictVar] && ([llength $key] == 0 || [dict exists $dictVar {*}$key])} {\n" " foreach n $keys {\n" " if {[info exists var_$n]} {\n" -" dict set dict {*}$args $n [set var_$n]\n" +" dict set dictVar {*}$key $n [set var_$n]\n" +" } else {\n" +" dict unset dictVar {*}$key $n\n" +" }\n" +" }\n" +" }\n" +" return {*}$opts $msg\n" +"}\n" +"\n" +"\n" +"proc {dict update} {&varName args script} {\n" +" set keys {}\n" +" foreach {n v} $args {\n" +" upvar $v var_$v\n" +" if {[dict exists $varName $n]} {\n" +" set var_$v [dict get $varName $n]\n" +" }\n" +" }\n" +" catch {uplevel 1 $script} msg opts\n" +" if {[info exists varName]} {\n" +" foreach {n v} $args {\n" +" if {[info exists var_$v]} {\n" +" dict set varName $n [set var_$v]\n" " } else {\n" -" dict unset dict {*}$args $n\n" +" dict unset varName $n\n" " }\n" " }\n" " }\n" " return {*}$opts $msg\n" "}\n" @@ -1396,10 +1448,69 @@ " dict set dict $k $v\n" " }\n" " }\n" " return $dict\n" "}\n" +"\n" +"proc {dict replace} {dictionary {args {key value}}} {\n" +" if {[llength ${key value}] % 2} {\n" +" tailcall {dict replace}\n" +" }\n" +" tailcall dict merge $dictionary ${key value}\n" +"}\n" +"\n" +"\n" +"proc {dict lappend} {varName key {args value}} {\n" +" upvar $varName dict\n" +" if {[exists dict] && [dict exists $dict $key]} {\n" +" set list [dict get $dict $key]\n" +" }\n" +" lappend list {*}$value\n" +" dict set dict $key $list\n" +"}\n" +"\n" +"\n" +"proc {dict append} {varName key {args value}} {\n" +" upvar $varName dict\n" +" if {[exists dict] && [dict exists $dict $key]} {\n" +" set str [dict get $dict $key]\n" +" }\n" +" append str {*}$value\n" +" dict set dict $key $str\n" +"}\n" +"\n" +"\n" +"proc {dict incr} {varName key {increment 1}} {\n" +" upvar $varName dict\n" +" if {[exists dict] && [dict exists $dict $key]} {\n" +" set value [dict get $dict $key]\n" +" }\n" +" incr value $increment\n" +" dict set dict $key $value\n" +"}\n" +"\n" +"\n" +"proc {dict remove} {dictionary {args key}} {\n" +" foreach k $key {\n" +" dict unset dictionary $k\n" +" }\n" +" return $dictionary\n" +"}\n" +"\n" +"\n" +"proc {dict values} {dictionary {pattern *}} {\n" +" dict keys [lreverse $dictionary] $pattern\n" +"}\n" +"\n" +"\n" +"proc {dict for} {vars dictionary script} {\n" +" if {[llength $vars] != 2} {\n" +" return -code error \"must have exactly two variable names\"\n" +" }\n" +" dict size $dictionary\n" +" tailcall foreach $vars $dictionary $script\n" +"}\n" ); } int Jim_tclcompatInit(Jim_Interp *interp) { if (Jim_PackageProvide(interp, "tclcompat", "1.0", JIM_ERRMSG)) @@ -1410,12 +1521,14 @@ "\n" "\n" "\n" "\n" "\n" +"\n" "\n" "set env [env]\n" +"\n" "\n" "if {[info commands stdout] ne \"\"} {\n" "\n" " foreach p {gets flush close eof seek tell} {\n" " proc $p {chan args} {p} {\n" @@ -1462,51 +1575,10 @@ " }\n" " }\n" " }\n" "}\n" "\n" -"\n" -"proc case {var args} {\n" -"\n" -" if {[lindex $args 0] eq \"in\"} {\n" -" set args [lrange $args 1 end]\n" -" }\n" -"\n" -"\n" -" if {[llength $args] == 1} {\n" -" set args [lindex $args 0]\n" -" }\n" -"\n" -"\n" -" if {[llength $args] % 2 != 0} {\n" -" return -code error \"extra case pattern with no body\"\n" -" }\n" -"\n" -"\n" -" local proc case.checker {value pattern} {\n" -" string match $pattern $value\n" -" }\n" -"\n" -" foreach {value action} $args {\n" -" if {$value eq \"default\"} {\n" -" set do_action $action\n" -" continue\n" -" } elseif {[lsearch -bool -command case.checker $value $var]} {\n" -" set do_action $action\n" -" break\n" -" }\n" -" }\n" -"\n" -" if {[info exists do_action]} {\n" -" set rc [catch [list uplevel 1 $do_action] result opts]\n" -" if {$rc} {\n" -" incr opts(-level)\n" -" }\n" -" return {*}$opts $result\n" -" }\n" -"}\n" -"\n" "\n" "proc fileevent {args} {\n" " tailcall {*}$args\n" "}\n" "\n" @@ -1534,17 +1606,29 @@ " try {\n" " if {$force ni {{} -force}} {\n" " error \"bad option \\\"$force\\\": should be -force\"\n" " }\n" "\n" -" set in [open $source]\n" +" set in [open $source rb]\n" +"\n" +" if {[file exists $target]} {\n" +" if {$force eq \"\"} {\n" +" error \"error copying \\\"$source\\\" to \\\"$target\\\": file already exists\"\n" +" }\n" +"\n" +" if {$source eq $target} {\n" +" return\n" +" }\n" +"\n" "\n" -" if {$force eq \"\" && [file exists $target]} {\n" -" $in close\n" -" error \"error copying \\\"$source\\\" to \\\"$target\\\": file already exists\"\n" +" file stat $source ss\n" +" file stat $target ts\n" +" if {$ss(dev) == $ts(dev) && $ss(ino) == $ts(ino) && $ss(ino)} {\n" +" return\n" +" }\n" " }\n" -" set out [open $target w]\n" +" set out [open $target wb]\n" " $in copyto $out\n" " $out close\n" " } on error {msg opts} {\n" " incr opts(-level)\n" " return {*}$opts $msg\n" @@ -1587,18 +1671,18 @@ " error $error\n" " }\n" "}\n" "\n" "\n" -"local proc pid {{chan {}}} {\n" -" if {$chan eq \"\"} {\n" +"local proc pid {{channelId {}}} {\n" +" if {$channelId eq \"\"} {\n" " tailcall upcall pid\n" " }\n" -" if {[catch {$chan tell}]} {\n" -" return -code error \"can not find channel named \\\"$chan\\\"\"\n" +" if {[catch {$channelId tell}]} {\n" +" return -code error \"can not find channel named \\\"$channelId\\\"\"\n" " }\n" -" if {[catch {$chan pid} pids]} {\n" +" if {[catch {$channelId pid} pids]} {\n" " return \"\"\n" " }\n" " return $pids\n" "}\n" "\n" @@ -1626,11 +1710,11 @@ " }\n" " if {[llength $args] == 0} {\n" " return -code error {wrong # args: should be \"try ?options? script ?argument ...?\"}\n" " }\n" " set args [lassign $args script]\n" -" set code [catch -eval {*}$catchopts [list uplevel 1 $script] msg opts]\n" +" set code [catch -eval {*}$catchopts {uplevel 1 $script} msg opts]\n" "\n" " set handled 0\n" "\n" " foreach {on codes vars script} $args {\n" " switch -- $on \\\n" @@ -1644,16 +1728,16 @@ " if {$optsvar ne \"\"} {\n" " upvar $optsvar hopts\n" " set hopts $opts\n" " }\n" "\n" -" set code [catch [list uplevel 1 $script] msg opts]\n" +" set code [catch {uplevel 1 $script} msg opts]\n" " incr handled\n" " }\n" " } \\\n" " finally {\n" -" set finalcode [catch [list uplevel 1 $codes] finalmsg finalopts]\n" +" set finalcode [catch {uplevel 1 $codes} finalmsg finalopts]\n" " if {$finalcode} {\n" "\n" " set code $finalcode\n" " set msg $finalmsg\n" " set opts $finalopts\n" @@ -1687,23 +1771,25 @@ "}\n" ); } - #include #include #include #include +#ifdef HAVE_UNISTD_H +#include +#include +#endif #if defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SELECT) && defined(HAVE_NETINET_IN_H) && defined(HAVE_NETDB_H) && defined(HAVE_ARPA_INET_H) #include #include #include #include -#include #ifdef HAVE_SYS_UN_H #include #endif #else #define JIM_ANSIC @@ -1735,15 +1821,12 @@ typedef struct AioFile { FILE *fp; Jim_Obj *filename; int type; - int OpenFlags; + int openFlags; int fd; -#ifdef O_NDELAY - int flags; -#endif Jim_Obj *rEvent; Jim_Obj *wEvent; Jim_Obj *eEvent; int addr_family; } AioFile; @@ -1767,28 +1850,21 @@ { AioFile *af = privData; JIM_NOTUSED(interp); - if (!(af->OpenFlags & AIO_KEEPOPEN)) { - fclose(af->fp); - } - Jim_DecrRefCount(interp, af->filename); #ifdef jim_ext_eventloop - if (af->rEvent) { - Jim_DeleteFileHandler(interp, af->fp); - } - if (af->wEvent) { - Jim_DeleteFileHandler(interp, af->fp); - } - if (af->eEvent) { - Jim_DeleteFileHandler(interp, af->fp); - } -#endif + Jim_DeleteFileHandler(interp, af->fp, JIM_EVENT_READABLE | JIM_EVENT_WRITABLE | JIM_EVENT_EXCEPTION); +#endif + + if (!(af->openFlags & AIO_KEEPOPEN)) { + fclose(af->fp); + } + Jim_Free(af); } static int JimCheckStreamError(Jim_Interp *interp, AioFile *af) { @@ -2035,12 +2111,31 @@ return JIM_OK; } static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - Jim_DeleteCommand(interp, Jim_String(argv[0])); - return JIM_OK; + if (argc == 3) { +#if !defined(JIM_ANSIC) && defined(HAVE_SHUTDOWN) + static const char * const options[] = { "r", "w", NULL }; + enum { OPT_R, OPT_W, }; + int option; + AioFile *af = Jim_CmdPrivData(interp); + + if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + if (shutdown(af->fd, option == OPT_R ? SHUT_RD : SHUT_WR) == 0) { + return JIM_OK; + } + JimAioSetError(interp, NULL); +#else + Jim_SetResultString(interp, "async close not supported", -1); +#endif + return JIM_ERR; + } + + return Jim_DeleteCommand(interp, Jim_String(argv[0])); } static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { AioFile *af = Jim_CmdPrivData(interp); @@ -2087,11 +2182,11 @@ #ifdef O_NDELAY static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { AioFile *af = Jim_CmdPrivData(interp); - int fmode = af->flags; + int fmode = fcntl(af->fd, F_GETFL); if (argc) { long nb; if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) { @@ -2101,12 +2196,11 @@ fmode |= O_NDELAY; } else { fmode &= ~O_NDELAY; } - fcntl(af->fd, F_SETFL, fmode); - af->flags = fmode; + (void)fcntl(af->fd, F_SETFL, fmode); } Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0); return JIM_OK; } #endif @@ -2147,27 +2241,26 @@ } #ifdef jim_ext_eventloop static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData) { - Jim_Obj *objPtr = clientData; + Jim_Obj **objPtrPtr = clientData; - Jim_DecrRefCount(interp, objPtr); + Jim_DecrRefCount(interp, *objPtrPtr); + *objPtrPtr = NULL; } static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask) { - Jim_Obj *objPtr = clientData; + Jim_Obj **objPtrPtr = clientData; - return Jim_EvalObjBackground(interp, objPtr); + return Jim_EvalObjBackground(interp, *objPtrPtr); } static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptHandlerObj, int argc, Jim_Obj * const *argv) { - int scriptlen = 0; - if (argc == 0) { if (*scriptHandlerObj) { Jim_SetResult(interp, *scriptHandlerObj); } @@ -2174,27 +2267,25 @@ return JIM_OK; } if (*scriptHandlerObj) { - Jim_DeleteFileHandler(interp, af->fp); - *scriptHandlerObj = NULL; + Jim_DeleteFileHandler(interp, af->fp, mask); } - Jim_GetString(argv[0], &scriptlen); - if (scriptlen == 0) { + if (Jim_Length(argv[0]) == 0) { return JIM_OK; } Jim_IncrRefCount(argv[0]); *scriptHandlerObj = argv[0]; Jim_CreateFileHandler(interp, af->fp, mask, - JimAioFileEventHandler, *scriptHandlerObj, JimAioFileEventFinalizer); + JimAioFileEventHandler, scriptHandlerObj, JimAioFileEventFinalizer); return JIM_OK; } static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) @@ -2213,11 +2304,11 @@ static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { AioFile *af = Jim_CmdPrivData(interp); - return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->wEvent, argc, argv); + return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->eEvent, argc, argv); } #endif static const jim_subcmd_type aio_command_table[] = { { "read", @@ -2268,14 +2359,14 @@ 0, 0, }, { "close", - NULL, + "?r(ead)|w(rite)?", aio_cmd_close, 0, - 0, + 1, JIM_MODFLAG_FULLARGV, }, { "seek", "offset ?start|current|end", @@ -2347,30 +2438,32 @@ static int JimAioOpenCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const char *mode; - const char *filename; if (argc != 2 && argc != 3) { Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?"); return JIM_ERR; } mode = (argc == 3) ? Jim_String(argv[2]) : "r"; - filename = Jim_String(argv[1]); #ifdef jim_ext_tclcompat - - if (*filename == '|') { - Jim_Obj *evalObj[3]; - - evalObj[0] = Jim_NewStringObj(interp, "popen", -1); - evalObj[1] = Jim_NewStringObj(interp, filename + 1, -1); - evalObj[2] = Jim_NewStringObj(interp, mode, -1); - - return Jim_EvalObjVector(interp, 3, evalObj); + { + const char *filename = Jim_String(argv[1]); + + + if (*filename == '|') { + Jim_Obj *evalObj[3]; + + evalObj[0] = Jim_NewStringObj(interp, "::popen", -1); + evalObj[1] = Jim_NewStringObj(interp, filename + 1, -1); + evalObj[2] = Jim_NewStringObj(interp, mode, -1); + + return Jim_EvalObjVector(interp, 3, evalObj); + } } #endif return JimMakeChannel(interp, NULL, -1, argv[1], "aio.handle%ld", 0, mode); } @@ -2377,70 +2470,131 @@ static int JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename, const char *hdlfmt, int family, const char *mode) { AioFile *af; char buf[AIO_CMD_LEN]; - int OpenFlags = 0; + int openFlags = 0; - if (filename == NULL) { + if (fh) { filename = Jim_NewStringObj(interp, hdlfmt, -1); + openFlags = AIO_KEEPOPEN; } Jim_IncrRefCount(filename); if (fh == NULL) { - if (fd < 0) { - fh = fopen(Jim_String(filename), mode); - } - else { +#if !defined(JIM_ANSIC) + if (fd >= 0) { fh = fdopen(fd, mode); } - } - else { - OpenFlags = AIO_KEEPOPEN; - } + else +#endif + fh = fopen(Jim_String(filename), mode); - if (fh == NULL) { - JimAioSetError(interp, filename); + if (fh == NULL) { + JimAioSetError(interp, filename); #if !defined(JIM_ANSIC) - if (fd >= 0) { - close(fd); + if (fd >= 0) { + close(fd); + } +#endif + Jim_DecrRefCount(interp, filename); + return JIM_ERR; } -#endif - Jim_DecrRefCount(interp, filename); - return JIM_ERR; } af = Jim_Alloc(sizeof(*af)); memset(af, 0, sizeof(*af)); af->fp = fh; af->fd = fileno(fh); af->filename = filename; #ifdef FD_CLOEXEC - if ((OpenFlags & AIO_KEEPOPEN) == 0) { - fcntl(af->fd, F_SETFD, FD_CLOEXEC); + if ((openFlags & AIO_KEEPOPEN) == 0) { + (void)fcntl(af->fd, F_SETFD, FD_CLOEXEC); } #endif - af->OpenFlags = OpenFlags; -#ifdef O_NDELAY - af->flags = fcntl(af->fd, F_GETFL); -#endif + af->openFlags = openFlags; af->addr_family = family; snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp)); Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc); - Jim_SetResultString(interp, buf, -1); + Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1))); return JIM_OK; } +#if defined(HAVE_PIPE) || (defined(HAVE_SOCKETPAIR) && defined(HAVE_SYS_UN_H)) +static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename, + const char *hdlfmt, int family, const char *mode[2]) +{ + if (JimMakeChannel(interp, NULL, p[0], filename, hdlfmt, family, mode[0]) == JIM_OK) { + Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0); + Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp)); + + if (JimMakeChannel(interp, NULL, p[1], filename, hdlfmt, family, mode[1]) == JIM_OK) { + Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp)); + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + } + + + close(p[0]); + close(p[1]); + JimAioSetError(interp, NULL); + return JIM_ERR; +} +#endif + + +int Jim_MakeTempFile(Jim_Interp *interp, const char *template) +{ +#ifdef HAVE_MKSTEMP + int fd; + mode_t mask; + Jim_Obj *filenameObj; + + if (template == NULL) { + const char *tmpdir = getenv("TMPDIR"); + if (tmpdir == NULL || *tmpdir == '\0' || access(tmpdir, W_OK) != 0) { + tmpdir = "/tmp/"; + } + filenameObj = Jim_NewStringObj(interp, tmpdir, -1); + if (tmpdir[0] && tmpdir[strlen(tmpdir) - 1] != '/') { + Jim_AppendString(interp, filenameObj, "/", 1); + } + Jim_AppendString(interp, filenameObj, "tcl.tmp.XXXXXX", -1); + } + else { + filenameObj = Jim_NewStringObj(interp, template, -1); + } + + mask = umask(S_IXUSR | S_IRWXG | S_IRWXO); + + + fd = mkstemp(filenameObj->bytes); + umask(mask); + if (fd < 0) { + JimAioSetError(interp, filenameObj); + Jim_FreeNewObj(interp, filenameObj); + return -1; + } + + Jim_SetResult(interp, filenameObj); + return fd; +#else + Jim_SetResultString(interp, "platform has no tempfile support", -1); + return -1; +#endif +} FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command) { Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG); + if (cmdPtr && !cmdPtr->isproc && cmdPtr->u.native.cmdProc == JimAioSubCmdProc) { return ((AioFile *) cmdPtr->u.native.privData)->fp; } Jim_SetResultFormatted(interp, "Not a filehandle: \"%#s\"", command); return NULL; @@ -2461,11 +2615,10 @@ JimMakeChannel(interp, stdout, -1, NULL, "stdout", 0, "w"); JimMakeChannel(interp, stderr, -1, NULL, "stderr", 0, "w"); return JIM_OK; } - #include #include #include @@ -2497,28 +2650,29 @@ return JIM_OK; } Jim_SetResultString(interp, strerror(errno), -1); return JIM_ERR; } - Jim_SetResultString(interp, strerror(errno), -1); - - Jim_SetResult(interp, Jim_NewListObj(interp, NULL, 0)); - - while ((entryPtr = readdir(dirPtr)) != NULL) { - if (entryPtr->d_name[0] == '.') { - if (entryPtr->d_name[1] == '\0') { - continue; - } - if ((entryPtr->d_name[1] == '.') && (entryPtr->d_name[2] == '\0')) - continue; - } - Jim_ListAppendElement(interp, Jim_GetResult(interp), Jim_NewStringObj(interp, - entryPtr->d_name, -1)); - } - closedir(dirPtr); - - return JIM_OK; + else { + Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0); + + while ((entryPtr = readdir(dirPtr)) != NULL) { + if (entryPtr->d_name[0] == '.') { + if (entryPtr->d_name[1] == '\0') { + continue; + } + if ((entryPtr->d_name[1] == '.') && (entryPtr->d_name[2] == '\0')) + continue; + } + Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, entryPtr->d_name, -1)); + } + closedir(dirPtr); + + Jim_SetResult(interp, listObj); + + return JIM_OK; + } } int Jim_readdirInit(Jim_Interp *interp) { if (Jim_PackageProvide(interp, "readdir", "1.0", JIM_ERRMSG)) @@ -2529,10 +2683,14 @@ } #include #include +#if defined(JIM_REGEXP) +#else + #include +#endif static void FreeRegexpInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) { regfree(objPtr->internalRep.regexpValue.compre); Jim_Free(objPtr->internalRep.regexpValue.compre); @@ -3039,10 +3197,16 @@ # ifndef MAXPATHLEN # define MAXPATHLEN JIM_PATH_LEN # endif +#if defined(__MINGW32__) || defined(_MSC_VER) +#define ISWINDOWS 1 +#else +#define ISWINDOWS 0 +#endif + static const char *JimGetFileType(int mode) { if (S_ISREG(mode)) { return "file"; @@ -3076,58 +3240,61 @@ } #endif return "unknown"; } - -static int set_array_int_value(Jim_Interp *interp, Jim_Obj *container, const char *key, - jim_wide value) -{ - Jim_Obj *nameobj = Jim_NewStringObj(interp, key, -1); - Jim_Obj *valobj = Jim_NewWideObj(interp, value); - - if (Jim_SetDictKeysVector(interp, container, &nameobj, 1, valobj, JIM_ERRMSG) != JIM_OK) { - Jim_FreeObj(interp, nameobj); - Jim_FreeObj(interp, valobj); - return JIM_ERR; - } - return JIM_OK; -} - -static int set_array_string_value(Jim_Interp *interp, Jim_Obj *container, const char *key, - const char *value) -{ - Jim_Obj *nameobj = Jim_NewStringObj(interp, key, -1); - Jim_Obj *valobj = Jim_NewStringObj(interp, value, -1); - - if (Jim_SetDictKeysVector(interp, container, &nameobj, 1, valobj, JIM_ERRMSG) != JIM_OK) { - Jim_FreeObj(interp, nameobj); - Jim_FreeObj(interp, valobj); - return JIM_ERR; - } - return JIM_OK; +static void AppendStatElement(Jim_Interp *interp, Jim_Obj *listObj, const char *key, jim_wide value) +{ + Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, key, -1)); + Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, value)); } static int StoreStatData(Jim_Interp *interp, Jim_Obj *varName, const struct stat *sb) { - if (set_array_int_value(interp, varName, "dev", sb->st_dev) != JIM_OK) { - Jim_SetResultFormatted(interp, "can't set \"%#s(dev)\": variable isn't array", varName); - return JIM_ERR; - } - set_array_int_value(interp, varName, "ino", sb->st_ino); - set_array_int_value(interp, varName, "mode", sb->st_mode); - set_array_int_value(interp, varName, "nlink", sb->st_nlink); - set_array_int_value(interp, varName, "uid", sb->st_uid); - set_array_int_value(interp, varName, "gid", sb->st_gid); - set_array_int_value(interp, varName, "size", sb->st_size); - set_array_int_value(interp, varName, "atime", sb->st_atime); - set_array_int_value(interp, varName, "mtime", sb->st_mtime); - set_array_int_value(interp, varName, "ctime", sb->st_ctime); - set_array_string_value(interp, varName, "type", JimGetFileType((int)sb->st_mode)); - - - Jim_SetResult(interp, Jim_GetVariable(interp, varName, 0)); + + Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0); + + AppendStatElement(interp, listObj, "dev", sb->st_dev); + AppendStatElement(interp, listObj, "ino", sb->st_ino); + AppendStatElement(interp, listObj, "mode", sb->st_mode); + AppendStatElement(interp, listObj, "nlink", sb->st_nlink); + AppendStatElement(interp, listObj, "uid", sb->st_uid); + AppendStatElement(interp, listObj, "gid", sb->st_gid); + AppendStatElement(interp, listObj, "size", sb->st_size); + AppendStatElement(interp, listObj, "atime", sb->st_atime); + AppendStatElement(interp, listObj, "mtime", sb->st_mtime); + AppendStatElement(interp, listObj, "ctime", sb->st_ctime); + Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, "type", -1)); + Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, JimGetFileType((int)sb->st_mode), -1)); + + + if (varName) { + Jim_Obj *objPtr = Jim_GetVariable(interp, varName, JIM_NONE); + if (objPtr) { + if (Jim_DictSize(interp, objPtr) < 0) { + + Jim_SetResultFormatted(interp, "can't set \"%#s(dev)\": variable isn't array", varName); + Jim_FreeNewObj(interp, listObj); + return JIM_ERR; + } + + if (Jim_IsShared(objPtr)) + objPtr = Jim_DuplicateObj(interp, objPtr); + + + Jim_ListAppendList(interp, objPtr, listObj); + Jim_DictSize(interp, objPtr); + Jim_InvalidateStringRep(objPtr); + + Jim_FreeNewObj(interp, listObj); + listObj = objPtr; + } + Jim_SetVariable(interp, varName, listObj); + } + + + Jim_SetResult(interp, listObj); return JIM_OK; } static int file_cmd_dirname(Jim_Interp *interp, int argc, Jim_Obj *const *argv) @@ -3141,16 +3308,14 @@ Jim_SetResultString(interp, ".", -1); } else if (p == path) { Jim_SetResultString(interp, "/", -1); } -#if defined(__MINGW32__) || defined(_MSC_VER) - else if (p[-1] == ':') { + else if (ISWINDOWS && p[-1] == ':') { Jim_SetResultString(interp, path, p - path + 1); } -#endif else { Jim_SetResultString(interp, path, p - path); } return JIM_OK; } @@ -3233,16 +3398,14 @@ if (*part == '/') { last = newname; } -#if defined(__MINGW32__) || defined(_MSC_VER) - else if (strchr(part, ':')) { + else if (ISWINDOWS && strchr(part, ':')) { last = newname; } -#endif else if (part[0] == '.') { if (part[1] == '/') { part += 2; len -= 2; } @@ -3267,11 +3430,14 @@ last += len; } if (last > newname + 1 && last[-1] == '/') { - *--last = 0; + + if (!ISWINDOWS || !(last > newname + 2 && last[-2] == ':')) { + *--last = 0; + } } } *last = 0; @@ -3282,14 +3448,11 @@ return JIM_OK; } static int file_access(Jim_Interp *interp, Jim_Obj *filename, int mode) { - const char *path = Jim_String(filename); - int rc = access(path, mode); - - Jim_SetResultBool(interp, rc != -1); + Jim_SetResultBool(interp, access(Jim_String(filename), mode) != -1); return JIM_OK; } static int file_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) @@ -3305,10 +3468,11 @@ static int file_cmd_executable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { #ifdef X_OK return file_access(interp, argv[0], X_OK); #else + Jim_SetResultBool(interp, 1); return JIM_OK; #endif } @@ -3409,33 +3573,21 @@ argv++; } return JIM_OK; } -#ifdef HAVE_MKSTEMP static int file_cmd_tempfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - int fd; - char *filename; - const char *template = "/tmp/tcl.tmp.XXXXXX"; - - if (argc >= 1) { - template = Jim_String(argv[0]); - } - filename = Jim_StrDup(template); - - fd = mkstemp(filename); - if (fd < 0) { - Jim_SetResultString(interp, "Failed to create tempfile", -1); + int fd = Jim_MakeTempFile(interp, (argc >= 1) ? Jim_String(argv[0]) : NULL); + + if (fd < 0) { return JIM_ERR; } close(fd); - Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, filename, -1)); return JIM_OK; } -#endif static int file_cmd_rename(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const char *source; const char *dest; @@ -3465,10 +3617,48 @@ return JIM_ERR; } return JIM_OK; } + +#if defined(HAVE_LINK) && defined(HAVE_SYMLINK) +static int file_cmd_link(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int ret; + const char *source; + const char *dest; + static const char * const options[] = { "-hard", "-symbolic", NULL }; + enum { OPT_HARD, OPT_SYMBOLIC, }; + int option = OPT_HARD; + + if (argc == 3) { + if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ENUM_ABBREV | JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + argv++; + argc--; + } + + dest = Jim_String(argv[0]); + source = Jim_String(argv[1]); + + if (option == OPT_HARD) { + ret = link(source, dest); + } + else { + ret = symlink(source, dest); + } + + if (ret != 0) { + Jim_SetResultFormatted(interp, "error linking \"%#s\" to \"%#s\": %s", argv[0], argv[1], + strerror(errno)); + return JIM_ERR; + } + + return JIM_OK; +} +#endif static int file_stat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb) { const char *path = Jim_String(filename); @@ -3477,14 +3667,11 @@ return JIM_ERR; } return JIM_OK; } -#ifndef HAVE_LSTAT -#define lstat stat -#endif - +#ifdef HAVE_LSTAT static int file_lstat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb) { const char *path = Jim_String(filename); if (lstat(path, sb) == -1) { @@ -3491,10 +3678,13 @@ Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno)); return JIM_ERR; } return JIM_OK; } +#else +#define file_lstat file_stat +#endif static int file_cmd_atime(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { struct stat sb; @@ -3619,28 +3809,32 @@ } Jim_SetResultString(interp, JimGetFileType((int)sb.st_mode), -1); return JIM_OK; } +#ifdef HAVE_LSTAT static int file_cmd_lstat(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { struct stat sb; if (file_lstat(interp, argv[0], &sb) != JIM_OK) { return JIM_ERR; } - return StoreStatData(interp, argv[1], &sb); + return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb); } +#else +#define file_cmd_lstat file_cmd_stat +#endif static int file_cmd_stat(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { struct stat sb; if (file_stat(interp, argv[0], &sb) != JIM_OK) { return JIM_ERR; } - return StoreStatData(interp, argv[1], &sb); + return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb); } static const jim_subcmd_type file_command_table[] = { { "atime", "name", @@ -3745,26 +3939,33 @@ file_cmd_mkdir, 1, -1, }, -#ifdef HAVE_MKSTEMP { "tempfile", "?template?", file_cmd_tempfile, 0, 1, }, -#endif { "rename", "?-force? source dest", file_cmd_rename, 2, 3, }, +#if defined(HAVE_LINK) && defined(HAVE_SYMLINK) + { "link", + "?-symbolic|-hard? newname target", + file_cmd_link, + 2, + 3, + + }, +#endif #if defined(HAVE_READLINK) { "readlink", "name", file_cmd_readlink, 1, @@ -3778,20 +3979,20 @@ 1, 1, }, { "stat", - "name var", + "name ?var?", file_cmd_stat, - 2, + 1, 2, }, { "lstat", - "name var", + "name ?var?", file_cmd_lstat, - 2, + 1, 2, }, { "type", "name", @@ -3847,30 +4048,28 @@ return JIM_OK; } static int Jim_PwdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - const int cwd_len = 2048; - char *cwd = malloc(cwd_len); + char *cwd = Jim_Alloc(MAXPATHLEN); - if (getcwd(cwd, cwd_len) == NULL) { + if (getcwd(cwd, MAXPATHLEN) == NULL) { Jim_SetResultString(interp, "Failed to get pwd", -1); + Jim_Free(cwd); return JIM_ERR; } -#if defined(__MINGW32__) || defined(_MSC_VER) - { + else if (ISWINDOWS) { char *p = cwd; while ((p = strchr(p, '\\')) != NULL) { *p++ = '/'; } } -#endif Jim_SetResultString(interp, cwd, -1); - free(cwd); + Jim_Free(cwd); return JIM_OK; } int Jim_fileInit(Jim_Interp *interp) { @@ -3935,10 +4134,11 @@ int Jim_execInit(Jim_Interp *interp) { if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG)) return JIM_ERR; + Jim_CreateCommand(interp, "exec", Jim_ExecCmd, NULL, NULL); return JIM_OK; } #else @@ -3978,10 +4178,11 @@ static int JimErrno(void); #else #include #include #include + #include typedef int fdtype; typedef int pidtype; #define JimPipe pipe #define JimErrno() errno @@ -4005,11 +4206,11 @@ static void JimRestoreEnv(char **env); static int JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, pidtype **pidArrayPtr, fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr); static void JimDetachPids(Jim_Interp *interp, int numPids, const pidtype *pidPtr); static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, fdtype errorId); -static fdtype JimCreateTemp(Jim_Interp *interp, const char *contents); +static fdtype JimCreateTemp(Jim_Interp *interp, const char *contents, int len); static fdtype JimOpenForWrite(const char *filename, int append); static int JimRewindFd(fdtype fd); static void Jim_SetResultErrno(Jim_Interp *interp, const char *msg) { @@ -4052,23 +4253,12 @@ Jim_RemoveTrailingNewline(strObj); fclose(fh); return JIM_OK; } -static void JimTrimTrailingNewline(Jim_Interp *interp) -{ - int len; - const char *p = Jim_GetString(Jim_GetResult(interp), &len); - - if (len > 0 && p[len - 1] == '\n') { - Jim_SetResultString(interp, p, len - 1); - } -} - static char **JimBuildEnv(Jim_Interp *interp) { -#if defined(jim_ext_tclcompat) int i; int size; int num; int n; char **envptr; @@ -4082,10 +4272,11 @@ num = Jim_ListLength(interp, objPtr); if (num % 2) { + num--; } size = Jim_Length(objPtr) + 2; envptr = Jim_Alloc(sizeof(*envptr) * (num / 2 + 1) + size); @@ -4108,22 +4299,17 @@ } envptr[n] = NULL; *envdata = 0; return envptr; -#else - return Jim_GetEnviron(); -#endif } static void JimFreeEnv(char **env, char **original_environ) { -#ifdef jim_ext_tclcompat if (env != original_environ) { Jim_Free(env); } -#endif } static int JimCheckWaitStatus(Jim_Interp *interp, pidtype pid, int waitStatus) { Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0); @@ -4172,19 +4358,19 @@ } struct WaitInfo { - pidtype pid; + pidtype pid; int status; int flags; }; struct WaitInfoTable { - struct WaitInfo *info; - int size; - int used; + struct WaitInfo *info; + int size; + int used; }; #define WI_DETACHED 2 @@ -4207,14 +4393,12 @@ return table; } static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - fdtype outputId; /* File id for output pipe. -1 - * means command overrode. */ - fdtype errorId; /* File id for temporary file - * containing error output. */ + fdtype outputId; + fdtype errorId; pidtype *pidPtr; int numPids, result; if (argc > 1 && Jim_CompareStringImmediate(interp, argv[argc - 1], "&")) { Jim_Obj *listObj; @@ -4261,26 +4445,32 @@ static void JimReapDetachedPids(struct WaitInfoTable *table) { struct WaitInfo *waitPtr; int count; + int dest; if (!table) { return; } - for (waitPtr = table->info, count = table->used; count > 0; waitPtr++, count--) { + waitPtr = table->info; + dest = 0; + for (count = table->used; count > 0; waitPtr++, count--) { if (waitPtr->flags & WI_DETACHED) { int status; pidtype pid = JimWaitPid(waitPtr->pid, &status, WNOHANG); - if (pid != JIM_BAD_PID) { - if (waitPtr != &table->info[table->used - 1]) { - *waitPtr = table->info[table->used - 1]; - } + if (pid == waitPtr->pid) { + table->used--; + continue; } } + if (waitPtr != &table->info[dest]) { + table->info[dest] = *waitPtr; + } + dest++; } } static pidtype JimWaitForProcess(struct WaitInfoTable *table, pidtype pid, int *statusPtr) { @@ -4302,11 +4492,10 @@ } return JIM_BAD_PID; } - static void JimDetachPids(Jim_Interp *interp, int numPids, const pidtype *pidPtr) { int j; struct WaitInfoTable *table = Jim_CmdPrivData(interp); @@ -4347,10 +4536,11 @@ int cmdCount; /* Count of number of distinct commands * found in argc/argv. */ const char *input = NULL; /* Describes input for pipeline, depending * on "inputFile". NULL means take input * from stdin/pipe. */ + int input_len = 0; #define FILE_NAME 0 #define FILE_APPEND 1 #define FILE_HANDLE 2 #define FILE_TEXT 3 @@ -4412,19 +4602,20 @@ if (arg[0] == '<') { inputFile = FILE_NAME; input = arg + 1; if (*input == '<') { inputFile = FILE_TEXT; + input_len = Jim_Length(argv[i]) - 2; input++; } else if (*input == '@') { inputFile = FILE_HANDLE; input++; } if (!*input && ++i < argc) { - input = Jim_String(argv[i]); + input = Jim_GetString(argv[i], &input_len); } } else if (arg[0] == '>') { int dup_error = 0; @@ -4498,11 +4689,11 @@ save_environ = JimSaveEnv(JimBuildEnv(interp)); if (input != NULL) { if (inputFile == FILE_TEXT) { - inputId = JimCreateTemp(interp, input); + inputId = JimCreateTemp(interp, input, input_len); if (inputId == JIM_BAD_FD) { goto error; } } else if (inputFile == FILE_HANDLE) { @@ -4587,11 +4778,11 @@ goto error; } } } else if (errFilePtr != NULL) { - errorId = JimCreateTemp(interp, NULL); + errorId = JimCreateTemp(interp, NULL, 0); if (errorId == JIM_BAD_FD) { goto error; } *errFilePtr = JimDupFd(errorId); } @@ -4625,27 +4816,23 @@ } outputId = pipeIds[1]; } + if (pipe_dup_err) { + errorId = outputId; + } + + #ifdef __MINGW32__ pid = JimStartWinProcess(interp, &arg_array[firstArg], save_environ ? save_environ[0] : NULL, inputId, outputId, errorId); if (pid == JIM_BAD_PID) { Jim_SetResultFormatted(interp, "couldn't exec \"%s\"", arg_array[firstArg]); goto error; } #else - if (table->info == NULL) { - (void)signal(SIGPIPE, SIG_IGN); - } - - - if (pipe_dup_err) { - errorId = outputId; - } - pid = vfork(); if (pid < 0) { Jim_SetResultErrno(interp, "couldn't fork child process"); goto error; } @@ -4658,14 +4845,17 @@ for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId); i++) { close(i); } + + (void)signal(SIGPIPE, SIG_DFL); + execvpe(arg_array[firstArg], &arg_array[firstArg], Jim_GetEnviron()); - fprintf(stderr, "couldn't exec \"%s\"", arg_array[firstArg]); + fprintf(stderr, "couldn't exec \"%s\"\n", arg_array[firstArg]); _exit(127); } #endif @@ -4767,19 +4957,24 @@ if (JimAppendStreamToString(interp, errorId, Jim_GetResult(interp)) != JIM_OK) { result = JIM_ERR; } } - JimTrimTrailingNewline(interp); + Jim_RemoveTrailingNewline(Jim_GetResult(interp)); return result; } int Jim_execInit(Jim_Interp *interp) { if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG)) return JIM_ERR; + +#ifdef SIGPIPE + (void)signal(SIGPIPE, SIG_IGN); +#endif + Jim_CreateCommand(interp, "exec", Jim_ExecCmd, JimAllocWaitInfoTable(), JimFreeWaitInfoTable); return JIM_OK; } #if defined(__MINGW32__) @@ -4953,11 +5148,11 @@ *status = ret; CloseHandle(pid); return pid; } -static HANDLE JimCreateTemp(Jim_Interp *interp, const char *contents) +static HANDLE JimCreateTemp(Jim_Interp *interp, const char *contents, int len) { char name[MAX_PATH]; HANDLE handle; if (!GetTempPath(MAX_PATH, name) || !GetTempFileName(name, "JIM", 0, name)) { @@ -4977,11 +5172,11 @@ FILE *fh = JimFdOpenForWrite(JimDupFd(handle)); if (fh == NULL) { goto error; } - if (fwrite(contents, strlen(contents), 1, fh) != 1) { + if (fwrite(contents, len, 1, fh) != 1) { fclose(fh); goto error; } fseek(fh, 0, SEEK_SET); fclose(fh); @@ -5104,28 +5299,26 @@ { STARTUPINFO startInfo; PROCESS_INFORMATION procInfo; HANDLE hProcess, h; char execPath[MAX_PATH]; - char *originalName; pidtype pid = JIM_BAD_PID; Jim_Obj *cmdLineObj; if (JimWinFindExecutable(argv[0], execPath) < 0) { return JIM_BAD_PID; } - originalName = argv[0]; argv[0] = execPath; hProcess = GetCurrentProcess(); cmdLineObj = JimWinBuildCommandLine(interp, argv); ZeroMemory(&startInfo, sizeof(startInfo)); startInfo.cb = sizeof(startInfo); startInfo.dwFlags = STARTF_USESTDHANDLES; - startInfo.hStdInput = INVALID_HANDLE_VALUE; + startInfo.hStdInput = INVALID_HANDLE_VALUE; startInfo.hStdOutput= INVALID_HANDLE_VALUE; startInfo.hStdError = INVALID_HANDLE_VALUE; if (inputId == JIM_BAD_FD) { if (CreatePipe(&startInfo.hStdInput, &h, JimStdSecAttrs(), 0) != FALSE) { @@ -5196,28 +5389,24 @@ static int JimRewindFd(int fd) { return lseek(fd, 0L, SEEK_SET); } -static int JimCreateTemp(Jim_Interp *interp, const char *contents) -{ - char inName[] = "/tmp/tcl.tmp.XXXXXX"; - - int fd = mkstemp(inName); - if (fd == JIM_BAD_FD) { - Jim_SetResultErrno(interp, "couldn't create temp file"); - return -1; - } - unlink(inName); - if (contents) { - int length = strlen(contents); - if (write(fd, contents, length) != length) { - Jim_SetResultErrno(interp, "couldn't write temp file"); - close(fd); - return -1; - } - lseek(fd, 0L, SEEK_SET); +static int JimCreateTemp(Jim_Interp *interp, const char *contents, int len) +{ + int fd = Jim_MakeTempFile(interp, NULL); + + if (fd != JIM_BAD_FD) { + unlink(Jim_String(Jim_GetResult(interp))); + if (contents) { + if (write(fd, contents, len) != len) { + Jim_SetResultErrno(interp, "couldn't write temp file"); + close(fd); + return -1; + } + lseek(fd, 0L, SEEK_SET); + } } return fd; } static char **JimSaveEnv(char **env) @@ -5232,11 +5421,10 @@ JimFreeEnv(Jim_GetEnviron(), env); Jim_SetEnviron(env); } #endif #endif - #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 500 #endif @@ -5256,11 +5444,11 @@ char buf[100]; time_t t; long seconds; - const char *format = "%a %b %d %H:%M:%S %Z %Y"; + const char *format = "%a %b %d %H:%M:%S %Z %Y"; if (argc == 2 || (argc == 3 && !Jim_CompareStringImmediate(interp, argv[1], "-format"))) { return -1; } @@ -5271,11 +5459,14 @@ if (Jim_GetLong(interp, argv[0], &seconds) != JIM_OK) { return JIM_ERR; } t = seconds; - strftime(buf, sizeof(buf), format, localtime(&t)); + if (strftime(buf, sizeof(buf), format, localtime(&t)) == 0) { + Jim_SetResultString(interp, "format string too long", -1); + return JIM_ERR; + } Jim_SetResultString(interp, buf, -1); return JIM_OK; } @@ -5391,11 +5582,10 @@ Jim_CreateCommand(interp, "clock", Jim_SubCmdProc, (void *)clock_command_table, NULL); return JIM_OK; } - #include #include #include #include #include @@ -5409,33 +5599,29 @@ } static int array_cmd_get(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *objPtr = Jim_GetVariable(interp, argv[0], JIM_NONE); + Jim_Obj *patternObj; if (!objPtr) { return JIM_OK; } - if (argc == 1 || Jim_CompareStringImmediate(interp, argv[1], "*")) { - - if (Jim_IsList(objPtr)) { - if (Jim_ListLength(interp, objPtr) % 2 != 0) { - - return JIM_ERR; - } - } - else if (Jim_DictSize(interp, objPtr) < 0) { - - return JIM_ERR; - } - Jim_SetResult(interp, objPtr); - return JIM_OK; + patternObj = (argc == 1) ? NULL : argv[1]; + + + if (patternObj == NULL || Jim_CompareStringImmediate(interp, patternObj, "*")) { + if (Jim_IsList(objPtr) && Jim_ListLength(interp, objPtr) % 2 == 0) { + + Jim_SetResult(interp, objPtr); + return JIM_OK; + } } - return Jim_DictValues(interp, objPtr, argv[1]); + return Jim_DictValues(interp, objPtr, patternObj); } static int array_cmd_names(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *objPtr = Jim_GetVariable(interp, argv[0], JIM_NONE); @@ -5460,10 +5646,15 @@ Jim_UnsetVariable(interp, argv[0], JIM_NONE); return JIM_OK; } objPtr = Jim_GetVariable(interp, argv[0], JIM_NONE); + + if (objPtr == NULL) { + + return JIM_OK; + } if (Jim_DictPairs(interp, objPtr, &dictValuesObj, &len) != JIM_OK) { return JIM_ERR; } @@ -5497,10 +5688,20 @@ Jim_SetResultInt(interp, len); return JIM_OK; } + +static int array_cmd_stat(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *objPtr = Jim_GetVariable(interp, argv[0], JIM_NONE); + if (objPtr) { + return Jim_DictInfo(interp, objPtr); + } + Jim_SetResultFormatted(interp, "\"%#s\" isn't an array", argv[0], NULL); + return JIM_ERR; +} static int array_cmd_set(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { int i; int len; @@ -5515,10 +5716,13 @@ dictObj = Jim_GetVariable(interp, argv[0], JIM_UNSHARED); if (!dictObj) { return Jim_SetVariable(interp, argv[0], listObj); + } + else if (Jim_DictSize(interp, dictObj) < 0) { + return JIM_ERR; } if (Jim_IsShared(dictObj)) { dictObj = Jim_DuplicateObj(interp, dictObj); } @@ -5568,10 +5772,17 @@ "arrayName", array_cmd_size, 1, 1, + }, + { "stat", + "arrayName", + array_cmd_stat, + 1, + 1, + }, { "unset", "arrayName ?pattern?", array_cmd_unset, 1, @@ -5614,11 +5825,10 @@ Jim_arrayInit(interp); Jim_stdlibInit(interp); Jim_tclcompatInit(interp); return JIM_OK; } - #define JIM_OPTIMIZATION #include #include @@ -5671,27 +5881,27 @@ #ifdef JIM_MAINTAINER #define JIM_DEBUG_COMMAND #define JIM_DEBUG_PANIC #endif + #define JIM_INTEGER_SPACE 24 const char *jim_tt_name(int type); #ifdef JIM_DEBUG_PANIC -static void JimPanicDump(int panic_condition, const char *fmt, ...); +static void JimPanicDump(int fail_condition, const char *fmt, ...); #define JimPanic(X) JimPanicDump X #else #define JimPanic(X) #endif static char JimEmptyStringRep[] = ""; -static void JimChangeCallFrameId(Jim_Interp *interp, Jim_CallFrame *cf); -static void JimFreeCallFrame(Jim_Interp *interp, Jim_CallFrame *cf, int flags); +static void JimFreeCallFrame(Jim_Interp *interp, Jim_CallFrame *cf, int action); static int ListSetIndex(Jim_Interp *interp, Jim_Obj *listPtr, int listindex, Jim_Obj *newObjPtr, int flags); static int JimDeleteLocalProcs(Jim_Interp *interp, Jim_Stack *localCommands); static Jim_Obj *JimExpandDictSugar(Jim_Interp *interp, Jim_Obj *objPtr); static void SetDictSubstFromAny(Jim_Interp *interp, Jim_Obj *objPtr); @@ -5938,44 +6148,10 @@ } return n; } #endif -static int JimWideToString(char *buf, jim_wide wideValue) -{ - int pos = 0; - - if (wideValue == 0) { - buf[pos++] = '0'; - } - else { - char tmp[JIM_INTEGER_SPACE]; - int num = 0; - int i; - - if (wideValue < 0) { - buf[pos++] = '-'; - - i = wideValue % 10; - tmp[num++] = (i > 0) ? (10 - i) : -i; - wideValue /= -10; - } - - while (wideValue) { - tmp[num++] = wideValue % 10; - wideValue /= 10; - } - - for (i = 0; i < num; i++) { - buf[pos++] = '0' + tmp[num - i - 1]; - } - } - buf[pos] = 0; - - return pos; -} - static int JimCheckConversion(const char *str, const char *endptr) { if (str[0] == '\0' || str == endptr) { return JIM_ERR; } @@ -6029,11 +6205,12 @@ if (str[i] != '-' && str[i] != '+' && !isspace(UCHAR(str[i]))) { return i; } - return 10; + *base = 10; + return 0; } static long jim_strtol(const char *str, char **endptr) { int sign; @@ -6085,46 +6262,10 @@ } return JimCheckConversion(str, endptr); } -int Jim_DoubleToString(char *buf, double doubleValue) -{ - int len; - int i; - - len = sprintf(buf, "%.12g", doubleValue); - - - for (i = 0; i < len; i++) { - if (buf[i] == '.' || buf[i] == 'e') { -#if defined(JIM_SPRINTF_DOUBLE_NEEDS_FIX) - char *e = strchr(buf, 'e'); - if (e && (e[1] == '-' || e[1] == '+') && e[2] == '0') { - - e += 2; - memmove(e, e + 1, len - (e - buf)); - return len - 1; - } -#endif - return len; - } - - if (buf[i] == 'i' || buf[i] == 'I' || buf[i] == 'n' || buf[i] == 'N') { - buf[i] = toupper(UCHAR(buf[i])); - buf[i + 3] = 0; - return i + 3; - } - } - - buf[i++] = '.'; - buf[i++] = '0'; - buf[i] = '\0'; - - return i; -} - int Jim_StringToDouble(const char *str, double *doublePtr) { char *endptr; @@ -6146,23 +6287,23 @@ } return res; } #ifdef JIM_DEBUG_PANIC -void JimPanicDump(int condition, const char *fmt, ...) +static void JimPanicDump(int condition, const char *fmt, ...) { va_list ap; if (!condition) { return; } va_start(ap, fmt); - fprintf(stderr, JIM_NL "JIM INTERPRETER PANIC: "); + fprintf(stderr, "\nJIM INTERPRETER PANIC: "); vfprintf(stderr, fmt, ap); - fprintf(stderr, JIM_NL JIM_NL); + fprintf(stderr, "\n\n"); va_end(ap); #ifdef HAVE_BACKTRACE { void *array[40]; @@ -6170,13 +6311,13 @@ char **strings; size = backtrace(array, 40); strings = backtrace_symbols(array, size); for (i = 0; i < size; i++) - fprintf(stderr, "[backtrace] %s" JIM_NL, strings[i]); - fprintf(stderr, "[backtrace] Include the above lines and the output" JIM_NL); - fprintf(stderr, "[backtrace] of 'nm ' in the bug report." JIM_NL); + fprintf(stderr, "[backtrace] %s\n", strings[i]); + fprintf(stderr, "[backtrace] Include the above lines and the output\n"); + fprintf(stderr, "[backtrace] of 'nm ' in the bug report.\n"); } #endif exit(1); } @@ -6250,18 +6391,24 @@ h += (h << 3) + *buf++; return h; } + static void JimResetHashTable(Jim_HashTable *ht) { ht->table = NULL; ht->size = 0; ht->sizemask = 0; ht->used = 0; ht->collisions = 0; +#ifdef JIM_RANDOMISE_HASH + ht->uniq = (rand() ^ time(NULL) ^ clock()); +#else + ht->uniq = 0; +#endif } static void JimInitHashTableIterator(Jim_HashTable *ht, Jim_HashTableIterator *iter) { iter->ht = ht; @@ -6299,10 +6446,12 @@ Jim_InitHashTable(&n, ht->type, ht->privdata); n.size = realsize; n.sizemask = realsize - 1; n.table = Jim_Alloc(realsize * sizeof(Jim_HashEntry *)); + + n.uniq = ht->uniq; memset(n.table, 0, realsize * sizeof(Jim_HashEntry *)); n.used = ht->used; @@ -6355,20 +6504,27 @@ int existed; Jim_HashEntry *entry; entry = JimInsertHashEntry(ht, key, 1); if (entry->key) { - - Jim_FreeEntryVal(ht, entry); + if (ht->type->valDestructor && ht->type->valDup) { + void *newval = ht->type->valDup(ht->privdata, val); + ht->type->valDestructor(ht->privdata, entry->u.val); + entry->u.val = newval; + } + else { + Jim_FreeEntryVal(ht, entry); + Jim_SetHashVal(ht, entry, val); + } existed = 1; } else { Jim_SetHashKey(ht, entry, key); + Jim_SetHashVal(ht, entry, val); existed = 0; } - Jim_SetHashVal(ht, entry, val); return existed; } @@ -6533,11 +6689,11 @@ return Jim_GenHashFunction(key, strlen(key)); } static void *JimStringCopyHTDup(void *privdata, const void *key) { - return strdup(key); + return Jim_StrDup(key); } static int JimStringCopyHTKeyCompare(void *privdata, const void *key1, const void *key2) { return strcmp(key1, key2) == 0; @@ -6633,11 +6789,11 @@ freeFunc(stack->vector[i]); } -#define JIM_TT_NONE 0 +#define JIM_TT_NONE 0 #define JIM_TT_STR 1 #define JIM_TT_ESC 2 #define JIM_TT_VAR 3 #define JIM_TT_DICTSUGAR 4 #define JIM_TT_CMD 5 @@ -6665,10 +6821,15 @@ #define JIM_PS_DEF 0 #define JIM_PS_QUOTE 1 #define JIM_PS_DICTSUGAR 2 + +struct JimParseMissing { + int ch; + int line; +}; struct JimParserCtx { const char *p; int len; @@ -6678,17 +6839,11 @@ int tline; int tt; int eof; int state; int comment; - char missing; - int missingline; -}; - -struct JimParseResult { - char missing; - int line; + struct JimParseMissing missing; }; static int JimParseScript(struct JimParserCtx *pc); static int JimParseSep(struct JimParserCtx *pc); static int JimParseEol(struct JimParserCtx *pc); @@ -6698,11 +6853,10 @@ static int JimParseBrace(struct JimParserCtx *pc); static int JimParseStr(struct JimParserCtx *pc); static int JimParseComment(struct JimParserCtx *pc); static void JimParseSubCmd(struct JimParserCtx *pc); static int JimParseSubQuote(struct JimParserCtx *pc); -static void JimParseSubCmd(struct JimParserCtx *pc); static Jim_Obj *JimParserGetTokenObj(Jim_Interp *interp, struct JimParserCtx *pc); static void JimParserInit(struct JimParserCtx *pc, const char *prg, int len, int linenr) { pc->p = prg; @@ -6713,12 +6867,12 @@ pc->tt = JIM_TT_NONE; pc->eof = 0; pc->state = JIM_PS_DEF; pc->linenr = linenr; pc->comment = 1; - pc->missing = ' '; - pc->missingline = linenr; + pc->missing.ch = ' '; + pc->missing.line = linenr; } static int JimParseScript(struct JimParserCtx *pc) { while (1) { @@ -6850,12 +7004,12 @@ break; } pc->p++; pc->len--; } - pc->missing = '{'; - pc->missingline = pc->tline; + pc->missing.ch = '{'; + pc->missing.line = pc->tline; pc->tend = pc->p - 1; } static int JimParseSubQuote(struct JimParserCtx *pc) { @@ -6897,12 +7051,12 @@ break; } pc->p++; pc->len--; } - pc->missing = '"'; - pc->missingline = line; + pc->missing.ch = '"'; + pc->missing.line = line; pc->tend = pc->p - 1; return tt; } static void JimParseSubCmd(struct JimParserCtx *pc) @@ -6956,12 +7110,12 @@ } startofword = isspace(UCHAR(*pc->p)); pc->p++; pc->len--; } - pc->missing = '['; - pc->missingline = line; + pc->missing.ch = '['; + pc->missing.line = line; pc->tend = pc->p - 1; } static int JimParseBrace(struct JimParserCtx *pc) { @@ -7101,19 +7255,19 @@ if (*pc->p == '"') { pc->state = JIM_PS_QUOTE; pc->p++; pc->len--; - pc->missingline = pc->tline; + pc->missing.line = pc->tline; } } pc->tstart = pc->p; pc->tline = pc->linenr; while (1) { if (pc->len == 0) { if (pc->state == JIM_PS_QUOTE) { - pc->missing = '"'; + pc->missing.ch = '"'; } pc->tend = pc->p - 1; pc->tt = JIM_TT_ESC; return JIM_OK; } @@ -7129,10 +7283,14 @@ pc->linenr++; } pc->p++; pc->len--; } + else if (pc->len == 1) { + + pc->missing.ch = '\\'; + } break; case '(': if (pc->len > 1 && pc->p[1] != '$') { break; @@ -7189,17 +7347,26 @@ } static int JimParseComment(struct JimParserCtx *pc) { while (*pc->p) { - if (*pc->p == '\n') { + if (*pc->p == '\\') { + pc->p++; + pc->len--; + if (pc->len == 0) { + pc->missing.ch = '\\'; + return JIM_OK; + } + if (*pc->p == '\n') { + pc->linenr++; + } + } + else if (*pc->p == '\n') { + pc->p++; + pc->len--; pc->linenr++; - if (*(pc->p - 1) != '\\') { - pc->p++; - pc->len--; - return JIM_OK; - } + break; } pc->p++; pc->len--; } return JIM_OK; @@ -7417,13 +7584,13 @@ JimParserInit(&parser, s, len, 1); while (!parser.eof) { JimParseScript(&parser); } if (stateCharPtr) { - *stateCharPtr = parser.missing; + *stateCharPtr = parser.missing.ch; } - return parser.missing == ' '; + return parser.missing.ch == ' '; } static int JimParseListSep(struct JimParserCtx *pc); static int JimParseListStr(struct JimParserCtx *pc); static int JimParseListQuote(struct JimParserCtx *pc); @@ -7581,17 +7748,21 @@ objPtr->prevObjPtr->nextObjPtr = objPtr->nextObjPtr; if (objPtr->nextObjPtr) objPtr->nextObjPtr->prevObjPtr = objPtr->prevObjPtr; if (interp->liveList == objPtr) interp->liveList = objPtr->nextObjPtr; +#ifdef JIM_DISABLE_OBJECT_POOL + Jim_Free(objPtr); +#else objPtr->prevObjPtr = NULL; objPtr->nextObjPtr = interp->freeList; if (interp->freeList) interp->freeList->prevObjPtr = objPtr; interp->freeList = objPtr; objPtr->refCount = -1; +#endif } void Jim_InvalidateStringRep(Jim_Obj *objPtr) { @@ -7666,15 +7837,22 @@ const char *Jim_String(Jim_Obj *objPtr) { if (objPtr->bytes == NULL) { + JimPanic((objPtr->typePtr == NULL, "UpdateStringProc called against typeless value.")); JimPanic((objPtr->typePtr->updateStringProc == NULL, "UpdateStringProc called against '%s' type.", objPtr->typePtr->name)); objPtr->typePtr->updateStringProc(objPtr); } return objPtr->bytes; } + +static void JimSetStringBytes(Jim_Obj *objPtr, const char *str) +{ + objPtr->bytes = Jim_StrDup(str); + objPtr->length = strlen(str); +} static void FreeDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *objPtr); static void DupDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr); static const Jim_ObjType dictSubstObjType = { @@ -7712,11 +7890,10 @@ static void DupStringInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) { JIM_NOTUSED(interp); dupPtr->internalRep.strValue.maxLength = srcPtr->length; - dupPtr->internalRep.strValue.charLength = srcPtr->internalRep.strValue.charLength; } static int SetStringFromAny(Jim_Interp *interp, Jim_Obj *objPtr) { @@ -7761,18 +7938,17 @@ if (len == -1) len = strlen(s); if (len == 0) { objPtr->bytes = JimEmptyStringRep; - objPtr->length = 0; } else { objPtr->bytes = Jim_Alloc(len + 1); - objPtr->length = len; memcpy(objPtr->bytes, s, len); objPtr->bytes[len] = '\0'; } + objPtr->length = len; objPtr->typePtr = NULL; return objPtr; } @@ -7800,11 +7976,11 @@ Jim_Obj *Jim_NewStringObjNoAlloc(Jim_Interp *interp, char *s, int len) { Jim_Obj *objPtr = Jim_NewObj(interp); objPtr->bytes = s; - objPtr->length = len == -1 ? strlen(s) : len; + objPtr->length = (len == -1) ? strlen(s) : len; objPtr->typePtr = NULL; return objPtr; } static void StringAppendString(Jim_Obj *objPtr, const char *str, int len) @@ -7829,17 +8005,17 @@ } objPtr->internalRep.strValue.maxLength = needlen; } memcpy(objPtr->bytes + objPtr->length, str, len); objPtr->bytes[objPtr->length + len] = '\0'; + if (objPtr->internalRep.strValue.charLength >= 0) { objPtr->internalRep.strValue.charLength += utf8_strlen(objPtr->bytes + objPtr->length, len); } objPtr->length += len; } - void Jim_AppendString(Jim_Interp *interp, Jim_Obj *objPtr, const char *str, int len) { JimPanic((Jim_IsShared(objPtr), "Jim_AppendString called with shared object")); SetStringFromAny(interp, objPtr); @@ -7847,13 +8023,11 @@ } void Jim_AppendObj(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *appendObjPtr) { int len; - const char *str; - - str = Jim_GetString(appendObjPtr, &len); + const char *str = Jim_GetString(appendObjPtr, &len); Jim_AppendString(interp, objPtr, str, len); } void Jim_AppendStrings(Jim_Interp *interp, Jim_Obj *objPtr, ...) { @@ -7860,11 +8034,11 @@ va_list ap; SetStringFromAny(interp, objPtr); va_start(ap, objPtr); while (1) { - char *s = va_arg(ap, char *); + const char *s = va_arg(ap, const char *); if (s == NULL) break; Jim_AppendString(interp, objPtr, s, -1); } @@ -7871,20 +8045,20 @@ va_end(ap); } int Jim_StringEqObj(Jim_Obj *aObjPtr, Jim_Obj *bObjPtr) { - const char *aStr, *bStr; - int aLen, bLen; - - if (aObjPtr == bObjPtr) + if (aObjPtr == bObjPtr) { return 1; - aStr = Jim_GetString(aObjPtr, &aLen); - bStr = Jim_GetString(bObjPtr, &bLen); - if (aLen != bLen) - return 0; - return JimStringCompare(aStr, aLen, bStr, bLen) == 0; + } + else { + int Alen, Blen; + const char *sA = Jim_GetString(aObjPtr, &Alen); + const char *sB = Jim_GetString(bObjPtr, &Blen); + + return Alen == Blen && memcmp(sA, sB, Alen) == 0; + } } int Jim_StringMatchObj(Jim_Interp *interp, Jim_Obj *patternObjPtr, Jim_Obj *objPtr, int nocase) { return JimGlobMatch(Jim_String(patternObjPtr), Jim_String(objPtr), nocase); @@ -8045,11 +8219,11 @@ static void JimStrCopyUpperLower(char *dest, const char *str, int uc) { while (*str) { int c; str += utf8_tounicode(str, &c); - dest += utf8_fromunicode(dest, uc ? utf8_upper(c) : utf8_lower(c)); + dest += utf8_getchars(dest, uc ? utf8_upper(c) : utf8_lower(c)); } *dest = 0; } static Jim_Obj *JimStringToLower(Jim_Interp *interp, Jim_Obj *strObjPtr) @@ -8105,11 +8279,11 @@ len *= 2; #endif buf = p = Jim_Alloc(len + 1); str += utf8_tounicode(str, &c); - p += utf8_fromunicode(p, utf8_title(c)); + p += utf8_getchars(p, utf8_title(c)); JimStrCopyUpperLower(p, str, 0); return Jim_NewStringObjNoAlloc(interp, buf, -1); } @@ -8212,10 +8386,11 @@ if (nontrim == NULL) { return Jim_NewEmptyStringObj(interp); } if (nontrim == strObjPtr->bytes + len) { + return strObjPtr; } if (Jim_IsShared(strObjPtr)) { strObjPtr = Jim_NewStringObj(interp, strObjPtr->bytes, (nontrim - strObjPtr->bytes)); @@ -8235,19 +8410,28 @@ Jim_Obj *objPtr = JimStringTrimLeft(interp, strObjPtr, trimcharsObjPtr); strObjPtr = JimStringTrimRight(interp, objPtr, trimcharsObjPtr); - if (objPtr != strObjPtr) { + + if (objPtr != strObjPtr && objPtr->refCount == 0) { - Jim_IncrRefCount(objPtr); - Jim_DecrRefCount(interp, objPtr); + Jim_FreeNewObj(interp, objPtr); } return strObjPtr; } + +#ifdef HAVE_ISASCII +#define jim_isascii isascii +#else +static int jim_isascii(int c) +{ + return !(c & ~0x7f); +} +#endif static int JimStringIs(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *strClass, int strict) { static const char * const strclassnames[] = { "integer", "alpha", "alnum", "ascii", "digit", @@ -8270,32 +8454,32 @@ return JIM_ERR; } str = Jim_GetString(strObjPtr, &len); if (len == 0) { - Jim_SetResultInt(interp, !strict); + Jim_SetResultBool(interp, !strict); return JIM_OK; } switch (strclass) { case STR_IS_INTEGER: { jim_wide w; - Jim_SetResultInt(interp, JimGetWideNoErr(interp, strObjPtr, &w) == JIM_OK); + Jim_SetResultBool(interp, JimGetWideNoErr(interp, strObjPtr, &w) == JIM_OK); return JIM_OK; } case STR_IS_DOUBLE: { double d; - Jim_SetResultInt(interp, Jim_GetDouble(interp, strObjPtr, &d) == JIM_OK && errno != ERANGE); + Jim_SetResultBool(interp, Jim_GetDouble(interp, strObjPtr, &d) == JIM_OK && errno != ERANGE); return JIM_OK; } case STR_IS_ALPHA: isclassfunc = isalpha; break; case STR_IS_ALNUM: isclassfunc = isalnum; break; - case STR_IS_ASCII: isclassfunc = isascii; break; + case STR_IS_ASCII: isclassfunc = jim_isascii; break; case STR_IS_DIGIT: isclassfunc = isdigit; break; case STR_IS_LOWER: isclassfunc = islower; break; case STR_IS_UPPER: isclassfunc = isupper; break; case STR_IS_SPACE: isclassfunc = isspace; break; case STR_IS_XDIGIT: isclassfunc = isxdigit; break; @@ -8307,15 +8491,15 @@ return JIM_ERR; } for (i = 0; i < len; i++) { if (!isclassfunc(str[i])) { - Jim_SetResultInt(interp, 0); + Jim_SetResultBool(interp, 0); return JIM_OK; } } - Jim_SetResultInt(interp, 1); + Jim_SetResultBool(interp, 1); return JIM_OK; } @@ -8327,17 +8511,19 @@ JIM_TYPE_REFERENCES, }; int Jim_CompareStringImmediate(Jim_Interp *interp, Jim_Obj *objPtr, const char *str) { - if (objPtr->typePtr == &comparedStringObjType && objPtr->internalRep.ptr == str) + if (objPtr->typePtr == &comparedStringObjType && objPtr->internalRep.ptr == str) { return 1; + } else { const char *objStr = Jim_String(objPtr); if (strcmp(str, objStr) != 0) return 0; + if (objPtr->typePtr != &comparedStringObjType) { Jim_FreeIntRep(interp, objPtr); objPtr->typePtr = &comparedStringObjType; } objPtr->internalRep.ptr = (char *)str; @@ -8379,24 +8565,23 @@ static void JimSetSourceInfo(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *fileNameObj, int lineNumber) { JimPanic((Jim_IsShared(objPtr), "JimSetSourceInfo called with shared object")); - JimPanic((objPtr->typePtr == &sourceObjType, "JimSetSourceInfo called with non-source object")); + JimPanic((objPtr->typePtr != NULL, "JimSetSourceInfo called with typed object")); Jim_IncrRefCount(fileNameObj); objPtr->internalRep.sourceValue.fileNameObj = fileNameObj; objPtr->internalRep.sourceValue.lineNumber = lineNumber; objPtr->typePtr = &sourceObjType; } - static const Jim_ObjType scriptLineObjType = { "scriptline", NULL, NULL, NULL, - 0, + JIM_NONE, }; static Jim_Obj *JimNewScriptLineObj(Jim_Interp *interp, int argc, int line) { Jim_Obj *objPtr; @@ -8413,15 +8598,14 @@ objPtr->internalRep.scriptLineValue.line = line; return objPtr; } -#define JIM_CMDSTRUCT_EXPAND -1 - static void FreeScriptInternalRep(Jim_Interp *interp, Jim_Obj *objPtr); static void DupScriptInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr); -static int SetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr, struct JimParseResult *result); +static void JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr); +static int JimParseCheckMissing(Jim_Interp *interp, int ch); static const Jim_ObjType scriptObjType = { "script", FreeScriptInternalRep, DupScriptInternalRep, @@ -8429,34 +8613,34 @@ JIM_TYPE_REFERENCES, }; typedef struct ScriptToken { - int type; Jim_Obj *objPtr; + int type; } ScriptToken; typedef struct ScriptObj { - int len; ScriptToken *token; + Jim_Obj *fileNameObj; + int len; int substFlags; int inUse; /* Used to share a ScriptObj. Currently only used by Jim_EvalObj() as protection against shimmering of the currently evaluated object. */ - Jim_Obj *fileNameObj; int firstline; int linenr; + int missing; } ScriptObj; void FreeScriptInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) { int i; struct ScriptObj *script = (void *)objPtr->internalRep.ptr; - script->inUse--; - if (script->inUse != 0) + if (--script->inUse != 0) return; for (i = 0; i < script->len; i++) { Jim_DecrRefCount(interp, script->token[i].objPtr); } Jim_Free(script->token); @@ -8467,11 +8651,10 @@ void DupScriptInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) { JIM_NOTUSED(interp); JIM_NOTUSED(srcPtr); - dupPtr->typePtr = NULL; } typedef struct { @@ -8666,11 +8849,11 @@ token--; } script->len = token - script->token; - assert(script->len < count); + JimPanic((script->len >= count, "allocated script array is too short")); #ifdef DEBUG_SHOW_SCRIPT printf("==== Script (%s) ====\n", Jim_String(script->fileNameObj)); for (i = 0; i < script->len; i++) { const ScriptToken *t = &script->token[i]; @@ -8677,10 +8860,35 @@ printf("[%2d] %s %s\n", i, jim_tt_name(t->type), Jim_String(t->objPtr)); } #endif } + +static int JimParseCheckMissing(Jim_Interp *interp, int ch) +{ + const char *msg; + + switch (ch) { + case '\\': + case ' ': + return JIM_OK; + + case '[': + msg = "unmatched \"[\""; + break; + case '{': + msg = "missing close-brace"; + break; + case '"': + default: + msg = "missing quote"; + break; + } + + Jim_SetResultString(interp, msg, -1); + return JIM_ERR; +} static void SubstObjAddTokens(Jim_Interp *interp, struct ScriptObj *script, ParseTokenList *tokenlist) { int i; @@ -8699,11 +8907,11 @@ } script->len = i; } -static int SetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr, struct JimParseResult *result) +static void JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr) { int scriptTextLen; const char *scriptText = Jim_GetString(objPtr, &scriptTextLen); struct JimParserCtx parser; struct ScriptObj *script; @@ -8722,16 +8930,10 @@ while (!parser.eof) { JimParseScript(&parser); ScriptAddToken(&tokenlist, parser.tstart, parser.tend - parser.tstart + 1, parser.tt, parser.tline); } - if (result && parser.missing != ' ') { - ScriptTokenListFree(&tokenlist); - result->missing = parser.missing; - result->line = parser.missingline; - return JIM_ERR; - } ScriptAddToken(&tokenlist, scriptText + scriptTextLen, 0, JIM_TT_EOF, 0); @@ -8743,10 +8945,12 @@ } else { script->fileNameObj = interp->emptyObj; } Jim_IncrRefCount(script->fileNameObj); + script->missing = parser.missing.ch; + script->linenr = parser.missing.line; ScriptObjAddTokens(interp, script, &tokenlist); ScriptTokenListFree(&tokenlist); @@ -8753,26 +8957,37 @@ Jim_FreeIntRep(interp, objPtr); Jim_SetIntRepPtr(objPtr, script); objPtr->typePtr = &scriptObjType; - - return JIM_OK; } -ScriptObj *Jim_GetScript(Jim_Interp *interp, Jim_Obj *objPtr) +static void JimAddErrorToStack(Jim_Interp *interp, ScriptObj *script); + +ScriptObj *JimGetScript(Jim_Interp *interp, Jim_Obj *objPtr) { if (objPtr == interp->emptyObj) { objPtr = interp->nullScriptObj; } if (objPtr->typePtr != &scriptObjType || ((struct ScriptObj *)Jim_GetIntRepPtr(objPtr))->substFlags) { - SetScriptFromAny(interp, objPtr, NULL); + JimSetScriptFromAny(interp, objPtr); } - return (ScriptObj *) Jim_GetIntRepPtr(objPtr); + + return (ScriptObj *)Jim_GetIntRepPtr(objPtr); } + +static int JimScriptValid(Jim_Interp *interp, ScriptObj *script) +{ + if (JimParseCheckMissing(interp, script->missing) == JIM_ERR) { + JimAddErrorToStack(interp, script); + return 0; + } + return 1; +} + static void JimIncrCmdRefCount(Jim_Cmd *cmdPtr) { cmdPtr->inUse++; } @@ -8850,10 +9065,26 @@ nsObj = Jim_DuplicateObj(interp, interp->framePtr->nsObj); Jim_AppendStrings(interp, nsObj, "::", name, NULL); } return nsObj; } + +Jim_Obj *Jim_MakeGlobalNamespaceName(Jim_Interp *interp, Jim_Obj *nameObjPtr) +{ + Jim_Obj *resultObj; + + const char *name = Jim_String(nameObjPtr); + if (name[0] == ':' && name[1] == ':') { + return nameObjPtr; + } + Jim_IncrRefCount(nameObjPtr); + resultObj = Jim_NewStringObj(interp, "::", -1); + Jim_AppendObj(interp, resultObj, nameObjPtr); + Jim_DecrRefCount(interp, nameObjPtr); + + return resultObj; +} static const char *JimQualifyName(Jim_Interp *interp, const char *name, Jim_Obj **objPtrPtr) { Jim_Obj *objPtr = interp->emptyObj; @@ -8877,10 +9108,15 @@ #else #define JimQualifyName(INTERP, NAME, DUMMY) (((NAME)[0] == ':' && (NAME)[1] == ':') ? (NAME) + 2 : (NAME)) #define JimFreeQualifiedName(INTERP, DUMMY) (void)(DUMMY) + +Jim_Obj *Jim_MakeGlobalNamespaceName(Jim_Interp *interp, Jim_Obj *nameObjPtr) +{ + return nameObjPtr; +} #endif static int JimCreateCommand(Jim_Interp *interp, const char *name, Jim_Cmd *cmd) { Jim_HashEntry *he = Jim_FindHashEntry(&interp->commands, name); @@ -8889,12 +9125,12 @@ Jim_InterpIncrProcEpoch(interp); } if (he && interp->local) { - cmd->prevCmd = he->u.val; - he->u.val = cmd; + cmd->prevCmd = Jim_GetHashEntryVal(he); + Jim_SetHashVal(&interp->commands, he, cmd); } else { if (he) { Jim_DeleteHashEntry(&interp->commands, name); @@ -8933,19 +9169,19 @@ } cmdPtr->u.proc.staticVars = Jim_Alloc(sizeof(Jim_HashTable)); Jim_InitHashTable(cmdPtr->u.proc.staticVars, &JimVariablesHashTableType, interp); for (i = 0; i < len; i++) { - Jim_Obj *objPtr = NULL, *initObjPtr = NULL, *nameObjPtr = NULL; + Jim_Obj *objPtr, *initObjPtr, *nameObjPtr; Jim_Var *varPtr; int subLen; - Jim_ListIndex(interp, staticsListObjPtr, i, &objPtr, JIM_NONE); + objPtr = Jim_ListGetIndex(interp, staticsListObjPtr, i); subLen = Jim_ListLength(interp, objPtr); if (subLen == 1 || subLen == 2) { - Jim_ListIndex(interp, objPtr, 0, &nameObjPtr, JIM_NONE); + nameObjPtr = Jim_ListGetIndex(interp, objPtr, 0); if (subLen == 1) { initObjPtr = Jim_GetVariable(interp, nameObjPtr, JIM_NONE); if (initObjPtr == NULL) { Jim_SetResultFormatted(interp, "variable for initialization of static \"%#s\" not found in the local context", @@ -8952,11 +9188,11 @@ nameObjPtr); return JIM_ERR; } } else { - Jim_ListIndex(interp, objPtr, 1, &initObjPtr, JIM_NONE); + initObjPtr = Jim_ListGetIndex(interp, objPtr, 1); } if (JimValidName(interp, "static variable", nameObjPtr) != JIM_OK) { return JIM_ERR; } @@ -9038,11 +9274,11 @@ Jim_Obj *nameObjPtr; Jim_Obj *defaultObjPtr; int len; - Jim_ListIndex(interp, argListObjPtr, i, &argPtr, JIM_NONE); + argPtr = Jim_ListGetIndex(interp, argListObjPtr, i); len = Jim_ListLength(interp, argPtr); if (len == 0) { Jim_SetResultString(interp, "argument with no name", -1); err: JimDecrCmdRefCount(interp, cmdPtr); @@ -9053,12 +9289,12 @@ goto err; } if (len == 2) { - Jim_ListIndex(interp, argPtr, 0, &nameObjPtr, JIM_NONE); - Jim_ListIndex(interp, argPtr, 1, &defaultObjPtr, JIM_NONE); + nameObjPtr = Jim_ListGetIndex(interp, argPtr, 0); + defaultObjPtr = Jim_ListGetIndex(interp, argPtr, 1); } else { nameObjPtr = argPtr; defaultObjPtr = NULL; @@ -9132,11 +9368,11 @@ else if (Jim_FindHashEntry(&interp->commands, fqnew)) { Jim_SetResultFormatted(interp, "can't rename to \"%s\": command already exists", newName); } else { - cmdPtr = he->u.val; + cmdPtr = Jim_GetHashEntryVal(he); JimIncrCmdRefCount(cmdPtr); JimUpdateProcNamespace(interp, cmdPtr, fqnew); Jim_AddHashEntry(&interp->commands, fqnew, cmdPtr); @@ -9217,11 +9453,11 @@ return NULL; } #ifdef jim_ext_namespace found: #endif - cmd = (Jim_Cmd *)he->u.val; + cmd = Jim_GetHashEntryVal(he); Jim_FreeIntRep(interp, objPtr); objPtr->typePtr = &commandObjType; objPtr->internalRep.cmdValue.procEpoch = interp->procEpoch; @@ -9323,11 +9559,11 @@ Jim_FreeIntRep(interp, objPtr); objPtr->typePtr = &variableObjType; objPtr->internalRep.varValue.callFrameId = framePtr->id; - objPtr->internalRep.varValue.varPtr = he->u.val; + objPtr->internalRep.varValue.varPtr = Jim_GetHashEntryVal(he); objPtr->internalRep.varValue.global = global; return JIM_OK; } @@ -9641,11 +9877,11 @@ } retval = Jim_DeleteHashEntry(&framePtr->vars, name); if (retval == JIM_OK) { - JimChangeCallFrameId(interp, framePtr); + framePtr->id = interp->callFrameEpoch++; } } } if (retval != JIM_OK && (flags & JIM_ERRMSG)) { Jim_SetResultFormatted(interp, "can't unset \"%#s\": no such variable", nameObjPtr); @@ -9725,28 +9961,17 @@ return NULL; } ret = Jim_DictKey(interp, dictObjPtr, keyObjPtr, &resObjPtr, JIM_NONE); if (ret != JIM_OK) { - resObjPtr = NULL; - if (ret < 0) { - Jim_SetResultFormatted(interp, - "can't read \"%#s(%#s)\": variable isn't array", varObjPtr, keyObjPtr); - } - else { - Jim_SetResultFormatted(interp, - "can't read \"%#s(%#s)\": no such element in array", varObjPtr, keyObjPtr); - } + Jim_SetResultFormatted(interp, + "can't read \"%#s(%#s)\": %s array", varObjPtr, keyObjPtr, + ret < 0 ? "variable isn't" : "no such element in"); } else if ((flags & JIM_UNSHARED) && Jim_IsShared(dictObjPtr)) { - dictObjPtr = Jim_DuplicateObj(interp, dictObjPtr); - if (Jim_SetVariable(interp, varObjPtr, dictObjPtr) != JIM_OK) { - - JimPanic((1, "SetVariable failed for JIM_UNSHARED")); - } - Jim_DictKey(interp, dictObjPtr, keyObjPtr, &resObjPtr, JIM_NONE); + Jim_SetVariable(interp, varObjPtr, Jim_DuplicateObj(interp, dictObjPtr)); } return resObjPtr; } @@ -9843,68 +10068,67 @@ Jim_CallFrame *cf; if (interp->freeFramesList) { cf = interp->freeFramesList; interp->freeFramesList = cf->next; + + cf->argv = NULL; + cf->argc = 0; + cf->procArgsObjPtr = NULL; + cf->procBodyObjPtr = NULL; + cf->next = NULL; + cf->staticVars = NULL; + cf->localCommands = NULL; + cf->tailcall = 0; + cf->tailcallObj = NULL; + cf->tailcallCmd = NULL; } else { cf = Jim_Alloc(sizeof(*cf)); - cf->vars.table = NULL; + memset(cf, 0, sizeof(*cf)); + + Jim_InitHashTable(&cf->vars, &JimVariablesHashTableType, interp); } cf->id = interp->callFrameEpoch++; cf->parent = parent; cf->level = parent ? parent->level + 1 : 0; - cf->argv = NULL; - cf->argc = 0; - cf->procArgsObjPtr = NULL; - cf->procBodyObjPtr = NULL; - cf->next = NULL; - cf->staticVars = NULL; - cf->localCommands = NULL; - cf->nsObj = nsObj; Jim_IncrRefCount(nsObj); - if (cf->vars.table == NULL) - Jim_InitHashTable(&cf->vars, &JimVariablesHashTableType, interp); + return cf; } - -static void JimChangeCallFrameId(Jim_Interp *interp, Jim_CallFrame *cf) -{ - cf->id = interp->callFrameEpoch++; -} - static int JimDeleteLocalProcs(Jim_Interp *interp, Jim_Stack *localCommands) { if (localCommands) { Jim_Obj *cmdNameObj; while ((cmdNameObj = Jim_StackPop(localCommands)) != NULL) { Jim_HashEntry *he; Jim_Obj *fqObjName; + Jim_HashTable *ht = &interp->commands; const char *fqname = JimQualifyName(interp, Jim_String(cmdNameObj), &fqObjName); - he = Jim_FindHashEntry(&interp->commands, fqname); + he = Jim_FindHashEntry(ht, fqname); if (he) { - Jim_Cmd *cmd = he->u.val; + Jim_Cmd *cmd = Jim_GetHashEntryVal(he); if (cmd->prevCmd) { Jim_Cmd *prevCmd = cmd->prevCmd; cmd->prevCmd = NULL; JimDecrCmdRefCount(interp, cmd); - he->u.val = prevCmd; + Jim_SetHashVal(ht, he, prevCmd); } else { - Jim_DeleteHashEntry(&interp->commands, fqname); + Jim_DeleteHashEntry(ht, fqname); Jim_InterpIncrProcEpoch(interp); } } Jim_DecrRefCount(interp, cmdNameObj); JimFreeQualifiedName(interp, fqObjName); @@ -9914,47 +10138,45 @@ } return JIM_OK; } -#define JIM_FCF_NONE 0 -#define JIM_FCF_NOHT 1 -static void JimFreeCallFrame(Jim_Interp *interp, Jim_CallFrame *cf, int flags) -{ +#define JIM_FCF_FULL 0 +#define JIM_FCF_REUSE 1 +static void JimFreeCallFrame(Jim_Interp *interp, Jim_CallFrame *cf, int action) + { + JimDeleteLocalProcs(interp, cf->localCommands); + if (cf->procArgsObjPtr) Jim_DecrRefCount(interp, cf->procArgsObjPtr); if (cf->procBodyObjPtr) Jim_DecrRefCount(interp, cf->procBodyObjPtr); Jim_DecrRefCount(interp, cf->nsObj); - if (!(flags & JIM_FCF_NOHT)) + if (action == JIM_FCF_FULL || cf->vars.size != JIM_HT_INITIAL_SIZE) Jim_FreeHashTable(&cf->vars); else { int i; Jim_HashEntry **table = cf->vars.table, *he; for (i = 0; i < JIM_HT_INITIAL_SIZE; i++) { he = table[i]; while (he != NULL) { Jim_HashEntry *nextEntry = he->next; - Jim_Var *varPtr = (void *)he->u.val; + Jim_Var *varPtr = Jim_GetHashEntryVal(he); Jim_DecrRefCount(interp, varPtr->objPtr); - Jim_Free(he->u.val); - Jim_Free((void *)he->key); + Jim_Free(Jim_GetHashEntryKey(he)); + Jim_Free(varPtr); Jim_Free(he); table[i] = NULL; he = nextEntry; } } cf->vars.used = 0; } - - JimDeleteLocalProcs(interp, cf->localCommands); - cf->next = interp->freeFramesList; interp->freeFramesList = cf; - } #ifdef JIM_REFERENCES @@ -10031,21 +10253,16 @@ NULL, UpdateStringOfReference, JIM_TYPE_REFERENCES, }; -void UpdateStringOfReference(struct Jim_Obj *objPtr) +static void UpdateStringOfReference(struct Jim_Obj *objPtr) { - int len; char buf[JIM_REFERENCE_SPACE + 1]; - Jim_Reference *refPtr; - refPtr = objPtr->internalRep.refValue.refPtr; - len = JimFormatReference(buf, refPtr, objPtr->internalRep.refValue.id); - objPtr->bytes = Jim_Alloc(len + 1); - memcpy(objPtr->bytes, buf, len + 1); - objPtr->length = len; + JimFormatReference(buf, objPtr->internalRep.refValue.refPtr, objPtr->internalRep.refValue.id); + JimSetStringBytes(objPtr, buf); } static int isrefchar(int c) { return (c == '_' || isalnum(c)); @@ -10096,11 +10313,11 @@ he = Jim_FindHashEntry(&interp->references, &value); if (he == NULL) { Jim_SetResultFormatted(interp, "invalid reference id \"%#s\"", objPtr); return JIM_ERR; } - refPtr = he->u.val; + refPtr = Jim_GetHashEntryVal(he); Jim_FreeIntRep(interp, objPtr); objPtr->typePtr = &referenceObjType; objPtr->internalRep.refValue.id = value; objPtr->internalRep.refValue.refPtr = refPtr; @@ -10213,11 +10430,11 @@ Jim_Collect(interp); } } #endif -static int JimIsBigEndian(void) +int Jim_IsBigEndian(void) { union { unsigned short s; unsigned char c[2]; } uval = {0x0102}; @@ -10269,23 +10486,30 @@ Jim_SetVariableStrWithStr(i, JIM_INTERACTIVE, "0"); Jim_SetVariableStrWithStr(i, "tcl_platform(os)", TCL_PLATFORM_OS); Jim_SetVariableStrWithStr(i, "tcl_platform(platform)", TCL_PLATFORM_PLATFORM); Jim_SetVariableStrWithStr(i, "tcl_platform(pathSeparator)", TCL_PLATFORM_PATH_SEPARATOR); - Jim_SetVariableStrWithStr(i, "tcl_platform(byteOrder)", JimIsBigEndian() ? "bigEndian" : "littleEndian"); + Jim_SetVariableStrWithStr(i, "tcl_platform(byteOrder)", Jim_IsBigEndian() ? "bigEndian" : "littleEndian"); Jim_SetVariableStrWithStr(i, "tcl_platform(threaded)", "0"); Jim_SetVariableStr(i, "tcl_platform(pointerSize)", Jim_NewIntObj(i, sizeof(void *))); Jim_SetVariableStr(i, "tcl_platform(wordSize)", Jim_NewIntObj(i, sizeof(jim_wide))); return i; } void Jim_FreeInterp(Jim_Interp *i) { - Jim_CallFrame *cf = i->framePtr, *prevcf, *nextcf; + Jim_CallFrame *cf, *cfx; + Jim_Obj *objPtr, *nextObjPtr; + + for (cf = i->framePtr; cf; cf = cfx) { + cfx = cf->parent; + JimFreeCallFrame(i, cf, JIM_FCF_FULL); + } + Jim_DecrRefCount(i, i->emptyObj); Jim_DecrRefCount(i, i->trueObj); Jim_DecrRefCount(i, i->falseObj); Jim_DecrRefCount(i, i->result); Jim_DecrRefCount(i, i->stackTrace); @@ -10300,61 +10524,54 @@ #endif Jim_FreeHashTable(&i->packages); Jim_Free(i->prngState); Jim_FreeHashTable(&i->assocData); - - while (cf) { - prevcf = cf->parent; - JimFreeCallFrame(i, cf, JIM_FCF_NONE); - cf = prevcf; - } +#ifdef JIM_MAINTAINER if (i->liveList != NULL) { objPtr = i->liveList; - printf(JIM_NL "-------------------------------------" JIM_NL); - printf("Objects still in the free list:" JIM_NL); + printf("\n-------------------------------------\n"); + printf("Objects still in the free list:\n"); while (objPtr) { const char *type = objPtr->typePtr ? objPtr->typePtr->name : "string"; if (objPtr->bytes && strlen(objPtr->bytes) > 20) { - printf("%p (%d) %-10s: '%.20s...'" JIM_NL, + printf("%p (%d) %-10s: '%.20s...'\n", (void *)objPtr, objPtr->refCount, type, objPtr->bytes); } else { - printf("%p (%d) %-10s: '%s'" JIM_NL, + printf("%p (%d) %-10s: '%s'\n", (void *)objPtr, objPtr->refCount, type, objPtr->bytes ? objPtr->bytes : "(null)"); } if (objPtr->typePtr == &sourceObjType) { - printf("FILE %s LINE %d" JIM_NL, + printf("FILE %s LINE %d\n", Jim_String(objPtr->internalRep.sourceValue.fileNameObj), objPtr->internalRep.sourceValue.lineNumber); } objPtr = objPtr->nextObjPtr; } - printf("-------------------------------------" JIM_NL JIM_NL); + printf("-------------------------------------\n\n"); JimPanic((1, "Live list non empty freeing the interpreter! Leak?")); } +#endif + objPtr = i->freeList; while (objPtr) { nextObjPtr = objPtr->nextObjPtr; Jim_Free(objPtr); objPtr = nextObjPtr; } + - cf = i->freeFramesList; - while (cf) { - nextcf = cf->next; - if (cf->vars.table != NULL) - Jim_Free(cf->vars.table); + for (cf = i->freeFramesList; cf; cf = cfx) { + cfx = cf->next; + if (cf->vars.table) + Jim_FreeHashTable(&cf->vars); Jim_Free(cf); - cf = nextcf; } -#ifdef jim_ext_load - Jim_FreeLoadHandles(i); -#endif Jim_Free(i); } @@ -10449,22 +10666,15 @@ interp->stackTrace = stackTraceObj; interp->errorFlag = 1; len = Jim_ListLength(interp, interp->stackTrace); if (len >= 3) { - Jim_Obj *filenameObj; - - Jim_ListIndex(interp, interp->stackTrace, len - 2, &filenameObj, JIM_NONE); - - Jim_GetString(filenameObj, &len); - - if (!Jim_Length(filenameObj)) { + if (Jim_Length(Jim_ListGetIndex(interp, interp->stackTrace, len - 2)) == 0) { interp->addStackTrace = 1; } } } - static void JimAppendStackTrace(Jim_Interp *interp, const char *procname, Jim_Obj *fileNameObj, int linenr) { if (strcmp(procname, "unknown") == 0) { @@ -10485,14 +10695,15 @@ if (!*procname && Jim_Length(fileNameObj)) { int len = Jim_ListLength(interp, interp->stackTrace); if (len >= 3) { - Jim_Obj *objPtr; - if (Jim_ListIndex(interp, interp->stackTrace, len - 3, &objPtr, JIM_NONE) == JIM_OK && Jim_Length(objPtr)) { + Jim_Obj *objPtr = Jim_ListGetIndex(interp, interp->stackTrace, len - 3); + if (Jim_Length(objPtr)) { - if (Jim_ListIndex(interp, interp->stackTrace, len - 2, &objPtr, JIM_NONE) == JIM_OK && !Jim_Length(objPtr)) { + objPtr = Jim_ListGetIndex(interp, interp->stackTrace, len - 2); + if (Jim_Length(objPtr) == 0) { ListSetIndex(interp, interp->stackTrace, len - 2, fileNameObj, 0); ListSetIndex(interp, interp->stackTrace, len - 1, Jim_NewIntObj(interp, linenr), 0); return; } @@ -10518,12 +10729,11 @@ void *Jim_GetAssocData(Jim_Interp *interp, const char *key) { Jim_HashEntry *entryPtr = Jim_FindHashEntry(&interp->assocData, key); if (entryPtr != NULL) { - AssocDataValue *assocEntryPtr = (AssocDataValue *) entryPtr->u.val; - + AssocDataValue *assocEntryPtr = Jim_GetHashEntryVal(entryPtr); return assocEntryPtr->data; } return NULL; } @@ -10557,20 +10767,44 @@ }; static void UpdateStringOfInt(struct Jim_Obj *objPtr) { - int len; char buf[JIM_INTEGER_SPACE + 1]; + jim_wide wideValue = JimWideValue(objPtr); + int pos = 0; - len = JimWideToString(buf, JimWideValue(objPtr)); - objPtr->bytes = Jim_Alloc(len + 1); - memcpy(objPtr->bytes, buf, len + 1); - objPtr->length = len; + if (wideValue == 0) { + buf[pos++] = '0'; + } + else { + char tmp[JIM_INTEGER_SPACE]; + int num = 0; + int i; + + if (wideValue < 0) { + buf[pos++] = '-'; + i = wideValue % 10; + tmp[num++] = (i > 0) ? (10 - i) : -i; + wideValue /= -10; + } + + while (wideValue) { + tmp[num++] = wideValue % 10; + wideValue /= 10; + } + + for (i = 0; i < num; i++) { + buf[pos++] = '0' + tmp[num - i - 1]; + } + } + buf[pos] = 0; + + JimSetStringBytes(objPtr, buf); } -int SetIntFromAny(Jim_Interp *interp, Jim_Obj *objPtr, int flags) +static int SetIntFromAny(Jim_Interp *interp, Jim_Obj *objPtr, int flags) { jim_wide wideValue; const char *str; if (objPtr->typePtr == &coercedDoubleObjType) { @@ -10658,22 +10892,65 @@ NULL, UpdateStringOfDouble, JIM_TYPE_NONE, }; -void UpdateStringOfDouble(struct Jim_Obj *objPtr) -{ - int len; - char buf[JIM_DOUBLE_SPACE + 1]; - - len = Jim_DoubleToString(buf, objPtr->internalRep.doubleValue); - objPtr->bytes = Jim_Alloc(len + 1); - memcpy(objPtr->bytes, buf, len + 1); - objPtr->length = len; +#ifndef HAVE_ISNAN +#undef isnan +#define isnan(X) ((X) != (X)) +#endif +#ifndef HAVE_ISINF +#undef isinf +#define isinf(X) (1.0 / (X) == 0.0) +#endif + +static void UpdateStringOfDouble(struct Jim_Obj *objPtr) +{ + double value = objPtr->internalRep.doubleValue; + + if (isnan(value)) { + JimSetStringBytes(objPtr, "NaN"); + return; + } + if (isinf(value)) { + if (value < 0) { + JimSetStringBytes(objPtr, "-Inf"); + } + else { + JimSetStringBytes(objPtr, "Inf"); + } + return; + } + { + char buf[JIM_DOUBLE_SPACE + 1]; + int i; + int len = sprintf(buf, "%.12g", value); + + + for (i = 0; i < len; i++) { + if (buf[i] == '.' || buf[i] == 'e') { +#if defined(JIM_SPRINTF_DOUBLE_NEEDS_FIX) + char *e = strchr(buf, 'e'); + if (e && (e[1] == '-' || e[1] == '+') && e[2] == '0') { + + e += 2; + memmove(e, e + 1, len - (e - buf)); + } +#endif + break; + } + } + if (buf[i] == '\0') { + buf[i++] = '.'; + buf[i++] = '0'; + buf[i] = '\0'; + } + JimSetStringBytes(objPtr, buf); + } } -int SetDoubleFromAny(Jim_Interp *interp, Jim_Obj *objPtr) +static int SetDoubleFromAny(Jim_Interp *interp, Jim_Obj *objPtr) { double doubleValue; jim_wide wideValue; const char *str; @@ -10702,11 +10979,11 @@ return JIM_OK; } else { if (Jim_StringToDouble(str, &doubleValue) != JIM_OK) { - Jim_SetResultFormatted(interp, "expected number but got \"%#s\"", objPtr); + Jim_SetResultFormatted(interp, "expected floating-point number but got \"%#s\"", objPtr); return JIM_ERR; } Jim_FreeIntRep(interp, objPtr); } @@ -10881,15 +11158,15 @@ return JIM_ELESTR_SIMPLE; } return JIM_ELESTR_QUOTE; } -static int BackslashQuoteString(const char *s, char *q) +static int BackslashQuoteString(const char *s, int len, char *q) { char *p = q; - while (*s) { + while (len--) { switch (*s) { case ' ': case '$': case '"': case '[': @@ -11001,11 +11278,11 @@ case JIM_ELESTR_QUOTE: if (i == 0 && strRep[0] == '#') { *p++ = '\\'; realLength++; } - qlen = BackslashQuoteString(strRep, p); + qlen = BackslashQuoteString(strRep, len, p); p += qlen; realLength += qlen; break; } @@ -11037,11 +11314,11 @@ if (objPtr->typePtr == &listObjType) { return JIM_OK; } - if (Jim_IsDict(objPtr) && !Jim_IsShared(objPtr)) { + if (Jim_IsDict(objPtr) && objPtr->bytes == NULL) { Jim_Obj **listObjPtrPtr; int len; int i; listObjPtrPtr = JimDictPairs(objPtr, &len); @@ -11141,15 +11418,17 @@ Jim_Interp *interp; enum { JIM_LSORT_ASCII, JIM_LSORT_NOCASE, JIM_LSORT_INTEGER, + JIM_LSORT_REAL, JIM_LSORT_COMMAND } type; int order; int index; int indexed; + int unique; int (*subfn)(Jim_Obj **, Jim_Obj **); }; static struct lsort_info *sort_info; @@ -11184,10 +11463,27 @@ longjmp(sort_info->jmpbuf, JIM_ERR); } return JimSign(lhs - rhs) * sort_info->order; } + +static int ListSortReal(Jim_Obj **lhsObj, Jim_Obj **rhsObj) +{ + double lhs = 0, rhs = 0; + + if (Jim_GetDouble(sort_info->interp, *lhsObj, &lhs) != JIM_OK || + Jim_GetDouble(sort_info->interp, *rhsObj, &rhs) != JIM_OK) { + longjmp(sort_info->jmpbuf, JIM_ERR); + } + if (lhs == rhs) { + return 0; + } + if (lhs > rhs) { + return sort_info->order; + } + return -sort_info->order; +} static int ListSortCommand(Jim_Obj **lhsObj, Jim_Obj **rhsObj) { Jim_Obj *compare_script; int rc; @@ -11206,10 +11502,34 @@ } return JimSign(ret) * sort_info->order; } +static void ListRemoveDuplicates(Jim_Obj *listObjPtr, int (*comp)(Jim_Obj **lhs, Jim_Obj **rhs)) +{ + int src; + int dst = 0; + Jim_Obj **ele = listObjPtr->internalRep.listValue.ele; + + for (src = 1; src < listObjPtr->internalRep.listValue.len; src++) { + if (comp(&ele[dst], &ele[src]) == 0) { + + Jim_DecrRefCount(sort_info->interp, ele[dst]); + } + else { + + dst++; + } + ele[dst] = ele[src]; + } + + ele[++dst] = ele[src]; + + + listObjPtr->internalRep.listValue.len = dst; +} + static int ListSortElements(Jim_Interp *interp, Jim_Obj *listObjPtr, struct lsort_info *info) { struct lsort_info *prev_info; @@ -11217,11 +11537,11 @@ int (*fn) (Jim_Obj **, Jim_Obj **); Jim_Obj **vector; int len; int rc; - JimPanic((Jim_IsShared(listObjPtr), "Jim_ListSortElements called with shared object")); + JimPanic((Jim_IsShared(listObjPtr), "ListSortElements called with shared object")); SetListFromAny(interp, listObjPtr); prev_info = sort_info; sort_info = info; @@ -11236,10 +11556,13 @@ fn = ListSortStringNoCase; break; case JIM_LSORT_INTEGER: fn = ListSortInteger; break; + case JIM_LSORT_REAL: + fn = ListSortReal; + break; case JIM_LSORT_COMMAND: fn = ListSortCommand; break; default: fn = NULL; @@ -11252,12 +11575,17 @@ fn = ListSortIndexHelper; } if ((rc = setjmp(info->jmpbuf)) == 0) { qsort(vector, len, sizeof(Jim_Obj *), (qsort_comparator *) fn); + + if (info->unique && len > 1) { + ListRemoveDuplicates(listObjPtr, fn); + } + + Jim_InvalidateStringRep(listObjPtr); } - Jim_InvalidateStringRep(listObjPtr); sort_info = prev_info; return rc; } @@ -11382,11 +11710,11 @@ listPtr->internalRep.listValue.ele[idx] = newObjPtr; Jim_IncrRefCount(newObjPtr); return JIM_OK; } -int Jim_SetListIndex(Jim_Interp *interp, Jim_Obj *varNamePtr, +int Jim_ListSetIndex(Jim_Interp *interp, Jim_Obj *varNamePtr, Jim_Obj *const *indexv, int indexc, Jim_Obj *newObjPtr) { Jim_Obj *varObjPtr, *objPtr, *listObjPtr; int shared, i, idx; @@ -11430,14 +11758,11 @@ int i; int listLen = Jim_ListLength(interp, listObjPtr); Jim_Obj *resObjPtr = Jim_NewEmptyStringObj(interp); for (i = 0; i < listLen; ) { - Jim_Obj *objPtr; - - Jim_ListIndex(interp, listObjPtr, i, &objPtr, JIM_NONE); - Jim_AppendObj(interp, resObjPtr, objPtr); + Jim_AppendObj(interp, resObjPtr, Jim_ListGetIndex(interp, listObjPtr, i)); if (++i != listLen) { Jim_AppendString(interp, resObjPtr, joinStr, joinStrLen); } } return resObjPtr; @@ -11463,43 +11788,42 @@ int len = 0, objLen; char *bytes, *p; for (i = 0; i < objc; i++) { - Jim_GetString(objv[i], &objLen); - len += objLen; + len += Jim_Length(objv[i]); } if (objc) len += objc - 1; p = bytes = Jim_Alloc(len + 1); for (i = 0; i < objc; i++) { const char *s = Jim_GetString(objv[i], &objLen); - while (objLen && (*s == ' ' || *s == '\t' || *s == '\n')) { + while (objLen && isspace(UCHAR(*s))) { s++; objLen--; len--; } - while (objLen && (s[objLen - 1] == ' ' || - s[objLen - 1] == '\n' || s[objLen - 1] == '\t')) { + while (objLen && isspace(UCHAR(s[objLen - 1]))) { if (objLen > 1 && s[objLen - 2] == '\\') { break; } objLen--; len--; } memcpy(p, s, objLen); p += objLen; - if (objLen && i + 1 != objc) { - *p++ = ' '; - } - else if (i + 1 != objc) { - len--; + if (i + 1 != objc) { + if (objLen) + *p++ = ' '; + else { + len--; + } } } *p = '\0'; return Jim_NewStringObjNoAlloc(interp, bytes, len); } @@ -11539,20 +11863,26 @@ static int JimObjectHTKeyCompare(void *privdata, const void *key1, const void *key2) { return Jim_StringEqObj((Jim_Obj *)key1, (Jim_Obj *)key2); } + +static void *JimObjectHTKeyValDup(void *privdata, const void *val) +{ + Jim_IncrRefCount((Jim_Obj *)val); + return (void *)val; +} static void JimObjectHTKeyValDestructor(void *interp, void *val) { Jim_DecrRefCount(interp, (Jim_Obj *)val); } static const Jim_HashTableType JimDictHashTableType = { JimObjectHTHashFunction, - NULL, - NULL, + JimObjectHTKeyValDup, + JimObjectHTKeyValDup, JimObjectHTKeyCompare, JimObjectHTKeyValDestructor, JimObjectHTKeyValDestructor }; @@ -11585,16 +11915,11 @@ if (ht->size != 0) Jim_ExpandHashTable(dupHt, ht->size); JimInitHashTableIterator(ht, &htiter); while ((he = Jim_NextHashEntry(&htiter)) != NULL) { - const Jim_Obj *keyObjPtr = he->key; - Jim_Obj *valObjPtr = he->u.val; - - Jim_IncrRefCount((Jim_Obj *)keyObjPtr); - Jim_IncrRefCount(valObjPtr); - Jim_AddHashEntry(dupHt, keyObjPtr, valObjPtr); + Jim_AddHashEntry(dupHt, he->key, he->u.val); } dupPtr->internalRep.ptr = dupHt; dupPtr->typePtr = &dictObjType; } @@ -11612,12 +11937,12 @@ objv = Jim_Alloc((ht->used * 2) * sizeof(Jim_Obj *)); JimInitHashTableIterator(ht, &htiter); i = 0; while ((he = Jim_NextHashEntry(&htiter)) != NULL) { - objv[i++] = (Jim_Obj *)he->key; - objv[i++] = he->u.val; + objv[i++] = Jim_GetHashEntryKey(he); + objv[i++] = Jim_GetHashEntryVal(he); } *len = i; return objv; } @@ -11625,10 +11950,11 @@ { int len; Jim_Obj **objv = JimDictPairs(objPtr, &len); + JimMakeListStringRep(objPtr, objv, len); Jim_Free(objv); } @@ -11638,11 +11964,13 @@ if (objPtr->typePtr == &dictObjType) { return JIM_OK; } - Jim_String(objPtr); + if (Jim_IsList(objPtr) && Jim_IsShared(objPtr)) { + Jim_String(objPtr); + } listlen = Jim_ListLength(interp, objPtr); if (listlen % 2) { Jim_SetResultString(interp, "missing value to go with key", -1); @@ -11655,28 +11983,14 @@ ht = Jim_Alloc(sizeof(*ht)); Jim_InitHashTable(ht, &JimDictHashTableType, interp); for (i = 0; i < listlen; i += 2) { - Jim_Obj *keyObjPtr; - Jim_Obj *valObjPtr; - - Jim_ListIndex(interp, objPtr, i, &keyObjPtr, JIM_NONE); - Jim_ListIndex(interp, objPtr, i + 1, &valObjPtr, JIM_NONE); - - Jim_IncrRefCount(keyObjPtr); - Jim_IncrRefCount(valObjPtr); - - if (Jim_AddHashEntry(ht, keyObjPtr, valObjPtr) != JIM_OK) { - Jim_HashEntry *he; - - he = Jim_FindHashEntry(ht, keyObjPtr); - Jim_DecrRefCount(interp, keyObjPtr); - - Jim_DecrRefCount(interp, (Jim_Obj *)he->u.val); - he->u.val = valObjPtr; - } + Jim_Obj *keyObjPtr = Jim_ListGetIndex(interp, objPtr, i); + Jim_Obj *valObjPtr = Jim_ListGetIndex(interp, objPtr, i + 1); + + Jim_ReplaceHashEntry(ht, keyObjPtr, valObjPtr); } Jim_FreeIntRep(interp, objPtr); objPtr->typePtr = &dictObjType; objPtr->internalRep.ptr = ht; @@ -11693,31 +12007,23 @@ Jim_HashTable *ht = objPtr->internalRep.ptr; if (valueObjPtr == NULL) { return Jim_DeleteHashEntry(ht, keyObjPtr); } - Jim_IncrRefCount(keyObjPtr); - Jim_IncrRefCount(valueObjPtr); - if (Jim_ReplaceHashEntry(ht, keyObjPtr, valueObjPtr)) { - - Jim_DecrRefCount(interp, keyObjPtr); - } + Jim_ReplaceHashEntry(ht, keyObjPtr, valueObjPtr); return JIM_OK; } int Jim_DictAddElement(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *keyObjPtr, Jim_Obj *valueObjPtr) { - int retcode; - JimPanic((Jim_IsShared(objPtr), "Jim_DictAddElement called with shared object")); if (SetDictFromAny(interp, objPtr) != JIM_OK) { return JIM_ERR; } - retcode = DictAddElement(interp, objPtr, keyObjPtr, valueObjPtr); Jim_InvalidateStringRep(objPtr); - return retcode; + return DictAddElement(interp, objPtr, keyObjPtr, valueObjPtr); } Jim_Obj *Jim_NewDictObj(Jim_Interp *interp, Jim_Obj *const *elements, int len) { Jim_Obj *objPtr; @@ -11844,10 +12150,11 @@ } objPtr = Jim_NewDictObj(interp, NULL, 0); DictAddElement(interp, dictObjPtr, keyv[i], objPtr); } } + Jim_InvalidateStringRep(objPtr); Jim_InvalidateStringRep(varObjPtr); if (Jim_SetVariable(interp, varNamePtr, varObjPtr) != JIM_OK) { goto err; } @@ -11869,28 +12176,29 @@ NULL, UpdateStringOfIndex, JIM_TYPE_NONE, }; -void UpdateStringOfIndex(struct Jim_Obj *objPtr) -{ - int len; - char buf[JIM_INTEGER_SPACE + 1]; - - if (objPtr->internalRep.intValue >= 0) - len = sprintf(buf, "%d", objPtr->internalRep.intValue); - else if (objPtr->internalRep.intValue == -1) - len = sprintf(buf, "end"); +static void UpdateStringOfIndex(struct Jim_Obj *objPtr) +{ + if (objPtr->internalRep.intValue == -1) { + JimSetStringBytes(objPtr, "end"); + } else { - len = sprintf(buf, "end%d", objPtr->internalRep.intValue + 1); + char buf[JIM_INTEGER_SPACE + 1]; + if (objPtr->internalRep.intValue >= 0) { + sprintf(buf, "%d", objPtr->internalRep.intValue); + } + else { + + sprintf(buf, "end%d", objPtr->internalRep.intValue + 1); + } + JimSetStringBytes(objPtr, buf); } - objPtr->bytes = Jim_Alloc(len + 1); - memcpy(objPtr->bytes, buf, len + 1); - objPtr->length = len; } -int SetIndexFromAny(Jim_Interp *interp, Jim_Obj *objPtr) +static int SetIndexFromAny(Jim_Interp *interp, Jim_Obj *objPtr) { int idx, end = 0; const char *str; char *endptr; @@ -11958,14 +12266,17 @@ { if (objPtr->typePtr == &intObjType) { jim_wide val = JimWideValue(objPtr); - if (!(val < LONG_MIN) && !(val > LONG_MAX)) { - *indexPtr = (val < 0) ? -INT_MAX : (long)val;; - return JIM_OK; - } + if (val < 0) + *indexPtr = -INT_MAX; + else if (val > INT_MAX) + *indexPtr = INT_MAX; + else + *indexPtr = (int)val; + return JIM_OK; } if (objPtr->typePtr != &indexObjType && SetIndexFromAny(interp, objPtr) == JIM_ERR) return JIM_ERR; *indexPtr = objPtr->internalRep.intValue; return JIM_OK; @@ -11985,12 +12296,10 @@ NULL }; #define jimReturnCodesSize (sizeof(jimReturnCodes)/sizeof(*jimReturnCodes)) -static int SetReturnCodeFromAny(Jim_Interp *interp, Jim_Obj *objPtr); - static const Jim_ObjType returnCodeObjType = { "return-code", NULL, NULL, NULL, @@ -12005,11 +12314,11 @@ else { return jimReturnCodes[code]; } } -int SetReturnCodeFromAny(Jim_Interp *interp, Jim_Obj *objPtr) +static int SetReturnCodeFromAny(Jim_Interp *interp, Jim_Obj *objPtr) { int returnCode; jim_wide wideValue; @@ -12100,10 +12409,11 @@ JIM_EXPROP_UNARYPLUS, JIM_EXPROP_FUNC_FIRST, JIM_EXPROP_FUNC_INT = JIM_EXPROP_FUNC_FIRST, + JIM_EXPROP_FUNC_WIDE, JIM_EXPROP_FUNC_ABS, JIM_EXPROP_FUNC_DOUBLE, JIM_EXPROP_FUNC_ROUND, JIM_EXPROP_FUNC_RAND, JIM_EXPROP_FUNC_SRAND, @@ -12157,24 +12467,22 @@ return e->stack[--e->stacklen]; } static int JimExprOpNumUnary(Jim_Interp *interp, struct JimExprState *e) { - int intresult = 0; + int intresult = 1; int rc = JIM_OK; Jim_Obj *A = ExprPop(e); double dA, dC = 0; jim_wide wA, wC = 0; if ((A->typePtr != &doubleObjType || A->bytes) && JimGetWideNoErr(interp, A, &wA) == JIM_OK) { - intresult = 1; - switch (e->opcode) { case JIM_EXPROP_FUNC_INT: - wC = wA; - break; + case JIM_EXPROP_FUNC_WIDE: case JIM_EXPROP_FUNC_ROUND: + case JIM_EXPROP_UNARYPLUS: wC = wA; break; case JIM_EXPROP_FUNC_DOUBLE: dC = wA; intresult = 0; @@ -12183,13 +12491,10 @@ wC = wA >= 0 ? wA : -wA; break; case JIM_EXPROP_UNARYMINUS: wC = -wA; break; - case JIM_EXPROP_UNARYPLUS: - wC = wA; - break; case JIM_EXPROP_NOT: wC = !wA; break; default: abort(); @@ -12196,32 +12501,31 @@ } } else if ((rc = Jim_GetDouble(interp, A, &dA)) == JIM_OK) { switch (e->opcode) { case JIM_EXPROP_FUNC_INT: + case JIM_EXPROP_FUNC_WIDE: wC = dA; - intresult = 1; break; case JIM_EXPROP_FUNC_ROUND: wC = dA < 0 ? (dA - 0.5) : (dA + 0.5); - intresult = 1; break; case JIM_EXPROP_FUNC_DOUBLE: + case JIM_EXPROP_UNARYPLUS: dC = dA; + intresult = 0; break; case JIM_EXPROP_FUNC_ABS: dC = dA >= 0 ? dA : -dA; + intresult = 0; break; case JIM_EXPROP_UNARYMINUS: dC = -dA; - break; - case JIM_EXPROP_UNARYPLUS: - dC = dA; + intresult = 0; break; case JIM_EXPROP_NOT: wC = !dA; - intresult = 1; break; default: abort(); } } @@ -12432,11 +12736,11 @@ static int JimExprOpBin(Jim_Interp *interp, struct JimExprState *e) { - int intresult = 0; + int intresult = 1; int rc = JIM_OK; double dA, dB, dC = 0; jim_wide wA, wB, wC = 0; Jim_Obj *B = ExprPop(e); @@ -12446,12 +12750,10 @@ (B->typePtr != &doubleObjType || B->bytes) && JimGetWideNoErr(interp, A, &wA) == JIM_OK && JimGetWideNoErr(interp, B, &wB) == JIM_OK) { - intresult = 1; - switch (e->opcode) { case JIM_EXPROP_POW: case JIM_EXPROP_FUNC_POW: wC = JimPowWide(wA, wB); break; @@ -12501,10 +12803,11 @@ default: abort(); } } else if (Jim_GetDouble(interp, A, &dA) == JIM_OK && Jim_GetDouble(interp, B, &dB) == JIM_OK) { + intresult = 0; switch (e->opcode) { case JIM_EXPROP_POW: case JIM_EXPROP_FUNC_POW: #ifdef JIM_MATH_FUNCTIONS dC = pow(dA, dB); @@ -12566,12 +12869,10 @@ int i = Jim_StringCompareObj(interp, A, B, 0); - intresult = 1; - switch (e->opcode) { case JIM_EXPROP_LT: wC = i < 0; break; case JIM_EXPROP_GT: @@ -12615,15 +12916,11 @@ int listlen; int i; listlen = Jim_ListLength(interp, listObjPtr); for (i = 0; i < listlen; i++) { - Jim_Obj *objPtr; - - Jim_ListIndex(interp, listObjPtr, i, &objPtr, JIM_NONE); - - if (Jim_StringEqObj(objPtr, valObj)) { + if (Jim_StringEqObj(Jim_ListGetIndex(interp, listObjPtr, i), valObj)) { return 1; } } return 0; } @@ -12635,23 +12932,16 @@ jim_wide wC; switch (e->opcode) { case JIM_EXPROP_STREQ: - case JIM_EXPROP_STRNE: { - int Alen, Blen; - const char *sA = Jim_GetString(A, &Alen); - const char *sB = Jim_GetString(B, &Blen); - - if (e->opcode == JIM_EXPROP_STREQ) { - wC = (Alen == Blen && memcmp(sA, sB, Alen) == 0); - } - else { - wC = (Alen != Blen || memcmp(sA, sB, Alen) != 0); + case JIM_EXPROP_STRNE: + wC = Jim_StringEqObj(A, B); + if (e->opcode == JIM_EXPROP_STRNE) { + wC = !wC; } break; - } case JIM_EXPROP_STRIN: wC = JimSearchList(interp, B, A); break; case JIM_EXPROP_STRNI: wC = !JimSearchList(interp, B, A); @@ -12823,96 +13113,99 @@ LAZY_OP, LAZY_LEFT, LAZY_RIGHT }; -#define OPRINIT(N, P, A, F, L) {N, F, P, A, L, sizeof(N) - 1} +#define OPRINIT(N, P, A, F) {N, F, P, A, LAZY_NONE, sizeof(N) - 1} +#define OPRINIT_LAZY(N, P, A, F, L) {N, F, P, A, L, sizeof(N) - 1} static const struct Jim_ExprOperator Jim_ExprOperators[] = { - OPRINIT("*", 110, 2, JimExprOpBin, LAZY_NONE), - OPRINIT("/", 110, 2, JimExprOpBin, LAZY_NONE), - OPRINIT("%", 110, 2, JimExprOpIntBin, LAZY_NONE), - - OPRINIT("-", 100, 2, JimExprOpBin, LAZY_NONE), - OPRINIT("+", 100, 2, JimExprOpBin, LAZY_NONE), - - OPRINIT("<<", 90, 2, JimExprOpIntBin, LAZY_NONE), - OPRINIT(">>", 90, 2, JimExprOpIntBin, LAZY_NONE), - - OPRINIT("<<<", 90, 2, JimExprOpIntBin, LAZY_NONE), - OPRINIT(">>>", 90, 2, JimExprOpIntBin, LAZY_NONE), - - OPRINIT("<", 80, 2, JimExprOpBin, LAZY_NONE), - OPRINIT(">", 80, 2, JimExprOpBin, LAZY_NONE), - OPRINIT("<=", 80, 2, JimExprOpBin, LAZY_NONE), - OPRINIT(">=", 80, 2, JimExprOpBin, LAZY_NONE), - - OPRINIT("==", 70, 2, JimExprOpBin, LAZY_NONE), - OPRINIT("!=", 70, 2, JimExprOpBin, LAZY_NONE), - - OPRINIT("&", 50, 2, JimExprOpIntBin, LAZY_NONE), - OPRINIT("^", 49, 2, JimExprOpIntBin, LAZY_NONE), - OPRINIT("|", 48, 2, JimExprOpIntBin, LAZY_NONE), - - OPRINIT("&&", 10, 2, NULL, LAZY_OP), - OPRINIT(NULL, 10, 2, JimExprOpAndLeft, LAZY_LEFT), - OPRINIT(NULL, 10, 2, JimExprOpAndOrRight, LAZY_RIGHT), - - OPRINIT("||", 9, 2, NULL, LAZY_OP), - OPRINIT(NULL, 9, 2, JimExprOpOrLeft, LAZY_LEFT), - OPRINIT(NULL, 9, 2, JimExprOpAndOrRight, LAZY_RIGHT), - - OPRINIT("?", 5, 2, JimExprOpNull, LAZY_OP), - OPRINIT(NULL, 5, 2, JimExprOpTernaryLeft, LAZY_LEFT), - OPRINIT(NULL, 5, 2, JimExprOpNull, LAZY_RIGHT), - - OPRINIT(":", 5, 2, JimExprOpNull, LAZY_OP), - OPRINIT(NULL, 5, 2, JimExprOpColonLeft, LAZY_LEFT), - OPRINIT(NULL, 5, 2, JimExprOpNull, LAZY_RIGHT), - - OPRINIT("**", 250, 2, JimExprOpBin, LAZY_NONE), - - OPRINIT("eq", 60, 2, JimExprOpStrBin, LAZY_NONE), - OPRINIT("ne", 60, 2, JimExprOpStrBin, LAZY_NONE), - - OPRINIT("in", 55, 2, JimExprOpStrBin, LAZY_NONE), - OPRINIT("ni", 55, 2, JimExprOpStrBin, LAZY_NONE), - - OPRINIT("!", 150, 1, JimExprOpNumUnary, LAZY_NONE), - OPRINIT("~", 150, 1, JimExprOpIntUnary, LAZY_NONE), - OPRINIT(NULL, 150, 1, JimExprOpNumUnary, LAZY_NONE), - OPRINIT(NULL, 150, 1, JimExprOpNumUnary, LAZY_NONE), - - - - OPRINIT("int", 200, 1, JimExprOpNumUnary, LAZY_NONE), - OPRINIT("abs", 200, 1, JimExprOpNumUnary, LAZY_NONE), - OPRINIT("double", 200, 1, JimExprOpNumUnary, LAZY_NONE), - OPRINIT("round", 200, 1, JimExprOpNumUnary, LAZY_NONE), - OPRINIT("rand", 200, 0, JimExprOpNone, LAZY_NONE), - OPRINIT("srand", 200, 1, JimExprOpIntUnary, LAZY_NONE), + OPRINIT("*", 110, 2, JimExprOpBin), + OPRINIT("/", 110, 2, JimExprOpBin), + OPRINIT("%", 110, 2, JimExprOpIntBin), + + OPRINIT("-", 100, 2, JimExprOpBin), + OPRINIT("+", 100, 2, JimExprOpBin), + + OPRINIT("<<", 90, 2, JimExprOpIntBin), + OPRINIT(">>", 90, 2, JimExprOpIntBin), + + OPRINIT("<<<", 90, 2, JimExprOpIntBin), + OPRINIT(">>>", 90, 2, JimExprOpIntBin), + + OPRINIT("<", 80, 2, JimExprOpBin), + OPRINIT(">", 80, 2, JimExprOpBin), + OPRINIT("<=", 80, 2, JimExprOpBin), + OPRINIT(">=", 80, 2, JimExprOpBin), + + OPRINIT("==", 70, 2, JimExprOpBin), + OPRINIT("!=", 70, 2, JimExprOpBin), + + OPRINIT("&", 50, 2, JimExprOpIntBin), + OPRINIT("^", 49, 2, JimExprOpIntBin), + OPRINIT("|", 48, 2, JimExprOpIntBin), + + OPRINIT_LAZY("&&", 10, 2, NULL, LAZY_OP), + OPRINIT_LAZY(NULL, 10, 2, JimExprOpAndLeft, LAZY_LEFT), + OPRINIT_LAZY(NULL, 10, 2, JimExprOpAndOrRight, LAZY_RIGHT), + + OPRINIT_LAZY("||", 9, 2, NULL, LAZY_OP), + OPRINIT_LAZY(NULL, 9, 2, JimExprOpOrLeft, LAZY_LEFT), + OPRINIT_LAZY(NULL, 9, 2, JimExprOpAndOrRight, LAZY_RIGHT), + + OPRINIT_LAZY("?", 5, 2, JimExprOpNull, LAZY_OP), + OPRINIT_LAZY(NULL, 5, 2, JimExprOpTernaryLeft, LAZY_LEFT), + OPRINIT_LAZY(NULL, 5, 2, JimExprOpNull, LAZY_RIGHT), + + OPRINIT_LAZY(":", 5, 2, JimExprOpNull, LAZY_OP), + OPRINIT_LAZY(NULL, 5, 2, JimExprOpColonLeft, LAZY_LEFT), + OPRINIT_LAZY(NULL, 5, 2, JimExprOpNull, LAZY_RIGHT), + + OPRINIT("**", 250, 2, JimExprOpBin), + + OPRINIT("eq", 60, 2, JimExprOpStrBin), + OPRINIT("ne", 60, 2, JimExprOpStrBin), + + OPRINIT("in", 55, 2, JimExprOpStrBin), + OPRINIT("ni", 55, 2, JimExprOpStrBin), + + OPRINIT("!", 150, 1, JimExprOpNumUnary), + OPRINIT("~", 150, 1, JimExprOpIntUnary), + OPRINIT(NULL, 150, 1, JimExprOpNumUnary), + OPRINIT(NULL, 150, 1, JimExprOpNumUnary), + + + + OPRINIT("int", 200, 1, JimExprOpNumUnary), + OPRINIT("wide", 200, 1, JimExprOpNumUnary), + OPRINIT("abs", 200, 1, JimExprOpNumUnary), + OPRINIT("double", 200, 1, JimExprOpNumUnary), + OPRINIT("round", 200, 1, JimExprOpNumUnary), + OPRINIT("rand", 200, 0, JimExprOpNone), + OPRINIT("srand", 200, 1, JimExprOpIntUnary), #ifdef JIM_MATH_FUNCTIONS - OPRINIT("sin", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("cos", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("tan", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("asin", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("acos", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("atan", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("sinh", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("cosh", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("tanh", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("ceil", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("floor", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("exp", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("log", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("log10", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("sqrt", 200, 1, JimExprOpDoubleUnary, LAZY_NONE), - OPRINIT("pow", 200, 2, JimExprOpBin, LAZY_NONE), + OPRINIT("sin", 200, 1, JimExprOpDoubleUnary), + OPRINIT("cos", 200, 1, JimExprOpDoubleUnary), + OPRINIT("tan", 200, 1, JimExprOpDoubleUnary), + OPRINIT("asin", 200, 1, JimExprOpDoubleUnary), + OPRINIT("acos", 200, 1, JimExprOpDoubleUnary), + OPRINIT("atan", 200, 1, JimExprOpDoubleUnary), + OPRINIT("sinh", 200, 1, JimExprOpDoubleUnary), + OPRINIT("cosh", 200, 1, JimExprOpDoubleUnary), + OPRINIT("tanh", 200, 1, JimExprOpDoubleUnary), + OPRINIT("ceil", 200, 1, JimExprOpDoubleUnary), + OPRINIT("floor", 200, 1, JimExprOpDoubleUnary), + OPRINIT("exp", 200, 1, JimExprOpDoubleUnary), + OPRINIT("log", 200, 1, JimExprOpDoubleUnary), + OPRINIT("log10", 200, 1, JimExprOpDoubleUnary), + OPRINIT("sqrt", 200, 1, JimExprOpDoubleUnary), + OPRINIT("pow", 200, 2, JimExprOpBin), #endif }; #undef OPRINIT +#undef OPRINIT_LAZY #define JIM_EXPR_OPERATORS_NUM \ (sizeof(Jim_ExprOperators)/sizeof(struct Jim_ExprOperator)) static int JimParseExpression(struct JimParserCtx *pc) @@ -12924,13 +13217,16 @@ } pc->p++; pc->len--; } + + pc->tline = pc->linenr; + pc->tstart = pc->p; + if (pc->len == 0) { - pc->tstart = pc->tend = pc->p; - pc->tline = pc->linenr; + pc->tend = pc->p; pc->tt = JIM_TT_EOL; pc->eof = 1; return JIM_OK; } switch (*(pc->p)) { @@ -12941,12 +13237,11 @@ pc->tt = JIM_TT_SUBEXPR_END; goto singlechar; case ',': pc->tt = JIM_TT_SUBEXPR_COMMA; singlechar: - pc->tstart = pc->tend = pc->p; - pc->tline = pc->linenr; + pc->tend = pc->p; pc->p++; pc->len--; break; case '[': return JimParseCmd(pc); @@ -12992,82 +13287,44 @@ return JIM_OK; } static int JimParseExprNumber(struct JimParserCtx *pc) { - int allowdot = 1; - int base = 10; + char *end; pc->tt = JIM_TT_EXPR_INT; - pc->tstart = pc->p; - pc->tline = pc->linenr; - - - if (pc->p[0] == '0') { - switch (pc->p[1]) { - case 'x': - case 'X': - base = 16; - allowdot = 0; - pc->p += 2; - pc->len -= 2; - break; - case 'o': - case 'O': - base = 8; - allowdot = 0; - pc->p += 2; - pc->len -= 2; - break; - case 'b': - case 'B': - base = 2; - allowdot = 0; - pc->p += 2; - pc->len -= 2; - break; - } - } - - while (isdigit(UCHAR(*pc->p)) - || (base == 16 && isxdigit(UCHAR(*pc->p))) - || (base == 8 && *pc->p >= '0' && *pc->p <= '7') - || (base == 2 && (*pc->p == '0' || *pc->p == '1')) - || (allowdot && *pc->p == '.') - ) { - if (*pc->p == '.') { - allowdot = 0; - pc->tt = JIM_TT_EXPR_DOUBLE; - } - pc->p++; - pc->len--; - if (base == 10 && (*pc->p == 'e' || *pc->p == 'E') && (pc->p[1] == '-' || pc->p[1] == '+' - || isdigit(UCHAR(pc->p[1])))) { - pc->p += 2; - pc->len -= 2; - pc->tt = JIM_TT_EXPR_DOUBLE; + + jim_strtoull(pc->p, (char **)&pc->p); + + if (strchr("eENnIi.", *pc->p) || pc->p == pc->tstart) { + if (strtod(pc->tstart, &end)) { } + if (end == pc->tstart) + return JIM_ERR; + if (end > pc->p) { + + pc->tt = JIM_TT_EXPR_DOUBLE; + pc->p = end; } } pc->tend = pc->p - 1; + pc->len -= (pc->p - pc->tstart); return JIM_OK; } static int JimParseExprIrrational(struct JimParserCtx *pc) { - const char *Tokens[] = { "NaN", "nan", "NAN", "Inf", "inf", "INF", NULL }; - const char **token; - - for (token = Tokens; *token != NULL; token++) { - int len = strlen(*token); - - if (strncmp(*token, pc->p, len) == 0) { - pc->tstart = pc->p; - pc->tend = pc->p + len - 1; - pc->p += len; - pc->len -= len; - pc->tline = pc->linenr; + const char *irrationals[] = { "NaN", "nan", "NAN", "Inf", "inf", "INF", NULL }; + int i; + + for (i = 0; irrationals[i]; i++) { + const char *irr = irrationals[i]; + + if (strncmp(irr, pc->p, 3) == 0) { + pc->p += 3; + pc->len -= 3; + pc->tend = pc->p - 1; pc->tt = JIM_TT_EXPR_DOUBLE; return JIM_OK; } } return JIM_ERR; @@ -13107,15 +13364,13 @@ } if (*p != '(') { return JIM_ERR; } } - pc->tstart = pc->p; pc->tend = pc->p + bestLen - 1; pc->p += bestLen; pc->len -= bestLen; - pc->tline = pc->linenr; pc->tt = bestIdx; return JIM_OK; } @@ -13613,17 +13868,23 @@ } #ifdef DEBUG_SHOW_EXPR_TOKENS { int i; - printf("==== Expr Tokens ====\n"); + printf("==== Expr Tokens (%s) ====\n", Jim_String(fileNameObj)); for (i = 0; i < tokenlist.count; i++) { printf("[%2d]@%d %s '%.*s'\n", i, tokenlist.list[i].line, jim_tt_name(tokenlist.list[i].type), tokenlist.list[i].len, tokenlist.list[i].token); } } #endif + + if (JimParseCheckMissing(interp, parser.missing.ch) == JIM_ERR) { + ScriptTokenListFree(&tokenlist); + Jim_DecrRefCount(interp, fileNameObj); + return JIM_ERR; + } expr = ExprCreateByteCode(interp, &tokenlist, fileNameObj); @@ -13670,10 +13931,24 @@ return NULL; } } return (ExprByteCode *) Jim_GetIntRepPtr(objPtr); } + +#ifdef JIM_OPTIMIZATION +static Jim_Obj *JimExprIntValOrVar(Jim_Interp *interp, const ScriptToken *token) +{ + if (token->type == JIM_TT_EXPR_INT) + return token->objPtr; + else if (token->type == JIM_TT_VAR) + return Jim_GetVariable(interp, token->objPtr, JIM_NONE); + else if (token->type == JIM_TT_DICTSUGAR) + return JimExpandDictSugar(interp, token->objPtr); + else + return NULL; +} +#endif #define JIM_EE_STATICSTACK_LEN 10 int Jim_EvalExpression(Jim_Interp *interp, Jim_Obj *exprObjPtr, Jim_Obj **exprResultPtrPtr) { @@ -13693,102 +13968,69 @@ Jim_Obj *objPtr; switch (expr->len) { case 1: - if (expr->token[0].type == JIM_TT_EXPR_INT) { - *exprResultPtrPtr = expr->token[0].objPtr; - Jim_IncrRefCount(*exprResultPtrPtr); - return JIM_OK; - } - if (expr->token[0].type == JIM_TT_VAR) { - objPtr = Jim_GetVariable(interp, expr->token[0].objPtr, JIM_ERRMSG); - if (objPtr) { - *exprResultPtrPtr = objPtr; - Jim_IncrRefCount(*exprResultPtrPtr); - return JIM_OK; - } + objPtr = JimExprIntValOrVar(interp, &expr->token[0]); + if (objPtr) { + Jim_IncrRefCount(objPtr); + *exprResultPtrPtr = objPtr; + return JIM_OK; } break; case 2: - if (expr->token[1].type == JIM_EXPROP_NOT && expr->token[0].type == JIM_TT_VAR) { - jim_wide wideValue; + if (expr->token[1].type == JIM_EXPROP_NOT) { + objPtr = JimExprIntValOrVar(interp, &expr->token[0]); - objPtr = Jim_GetVariable(interp, expr->token[0].objPtr, JIM_NONE); - if (objPtr && JimIsWide(objPtr) - && Jim_GetWide(interp, objPtr, &wideValue) == JIM_OK) { - *exprResultPtrPtr = wideValue ? interp->falseObj : interp->trueObj; + if (objPtr && JimIsWide(objPtr)) { + *exprResultPtrPtr = JimWideValue(objPtr) ? interp->falseObj : interp->trueObj; Jim_IncrRefCount(*exprResultPtrPtr); return JIM_OK; } } break; case 3: - if (expr->token[0].type == JIM_TT_VAR && (expr->token[1].type == JIM_TT_EXPR_INT - || expr->token[1].type == JIM_TT_VAR)) { - switch (expr->token[2].type) { - case JIM_EXPROP_LT: - case JIM_EXPROP_LTE: - case JIM_EXPROP_GT: - case JIM_EXPROP_GTE: - case JIM_EXPROP_NUMEQ: - case JIM_EXPROP_NUMNE:{ - - jim_wide wideValueA; - jim_wide wideValueB; - - objPtr = Jim_GetVariable(interp, expr->token[0].objPtr, JIM_NONE); - if (objPtr && JimIsWide(objPtr) - && Jim_GetWide(interp, objPtr, &wideValueA) == JIM_OK) { - if (expr->token[1].type == JIM_TT_VAR) { - objPtr = - Jim_GetVariable(interp, expr->token[1].objPtr, - JIM_NONE); - } - else { - objPtr = expr->token[1].objPtr; - } - if (objPtr && JimIsWide(objPtr) - && Jim_GetWide(interp, objPtr, &wideValueB) == JIM_OK) { - int cmpRes; - - switch (expr->token[2].type) { - case JIM_EXPROP_LT: - cmpRes = wideValueA < wideValueB; - break; - case JIM_EXPROP_LTE: - cmpRes = wideValueA <= wideValueB; - break; - case JIM_EXPROP_GT: - cmpRes = wideValueA > wideValueB; - break; - case JIM_EXPROP_GTE: - cmpRes = wideValueA >= wideValueB; - break; - case JIM_EXPROP_NUMEQ: - cmpRes = wideValueA == wideValueB; - break; - case JIM_EXPROP_NUMNE: - cmpRes = wideValueA != wideValueB; - break; - default: - cmpRes = 0; - } - *exprResultPtrPtr = - cmpRes ? interp->trueObj : interp->falseObj; - Jim_IncrRefCount(*exprResultPtrPtr); - return JIM_OK; - } - } - } - } - } - break; - } - } + objPtr = JimExprIntValOrVar(interp, &expr->token[0]); + if (objPtr && JimIsWide(objPtr)) { + Jim_Obj *objPtr2 = JimExprIntValOrVar(interp, &expr->token[1]); + if (objPtr2 && JimIsWide(objPtr2)) { + jim_wide wideValueA = JimWideValue(objPtr); + jim_wide wideValueB = JimWideValue(objPtr2); + int cmpRes; + switch (expr->token[2].type) { + case JIM_EXPROP_LT: + cmpRes = wideValueA < wideValueB; + break; + case JIM_EXPROP_LTE: + cmpRes = wideValueA <= wideValueB; + break; + case JIM_EXPROP_GT: + cmpRes = wideValueA > wideValueB; + break; + case JIM_EXPROP_GTE: + cmpRes = wideValueA >= wideValueB; + break; + case JIM_EXPROP_NUMEQ: + cmpRes = wideValueA == wideValueB; + break; + case JIM_EXPROP_NUMNE: + cmpRes = wideValueA != wideValueB; + break; + default: + goto noopt; + } + *exprResultPtrPtr = cmpRes ? interp->trueObj : interp->falseObj; + Jim_IncrRefCount(*exprResultPtrPtr); + return JIM_OK; + } + } + break; + } + } +noopt: #endif expr->inUse++; @@ -13957,16 +14199,13 @@ memcpy(newVec, srcPtr->internalRep.ptr, size); dupPtr->internalRep.ptr = newVec; dupPtr->typePtr = &scanFmtStringObjType; } -void UpdateStringOfScanFmt(Jim_Obj *objPtr) +static void UpdateStringOfScanFmt(Jim_Obj *objPtr) { - char *bytes = ((ScanFmtStringObj *) objPtr->internalRep.ptr)->stringRep; - - objPtr->bytes = Jim_StrDup(bytes); - objPtr->length = strlen(bytes); + JimSetStringBytes(objPtr, ((ScanFmtStringObj *) objPtr->internalRep.ptr)->stringRep); } static int SetScanFmtFromAny(Jim_Interp *interp, Jim_Obj *objPtr) { @@ -14176,11 +14415,10 @@ Jim_Obj *tmpObj = NULL; *valObjPtr = 0; if (descr->prefix) { - for (i = 0; pos < strLen && descr->prefix[i]; ++i) { if (isspace(UCHAR(descr->prefix[i]))) while (pos < strLen && isspace(UCHAR(str[pos]))) ++pos; @@ -14517,34 +14755,55 @@ } static int JimInvokeCommand(Jim_Interp *interp, int objc, Jim_Obj *const *objv) { int retcode; - Jim_Cmd *cmdPtr = Jim_GetCommand(interp, objv[0], JIM_ERRMSG); + Jim_Cmd *cmdPtr; - if (cmdPtr == NULL) { - return JimUnknown(interp, objc, objv); +#if 0 + printf("invoke"); + int j; + for (j = 0; j < objc; j++) { + printf(" '%s'", Jim_String(objv[j])); } + printf("\n"); +#endif + + if (interp->framePtr->tailcallCmd) { + + cmdPtr = interp->framePtr->tailcallCmd; + interp->framePtr->tailcallCmd = NULL; + } + else { + cmdPtr = Jim_GetCommand(interp, objv[0], JIM_ERRMSG); + if (cmdPtr == NULL) { + return JimUnknown(interp, objc, objv); + } + JimIncrCmdRefCount(cmdPtr); + } + if (interp->evalDepth == interp->maxEvalDepth) { Jim_SetResultString(interp, "Infinite eval recursion", -1); - return JIM_ERR; + retcode = JIM_ERR; + goto out; } interp->evalDepth++; - JimIncrCmdRefCount(cmdPtr); Jim_SetEmptyResult(interp); if (cmdPtr->isproc) { retcode = JimCallProcedure(interp, cmdPtr, objc, objv); } else { interp->cmdPrivData = cmdPtr->u.native.privData; retcode = cmdPtr->u.native.cmdProc(interp, objc, objv); } - JimDecrCmdRefCount(interp, cmdPtr); interp->evalDepth--; +out: + JimDecrCmdRefCount(interp, cmdPtr); + return retcode; } int Jim_EvalObjVector(Jim_Interp *interp, int objc, Jim_Obj *const *objv) { @@ -14573,15 +14832,13 @@ ret = Jim_EvalObjVector(interp, objc + 1, nargv); Jim_Free(nargv); return ret; } -static void JimAddErrorToStack(Jim_Interp *interp, int retcode, ScriptObj *script) +static void JimAddErrorToStack(Jim_Interp *interp, ScriptObj *script) { - int rc = retcode; - - if (rc == JIM_ERR && !interp->errorFlag) { + if (!interp->errorFlag) { interp->errorFlag = 1; Jim_IncrRefCount(script->fileNameObj); Jim_DecrRefCount(interp, interp->errorFileNameObj); interp->errorFileNameObj = script->fileNameObj; @@ -14591,11 +14848,11 @@ interp->addStackTrace++; } - if (rc == JIM_ERR && interp->addStackTrace > 0) { + if (interp->addStackTrace > 0) { JimAppendStackTrace(interp, Jim_String(interp->errorProc), script->fileNameObj, script->linenr); if (Jim_Length(script->fileNameObj)) { @@ -14604,16 +14861,10 @@ Jim_DecrRefCount(interp, interp->errorProc); interp->errorProc = interp->emptyObj; Jim_IncrRefCount(interp->errorProc); } - else if (rc == JIM_RETURN && interp->returnCode == JIM_ERR) { - - } - else { - interp->addStackTrace = 0; - } } static int JimSubstOneToken(Jim_Interp *interp, const ScriptToken *token, Jim_Obj **objPtrPtr) { Jim_Obj *objPtr; @@ -14722,10 +14973,15 @@ objPtr->typePtr = &interpolatedObjType; objPtr->internalRep.dictSubstValue.varNameObjPtr = token[0].objPtr; objPtr->internalRep.dictSubstValue.indexObjPtr = intv[2]; Jim_IncrRefCount(intv[2]); } + else if (tokens && intv[0] && intv[0]->typePtr == &sourceObjType) { + + JimSetSourceInfo(interp, objPtr, intv[0]->internalRep.sourceValue.fileNameObj, intv[0]->internalRep.sourceValue.lineNumber); + } + s = objPtr->bytes = Jim_Alloc(totlen + 1); objPtr->length = totlen; for (i = 0; i < tokens; i++) { if (intv[i]) { @@ -14745,10 +15001,12 @@ static int JimEvalObjList(Jim_Interp *interp, Jim_Obj *listPtr) { int retcode = JIM_OK; + + JimPanic((Jim_IsList(listPtr) == 0, "JimEvalObjList() invoked on non-list.")); if (listPtr->internalRep.listValue.len) { Jim_IncrRefCount(listPtr); retcode = JimInvokeCommand(interp, listPtr->internalRep.listValue.len, @@ -14776,11 +15034,15 @@ if (Jim_IsList(scriptObjPtr) && scriptObjPtr->bytes == NULL) { return JimEvalObjList(interp, scriptObjPtr); } Jim_IncrRefCount(scriptObjPtr); - script = Jim_GetScript(interp, scriptObjPtr); + script = JimGetScript(interp, scriptObjPtr); + if (!JimScriptValid(interp, script)) { + Jim_DecrRefCount(interp, scriptObjPtr); + return JIM_ERR; + } Jim_SetEmptyResult(interp); token = script->token; @@ -14939,11 +15201,18 @@ argv = sargv; } } - JimAddErrorToStack(interp, retcode, script); + if (retcode == JIM_ERR) { + JimAddErrorToStack(interp, script); + } + + else if (retcode != JIM_RETURN || interp->returnCode != JIM_ERR) { + + interp->addStackTrace = 0; + } interp->currentScriptObj = prevScriptObj; Jim_FreeIntRep(interp, scriptObjPtr); @@ -15051,26 +15320,20 @@ retcode = Jim_EvalObj(interp, scriptObj); } interp->framePtr = interp->framePtr->parent; - if (callFramePtr->vars.size != JIM_HT_INITIAL_SIZE) { - JimFreeCallFrame(interp, callFramePtr, JIM_FCF_NONE); - } - else { - JimFreeCallFrame(interp, callFramePtr, JIM_FCF_NOHT); - } + JimFreeCallFrame(interp, callFramePtr, JIM_FCF_REUSE); return retcode; } #endif static int JimCallProcedure(Jim_Interp *interp, Jim_Cmd *cmd, int argc, Jim_Obj *const *argv) { Jim_CallFrame *callFramePtr; int i, d, retcode, optargs; - Jim_Stack *localCommands; ScriptObj *script; if (argc - 1 < cmd->u.proc.reqArity || (cmd->u.proc.argsPos < 0 && argc - 1 > cmd->u.proc.reqArity + cmd->u.proc.optArity)) { @@ -15096,11 +15359,11 @@ callFramePtr->procArgsObjPtr = cmd->u.proc.argListObjPtr; callFramePtr->procBodyObjPtr = cmd->u.proc.bodyObjPtr; callFramePtr->staticVars = cmd->u.proc.staticVars; - script = Jim_GetScript(interp, interp->currentScriptObj); + script = JimGetScript(interp, interp->currentScriptObj); callFramePtr->fileNameObj = script->fileNameObj; callFramePtr->line = script->linenr; Jim_IncrRefCount(cmd->u.proc.argListObjPtr); Jim_IncrRefCount(cmd->u.proc.bodyObjPtr); @@ -15150,37 +15413,42 @@ retcode = Jim_EvalObj(interp, cmd->u.proc.bodyObjPtr); badargset: + - - localCommands = callFramePtr->localCommands; - callFramePtr->localCommands = NULL; - interp->framePtr = interp->framePtr->parent; - if (callFramePtr->vars.size != JIM_HT_INITIAL_SIZE) { - JimFreeCallFrame(interp, callFramePtr, JIM_FCF_NONE); - } - else { - JimFreeCallFrame(interp, callFramePtr, JIM_FCF_NOHT); - } - - - while (retcode == JIM_EVAL) { - Jim_Obj *resultScriptObjPtr = Jim_GetResult(interp); - - Jim_IncrRefCount(resultScriptObjPtr); - - JimPanic((!Jim_IsList(resultScriptObjPtr), "tailcall (JIM_EVAL) returned non-list")); - - retcode = JimEvalObjList(interp, resultScriptObjPtr); - if (retcode == JIM_RETURN) { - interp->returnLevel++; - } - Jim_DecrRefCount(interp, resultScriptObjPtr); - } + JimFreeCallFrame(interp, callFramePtr, JIM_FCF_REUSE); + + if (interp->framePtr->tailcallObj) { + + if (interp->framePtr->tailcall++ == 0) { + + do { + Jim_Obj *tailcallObj = interp->framePtr->tailcallObj; + + interp->framePtr->tailcallObj = NULL; + + if (retcode == JIM_EVAL) { + retcode = Jim_EvalObjList(interp, tailcallObj); + if (retcode == JIM_RETURN) { + interp->returnLevel++; + } + } + Jim_DecrRefCount(interp, tailcallObj); + } while (interp->framePtr->tailcallObj); + + + if (interp->framePtr->tailcallCmd) { + JimDecrCmdRefCount(interp, interp->framePtr->tailcallCmd); + interp->framePtr->tailcallCmd = NULL; + } + } + interp->framePtr->tailcall--; + } + if (retcode == JIM_RETURN) { if (--interp->returnLevel <= 0) { retcode = interp->returnCode; interp->returnCode = JIM_OK; @@ -15192,13 +15460,10 @@ Jim_DecrRefCount(interp, interp->errorProc); interp->errorProc = argv[0]; Jim_IncrRefCount(interp->errorProc); } - - JimDeleteLocalProcs(interp, localCommands); - return retcode; } int Jim_EvalSource(Jim_Interp *interp, const char *filename, int lineno, const char *script) { @@ -15266,11 +15531,10 @@ Jim_Obj *scriptObjPtr; Jim_Obj *prevScriptObj; struct stat sb; int retcode; int readlen; - struct JimParseResult result; if (stat(filename, &sb) != 0 || (fp = fopen(filename, "rt")) == NULL) { Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", filename, strerror(errno)); return JIM_ERR; } @@ -15292,36 +15556,10 @@ scriptObjPtr = Jim_NewStringObjNoAlloc(interp, buf, readlen); JimSetSourceInfo(interp, scriptObjPtr, Jim_NewStringObj(interp, filename, -1), 1); Jim_IncrRefCount(scriptObjPtr); - - if (SetScriptFromAny(interp, scriptObjPtr, &result) == JIM_ERR) { - const char *msg; - char linebuf[20]; - - switch (result.missing) { - case '[': - msg = "unmatched \"[\""; - break; - case '{': - msg = "missing close-brace"; - break; - case '"': - default: - msg = "missing quote"; - break; - } - - snprintf(linebuf, sizeof(linebuf), "%d", result.line); - - Jim_SetResultFormatted(interp, "%s in \"%s\" at line %s", - msg, filename, linebuf); - Jim_DecrRefCount(interp, scriptObjPtr); - return JIM_ERR; - } - prevScriptObj = interp->currentScriptObj; interp->currentScriptObj = scriptObjPtr; retcode = Jim_EvalObj(interp, scriptObjPtr); @@ -15480,11 +15718,11 @@ } typedef void JimHashtableIteratorCallbackType(Jim_Interp *interp, Jim_Obj *listObjPtr, Jim_HashEntry *he, int type); -#define JimTrivialMatch(pattern) (strpbrk((pattern), "*[?\\") == NULL) +#define JimTrivialMatch(pattern) (strpbrk((pattern), "*[?\\") == NULL) static Jim_Obj *JimHashtablePatternMatch(Jim_Interp *interp, Jim_HashTable *ht, Jim_Obj *patternObjPtr, JimHashtableIteratorCallbackType *callback, int type) { Jim_HashEntry *he; @@ -15515,11 +15753,11 @@ #define JIM_CMDLIST_CHANNELS 2 static void JimCommandMatch(Jim_Interp *interp, Jim_Obj *listObjPtr, Jim_HashEntry *he, int type) { - Jim_Cmd *cmdPtr = (Jim_Cmd *)he->u.val; + Jim_Cmd *cmdPtr = Jim_GetHashEntryVal(he); Jim_Obj *objPtr; if (type == JIM_CMDLIST_PROCS && !cmdPtr->isproc) { return; @@ -15548,11 +15786,11 @@ #define JIM_VARLIST_VALUES 0x1000 static void JimVariablesMatch(Jim_Interp *interp, Jim_Obj *listObjPtr, Jim_HashEntry *he, int type) { - Jim_Var *varPtr = (Jim_Var *)he->u.val; + Jim_Var *varPtr = Jim_GetHashEntryVal(he); if (type != JIM_VARLIST_LOCALS || varPtr->linkFramePtr == NULL) { Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, he->key, -1)); if (type & JIM_VARLIST_VALUES) { Jim_ListAppendElement(interp, listObjPtr, varPtr->objPtr); @@ -15867,14 +16105,14 @@ Jim_Obj *objPtr; int cmpOffset; expr = JimGetExpression(interp, argv[2]); - incrScript = Jim_GetScript(interp, argv[3]); + incrScript = JimGetScript(interp, argv[3]); - if (incrScript->len != 3 || !expr || expr->len != 3) { + if (incrScript == NULL || incrScript->len != 3 || !expr || expr->len != 3) { goto evalstart; } if (incrScript->token[1].type != JIM_TT_ESC || expr->token[0].type != JIM_TT_VAR || @@ -16093,11 +16331,11 @@ } static int JimForeachMapHelper(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int doMap) { - int result = JIM_ERR; + int result = JIM_OK; int i, numargs; Jim_ListIter twoiters[2]; Jim_ListIter *iters; Jim_Obj *script; Jim_Obj *resultObj; @@ -16116,13 +16354,16 @@ iters = Jim_Alloc(numargs * sizeof(*iters)); } for (i = 0; i < numargs; i++) { JimListIterInit(&iters[i], argv[i + 1]); if (i % 2 == 0 && JimListIterDone(interp, &iters[i])) { - Jim_SetResultString(interp, "foreach varlist is empty", -1); - return JIM_ERR; + result = JIM_ERR; } + } + if (result != JIM_OK) { + Jim_SetResultString(interp, "foreach varlist is empty", -1); + return result; } if (doMap) { resultObj = Jim_NewListObj(interp, NULL, 0); } @@ -16435,12 +16676,12 @@ { Jim_Obj *objPtr, *listObjPtr; int i; int idx; - if (argc < 3) { - Jim_WrongNumArgs(interp, 1, argv, "list index ?...?"); + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "list ?index ...?"); return JIM_ERR; } objPtr = argv[1]; Jim_IncrRefCount(objPtr); for (i = 2; i < argc; i++) { @@ -16552,14 +16793,13 @@ Jim_IncrRefCount(commandObj); } listlen = Jim_ListLength(interp, argv[0]); for (i = 0; i < listlen; i++) { - Jim_Obj *objPtr; int eq = 0; + Jim_Obj *objPtr = Jim_ListGetIndex(interp, argv[0], i); - Jim_ListIndex(interp, argv[0], i, &objPtr, JIM_NONE); switch (opt_match) { case OPT_EXACT: eq = Jim_StringCompareObj(interp, argv[1], objPtr, opt_nocase) == 0; break; @@ -16749,29 +16989,27 @@ if (argc < 3) { Jim_WrongNumArgs(interp, 1, argv, "listVar ?index...? newVal"); return JIM_ERR; } else if (argc == 3) { + if (Jim_SetVariable(interp, argv[1], argv[2]) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, argv[2]); return JIM_OK; } - if (Jim_SetListIndex(interp, argv[1], argv + 2, argc - 3, argv[argc - 1]) - == JIM_ERR) - return JIM_ERR; - return JIM_OK; + return Jim_ListSetIndex(interp, argv[1], argv + 2, argc - 3, argv[argc - 1]); } static int Jim_LsortCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const argv[]) { static const char * const options[] = { - "-ascii", "-nocase", "-increasing", "-decreasing", "-command", "-integer", "-index", NULL + "-ascii", "-nocase", "-increasing", "-decreasing", "-command", "-integer", "-real", "-index", "-unique", NULL }; enum - { OPT_ASCII, OPT_NOCASE, OPT_INCREASING, OPT_DECREASING, OPT_COMMAND, OPT_INTEGER, OPT_INDEX }; + { OPT_ASCII, OPT_NOCASE, OPT_INCREASING, OPT_DECREASING, OPT_COMMAND, OPT_INTEGER, OPT_REAL, OPT_INDEX, OPT_UNIQUE }; Jim_Obj *resObj; int i; int retCode; struct lsort_info info; @@ -16782,17 +17020,18 @@ } info.type = JIM_LSORT_ASCII; info.order = 1; info.indexed = 0; + info.unique = 0; info.command = NULL; info.interp = interp; for (i = 1; i < (argc - 1); i++) { int option; - if (Jim_GetEnum(interp, argv[i], options, &option, NULL, JIM_ERRMSG) + if (Jim_GetEnum(interp, argv[i], options, &option, NULL, JIM_ENUM_ABBREV | JIM_ERRMSG) != JIM_OK) return JIM_ERR; switch (option) { case OPT_ASCII: info.type = JIM_LSORT_ASCII; @@ -16801,16 +17040,22 @@ info.type = JIM_LSORT_NOCASE; break; case OPT_INTEGER: info.type = JIM_LSORT_INTEGER; break; + case OPT_REAL: + info.type = JIM_LSORT_REAL; + break; case OPT_INCREASING: info.order = 1; break; case OPT_DECREASING: info.order = -1; break; + case OPT_UNIQUE: + info.unique = 1; + break; case OPT_COMMAND: if (i >= (argc - 2)) { Jim_SetResultString(interp, "\"-command\" option must be followed by comparison command", -1); return JIM_ERR; } @@ -16847,11 +17092,11 @@ { Jim_Obj *stringObjPtr; int i; if (argc < 2) { - Jim_WrongNumArgs(interp, 1, argv, "varName ?value value ...?"); + Jim_WrongNumArgs(interp, 1, argv, "varName ?value ...?"); return JIM_ERR; } if (argc == 2) { stringObjPtr = Jim_GetVariable(interp, argv[1], JIM_ERRMSG); if (!stringObjPtr) @@ -16896,11 +17141,11 @@ static int Jim_EvalCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { int rc; if (argc < 2) { - Jim_WrongNumArgs(interp, 1, argv, "script ?...?"); + Jim_WrongNumArgs(interp, 1, argv, "arg ?arg ...?"); return JIM_ERR; } if (argc == 2) { rc = Jim_EvalObj(interp, argv[1]); @@ -16920,20 +17165,20 @@ static int Jim_UplevelCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { if (argc >= 2) { int retcode; Jim_CallFrame *savedCallFrame, *targetCallFrame; - Jim_Obj *objPtr; + int savedTailcall; const char *str; savedCallFrame = interp->framePtr; str = Jim_String(argv[1]); if ((str[0] >= '0' && str[0] <= '9') || str[0] == '#') { - targetCallFrame =Jim_GetCallFrameByLevel(interp, argv[1]); + targetCallFrame = Jim_GetCallFrameByLevel(interp, argv[1]); argc--; argv++; } else { targetCallFrame = Jim_GetCallFrameByLevel(interp, NULL); @@ -16940,25 +17185,25 @@ } if (targetCallFrame == NULL) { return JIM_ERR; } if (argc < 2) { - argv--; - Jim_WrongNumArgs(interp, 1, argv, "?level? command ?arg ...?"); + Jim_WrongNumArgs(interp, 1, argv - 1, "?level? command ?arg ...?"); return JIM_ERR; } interp->framePtr = targetCallFrame; + + savedTailcall = interp->framePtr->tailcall; + interp->framePtr->tailcall = 0; if (argc == 2) { retcode = Jim_EvalObj(interp, argv[1]); } else { - objPtr = Jim_ConcatObj(interp, argc - 1, argv + 1); - Jim_IncrRefCount(objPtr); - retcode = Jim_EvalObj(interp, objPtr); - Jim_DecrRefCount(interp, objPtr); + retcode = Jim_EvalObj(interp, Jim_ConcatObj(interp, argc - 1, argv + 1)); } + interp->framePtr->tailcall = savedTailcall; interp->framePtr = savedCallFrame; return retcode; } else { Jim_WrongNumArgs(interp, 1, argv, "?level? command ?arg ...?"); @@ -17069,22 +17314,49 @@ } static int Jim_TailcallCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - Jim_SetResult(interp, Jim_NewListObj(interp, argv + 1, argc - 1)); - return JIM_EVAL; + if (interp->framePtr->level == 0) { + Jim_SetResultString(interp, "tailcall can only be called from a proc or lambda", -1); + return JIM_ERR; + } + else if (argc >= 2) { + + Jim_CallFrame *cf = interp->framePtr->parent; + + Jim_Cmd *cmdPtr = Jim_GetCommand(interp, argv[1], JIM_ERRMSG); + if (cmdPtr == NULL) { + return JIM_ERR; + } + + JimPanic((cf->tailcallCmd != NULL, "Already have a tailcallCmd")); + + + JimIncrCmdRefCount(cmdPtr); + cf->tailcallCmd = cmdPtr; + + + JimPanic((cf->tailcallObj != NULL, "Already have a tailcallobj")); + + cf->tailcallObj = Jim_NewListObj(interp, argv + 1, argc - 1); + Jim_IncrRefCount(cf->tailcallObj); + + + return JIM_EVAL; + } + return JIM_OK; } static int JimAliasCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *cmdList; Jim_Obj *prefixListObj = Jim_CmdPrivData(interp); cmdList = Jim_DuplicateObj(interp, prefixListObj); - ListInsertElements(cmdList, -1, argc - 1, argv + 1); + Jim_ListInsertElements(interp, cmdList, Jim_ListLength(interp, cmdList), argc - 1, argv + 1); return JimEvalObjList(interp, cmdList); } static void JimAliasCmdDelete(Jim_Interp *interp, void *privData) @@ -17158,10 +17430,15 @@ static int Jim_LocalCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { int retcode; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "cmd ?args ...?"); + return JIM_ERR; + } interp->local++; retcode = Jim_EvalObjVector(interp, argc - 1, argv + 1); interp->local--; @@ -17354,11 +17631,11 @@ for (i = 0; i < numMaps; i += 2) { Jim_Obj *objPtr; const char *k; int kl; - Jim_ListIndex(interp, mapListObjPtr, i, &objPtr, JIM_NONE); + objPtr = Jim_ListGetIndex(interp, mapListObjPtr, i); k = Jim_String(objPtr); kl = Jim_Utf8Length(interp, objPtr); if (strLen >= kl && kl) { int rc; @@ -17366,12 +17643,11 @@ if (rc == 0) { if (noMatchStart) { Jim_AppendString(interp, resultObjPtr, noMatchStart, str - noMatchStart); noMatchStart = NULL; } - Jim_ListIndex(interp, mapListObjPtr, i + 1, &objPtr, JIM_NONE); - Jim_AppendObj(interp, resultObjPtr, objPtr); + Jim_AppendObj(interp, resultObjPtr, Jim_ListGetIndex(interp, mapListObjPtr, i + 1)); str += utf8_index(str, kl); strLen -= kl; break; } } @@ -17396,17 +17672,17 @@ int len; int opt_case = 1; int option; static const char * const options[] = { "bytelength", "length", "compare", "match", "equal", "is", "byterange", "range", "replace", - "map", "repeat", "reverse", "index", "first", "last", + "map", "repeat", "reverse", "index", "first", "last", "cat", "trim", "trimleft", "trimright", "tolower", "toupper", "totitle", NULL }; enum { OPT_BYTELENGTH, OPT_LENGTH, OPT_COMPARE, OPT_MATCH, OPT_EQUAL, OPT_IS, OPT_BYTERANGE, OPT_RANGE, OPT_REPLACE, - OPT_MAP, OPT_REPEAT, OPT_REVERSE, OPT_INDEX, OPT_FIRST, OPT_LAST, + OPT_MAP, OPT_REPEAT, OPT_REVERSE, OPT_INDEX, OPT_FIRST, OPT_LAST, OPT_CAT, OPT_TRIM, OPT_TRIMLEFT, OPT_TRIMRIGHT, OPT_TOLOWER, OPT_TOUPPER, OPT_TOTITLE }; static const char * const nocase_options[] = { "-nocase", NULL }; @@ -17435,10 +17711,29 @@ else { len = Jim_Length(argv[2]); } Jim_SetResultInt(interp, len); return JIM_OK; + + case OPT_CAT:{ + Jim_Obj *objPtr; + if (argc == 3) { + + objPtr = argv[2]; + } + else { + int i; + + objPtr = Jim_NewStringObj(interp, "", 0); + + for (i = 2; i < argc; i++) { + Jim_AppendObj(interp, objPtr, argv[i]); + } + } + Jim_SetResult(interp, objPtr); + return JIM_OK; + } case OPT_COMPARE: case OPT_EQUAL: { @@ -17851,15 +18146,17 @@ exitCode = JIM_SIGNAL; } else { exitCode = Jim_EvalObj(interp, argv[0]); + + interp->errorFlag = 0; } interp->signal_level -= sig; - if (exitCode >= 0 && exitCode < max_ignore_code && ((1 << exitCode) & ignore_mask)) { + if (exitCode >= 0 && exitCode < max_ignore_code && (((unsigned jim_wide)1 << exitCode) & ignore_mask)) { return exitCode; } if (sig && exitCode == JIM_SIGNAL) { @@ -18009,11 +18306,11 @@ listObjPtr = Jim_NewListObj(interp, NULL, 0); JimInitHashTableIterator(&interp->references, &htiter); while ((he = Jim_NextHashEntry(&htiter)) != NULL) { char buf[JIM_REFERENCE_SPACE + 1]; - Jim_Reference *refPtr = he->u.val; + Jim_Reference *refPtr = Jim_GetHashEntryVal(he); const unsigned long *refId = he->key; JimFormatReference(buf, refPtr, *refId); Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, buf, -1)); } @@ -18043,11 +18340,11 @@ static void JimDictMatchKeys(Jim_Interp *interp, Jim_Obj *listObjPtr, Jim_HashEntry *he, int type) { Jim_ListAppendElement(interp, listObjPtr, (Jim_Obj *)he->key); if (type & JIM_DICTMATCH_VALUES) { - Jim_ListAppendElement(interp, listObjPtr, (Jim_Obj *)he->u.val); + Jim_ListAppendElement(interp, listObjPtr, Jim_GetHashEntryVal(he)); } } static Jim_Obj *JimDictPatternMatch(Jim_Interp *interp, Jim_HashTable *ht, Jim_Obj *patternObjPtr, JimDictMatchCallbackType *callback, int type) @@ -18092,21 +18389,65 @@ return -1; } return ((Jim_HashTable *)objPtr->internalRep.ptr)->used; } +int Jim_DictInfo(Jim_Interp *interp, Jim_Obj *objPtr) +{ + Jim_HashTable *ht; + unsigned int i; + + if (SetDictFromAny(interp, objPtr) != JIM_OK) { + return JIM_ERR; + } + + ht = (Jim_HashTable *)objPtr->internalRep.ptr; + + + printf("%d entries in table, %d buckets\n", ht->used, ht->size); + + for (i = 0; i < ht->size; i++) { + Jim_HashEntry *he = ht->table[i]; + + if (he) { + printf("%d: ", i); + + while (he) { + printf(" %s", Jim_String(he->key)); + he = he->next; + } + printf("\n"); + } + } + return JIM_OK; +} + +static int Jim_EvalEnsemble(Jim_Interp *interp, const char *basecmd, const char *subcmd, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *prefixObj = Jim_NewStringObj(interp, basecmd, -1); + + Jim_AppendString(interp, prefixObj, " ", 1); + Jim_AppendString(interp, prefixObj, subcmd, -1); + + return Jim_EvalObjPrefix(interp, prefixObj, argc, argv); +} + static int Jim_DictCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *objPtr; int option; static const char * const options[] = { - "create", "get", "set", "unset", "exists", "keys", "merge", "size", "with", NULL + "create", "get", "set", "unset", "exists", "keys", "size", "info", + "merge", "with", "append", "lappend", "incr", "remove", "values", "for", + "replace", "update", NULL }; enum { - OPT_CREATE, OPT_GET, OPT_SET, OPT_UNSET, OPT_EXIST, OPT_KEYS, OPT_MERGE, OPT_SIZE, OPT_WITH, + OPT_CREATE, OPT_GET, OPT_SET, OPT_UNSET, OPT_EXISTS, OPT_KEYS, OPT_SIZE, OPT_INFO, + OPT_MERGE, OPT_WITH, OPT_APPEND, OPT_LAPPEND, OPT_INCR, OPT_REMOVE, OPT_VALUES, OPT_FOR, + OPT_REPLACE, OPT_UPDATE, }; if (argc < 2) { Jim_WrongNumArgs(interp, 1, argv, "subcommand ?arguments ...?"); return JIM_ERR; @@ -18117,11 +18458,11 @@ } switch (option) { case OPT_GET: if (argc < 3) { - Jim_WrongNumArgs(interp, 2, argv, "varName ?key ...?"); + Jim_WrongNumArgs(interp, 2, argv, "dictionary ?key ...?"); return JIM_ERR; } if (Jim_DictKeysVector(interp, argv[2], argv + 3, argc - 3, &objPtr, JIM_ERRMSG) != JIM_OK) { return JIM_ERR; @@ -18134,82 +18475,87 @@ Jim_WrongNumArgs(interp, 2, argv, "varName key ?key ...? value"); return JIM_ERR; } return Jim_SetDictKeysVector(interp, argv[2], argv + 3, argc - 4, argv[argc - 1], JIM_ERRMSG); - case OPT_EXIST: - if (argc < 3) { - Jim_WrongNumArgs(interp, 2, argv, "varName ?key ...?"); + case OPT_EXISTS: + if (argc < 4) { + Jim_WrongNumArgs(interp, 2, argv, "dictionary key ?key ...?"); return JIM_ERR; } - Jim_SetResultBool(interp, Jim_DictKeysVector(interp, argv[2], argv + 3, argc - 3, - &objPtr, JIM_ERRMSG) == JIM_OK); - return JIM_OK; + else { + int rc = Jim_DictKeysVector(interp, argv[2], argv + 3, argc - 3, &objPtr, JIM_ERRMSG); + if (rc < 0) { + return JIM_ERR; + } + Jim_SetResultBool(interp, rc == JIM_OK); + return JIM_OK; + } case OPT_UNSET: if (argc < 4) { Jim_WrongNumArgs(interp, 2, argv, "varName key ?key ...?"); return JIM_ERR; } - return Jim_SetDictKeysVector(interp, argv[2], argv + 3, argc - 3, NULL, JIM_NONE); + if (Jim_SetDictKeysVector(interp, argv[2], argv + 3, argc - 3, NULL, 0) != JIM_OK) { + return JIM_ERR; + } + return JIM_OK; case OPT_KEYS: if (argc != 3 && argc != 4) { - Jim_WrongNumArgs(interp, 2, argv, "dictVar ?pattern?"); + Jim_WrongNumArgs(interp, 2, argv, "dictionary ?pattern?"); return JIM_ERR; } return Jim_DictKeys(interp, argv[2], argc == 4 ? argv[3] : NULL); - case OPT_SIZE: { - int size; - + case OPT_SIZE: if (argc != 3) { - Jim_WrongNumArgs(interp, 2, argv, "dictVar"); + Jim_WrongNumArgs(interp, 2, argv, "dictionary"); return JIM_ERR; } - - size = Jim_DictSize(interp, argv[2]); - if (size < 0) { + else if (Jim_DictSize(interp, argv[2]) < 0) { return JIM_ERR; } - Jim_SetResultInt(interp, size); + Jim_SetResultInt(interp, Jim_DictSize(interp, argv[2])); return JIM_OK; - } case OPT_MERGE: if (argc == 2) { return JIM_OK; } - else if (SetDictFromAny(interp, argv[2]) != JIM_OK) { - return JIM_ERR; - } - else { - return Jim_EvalPrefix(interp, "dict merge", argc - 2, argv + 2); - } - - case OPT_WITH: - if (argc < 4) { - Jim_WrongNumArgs(interp, 2, argv, "dictVar ?key ...? script"); - return JIM_ERR; - } - else if (Jim_GetVariable(interp, argv[2], JIM_ERRMSG) == NULL) { - return JIM_ERR; - } - else { - return Jim_EvalPrefix(interp, "dict with", argc - 2, argv + 2); - } + if (Jim_DictSize(interp, argv[2]) < 0) { + return JIM_ERR; + } + + break; + + case OPT_UPDATE: + if (argc < 6 || argc % 2) { + + argc = 2; + } + break; case OPT_CREATE: if (argc % 2) { Jim_WrongNumArgs(interp, 2, argv, "?key value ...?"); return JIM_ERR; } objPtr = Jim_NewDictObj(interp, argv + 2, argc - 2); Jim_SetResult(interp, objPtr); return JIM_OK; + + case OPT_INFO: + if (argc != 3) { + Jim_WrongNumArgs(interp, 2, argv, "dictionary"); + return JIM_ERR; + } + return Jim_DictInfo(interp, argv[2]); } - return JIM_ERR; + + return Jim_EvalEnsemble(interp, "dict", options[option], argc - 2, argv + 2); } static int Jim_SubstCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { @@ -18267,11 +18613,11 @@ }; enum { INFO_BODY, INFO_STATICS, INFO_COMMANDS, INFO_PROCS, INFO_CHANNELS, INFO_EXISTS, INFO_GLOBALS, INFO_LEVEL, INFO_FRAME, INFO_LOCALS, INFO_VARS, INFO_VERSION, INFO_PATCHLEVEL, INFO_COMPLETE, INFO_ARGS, INFO_HOSTNAME, INFO_SCRIPT, INFO_SOURCE, INFO_STACKTRACE, INFO_NAMEOFEXECUTABLE, - INFO_RETURNCODES, INFO_REFERENCES, INFO_ALIAS + INFO_RETURNCODES, INFO_REFERENCES, INFO_ALIAS, }; #ifdef jim_ext_namespace int nons = 0; @@ -18367,38 +18713,47 @@ case INFO_SCRIPT: if (argc != 2) { Jim_WrongNumArgs(interp, 2, argv, ""); return JIM_ERR; } - Jim_SetResult(interp, Jim_GetScript(interp, interp->currentScriptObj)->fileNameObj); + Jim_SetResult(interp, JimGetScript(interp, interp->currentScriptObj)->fileNameObj); break; case INFO_SOURCE:{ - int line; + jim_wide line; Jim_Obj *resObjPtr; Jim_Obj *fileNameObj; - if (argc != 3) { - Jim_WrongNumArgs(interp, 2, argv, "source"); + if (argc != 3 && argc != 5) { + Jim_WrongNumArgs(interp, 2, argv, "source ?filename line?"); return JIM_ERR; } - if (argv[2]->typePtr == &sourceObjType) { - fileNameObj = argv[2]->internalRep.sourceValue.fileNameObj; - line = argv[2]->internalRep.sourceValue.lineNumber; - } - else if (argv[2]->typePtr == &scriptObjType) { - ScriptObj *script = Jim_GetScript(interp, argv[2]); - fileNameObj = script->fileNameObj; - line = script->firstline; + if (argc == 5) { + if (Jim_GetWide(interp, argv[4], &line) != JIM_OK) { + return JIM_ERR; + } + resObjPtr = Jim_NewStringObj(interp, Jim_String(argv[2]), Jim_Length(argv[2])); + JimSetSourceInfo(interp, resObjPtr, argv[3], line); } else { - fileNameObj = interp->emptyObj; - line = 1; + if (argv[2]->typePtr == &sourceObjType) { + fileNameObj = argv[2]->internalRep.sourceValue.fileNameObj; + line = argv[2]->internalRep.sourceValue.lineNumber; + } + else if (argv[2]->typePtr == &scriptObjType) { + ScriptObj *script = JimGetScript(interp, argv[2]); + fileNameObj = script->fileNameObj; + line = script->firstline; + } + else { + fileNameObj = interp->emptyObj; + line = 1; + } + resObjPtr = Jim_NewListObj(interp, NULL, 0); + Jim_ListAppendElement(interp, resObjPtr, fileNameObj); + Jim_ListAppendElement(interp, resObjPtr, Jim_NewIntObj(interp, line)); } - resObjPtr = Jim_NewListObj(interp, NULL, 0); - Jim_ListAppendElement(interp, resObjPtr, fileNameObj); - Jim_ListAppendElement(interp, resObjPtr, Jim_NewIntObj(interp, line)); Jim_SetResult(interp, resObjPtr); break; } case INFO_STACKTRACE: @@ -19047,11 +19402,11 @@ } } static const struct { const char *name; - Jim_CmdProc cmdProc; + Jim_CmdProc *cmdProc; } Jim_CoreCommandsTable[] = { {"alias", Jim_AliasCoreCommand}, {"set", Jim_SetCoreCommand}, {"unset", Jim_UnsetCoreCommand}, {"puts", Jim_PutsCoreCommand}, @@ -19279,10 +19634,12 @@ } len += extra; buf = Jim_Alloc(len + 1); len = snprintf(buf, len + 1, format, params[0], params[1], params[2], params[3], params[4]); + + va_end(args); Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, len)); } @@ -19530,344 +19887,373 @@ #include #include -#define JIM_UTF_MAX 3 #define JIM_INTEGER_SPACE 24 #define MAX_FLOAT_WIDTH 320 Jim_Obj *Jim_FormatString(Jim_Interp *interp, Jim_Obj *fmtObjPtr, int objc, Jim_Obj *const *objv) { const char *span, *format, *formatEnd, *msg; int numBytes = 0, objIndex = 0, gotXpg = 0, gotSequential = 0; static const char * const mixedXPG = - "cannot mix \"%\" and \"%n$\" conversion specifiers"; + "cannot mix \"%\" and \"%n$\" conversion specifiers"; static const char * const badIndex[2] = { - "not enough arguments for all format specifiers", - "\"%n$\" argument index out of range" + "not enough arguments for all format specifiers", + "\"%n$\" argument index out of range" }; int formatLen; Jim_Obj *resultPtr; char *num_buffer = NULL; int num_buffer_size = 0; span = format = Jim_GetString(fmtObjPtr, &formatLen); formatEnd = format + formatLen; - resultPtr = Jim_NewStringObj(interp, "", 0); + resultPtr = Jim_NewEmptyStringObj(interp); while (format != formatEnd) { - char *end; - int gotMinus, sawFlag; - int gotPrecision, useShort; - long width, precision; - int newXpg; - int ch; - int step; - int doubleType; - char pad = ' '; - char spec[2*JIM_INTEGER_SPACE + 12]; - char *p; - - int formatted_chars; - int formatted_bytes; - const char *formatted_buf; - - step = utf8_tounicode(format, &ch); - format += step; - if (ch != '%') { - numBytes += step; - continue; - } - if (numBytes) { - Jim_AppendString(interp, resultPtr, span, numBytes); - numBytes = 0; - } - - - step = utf8_tounicode(format, &ch); - if (ch == '%') { - span = format; - numBytes = step; - format += step; - continue; - } - - - newXpg = 0; - if (isdigit(ch)) { - int position = strtoul(format, &end, 10); - if (*end == '$') { - newXpg = 1; - objIndex = position - 1; - format = end + 1; - step = utf8_tounicode(format, &ch); - } - } - if (newXpg) { - if (gotSequential) { - msg = mixedXPG; - goto errorMsg; - } - gotXpg = 1; - } else { - if (gotXpg) { - msg = mixedXPG; - goto errorMsg; - } - gotSequential = 1; - } - if ((objIndex < 0) || (objIndex >= objc)) { - msg = badIndex[gotXpg]; - goto errorMsg; - } - - p = spec; - *p++ = '%'; - - gotMinus = 0; - sawFlag = 1; - do { - switch (ch) { - case '-': - gotMinus = 1; - break; - case '0': - pad = ch; - break; - case ' ': - case '+': - case '#': - break; - default: - sawFlag = 0; - continue; - } - *p++ = ch; - format += step; - step = utf8_tounicode(format, &ch); - } while (sawFlag); - - - width = 0; - if (isdigit(ch)) { - width = strtoul(format, &end, 10); - format = end; - step = utf8_tounicode(format, &ch); - } else if (ch == '*') { - if (objIndex >= objc - 1) { - msg = badIndex[gotXpg]; - goto errorMsg; - } - if (Jim_GetLong(interp, objv[objIndex], &width) != JIM_OK) { - goto error; - } - if (width < 0) { - width = -width; - if (!gotMinus) { - *p++ = '-'; - gotMinus = 1; - } - } - objIndex++; - format += step; - step = utf8_tounicode(format, &ch); - } - - - gotPrecision = precision = 0; - if (ch == '.') { - gotPrecision = 1; - format += step; - step = utf8_tounicode(format, &ch); - } - if (isdigit(ch)) { - precision = strtoul(format, &end, 10); - format = end; - step = utf8_tounicode(format, &ch); - } else if (ch == '*') { - if (objIndex >= objc - 1) { - msg = badIndex[gotXpg]; - goto errorMsg; - } - if (Jim_GetLong(interp, objv[objIndex], &precision) != JIM_OK) { - goto error; - } - - - if (precision < 0) { - precision = 0; - } - objIndex++; - format += step; - step = utf8_tounicode(format, &ch); - } - - - useShort = 0; - if (ch == 'h') { - useShort = 1; - format += step; - step = utf8_tounicode(format, &ch); - } else if (ch == 'l') { - - format += step; - step = utf8_tounicode(format, &ch); - if (ch == 'l') { - format += step; - step = utf8_tounicode(format, &ch); - } - } - - format += step; - span = format; - - - if (ch == 'i') { - ch = 'd'; - } - - doubleType = 0; - - switch (ch) { - case '\0': - msg = "format string ended in middle of field specifier"; - goto errorMsg; - case 's': { - formatted_buf = Jim_GetString(objv[objIndex], &formatted_bytes); - formatted_chars = Jim_Utf8Length(interp, objv[objIndex]); - if (gotPrecision && (precision < formatted_chars)) { - - formatted_chars = precision; - formatted_bytes = utf8_index(formatted_buf, precision); - } - break; - } - case 'c': { - jim_wide code; - - if (Jim_GetWide(interp, objv[objIndex], &code) != JIM_OK) { - goto error; - } - - formatted_bytes = utf8_fromunicode(spec, code); - formatted_buf = spec; - formatted_chars = 1; - break; - } - - case 'e': - case 'E': - case 'f': - case 'g': - case 'G': - doubleType = 1; - - case 'd': - case 'u': - case 'o': - case 'x': - case 'X': { - jim_wide w; - double d; - int length; - - - if (width) { - p += sprintf(p, "%ld", width); - } - if (gotPrecision) { - p += sprintf(p, ".%ld", precision); - } - - - if (doubleType) { - if (Jim_GetDouble(interp, objv[objIndex], &d) != JIM_OK) { - goto error; - } - length = MAX_FLOAT_WIDTH; - } - else { - if (Jim_GetWide(interp, objv[objIndex], &w) != JIM_OK) { - goto error; - } - length = JIM_INTEGER_SPACE; - if (useShort) { - *p++ = 'h'; - if (ch == 'd') { - w = (short)w; - } - else { - w = (unsigned short)w; - } - } - else { - *p++ = 'l'; -#ifdef HAVE_LONG_LONG - if (sizeof(long long) == sizeof(jim_wide)) { - *p++ = 'l'; - } -#endif - } - } - - *p++ = (char) ch; - *p = '\0'; - - - if (width > length) { - length = width; - } - if (gotPrecision) { - length += precision; - } - - - if (num_buffer_size < length + 1) { - num_buffer_size = length + 1; - num_buffer = Jim_Realloc(num_buffer, num_buffer_size); - } - - if (doubleType) { - snprintf(num_buffer, length + 1, spec, d); - } - else { - formatted_bytes = snprintf(num_buffer, length + 1, spec, w); - } - formatted_chars = formatted_bytes = strlen(num_buffer); - formatted_buf = num_buffer; - break; - } - - default: { - - spec[0] = ch; - spec[1] = '\0'; - Jim_SetResultFormatted(interp, "bad field specifier \"%s\"", spec); - goto error; - } - } - - if (!gotMinus) { - while (formatted_chars < width) { - Jim_AppendString(interp, resultPtr, &pad, 1); - formatted_chars++; - } - } - - Jim_AppendString(interp, resultPtr, formatted_buf, formatted_bytes); - - while (formatted_chars < width) { - Jim_AppendString(interp, resultPtr, &pad, 1); - formatted_chars++; - } - - objIndex += gotSequential; - } - if (numBytes) { - Jim_AppendString(interp, resultPtr, span, numBytes); + char *end; + int gotMinus, sawFlag; + int gotPrecision, useShort; + long width, precision; + int newXpg; + int ch; + int step; + int doubleType; + char pad = ' '; + char spec[2*JIM_INTEGER_SPACE + 12]; + char *p; + + int formatted_chars; + int formatted_bytes; + const char *formatted_buf; + + step = utf8_tounicode(format, &ch); + format += step; + if (ch != '%') { + numBytes += step; + continue; + } + if (numBytes) { + Jim_AppendString(interp, resultPtr, span, numBytes); + numBytes = 0; + } + + + step = utf8_tounicode(format, &ch); + if (ch == '%') { + span = format; + numBytes = step; + format += step; + continue; + } + + + newXpg = 0; + if (isdigit(ch)) { + int position = strtoul(format, &end, 10); + if (*end == '$') { + newXpg = 1; + objIndex = position - 1; + format = end + 1; + step = utf8_tounicode(format, &ch); + } + } + if (newXpg) { + if (gotSequential) { + msg = mixedXPG; + goto errorMsg; + } + gotXpg = 1; + } else { + if (gotXpg) { + msg = mixedXPG; + goto errorMsg; + } + gotSequential = 1; + } + if ((objIndex < 0) || (objIndex >= objc)) { + msg = badIndex[gotXpg]; + goto errorMsg; + } + + p = spec; + *p++ = '%'; + + gotMinus = 0; + sawFlag = 1; + do { + switch (ch) { + case '-': + gotMinus = 1; + break; + case '0': + pad = ch; + break; + case ' ': + case '+': + case '#': + break; + default: + sawFlag = 0; + continue; + } + *p++ = ch; + format += step; + step = utf8_tounicode(format, &ch); + } while (sawFlag); + + + width = 0; + if (isdigit(ch)) { + width = strtoul(format, &end, 10); + format = end; + step = utf8_tounicode(format, &ch); + } else if (ch == '*') { + if (objIndex >= objc - 1) { + msg = badIndex[gotXpg]; + goto errorMsg; + } + if (Jim_GetLong(interp, objv[objIndex], &width) != JIM_OK) { + goto error; + } + if (width < 0) { + width = -width; + if (!gotMinus) { + *p++ = '-'; + gotMinus = 1; + } + } + objIndex++; + format += step; + step = utf8_tounicode(format, &ch); + } + + + gotPrecision = precision = 0; + if (ch == '.') { + gotPrecision = 1; + format += step; + step = utf8_tounicode(format, &ch); + } + if (isdigit(ch)) { + precision = strtoul(format, &end, 10); + format = end; + step = utf8_tounicode(format, &ch); + } else if (ch == '*') { + if (objIndex >= objc - 1) { + msg = badIndex[gotXpg]; + goto errorMsg; + } + if (Jim_GetLong(interp, objv[objIndex], &precision) != JIM_OK) { + goto error; + } + + + if (precision < 0) { + precision = 0; + } + objIndex++; + format += step; + step = utf8_tounicode(format, &ch); + } + + + useShort = 0; + if (ch == 'h') { + useShort = 1; + format += step; + step = utf8_tounicode(format, &ch); + } else if (ch == 'l') { + + format += step; + step = utf8_tounicode(format, &ch); + if (ch == 'l') { + format += step; + step = utf8_tounicode(format, &ch); + } + } + + format += step; + span = format; + + + if (ch == 'i') { + ch = 'd'; + } + + doubleType = 0; + + switch (ch) { + case '\0': + msg = "format string ended in middle of field specifier"; + goto errorMsg; + case 's': { + formatted_buf = Jim_GetString(objv[objIndex], &formatted_bytes); + formatted_chars = Jim_Utf8Length(interp, objv[objIndex]); + if (gotPrecision && (precision < formatted_chars)) { + + formatted_chars = precision; + formatted_bytes = utf8_index(formatted_buf, precision); + } + break; + } + case 'c': { + jim_wide code; + + if (Jim_GetWide(interp, objv[objIndex], &code) != JIM_OK) { + goto error; + } + + formatted_bytes = utf8_getchars(spec, code); + formatted_buf = spec; + formatted_chars = 1; + break; + } + case 'b': { + unsigned jim_wide w; + int length; + int i; + int j; + + if (Jim_GetWide(interp, objv[objIndex], (jim_wide *)&w) != JIM_OK) { + goto error; + } + length = sizeof(w) * 8; + + + + if (num_buffer_size < length + 1) { + num_buffer_size = length + 1; + num_buffer = Jim_Realloc(num_buffer, num_buffer_size); + } + + j = 0; + for (i = length; i > 0; ) { + i--; + if (w & ((unsigned jim_wide)1 << i)) { + num_buffer[j++] = '1'; + } + else if (j || i == 0) { + num_buffer[j++] = '0'; + } + } + num_buffer[j] = 0; + formatted_chars = formatted_bytes = j; + formatted_buf = num_buffer; + break; + } + + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + doubleType = 1; + + case 'd': + case 'u': + case 'o': + case 'x': + case 'X': { + jim_wide w; + double d; + int length; + + + if (width) { + p += sprintf(p, "%ld", width); + } + if (gotPrecision) { + p += sprintf(p, ".%ld", precision); + } + + + if (doubleType) { + if (Jim_GetDouble(interp, objv[objIndex], &d) != JIM_OK) { + goto error; + } + length = MAX_FLOAT_WIDTH; + } + else { + if (Jim_GetWide(interp, objv[objIndex], &w) != JIM_OK) { + goto error; + } + length = JIM_INTEGER_SPACE; + if (useShort) { + if (ch == 'd') { + w = (short)w; + } + else { + w = (unsigned short)w; + } + } + *p++ = 'l'; +#ifdef HAVE_LONG_LONG + if (sizeof(long long) == sizeof(jim_wide)) { + *p++ = 'l'; + } +#endif + } + + *p++ = (char) ch; + *p = '\0'; + + + if (width > length) { + length = width; + } + if (gotPrecision) { + length += precision; + } + + + if (num_buffer_size < length + 1) { + num_buffer_size = length + 1; + num_buffer = Jim_Realloc(num_buffer, num_buffer_size); + } + + if (doubleType) { + snprintf(num_buffer, length + 1, spec, d); + } + else { + formatted_bytes = snprintf(num_buffer, length + 1, spec, w); + } + formatted_chars = formatted_bytes = strlen(num_buffer); + formatted_buf = num_buffer; + break; + } + + default: { + + spec[0] = ch; + spec[1] = '\0'; + Jim_SetResultFormatted(interp, "bad field specifier \"%s\"", spec); + goto error; + } + } + + if (!gotMinus) { + while (formatted_chars < width) { + Jim_AppendString(interp, resultPtr, &pad, 1); + formatted_chars++; + } + } + + Jim_AppendString(interp, resultPtr, formatted_buf, formatted_bytes); + + while (formatted_chars < width) { + Jim_AppendString(interp, resultPtr, &pad, 1); + formatted_chars++; + } + + objIndex += gotSequential; + } + if (numBytes) { + Jim_AppendString(interp, resultPtr, span, numBytes); } Jim_Free(num_buffer); return resultPtr; @@ -19876,21 +20262,22 @@ error: Jim_FreeNewObj(interp, resultPtr); Jim_Free(num_buffer); return NULL; } + + +#if defined(JIM_REGEXP) #include #include #include #include - -#if !defined(HAVE_REGCOMP) || defined(JIM_REGEXP) - #define REG_MAX_PAREN 100 + #define END 0 #define BOL 1 #define EOL 2 @@ -19899,23 +20286,27 @@ #define ANYBUT 5 #define BRANCH 6 #define BACK 7 #define EXACTLY 8 #define NOTHING 9 -#define REP 10 +#define REP 10 #define REPMIN 11 #define REPX 12 #define REPXMIN 13 #define WORDA 15 #define WORDZ 16 -#define OPENNC 19 -#define OPEN 20 + +#define OPENNC 1000 +#define OPEN 1001 -#define CLOSE (OPEN+REG_MAX_PAREN+1) + + + +#define CLOSENC 2000 +#define CLOSE 2001 #define CLOSE_END (CLOSE+REG_MAX_PAREN) -#define CLOSENC (CLOSE-1) #define REG_MAGIC 0xFADED00D #define OP(preg, p) (preg->program[p]) @@ -19925,15 +20316,15 @@ #define FAIL(R,M) { (R)->err = (M); return (M); } #define ISMULT(c) ((c) == '*' || (c) == '+' || (c) == '?' || (c) == '{') -#define META "^$.[()|?{+*" +#define META "^$.[()|?{+*" -#define HASWIDTH 01 -#define SIMPLE 02 -#define SPSTART 04 +#define HASWIDTH 1 +#define SIMPLE 2 +#define SPSTART 4 #define WORST 0 #define MAX_REP_COUNT 1000000 static int reg(regex_t *preg, int paren , int *flagp ); @@ -19942,13 +20333,13 @@ static int regatom(regex_t *preg, int *flagp ); static int regnode(regex_t *preg, int op ); static int regnext(regex_t *preg, int p ); static void regc(regex_t *preg, int b ); static int reginsert(regex_t *preg, int op, int size, int opnd ); -static void regtail_(regex_t *preg, int p, int val, int line ); +static void regtail(regex_t *preg, int p, int val); static void regoptail(regex_t *preg, int p, int val ); -#define regtail(PREG, P, VAL) regtail_(PREG, P, VAL, __LINE__) +static int regopsize(regex_t *preg, int p ); static int reg_range_find(const int *string, int c); static const char *str_find(const char *string, int c, int nocase); static int prefix_cmp(const int *prog, int proglen, const char *string, int nocase); @@ -19985,13 +20376,10 @@ FAIL(preg, REG_ERR_NULL_ARGUMENT); preg->cflags = cflags; preg->regparse = exp; - - preg->program = NULL; - preg->proglen = 0; preg->proglen = (strlen(exp) + 1) * 5; preg->program = malloc(preg->proglen * sizeof(int)); if (preg->program == NULL) @@ -20152,11 +20540,10 @@ { int ret; char op; int next; int flags; - int chain = 0; int min; int max; ret = regatom(preg, &flags); if (ret == 0) @@ -20235,11 +20622,11 @@ if (ISMULT(*preg->regparse)) { preg->err = REG_ERR_NESTED_COUNT; return 0; } - return chain ? chain : ret; + return ret; } static void reg_addrange(regex_t *preg, int lower, int upper) { if (lower > upper) { @@ -20329,10 +20716,11 @@ break; case 'U': if ((n = parse_hex(s, 8, ch)) > 0) { s += n; } + break; case 'x': if ((n = parse_hex(s, 2, ch)) > 0) { s += n; } break; @@ -20577,10 +20965,11 @@ static int regnode(regex_t *preg, int op) { reg_grow(preg, 2); + preg->program[preg->p++] = op; preg->program[preg->p++] = 0; return preg->p - 2; @@ -20606,11 +20995,11 @@ preg->p += size; return opnd + size; } -static void regtail_(regex_t *preg, int p, int val, int line ) +static void regtail(regex_t *preg, int p, int val) { int scan; int temp; int offset; @@ -20669,36 +21058,16 @@ preg->pmatch = pmatch; preg->nmatch = nmatch; preg->start = string; - for (scan = OPERAND(1); scan != 0; ) { - switch (OP(preg, scan)) { - case REP: - case REPMIN: - case REPX: - case REPXMIN: - preg->program[scan + 4] = 0; - scan += 5; - break; - - case ANYOF: - case ANYBUT: - case EXACTLY: - scan += 2; - while (preg->program[scan++]) { - } - break; - - case END: - scan = 0; - break; - - default: - scan += 2; - break; - } + for (scan = OPERAND(1); scan != 0; scan += regopsize(preg, scan)) { + int op = OP(preg, scan); + if (op == END) + break; + if (op == REPX || op == REPXMIN) + preg->program[scan + 4] = 0; } if (preg->regmust != 0) { s = string; @@ -20950,10 +21319,11 @@ static int regmatch(regex_t *preg, int prog) { int scan; int next; + const char *save; scan = prog; #ifdef DEBUG if (scan != 0 && regnarrate) @@ -21038,27 +21408,24 @@ break; case NOTHING: break; case BACK: break; - case BRANCH: { - const char *save; - - if (OP(preg, next) != BRANCH) - next = OPERAND(scan); - else { - do { - save = preg->reginput; - if (regmatch(preg, OPERAND(scan))) { - return(1); - } - preg->reginput = save; - scan = regnext(preg, scan); - } while (scan != 0 && OP(preg, scan) == BRANCH); - return(0); - - } + case BRANCH: + if (OP(preg, next) != BRANCH) + next = OPERAND(scan); + else { + do { + save = preg->reginput; + if (regmatch(preg, OPERAND(scan))) { + return(1); + } + preg->reginput = save; + scan = regnext(preg, scan); + } while (scan != 0 && OP(preg, scan) == BRANCH); + return(0); + } break; case REP: case REPMIN: return regmatchsimplerepeat(preg, scan, OP(preg, scan) == REPMIN); @@ -21066,43 +21433,35 @@ case REPX: case REPXMIN: return regmatchrepeat(preg, scan, OP(preg, scan) == REPXMIN); case END: - return(1); - break; + return 1; case OPENNC: case CLOSENC: - if (regmatch(preg, next)) { - return 1; - } - return 0; + return regmatch(preg, next); default: if (OP(preg, scan) >= OPEN+1 && OP(preg, scan) < CLOSE_END) { - const char *save; - save = preg->reginput; - if (regmatch(preg, next)) { - int no; if (OP(preg, scan) < CLOSE) { - no = OP(preg, scan) - OPEN; + int no = OP(preg, scan) - OPEN; if (no < preg->nmatch && preg->pmatch[no].rm_so == -1) { preg->pmatch[no].rm_so = save - preg->start; } } else { - no = OP(preg, scan) - CLOSE; + int no = OP(preg, scan) - CLOSE; if (no < preg->nmatch && preg->pmatch[no].rm_eo == -1) { preg->pmatch[no].rm_eo = save - preg->start; } } return(1); - } else - return(0); + } + return(0); } return REG_ERR_INTERNAL; } scan = next; @@ -21181,10 +21540,32 @@ if (OP(preg, p) == BACK) return(p-offset); else return(p+offset); } + +static int regopsize(regex_t *preg, int p ) +{ + + switch (OP(preg, p)) { + case REP: + case REPMIN: + case REPX: + case REPXMIN: + return 5; + + case ANYOF: + case ANYBUT: + case EXACTLY: { + int s = p + 2; + while (preg->program[s++]) { + } + return s - p; + } + } + return 2; +} size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size) { static const char *error_strings[] = { @@ -21359,19 +21740,24 @@ char *Jim_HistoryGetline(const char *prompt) { #ifdef USE_LINENOISE return linenoise(prompt); #else + int len; char *line = malloc(MAX_LINE_LEN); fputs(prompt, stdout); fflush(stdout); if (fgets(line, MAX_LINE_LEN, stdin) == NULL) { free(line); return NULL; } + len = strlen(line); + if (len && line[len - 1] == '\n') { + line[len - 1] = '\0'; + } return line; #endif } void Jim_HistoryLoad(const char *filename) @@ -21422,11 +21808,11 @@ snprintf(history_file, history_len, "%s/.jim_history", home); Jim_HistoryLoad(history_file); } #endif - printf("Welcome to Jim version %d.%d" JIM_NL, + printf("Welcome to Jim version %d.%d\n", JIM_VERSION / 100, JIM_VERSION % 100); Jim_SetVariableStrWithStr(interp, JIM_INTERACTIVE, "1"); while (1) { Jim_Obj *scriptObjPtr; @@ -21534,10 +21920,16 @@ } Jim_SetVariableStr(interp, "argv", listObj); Jim_SetVariableStr(interp, "argc", Jim_NewIntObj(interp, argc)); } + +static void JimPrintErrorMessage(Jim_Interp *interp) +{ + Jim_MakeErrorMessage(interp); + fprintf(stderr, "%s\n", Jim_String(Jim_GetResult(interp))); +} int main(int argc, char *const argv[]) { int retcode; Jim_Interp *interp; @@ -21551,22 +21943,20 @@ interp = Jim_CreateInterp(); Jim_RegisterCoreCommands(interp); if (Jim_InitStaticExtensions(interp) != JIM_OK) { - Jim_MakeErrorMessage(interp); - fprintf(stderr, "%s\n", Jim_String(Jim_GetResult(interp))); + JimPrintErrorMessage(interp); } - Jim_SetVariableStrWithStr(interp, "jim_argv0", argv[0]); + Jim_SetVariableStrWithStr(interp, "jim::argv0", argv[0]); Jim_SetVariableStrWithStr(interp, JIM_INTERACTIVE, argc == 1 ? "1" : "0"); retcode = Jim_initjimshInit(interp); if (argc == 1) { if (retcode == JIM_ERR) { - Jim_MakeErrorMessage(interp); - fprintf(stderr, "%s\n", Jim_String(Jim_GetResult(interp))); + JimPrintErrorMessage(interp); } if (retcode != JIM_EXIT) { JimSetArgv(interp, 0, NULL); retcode = Jim_InteractivePrompt(interp); } @@ -21583,12 +21973,11 @@ Jim_SetVariableStr(interp, "argv0", Jim_NewStringObj(interp, argv[1], -1)); JimSetArgv(interp, argc - 2, argv + 2); retcode = Jim_EvalFile(interp, argv[1]); } if (retcode == JIM_ERR) { - Jim_MakeErrorMessage(interp); - fprintf(stderr, "%s\n", Jim_String(Jim_GetResult(interp))); + JimPrintErrorMessage(interp); } } if (retcode == JIM_EXIT) { retcode = Jim_GetExitCode(interp); } Index: autosetup/local.tcl ================================================================== --- autosetup/local.tcl +++ autosetup/local.tcl @@ -26,5 +26,200 @@ set tclconfig($name) [string trim $value '] } } return [array get tclconfig] } + +# The complex extension checking is done here. + +global withinfo +global extdb + +# Final determination of module status +dict set extdb status {} + +# Returns 1 if the extension has the attribute +proc ext-has {ext attr} { + expr {$attr in [dict get $::extdb attrs $ext]} +} + +# Returns an entry from the extension 'info' table, or $default otherwise +proc ext-get {ext key {default {}}} { + if {[dict exists $::extdb info $ext $key]} { + return [dict get $::extdb info $ext $key] + } else { + return $default + } +} + +# Set the status of the extension to the given value, and returns the value +proc ext-set-status {ext value} { + dict set ::extdb status $ext $value + return $value +} + +# Returns the status of the extension, or ? if unknown +proc ext-get-status {ext} { + if {[dict exists $::extdb status $ext]} { + return [dict get $::extdb status $ext] + } + return ? +} + +proc check-extension-status {ext required} { + global withinfo + + set status [ext-get-status $ext] + + if {$ext in $withinfo(without)} { + # Disabled without further ado + msg-result "Extension $ext...disabled" + return [ext-set-status $ext n] + } + + if {$status in {m y n}} { + return $status + } + + # required is "required" if this extension *must* be enabled + # required is "wanted" if it is not fatal for this extension + # not to be enabled + + array set depinfo {m 0 y 0 n 0} + + # Check direct dependencies + if [ext-get $ext check 1] { + # "check" conditions are met + } else { + # not met + incr depinfo(n) + } + + if {$depinfo(n) == 0} { + # Now extension dependencies + foreach i [ext-get $ext dep] { + set status [check-extension-status $i $required] + #puts "$ext: dep $i $required => $status" + incr depinfo($status) + if {$depinfo(n)} { + break + } + } + } + + #parray depinfo + + if {$depinfo(n)} { + msg-checking "Extension $ext..." + if {$required eq "required"} { + user-error "dependencies not met" + } + msg-result "disabled (dependencies)" + return [ext-set-status $ext n] + } + + # Selected as a module? + if {$ext in $withinfo(mod)} { + if {[ext-has $ext tcl]} { + # Easy, a Tcl module + msg-result "Extension $ext...tcl" + } elseif {[ext-has $ext static]} { + user-error "Extension $ext can't be a module" + } else { + msg-result "Extension $ext...module" + foreach i [ext-get $ext libdep] { + define-append LDLIBS_$ext [get-define $i ""] + } + } + return [ext-set-status $ext m] + } + + # Selected as a static extension? + if {[ext-has $ext shared]} { + user-error "Extension $ext can only be selected as a module" + } elseif {$ext in $withinfo(ext) || $required eq "$required"} { + msg-result "Extension $ext...enabled" + } elseif {$ext in $withinfo(maybe)} { + msg-result "Extension $ext...enabled (default)" + } else { + # Could be selected, but isn't (yet) + return [ext-set-status $ext x] + } + foreach i [ext-get $ext libdep] { + define-append LDLIBS [get-define $i ""] + } + return [ext-set-status $ext y] +} + +# Examines the user options (the $withinfo array) +# and the extension database ($extdb) to determine +# what is selected, and in what way. +# +# The results are available via ext-get-status +# And a dictionary is returned containing four keys: +# static-c extensions which are static C +# static-tcl extensions which are static Tcl +# module-c extensions which are C modules +# module-tcl extensions which are Tcl modules +proc check-extensions {} { + global extdb withinfo + + # Check valid extension names + foreach i [concat $withinfo(ext) $withinfo(mod)] { + if {![dict exists $extdb attrs $i]} { + user-error "Unknown extension: $i" + } + } + + set extlist [lsort [dict keys [dict get $extdb attrs]]] + + set withinfo(maybe) {} + + # Now work out the default status. We have. + # normal case, include !optional if possible + # --without=default, don't include optional + if {$withinfo(nodefault)} { + lappend withinfo(maybe) stdlib + } else { + foreach i $extlist { + if {![ext-has $i optional]} { + lappend withinfo(maybe) $i + } + } + } + + foreach i $extlist { + define LDLIBS_$i "" + } + + foreach i [concat $withinfo(ext) $withinfo(mod)] { + check-extension-status $i required + } + foreach i $withinfo(maybe) { + check-extension-status $i wanted + } + + array set extinfo {static-c {} static-tcl {} module-c {} module-tcl {}} + + foreach i $extlist { + set status [ext-get-status $i] + set tcl [ext-has $i tcl] + switch $status,$tcl { + y,1 { + define jim_ext_$i + lappend extinfo(static-tcl) $i + } + y,0 { + define jim_ext_$i + lappend extinfo(static-c) $i + # If there are any static C++ extensions, jimsh must be linked using + # the C++ compiler + if {[ext-has $i cpp]} { + define HAVE_CXX_EXTENSIONS + } + } + m,1 { lappend extinfo(module-tcl) $i } + m,0 { lappend extinfo(module-c) $i } + } + } + return [array get extinfo] +} ADDED fossil.1 Index: fossil.1 ================================================================== --- /dev/null +++ fossil.1 @@ -0,0 +1,100 @@ +.TH FOSSIL "1" "February 2015" "http://fossil-scm.org" "User Commands" +.SH NAME +fossil \- Distributed Version Control System +.SH SYNOPSIS +.B fossil +\fIhelp\fR +.br +.B fossil +\fIhelp COMMAND\fR +.br +.B fossil +\fICOMMAND [OPTIONS]\fR +.SH DESCRIPTION +Fossil is a distributed version control system (DVCS) with built-in +wiki, ticket tracker, CGI/http interface, and http server. + +.SH Common COMMANDs: + +add clean import pull stash +.br +addremove clone info purge status +.br +all commit init push sync +.br +annotate diff json rebuild tag +.br +bisect export ls remote-url timeline +.br +blame extras merge revert ui +.br +branch finfo mv rm undo +.br +bundle fusefs open rss unpublish +.br +cat gdiff praise settings update +.br +changes help publish sqlite3 version + +.SH FEATURES + +Features as described on the fossil home page. + +.HP +1. +.B Integrated Bug Tracking, Wiki, & Technotes +- In addition to doing distributed version control like Git and +Mercurial, Fossil also supports bug tracking, wiki, and technotes. + +.HP +2. +.B Built-in Web Interface +- Fossil has a built-in and intuitive web interface that promotes +project situational awareness. Type "fossil ui" and Fossil automatically +opens a web browser to a page that shows detailed graphical history and +status information on that project. + +.HP +3. +.B Self-Contained +- Fossil is a single self-contained stand-alone executable. To install, +simply download a precompiled binary for Linux, Mac, OpenBSD, or Windows +and put it on your $PATH. Easy-to-compile source code is available for +users on other platforms. + +.HP +4. +.B Simple Networking +- No custom protocols or TCP ports. Fossil uses plain old HTTP (or HTTPS +or SSH) for all network communications, so it works fine from behind +restrictive firewalls, including proxies. The protocol is bandwidth +efficient to the point that Fossil can be used comfortably over dial-up. + +.HP +5. +.B CGI/SCGI Enabled +- No server is required, but if you want to set one up, Fossil supports +four simple server configurations. + +.HP +6. +.B Autosync +- Fossil supports "autosync" mode which helps to keep projects moving +forward by reducing the amount of needless forking and merging often +associated with distributed projects. + +.HP +7. +.B Robust & Reliable +- Fossil stores content using an enduring file format in an SQLite +database so that transactions are atomic even if interrupted by a +power loss or system crash. Automatic self-checks verify that all +aspects of the repository are consistent prior to each commit. In +over seven years of operation, no work has ever been lost after +having been committed to a Fossil repository. + +.SH DOCUMENTATION +http://www.fossil-scm.org/ +.br +.B fossil +\fIui\fR DELETED fossil.nsi Index: fossil.nsi ================================================================== --- fossil.nsi +++ /dev/null @@ -1,59 +0,0 @@ -; example2.nsi -; -; This script is based on example1.nsi, but adds uninstall support -; and (optionally) start menu shortcuts. -; -; It will install notepad.exe into a directory that the user selects, -; - -; The name of the installer -Name "Fossil" - -; The file to write -OutFile "fossil-setup.exe" - -; The default installation directory -InstallDir $PROGRAMFILES\Fossil -; Registry key to check for directory (so if you install again, it will -; overwrite the old one automatically) -InstallDirRegKey HKLM SOFTWARE\Fossil "Install_Dir" - -; The text to prompt the user to enter a directory -ComponentText "This will install fossil on your computer." -; The text to prompt the user to enter a directory -DirText "Choose a directory to install in to:" - -; The stuff to install -Section "Fossil (required)" - ; Set output path to the installation directory. - SetOutPath $INSTDIR - ; Put file there - File ".\fossil.exe" - ; Write the installation path into the registry - WriteRegStr HKLM SOFTWARE\Fossil "Install_Dir" "$INSTDIR" - ; Write the uninstall keys for Windows - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Fossil" "DisplayName" "Fossil (remove only)" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Fossil" "UninstallString" '"$INSTDIR\uninstall.exe"' - WriteUninstaller "uninstall.exe" -SectionEnd - - -; uninstall stuff - -UninstallText "This will uninstall fossil. Hit next to continue." - -; special uninstall section. -Section "Uninstall" - ; remove registry keys - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Fossil" - DeleteRegKey HKLM SOFTWARE\Fossil - ; remove files - Delete $INSTDIR\fossil.exe - ; MUST REMOVE UNINSTALLER, too - Delete $INSTDIR\uninstall.exe - ; remove shortcuts, if any. - RMDir "$SMPROGRAMS\Fossil" - RMDir "$INSTDIR" -SectionEnd - -; eof ADDED setup/fossil.iss Index: setup/fossil.iss ================================================================== --- /dev/null +++ setup/fossil.iss @@ -0,0 +1,49 @@ +; +; Copyright (c) 2014 D. Richard Hipp +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the Simplified BSD License (also +; known as the "2-Clause License" or "FreeBSD License".) +; +; This program is distributed in the hope that it will be useful, +; but without any warranty; without even the implied warranty of +; merchantability or fitness for a particular purpose. +; +; Author contact information: +; drh@hwaci.com +; http://www.hwaci.com/drh/ +; + +[Setup] +ArchitecturesAllowed=x86 x64 +AlwaysShowComponentsList=false +AppCopyright=Copyright (c) D. Richard Hipp. All rights reserved. +AppID={{f1c25a1f-3954-4e1a-ac36-4314c52f057c} +AppName=Fossil +AppPublisher=Fossil Development Team +AppPublisherURL=https://www.fossil-scm.org/ +AppSupportURL=https://www.fossil-scm.org/ +AppUpdatesURL=https://www.fossil-scm.org/ +AppVerName=Fossil v{#AppVersion} +AppVersion={#AppVersion} +AppComments=Simple, high-reliability, distributed software configuration management system. +AppReadmeFile=https://www.fossil-scm.org/index.html/doc/tip/www/quickstart.wiki +DefaultDirName={pf}\Fossil +DefaultGroupName=Fossil +OutputBaseFilename=fossil-win32-{#AppVersion} +OutputManifestFile=fossil-win32-{#AppVersion}-manifest.txt +SetupLogging=true +UninstallFilesDir={app}\uninstall +VersionInfoVersion={#AppVersion} + +[Components] +Name: Application; Description: Core application.; Types: custom compact full; Flags: fixed + +[Dirs] +Name: {app}\bin + +[Files] +Components: Application; Source: ..\fossil.exe; DestDir: {app}\bin; Flags: restartreplace uninsrestartdelete + +[Registry] +Components: Application; Root: HKLM32; SubKey: Software\Fossil; ValueType: string; ValueName: Install_Dir; ValueData: {app}; Flags: uninsdeletekeyifempty uninsdeletevalue ADDED setup/fossil.nsi Index: setup/fossil.nsi ================================================================== --- /dev/null +++ setup/fossil.nsi @@ -0,0 +1,59 @@ +; example2.nsi +; +; This script is based on example1.nsi, but adds uninstall support +; and (optionally) start menu shortcuts. +; +; It will install notepad.exe into a directory that the user selects, +; + +; The name of the installer +Name "Fossil" + +; The file to write +OutFile "fossil-setup.exe" + +; The default installation directory +InstallDir $PROGRAMFILES\Fossil +; Registry key to check for directory (so if you install again, it will +; overwrite the old one automatically) +InstallDirRegKey HKLM SOFTWARE\Fossil "Install_Dir" + +; The text to prompt the user to enter a directory +ComponentText "This will install fossil on your computer." +; The text to prompt the user to enter a directory +DirText "Choose a directory to install in to:" + +; The stuff to install +Section "Fossil (required)" + ; Set output path to the installation directory. + SetOutPath $INSTDIR + ; Put file there + File "..\fossil.exe" + ; Write the installation path into the registry + WriteRegStr HKLM SOFTWARE\Fossil "Install_Dir" "$INSTDIR" + ; Write the uninstall keys for Windows + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Fossil" "DisplayName" "Fossil (remove only)" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Fossil" "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteUninstaller "uninstall.exe" +SectionEnd + + +; uninstall stuff + +UninstallText "This will uninstall fossil. Hit next to continue." + +; special uninstall section. +Section "Uninstall" + ; remove registry keys + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Fossil" + DeleteRegKey HKLM SOFTWARE\Fossil + ; remove files + Delete $INSTDIR\fossil.exe + ; MUST REMOVE UNINSTALLER, too + Delete $INSTDIR\uninstall.exe + ; remove shortcuts, if any. + RMDir "$SMPROGRAMS\Fossil" + RMDir "$INSTDIR" +SectionEnd + +; eof ADDED skins/README.md Index: skins/README.md ================================================================== --- /dev/null +++ skins/README.md @@ -0,0 +1,45 @@ +Built-in Skins +============== + +Each subdirectory under this folder describes a built-in "skin". +There are four files in each subdirectory for the CSS, the "details" +file, the footer, and the header for that skin. + +To improve an existing built-in skin, simply edit the appropriate +files and recompile. + +To add a new skin: + + 1. Create a new subdirectory under skins/. (The new directory is + called "skins/newskin" below but you should use a new original + name, of course.) + + 2. Add files skins/newskin/css.txt, skins/newskin/details.txt, + skins/newskin/footer.txt and skins/newskin/header.txt. + Be sure to "fossil add" these files. + + 3. Go to the src/ directory and rerun "tclsh makemake.tcl". This + step rebuilds the various makefiles so that they have dependencies + on the skin files you just installed. + + 4. Edit the BuiltinSkin[] array near the top of the src/skins.c source + file so that it describes and references the "newskin" skin. + + 5. Type "make" to rebuild. + +Development Hints +----------------- + +One way to develop a new skin is to copy the baseline files (css.txt, +details.txt, footer.txt, and header.txt) into a working directory $WORKDIR +then launch Fossil with a command-line option "--skin $WORKDIR". Example: + + cp -r skins/default newskin + fossil ui --skin ./newskin + +When the argument to --skin contains one or more '/' characters, the +appropriate skin files are read from disk from the directory specified. +So after launching fossil as shown above, you can edit the newskin/css.txt, +newskin/details.txt, newskin/footer.txt, and newskin/header.txt files using +your favorite text editor, then press Reload on your browser to see +immediate results. ADDED skins/aht/details.txt Index: skins/aht/details.txt ================================================================== --- /dev/null +++ skins/aht/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 0 ADDED skins/black_and_white/css.txt Index: skins/black_and_white/css.txt ================================================================== --- /dev/null +++ skins/black_and_white/css.txt @@ -0,0 +1,177 @@ +/* General settings for the entire page */ +body { + margin:0px 0px 0px 0px; + padding:0px; + font-family:verdana, arial, helvetica, "sans serif"; + color:#333; + background-color:white; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + -mx-text-size-adjust: none; +} + +/* consistent colours */ +h2 { + color: #333; +} +h3 { + color: #333; +} + +/* The project logo in the upper left-hand corner of each page */ +div.logo { + display: table-cell; + text-align: left; + vertical-align: bottom; + font-weight: bold; + color: #333; + white-space: nowrap; +} + +/* The page title centered at the top of each page */ +div.title { + display: table-cell; + font-size: 2em; + font-weight: bold; + text-align: center; + color: #333; + vertical-align: bottom; + width: 100%; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + padding-right: 10px; + text-align: right; + vertical-align: bottom; + padding-bottom: 5px; + color: #333; + font-size: 0.8em; + font-weight: bold; + white-space: nowrap; +} + +/* The header across the top of the page */ +div.header { + margin:10px 0px 10px 0px; + padding:1px 0px 0px 20px; + border-style:solid; + border-color:black; + border-width:1px 0px; + background-color:#eee; +} + +/* The main menu bar that appears at the top left of the page beneath +** the header. Width must be co-ordinated with the container below */ +div.mainmenu { + float: left; + margin-left: 10px; + margin-right: 10px; + font-size: 0.9em; + font-weight: bold; + padding:5px; + background-color:#eee; + border:1px solid #999; + width:8em; +} + +/* Main menu is now a list */ +div.mainmenu ul { + padding: 0; + list-style:none; +} +div.mainmenu a, div.mainmenu a:visited{ + padding: 1px 10px 1px 10px; + color: #333; + text-decoration: none; +} +div.mainmenu a:hover { + color: #eee; + background-color: #333; +} + +/* Container for the sub-menu and content so they don't spread +** out underneath the main menu */ +#container { + padding-left: 9em; +} + +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu, div.sectionmenu { + padding: 3px 10px 3px 10px; + font-size: 0.9em; + text-align: center; + border:1px solid #999; + border-width:1px 0px; + background-color: #eee; + color: #333; +} +div.submenu a, div.submenu a:visited, div.sectionmenu>a.button:link, +div.sectionmenu>a.button:visited { + padding: 3px 10px 3px 10px; + color: #333; + text-decoration: none; +} +div.submenu a:hover, div.sectionmenu>a.button:hover { + color: #eee; + background-color: #333; +} + +/* All page content from the bottom of the menu or submenu down to +** the footer */ +div.content { + padding: 2ex 1ex 0ex 2ex; +} + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0px; + margin-top: 1em; + padding: 1px 1px 1px 1px; + font-size: 1.2em; + font-weight: bold; + border-style:solid; + border-color:#999; + border-width:1px 0px; + background-color: #eee; + color: #333; + white-space: nowrap; +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + background: #eee; + border: 2px #999 solid; + font-size: 1em; font-weight: normal; + padding: .25em; + margin: .2em 0 .2em 0; + float: left; + clear: left; + color: #333; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + font-size: 0.8em; + margin-top: 12px; + padding: 5px 10px 5px 10px; + text-align: right; + background-color: #eee; + color: #555; +} + +/* blocks */ +pre.verbatim { + background-color: #f5f5f5; + padding: 0.5em; + white-space: pre-wrap; +} + +/* The label/value pairs on (for example) the ci page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} ADDED skins/black_and_white/details.txt Index: skins/black_and_white/details.txt ================================================================== --- /dev/null +++ skins/black_and_white/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 0 ADDED skins/black_and_white/footer.txt Index: skins/black_and_white/footer.txt ================================================================== --- /dev/null +++ skins/black_and_white/footer.txt @@ -0,0 +1,4 @@ + + ADDED skins/black_and_white/header.txt Index: skins/black_and_white/header.txt ================================================================== --- /dev/null +++ skins/black_and_white/header.txt @@ -0,0 +1,54 @@ + + + +$<project_name>: $<title> + + + + +
+ +
$</div> + <div class="status"><th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></div> +</div> +<div class="mainmenu"> +<th1> +html "<a href='$home$index_page'>Home</a>\n" +if {[anycap jor]} { + html "<a href='$home/timeline'>Timeline</a>\n" +} +if {[anoncap oh]} { + html "<a href='$home/tree?ci=tip'>Files</a>\n" +} +if {[anoncap o]} { + html "<a href='$home/brlist'>Branches</a>\n" + html "<a href='$home/taglist'>Tags</a>\n" +} +if {[anoncap r]} { + html "<a href='$home/ticket'>Tickets</a>\n" +} +if {[anoncap j]} { + html "<a href='$home/wiki'>Wiki</a>\n" +} +if {[hascap s]} { + html "<a href='$home/setup'>Admin</a>\n" +} elseif {[hascap a]} { + html "<a href='$home/setup_ulist'>Users</a>\n" +} +if {[info exists login]} { + html "<a href='$home/login'>Logout</a>\n" +} else { + html "<a href='$home/login'>Login</a>\n" +} +</th1></ul></div> ADDED skins/blitz/README.md Index: skins/blitz/README.md ================================================================== --- /dev/null +++ skins/blitz/README.md @@ -0,0 +1,13 @@ +## Blitz Theme + +Contributed by James Moger (james.moger@gitblit.com) + +This theme is inspired by my own project, [Gitblit](http://gitblit.com), and offered to the Fossil project. + +This theme embeds & uses an unmodified copy of [Normalize 3.0.2](https://necolas.github.io/normalize.css/) which is distributed under an [MIT license](https://github.com/necolas/normalize.css/blob/master/LICENSE.md). + +This theme uses half of a heavily-modified version of [Skeleton](http://getskeleton.com) which is distributed under an [MIT license](https://github.com/dhg/Skeleton/blob/master/LICENSE.md). None of the responsive elements (media queries) are included at this time. + +The font used in the included Fossil logo image is [Trillium Web Light](http://www.google.com/fonts/specimen/Titillium+Web) @ 48px HTML color code #456a7a. + +The RSS feed icon is sourced from [Font-Awesome](https://fortawesome.github.io/Font-Awesome/icons) by Dave Gandy and is distributed under the [SIL OFL 1.1 ](http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL) license. ADDED skins/blitz/arrow_project.png Index: skins/blitz/arrow_project.png ================================================================== --- /dev/null +++ skins/blitz/arrow_project.png cannot compute difference between binary files ADDED skins/blitz/css.txt Index: skins/blitz/css.txt ================================================================== --- /dev/null +++ skins/blitz/css.txt @@ -0,0 +1,1237 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} + + +/* + * Blitz + * + * Skin inspired by Gitblit with heavily-modified excerpts from Skeleton 2.0.4. + * Blitz is authored by james.moger@gitblit.com. + * + * Skeleton is authored by Dave Gamache and is distributed under the MIT license. + * http://getskeleton.com + * +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ + +html { + /* 62.5% so that the REM values are base 10px. */ + /* 1.5rem = 15px */ + font-size: 62.5%; +} + +/* Typography +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +h1, h2, h3, h4, h5, h6 { + margin: 0; + margin-bottom: 1rem; + font-weight: 700; +} + +h1 { font-size: 3.0rem; line-height: 1.2; } +h2 { font-size: 2.6rem; line-height: 1.25; } +h3 { font-size: 2.4rem; line-height: 1.3; } +h4 { font-size: 2.0rem; line-height: 1.35; } +h5 { font-size: 1.6rem; line-height: 1.5; } +h6 { font-size: 1.4rem; line-height: 1.6; } + +h1 small, h2 small, h3 small, h4 small, h5 small, h6 small { + font-size: 0.75em; + font-weight: 400; + color: #ccc; +} + +pre, code { + font-size: 1.2rem; +} + +body { + font-size: 1.4em; /* currently ems cause chrome bug misinterpreting rems on body element */ + line-height: 1.5; + font-weight: 400; + font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #333; + background-color: #f8f8f8; +} + +/* Spacing +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +button, +.button { + margin-bottom: 1rem; +} + +input, +textarea, +select, +fieldset, +pre, +blockquote, +dl, +figure, +table, +p, +ul, +ol { + margin-bottom: 1rem; +} + +p { + margin-top: 0; +} + +hr { + margin-top: 3rem; + margin-bottom: 3.5rem; + border-width: 0; + border-top: 1px solid #ccc; +} + + +/* Buttons +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.button, +button, +input[type="button"], +input[type="reset"], +input[type="submit"] { + display: inline-block; + height: 3.3rem; + padding: 0 2.2rem; + color: #555 !important; + text-align: center; + font-size: 1.1rem; + font-weight: 700; + line-height: 3.3rem; + letter-spacing: .08rem; + text-transform: uppercase; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border-radius: 4px; + border: 1px solid #ccc; + cursor: pointer; + box-sizing: border-box; +} + +.button:hover, +button:hover, +input[type="button"]:hover, +input[type="reset"]:hover, +.button:focus, +button:focus, +input[type="button"]:focus, +input[type="reset"]:focus { + color: #444 !important; + background-color: #eee; + border-color: #aaa; + outline: 0; +} + +input[type="submit"] { + color: white !important; + background-color: #446979; + border-color: #446979; +} + +input[type="submit"]:hover, +input[type="submit"]:focus { + color: white !important; + background-color: #648898; + border-color: #648898; +} + + +/* Forms +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea, +select { + height: 3.3rem; + padding: 6px 10px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: none; + box-sizing: border-box; +} + +/* Removes awkward default styles on some inputs for iOS */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +textarea { + height: inherit; + min-height: 65px; + padding-top: 6px; + padding-bottom: 6px; +} + +input[type="email"]:focus, +input[type="number"]:focus, +input[type="search"]:focus, +input[type="text"]:focus, +input[type="tel"]:focus, +input[type="url"]:focus, +input[type="password"]:focus, +textarea:focus, +select:focus { + border: 1px solid #aaa; + outline: 0; +} + +label, +legend { + display: block; + margin-bottom: .5rem; + font-weight: 700; +} + +fieldset { + padding: 0; + border-width: 0; +} + +input[type="checkbox"], +input[type="radio"] { + display: inline; +} + + +/* Links +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +a { + color: #446979; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + +/* Lists +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul { + list-style: square; +} + +ol { + list-style: decimal; +} + +ol, ul { + padding-left: 3rem; + margin-top: 0; +} + +li { + margin-bottom: 0.5rem; +} + + +/* Nested Lists +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul ul, +ul ol, +ol ol, +ol ul { + margin: 1rem 0 1rem 2rem; +} + + +/* Code +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +code, kbd { + padding: .2rem .5rem; + margin: 0 .2rem; + white-space: nowrap; + background: #f8f8f8; + border: 1px solid #ccc; + border-radius: 4px; +} + +pre > code { + display: block; + padding: 1rem 1.5rem; + white-space: pre; +} + +pre.verbatim { + background-color: inherit; + white-space: pre-wrap; +} + + +/* Blockquote +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +blockquote { + padding: 0px 20px; + margin: 0 0 20px; + border-left: 4px solid #ccc; +} + + +/* Tables +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +th, +td { + padding: 6px 5px; + text-align: left; + border-bottom: 1px solid #ddd; } +th:first-child, +td:first-child { + padding-left: 0; } +th:last-child, +td:last-child { + padding-right: 0; } + + +/* + * Blitz Page Layout Design + * + * html > body > header > container > mainmenu + * middle > container > submenu & content + * footer > container > generation stats, fossil logo, version + * + ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ + + +/* Container + * Represents the usable layout space for header, middle, and footer. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.container { + position: relative; + width: 100%; + max-width: 900px; + margin: 0 auto; + box-sizing: border-box; +} + + +/* Header + * Div displayed at the top of every page. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.header { + color: #666; + font-weight: 400; + padding-top: 10px; + border-width: 0px; + border-top: 4px solid #446979; + border-bottom: 1px solid #ccc; +} + +.header .logo { + display: inline-block; +} + +.header .login { + padding-top: 2px; + text-align: right; +} + +.header .login .button { + margin: 0; +} + +.header h1 { + margin: 0px; + color: #666; + display: inline-block; +} + +.header .logo h1 { + display: inline-block; +} + +.header .title h1 { + padding-bottom: 10px; +} + +.header h1 small, .header h2 small { + color: #888; +} + +.header a.rss { + display: inline-block; + padding: 10px 15px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMNDhwn05VjawAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAGlSURBVDjLrdPfb8xREAXwT7tIl+paVNaPJghCKC8kXv0XXvyNXsRfwYPQJqVKiqykWFVZXd12vcxNJtduUtJJvrm7984998ycMxxwNGI9jPs4j7nY+/U/gIdiPYO71dk21rCE7r8ybOHGmMfmcRNnsbEf1gXwNzqYSXs5WljEMXzAaBLg1Ji9Js7hOi6OeeAznqC/X8AcMyHWYpX7E4/Rm1QyHMdefCWGeI/VcMDR2D8S7Fci5y/AeTzCPVyLi1sYJAut4BTaiX0n9kc14MmkcjPY3I5LXezGtxqKtyJ3Lir6VAM2AmCq6m8Hl6PsQTB5hyvxmMhZxk4G3MZLfAwLtdNZM9rwOs528TVVNB3ga7UoQ2wGmyWciFaU0VwIJiP8iL6Xfp7GK+w0JthliDep8UKonTSGvbBTaU8f3QzYxgPcCsBvWK9E6OBFCNGPVjTTqC430p+H6fLVGLGtmIw7SbwevqT+XkgVPJ9Otpmtyl6I9XswLXEp/d6oPN0ugJu14xMLob4kgPRYjtkCOMDTUG+AZ3ibEtfDLorfEmAB3UuTdXDxBzUUZV+B82aLAAAAAElFTkSuQmCC); + background-position: center center; + background-repeat: no-repeat; +} + + +/* Middle + * Center div displayed between header and footer. Contains per-page content. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.middle { + background-color: white; + padding-bottom: 20px; + max-width: 100%; + box-sizing: border-box; +} + + +/* Content + * Displayed below submenu within the middle div. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.content { + padding-top: 15px; +} + +.content a { + color: #002060; +} + + +/* Footer + * Displayed after the middle div and forms the page bottom. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.footer { + padding: 10px 0 60px; + border-top: 1px solid #ccc; + background-color: #f8f8f8; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABGCAQAAADxl9ugAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQffAwkQBRPw+yfrAAAAPHRFWHRDb21tZW50ACBJbWFnZSBnZW5lcmF0ZWQgYnkgRVNQIEdob3N0c2NyaXB0IChkZXZpY2U9cG5tcmF3KQqV01S1AAANg0lEQVRo3s2aa3RVRZbH/3tXnftIbhJCeJMXyEsNCsgrSBzAYXyt0YVj2zSOonbLqNNO+25FB2kdoBVdrp6l09P2NNpOL/ExSrtUdGRU5JEQgggCggIhQIBAIIGQ5N57TlXt+ZAICY+AGCT76zlV91dVe+/a+38uDQ7dYmrxw41QocJuni28vWTedEa7Gd9iatplos7Y2mfDRED6A2G0I2At6AdPIiA84HgbLhx1o9QX9Zoh7QjYHpOkYJYrfNyOKH0O/f2FJpfkSRmMCNqDk9rDXwTAuxK7h3uFf7vXxC6QVO8w19taNOT4Y0wu1sCdW0AA6IZ73bCUaI/GHuqnsJqcEqVrzSo+wNtLdgAPsz6jeVURtQ9gA8bQT8xztW7X2u3I4G6UdA0cohxcSAN7nt939/zDQzl0LnfwaLpxqFT/i7w7XLYXhoUQ+YFW3pJlC0/nt6jZZb4zjXY2AaGXvQOz/gMY09NM4C6ixRL7OU3PTm4ORdiIjyLsJvq1Z28Hj5rBM82xMTqPuhHznmU72vq12W5M3+LykQWUW9pirzXOmmk8wjE87kb8HPmSKlY+wY62R9jhI69xf/AmYiEfiXs+e4CCAZiDUc9wP6cRIJn8tq23PQClbwap9Ivk7usiGj/CDs5xl3Qe/EiQCEWsgXLlq8sfamM73osMna43ef/pfm731Hsxv+Z00ozFzaJpF+gMrsNB2BwL3cGpSsNRWHbmvTQNiTbev9q4xXszMLzqpdgDNjxv/YjmvHlSQIc5brl8nrqT5pllMpHM9wRc4gU3oC8JyDLt8P78ZLyM2lrmAeTg5YrKdf1S1N9QTs6At1YM49DJo1gwWy7uE5kKBQHbler9K8R+L8API2qmFoDY7ejxpxGJxtMctyZcfYvJDYX4i5LXp/MJAS2edkM6edNUDuLNOdMDmz913bIw8RidKO/1w+3NQfePbHQjdbd/NKOjbq7UqQOmYtXb/+BdbGc4AJjPX53CXe6UGweYWxUR8N8rNkzn4wAFc9yYPvGbI+mttoycpo14fWbD0hZv+njOAcBl6Yl8jiEMbSKhqGPypZ5N0B2kDxrjOjF0QuI2oRqj2z6rAebx5pNiRrEo0jhV9wFkP//xpkPHAU6UGdnBLyR8jMOQs6xwcMAznVys+Uqa5YCCnilFkuVSkeKlGKfDED8JaI+VifMWR9LPC/u+UkoZA6edOezVu83pny4yD6qQnCxhDzk/MhUED+8PWHJMkAgWROvvQoxOcEdayynVY9/5tIgAwsfIvaD3XaFx6M6ddMx3WlurCGICiAIhMLxB1VM+wQRMDOeYjHCUMum85OW909/acOlJgqaI3qrulU9dYGlgzVI1tsVrETzlOt3MeUGCudVoCgJmAhFU78F+6Usu0V39iylSHCRZMROM0do5pUDWQamEPZTy5oqyXVt77bG94HGEyTkCE8BsjLOcl11Uvf7V+qvIP86jgWH8zhc9LpKYU7qRhH5NTRknwFw3+jpMQCIItG69PCLfZ1YKAMlS/7PQDCc2qbRzSjET4slINBAvhsPxjarKKy/9FhjZG8Ol4tINi/uFzkcfl8sEnwExATGRY73Ze2VpAHxHoPFki7r2iljN3a6LqqOxnZ+vXUBNwTF6uL0e0Nr3tSYWpqNhQtaIaA0A1oZftuf5Y1xmCMRO2IM1VXqf3aYr5IDXODTx7+43PAjPFcit1MANeHfFxuFpSFW9TbbORpaNEqt6/5tIoxuBOO049NHXNTN5lFwlE3o09JUMlWKdSmJ/UC8TvRwqjNzoVwNIw3upcluyq4hSAq3Idyt5HOyRIDGuGdA42un+6wv/Cq7sFA1F/B4N/5MEgL/yOsSPlFSMOzD5JhkMEQ8l8u41Job7hVoExsiRNMU1Biac4oqD92Np8Xt0BgUiEOeIHLyobbQb9U/8fSAItsFdJv1wECASB5L92xb2yefcE3Q+jgsof//m3+PO2i7YhgV0HisAK48UnE03EaT67axMyjMNXpFkf/za8r3VPP2I46Ri+aYDW0z3SMT6NJb62wPiJEFhEoFCYIhNoyS4VF1CBKArPu6MKWysBZhFVFrD7yri2VkuR4QZAIkTMAOAiDIY9KvFQ3gVPqXVFD9JSbSS3vP7b/QzKY8SlO6G5e2fv/dozvDxciKvCqOYrIURT/XU2+0SKrdVqtpVojz4Sq1Vi1duV0UE+PSEy33AhRRZAzDDc4vXrB1xKU0gRUR0LCCRi/be8XZ12/0M41Ka5K/5JjA80PlQKMjfeVVN9ZG0NZbmH8xf5Y8nJxIKmYC7SmLlexeVp2wKb+y++f6KZ3c/03g+qSISPONGDqHRZOk7QGde6XsrjTP+kXTTCpCVGErZteZUDRdhD3Ls65tz64J85ZGyQ0tXDAzckadFlOtv+ZQLoYMgHHaGcrJ7fb56inR3WVJJRVTdVM3kQSh5r1hjlbIGIKVX6ammE1vnmImh4Y4BZHGcvmvx5XQ6BcRIfnPHxV82ZnFUYsh+bWXLzMuwquGwCnvdBUTkqHcPmb+l5cJZcKervlwc8N04sbYwSJC1tsnneemJqh32LksZflr1SUh+pT6q+/ov/krztitDq1s4hP1p7mbfuFfkEKJgSejLh/Wa7Y5GJsdQmEYDoVocDCvnLCvnwPDMzOK/uiVoPS8IxoQi409D2xAU4Hd2xE2DZ3gjvcpVZY+0iqgAbFh7F9grp84xf5D6QFOE/rYo1vto4z6ZVg/COHKAiGJrACaljNHaiaqTp0NeXlr37Ydz/ahSLY9YzLIPMzlxSrxMzM/ochfnO8UHvJJpgTnm+UE5GKMeOu3LrLJPd32+Z1FamfczKli36jzTtHplvPg46o2WgNwEaOqoTk8Jxsu4+vGUruDkCCCJkg171g2ntqMkjrmuYUBwG7LglIq/U7Zr1HEjJrtiay9imIzsqt37gb4/RSZlSsP88iZf5T3kX2xOuBEck2yp13F32NbDuZbHqSjwFrzJbeP5eN6NHOJuoQyXRIZ7Y+3Gh08wohjpW/VOkFMyqagHILnOSAOuflh1bqKggew524Zs1ex+R6cmz1W7Z6clvzzF8T7rLsvnSfBgJVO9cN+ax4/IRwazZLZrqmOSWJjAR0hCdFc/F2AtToTos34POQHAkVw2Sp2m7EKwOEyLVj59w6EtpyjdFYSCCYhaC0n9fcmmr5pb8QBrQl0wqn/hNc82J8Rfc8lWVwVGwLeNDVPTdW30+KZN0Sab5JTBqOBBowIHzeZVJcBjXHdKXfQp93+DgwuIgy9QtqJ8evPhOtRRzZi30iKfBZPxQVNhofDP6sUXh05zA5MH+WKdpAgAJ/lAChqhyQmd5HgFChFoMvar0AaKq8qSGmA6o03lVFCEqx1Q0Ce0075hkgvW93EPNeMZ3Cl93eRl5Q8GVjoBs1tUN1l/rvkn1VOuNXUIAyATv6zz3bXPEo3t7N/L6bBirfV0MimktOclgnDUJHiTWxo9oJOZQT/TDUk0nIYfFODFaMMN4WFqbnEl8BzXtlBX++ATr2KW3eC9kbg/9d2l6/qmdxrq9bdRkBwMtrgd+lpkpxwyWSwAkvRKSfl0phf4l+6acGVeak6QQbCJYH/Gwci+zPi/+oMFEJpLdTDfQ8IpTTl8r/lkQuniX3oR98mjX65sJQZ0wYNu1NU0wZ8RjSUf0Qo22aCU1oAoCazTFe5bdZ1pVIqcv2B12XSm6awxCFNaichCr1I1auDOQFvKxzQHFD4mexGXNfds/PoYB+iMhxzwIC8vcJe7HKVIrBEoLRQkuVL+Ii70lDlMUOR/uHrJdNaAwXo82orksR8gfVUAGFkgy5JfhKdEKncelwhq8SinwMeidX+303VHuvOgCGSRRF2o6vb6FyKmQTGRNTraQt1qJ6EaAPA4V22MYH20wXx+aCyfKFvFAcymiYfCh/YhSQZACFHpBB/l6On2l0s/AgTBWdIHHbpIulzfWDbvAW4r3gPUIwWZ0lW6SoaE0AgDYG6Six0R4OjgWRMwGUAVHuUz+SS2iNQ+XSMAvKqzrLCemdtUo3ifWhpoTi7b/RSfVRH9zBc2y43umXVgtImf3R08c7zCIeYpVoPOtoh+ZjYEhd3kWtV4IHSldEjAV7U/ATHxig89Tx0QUFCtvUJisxnNknuHArSY4+xtNuk4ZQWai4wOBThJRqZJvhYXtptnNTVAHQnQYbT4k7QI6/Vd6w6f/U9h39cUgEg+BCG3rk/AHQ9wlhsz1IUCI3tRPrDVtdlhzO9rxPNke+nuXR0TUIdAMOrDlkJshwEM4QId98C8vKTmUUJHAwww03VNT8ny9hd/cJ9qCaU7QnrpjXvcsKuT6W7VdcvHc+sq95wDCuZILxp9t+TZJWVL/56PPdJzDhjDhG75UyUrsXXtBw9z/PjAObd3729leA++mTOldO07dyl9ghbmnALmYpjybndhO3/VV/dx9IQdlj53YQEAo2din3p1cv1VfDIF8EfvSdJQSZvU3ljGJJOvNuH94kOPs22jwWq3P5edTryG8G/OdtsxOPiZd6O1ppTeK0n43Hb/9yPuYBhPuFFXumEq3231S+ibL/e+yhtP2Zz+iD74hCu83vblr+W1yJ5LgguxmzedRu/8//AUSaqMTR1xAAAAAElFTkSuQmCC); + background-repeat: no-repeat; + background-position: center top 10px; +} + +.footer a { + color: #3b5c6b; +} + + +/* Main Menu + * Displayed in header, contains repository links. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.mainmenu { + clear:both; +} + +.mainmenu ul { + list-style: none outside; + display: block; + position: relative; + border-top: 1px solid #ccc; + padding: 0; +} + +.mainmenu li { + outline: 0; + display: block; + float: left; + margin: 0; +} + +.mainmenu li.active { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEQAACxEBf2RfkQAAAAd0SU1FB90FDxEXAZ2XRzAAAABJSURBVCjPY2CgBzhz5sx/QmoYiTXAxMSEkWRDsLkAl0GMpHoBm0EoAlu3bmUQFxcnGAboBjEhc4gxAJtLGUmJBVwuYiTXAGSDAIx5IBObnuVxAAAAAElFTkSuQmCC); + background-repeat: no-repeat; + background-position: center bottom; +} + +.mainmenu li a { + color: #3b5c6b; + display: block; + padding: 10px 15px; +} + +.mainmenu li.active a { + font-weight: bold; +} + +.mainmenu li:hover { + background-color: #eee; +} + + +/* Submenu + * Displayed in the middle div. Contains page-specific form controls. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.submenu { + padding: 10px 0px; + border-bottom: 1px solid #ddd; +} + +.submenu input, .submenu select { + margin: 0 0 0 5px; +} + +.submenu a { + color: #3b5c6b; + padding: 5px 15px; + text-decoration: none; + border: 1px solid transparent; + border-radius: 5px; +} + +.submenu a:hover { + border: 1px solid #ccc; +} + + +/* Section + * Cap/header to distinguish a section. Displayed within a content div. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.section { + font-weight: bold; + background-color: #f5f5f5; + border: 1px solid #ccc; + padding: 9px 10px 10px; + margin: 10px 0; +} + + +/* Section Menu + * Div of buttons/links, displayed with a section div within a content div. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.sectionmenu { + border: 1px solid #ccc; + border-top: 0; + margin-top: -10px; + margin-bottom: 10px; + padding: 5px; + text-align: center; +} +.sectionmenu a { + display: inline-block; + margin-top: 5px; + margin-right: 1em; +} + + +/* File browser + * Repository tree navigation. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul.browser { + list-style: none; +} + +ul.browser li.dir { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAVQBVAFV4xrLkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMLExABnLjGZQAAAEFJREFUOMtjYKAQMIaGhv4npGj16tWMuORYGBgYGOZW+eDUnNy2Ba/hLMQ4E58rCRpAyHVMlAbiqAGjBhCdmWgKAHp4Dh0ZusP3AAAAAElFTkSuQmCC); + background-repeat: no-repeat; + background-position: 0px center; + padding-left: 22px; + padding-top: 2px; +} + +ul.browser li.file { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAVQBVAFV4xrLkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMLExMaPfBcSgAAAFNJREFUOMvtkzEOwDAIA02VL5pHwiOTJZFQmkqFOTex+CwPCCYkAaDjB+4u65ZdYGafQVV9SR4kWQUke0mwS1o2HGcAQKs0R1lpQuQKruD4jVnBAG/cGRqf0U66AAAAAElFTkSuQmCC); + background-repeat: no-repeat; + background-position: 0px center; + padding-left: 22px; + padding-top: 2px; +} + +div.filetreeline { + display: table; + width: 100%; + white-space: nowrap; +} + +/* tree-view top-level list */ +.filetree > ul { + display: inline-block; +} + +/* tree-view lists */ +.filetree ul { + margin: 0; + padding: 0; + list-style: none; +} +/* tree-view collapsed list */ +.filetree ul.collapsed { + display: none; +} +/* tree-view lists below the root */ +.filetree ul ul { + position: relative; + margin: 0 0 0 21px; +} +/* tree-view lists items */ +.filetree li { + position: relative; + margin: 0; + padding: 0; +} +/* tree-view node lines */ +.filetree li li:before { + content: ''; + position: absolute; + top: -.8em; + left: -14px; + width: 16px; + height: 1.5em; + border-left: 1px solid #ccc; + border-bottom: 1px solid #ccc; +} +/* tree-view directory lines */ +.filetree li > ul:before { + content: ''; + position: absolute; + top: -1.5em; + bottom: 0; + left: -35px; + border-left: 1px solid #ccc; +} +/* hide lines for last-child directories */ +.filetree li.last > ul:before { + display: none; +} + +.filetree a { + position: relative; + z-index: 1; + display: table-cell; + min-height: 16px; + padding-left: 22px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAVQBVAFV4xrLkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMLExMaPfBcSgAAAFNJREFUOMvtkzEOwDAIA02VL5pHwiOTJZFQmkqFOTex+CwPCCYkAaDjB+4u65ZdYGafQVV9SR4kWQUke0mwS1o2HGcAQKs0R1lpQuQKruD4jVnBAG/cGRqf0U66AAAAAElFTkSuQmCC); + background-position: center left; + background-repeat: no-repeat; +} + +.filetree .dir > div.filetreeline > a { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAVQBVAFV4xrLkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMLExABnLjGZQAAAEFJREFUOMtjYKAQMIaGhv4npGj16tWMuORYGBgYGOZW+eDUnNy2Ba/hLMQ4E58rCRpAyHVMlAbiqAGjBhCdmWgKAHp4Dh0ZusP3AAAAAElFTkSuQmCC); +} + + +/* Label-Value table + * Displayed on the Check-in & Admin pages. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +table.label-value th { + vertical-align: middle; +} + + +/* Branches table +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.brlist table td { + padding: 5px; +} + + +/* Timeline + * Displays chronologically-ordered check-ins with a branch graph. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +tr.timelineCurrent { + border-left: 2px solid orange; + background-color: #ffc; +} + +tr.timelineSelected { + border-left: 2px solid orange; + background-color: #ffffe8; +} + +tr.timelineCurrent td.timelineTableCell { +} + +tr.timelineSpacer { +} + +div.timelineDate { + font-weight: bold; + white-space: nowrap; +} + +a.timelineHistLink { + text-transform: lowercase; +} + +span.timelineComment { + padding: 0px 5px; +} + + +/* Login/Loguot +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +table.login_out { +} + +table.login_out .login_out_label { + font-weight: 700; + text-align: right; +} + +table.login_out td { + border: 0; +} + + +/* Diff displays +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +pre.udiff, table.sbsdiffcols { + width: 100%; + overflow: auto; + border: 1px solid #ccc; + padding: 5px; + font-size: 1rem; +} + +pre.udiff:focus, table.sbsdiffcols:focus { + outline: none; +} + + +/* Ticket Reports +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +table.report { + width: 100%; + cursor: auto; + border-radius: 4px; + border: 1px solid #ccc; + margin: 0 0 1em 0; +} + +.report td, .report th { + border: 0; + font-size: .9em; + padding: 5px; +} + +.report th { + cursor: pointer; +} + +.report thead+tbody tr:hover { + background-color: #f5f9fc !important; +} + + +/* Ticket page +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +table.tktDsp { + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + width: 100%; + margin: 15px 0px 10px 0px; +} + +td.tktDspLabel, td.tktDescLabel { + width: 70px; + text-align: right; + overflow: hidden; + font-weight: 700; + padding: 10px; + background-color: #f8f8f8; +} + +td.tktDescLabel { + vertical-align: top; +} + +td.tktDspValue, td.tktDescValue { + text-align: left; + vertical-align: top; + border: 1px solid #ccc; + padding: 10px; +} + +td.tktDspValue pre, td.tktDescValue pre, +td.tktDspValue code, td.tktDescValue code { + white-space: pre-wrap; +} + +div.tktComments { + width: 100%; + margin: 30px 0px 10px 0px; +} + +div.tktComment { +} + +div.tktCommentHeader { + border: 1px solid #ccc; + background-color: #f8f8f8; + padding: 10px 10px; + margin-bottom: 10px; +} + +span.tktCommentLogin { + display: inline-block; + font-weight: 700; + color: #002060; +} + +div.tktCommentBody { + margin: 10px 40px 30px; +} + + +/* User setup table +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +td.usetupEditLabel { + font-weight: 700; +} + + +/* Utilities +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.full-width { + width: 100%; + box-sizing: border-box; +} + +.max-full-width { + max-width: 100%; + box-sizing: border-box; +} + +.pull-right { + float: right; +} + +.pull-left { + float: left; +} + +/* Clearing +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.container:after, +.mainmenu:after, +.row:after, +.u-cf { + content: ""; + display: table; + clear: both; +} ADDED skins/blitz/details.txt Index: skins/blitz/details.txt ================================================================== --- /dev/null +++ skins/blitz/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 0 +timeline-circle-nodes: 1 +timeline-color-graph-lines: 1 +white-foreground: 0 ADDED skins/blitz/dir.png Index: skins/blitz/dir.png ================================================================== --- /dev/null +++ skins/blitz/dir.png cannot compute difference between binary files ADDED skins/blitz/file.png Index: skins/blitz/file.png ================================================================== --- /dev/null +++ skins/blitz/file.png cannot compute difference between binary files ADDED skins/blitz/footer.txt Index: skins/blitz/footer.txt ================================================================== --- /dev/null +++ skins/blitz/footer.txt @@ -0,0 +1,12 @@ + </div> <!-- end div container --> + </div> <!-- end div middle max-full-width --> + <div class="footer"> + <div class="container"> + <div class="pull-right"> + <a href="http://fossil-scm.org">Fossil version $manifest_version $manifest_date</a> + </div> + This page was generated in about <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s + </div> + </div> + </body> +</html> ADDED skins/blitz/fossil_100.png Index: skins/blitz/fossil_100.png ================================================================== --- /dev/null +++ skins/blitz/fossil_100.png cannot compute difference between binary files ADDED skins/blitz/fossil_80_reversed_darkcyan.png Index: skins/blitz/fossil_80_reversed_darkcyan.png ================================================================== --- /dev/null +++ skins/blitz/fossil_80_reversed_darkcyan.png cannot compute difference between binary files ADDED skins/blitz/fossil_80_reversed_darkcyan_text.png Index: skins/blitz/fossil_80_reversed_darkcyan_text.png ================================================================== --- /dev/null +++ skins/blitz/fossil_80_reversed_darkcyan_text.png cannot compute difference between binary files ADDED skins/blitz/header.txt Index: skins/blitz/header.txt ================================================================== --- /dev/null +++ skins/blitz/header.txt @@ -0,0 +1,80 @@ +<html> + <head> + <base href="$baseurl/$current_page" /> + <title>$<project_name>: $<title> + + + + + +
+
+ + + + + + + +
+
+
+
ADDED skins/blitz/rss_20.png Index: skins/blitz/rss_20.png ================================================================== --- /dev/null +++ skins/blitz/rss_20.png cannot compute difference between binary files ADDED skins/blitz/ticket.txt Index: skins/blitz/ticket.txt ================================================================== --- /dev/null +++ skins/blitz/ticket.txt @@ -0,0 +1,115 @@ +

$</h4> +<table class="tktDsp"> +<tr><td class="tktDspLabel">Ticket UUID</td> +<th1> +if {[info exists tkt_uuid]} { + if {[hascap s]} { + html "<td class='tktDspValue' colspan='3'>$tkt_uuid " + html "($tkt_id)</td></tr>\n" + } else { + html "<td class='tktDspValue' colspan='3'>$tkt_uuid</td></tr>\n" + } +} else { + if {[hascap s]} { + html "<td class='tktDspValue' colspan='3'>Deleted " + html "(0)</td></tr>\n" + } else { + html "<td class='tktDspValue' colspan='3'>Deleted</td></tr>\n" + } +} +</th1> +<tr><td class="tktDspLabel">Status</td><td class="tktDspValue"> +$<status> +</td> +<td class="tktDspLabel">Type</td><td class="tktDspValue"> +$<type> +</td></tr> +<tr><td class="tktDspLabel">Severity</td><td class="tktDspValue"> +$<severity> +</td> +<td class="tktDspLabel">Priority</td><td class="tktDspValue"> +$<priority> +</td></tr> +<tr><td class="tktDspLabel">Subsystem</td><td class="tktDspValue"> +$<subsystem> +</td> +<td class="tktDspLabel">Resolution</td><td class="tktDspValue"> +$<resolution> +</td></tr> +<tr><td class="tktDspLabel">Last Modified</td><td class="tktDspValue"> +<th1> +if {[info exists tkt_datetime]} { + html $tkt_datetime +} +</th1> +</td> +<th1>enable_output [hascap e]</th1> + <td class="tktDspLabel">Contact</td><td class="tktDspValue"> + $<private_contact> + </td> +<th1>enable_output 1</th1> +</tr> +<tr><td class="tktDspLabel">Version Found In</td> +<td colspan="3" valign="top" class="tktDspValue"> +$<foundin> +</td></tr> + +<th1> +if {[info exists comment]} { + if {[string length $comment]>10} { + html { + <tr> + <td class="tktDescLabel">Description</td> + <td class="tktDescValue" colspan="3"> + } + if {[info exists plaintext]} { + set r [randhex] + wiki "<verbatim-$r links>\n$comment\n</verbatim-$r>" + } else { + wiki $comment + } + html "</td></tr>\n" + } +} +</th1> +</table> + +<div class="tktComments"> +<th1> +set seenRow 0 +set alwaysPlaintext [info exists plaintext] +query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin, + mimetype as xmimetype, icomment AS xcomment, + username AS xusername + FROM ticketchng + WHERE tkt_id=$tkt_id AND length(icomment)>0} { + if {$seenRow eq "0"} { + html "<h5>User Comments</h5>\n" + set seenRow 1 + } + html "<div class='tktComment'>\n" + html "<div class='tktCommentHeader'>\n" + html "<div class='pull-right'>$xdate</div>\n" + html "<span class='tktCommentLogin'>[htmlize $xlogin]</span>" + if {$xlogin ne $xusername && [string length $xusername]>0} { + html " (claiming to be <span class='tktCommentLogin'>[htmlize $xusername]</span>)" + } + html " commented</div>\n" + html "<div class='tktCommentBody'>\n" + if {$alwaysPlaintext || $xmimetype eq "text/plain"} { + set r [randhex] + if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"} + wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n" + } elseif {$xmimetype eq "text/x-fossil-wiki"} { + wiki "<p>\n[string trimright $xcomment]\n</p>\n" + } elseif {$xmimetype eq "text/html"} { + wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki></p>\n" + } else { + set r [randhex] + wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n" + } + html "</div><!-- end comment body -->\n" + html "</div><!-- end comment -->\n" +} +</th1> +</div> ADDED skins/blitz_no_logo/README.md Index: skins/blitz_no_logo/README.md ================================================================== --- /dev/null +++ skins/blitz_no_logo/README.md @@ -0,0 +1,13 @@ +## Blitz Theme (no logo) + +Contributed by James Moger (james.moger@gitblit.com) + +This theme is inspired by my own project, [Gitblit](http://gitblit.com), and offered to the Fossil project. + +This theme embeds & uses an unmodified copy of [Normalize 3.0.2](https://necolas.github.io/normalize.css/) which is distributed under an [MIT license](https://github.com/necolas/normalize.css/blob/master/LICENSE.md). + +This theme uses half of a heavily-modified version of [Skeleton](http://getskeleton.com) which is distributed under an [MIT license](https://github.com/dhg/Skeleton/blob/master/LICENSE.md). None of the responsive elements (media queries) are included at this time. + +The font used in the included Fossil logo image is [Trillium Web Light](http://www.google.com/fonts/specimen/Titillium+Web) @ 48px HTML color code #456a7a. + +The RSS feed icon is sourced from [Font-Awesome](https://fortawesome.github.io/Font-Awesome/icons) by Dave Gandy and is distributed under the [SIL OFL 1.1 ](http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL) license. ADDED skins/blitz_no_logo/css.txt Index: skins/blitz_no_logo/css.txt ================================================================== --- /dev/null +++ skins/blitz_no_logo/css.txt @@ -0,0 +1,1237 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} + + +/* + * Blitz + * + * Skin inspired by Gitblit with heavily-modified excerpts from Skeleton 2.0.4. + * Blitz is authored by james.moger@gitblit.com. + * + * Skeleton is authored by Dave Gamache and is distributed under the MIT license. + * http://getskeleton.com + * +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ + +html { + /* 62.5% so that the REM values are base 10px. */ + /* 1.5rem = 15px */ + font-size: 62.5%; +} + +/* Typography +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +h1, h2, h3, h4, h5, h6 { + margin: 0; + margin-bottom: 1rem; + font-weight: 700; +} + +h1 { font-size: 3.0rem; line-height: 1.2; } +h2 { font-size: 2.6rem; line-height: 1.25; } +h3 { font-size: 2.4rem; line-height: 1.3; } +h4 { font-size: 2.0rem; line-height: 1.35; } +h5 { font-size: 1.6rem; line-height: 1.5; } +h6 { font-size: 1.4rem; line-height: 1.6; } + +h1 small, h2 small, h3 small, h4 small, h5 small, h6 small { + font-size: 0.75em; + font-weight: 400; + color: #ccc; +} + +pre, code { + font-size: 1.2rem; +} + +body { + font-size: 1.4em; /* currently ems cause chrome bug misinterpreting rems on body element */ + line-height: 1.5; + font-weight: 400; + font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #333; + background-color: #f8f8f8; +} + +/* Spacing +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +button, +.button { + margin-bottom: 1rem; +} + +input, +textarea, +select, +fieldset, +pre, +blockquote, +dl, +figure, +table, +p, +ul, +ol { + margin-bottom: 1rem; +} + +p { + margin-top: 0; +} + +hr { + margin-top: 3rem; + margin-bottom: 3.5rem; + border-width: 0; + border-top: 1px solid #ccc; +} + + +/* Buttons +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.button, +button, +input[type="button"], +input[type="reset"], +input[type="submit"] { + display: inline-block; + height: 3.3rem; + padding: 0 2.2rem; + color: #555 !important; + text-align: center; + font-size: 1.1rem; + font-weight: 700; + line-height: 3.3rem; + letter-spacing: .08rem; + text-transform: uppercase; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border-radius: 4px; + border: 1px solid #ccc; + cursor: pointer; + box-sizing: border-box; +} + +.button:hover, +button:hover, +input[type="button"]:hover, +input[type="reset"]:hover, +.button:focus, +button:focus, +input[type="button"]:focus, +input[type="reset"]:focus { + color: #444 !important; + background-color: #eee; + border-color: #aaa; + outline: 0; +} + +input[type="submit"] { + color: white !important; + background-color: #446979; + border-color: #446979; +} + +input[type="submit"]:hover, +input[type="submit"]:focus { + color: white !important; + background-color: #648898; + border-color: #648898; +} + + +/* Forms +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea, +select { + height: 3.3rem; + padding: 6px 10px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: none; + box-sizing: border-box; +} + +/* Removes awkward default styles on some inputs for iOS */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +textarea { + height: inherit; + min-height: 65px; + padding-top: 6px; + padding-bottom: 6px; +} + +input[type="email"]:focus, +input[type="number"]:focus, +input[type="search"]:focus, +input[type="text"]:focus, +input[type="tel"]:focus, +input[type="url"]:focus, +input[type="password"]:focus, +textarea:focus, +select:focus { + border: 1px solid #aaa; + outline: 0; +} + +label, +legend { + display: block; + margin-bottom: .5rem; + font-weight: 700; +} + +fieldset { + padding: 0; + border-width: 0; +} + +input[type="checkbox"], +input[type="radio"] { + display: inline; +} + + +/* Links +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +a { + color: #446979; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + +/* Lists +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul { + list-style: square; +} + +ol { + list-style: decimal; +} + +ol, ul { + padding-left: 3rem; + margin-top: 0; +} + +li { + margin-bottom: 0.5rem; +} + + +/* Nested Lists +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul ul, +ul ol, +ol ol, +ol ul { + margin: 1rem 0 1rem 2rem; +} + + +/* Code +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +code, kbd { + padding: .2rem .5rem; + margin: 0 .2rem; + white-space: nowrap; + background: #f8f8f8; + border: 1px solid #ccc; + border-radius: 4px; +} + +pre > code { + display: block; + padding: 1rem 1.5rem; + white-space: pre; +} + +pre.verbatim { + background-color: inherit; + white-space: pre-wrap; +} + + +/* Blockquote +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +blockquote { + padding: 0px 20px; + margin: 0 0 20px; + border-left: 4px solid #ccc; +} + + +/* Tables +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +th, +td { + padding: 6px 5px; + text-align: left; + border-bottom: 1px solid #ddd; } +th:first-child, +td:first-child { + padding-left: 0; } +th:last-child, +td:last-child { + padding-right: 0; } + + +/* + * Blitz Page Layout Design + * + * html > body > header > container > mainmenu + * middle > container > submenu & content + * footer > container > generation stats, fossil logo, version + * + ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ + + +/* Container + * Represents the usable layout space for header, middle, and footer. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.container { + position: relative; + width: 100%; + max-width: 900px; + margin: 0 auto; + box-sizing: border-box; +} + + +/* Header + * Div displayed at the top of every page. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.header { + color: #666; + font-weight: 400; + padding-top: 10px; + border-width: 0px; + border-top: 4px solid #446979; + border-bottom: 1px solid #ccc; +} + +.header .logo { + display: inline-block; +} + +.header .login { + padding-top: 2px; + text-align: right; +} + +.header .login .button { + margin: 0; +} + +.header h1 { + margin: 0px; + color: #666; + display: inline-block; +} + +.header .logo h1 { + display: inline-block; +} + +.header .title h1 { + padding-bottom: 10px; +} + +.header h1 small, .header h2 small, .header .login { + color: #888; +} + +.header a.rss { + display: inline-block; + padding: 10px 15px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMNDhwn05VjawAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAGlSURBVDjLrdPfb8xREAXwT7tIl+paVNaPJghCKC8kXv0XXvyNXsRfwYPQJqVKiqykWFVZXd12vcxNJtduUtJJvrm7984998ycMxxwNGI9jPs4j7nY+/U/gIdiPYO71dk21rCE7r8ybOHGmMfmcRNnsbEf1gXwNzqYSXs5WljEMXzAaBLg1Ji9Js7hOi6OeeAznqC/X8AcMyHWYpX7E4/Rm1QyHMdefCWGeI/VcMDR2D8S7Fci5y/AeTzCPVyLi1sYJAut4BTaiX0n9kc14MmkcjPY3I5LXezGtxqKtyJ3Lir6VAM2AmCq6m8Hl6PsQTB5hyvxmMhZxk4G3MZLfAwLtdNZM9rwOs528TVVNB3ga7UoQ2wGmyWciFaU0VwIJiP8iL6Xfp7GK+w0JthliDep8UKonTSGvbBTaU8f3QzYxgPcCsBvWK9E6OBFCNGPVjTTqC430p+H6fLVGLGtmIw7SbwevqT+XkgVPJ9Otpmtyl6I9XswLXEp/d6oPN0ugJu14xMLob4kgPRYjtkCOMDTUG+AZ3ibEtfDLorfEmAB3UuTdXDxBzUUZV+B82aLAAAAAElFTkSuQmCC); + background-position: center center; + background-repeat: no-repeat; +} + + +/* Middle + * Center div displayed between header and footer. Contains per-page content. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.middle { + background-color: white; + padding-bottom: 20px; + max-width: 100%; + box-sizing: border-box; +} + + +/* Content + * Displayed below submenu within the middle div. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.content { + padding-top: 15px; +} + +.content a { + color: #002060; +} + + +/* Footer + * Displayed after the middle div and forms the page bottom. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.footer { + padding: 10px 0 60px; + border-top: 1px solid #ccc; + background-color: #f8f8f8; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABGCAQAAADxl9ugAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQffAwkQBRPw+yfrAAAAPHRFWHRDb21tZW50ACBJbWFnZSBnZW5lcmF0ZWQgYnkgRVNQIEdob3N0c2NyaXB0IChkZXZpY2U9cG5tcmF3KQqV01S1AAANg0lEQVRo3s2aa3RVRZbH/3tXnftIbhJCeJMXyEsNCsgrSBzAYXyt0YVj2zSOonbLqNNO+25FB2kdoBVdrp6l09P2NNpOL/ExSrtUdGRU5JEQgggCggIhQIBAIIGQ5N57TlXt+ZAICY+AGCT76zlV91dVe+/a+38uDQ7dYmrxw41QocJuni28vWTedEa7Gd9iatplos7Y2mfDRED6A2G0I2At6AdPIiA84HgbLhx1o9QX9Zoh7QjYHpOkYJYrfNyOKH0O/f2FJpfkSRmMCNqDk9rDXwTAuxK7h3uFf7vXxC6QVO8w19taNOT4Y0wu1sCdW0AA6IZ73bCUaI/GHuqnsJqcEqVrzSo+wNtLdgAPsz6jeVURtQ9gA8bQT8xztW7X2u3I4G6UdA0cohxcSAN7nt939/zDQzl0LnfwaLpxqFT/i7w7XLYXhoUQ+YFW3pJlC0/nt6jZZb4zjXY2AaGXvQOz/gMY09NM4C6ixRL7OU3PTm4ORdiIjyLsJvq1Z28Hj5rBM82xMTqPuhHznmU72vq12W5M3+LykQWUW9pirzXOmmk8wjE87kb8HPmSKlY+wY62R9jhI69xf/AmYiEfiXs+e4CCAZiDUc9wP6cRIJn8tq23PQClbwap9Ivk7usiGj/CDs5xl3Qe/EiQCEWsgXLlq8sfamM73osMna43ef/pfm731Hsxv+Z00ozFzaJpF+gMrsNB2BwL3cGpSsNRWHbmvTQNiTbev9q4xXszMLzqpdgDNjxv/YjmvHlSQIc5brl8nrqT5pllMpHM9wRc4gU3oC8JyDLt8P78ZLyM2lrmAeTg5YrKdf1S1N9QTs6At1YM49DJo1gwWy7uE5kKBQHbler9K8R+L8API2qmFoDY7ejxpxGJxtMctyZcfYvJDYX4i5LXp/MJAS2edkM6edNUDuLNOdMDmz913bIw8RidKO/1w+3NQfePbHQjdbd/NKOjbq7UqQOmYtXb/+BdbGc4AJjPX53CXe6UGweYWxUR8N8rNkzn4wAFc9yYPvGbI+mttoycpo14fWbD0hZv+njOAcBl6Yl8jiEMbSKhqGPypZ5N0B2kDxrjOjF0QuI2oRqj2z6rAebx5pNiRrEo0jhV9wFkP//xpkPHAU6UGdnBLyR8jMOQs6xwcMAznVys+Uqa5YCCnilFkuVSkeKlGKfDED8JaI+VifMWR9LPC/u+UkoZA6edOezVu83pny4yD6qQnCxhDzk/MhUED+8PWHJMkAgWROvvQoxOcEdayynVY9/5tIgAwsfIvaD3XaFx6M6ddMx3WlurCGICiAIhMLxB1VM+wQRMDOeYjHCUMum85OW909/acOlJgqaI3qrulU9dYGlgzVI1tsVrETzlOt3MeUGCudVoCgJmAhFU78F+6Usu0V39iylSHCRZMROM0do5pUDWQamEPZTy5oqyXVt77bG94HGEyTkCE8BsjLOcl11Uvf7V+qvIP86jgWH8zhc9LpKYU7qRhH5NTRknwFw3+jpMQCIItG69PCLfZ1YKAMlS/7PQDCc2qbRzSjET4slINBAvhsPxjarKKy/9FhjZG8Ol4tINi/uFzkcfl8sEnwExATGRY73Ze2VpAHxHoPFki7r2iljN3a6LqqOxnZ+vXUBNwTF6uL0e0Nr3tSYWpqNhQtaIaA0A1oZftuf5Y1xmCMRO2IM1VXqf3aYr5IDXODTx7+43PAjPFcit1MANeHfFxuFpSFW9TbbORpaNEqt6/5tIoxuBOO049NHXNTN5lFwlE3o09JUMlWKdSmJ/UC8TvRwqjNzoVwNIw3upcluyq4hSAq3Idyt5HOyRIDGuGdA42un+6wv/Cq7sFA1F/B4N/5MEgL/yOsSPlFSMOzD5JhkMEQ8l8u41Job7hVoExsiRNMU1Biac4oqD92Np8Xt0BgUiEOeIHLyobbQb9U/8fSAItsFdJv1wECASB5L92xb2yefcE3Q+jgsof//m3+PO2i7YhgV0HisAK48UnE03EaT67axMyjMNXpFkf/za8r3VPP2I46Ri+aYDW0z3SMT6NJb62wPiJEFhEoFCYIhNoyS4VF1CBKArPu6MKWysBZhFVFrD7yri2VkuR4QZAIkTMAOAiDIY9KvFQ3gVPqXVFD9JSbSS3vP7b/QzKY8SlO6G5e2fv/dozvDxciKvCqOYrIURT/XU2+0SKrdVqtpVojz4Sq1Vi1duV0UE+PSEy33AhRRZAzDDc4vXrB1xKU0gRUR0LCCRi/be8XZ12/0M41Ka5K/5JjA80PlQKMjfeVVN9ZG0NZbmH8xf5Y8nJxIKmYC7SmLlexeVp2wKb+y++f6KZ3c/03g+qSISPONGDqHRZOk7QGde6XsrjTP+kXTTCpCVGErZteZUDRdhD3Ls65tz64J85ZGyQ0tXDAzckadFlOtv+ZQLoYMgHHaGcrJ7fb56inR3WVJJRVTdVM3kQSh5r1hjlbIGIKVX6ammE1vnmImh4Y4BZHGcvmvx5XQ6BcRIfnPHxV82ZnFUYsh+bWXLzMuwquGwCnvdBUTkqHcPmb+l5cJZcKervlwc8N04sbYwSJC1tsnneemJqh32LksZflr1SUh+pT6q+/ov/krztitDq1s4hP1p7mbfuFfkEKJgSejLh/Wa7Y5GJsdQmEYDoVocDCvnLCvnwPDMzOK/uiVoPS8IxoQi409D2xAU4Hd2xE2DZ3gjvcpVZY+0iqgAbFh7F9grp84xf5D6QFOE/rYo1vto4z6ZVg/COHKAiGJrACaljNHaiaqTp0NeXlr37Ydz/ahSLY9YzLIPMzlxSrxMzM/ochfnO8UHvJJpgTnm+UE5GKMeOu3LrLJPd32+Z1FamfczKli36jzTtHplvPg46o2WgNwEaOqoTk8Jxsu4+vGUruDkCCCJkg171g2ntqMkjrmuYUBwG7LglIq/U7Zr1HEjJrtiay9imIzsqt37gb4/RSZlSsP88iZf5T3kX2xOuBEck2yp13F32NbDuZbHqSjwFrzJbeP5eN6NHOJuoQyXRIZ7Y+3Gh08wohjpW/VOkFMyqagHILnOSAOuflh1bqKggew524Zs1ex+R6cmz1W7Z6clvzzF8T7rLsvnSfBgJVO9cN+ax4/IRwazZLZrqmOSWJjAR0hCdFc/F2AtToTos34POQHAkVw2Sp2m7EKwOEyLVj59w6EtpyjdFYSCCYhaC0n9fcmmr5pb8QBrQl0wqn/hNc82J8Rfc8lWVwVGwLeNDVPTdW30+KZN0Sab5JTBqOBBowIHzeZVJcBjXHdKXfQp93+DgwuIgy9QtqJ8evPhOtRRzZi30iKfBZPxQVNhofDP6sUXh05zA5MH+WKdpAgAJ/lAChqhyQmd5HgFChFoMvar0AaKq8qSGmA6o03lVFCEqx1Q0Ce0075hkgvW93EPNeMZ3Cl93eRl5Q8GVjoBs1tUN1l/rvkn1VOuNXUIAyATv6zz3bXPEo3t7N/L6bBirfV0MimktOclgnDUJHiTWxo9oJOZQT/TDUk0nIYfFODFaMMN4WFqbnEl8BzXtlBX++ATr2KW3eC9kbg/9d2l6/qmdxrq9bdRkBwMtrgd+lpkpxwyWSwAkvRKSfl0phf4l+6acGVeak6QQbCJYH/Gwci+zPi/+oMFEJpLdTDfQ8IpTTl8r/lkQuniX3oR98mjX65sJQZ0wYNu1NU0wZ8RjSUf0Qo22aCU1oAoCazTFe5bdZ1pVIqcv2B12XSm6awxCFNaichCr1I1auDOQFvKxzQHFD4mexGXNfds/PoYB+iMhxzwIC8vcJe7HKVIrBEoLRQkuVL+Ii70lDlMUOR/uHrJdNaAwXo82orksR8gfVUAGFkgy5JfhKdEKncelwhq8SinwMeidX+303VHuvOgCGSRRF2o6vb6FyKmQTGRNTraQt1qJ6EaAPA4V22MYH20wXx+aCyfKFvFAcymiYfCh/YhSQZACFHpBB/l6On2l0s/AgTBWdIHHbpIulzfWDbvAW4r3gPUIwWZ0lW6SoaE0AgDYG6Six0R4OjgWRMwGUAVHuUz+SS2iNQ+XSMAvKqzrLCemdtUo3ifWhpoTi7b/RSfVRH9zBc2y43umXVgtImf3R08c7zCIeYpVoPOtoh+ZjYEhd3kWtV4IHSldEjAV7U/ATHxig89Tx0QUFCtvUJisxnNknuHArSY4+xtNuk4ZQWai4wOBThJRqZJvhYXtptnNTVAHQnQYbT4k7QI6/Vd6w6f/U9h39cUgEg+BCG3rk/AHQ9wlhsz1IUCI3tRPrDVtdlhzO9rxPNke+nuXR0TUIdAMOrDlkJshwEM4QId98C8vKTmUUJHAwww03VNT8ny9hd/cJ9qCaU7QnrpjXvcsKuT6W7VdcvHc+sq95wDCuZILxp9t+TZJWVL/56PPdJzDhjDhG75UyUrsXXtBw9z/PjAObd3729leA++mTOldO07dyl9ghbmnALmYpjybndhO3/VV/dx9IQdlj53YQEAo2din3p1cv1VfDIF8EfvSdJQSZvU3ljGJJOvNuH94kOPs22jwWq3P5edTryG8G/OdtsxOPiZd6O1ppTeK0n43Hb/9yPuYBhPuFFXumEq3231S+ibL/e+yhtP2Zz+iD74hCu83vblr+W1yJ5LgguxmzedRu/8//AUSaqMTR1xAAAAAElFTkSuQmCC); + background-repeat: no-repeat; + background-position: center top 10px; +} + +.footer a { + color: #3b5c6b; +} + + +/* Main Menu + * Displayed in header, contains repository links. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.mainmenu { + clear:both; +} + +.mainmenu ul { + list-style: none outside; + display: block; + position: relative; + border-top: 1px solid #ccc; + padding: 0; +} + +.mainmenu li { + outline: 0; + display: block; + float: left; + margin: 0; +} + +.mainmenu li.active { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEQAACxEBf2RfkQAAAAd0SU1FB90FDxEXAZ2XRzAAAABJSURBVCjPY2CgBzhz5sx/QmoYiTXAxMSEkWRDsLkAl0GMpHoBm0EoAlu3bmUQFxcnGAboBjEhc4gxAJtLGUmJBVwuYiTXAGSDAIx5IBObnuVxAAAAAElFTkSuQmCC); + background-repeat: no-repeat; + background-position: center bottom; +} + +.mainmenu li a { + color: #3b5c6b; + display: block; + padding: 10px 15px; +} + +.mainmenu li.active a { + font-weight: bold; +} + +.mainmenu li:hover { + background-color: #eee; +} + + +/* Submenu + * Displayed in the middle div. Contains page-specific form controls. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.submenu { + padding: 10px 0px; + border-bottom: 1px solid #ddd; +} + +.submenu input, .submenu select { + margin: 0 0 0 5px; +} + +.submenu a { + color: #3b5c6b; + padding: 5px 15px; + text-decoration: none; + border: 1px solid transparent; + border-radius: 5px; +} + +.submenu a:hover { + border: 1px solid #ccc; +} + + +/* Section + * Cap/header to distinguish a section. Displayed within a content div. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.section { + font-weight: bold; + background-color: #f5f5f5; + border: 1px solid #ccc; + padding: 9px 10px 10px; + margin: 10px 0; +} + + +/* Section Menu + * Div of buttons/links, displayed with a section div within a content div. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.sectionmenu { + border: 1px solid #ccc; + border-top: 0; + margin-top: -10px; + margin-bottom: 10px; + padding: 5px; + text-align: center; +} +.sectionmenu a { + display: inline-block; + margin-top: 5px; + margin-right: 1em; +} + + +/* File browser + * Repository tree navigation. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul.browser { + list-style: none; +} + +ul.browser li.dir { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAVQBVAFV4xrLkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMLExABnLjGZQAAAEFJREFUOMtjYKAQMIaGhv4npGj16tWMuORYGBgYGOZW+eDUnNy2Ba/hLMQ4E58rCRpAyHVMlAbiqAGjBhCdmWgKAHp4Dh0ZusP3AAAAAElFTkSuQmCC); + background-repeat: no-repeat; + background-position: 0px center; + padding-left: 22px; + padding-top: 2px; +} + +ul.browser li.file { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAVQBVAFV4xrLkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMLExMaPfBcSgAAAFNJREFUOMvtkzEOwDAIA02VL5pHwiOTJZFQmkqFOTex+CwPCCYkAaDjB+4u65ZdYGafQVV9SR4kWQUke0mwS1o2HGcAQKs0R1lpQuQKruD4jVnBAG/cGRqf0U66AAAAAElFTkSuQmCC); + background-repeat: no-repeat; + background-position: 0px center; + padding-left: 22px; + padding-top: 2px; +} + +div.filetreeline { + display: table; + width: 100%; + white-space: nowrap; +} + +/* tree-view top-level list */ +.filetree > ul { + display: inline-block; +} + +/* tree-view lists */ +.filetree ul { + margin: 0; + padding: 0; + list-style: none; +} +/* tree-view collapsed list */ +.filetree ul.collapsed { + display: none; +} +/* tree-view lists below the root */ +.filetree ul ul { + position: relative; + margin: 0 0 0 21px; +} +/* tree-view lists items */ +.filetree li { + position: relative; + margin: 0; + padding: 0; +} +/* tree-view node lines */ +.filetree li li:before { + content: ''; + position: absolute; + top: -.8em; + left: -14px; + width: 16px; + height: 1.5em; + border-left: 1px solid #ccc; + border-bottom: 1px solid #ccc; +} +/* tree-view directory lines */ +.filetree li > ul:before { + content: ''; + position: absolute; + top: -1.5em; + bottom: 0; + left: -35px; + border-left: 1px solid #ccc; +} +/* hide lines for last-child directories */ +.filetree li.last > ul:before { + display: none; +} + +.filetree a { + position: relative; + z-index: 1; + display: table-cell; + min-height: 16px; + padding-left: 22px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAVQBVAFV4xrLkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMLExMaPfBcSgAAAFNJREFUOMvtkzEOwDAIA02VL5pHwiOTJZFQmkqFOTex+CwPCCYkAaDjB+4u65ZdYGafQVV9SR4kWQUke0mwS1o2HGcAQKs0R1lpQuQKruD4jVnBAG/cGRqf0U66AAAAAElFTkSuQmCC); + background-position: center left; + background-repeat: no-repeat; +} + +.filetree .dir > div.filetreeline > a { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAVQBVAFV4xrLkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMLExABnLjGZQAAAEFJREFUOMtjYKAQMIaGhv4npGj16tWMuORYGBgYGOZW+eDUnNy2Ba/hLMQ4E58rCRpAyHVMlAbiqAGjBhCdmWgKAHp4Dh0ZusP3AAAAAElFTkSuQmCC); +} + + +/* Label-Value table + * Displayed on the Check-in & Admin pages. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +table.label-value th { + vertical-align: middle; +} + + +/* Branches table +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.brlist table td { + padding: 5px; +} + + +/* Timeline + * Displays chronologically-ordered check-ins with a branch graph. +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +tr.timelineCurrent { + border-left: 2px solid orange; + background-color: #ffc; +} + +tr.timelineSelected { + border-left: 2px solid orange; + background-color: #ffffe8; +} + +tr.timelineCurrent td.timelineTableCell { +} + +tr.timelineSpacer { +} + +div.timelineDate { + font-weight: bold; + white-space: nowrap; +} + +a.timelineHistLink { + text-transform: lowercase; +} + +span.timelineComment { + padding: 0px 5px; +} + + +/* Login/Loguot +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +table.login_out { +} + +table.login_out .login_out_label { + font-weight: 700; + text-align: right; +} + +table.login_out td { + border: 0; +} + + +/* Diff displays +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +pre.udiff, table.sbsdiffcols { + width: 100%; + overflow: auto; + border: 1px solid #ccc; + padding: 0px 5px; + font-size: 1rem; +} + +pre.udiff:focus, table.sbsdiffcols:focus { + outline: none; +} + + +/* Ticket Reports +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +table.report { + width: 100%; + cursor: auto; + border-radius: 4px; + border: 1px solid #ccc; + margin: 0 0 1em 0; +} + +.report td, .report th { + border: 0; + font-size: .9em; + padding: 5px; +} + +.report th { + cursor: pointer; +} + +.report thead+tbody tr:hover { + background-color: #f5f9fc !important; +} + + +/* Ticket page +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +table.tktDsp { + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + width: 100%; + margin: 15px 0px 10px 0px; +} + +td.tktDspLabel, td.tktDescLabel { + width: 70px; + text-align: right; + overflow: hidden; + font-weight: 700; + padding: 10px; + background-color: #f8f8f8; +} + +td.tktDescLabel { + vertical-align: top; +} + +td.tktDspValue, td.tktDescValue { + text-align: left; + vertical-align: top; + border: 1px solid #ccc; + padding: 10px; +} + +td.tktDspValue pre, td.tktDescValue pre, +td.tktDspValue code, td.tktDescValue code { + white-space: pre-wrap; +} + +div.tktComments { + width: 100%; + margin: 30px 0px 10px 0px; +} + +div.tktComment { +} + +div.tktCommentHeader { + border: 1px solid #ccc; + background-color: #f8f8f8; + padding: 10px 10px; + margin-bottom: 10px; +} + +span.tktCommentLogin { + display: inline-block; + font-weight: 700; + color: #002060; +} + +div.tktCommentBody { + margin: 10px 40px 30px; +} + + +/* User setup table +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +td.usetupEditLabel { + font-weight: 700; +} + + +/* Utilities +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.full-width { + width: 100%; + box-sizing: border-box; +} + +.max-full-width { + max-width: 100%; + box-sizing: border-box; +} + +.pull-right { + float: right; +} + +.pull-left { + float: left; +} + +/* Clearing +––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– */ +.container:after, +.mainmenu:after, +.row:after, +.u-cf { + content: ""; + display: table; + clear: both; +} ADDED skins/blitz_no_logo/details.txt Index: skins/blitz_no_logo/details.txt ================================================================== --- /dev/null +++ skins/blitz_no_logo/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 0 +timeline-circle-nodes: 1 +timeline-color-graph-lines: 1 +white-foreground: 0 ADDED skins/blitz_no_logo/footer.txt Index: skins/blitz_no_logo/footer.txt ================================================================== --- /dev/null +++ skins/blitz_no_logo/footer.txt @@ -0,0 +1,12 @@ + </div> <!-- end div container --> + </div> <!-- end div middle max-full-width --> + <div class="footer"> + <div class="container"> + <div class="pull-right"> + <a href="http://fossil-scm.org">Fossil version $manifest_version $manifest_date</a> + </div> + This page was generated in about <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s + </div> + </div> + </body> +</html> ADDED skins/blitz_no_logo/header.txt Index: skins/blitz_no_logo/header.txt ================================================================== --- /dev/null +++ skins/blitz_no_logo/header.txt @@ -0,0 +1,77 @@ +<html> + <head> + <base href="$baseurl/$current_page" /> + <title>$<project_name>: $<title> + + + + + +
+
+ + +
+

$ + + if {[anycap jor]} { + html "" + } + +  $</small></h1> + </div> + + <!-- Main Menu --> + <div class="mainmenu"> + <ul> + <th1> +proc menulink {url name} { + upvar current_page current + upvar home home + if {[string range $url 0 [string length $current]] eq "/$current"} { + html "<li class='active'>" + } else { + html "<li>" + } + html "<a href='$home$url'>$name</a></li>\n" +} +menulink $index_page Home +if {[anycap jor]} { + menulink /timeline Timeline +} +if {[hascap oh]} { + menulink /dir?ci=tip Files +} +if {[hascap o]} { + menulink /brlist Branches + menulink /taglist Tags +} +if {[hascap r]} { + menulink /ticket Tickets +} +if {[hascap j]} { + menulink /wiki Wiki +} +if {[hascap o]} { + menulink /help Help + } +if {[hascap s]} { + menulink /setup Admin +} elseif {[hascap a]} { + menulink /setup_ulist Users +} + </th1> + </ul> + </div> <!-- end div mainmenu --> + </div> <!-- end div container --> + </div> <!-- end div header --> + <div class="middle max-full-width"> + <div class="container"> ADDED skins/blitz_no_logo/ticket.txt Index: skins/blitz_no_logo/ticket.txt ================================================================== --- /dev/null +++ skins/blitz_no_logo/ticket.txt @@ -0,0 +1,115 @@ +<h4>$<title></h4> +<table class="tktDsp"> +<tr><td class="tktDspLabel">Ticket UUID</td> +<th1> +if {[info exists tkt_uuid]} { + if {[hascap s]} { + html "<td class='tktDspValue' colspan='3'>$tkt_uuid " + html "($tkt_id)</td></tr>\n" + } else { + html "<td class='tktDspValue' colspan='3'>$tkt_uuid</td></tr>\n" + } +} else { + if {[hascap s]} { + html "<td class='tktDspValue' colspan='3'>Deleted " + html "(0)</td></tr>\n" + } else { + html "<td class='tktDspValue' colspan='3'>Deleted</td></tr>\n" + } +} +</th1> +<tr><td class="tktDspLabel">Status</td><td class="tktDspValue"> +$<status> +</td> +<td class="tktDspLabel">Type</td><td class="tktDspValue"> +$<type> +</td></tr> +<tr><td class="tktDspLabel">Severity</td><td class="tktDspValue"> +$<severity> +</td> +<td class="tktDspLabel">Priority</td><td class="tktDspValue"> +$<priority> +</td></tr> +<tr><td class="tktDspLabel">Subsystem</td><td class="tktDspValue"> +$<subsystem> +</td> +<td class="tktDspLabel">Resolution</td><td class="tktDspValue"> +$<resolution> +</td></tr> +<tr><td class="tktDspLabel">Last Modified</td><td class="tktDspValue"> +<th1> +if {[info exists tkt_datetime]} { + html $tkt_datetime +} +</th1> +</td> +<th1>enable_output [hascap e]</th1> + <td class="tktDspLabel">Contact</td><td class="tktDspValue"> + $<private_contact> + </td> +<th1>enable_output 1</th1> +</tr> +<tr><td class="tktDspLabel">Version Found In</td> +<td colspan="3" valign="top" class="tktDspValue"> +$<foundin> +</td></tr> + +<th1> +if {[info exists comment]} { + if {[string length $comment]>10} { + html { + <tr> + <td class="tktDescLabel">Description</td> + <td class="tktDescValue" colspan="3"> + } + if {[info exists plaintext]} { + set r [randhex] + wiki "<verbatim-$r links>\n$comment\n</verbatim-$r>" + } else { + wiki $comment + } + html "</td></tr>\n" + } +} +</th1> +</table> + +<div class="tktComments"> +<th1> +set seenRow 0 +set alwaysPlaintext [info exists plaintext] +query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin, + mimetype as xmimetype, icomment AS xcomment, + username AS xusername + FROM ticketchng + WHERE tkt_id=$tkt_id AND length(icomment)>0} { + if {$seenRow eq "0"} { + html "<h5>User Comments</h5>\n" + set seenRow 1 + } + html "<div class='tktComment'>\n" + html "<div class='tktCommentHeader'>\n" + html "<div class='pull-right'>$xdate</div>\n" + html "<span class='tktCommentLogin'>[htmlize $xlogin]</span>" + if {$xlogin ne $xusername && [string length $xusername]>0} { + html " (claiming to be <span class='tktCommentLogin'>[htmlize $xusername]</span>)" + } + html " commented</div>\n" + html "<div class='tktCommentBody'>\n" + if {$alwaysPlaintext || $xmimetype eq "text/plain"} { + set r [randhex] + if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"} + wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n" + } elseif {$xmimetype eq "text/x-fossil-wiki"} { + wiki "<p>\n[string trimright $xcomment]\n</p>\n" + } elseif {$xmimetype eq "text/html"} { + wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki></p>\n" + } else { + set r [randhex] + wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n" + } + html "</div><!-- end comment body -->\n" + html "</div><!-- end comment -->\n" +} +</th1> +</div> ADDED skins/default/README.md Index: skins/default/README.md ================================================================== --- /dev/null +++ skins/default/README.md @@ -0,0 +1,5 @@ +This skin was contributed by Étienne Deparis. + +On 2015-03-14 this skin was promoted from an option to the default, which +involved moving it from its original home in the skins/etienne1 directory +into skins/default. ADDED skins/default/css.txt Index: skins/default/css.txt ================================================================== --- /dev/null +++ skins/default/css.txt @@ -0,0 +1,200 @@ +body { + margin: 0 auto; + min-width: 800px; + padding: 0px 20px; + background-color: white; + font-family: sans-serif; + font-size:14pt; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + -mx-text-size-adjust: none; +} + +a { + color: #4183C4; + text-decoration: none; +} +a:hover { + color: #4183C4; + text-decoration: underline; +} + +hr { + color: #eee; +} + +.title { + color: #4183C4; + float:left; + padding-top: 30px; + padding-bottom: 10px; +} +.title h1 { + display:inline; +} +.title h1:after { + content: " / "; + color: #777; + font-weight: normal; +} + +.content h1 { + font-size: 1.25em; +} +.content h2 { + font-size: 1.15em; +} +.content h2 { + font-size: 1.05em; + font-weight: bold; +} + +.section { + font-size: 1em; + font-weight: bold; + background-color: #f5f5f5; + border: 1px solid #d8d8d8; + border-radius: 3px 3px 0 0; + padding: 9px 10px 10px; + margin: 10px 0; +} + +.sectionmenu { + border: 1px solid #d8d8d8; + border-radius: 0 0 3px 3px; + border-top: 0; + margin-top: -10px; + margin-bottom: 10px; + padding: 10px; +} +.sectionmenu a { + display: inline-block; + margin-right: 1em; +} + +.status { + float:right; + font-size:.7em; + padding-top:50px; +} + +.mainmenu { + font-size:.8em; + clear:both; + padding:10px; + background:#eaeaea linear-gradient(#fafafa, #eaeaea) repeat-x; + border:1px solid #eaeaea; + border-radius:5px; +} + +.mainmenu a { + padding: 10px 20px; + text-decoration:none; + color: #777; + border-right:1px solid #eaeaea; +} +.mainmenu a.active, +.mainmenu a:hover { + color: #000; + border-bottom:2px solid #D26911; +} + +.submenu { + font-size: .7em; + margin-top: 10px; + padding: 10px; + border-bottom: 1px solid #ccc; +} + +.submenu a { + padding: 10px 11px; + text-decoration:none; + color: #777; +} + +.submenu a:hover { + padding: 6px 10px; + border: 1px solid #ccc; + border-radius: 5px; + color: #000; +} + +.content { + padding-top: 10px; + font-size:.8em; + color: #444; +} + +.udiff, .sbsdiff { + font-size: .85em !important; + overflow: auto; + border: 1px solid #ccc; + border-radius: 5px; +} +.content blockquote { + padding: 0 15px; +} + +table.report { + cursor: auto; + border-radius: 5px; + border: 1px solid #ccc; + margin: 1em 0; +} +.report td, .report th { + border: 0; + font-size: .8em; + padding: 10px; +} +.report td:first-child { + border-top-left-radius: 5px; +} +.report tbody tr:last-child td:first-child { + border-bottom-left-radius: 5px; +} +.report td:last-child { + border-top-right-radius: 5px; +} +.report tbody tr:last-child { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} +.report tbody tr:last-child td:last-child { + border-bottom-right-radius: 5px; +} +.report th { + cursor: pointer; +} +.report thead+tbody tr:hover { + background-color: #f5f9fc !important; +} + +td.tktDspLabel { + width: 70px; + text-align: right; + overflow: hidden; +} +td.tktDspValue { + text-align: left; + vertical-align: top; + background-color: #f8f8f8; + border: 1px solid #ccc; +} +td.tktDspValue pre { + white-space: pre-wrap; +} + +.footer { + border-top: 1px solid #ccc; + padding: 10px; + font-size:.7em; + margin-top: 10px; + color: #ccc; +} +div.timelineDate { + font-weight: bold; + white-space: nowrap; +} +span.submenuctrl, span.submenuctrl input, select.submenuctrl { + color: #777; +} ADDED skins/default/details.txt Index: skins/default/details.txt ================================================================== --- /dev/null +++ skins/default/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 0 ADDED skins/default/footer.txt Index: skins/default/footer.txt ================================================================== --- /dev/null +++ skins/default/footer.txt @@ -0,0 +1,6 @@ +<div class="footer"> +This page was generated in about +<th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by +Fossil version $manifest_version $manifest_date +</div> +</body></html> ADDED skins/default/header.txt Index: skins/default/header.txt ================================================================== --- /dev/null +++ skins/default/header.txt @@ -0,0 +1,56 @@ +<html> + <head> + <base href="$baseurl/$current_page" /> + <title>$<project_name>: $<title> + + + + + +
+

$

$</div> + <div class="status"><th1> + if {[info exists login]} { + html "$login — <a href='$home/login'>Logout</a>\n" + } else { + html "<a href='$home/login'>Login</a>\n" + } + </th1></div> + </div> + + <div class="mainmenu"> +<th1> +proc menulink {url name} { + upvar current_page current + upvar home home + if {[string range $url 0 [string length $current]] eq "/$current"} { + html "<a href='$home$url' class='active'>$name</a>\n" + } else { + html "<a href='$home$url'>$name</a>\n" + } +} +menulink $index_page Home +if {[anycap jor]} { + menulink /timeline Timeline +} +if {[hascap oh]} { + menulink /dir?ci=tip Files +} +if {[hascap o]} { + menulink /brlist Branches + menulink /taglist Tags +} +if {[hascap r]} { + menulink /ticket Tickets +} +if {[hascap j]} { + menulink /wiki Wiki +} +if {[hascap s]} { + menulink /setup Admin +} elseif {[hascap a]} { + menulink /setup_ulist Users +} +</th1></div> ADDED skins/eagle/README.md Index: skins/eagle/README.md ================================================================== --- /dev/null +++ skins/eagle/README.md @@ -0,0 +1,4 @@ +For this skin to look exactly as it was intended to, the **white-foreground** +setting should be enabled. + +This skin was contributed by Joe Mistachkin. ADDED skins/eagle/css.txt Index: skins/eagle/css.txt ================================================================== --- /dev/null +++ skins/eagle/css.txt @@ -0,0 +1,291 @@ +/* General settings for the entire page */ +body { + margin: 0ex 1ex; + padding: 0px; + background-color: #485D7B; + font-family: sans-serif; + color: white; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + -mx-text-size-adjust: none; +} + +/* The project logo in the upper left-hand corner of each page */ +div.logo { + display: table-cell; + text-align: center; + vertical-align: bottom; + font-weight: bold; + color: white; + padding: 5 0 5 0em; + white-space: nowrap; +} + +/* The page title centered at the top of each page */ +div.title { + display: table-cell; + font-size: 2em; + font-weight: bold; + text-align: left; + padding: 0 0 0 1em; + color: white; + vertical-align: bottom; + width: 100%; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + text-align: right; + vertical-align: bottom; + color: white; + font-size: 0.8em; + font-weight: bold; + min-width: 200px; + white-space: nowrap; +} + +/* The header across the top of the page */ +div.header { + display: table; + width: 100%; +} + +/* The main menu bar that appears at the top of the page beneath +** the header */ +div.mainmenu { + padding: 5px 10px 5px 10px; + font-size: 0.9em; + font-weight: bold; + text-align: center; + letter-spacing: 1px; + background-color: #76869D; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + color: white; +} + +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu, div.sectionmenu { + padding: 3px 10px 3px 0px; + font-size: 0.9em; + font-weight: bold; + text-align: center; + background-color: #485D7B; + color: white; +} +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited { + padding: 3px 10px 3px 10px; + color: white; + text-decoration: none; +} +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover { + text-decoration: underline; +} + +/* All page content from the bottom of the menu or submenu down to +** the footer */ +div.content { + padding: 0ex 1ex 0ex 2ex; +} + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0px; + margin-top: 1em; + padding: 1px 1px 1px 1px; + font-size: 1.2em; + font-weight: bold; + background-color: #485D7B; + color: white; + white-space: nowrap; +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + background: #9DB0CC; + color: white; + border: 2px white solid; + font-size: 1em; font-weight: normal; + padding: .25em; + margin: .2em 0 .2em 0; + float: left; + clear: left; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + clear: both; + font-size: 0.8em; + margin-top: 12px; + padding: 5px 10px 5px 10px; + text-align: right; + background-color: #485D7B; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + color: white; +} + +/* Hyperlink colors in the footer */ +a { color: white; } +a:link { color: white; } +a:visited { color: white; } +a:hover { color: #9DB0CC; } + +/* verbatim blocks */ +pre.verbatim { + background-color: #485D7B; + color: white; + padding: 0.5em; + white-space: pre-wrap; +} + +/* The label/value pairs on (for example) the ci page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} + +/* The nomenclature sidebox for branches,.. */ +div.sidebox { + float: right; + background-color: #485D7B; + border-width: medium; + border-style: double; + margin: 10px; +} + +/* the format for the timeline data table */ +table.timelineTable { + cellspacing: 0; + border: 0; + cellpadding: 0; + font-family: "courier new"; + border-collapse: collapse; +} + +tr.timelineSelected { + background-color: #7EA2D9; +} + +/* Side-by-side diff */ +table.sbsdiff { + background-color: #485D7B; + font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; + font-size: 8pt; + border-collapse:collapse; + white-space: pre; + width: 98%; + border: 1px #000 dashed; + margin-left: auto; + margin-right: auto; +} + +/* format for the layout table, used for the captcha display */ +table.captcha { + margin: auto; + padding: 10px; + border-width: 4px; + border-style: double; + border-color: white; +} + +/* format for the user list table on the user setup page */ +table.usetupUserList { + outline-style: double; + outline-width: 1px; + outline-color: white; + padding: 10px; +} + +/* color for capabilities, inherited by reader */ +span.ueditInheritReader { + color: white; +} + +/* format for values on ticket display page */ +td.tktDspValue { + text-align: left; + vertical-align: top; + background-color: #485D7B; +} + +/* format for example table cells on the report edit page */ +td.rpteditex { + border-width: thin; + border-color: white; + border-style: solid; +} + +/* List of files in a timeline */ +ul.filelist { + margin-top: 3px; + line-height: 100%; +} + +/* side-by-side diff display */ +div.sbsdiff { + font-family: monospace; + font-size: smaller; + white-space: pre; +} + +/* context diff display */ +div.udiff { + font-family: monospace; + white-space: pre; +} + +/* changes in a diff */ +span.diffchng { + background-color: rgb(170, 170, 140); +} + +/* added code in a diff */ +span.diffadd { + background-color: rgb(100, 200, 100); +} + +/* deleted in a diff */ +span.diffrm { + background-color: rgb(230, 110, 110); +} + +/* suppressed lines in a diff */ +span.diffhr { + display: inline-block; + margin: .5em 0 1em; + color: rgb(150, 150, 140); +} + +/* line numbers in a diff */ +span.diffln { + color: white; +} + +#canvas { + background-color: #485D7B; +} + +.fileage tr:hover { + background-color: #7EA2D9; +} + +.fileage td { + font-family: "courier new"; +} + +div.filetreeline:hover { + background-color: #7EA2D9; +} + +div.selectedText { + background-color: #7EA2D9; +} + +.statistics-report-graph-line { + background-color: #7EA2D9; +} ADDED skins/eagle/details.txt Index: skins/eagle/details.txt ================================================================== --- /dev/null +++ skins/eagle/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 1 ADDED skins/eagle/footer.txt Index: skins/eagle/footer.txt ================================================================== --- /dev/null +++ skins/eagle/footer.txt @@ -0,0 +1,25 @@ +<div class="footer"> + <th1> + proc getTclVersion {} { + if {[catch {tclEval info patchlevel} tclVersion] == 0} { + return "<a href=\"http://www.tcl.tk/\">Tcl</a> version $tclVersion" + } + return "" + } + proc getVersion { version } { + set length [string length $version] + return [string range $version 1 [expr {$length - 2}]] + } + set version [getVersion $manifest_version] + set tclVersion [getTclVersion] + set fossilUrl https://www.fossil-scm.org + set fossilDate [string range $manifest_date 0 9]T[string range $manifest_date 11 end] + </th1> + This page was generated in about + <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by + <a href="$fossilUrl/">Fossil</a> + version $release_version $tclVersion + <a href="$fossilUrl/index.html/info/$version">$manifest_version</a> + <a href="$fossilUrl/index.html/timeline?c=$fossilDate&y=ci">$manifest_date</a> +</div> +</body></html> ADDED skins/eagle/header.txt Index: skins/eagle/header.txt ================================================================== --- /dev/null +++ skins/eagle/header.txt @@ -0,0 +1,138 @@ +<html> +<head> +<base href="$baseurl/$current_page" /> +<title>$<project_name>: $<title> + + + + +
+ +
$</div> + <div class="status"><nobr><th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></nobr><small><div id="clock"></div></small></div> +</div> +<script> +function updateClock(){ + var e = document.getElementById("clock"); + if(e){ + var d = new Date(); + function f(n) { + return n < 10 ? '0' + n : n; + } + e.innerHTML = d.getUTCFullYear()+ '-' + + f(d.getUTCMonth() + 1) + '-' + + f(d.getUTCDate()) + ' ' + + f(d.getUTCHours()) + ':' + + f(d.getUTCMinutes()); + setTimeout("updateClock();",(60-d.getUTCSeconds())*1000); + } +} +updateClock(); +</script> +<div class="mainmenu"> +<th1> +proc menulink {url name} { + upvar home home + html "<a href='$home$url'>$name</a>\n" +} +menulink $index_page Home +menulink /help Help +if {[anycap jor]} { + menulink /timeline Timeline +} +if {[anoncap oh]} { + menulink /dir?ci=tip Files +} +if {[anoncap o]} { + menulink /brlist Branches + menulink /taglist Tags +} +if {[anoncap r]} { + menulink /ticket Tickets +} +if {[anoncap j]} { + menulink /wiki Wiki +} +if {[hascap s]} { + menulink /setup Admin +} elseif {[hascap a]} { + menulink /setup_ulist Users +} +if {[info exists login]} { + menulink /login Logout +} else { + menulink /login Login +} +</th1></div> ADDED skins/enhanced1/css.txt Index: skins/enhanced1/css.txt ================================================================== --- /dev/null +++ skins/enhanced1/css.txt @@ -0,0 +1,148 @@ +/* General settings for the entire page */ +body { + margin: 0ex 1ex; + padding: 0px; + background-color: white; + font-family: sans-serif; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + -mx-text-size-adjust: none; +} + +/* The project logo in the upper left-hand corner of each page */ +div.logo { + display: table-cell; + text-align: center; + vertical-align: bottom; + font-weight: bold; + color: #558195; + min-width: 200px; + white-space: nowrap; +} + +/* The page title centered at the top of each page */ +div.title { + display: table-cell; + font-size: 2em; + font-weight: bold; + text-align: center; + padding: 0 0 0 1em; + color: #558195; + vertical-align: bottom; + width: 100%; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + text-align: right; + vertical-align: bottom; + color: #558195; + font-size: 0.8em; + font-weight: bold; + min-width: 200px; + white-space: nowrap; +} + +/* The header across the top of the page */ +div.header { + display: table; + width: 100%; +} + +/* The main menu bar that appears at the top of the page beneath +** the header */ +div.mainmenu { + padding: 5px 10px 5px 10px; + font-size: 0.9em; + font-weight: bold; + text-align: center; + letter-spacing: 1px; + background-color: #558195; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + color: white; +} + +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu, div.sectionmenu { + padding: 3px 10px 3px 0px; + font-size: 0.9em; + text-align: center; + background-color: #456878; + color: white; +} +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited { + padding: 3px 10px 3px 10px; + color: white; + text-decoration: none; +} +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover { + color: #558195; + background-color: white; +} + +/* All page content from the bottom of the menu or submenu down to +** the footer */ +div.content { + padding: 0ex 1ex 1ex 1ex; + border: solid #aaa; + border-width: 1px; +} + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0px; + margin-top: 1em; + padding: 1px 1px 1px 1px; + font-size: 1.2em; + font-weight: bold; + background-color: #558195; + color: white; + white-space: nowrap; +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + background: #a1c4d4; + border: 2px #558195 solid; + font-size: 1em; font-weight: normal; + padding: .25em; + margin: .2em 0 .2em 0; + float: left; + clear: left; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + clear: both; + font-size: 0.8em; + padding: 5px 10px 5px 10px; + text-align: right; + background-color: #558195; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + color: white; +} + +/* Hyperlink colors in the footer */ +div.footer a { color: white; } +div.footer a:link { color: white; } +div.footer a:visited { color: white; } +div.footer a:hover { background-color: white; color: #558195; } + +/* verbatim blocks */ +pre.verbatim { + background-color: #f5f5f5; + padding: 0.5em; + white-space: pre-wrap; +} + +/* The label/value pairs on (for example) the ci page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} ADDED skins/enhanced1/details.txt Index: skins/enhanced1/details.txt ================================================================== --- /dev/null +++ skins/enhanced1/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 0 ADDED skins/enhanced1/footer.txt Index: skins/enhanced1/footer.txt ================================================================== --- /dev/null +++ skins/enhanced1/footer.txt @@ -0,0 +1,25 @@ +<div class="footer"> + <th1> + proc getTclVersion {} { + if {[catch {tclEval info patchlevel} tclVersion] == 0} { + return "<a href=\"http://www.tcl.tk/\">Tcl</a> version $tclVersion" + } + return "" + } + proc getVersion { version } { + set length [string length $version] + return [string range $version 1 [expr {$length - 2}]] + } + set version [getVersion $manifest_version] + set tclVersion [getTclVersion] + set fossilUrl https://www.fossil-scm.org + set fossilDate [string range $manifest_date 0 9]T[string range $manifest_date 11 end] + </th1> + This page was generated in about + <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by + <a href="$fossilUrl/">Fossil</a> + version $release_version $tclVersion + <a href="$fossilUrl/index.html/info/$version">$manifest_version</a> + <a href="$fossilUrl/index.html/timeline?c=$fossilDate&y=ci">$manifest_date</a> +</div> +</body></html> ADDED skins/enhanced1/header.txt Index: skins/enhanced1/header.txt ================================================================== --- /dev/null +++ skins/enhanced1/header.txt @@ -0,0 +1,138 @@ +<html> +<head> +<base href="$baseurl/$current_page" /> +<title>$<project_name>: $<title> + + + + +
+ +
$</div> + <div class="status"><th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></nobr><small><div id="clock"></div></small></div> +</div> +<script> +function updateClock(){ + var e = document.getElementById("clock"); + if(e){ + var d = new Date(); + function f(n) { + return n < 10 ? '0' + n : n; + } + e.innerHTML = d.getUTCFullYear()+ '-' + + f(d.getUTCMonth() + 1) + '-' + + f(d.getUTCDate()) + ' ' + + f(d.getUTCHours()) + ':' + + f(d.getUTCMinutes()); + setTimeout("updateClock();",(60-d.getUTCSeconds())*1000); + } +} +updateClock(); +</script> +<div class="mainmenu"> +<th1> +proc menulink {url name} { + upvar home home + html "<a href='$home$url'>$name</a>\n" +} +menulink $index_page Home +menulink /help Help +if {[anycap jor]} { + menulink /timeline Timeline +} +if {[anoncap oh]} { + menulink /dir?ci=tip Files +} +if {[anoncap o]} { + menulink /brlist Branches + menulink /taglist Tags +} +if {[anoncap r]} { + menulink /ticket Tickets +} +if {[anoncap j]} { + menulink /wiki Wiki +} +if {[hascap s]} { + menulink /setup Admin +} elseif {[hascap a]} { + menulink /setup_ulist Users +} +if {[info exists login]} { + menulink /login Logout +} else { + menulink /login Login +} +</th1></div> ADDED skins/khaki/css.txt Index: skins/khaki/css.txt ================================================================== --- /dev/null +++ skins/khaki/css.txt @@ -0,0 +1,146 @@ +/* General settings for the entire page */ +body { + margin: 0ex 0ex; + padding: 0px; + background-color: #fef3bc; + font-family: sans-serif; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + -mx-text-size-adjust: none; +} + +/* The project logo in the upper left-hand corner of each page */ +div.logo { + display: inline; + text-align: center; + vertical-align: bottom; + font-weight: bold; + font-size: 2.5em; + color: #a09048; + white-space: nowrap; +} + +/* The page title centered at the top of each page */ +div.title { + display: table-cell; + font-size: 2em; + font-weight: bold; + text-align: left; + padding: 0 0 0 5px; + color: #a09048; + vertical-align: bottom; + width: 100%; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + text-align: right; + vertical-align: bottom; + color: #a09048; + padding: 5px 5px 0 0; + font-size: 0.8em; + font-weight: bold; + white-space: nowrap; +} + +/* The header across the top of the page */ +div.header { + display: table; + width: 100%; +} + +/* The main menu bar that appears at the top of the page beneath +** the header */ +div.mainmenu { + padding: 5px 10px 5px 10px; + font-size: 0.9em; + font-weight: bold; + text-align: center; + letter-spacing: 1px; + background-color: #a09048; + color: black; +} + +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu, div.sectionmenu { + padding: 3px 10px 3px 0px; + font-size: 0.9em; + text-align: center; + background-color: #c0af58; + color: white; +} +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited { + padding: 3px 10px 3px 10px; + color: white; + text-decoration: none; +} +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover { + color: #a09048; + background-color: white; +} + +/* All page content from the bottom of the menu or submenu down to +** the footer */ +div.content { + padding: 1ex 5px; +} +div.content a { color: #706532; } +div.content a:link { color: #706532; } +div.content a:visited { color: #704032; } +div.content a:hover { background-color: white; color: #706532; } + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0px; + margin-top: 1em; + padding: 3px 3px 0 3px; + font-size: 1.2em; + font-weight: bold; + background-color: #a09048; + color: white; + white-space: nowrap; +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + background: #e1d498; + border: 2px #a09048 solid; + font-size: 1em; font-weight: normal; + padding: .25em; + margin: .2em 0 .2em 0; + float: left; + clear: left; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + font-size: 0.8em; + margin-top: 12px; + padding: 5px 10px 5px 10px; + text-align: right; + background-color: #a09048; + color: white; +} + +/* Hyperlink colors */ +div.footer a { color: white; } +div.footer a:link { color: white; } +div.footer a:visited { color: white; } +div.footer a:hover { background-color: white; color: #558195; } + +/* <verbatim> blocks */ +pre.verbatim { + background-color: #f5f5f5; + padding: 0.5em; + white-space: pre-wrap; +} + +/* The label/value pairs on (for example) the ci page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} ADDED skins/khaki/details.txt Index: skins/khaki/details.txt ================================================================== --- /dev/null +++ skins/khaki/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 0 ADDED skins/khaki/footer.txt Index: skins/khaki/footer.txt ================================================================== --- /dev/null +++ skins/khaki/footer.txt @@ -0,0 +1,4 @@ +<div class="footer"> +Fossil version $manifest_version $manifest_date +</div> +</body></html> ADDED skins/khaki/header.txt Index: skins/khaki/header.txt ================================================================== --- /dev/null +++ skins/khaki/header.txt @@ -0,0 +1,52 @@ +<html> +<head> +<base href="$baseurl/$current_page" /> +<title>$<project_name>: $<title> + + + + +
+
$</div> + <div class="status"> + <div class="logo">$<project_name></div><br/> + <th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></div> +</div> +<div class="mainmenu"> +<th1> +html "<a href='$home$index_page'>Home</a>\n" +if {[anycap jor]} { + html "<a href='$home/timeline'>Timeline</a>\n" +} +if {[anoncap oh]} { + html "<a href='$home/tree?ci=tip'>Files</a>\n" +} +if {[anoncap o]} { + html "<a href='$home/brlist'>Branches</a>\n" + html "<a href='$home/taglist'>Tags</a>\n" +} +if {[anoncap r]} { + html "<a href='$home/ticket'>Tickets</a>\n" +} +if {[anoncap j]} { + html "<a href='$home/wiki'>Wiki</a>\n" +} +if {[hascap s]} { + html "<a href='$home/setup'>Admin</a>\n" +} elseif {[hascap a]} { + html "<a href='$home/setup_ulist'>Users</a>\n" +} +if {[info exists login]} { + html "<a href='$home/login'>Logout</a>\n" +} else { + html "<a href='$home/login'>Login</a>\n" +} +</th1></div> ADDED skins/original/css.txt Index: skins/original/css.txt ================================================================== --- /dev/null +++ skins/original/css.txt @@ -0,0 +1,148 @@ +/* General settings for the entire page */ +body { + margin: 0ex 1ex; + padding: 0px; + background-color: white; + font-family: sans-serif; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + -mx-text-size-adjust: none; +} + +/* The project logo in the upper left-hand corner of each page */ +div.logo { + display: table-cell; + text-align: center; + vertical-align: bottom; + font-weight: bold; + color: #558195; + min-width: 200px; + white-space: nowrap; +} + +/* The page title centered at the top of each page */ +div.title { + display: table-cell; + font-size: 2em; + font-weight: bold; + text-align: center; + padding: 0 0 0 1em; + color: #558195; + vertical-align: bottom; + width: 100%; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + text-align: right; + vertical-align: bottom; + color: #558195; + font-size: 0.8em; + font-weight: bold; + min-width: 200px; + white-space: nowrap; +} + +/* The header across the top of the page */ +div.header { + display: table; + width: 100%; +} + +/* The main menu bar that appears at the top of the page beneath +** the header */ +div.mainmenu { + padding: 5px 10px 5px 10px; + font-size: 0.9em; + font-weight: bold; + text-align: center; + letter-spacing: 1px; + background-color: #558195; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + color: white; +} + +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu, div.sectionmenu { + padding: 3px 10px 3px 0px; + font-size: 0.9em; + text-align: center; + background-color: #456878; + color: white; +} +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited { + padding: 3px 10px 3px 10px; + color: white; + text-decoration: none; +} +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover { + color: #558195; + background-color: white; +} + +/* All page content from the bottom of the menu or submenu down to +** the footer */ +div.content { + padding: 0ex 1ex 1ex 1ex; + border: solid #aaa; + border-width: 1px; +} + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0px; + margin-top: 1em; + padding: 1px 1px 1px 1px; + font-size: 1.2em; + font-weight: bold; + background-color: #558195; + color: white; + white-space: nowrap; +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + background: #a1c4d4; + border: 2px #558195 solid; + font-size: 1em; font-weight: normal; + padding: .25em; + margin: .2em 0 .2em 0; + float: left; + clear: left; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + clear: both; + font-size: 0.8em; + padding: 5px 10px 5px 10px; + text-align: right; + background-color: #558195; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + color: white; +} + +/* Hyperlink colors in the footer */ +div.footer a { color: white; } +div.footer a:link { color: white; } +div.footer a:visited { color: white; } +div.footer a:hover { background-color: white; color: #558195; } + +/* verbatim blocks */ +pre.verbatim { + background-color: #f5f5f5; + padding: 0.5em; + white-space: pre-wrap; +} + +/* The label/value pairs on (for example) the ci page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} ADDED skins/original/details.txt Index: skins/original/details.txt ================================================================== --- /dev/null +++ skins/original/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 0 ADDED skins/original/footer.txt Index: skins/original/footer.txt ================================================================== --- /dev/null +++ skins/original/footer.txt @@ -0,0 +1,6 @@ +<div class="footer"> +This page was generated in about +<th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by +Fossil version $manifest_version $manifest_date +</div> +</body></html> ADDED skins/original/header.txt Index: skins/original/header.txt ================================================================== --- /dev/null +++ skins/original/header.txt @@ -0,0 +1,53 @@ +<html> +<head> +<base href="$baseurl/$current_page" /> +<title>$<project_name>: $<title> + + + + +
+ +
$
$</div> + <div class="status"><th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></div> +</div> +<div class="mainmenu"> +<th1> +html "<a href='$home$index_page'>Home</a>\n" +if {[anycap jor]} { + html "<a href='$home/timeline'>Timeline</a>\n" +} +if {[anoncap oh]} { + html "<a href='$home/tree?ci=tip'>Files</a>\n" +} +if {[anoncap o]} { + html "<a href='$home/brlist'>Branches</a>\n" + html "<a href='$home/taglist'>Tags</a>\n" +} +if {[anoncap r]} { + html "<a href='$home/ticket'>Tickets</a>\n" +} +if {[anoncap j]} { + html "<a href='$home/wiki'>Wiki</a>\n" +} +if {[hascap s]} { + html "<a href='$home/setup'>Admin</a>\n" +} elseif {[hascap a]} { + html "<a href='$home/setup_ulist'>Users</a>\n" +} +if {[info exists login]} { + html "<a href='$home/login'>Logout</a>\n" +} else { + html "<a href='$home/login'>Login</a>\n" +} +</th1></div> ADDED skins/plain_gray/css.txt Index: skins/plain_gray/css.txt ================================================================== --- /dev/null +++ skins/plain_gray/css.txt @@ -0,0 +1,142 @@ +/* General settings for the entire page */ +body { + margin: 0ex 1ex; + padding: 0px; + background-color: white; + font-family: sans-serif; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + -mx-text-size-adjust: none; +} + +/* The project logo in the upper left-hand corner of each page */ +div.logo { + display: table-row; + text-align: center; + /* vertical-align: bottom;*/ + font-size: 2em; + font-weight: bold; + background-color: #707070; + color: #ffffff; + min-width: 200px; + white-space: nowrap; +} + +/* The page title centered at the top of each page */ +div.title { + display: table-cell; + font-size: 1.5em; + font-weight: bold; + text-align: center; + padding: 0 0 0 10px; + color: #404040; + vertical-align: bottom; + width: 100%; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + text-align: right; + vertical-align: bottom; + color: #404040; + font-size: 0.8em; + font-weight: bold; + min-width: 200px; + white-space: nowrap; +} + +/* The header across the top of the page */ +div.header { + display: table; + width: 100%; +} + +/* The main menu bar that appears at the top of the page beneath +** the header */ +div.mainmenu { + padding: 5px 10px 5px 10px; + font-size: 0.9em; + font-weight: bold; + text-align: center; + letter-spacing: 1px; + background-color: #404040; + color: white; +} + +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu, div.sectionmenu { + padding: 3px 10px 3px 0px; + font-size: 0.9em; + text-align: center; + background-color: #606060; + color: white; +} +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited { + padding: 3px 10px 3px 10px; + color: white; + text-decoration: none; +} +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover { + color: #404040; + background-color: white; +} + +/* All page content from the bottom of the menu or submenu down to +** the footer */ +div.content { + padding: 0ex 0ex 0ex 0ex; +} +/* Hyperlink colors */ +div.content a { color: #604000; } +div.content a:link { color: #604000;} +div.content a:visited { color: #600000; } + +/* <verbatim> blocks */ +pre.verbatim { + background-color: #ffffff; + padding: 0.5em; + white-space: pre-wrap; +} + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0px; + margin-top: 1em; + padding: 1px 1px 1px 1px; + font-size: 1.2em; + font-weight: bold; + background-color: #404040; + color: white; + white-space: nowrap; +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + background: #a0a0a0; + border: 2px #505050 solid; + font-size: 1em; font-weight: normal; + padding: .25em; + margin: .2em 0 .2em 0; + float: left; + clear: left; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + font-size: 0.8em; + margin-top: 12px; + padding: 5px 10px 5px 10px; + text-align: right; + background-color: #404040; + color: white; +} + +/* The label/value pairs on (for example) the vinfo page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} ADDED skins/plain_gray/details.txt Index: skins/plain_gray/details.txt ================================================================== --- /dev/null +++ skins/plain_gray/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 0 ADDED skins/plain_gray/footer.txt Index: skins/plain_gray/footer.txt ================================================================== --- /dev/null +++ skins/plain_gray/footer.txt @@ -0,0 +1,4 @@ +<div class="footer"> +Fossil version $manifest_version $manifest_date +</div> +</body></html> ADDED skins/plain_gray/header.txt Index: skins/plain_gray/header.txt ================================================================== --- /dev/null +++ skins/plain_gray/header.txt @@ -0,0 +1,50 @@ +<html> +<head> +<base href="$baseurl/$current_page" /> +<title>$<project_name>: $<title> + + + + +
+
$
$</div> + <div class="status"><th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></div> +</div> +<div class="mainmenu"> +<th1> +html "<a href='$home$index_page'>Home</a>\n" +if {[anycap jor]} { + html "<a href='$home/timeline'>Timeline</a>\n" +} +if {[anoncap oh]} { + html "<a href='$home/tree?ci=tip'>Files</a>\n" +} +if {[anoncap o]} { + html "<a href='$home/brlist'>Branches</a>\n" + html "<a href='$home/taglist'>Tags</a>\n" +} +if {[anoncap r]} { + html "<a href='$home/ticket'>Tickets</a>\n" +} +if {[anoncap j]} { + html "<a href='$home/wiki'>Wiki</a>\n" +} +if {[hascap s]} { + html "<a href='$home/setup'>Admin</a>\n" +} elseif {[hascap a]} { + html "<a href='$home/setup_ulist'>Users</a>\n" +} +if {[info exists login]} { + html "<a href='$home/login'>Logout</a>\n" +} else { + html "<a href='$home/login'>Login</a>\n" +} +</th1></div> ADDED skins/rounded1/css.txt Index: skins/rounded1/css.txt ================================================================== --- /dev/null +++ skins/rounded1/css.txt @@ -0,0 +1,197 @@ +/* General settings for the entire page */ +html { + min-height: 100%; +} +body { + margin: 0ex 1ex; + padding: 0px; + background-color: white; + color: #333; + font-family: Verdana, sans-serif; + font-size: 0.8em; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + -mx-text-size-adjust: none; +} + +/* The project logo in the upper left-hand corner of each page */ +div.logo { + display: table-cell; + text-align: right; + vertical-align: bottom; + font-weight: normal; + white-space: nowrap; +} + +/* Widths */ +div.header, div.mainmenu, div.submenu, div.content, div.footer { + max-width: 900px; + margin: auto; + padding: 3px 20px 3px 20px; + clear: both; +} + +/* The page title at the top of each page */ +div.title { + display: table-cell; + padding-left: 10px; + font-size: 2em; + margin: 10px 0 10px -20px; + vertical-align: bottom; + text-align: left; + width: 80%; + font-family: Verdana, sans-serif; + font-weight: bold; + color: #558195; + text-shadow: 0px 2px 2px #999999; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + text-align: right; + vertical-align: bottom; + color: #333; + margin-right: -20px; + white-space: nowrap; +} + +/* The main menu bar that appears at the top of the page beneath + ** the header */ +div.mainmenu { + text-align: center; + color: white; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + vertical-align: middle; + padding-top: 8px; + padding-bottom: 8px; + background-color: #446979; + box-shadow: 0px 3px 4px #333333; +} + +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu { + padding-top:10px; + padding-bottom:0; + text-align: right; + color: #000; + background-color: #fff; + height: 1.5em; + vertical-align:middle; + box-shadow: 0px 3px 4px #999; +} +div.mainmenu a, div.mainmenu a:visited { + padding: 3px 10px 3px 10px; + color: white; + text-decoration: none; +} +div.submenu a, div.submenu a:visited, a.button, +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited { + padding: 2px 8px; + color: #000; + font-family: Arial; + text-decoration: none; + margin:auto; + border-radius: 5px; + background-color: #e0e0e0; + text-shadow: 0px -1px 0px #eee; + border: 1px solid #000; +} + +div.mainmenu a:hover { + color: #000; + background-color: white; +} + +div.submenu a:hover, div.sectionmenu>a.button:hover { + background-color: #c0c0c0; +} + +/* All page content from the bottom of the menu or submenu down to + ** the footer */ +div.content { + background-color: #fff; + box-shadow: 0px 3px 4px #999; + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; + padding-bottom: 1em; + min-height:40%; +} + + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0.5em; + margin-top: 1em; + margin-right: auto; + padding: 1px 1px 1px 1px; + font-size: 1.2em; + font-weight: bold; + text-align: center; + color: white; + border-radius: 5px; + background-color: #446979; + box-shadow: 0px 3px 4px #333333; + white-space: nowrap; +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + font-size: 1.2em; + font-family: Georgia, serif; + font-weight: bold; + margin-top: 1em; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + font-size: 0.9em; + text-align: right; + margin-bottom: 1em; + color: #666; +} + +/* Hyperlink colors in the footer */ +div.footer a { color: white; } +div.footer a:link { color: white; } +div.footer a:visited { color: white; } +div.footer a:hover { background-color: white; color: #558195; } + +/* <verbatim> blocks */ +pre.verbatim, blockquote pre { + font-family: Dejavu Sans Mono, Monaco, Lucida Console, monospace; + background-color: #f3f3f3; + padding: 0.5em; + white-space: pre-wrap; +} + +blockquote pre { + border: 1px #000 dashed; +} + +/* The label/value pairs on (for example) the ci page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} + +table.report tr th { + padding: 3px 5px; + text-transform: capitalize; + cursor: pointer; +} + +table.report tr td { + padding: 3px 5px; +} + +textarea { + font-size: 1em; +} + +.fullsize-text { + font-size: 1.25em; +} ADDED skins/rounded1/details.txt Index: skins/rounded1/details.txt ================================================================== --- /dev/null +++ skins/rounded1/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 0 ADDED skins/rounded1/footer.txt Index: skins/rounded1/footer.txt ================================================================== --- /dev/null +++ skins/rounded1/footer.txt @@ -0,0 +1,4 @@ +<div class="footer"> +Fossil version $manifest_version $manifest_date +</div> +</body></html> ADDED skins/rounded1/header.txt Index: skins/rounded1/header.txt ================================================================== --- /dev/null +++ skins/rounded1/header.txt @@ -0,0 +1,54 @@ +<html> +<head> +<base href="$baseurl/$current_page" /> +<title>$<project_name>: $<title> + + + + +
+ +
$</div> + <div class="status"><th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></div> +</div> +<div class="mainmenu"> +<th1> +html "<a href='$home$index_page'>Home</a>\n" +if {[anycap jor]} { + html "<a href='$home/timeline'>Timeline</a>\n" +} +if {[anoncap oh]} { + html "<a href='$home/tree?ci=tip'>Files</a>\n" +} +if {[anoncap o]} { + html "<a href='$home/brlist'>Branches</a>\n" + html "<a href='$home/taglist'>Tags</a>\n" +} +if {[anoncap r]} { + html "<a href='$home/ticket'>Tickets</a>\n" +} +if {[anoncap j]} { + html "<a href='$home/wiki'>Wiki</a>\n" +} +if {[hascap s]} { + html "<a href='$home/setup'>Admin</a>\n" +} elseif {[hascap a]} { + html "<a href='$home/setup_ulist'>Users</a>\n" +} +if {[info exists login]} { + html "<a href='$home/login'>Logout</a>\n" +} else { + html "<a href='$home/login'>Login</a>\n" +} +</th1></div> ADDED skins/xekri/README.md Index: skins/xekri/README.md ================================================================== --- /dev/null +++ skins/xekri/README.md @@ -0,0 +1,2 @@ +"xekri" is a Lojban word that means "extermely dark-colored". +This skin was contributed by Andrew Moore. ADDED skins/xekri/css.txt Index: skins/xekri/css.txt ================================================================== --- /dev/null +++ skins/xekri/css.txt @@ -0,0 +1,1032 @@ +/****************************************************************************** + * Xekri + * + * To adjust the width of the contents for this skin, look for the "max-width" + * property and change its value. (It's in the "Main Area" section) The value + * determines how much of the browser window to use. Some like 100%, so that + * the entire window is used. Others prefer 80%, which makes the contents + * easier to read for them. + */ + + +/************************************** + * General HTML + */ + +html { + background-color: #333; + color: #eee; + font-family: Monospace; + font-size: 1em; + min-height: 100%; +} + +body { + margin: 0; + padding: 0; + -moz-text-size-adjust: none; + -ms-text-size-adjust: none; + -webkit-text-size-adjust: none; +} + +a { + color: #07e; +} + +a:hover { + font-weight: bold; +} + +blockquote pre { + border: 1px dashed #ee0; +} + +blockquote pre, pre.verbatim { + background-color: #000; + border-radius: 0.75rem; + padding: 0.5rem; + white-space: pre-wrap; +} + +input[type="password"], input[type="text"], textarea { + background-color: #111; + color: #fff; + font-size: 1rem; +} + +h1 { + font-size: 2rem; +} + +h2 { + font-size: 1.5rem; +} + +h3 { + font-size: 1.25rem; +} + +span[style^=background-color] { + color: #000; +} + +td[style^=background-color] { + color: #000; +} + + +/************************************** + * Main Area + */ + +div.header, div.mainmenu, div.submenu, div.content, div.footer { + clear: both; + margin: 0 auto; + max-width: 90%; + padding: 0.25rem 1rem; +} + + +/************************************** + * Main Area: Header + */ + +div.header { + margin: 0.5rem auto 0 auto; +} + +div.logo img { + float: left; + padding: 0; + box-shadow: 3px 3px 1px #000; + margin: 0 6px 6px 0; +} + +div.logo br { + display: none; +} + +div.logo nobr { + color: #eee; + font-size: 1.2rem; + font-weight: bold; + padding: 0; + text-shadow: 3px 3px 1px #000; + vertical-align: top; + white-space: nowrap; +} + +div.title { + color: #07e; + font-family: Verdana, sans-serif; + font-weight: bold; + font-size: 2.5rem; + padding: 0.5rem; + text-align: center; + text-shadow: 3px 3px 1px #000; +} + +div.status { + color: #ee0; + font-size: 1rem; + padding: 0.25rem; + text-align: right; + text-shadow: 2px 2px 1px #000; +} + + +/************************************** + * Main Area: Global Menu + */ + +div.mainmenu, div.submenu { + background-color: #080; + border-radius: 1rem 1rem 0 0; + box-shadow: 3px 4px 1px #000; + color: #000; + font-weight: bold; + font-size: 1.1rem; + text-align: center; +} + +div.mainmenu { + padding-top: 0.33rem; + padding-bottom: 0.25rem; +} + +div.submenu { + border-top: 1px solid #0a0; + border-radius: 0; + display: block; +} + +div.mainmenu a, div.submenu a { + color: #000; + padding: 0 0.75rem; + text-decoration: none; +} + +div.mainmenu a:hover, div.submenu a:hover { + color: #fff; + text-shadow: 0px 0px 6px #0f0; +} + +div.submenu * { + margin: 0 0.5rem; + vertical-align: middle; +} + +div.submenu select, div.submenu input { + background-color: #222; + border: 1px inset #080; + color: #eee; + cursor: pointer; + font-size: 0.9rem; +} + +div.submenu select { + height: 1.75rem; +} + +/************************************** + * Main Area: Content + */ + +div.content { + background-color: #222; + border-radius: 0 0 1rem 1rem; + box-shadow: 3px 3px 1px #000; + min-height:40%; + padding-bottom: 1rem; + padding-top: 0.5rem; +} + +div.content table[bgcolor="white"] { + color: #000; +} + +/************************************** + * Main Area: Footer + */ + +div.footer { + color: #ee0; + font-size: 0.75rem; + padding: 0; + text-align: right; + width: 75%; +} + + +div.footer div { + background-color: #222; + box-shadow: 3px 3px 1px #000; + border-radius: 0 0 1rem 1rem; + margin: 0 0 10px 0; + padding: 0.5rem 0.75rem; +} + +div.footer div.page-time { + float: left; +} + +div.footer div.fossil-info { + float: right; +} + +div.footer a, div.footer a:link, div.footer a:visited { + color: #ee0; +} + +div.footer a:hover { + color: #fff; + text-shadow: 0px 0px 6px #ee0; +} + + +/************************************** + * Check-in + */ + +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.1rem 1rem; +} + + +/************************************** + * Diffs + */ + +/* Code Added */ +span.diffadd { + background-color: #7f7; + color: #000; +} + +/* Code Changed */ +span.diffchng { + background-color: #77f; + color: #000; +} + +/* Code Deleted */ +span.diffrm { + background-color: #f77; + color: #000; +} + + +/************************************** + * Diffs : Side-By-Side + */ + +/* display (column-based) */ +table.sbsdiffcols { + border-spacing: 0; + font-size: 0.85rem; + width: 90%; +} + +table.sbsdiffcols pre { + border: 0; + margin: 0; + padding: 0; +} + +table.sbsdiffcols td { + padding: 0; + vertical-align: top; +} + +/* line number column */ +div.difflncol { + color: #ee0; + padding-right: 0.75em; + text-align: right; +} + +/* diff text column */ +div.difftxtcol { + background-color: #111; + overflow-x: auto; + width: 45em; +} + +/* suppressed lines */ +span.diffhr { + display: inline-block; + margin-bottom: 0.75em; + color: #ff0; +} + +/* diff marker column */ +div.diffmkrcol { + padding: 0 0.5em; +} + + +/************************************** + * Diffs : Unified + */ + +pre.udiff { + background-color: #111; +} + +/* line numbers */ +span.diffln { + background-color: #222; + color: #ee0; +} + + +/************************************** + * File List : Flat + */ + +table.browser { + width: 100%; + border: 0; +} + +td.browser { + width: 24%; + vertical-align: top; +} + +ul.browser { + margin: 0.5rem; + padding: 0.5rem; + white-space: nowrap; +} + +ul.browser li.dir { + font-style: italic +} + + +/************************************** + * File List : Age + */ + +.fileage tr:hover td { + background-color: #225; +} + + +/************************************** + * File List : Tree + */ + +.filetree { + line-height: 1.5; + margin: 1rem 0; +} + +/* list */ +.filetree ul { + list-style: none; + margin: 0; + padding: 0; +} + +/* collapsed list */ +.filetree ul.collapsed { + display: none; +} + +/* lists below the root */ +.filetree ul ul { + margin: 0 0 0 21px; + position: relative; +} + +/* lists items */ +.filetree li { + margin: 0; + padding: 0; + position: relative; +} + +/* node lines */ +.filetree li li:before { + border-bottom: 2px solid #000; + border-left: 2px solid #000; + content: ''; + height: 1.5rem; + left: -14px; + position: absolute; + top: -0.8rem; + width: 14px; +} + +/* directory lines */ +.filetree li > ul:before { + border-left: 2px solid #000; + bottom: 0; + content: ''; + left: -35px; + position: absolute; + top: -1.5rem; +} + +/* hide lines for last-child directories */ +.filetree li.last > ul:before { + display: none; +} + +.filetree a { + background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP\/\/\/yEhIf\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmgOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==); + background-position: center left; + background-repeat: no-repeat; + display: inline-block; + min-height: 16px; + padding-left: 21px; + position: relative; + z-index: 1; +} + +.filetree .dir > a { + background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIiIv\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo+jUs6b5Z/K4siDu5RPUFADs=); + font-style: italic +} + +.filetreeline:hover { + color: #000; + font-weight: bold; +} + +.filetreeline .filetreeage { + padding-right: 0.5rem; +} + +/************************************** + * Logout + */ + +span.loginError { + color: #f00; +} + +table.login_out { + margin: 10px; + text-align: left; +} + +td.login_out_label { + text-align: center; +} + +div.captcha { + padding: 1rem; + text-align: center; +} + +table.captcha { + background-color: #111; + border-color: #111; + border-style: inset; + border-width: 2px; + margin: auto; + padding: 0.5rem; +} + +table.captcha pre { + color: #ee0; +} + + +/************************************** + * Statistics Reports + */ + +.statistics-report-graph-line { + background-color: #22e; +} + +.statistics-report-table-events th { + padding: 0 1rem; +} + +.statistics-report-table-events td { + padding: 0.1rem 1rem; +} + +.statistics-report-row-year { + color: #ee0; + text-align: left; +} + +.statistics-report-week-number-label { + font-size: 0.8rem; + text-align: right; +} + +.statistics-report-week-of-year-list { + font-size: 0.8rem; +} + + +/************************************** + * Search + */ + +.searchResult .snippet mark { + color: #ee0; +} + + +/************************************** + * Sections + */ + +div.section, div.sectionmenu { + color: #2ee; + background-color: #22c; + border-radius: 0 3rem; + box-shadow: 2px 2px #000; + display: flex; + font-size: 1.1rem; + font-weight: bold; + justify-content: space-around; + margin: 1.2rem auto 0.75rem auto; + padding: 0.2rem; + text-align: center; +} + +div.sectionmenu { + border-radius: 0 0 3rem 3rem; + margin-top: -0.75rem; + width: 75%; +} + +div.sectionmenu > a:link, div.sectionmenu > a:visited { + color: #000; + text-decoration: none; +} + +div.sectionmenu > a:hover { + color: #eee; + text-shadow: 0px 0px 6px #eee; +} + + +/************************************** + * Sidebox + */ + +div.sidebox { + background-color: #333; + border-radius: 0.5rem; + box-shadow: 3px 3px 1px #000; + float: right; + margin: 1rem 0.5rem; + padding: 0.5rem; +} + +div.sidebox ol { + margin: 0 0 0.5rem 2.5rem; + padding: 0 0; +} + +div.sidebox ol li { + margin-top: 0.75rem; +} + +div.sideboxTitle { + background-color: #ee0; + border-radius: 0.5rem 0.5rem 0 0; + color: #000; + font-weight: bold; + margin: -0.5rem -0.5rem 0 -0.5rem; + padding: 0.25rem; + text-align: center; +} + +div.sideboxDescribed { + display: inline; +} + +/* --- Untested : Begin --- */ +/* The defined element in sideboxes for branches,.. */ +span.disabled { + color: #f00; +} +/* --- Untested : End --- */ + + +/************************************** + * Tag + */ + +/* --- Untested : Begin --- */ +/* the format for the tag links */ +a.tagLink { +} +/* the format for the tag display(no history permission!) */ +span.tagDsp { + font-weight: bold; +} +/* the format for fixed/canceled tags,.. */ +span.infoTagCancelled { + font-weight: bold; + text-decoration: line-through; +} +/* --- Untested : End --- */ + + +/************************************** + * Ticket + */ + +table.report { + color: #000; + border: 1px solid #999; + border-collapse: collapse; + margin: 1rem 0; +} + +table.report tr th { + color: #eee; + padding: 3px 5px; + text-transform : capitalize; +} + +table.report tr td { + padding: 3px 5px; +} + +/* example ticket colors */ +table.rpteditex { + border-collapse: collapse; + border-spacing: 0; + color: #000; + float: right; + margin: 0; + padding: 0; + text-align: center; + width: 125px; +} + +td.rpteditex { + border-color: #000; + border-style: solid; + border-width: thin; +} + +#reportTable { +} + +/* format for labels on ticket display page */ +td.tktDspLabel { + text-align: right; +} + +/* format for values on ticket display page */ +td.tktDspValue { + background-color: #111; + text-align: left; + vertical-align: top; +} + +/* format for ticket error messages */ +span.tktError { + color: #f00; + font-weight: bold; +} + + +/************************************** + * Timeline + */ + +#canvas { + color: #000; + background-color: #fff; +} + +div.divider { + color: #ee0; + font-size: 1.2rem; + font-weight: bold; + margin-top: 1rem; + white-space: nowrap; +} + +/* The suppressed duplicates lines in timeline, .. */ +span.timelineDisabled { + font-size: 0.5rem; + font-style: italic; +} + +/* the format for the timeline data table */ +table.timelineTable { + border: 0; +} + +/* The row in the timeline table that contains the entry of interest */ +tr.timelineSelected { + border: 1px solid #eee; + border-radius: 1rem; +} + +tr.timelineSelected td.timelineTime +, tr.timelineSelected td.timelineTableCell { + background-color: #333; + box-shadow: 2px 2px 1px #000; + padding: 0.5rem; +} + +tr.timelineSelected td.timelineTime { + border-radius: 1rem 0 0 1rem; +} + +tr.timelineSelected td.timelineTableCell { + border-radius: 0 1rem 1rem 0; +} + +/* the format for the timeline data cells */ +td.timelineTableCell { + padding: 0.3rem; + text-align: left; + vertical-align: top; +} + +td.timelineTableCell[style] { + color: #000; +} + +/* the format for the timeline data cell of the current checkout */ +tr.timelineCurrent td.timelineTableCell { + border: 0; + border-radius: 1em 0em; +} + +/* the format for the timeline leaf marks */ +span.timelineLeaf { + font-weight: bold; +} + +/* the format for the timeline version links */ +a.timelineHistLink { +} + +/* the format for the timeline version display(no history permission!) */ +span.timelineHistDsp { + font-weight: bold; +} + +/* the format for the timeline time display */ +td.timelineTime { + text-align: right; + vertical-align: top; + white-space: nowrap; +} + +/* the format for the grap placeholder cells in timelines */ +td.timelineGraph { + text-align: left; + vertical-align: top; + width: 20px; +} + + +/************************************** + * User Edit + */ + +/* layout definition for the capabilities box on the user edit detail page */ +div.ueditCapBox { + float: left; + margin: 0 20px 20px 0; +} + +/* format of the label cells in the detailed user edit page */ +td.usetupEditLabel { + text-align: right; + vertical-align: top; + white-space: nowrap; +} + +/* color for capabilities, inherited by nobody */ +span.ueditInheritNobody { + color: #0f0; +} + +/* color for capabilities, inherited by developer */ +span.ueditInheritDeveloper { + color: #f00; +} + +/* color for capabilities, inherited by reader */ +span.ueditInheritReader { + color: black; +} + +/* color for capabilities, inherited by anonymous */ +span.ueditInheritAnonymous { + color: #00f; +} + +/* format for capabilities */ +span.capability { + font-weight: bold; +} + +/* format for different user types */ +span.usertype { + font-weight: bold; +} + +span.usertype:before { + content:"'"; +} + +span.usertype:after { + content:"'"; +} + + +/************************************** + * User List + */ + +table.usetupLayoutTable { + margin: 0.5rem; + outline-style: none; + padding: 0; +} + +td.usetupColumnLayout { + vertical-align: top +} + +td.usetupColumnLayout ol th { + padding: 0 0.75rem 0.5rem 0; +} + +span.note { + color: #ee0; + font-weight: bold; +} + +table.usetupUserList { + margin: 0.5rem; +} + +.usetupListUser { + padding-right: 20px; + text-align: right; +} + +.usetupListCap { + padding-right: 15px; + text-align: center; +} + +.usetupListCon { + text-align: left; +} + + +/************************************** + * Wiki + */ + +span.wikiError { + font-weight: bold; + color: #f00; +} + +/* the format for fixed/cancelled tags */ +span.wikiTagCancelled { + text-decoration: line-through; +} + + +/************************************** + * Did not encounter these + */ + +/* selected lines of text within a linenumbered artifact display */ +div.selectedText { + font-weight: bold; + color: #00f; + background-color: #d5d5ff; + border: 1px #00f solid; +} + +/* format for missing privileges note on user setup page */ +p.missingPriv { + color: #00f; +} + +/* format for leading text in wikirules definitions */ +span.wikiruleHead { + font-weight: bold; +} + + +/* format for user color input on checkin edit page */ +input.checkinUserColor { + /* no special definitions, class defined, to enable color pickers, + * f.e.: + * ** add the color picker found at http:jscolor.com as java script + * include + * ** to the header and configure the java script file with + * ** 1. use as bindClass :checkinUserColor + * ** 2. change the default hash adding behaviour to ON + * ** or change the class defition of element identified by + * id="clrcust" + * ** to a standard jscolor definition with java script in the footer. + * */ +} + +/* format for end of content area, to be used to clear page flow. */ +div.endContent { + clear: both; +} + +/* format for general errors */ +p.generalError { + color: #f00; +} + +/* format for tktsetup errors */ +p.tktsetupError { + color: #f00; + font-weight: bold; +} +/* format for xfersetup errors */ +p.xfersetupError { + color: #f00; + font-weight: bold; +} +/* format for th script errors */ +p.thmainError { + color: #f00; + font-weight: bold; +} +/* format for th script trace messages */ +span.thTrace { + color: #f00; +} +/* format for report configuration errors */ +p.reportError { + color: #f00; + font-weight: bold; +} +/* format for report configuration errors */ +blockquote.reportError { + color: #f00; + font-weight: bold; +} +/* format for artifact lines, no longer shunned */ +p.noMoreShun { + color: #00f; +} +/* format for artifact lines beeing shunned */ +p.shunned { + color: #00f; +} +/* a broken hyperlink */ +span.brokenlink { + color: #f00; +} +/* List of files in a timeline */ +ul.filelist { + margin-top: 3px; + line-height: 100%; +} +/* Moderation Pending message on timeline */ +span.modpending { + color: #b30; + font-style: italic; +} +/* format for textarea labels */ +span.textareaLabel { + font-weight: bold; +} +/* format for th1 script results */ +pre.th1result { + white-space: pre-wrap; + word-wrap: break-word; +} +/* format for th1 script errors */ +pre.th1error { + white-space: pre-wrap; + word-wrap: break-word; + color: #f00; +} + +/* even table row color */ +tr.row0 { + /* use default */ +} +/* odd table row color */ +tr.row1 { + /* Use default */ +} + ADDED skins/xekri/details.txt Index: skins/xekri/details.txt ================================================================== --- /dev/null +++ skins/xekri/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 0 ADDED skins/xekri/footer.txt Index: skins/xekri/footer.txt ================================================================== --- /dev/null +++ skins/xekri/footer.txt @@ -0,0 +1,11 @@ +</div> +<div class="footer"> +<div class="page-time"> +Generated in <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s +</div> +<div class="fossil-info"> +Fossil v$release_version $manifest_version +</div> +</div> +</body> +</html> ADDED skins/xekri/header.txt Index: skins/xekri/header.txt ================================================================== --- /dev/null +++ skins/xekri/header.txt @@ -0,0 +1,142 @@ +<html> +<head> +<base href="$baseurl/$current_page" /> +<title>$<project_name>: $<title> + + + + +
+ +
$</div> + <div class="status"><nobr><th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></nobr><small><div id="clock"></div></small></div> +</div> +<script> +function updateClock(){ + var e = document.getElementById("clock"); + if(e){ + var d = new Date(); + function f(n) { + return n < 10 ? '0' + n : n; + } + e.innerHTML = d.getUTCFullYear()+ '-' + + f(d.getUTCMonth() + 1) + '-' + + f(d.getUTCDate()) + ' ' + + f(d.getUTCHours()) + ':' + + f(d.getUTCMinutes()); + setTimeout("updateClock();",(60-d.getUTCSeconds())*1000); + } +} +updateClock(); +</script> +<div class="mainmenu"> +<th1> +proc menulink {url name} { + upvar current_page current + upvar home home + if {[string range $url 0 [string length $current]] eq "/$current"} { + html "<a href='$home$url' class='active'>$name</a>\n" + } else { + html "<a href='$home$url'>$name</a>\n" + } +} +menulink $index_page Home +if {[anycap jor]} { + menulink /timeline Timeline +} +if {[anoncap oh]} { + menulink /dir?ci=tip Files +} +if {[anoncap o]} { + menulink /brlist Branches + menulink /taglist Tags +} +if {[anoncap r]} { + menulink /ticket Tickets +} +if {[anoncap j]} { + menulink /wiki Wiki +} +if {[hascap s]} { + menulink /setup Admin +} elseif {[hascap a]} { + menulink /setup_ulist Users +} +if {[info exists login]} { + menulink /login Logout +} else { + menulink /login Login +} +</th1></div> Index: src/add.c ================================================================== --- src/add.c +++ src/add.c @@ -25,11 +25,11 @@ #include "cygsup.h" /* ** This routine returns the names of files in a working checkout that ** are created by Fossil itself, and hence should not be added, deleted, -** or merge, and should be omitted from "clean" and "extra" lists. +** or merge, and should be omitted from "clean" and "extras" lists. ** ** Return the N-th name. The first name has N==0. When all names have ** been used, return 0. */ const char *fossil_reserved_name(int N, int omitRepo){ @@ -44,11 +44,11 @@ ".fslckout", ".fslckout-journal", ".fslckout-wal", ".fslckout-shm", - /* The use of ".fos" as the name of the checkout database is + /* The use of ".fos" as the name of the checkout database is ** deprecated. Use ".fslckout" instead. At some point, the following ** entries should be removed. 2012-02-04 */ ".fos", ".fos-journal", ".fos-wal", @@ -123,10 +123,14 @@ */ void test_reserved_names(void){ int i; const char *z; int omitRepo = find_option("omitrepo",0,0)!=0; + + /* We should be done with options.. */ + verify_all_options(); + db_must_be_within_tree(); for(i=0; (z = fossil_reserved_name(i, omitRepo))!=0; i++){ fossil_print("%3d: %s\n", i, z); } fossil_print("ALL: (%s)\n", fossil_all_reserved_names(omitRepo)); @@ -149,14 +153,15 @@ " WHERE pathname=%Q %s", zPath, filename_collation()) ){ db_multi_exec("UPDATE vfile SET deleted=0" " WHERE pathname=%Q %s", zPath, filename_collation()); }else{ char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath); + int isExe = file_wd_isexe(zFullname); db_multi_exec( "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink)" "VALUES(%d,0,0,0,%Q,%d,%d)", - vid, zPath, file_wd_isexe(zFullname), file_wd_islink(zFullname)); + vid, zPath, isExe, file_wd_islink(0)); fossil_free(zFullname); } if( db_changes() ){ fossil_print("ADDED %s\n", zPath); return 1; @@ -177,11 +182,11 @@ int i; /* Loop counter */ const char *zReserved; /* Name of a reserved file */ Blob repoName; /* Treename of the repository */ Stmt loop; /* SQL to loop over all files to add */ int (*xCmp)(const char*,const char*); - + if( !file_tree_name(g.zRepositoryName, &repoName, 0) ){ blob_zero(&repoName); zRepo = ""; }else{ zRepo = blob_str(&repoName); @@ -221,24 +226,29 @@ ** The --ignore and --clean options are comma-separate lists of glob patterns ** for files to be excluded. Example: '*.o,*.obj,*.exe' If the --ignore ** option does not appear on the command line then the "ignore-glob" setting ** is used. If the --clean option does not appear on the command line then ** the "clean-glob" setting is used. +** +** If files are attempted to be added explicitly on the command line which +** match "ignore-glob", a confirmation is asked first. This can be prevented +** using the -f|--force option. ** ** The --case-sensitive option determines whether or not filenames should ** be treated case sensitive or not. If the option is not given, the default ** depends on the global setting, or the operating system default, if not set. ** ** Options: ** -** --case-sensitive <BOOL> override case-sensitive setting -** --dotfiles include files beginning with a dot (".") -** --ignore <CSG> ignore files matching patterns from the +** --case-sensitive <BOOL> Override the case-sensitive setting. +** --dotfiles include files beginning with a dot (".") +** -f|--force Add files without prompting +** --ignore <CSG> Ignore files matching patterns from the ** comma separated list of glob patterns. -** --clean <CSG> also ignore files matching patterns from +** --clean <CSG> Also ignore files matching patterns from ** the comma separated list of glob patterns. -** +** ** See also: addremove, rm */ void add_cmd(void){ int i; /* Loop counter */ int vid; /* Currently checked out version */ @@ -245,38 +255,46 @@ int nRoot; /* Full path characters in g.zLocalRoot */ const char *zCleanFlag; /* The --clean option or clean-glob setting */ const char *zIgnoreFlag; /* The --ignore option or ignore-glob setting */ Glob *pIgnore, *pClean; /* Ignore everything matching the glob patterns */ unsigned scanFlags = 0; /* Flags passed to vfile_scan() */ + int forceFlag; zCleanFlag = find_option("clean",0,1); zIgnoreFlag = find_option("ignore",0,1); + forceFlag = find_option("force","f",0)!=0; if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL; - capture_case_sensitive_option(); + + /* We should be done with options.. */ + verify_all_options(); + db_must_be_within_tree(); if( zCleanFlag==0 ){ zCleanFlag = db_get("clean-glob", 0); } if( zIgnoreFlag==0 ){ zIgnoreFlag = db_get("ignore-glob", 0); } + if( db_get_boolean("dotfiles", 0) ) scanFlags |= SCAN_ALL; vid = db_lget_int("checkout",0); - if( vid==0 ){ - fossil_fatal("no checkout to add to"); - } db_begin_transaction(); db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)", filename_collation()); pClean = glob_create(zCleanFlag); pIgnore = glob_create(zIgnoreFlag); nRoot = strlen(g.zLocalRoot); - + /* Load the names of all files that are to be added into sfile temp table */ for(i=2; i<g.argc; i++){ char *zName; int isDir; Blob fullName; + + /* file_tree_name() throws a fatal error if g.argv[i] is outside of the + ** checkout. */ + file_tree_name(g.argv[i], &fullName, 1); + blob_reset(&fullName); file_canonical_name(g.argv[i], &fullName, 0); zName = blob_str(&fullName); isDir = file_wd_isdir(zName); if( isDir==1 ){ @@ -285,10 +303,25 @@ fossil_warning("not found: %s", zName); }else if( file_access(zName, R_OK) ){ fossil_fatal("cannot open %s", zName); }else{ char *zTreeName = &zName[nRoot]; + if( !forceFlag && glob_match(pIgnore, zTreeName) ){ + Blob ans; + char cReply; + char *prompt = mprintf("file \"%s\" matches \"ignore-glob\". " + "Add it (a=all/y/N)? ", zTreeName); + prompt_user(prompt, &ans); + cReply = blob_str(&ans)[0]; + blob_reset(&ans); + if( cReply=='a' || cReply=='A' ){ + forceFlag = 1; + }else if( cReply!='y' && cReply!='Y' ){ + blob_reset(&fullName); + continue; + } + } db_multi_exec( "INSERT OR IGNORE INTO sfile(x) VALUES(%Q)", zTreeName ); } @@ -301,37 +334,34 @@ db_end_transaction(0); } /* ** COMMAND: rm -** COMMAND: delete* +** COMMAND: delete +** COMMAND: forget* ** -** Usage: %fossil rm FILE1 ?FILE2 ...? -** or: %fossil delete FILE1 ?FILE2 ...? +** Usage: %fossil rm|delete|forget FILE1 ?FILE2 ...? ** ** Remove one or more files or directories from the repository. ** ** This command does NOT remove the files from disk. It just marks the ** files as no longer being part of the project. In other words, future ** changes to the named files will not be versioned. ** ** Options: -** --case-sensitive <BOOL> override case-sensitive setting +** --case-sensitive <BOOL> Override the case-sensitive setting. ** ** See also: addremove, add */ void delete_cmd(void){ int i; - int vid; Stmt loop; - capture_case_sensitive_option(); + /* We should be done with options.. */ + verify_all_options(); + db_must_be_within_tree(); - vid = db_lget_int("checkout", 0); - if( vid==0 ){ - fossil_fatal("no checkout to remove from"); - } db_begin_transaction(); db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)", filename_collation()); for(i=2; i<g.argc; i++){ Blob treeName; @@ -348,11 +378,11 @@ zTreeName, filename_collation(), zTreeName, filename_collation(), zTreeName, filename_collation() ); blob_reset(&treeName); } - + db_prepare(&loop, "SELECT x FROM sfile"); while( db_step(&loop)==SQLITE_ROW ){ fossil_print("DELETED %s\n", db_column_text(&loop, 0)); } db_finalize(&loop); @@ -414,12 +444,13 @@ #endif caseSensitive = db_get_boolean("case-sensitive",caseSensitive); } if( !caseSensitive && g.localOpen ){ db_multi_exec( - "CREATE INDEX IF NOT EXISTS vfile_nocase" - " ON vfile(pathname COLLATE nocase)" + "CREATE INDEX IF NOT EXISTS %s.vfile_nocase" + " ON vfile(pathname COLLATE nocase)", + db_name("localdb") ); } } return caseSensitive; } @@ -442,11 +473,11 @@ ** ** Do all necessary "add" and "rm" commands to synchronize the repository ** with the content of the working checkout: ** ** * All files in the checkout but not in the repository (that is, -** all files displayed using the "extra" command) are added as +** all files displayed using the "extras" command) are added as ** if by the "add" command. ** ** * All files in the repository but missing from the checkout (that is, ** all files that show as MISSING with the "status" command) are ** removed as if by the "rm" command. @@ -460,22 +491,23 @@ ** The --ignore option overrides the "ignore-glob" setting, as do the ** --case-sensitive option with the "case-sensitive" setting and the ** --clean option with the "clean-glob" setting. See the documentation ** on the "settings" command for further information. ** -** The -n|--dry-run option shows what would happen without actually doing anything. +** The -n|--dry-run option shows what would happen without actually doing +** anything. ** ** This command can be used to track third party software. -** -** Options: -** --case-sensitive <BOOL> override case-sensitive setting -** --dotfiles include files beginning with a dot (".") -** --ignore <CSG> ignore files matching patterns from the +** +** Options: +** --case-sensitive <BOOL> Override the case-sensitive setting. +** --dotfiles Include files beginning with a dot (".") +** --ignore <CSG> Ignore files matching patterns from the ** comma separated list of glob patterns. -** --clean <CSG> also ignore files matching patterns from +** --clean <CSG> Also ignore files matching patterns from ** the comma separated list of glob patterns. -** -n|--dry-run If given, display instead of run actions +** -n|--dry-run If given, display instead of run actions. ** ** See also: add, rm */ void addremove_cmd(void){ Blob path; @@ -491,25 +523,26 @@ Glob *pIgnore, *pClean; if( !dryRunFlag ){ dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ } - capture_case_sensitive_option(); + + /* We should be done with options.. */ + verify_all_options(); + db_must_be_within_tree(); if( zCleanFlag==0 ){ zCleanFlag = db_get("clean-glob", 0); } if( zIgnoreFlag==0 ){ zIgnoreFlag = db_get("ignore-glob", 0); } + if( db_get_boolean("dotfiles", 0) ) scanFlags |= SCAN_ALL; vid = db_lget_int("checkout",0); - if( vid==0 ){ - fossil_fatal("no checkout to add to"); - } db_begin_transaction(); - /* step 1: + /* step 1: ** Populate the temp table "sfile" with the names of all unmanaged ** files currently in the check-out, except for files that match the ** --ignore or ignore-glob patterns and dot-files. Then add all of ** the files in the sfile temp table to the set of managed files. */ @@ -531,12 +564,12 @@ " WHERE NOT deleted" " ORDER BY 1", g.zLocalRoot ); while( db_step(&q)==SQLITE_ROW ){ - const char * zFile; - const char * zPath; + const char *zFile; + const char *zPath; zFile = db_column_text(&q, 0); zPath = db_column_text(&q, 1); if( !file_wd_isfile_or_link(zPath) ){ if( !dryRunFlag ){ @@ -557,17 +590,21 @@ /* ** Rename a single file. ** ** The original name of the file is zOrig. The new filename is zNew. */ -static void mv_one_file(int vid, const char *zOrig, const char *zNew){ +static void mv_one_file( + int vid, + const char *zOrig, + const char *zNew +){ int x = db_int(-1, "SELECT deleted FROM vfile WHERE pathname=%Q %s", - zNew, filename_collation()); + zNew, filename_collation()); if( x>=0 ){ if( x==0 ){ fossil_fatal("cannot rename '%s' to '%s' since another file named '%s'" - " is currently under management", zOrig, zNew, zNew); + " is currently under management", zOrig, zNew, zNew); }else{ fossil_fatal("cannot rename '%s' to '%s' since the delete of '%s' has " "not yet been committed", zOrig, zNew, zNew); } } @@ -588,14 +625,14 @@ ** Move or rename one or more files or directories within the repository tree. ** You can either rename a file or directory or move it to another subdirectory. ** ** This command does NOT rename or move the files on disk. This command merely ** records the fact that filenames have changed so that appropriate notations -** can be made at the next commit/checkin. +** can be made at the next commit/check-in. ** ** Options: -** --case-sensitive <BOOL> override case-sensitive setting +** --case-sensitive <BOOL> Override the case-sensitive setting. ** ** See also: changes, status */ void mv_cmd(void){ int i; @@ -602,12 +639,15 @@ int vid; char *zDest; Blob dest; Stmt q; - capture_case_sensitive_option(); db_must_be_within_tree(); + + /* We should be done with options.. */ + verify_all_options(); + vid = db_lget_int("checkout", 0); if( vid==0 ){ fossil_fatal("no checkout rename files in"); } if( g.argc<4 ){ @@ -676,5 +716,13 @@ mv_one_file(vid, zFrom, zTo); } db_finalize(&q); db_end_transaction(0); } + +/* +** Function for stash_apply to be able to restore a file and indicate +** newly ADDED state. +*/ +int stash_add_files_in_sfile(int vid){ + return add_files_in_sfile(vid); +} Index: src/allrepo.c ================================================================== --- src/allrepo.c +++ src/allrepo.c @@ -58,19 +58,29 @@ } } static void collect_argument_value(Blob *pExtra, const char *zArg){ const char *zValue = find_option(zArg, 0, 1); if( zValue ){ - blob_appendf(pExtra, " --%s %s", zArg, zValue); + if( zValue[0] ){ + blob_appendf(pExtra, " --%s %s", zArg, zValue); + }else{ + blob_appendf(pExtra, " --%s \"\"", zArg); + } + } +} +static void collect_argv(Blob *pExtra, int iStart){ + int i; + for(i=iStart; i<g.argc; i++){ + blob_appendf(pExtra, " %s", g.argv[i]); } } /* ** COMMAND: all ** -** Usage: %fossil all (changes|ignore|list|ls|pull|push|rebuild|sync) +** Usage: %fossil all SUBCOMMAND ... ** ** The ~/.fossil file records the location of all repositories for a ** user. This command performs certain operations on all repositories ** that can be useful before or after a period of disconnected operation. ** @@ -77,33 +87,75 @@ ** On Win32 systems, the file is named "_fossil" and is located in ** %LOCALAPPDATA%, %APPDATA% or %HOMEPATH%. ** ** Available operations are: ** -** changes Shows all local checkouts that have uncommitted changes -** -** ignore Arguments are repositories that should be ignored -** by subsequent list, pull, push, rebuild, and sync. -** The -c|--ckout option causes the listed local checkouts -** to be ignored instead. -** -** list | ls Display the location of all repositories. -** The -c|--ckout option causes all local checkouts to be -** list instead. -** -** pull Run a "pull" operation on all repositories -** -** push Run a "push" on all repositories -** -** rebuild Rebuild on all repositories -** -** sync Run a "sync" on all repositories +** changes Shows all local checkouts that have uncommitted changes. +** This operation has no additional options. +** +** clean Delete all "extra" files in all local checkouts. Extreme +** caution should be exercised with this command because its +** effects cannot be undone. Use of the --dry-run option to +** carefully review the local checkouts to be operated upon +** and the --whatif option to carefully review the files to +** be deleted beforehand is highly recommended. The command +** line options supported by the clean command itself, if any +** are present, are passed along verbatim. +** +** dbstat Run the "dbstat" command on all repositories. +** +** extras Shows "extra" files from all local checkouts. The command +** line options supported by the extra command itself, if any +** are present, are passed along verbatim. +** +** fts-config Run the "fts-config" command on all repositories. +** +** info Run the "info" command on all repositories. +** +** pull Run a "pull" operation on all repositories. Only the +** --verbose option is supported. +** +** push Run a "push" on all repositories. Only the --verbose +** option is supported. +** +** rebuild Rebuild on all repositories. The command line options +** supported by the rebuild command itself, if any are +** present, are passed along verbatim. The --force and +** --randomize options are not supported. +** +** sync Run a "sync" on all repositories. Only the --verbose +** option is supported. +** +** setting Run the "setting", "set", or "unset" commands on all +** set repositories. These command are particularly useful in +** unset conjunction with the "max-loadavg" setting which cannot +** otherwise be set globally. +** +** In addition, the following maintenance operations are supported: +** +** add Add all the repositories named to the set of repositories +** tracked by Fossil. Normally Fossil is able to keep up with +** this list by itself, but sometime it can benefit from this +** hint if you rename repositories. +** +** ignore Arguments are repositories that should be ignored by +** subsequent clean, extras, list, pull, push, rebuild, and +** sync operations. The -c|--ckout option causes the listed +** local checkouts to be ignored instead. +** +** list | ls Display the location of all repositories. The -c|--ckout +** option causes all local checkouts to be listed instead. ** ** Repositories are automatically added to the set of known repositories -** when one of the following commands are run against the repository: clone, -** info, pull, push, or sync. Even previously ignored repositories are -** added back to the list of repositories by these commands. +** when one of the following commands are run against the repository: +** clone, info, pull, push, or sync. Even previously ignored repositories +** are added back to the list of repositories by these commands. +** +** Options: +** --showfile Show the repository or checkout being operated upon. +** --dontstop Continue with other repositories even after an error. +** --dry-run If given, display instead of run actions. */ void all_cmd(void){ int n; Stmt q; const char *zCmd; @@ -112,30 +164,70 @@ char *zQFilename; Blob extra; int useCheckouts = 0; int quiet = 0; int dryRunFlag = 0; + int showFile = find_option("showfile",0,0)!=0; int stopOnError = find_option("dontstop",0,0)==0; int rc; - Bag outOfDate; - + int rowCount, i = 0; + char **azFilename = 0; + char **azTag = 0; + int nToDel = 0; + int showLabel = 0; + dryRunFlag = find_option("dry-run","n",0)!=0; if( !dryRunFlag ){ dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ } if( g.argc<3 ){ - usage("changes|ignore|list|ls|pull|push|rebuild|sync"); + usage("SUBCOMMAND ..."); } n = strlen(g.argv[2]); db_open_config(1); blob_zero(&extra); zCmd = g.argv[2]; - if( g.zLogin ) blob_appendf(&extra, " -U %s", g.zLogin); + if( !login_is_nobody() ) blob_appendf(&extra, " -U %s", g.zLogin); if( strncmp(zCmd, "list", n)==0 || strncmp(zCmd,"ls",n)==0 ){ zCmd = "list"; useCheckouts = find_option("ckout","c",0)!=0; + }else if( strncmp(zCmd, "clean", n)==0 ){ + zCmd = "clean --chdir"; + collect_argument(&extra, "allckouts",0); + collect_argument_value(&extra, "case-sensitive"); + collect_argument_value(&extra, "clean"); + collect_argument(&extra, "dirsonly",0); + collect_argument(&extra, "dotfiles",0); + collect_argument(&extra, "emptydirs",0); + collect_argument(&extra, "force","f"); + collect_argument_value(&extra, "ignore"); + collect_argument_value(&extra, "keep"); + collect_argument(&extra, "temp",0); + collect_argument(&extra, "verbose","v"); + collect_argument(&extra, "whatif",0); + useCheckouts = 1; + }else if( strncmp(zCmd, "dbstat", n)==0 ){ + zCmd = "dbstat --omit-version-info -R"; + showLabel = 1; + quiet = 1; + collect_argument(&extra, "brief", "b"); + collect_argument(&extra, "db-check", 0); + }else if( strncmp(zCmd, "extras", n)==0 ){ + if( showFile ){ + zCmd = "extras --chdir"; + }else{ + zCmd = "extras --header --chdir"; + } + collect_argument(&extra, "abs-paths",0); + collect_argument_value(&extra, "case-sensitive"); + collect_argument(&extra, "dotfiles",0); + collect_argument_value(&extra, "ignore"); + collect_argument(&extra, "rel-paths",0); + useCheckouts = 1; + stopOnError = 0; + quiet = 1; }else if( strncmp(zCmd, "push", n)==0 ){ zCmd = "push -autourl -R"; collect_argument(&extra, "verbose","v"); }else if( strncmp(zCmd, "pull", n)==0 ){ zCmd = "pull -autourl -R"; @@ -149,10 +241,22 @@ collect_argument(&extra, "vacuum",0); collect_argument(&extra, "deanalyze",0); collect_argument(&extra, "analyze",0); collect_argument(&extra, "wal",0); collect_argument(&extra, "stats",0); + collect_argument(&extra, "index",0); + collect_argument(&extra, "noindex",0); + collect_argument(&extra, "ifneeded", 0); + }else if( strncmp(zCmd, "setting", n)==0 ){ + zCmd = "setting -R"; + collect_argv(&extra, 3); + }else if( strncmp(zCmd, "unset", n)==0 ){ + zCmd = "unset -R"; + collect_argv(&extra, 3); + }else if( strncmp(zCmd, "fts-config", n)==0 ){ + zCmd = "fts-config -R"; + collect_argv(&extra, 3); }else if( strncmp(zCmd, "sync", n)==0 ){ zCmd = "sync -autourl -R"; collect_argument(&extra, "verbose","v"); }else if( strncmp(zCmd, "test-integrity", n)==0 ){ collect_argument(&extra, "parse", 0); @@ -167,66 +271,118 @@ useCheckouts = 1; stopOnError = 0; quiet = 1; }else if( strncmp(zCmd, "ignore", n)==0 ){ int j; + Blob fn = BLOB_INITIALIZER; + Blob sql = BLOB_INITIALIZER; useCheckouts = find_option("ckout","c",0)!=0; verify_all_options(); db_begin_transaction(); - for(j=3; j<g.argc; j++){ - char *zSql = mprintf("DELETE FROM global_config" - " WHERE name GLOB '%s:%q'", - useCheckouts?"ckout":"repo", g.argv[j]); - if( dryRunFlag ){ - fossil_print("%s\n", zSql); - }else{ - db_multi_exec("%s", zSql); - } - fossil_free(zSql); + for(j=3; j<g.argc; j++, blob_reset(&sql), blob_reset(&fn)){ + file_canonical_name(g.argv[j], &fn, 0); + blob_append_sql(&sql, + "DELETE FROM global_config WHERE name GLOB '%s:%q'", + useCheckouts?"ckout":"repo", blob_str(&fn) + ); + if( dryRunFlag ){ + fossil_print("%s\n", blob_sql_text(&sql)); + }else{ + db_multi_exec("%s", blob_sql_text(&sql)); + } + } + db_end_transaction(0); + return; + }else if( strncmp(zCmd, "add", n)==0 ){ + int j; + Blob fn = BLOB_INITIALIZER; + Blob sql = BLOB_INITIALIZER; + verify_all_options(); + db_begin_transaction(); + for(j=3; j<g.argc; j++, blob_reset(&fn), blob_reset(&sql)){ + sqlite3 *db; + int rc; + const char *z; + file_canonical_name(g.argv[j], &fn, 0); + z = blob_str(&fn); + if( !file_isfile(z) ) continue; + rc = sqlite3_open(z, &db); + if( rc!=SQLITE_OK ){ sqlite3_close(db); continue; } + rc = sqlite3_exec(db, "SELECT rcvid FROM blob, delta LIMIT 1", 0, 0, 0); + sqlite3_close(db); + if( rc!=SQLITE_OK ) continue; + blob_append_sql(&sql, + "INSERT INTO global_config(name,value)VALUES('repo:%q',1)", z + ); + if( dryRunFlag ){ + fossil_print("%s\n", blob_sql_text(&sql)); + }else{ + db_multi_exec("%s", blob_sql_text(&sql)); + } } db_end_transaction(0); return; + }else if( strncmp(zCmd, "info", n)==0 ){ + zCmd = "info"; + showLabel = 1; + quiet = 1; }else{ fossil_fatal("\"all\" subcommand should be one of: " - "changes ignore list ls push pull rebuild sync"); + "add changes clean dbstat extras fts-config ignore " + "info list ls pull push rebuild setting sync unset"); } verify_all_options(); zFossil = quoteFilename(g.nameOfExe); - if( useCheckouts ){ - db_prepare(&q, - "SELECT substr(name, 7) COLLATE nocase, max(rowid)" - " FROM global_config" - " WHERE substr(name, 1, 6)=='ckout:'" - " GROUP BY 1 ORDER BY 1" - ); - }else{ - db_prepare(&q, - "SELECT substr(name, 6) COLLATE nocase, max(rowid)" - " FROM global_config" - " WHERE substr(name, 1, 5)=='repo:'" - " GROUP BY 1 ORDER BY 1" - ); - } - bag_init(&outOfDate); - while( db_step(&q)==SQLITE_ROW ){ - const char *zFilename = db_column_text(&q, 0); - int rowid = db_column_int(&q, 1); - if( file_access(zFilename, 0) || !file_is_canonical(zFilename) ){ - bag_insert(&outOfDate, rowid); - continue; - } - if( useCheckouts && file_isdir(zFilename)!=1 ){ - bag_insert(&outOfDate, rowid); - continue; + db_multi_exec("CREATE TEMP TABLE repolist(name,tag);"); + if( useCheckouts ){ + db_multi_exec( + "INSERT INTO repolist " + "SELECT DISTINCT substr(name, 7), name COLLATE nocase" + " FROM global_config" + " WHERE substr(name, 1, 6)=='ckout:'" + " ORDER BY 1" + ); + }else{ + db_multi_exec( + "INSERT INTO repolist " + "SELECT DISTINCT substr(name, 6), name COLLATE nocase" + " FROM global_config" + " WHERE substr(name, 1, 5)=='repo:'" + " ORDER BY 1" + ); + } + db_prepare(&q, "SELECT name, tag FROM repolist ORDER BY 1"); + rowCount = db_all_column_text(&q, 0, &azFilename, 1, &azTag); + db_finalize(&q); + db_multi_exec("CREATE TEMP TABLE todel(x TEXT)"); + while( i<rowCount ){ + const char *zFilename = azFilename[i]; + const char *zTag = azTag[i]; + if( file_access(zFilename, F_OK) + || !file_is_canonical(zFilename) + || (useCheckouts && file_isdir(zFilename)!=1) + ){ + db_multi_exec("INSERT INTO todel VALUES(%Q)", zTag); + nToDel++; + i++; continue; } if( zCmd[0]=='l' ){ fossil_print("%s\n", zFilename); - continue; + i++; continue; + }else if( showFile ){ + fossil_print("%s: %s\n", useCheckouts ? "checkout" : "repository", + zFilename); } zQFilename = quoteFilename(zFilename); zSyscmd = mprintf("%s %s %s%s", zFossil, zCmd, zQFilename, blob_str(&extra)); + if( showLabel ){ + int len = (int)strlen(zFilename); + int nStar = 80 - (len + 15); + if( nStar<2 ) nStar = 1; + fossil_print("%.13c %s %.*c\n", '*', zFilename, nStar, '*'); + } if( !quiet || dryRunFlag ){ fossil_print("%s\n", zSyscmd); fflush(stdout); } rc = dryRunFlag ? 0 : fossil_system(zSyscmd); @@ -233,30 +389,24 @@ free(zSyscmd); free(zQFilename); if( stopOnError && rc ){ break; } + i++; } - db_finalize(&q); - + db_all_column_free(rowCount, &azFilename); + db_all_column_free(rowCount, &azTag); + assert( !azFilename ); + assert( !azTag ); + /* If any repositories whose names appear in the ~/.fossil file could not ** be found, remove those names from the ~/.fossil file. */ - if( bag_count(&outOfDate)>0 ){ - Blob sql; - char *zSep = "("; - int rowid; - blob_zero(&sql); - blob_appendf(&sql, "DELETE FROM global_config WHERE rowid IN "); - for(rowid=bag_first(&outOfDate); rowid>0; rowid=bag_next(&outOfDate,rowid)){ - blob_appendf(&sql, "%s%d", zSep, rowid); - zSep = ","; - } - blob_appendf(&sql, ")"); + if( nToDel>0 ){ + const char *zSql = "DELETE FROM global_config WHERE name IN toDel"; if( dryRunFlag ){ - fossil_print("%s\n", blob_str(&sql)); + fossil_print("%s\n", zSql); }else{ - db_multi_exec(blob_str(&sql)); + db_multi_exec("%s", zSql /*safe-for-%s*/ ); } - blob_reset(&sql); } } Index: src/attach.c ================================================================== --- src/attach.c +++ src/attach.c @@ -29,11 +29,11 @@ ** ** List attachments. ** Either one of tkt= or page= are supplied or neither. If neither ** are given, all attachments are listed. If one is given, only ** attachments for the designated ticket or wiki page are shown. -** TICKETUUID must be complete +** TICKETUUID must be complete */ void attachlist_page(void){ const char *zPage = P("page"); const char *zTkt = P("tkt"); Blob sql; @@ -40,31 +40,34 @@ Stmt q; if( zPage && zTkt ) zTkt = 0; login_check_credentials(); blob_zero(&sql); - blob_append(&sql, - "SELECT datetime(mtime,'localtime'), src, target, filename," + blob_append_sql(&sql, + "SELECT datetime(mtime%s), src, target, filename," " comment, user," " (SELECT uuid FROM blob WHERE rid=attachid), attachid" " FROM attachment", - -1 + timeline_utc() ); if( zPage ){ - if( g.perm.RdWiki==0 ) login_needed(); + if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } style_header("Attachments To %h", zPage); - blob_appendf(&sql, " WHERE target=%Q", zPage); + blob_append_sql(&sql, " WHERE target=%Q", zPage); }else if( zTkt ){ - if( g.perm.RdTkt==0 ) login_needed(); - style_header("Attachments To Ticket %.10s", zTkt); - blob_appendf(&sql, " WHERE target GLOB '%q*'", zTkt); + if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; } + style_header("Attachments To Ticket %S", zTkt); + blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt); }else{ - if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ) login_needed(); + if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){ + login_needed(g.anon.RdTkt || g.anon.RdWiki); + return; + } style_header("All Attachments"); } - blob_appendf(&sql, " ORDER BY mtime DESC"); - db_prepare(&q, "%s", blob_str(&sql)); + blob_append_sql(&sql, " ORDER BY mtime DESC"); + db_prepare(&q, "%s", blob_sql_text(&sql)); @ <ol> while( db_step(&q)==SQLITE_ROW ){ const char *zDate = db_column_text(&q, 0); const char *zSrc = db_column_text(&q, 1); const char *zTarget = db_column_text(&q, 2); @@ -75,11 +78,11 @@ int attachid = db_column_int(&q, 7); const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; int i; char *zUrlTail; for(i=0; zFilename[i]; i++){ - if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ + if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ zFilename = &zFilename[i+1]; i = -1; } } if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ @@ -86,31 +89,31 @@ zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); }else{ zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename); } @ <li><p> - @ Attachment %z(href("%R/ainfo/%s",zUuid))%S(zUuid)</a> + @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a> if( moderation_pending(attachid) ){ @ <span class="modpending">*** Awaiting Moderator Approval ***</span> } - @ <br><a href="/attachview?%s(zUrlTail)">%h(zFilename)</a> - @ [<a href="/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br /> + @ <br><a href="%R/attachview?%s(zUrlTail)">%h(zFilename)</a> + @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br /> if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++; if( zComment && zComment[0] ){ - @ %!w(zComment)<br /> + @ %!W(zComment)<br /> } if( zPage==0 && zTkt==0 ){ if( zSrc==0 || zSrc[0]==0 ){ zSrc = "Deleted from"; }else { zSrc = "Added to"; } if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){ - @ %s(zSrc) ticket <a href="%s(g.zTop)/tktview?name=%s(zTarget)"> + @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)"> @ %S(zTarget)</a> }else{ - @ %s(zSrc) wiki page <a href="%s(g.zTop)/wiki?name=%t(zTarget)"> + @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)"> @ %h(zTarget)</a> } }else{ if( zSrc==0 || zSrc[0]==0 ){ @ Deleted @@ -150,14 +153,14 @@ if( zPage && zTkt ) zTkt = 0; if( zFile==0 ) fossil_redirect_home(); login_check_credentials(); if( zPage ){ - if( g.perm.RdWiki==0 ) login_needed(); + if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } zTarget = zPage; }else if( zTkt ){ - if( g.perm.RdTkt==0 ) login_needed(); + if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; } zTarget = zTkt; }else{ fossil_redirect_home(); } if( attachid>0 ){ @@ -214,11 +217,11 @@ }else{ rid = content_put(pAttach); db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid); db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid); } - manifest_crosslink(rid, pAttach); + manifest_crosslink(rid, pAttach, MC_NONE); } /* ** WEBPAGE: attachadd @@ -243,27 +246,33 @@ if( P("cancel") ) cgi_redirect(zFrom); if( zPage && zTkt ) fossil_redirect_home(); if( zPage==0 && zTkt==0 ) fossil_redirect_home(); login_check_credentials(); if( zPage ){ - if( g.perm.ApndWiki==0 || g.perm.Attach==0 ) login_needed(); + if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){ + login_needed(g.anon.ApndWiki && g.anon.Attach); + return; + } if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){ fossil_redirect_home(); } zTarget = zPage; - zTargetType = mprintf("Wiki Page <a href=\"%s/wiki?name=%h\">%h</a>", - g.zTop, zPage, zPage); + zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>", + zPage, zPage); }else{ - if( g.perm.ApndTkt==0 || g.perm.Attach==0 ) login_needed(); + if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){ + login_needed(g.anon.ApndTkt && g.anon.Attach); + return; + } if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){ - zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag" + zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag" " WHERE tagname GLOB 'tkt-%q*'", zTkt); if( zTkt==0 ) fossil_redirect_home(); } zTarget = zTkt; - zTargetType = mprintf("Ticket <a href=\"%s/tktview/%S\">%S</a>", - g.zTop, zTkt, zTkt); + zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>", + zTkt, zTkt); } if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop); if( P("cancel") ){ cgi_redirect(zFrom); } @@ -288,12 +297,12 @@ if( pManifest ){ blob_compress(&content, &content); addCompress = 1; } needModerator = - (zTkt!=0 && g.perm.ModTkt==0 && db_get_boolean("modreq-tkt",0)==1) || - (zPage!=0 && g.perm.ModWiki==0 && db_get_boolean("modreq-wiki",0)==1); + (zTkt!=0 && ticket_need_moderation(0)) || + (zPage!=0 && wiki_need_moderation(0)); rid = content_put_ex(&content, 0, 0, 0, needModerator); zUUID = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); blob_zero(&manifest); for(i=n=0; zName[i]; i++){ if( zName[i]=='/' || zName[i]=='\\' ) n = i; @@ -309,11 +318,11 @@ if( n>0 ){ blob_appendf(&manifest, "C %#F\n", n, zComment); } zDate = date_in_standard_format("now"); blob_appendf(&manifest, "D %s\n", zDate); - blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody"); + blob_appendf(&manifest, "U %F\n", login_name()); md5sum_blob(&manifest, &cksum); blob_appendf(&manifest, "Z %b\n", &cksum); attach_put(&manifest, rid, needModerator); assert( blob_is_reset(&manifest) ); db_end_transaction(0); @@ -365,21 +374,26 @@ int modPending; /* True if awaiting moderation */ const char *zModAction; /* Moderation action or NULL */ int isModerator; /* TRUE if user is the moderator */ const char *zMime; /* MIME Type */ Blob attach; /* Content of the attachment */ + int fShowContent = 0; + const char *zLn = P("ln"); login_check_credentials(); - if( !g.perm.RdTkt && !g.perm.RdWiki ){ login_needed(); return; } + if( !g.perm.RdTkt && !g.perm.RdWiki ){ + login_needed(g.anon.RdTkt || g.anon.RdWiki); + return; + } rid = name_to_rid_www("name"); if( rid==0 ){ fossil_redirect_home(); } zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); #if 0 /* Shunning here needs to get both the attachment control artifact and ** the object that is attached. */ if( g.perm.Admin ){ - if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ + if( db_exists("SELECT 1 FROM shun WHERE uuid='%q'", zUuid) ){ style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", g.zTop, zUuid); }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); @@ -388,24 +402,26 @@ #endif pAttach = manifest_get(rid, CFTYPE_ATTACHMENT, 0); if( pAttach==0 ) fossil_redirect_home(); zTarget = pAttach->zAttachTarget; zSrc = pAttach->zAttachSrc; - ridSrc = db_int(0,"SELECT rid FROM blob WHERE uuid='%s'", zSrc); + ridSrc = db_int(0,"SELECT rid FROM blob WHERE uuid='%q'", zSrc); zName = pAttach->zAttachName; zDesc = pAttach->zComment; + zMime = mimetype_from_name(zName); + fShowContent = zMime ? strncmp(zMime,"text/", 5)==0 : 0; if( validate16(zTarget, strlen(zTarget)) - && db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%s'", zTarget) + && db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%q'", zTarget) ){ zTktUuid = zTarget; - if( !g.perm.RdTkt ){ login_needed(); return; } + if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; } if( g.perm.WrTkt ){ style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid); } }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){ zWikiName = zTarget; - if( !g.perm.RdWiki ){ login_needed(); return; } + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } if( g.perm.WrWiki ){ style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid); } } zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate); @@ -427,36 +443,36 @@ zFile += n; if( zFile[0]==0 ) zFile = "unknown"; blob_appendf(&manifest, "A %F %F\n", zFile, zTarget); zDate = date_in_standard_format("now"); blob_appendf(&manifest, "D %s\n", zDate); - blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody"); + blob_appendf(&manifest, "U %F\n", login_name()); md5sum_blob(&manifest, &cksum); blob_appendf(&manifest, "Z %b\n", &cksum); rid = content_put(&manifest); - manifest_crosslink(rid, &manifest); + manifest_crosslink(rid, &manifest, MC_NONE); db_end_transaction(0); @ <p>The attachment below has been deleted.</p> } if( P("del") && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki)) ){ - form_begin(0, "%R/ainfo/%s", zUuid); + form_begin(0, "%R/ainfo/%!S", zUuid); @ <p>Confirm you want to delete the attachment shown below. @ <input type="submit" name="confirm" value="Confirm"> @ </form> } - isModerator = g.perm.Admin || + isModerator = g.perm.Admin || (zTktUuid && g.perm.ModTkt) || (zWikiName && g.perm.ModWiki); if( isModerator && (zModAction = P("modaction"))!=0 ){ if( strcmp(zModAction,"delete")==0 ){ moderation_disapprove(rid); if( zTktUuid ){ - cgi_redirectf("%R/tktview/%s", zTktUuid); + cgi_redirectf("%R/tktview/%!S", zTktUuid); }else{ cgi_redirectf("%R/wiki?name=%t", zWikiName); } return; } @@ -463,16 +479,21 @@ if( strcmp(zModAction,"approve")==0 ){ moderation_approve(rid); } } style_header("Attachment Details"); - style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid); + style_submenu_element("Raw", "Raw", "%R/artifact/%s", zUuid); + if(fShowContent){ + style_submenu_element("Line Numbers", "Line Numbers", + "%R/ainfo/%s%s",zUuid, + ((zLn&&*zLn) ? "" : "?ln=0")); + } @ <div class="section">Overview</div> @ <p><table class="label-value"> @ <tr><th>Artifact ID:</th> - @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a> + @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a> if( g.perm.Setup ){ @ (%d(rid)) } modPending = moderation_pending(rid); if( modPending ){ @@ -494,17 +515,16 @@ @ <td>%z(href("%R/artifact/%s",zSrc))%s(zSrc)</a> if( g.perm.Setup ){ @ (%d(ridSrc)) } @ <tr><th>Filename:</th><td>%h(zName)</td></tr> - zMime = mimetype_from_name(zName); if( g.perm.Setup ){ @ <tr><th>MIME-Type:</th><td>%h(zMime)</td></tr> } @ <tr><th valign="top">Description:</th><td valign="top">%h(zDesc)</td></tr> @ </table> - + if( isModerator && modPending ){ @ <div class="section">Moderation</div> @ <blockquote> form_begin(0, "%R/ainfo/%s", zUuid); @ <label><input type="radio" name="modaction" value="delete"> @@ -517,13 +537,12 @@ } @ <div class="section">Content Appended</div> @ <blockquote> blob_zero(&attach); - if( zMime==0 || strncmp(zMime,"text/", 5)==0 ){ + if( fShowContent ){ const char *z; - const char *zLn = P("ln"); content_get(ridSrc, &attach); blob_to_utf8_no_bom(&attach, 0); z = blob_str(&attach); if( zLn ){ output_text_with_line_numbers(z, zLn); @@ -531,12 +550,12 @@ @ <pre> @ %h(z) @ </pre> } }else if( strncmp(zMime, "image/", 6)==0 ){ - @ <img src="%R/raw/%S(zSrc)?m=%s(zMime)"></img> - style_submenu_element("Image", "Image", "%R/raw/%S?m=%s", zSrc, zMime); + @ <img src="%R/raw/%s(zSrc)?m=%s(zMime)"></img> + style_submenu_element("Image", "Image", "%R/raw/%s?m=%s", zSrc, zMime); }else{ int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc); @ <i>(file is %d(sz) bytes of binary data)</i> } @ </blockquote> @@ -553,16 +572,16 @@ const char *zHeader /* Header to display with attachments */ ){ int cnt = 0; Stmt q; db_prepare(&q, - "SELECT datetime(mtime,'localtime'), filename, user," + "SELECT datetime(mtime%s), filename, user," " (SELECT uuid FROM blob WHERE rid=attachid), src" " FROM attachment" " WHERE isLatest AND src!='' AND target=%Q" - " ORDER BY mtime DESC", - zTarget + " ORDER BY mtime DESC", + timeline_utc(), zTarget ); while( db_step(&q)==SQLITE_ROW ){ const char *zDate = db_column_text(&q, 0); const char *zFile = db_column_text(&q, 1); const char *zUser = db_column_text(&q, 2); @@ -572,17 +591,17 @@ if( cnt==0 ){ @ %s(zHeader) } cnt++; @ <li> - @ %z(href("%R/artifact/%s",zSrc))%h(zFile)</a> + @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a> @ added by %h(zDispUser) on hyperlink_to_date(zDate, "."); - @ [%z(href("%R/ainfo/%s",zUuid))details</a>] + @ [%z(href("%R/ainfo/%!S",zUuid))details</a>] @ </li> } if( cnt ){ @ </ul> } db_finalize(&q); - + } Index: src/bag.c ================================================================== --- src/bag.c +++ src/bag.c @@ -75,11 +75,11 @@ /* ** Change the size of the hash table on a bag so that ** it contains N slots ** ** Completely reconstruct the hash table from scratch. Deleted -** entries (indicated by a -1) are removed. When finished, it +** entries (indicated by a -1) are removed. When finished, it ** should be the case that p->cnt==p->used. */ static void bag_resize(Bag *p, int newSize){ int i; Bag old; Index: src/bisect.c ================================================================== --- src/bisect.c +++ src/bisect.c @@ -206,12 +206,12 @@ "SELECT bilog.seq, bilog.stat," " substr(blob.uuid,1,16), datetime(event.mtime)" " FROM bilog, blob, event" " WHERE blob.rid=bilog.rid AND event.objid=bilog.rid" " AND event.type='ci'" - " ORDER BY %s", - (sortByCkinTime ? "event.mtime DESC" : "bilog.rowid ASC") + " ORDER BY %s bilog.rowid ASC", + (sortByCkinTime ? "event.mtime DESC, " : "") ); while( db_step(&q)==SQLITE_ROW ){ fossil_print("%3d %-7s %s %s\n", db_column_int(&q, 0), db_column_text(&q, 1), @@ -370,11 +370,11 @@ g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid); g.argc = 3; g.fNoSync = 1; update_cmd(); } - + if( strncmp(zDisplay,"chart",m)==0 ){ bisect_chart(1); }else if( strncmp(zDisplay, "log", m)==0 ){ bisect_chart(0); }else if( strncmp(zDisplay, "status", m)==0 ){ @@ -390,17 +390,17 @@ for(i=0; i<sizeof(aBisectOption)/sizeof(aBisectOption[0]); i++){ char *z = mprintf("bisect-%s", aBisectOption[i].zName); fossil_print(" %-15s %-6s ", aBisectOption[i].zName, db_lget(z, (char*)aBisectOption[i].zDefault)); fossil_free(z); - comment_print(aBisectOption[i].zDesc, 27, 79); + comment_print(aBisectOption[i].zDesc, 0, 27, -1, g.comFmtFlags); } }else if( g.argc==4 || g.argc==5 ){ unsigned int i; n = strlen(g.argv[3]); for(i=0; i<sizeof(aBisectOption)/sizeof(aBisectOption[0]); i++){ - if( memcmp(g.argv[3], aBisectOption[i].zName, n)==0 ){ + if( strncmp(g.argv[3], aBisectOption[i].zName, n)==0 ){ char *z = mprintf("bisect-%s", aBisectOption[i].zName); if( g.argc==5 ){ db_lset(z, g.argv[4]); } fossil_print("%s\n", db_lget(z, (char*)aBisectOption[i].zDefault)); Index: src/blob.c ================================================================== --- src/blob.c +++ src/blob.c @@ -17,12 +17,21 @@ ** ** A Blob is a variable-length containers for arbitrary string ** or binary data. */ #include "config.h" -#include <zlib.h> +#if defined(FOSSIL_ENABLE_MINIZ) +# define MINIZ_HEADER_FILE_ONLY +# include "miniz.c" +#else +# include <zlib.h> +#endif #include "blob.h" +#if defined(_WIN32) +#include <fcntl.h> +#include <io.h> +#endif #if INTERFACE /* ** A Blob can hold a string or a binary object of arbitrary size. The ** size changes as necessary. @@ -29,14 +38,20 @@ */ struct Blob { unsigned int nUsed; /* Number of bytes used in aData[] */ unsigned int nAlloc; /* Number of bytes allocated for aData[] */ unsigned int iCursor; /* Next character of input to parse */ + unsigned int blobFlags; /* One or more BLOBFLAG_* bits */ char *aData; /* Where the information is stored */ void (*xRealloc)(Blob*, unsigned int); /* Function to reallocate the buffer */ }; +/* +** Allowed values for Blob.blobFlags +*/ +#define BLOBFLAG_NotSQL 0x0001 /* Non-SQL text */ + /* ** The current size of a Blob */ #define blob_size(X) ((X)->nUsed) @@ -143,10 +158,11 @@ free(pBlob->aData); pBlob->aData = 0; pBlob->nAlloc = 0; pBlob->nUsed = 0; pBlob->iCursor = 0; + pBlob->blobFlags = 0; }else if( newSize>pBlob->nAlloc || newSize<pBlob->nAlloc-4000 ){ char *pNew = fossil_realloc(pBlob->aData, newSize); pBlob->aData = pNew; pBlob->nAlloc = newSize; if( pBlob->nUsed>pBlob->nAlloc ){ @@ -157,11 +173,11 @@ /* ** An initializer for Blobs */ #if INTERFACE -#define BLOB_INITIALIZER {0,0,0,0,blobReallocMalloc} +#define BLOB_INITIALIZER {0,0,0,0,0,blobReallocMalloc} #endif const Blob empty_blob = BLOB_INITIALIZER; /* ** A reallocation function for when the initial string is in unmanaged @@ -212,10 +228,11 @@ }else{ if( size<=0 ) size = strlen(zData); pBlob->nUsed = pBlob->nAlloc = size; pBlob->aData = (char*)zData; pBlob->iCursor = 0; + pBlob->blobFlags = 0; pBlob->xRealloc = blobReallocStatic; } } /* @@ -223,10 +240,19 @@ ** Any prior data in the blob is discarded. */ void blob_set(Blob *pBlob, const char *zStr){ blob_init(pBlob, zStr, -1); } + +/* +** Initialize a blob to a nul-terminated string obtained from fossil_malloc(). +** The blob will take responsibility for freeing the string. +*/ +void blob_set_dynamic(Blob *pBlob, char *zStr){ + blob_init(pBlob, zStr, -1); + pBlob->xRealloc = blobReallocMalloc; +} /* ** Initialize a blob to an empty string. */ void blob_zero(Blob *pBlob){ @@ -234,10 +260,11 @@ assert_blob_is_reset(pBlob); pBlob->nUsed = 0; pBlob->nAlloc = 1; pBlob->aData = (char*)zEmpty; pBlob->iCursor = 0; + pBlob->blobFlags = 0; pBlob->xRealloc = blobReallocStatic; } /* ** Append text or data to the end of a blob. @@ -278,10 +305,24 @@ if( p->aData[p->nUsed]!=0 ){ blob_materialize(p); } return p->aData; } + +/* +** Return a pointer to a null-terminated string for a blob that has +** been created using blob_append_sql() and not blob_appendf(). If +** text was ever added using blob_appendf() then throw an error. +*/ +char *blob_sql_text(Blob *p){ + blob_is_init(p); + if( (p->blobFlags & BLOBFLAG_NotSQL) ){ + fossil_fatal("Internal error: Use of blob_appendf() to construct SQL text"); + } + return blob_str(p); +} + /* ** Return a pointer to a null-terminated string for a blob. ** ** WARNING: If the blob is ephemeral, it might cause a '\000' @@ -657,13 +698,25 @@ return i; } /* ** Do printf-style string rendering and append the results to a blob. +** +** The blob_appendf() version sets the BLOBFLAG_NotSQL bit in Blob.blobFlags +** whereas blob_append_sql() does not. */ void blob_appendf(Blob *pBlob, const char *zFormat, ...){ if( pBlob ){ + va_list ap; + va_start(ap, zFormat); + vxprintf(pBlob, zFormat, ap); + va_end(ap); + pBlob->blobFlags |= BLOBFLAG_NotSQL; + } +} +void blob_append_sql(Blob *pBlob, const char *zFormat, ...){ + if( pBlob ){ va_list ap; va_start(ap, zFormat); vxprintf(pBlob, zFormat, ap); va_end(ap); } @@ -700,11 +753,11 @@ ** Initialize a blob to be the content of a file. If the filename ** is blank or "-" then read from standard input. ** ** Any prior content of the blob is discarded, not freed. ** -** Return the number of bytes read. Calls fossil_fatal() error (i.e. +** Return the number of bytes read. Calls fossil_fatal() on error (i.e. ** it exit()s and does not return). */ int blob_read_from_file(Blob *pBlob, const char *zFilename){ int size, got; FILE *in; @@ -774,51 +827,26 @@ nWrote = blob_size(pBlob); #if defined(_WIN32) if( fossil_utf8_to_console(blob_buffer(pBlob), nWrote, 0) >= 0 ){ return nWrote; } + fflush(stdout); + _setmode(_fileno(stdout), _O_BINARY); #endif fwrite(blob_buffer(pBlob), 1, nWrote, stdout); - }else{ - int i, nName; - char *zName, zBuf[1000]; - - nName = strlen(zFilename); - if( nName>=sizeof(zBuf) ){ - zName = mprintf("%s", zFilename); - }else{ - zName = zBuf; - memcpy(zName, zFilename, nName+1); - } - nName = file_simplify_name(zName, nName, 0); - for(i=1; i<nName; i++){ - if( zName[i]=='/' ){ - zName[i] = 0; -#if defined(_WIN32) || defined(__CYGWIN__) - /* - ** On Windows, local path looks like: C:/develop/project/file.txt - ** The if stops us from trying to create a directory of a drive letter - ** C: in this example. - */ - if( !(i==2 && zName[1]==':') ){ -#endif - if( file_mkdir(zName, 1) && file_isdir(zName)!=1 ){ - fossil_fatal_recursive("unable to create directory %s", zName); - return 0; - } -#if defined(_WIN32) || defined(__CYGWIN__) - } -#endif - zName[i] = '/'; - } - } - out = fossil_fopen(zName, "wb"); - if( out==0 ){ - fossil_fatal_recursive("unable to open file \"%s\" for writing", zName); - return 0; - } - if( zName!=zBuf ) free(zName); +#if defined(_WIN32) + fflush(stdout); + _setmode(_fileno(stdout), _O_TEXT); +#endif + }else{ + file_mkfolder(zFilename, 1); + out = fossil_fopen(zFilename, "wb"); + if( out==0 ){ + fossil_fatal_recursive("unable to open file \"%s\" for writing", + zFilename); + return 0; + } blob_is_init(pBlob); nWrote = fwrite(blob_buffer(pBlob), 1, blob_size(pBlob), out); fclose(out); if( nWrote!=blob_size(pBlob) ){ fossil_fatal_recursive("short write: %d of %d bytes to %s", nWrote, @@ -1026,10 +1054,65 @@ else if( z[i+1]!='\n' ) z[j++] = '\n'; } z[j] = 0; p->nUsed = j; } + +/* +** Convert blob from cp1252 to UTF-8. As cp1252 is a superset +** of iso8859-1, this is useful on UNIX as well. +** +** This table contains the character translations for 0x80..0xA0. +*/ + +static const unsigned short cp1252[32] = { + 0x20ac, 0x81, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x8D, 0x017D, 0x8F, + 0x90, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x2DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x9D, 0x017E, 0x0178 +}; + +void blob_cp1252_to_utf8(Blob *p){ + unsigned char *z = (unsigned char *)p->aData; + int j = p->nUsed; + int i, n; + for(i=n=0; i<j; i++){ + if( z[i]>=0x80 ){ + if( (z[i]<0xa0) && (cp1252[z[i]&0x1f]>=0x800) ){ + n++; + } + n++; + } + } + j += n; + if( j>=p->nAlloc ){ + blob_resize(p, j); + z = (unsigned char *)p->aData; + } + p->nUsed = j; + z[j] = 0; + while( j>i ){ + if( z[--i]>=0x80 ){ + if( z[i]<0xa0 ){ + unsigned short sym = cp1252[z[i]&0x1f]; + if( sym>=0x800 ){ + z[--j] = 0x80 | (sym&0x3f); + z[--j] = 0x80 | ((sym>>6)&0x3f); + z[--j] = 0xe0 | (sym>>12); + }else{ + z[--j] = 0x80 | (sym&0x3f); + z[--j] = 0xc0 | (sym>>6); + } + }else{ + z[--j] = 0x80 | (z[i]&0x3f); + z[--j] = 0xC0 | (z[i]>>6); + } + }else{ + z[--j] = z[i]; + } + } +} /* ** Shell-escape the given string. Append the result to a blob. */ void shell_escape(Blob *pBlob, const char *zIn){ @@ -1095,21 +1178,18 @@ ** to be UTF-8 already, so no conversion is done. */ void blob_to_utf8_no_bom(Blob *pBlob, int useMbcs){ char *zUtf8; int bomSize = 0; -#if defined(_WIN32) || defined(__CYGWIN__) int bomReverse = 0; -#endif if( starts_with_utf8_bom(pBlob, &bomSize) ){ struct Blob temp; zUtf8 = blob_str(pBlob) + bomSize; blob_zero(&temp); blob_append(&temp, zUtf8, -1); blob_swap(pBlob, &temp); blob_reset(&temp); -#if defined(_WIN32) || defined(__CYGWIN__) }else if( starts_with_utf16_bom(pBlob, &bomSize, &bomReverse) ){ zUtf8 = blob_buffer(pBlob); if( bomReverse ){ /* Found BOM, but with reversed bytes */ unsigned int i = blob_size(pBlob); @@ -1122,18 +1202,17 @@ } /* Make sure the blob contains two terminating 0-bytes */ blob_append(pBlob, "", 1); zUtf8 = blob_str(pBlob) + bomSize; zUtf8 = fossil_unicode_to_utf8(zUtf8); - blob_zero(pBlob); - blob_append(pBlob, zUtf8, -1); - fossil_unicode_free(zUtf8); -#endif /* _WIN32 || __CYGWIN__ */ -#if defined(_WIN32) - }else if( useMbcs ){ + blob_set_dynamic(pBlob, zUtf8); + }else if( useMbcs && invalid_utf8(pBlob) ){ +#if defined(_WIN32) || defined(__CYGWIN__) zUtf8 = fossil_mbcs_to_utf8(blob_str(pBlob)); blob_reset(pBlob); blob_append(pBlob, zUtf8, -1); fossil_mbcs_free(zUtf8); +#else + blob_cp1252_to_utf8(pBlob); #endif /* _WIN32 */ } } Index: src/branch.c ================================================================== --- src/branch.c +++ src/branch.c @@ -51,11 +51,12 @@ verify_all_options(); if( g.argc<5 ){ usage("new BRANCH-NAME BASIS ?OPTIONS?"); } db_find_and_open_repository(0, 0); - noSign = db_get_int("omitsign", 0)|noSign; + noSign = db_get_boolean("omitsign", 0)|noSign; + if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; } /* fossil branch new name */ zBranch = g.argv[3]; if( zBranch==0 || zBranch[0]==0 ){ fossil_fatal("branch name cannot be empty"); @@ -133,17 +134,16 @@ const char *zTag = db_column_text(&q, 0); blob_appendf(&branch, "T -%F *\n", zTag); } db_finalize(&q); - blob_appendf(&branch, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); + blob_appendf(&branch, "U %F\n", zUserOvrd ? zUserOvrd : login_name()); md5sum_blob(&branch, &mcksum); blob_appendf(&branch, "Z %b\n", &mcksum); if( !noSign && clearsign(&branch, &branch) ){ Blob ans; char cReply; - blob_zero(&ans); prompt_user("unable to sign manifest. continue (y/N)? ", &ans); cReply = blob_str(&ans)[0]; if( cReply!='y' && cReply!='Y'){ db_end_transaction(1); fossil_exit(1); @@ -153,12 +153,12 @@ brid = content_put_ex(&branch, 0, 0, 0, isPrivate); if( brid==0 ){ fossil_fatal("trouble committing manifest: %s", g.zErrMsg); } db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid); - if( manifest_crosslink(brid, &branch)==0 ){ - fossil_fatal("unable to install new manifest"); + if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){ + fossil_fatal("%s\n", g.zErrMsg); } assert( blob_is_reset(&branch) ); content_deltify(rootid, brid, 0); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid); fossil_print("New branch: %s\n", zUuid); @@ -176,50 +176,70 @@ /* Commit */ db_end_transaction(0); /* Do an autosync push, if requested */ - if( !isPrivate ) autosync(SYNC_PUSH); + if( !isPrivate ) autosync_loop(SYNC_PUSH, db_get_int("autosync-tries", 1)); } +#if INTERFACE +/* +** Allows bits in the mBplqFlags parameter to branch_prepare_list_query(). +*/ +#define BRL_CLOSED_ONLY 0x001 /* Show only closed branches */ +#define BRL_OPEN_ONLY 0x002 /* Show only open branches */ +#define BRL_BOTH 0x003 /* Show both open and closed branches */ +#define BRL_OPEN_CLOSED_MASK 0x003 +#define BRL_MTIME 0x004 /* Include lastest check-in time */ +#dfeine BRL_ORDERBY_MTIME 0x008 /* Sort by MTIME. (otherwise sort by name)*/ + +#endif /* INTERFACE */ + /* ** Prepare a query that will list branches. ** ** If (which<0) then the query pulls only closed branches. If ** (which>0) then the query pulls all (closed and opened) ** branches. Else the query pulls currently-opened branches. */ -void branch_prepare_list_query(Stmt *pQuery, int which ){ - if( which < 0 ){ - db_prepare(pQuery, - "SELECT value FROM tagxref" - " WHERE tagid=%d AND value NOT NULL " - "EXCEPT " - "SELECT value FROM tagxref" - " WHERE tagid=%d" - " AND rid IN leaf" - " AND NOT %z" - " ORDER BY value COLLATE nocase /*sort*/", - TAG_BRANCH, TAG_BRANCH, leaf_is_closed_sql("tagxref.rid") - ); - }else if( which>0 ){ - db_prepare(pQuery, - "SELECT DISTINCT value FROM tagxref" - " WHERE tagid=%d AND value NOT NULL" - " AND rid IN leaf" - " ORDER BY value COLLATE nocase /*sort*/", - TAG_BRANCH - ); - }else{ - db_prepare(pQuery, - "SELECT DISTINCT value FROM tagxref" - " WHERE tagid=%d AND value NOT NULL" - " AND rid IN leaf" - " AND NOT %z" - " ORDER BY value COLLATE nocase /*sort*/", - TAG_BRANCH, leaf_is_closed_sql("tagxref.rid") - ); +void branch_prepare_list_query(Stmt *pQuery, int brFlags){ + switch( brFlags & BRL_OPEN_CLOSED_MASK ){ + case BRL_CLOSED_ONLY: { + db_prepare(pQuery, + "SELECT value FROM tagxref" + " WHERE tagid=%d AND value NOT NULL " + "EXCEPT " + "SELECT value FROM tagxref" + " WHERE tagid=%d" + " AND rid IN leaf" + " AND NOT %z" + " ORDER BY value COLLATE nocase /*sort*/", + TAG_BRANCH, TAG_BRANCH, leaf_is_closed_sql("tagxref.rid") + ); + break; + } + case BRL_BOTH: { + db_prepare(pQuery, + "SELECT DISTINCT value FROM tagxref" + " WHERE tagid=%d AND value NOT NULL" + " AND rid IN leaf" + " ORDER BY value COLLATE nocase /*sort*/", + TAG_BRANCH + ); + break; + } + case BRL_OPEN_ONLY: { + db_prepare(pQuery, + "SELECT DISTINCT value FROM tagxref" + " WHERE tagid=%d AND value NOT NULL" + " AND rid IN leaf" + " AND NOT %z" + " ORDER BY value COLLATE nocase /*sort*/", + TAG_BRANCH, leaf_is_closed_sql("tagxref.rid") + ); + break; + } } } /* @@ -252,30 +272,28 @@ */ void branch_cmd(void){ int n; const char *zCmd = "list"; db_find_and_open_repository(0, 0); - if( g.argc<2 ){ - usage("new|list|ls ..."); - } if( g.argc>=3 ) zCmd = g.argv[2]; n = strlen(zCmd); if( strncmp(zCmd,"new",n)==0 ){ branch_new(); }else if( (strncmp(zCmd,"list",n)==0)||(strncmp(zCmd, "ls", n)==0) ){ Stmt q; int vid; char *zCurrent = 0; - int showAll = find_option("all","a",0)!=0; - int showClosed = find_option("closed","c",0)!=0; + int brFlags = BRL_OPEN_ONLY; + if( find_option("all","a",0)!=0 ) brFlags = BRL_BOTH; + if( find_option("closed","c",0)!=0 ) brFlags = BRL_CLOSED_ONLY; if( g.localOpen ){ vid = db_lget_int("checkout", 0); zCurrent = db_text(0, "SELECT value FROM tagxref" " WHERE rid=%d AND tagid=%d", vid, TAG_BRANCH); } - branch_prepare_list_query(&q, showAll?1:(showClosed?-1:0)); + branch_prepare_list_query(&q, brFlags); while( db_step(&q)==SQLITE_ROW ){ const char *zBr = db_column_text(&q, 0); int isCur = zCurrent!=0 && fossil_strcmp(zCurrent,zBr)==0; fossil_print("%s%s\n", (isCur ? "* " : " "), zBr); } @@ -283,36 +301,131 @@ }else{ fossil_fatal("branch subcommand should be one of: " "new list ls"); } } + +static const char brlistQuery[] = +@ SELECT +@ tagxref.value, +@ max(event.mtime), +@ EXISTS(SELECT 1 FROM tagxref AS tx +@ WHERE tx.rid=tagxref.rid +@ AND tx.tagid=(SELECT tagid FROM tag WHERE tagname='closed') +@ AND tx.tagtype>0), +@ (SELECT tagxref.value +@ FROM plink CROSS JOIN tagxref +@ WHERE plink.pid=event.objid +@ AND tagxref.rid=plink.cid +@ AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='branch') +@ AND tagtype>0), +@ count(*), +@ (SELECT uuid FROM blob WHERE rid=tagxref.rid) +@ FROM tagxref, tag, event +@ WHERE tagxref.tagid=tag.tagid +@ AND tagxref.tagtype>0 +@ AND tag.tagname='branch' +@ AND event.objid=tagxref.rid +@ GROUP BY 1 +@ ORDER BY 2 DESC; +; + +/* +** This is the new-style branch-list page that shows the branch names +** together with their ages (time of last check-in) and whether or not +** they are closed or merged to another branch. +** +** Control jumps to this routine from brlist_page() (the /brlist handler) +** if there are no query parameters. +*/ +static void new_brlist_page(void){ + Stmt q; + double rNow; + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + style_header("Branches"); + style_adunit_config(ADUNIT_RIGHT_OK); + login_anonymous_available(); + + db_prepare(&q, brlistQuery/*works-like:""*/); + rNow = db_double(0.0, "SELECT julianday('now')"); + @ <div class="brlist"><table id="branchlisttable"> + @ <thead><tr> + @ <th>Branch Name</th> + @ <th>Age</th> + @ <th>Check-ins</th> + @ <th>Status</th> + @ <th>Resolution</th> + @ </tr></thead><tbody> + while( db_step(&q)==SQLITE_ROW ){ + const char *zBranch = db_column_text(&q, 0); + double rMtime = db_column_double(&q, 1); + int isClosed = db_column_int(&q, 2); + const char *zMergeTo = db_column_text(&q, 3); + int nCkin = db_column_int(&q, 4); + const char *zLastCkin = db_column_text(&q, 5); + char *zAge = human_readable_age(rNow - rMtime); + sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0); + if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0; + @ <tr> + @ <td>%z(href("%R/timeline?n=100&r=%T",zBranch))%h(zBranch)</a></td> + @ <td data-sortkey="%016llx(-iMtime)">%s(zAge)</td> + @ <td>%d(nCkin)</td> + fossil_free(zAge); + @ <td>%s(isClosed?"closed":"")</td> + if( zMergeTo ){ + @ <td>merged into + @ %z(href("%R/timeline?f=%!S",zLastCkin))%h(zMergeTo)</a></td> + }else{ + @ <td></td> + } + @ </tr> + } + @ </tbody></table></div> + db_finalize(&q); + output_table_sorting_javascript("branchlisttable","tkNtt",2); + style_footer(); +} /* ** WEBPAGE: brlist +** Show a list of branches +** Query parameters: ** -** Show a timeline of all branches +** all Show all branches +** closed Show only closed branches +** open Show only open branches (default behavior) +** colortest Show all branches with automatic color */ void brlist_page(void){ Stmt q; int cnt; int showClosed = P("closed")!=0; int showAll = P("all")!=0; + int showOpen = P("open")!=0; int colorTest = P("colortest")!=0; + int brFlags = BRL_OPEN_ONLY; + if( showClosed==0 && showAll==0 && showOpen==0 && colorTest==0 ){ + new_brlist_page(); + return; + } login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } if( colorTest ){ showClosed = 0; showAll = 1; } + if( showAll ) brFlags = BRL_BOTH; + if( showClosed ) brFlags = BRL_CLOSED_ONLY; - style_header(showClosed ? "Closed Branches" : - showAll ? "All Branches" : "Open Branches"); + style_header("%s", showClosed ? "Closed Branches" : + showAll ? "All Branches" : "Open Branches"); style_submenu_element("Timeline", "Timeline", "brtimeline"); if( showClosed ){ style_submenu_element("All", "All", "brlist?all"); - style_submenu_element("Open","Open","brlist"); + style_submenu_element("Open","Open","brlist?open"); }else if( showAll ){ style_submenu_element("Closed", "Closed", "brlist?closed"); style_submenu_element("Open","Open","brlist"); }else{ style_submenu_element("All", "All", "brlist?all"); @@ -322,10 +435,11 @@ style_submenu_element("Color-Test", "Color-Test", "brlist?colortest"); }else{ style_submenu_element("All", "All", "brlist?all"); } login_anonymous_available(); +#if 0 style_sidebox_begin("Nomenclature:", "33%"); @ <ol> @ <li> An <div class="sideboxDescribed">%z(href("brlist")) @ open branch</a></div> is a branch that has one or more @ <div class="sideboxDescribed">%z(href("leaves"))open leaves.</a></div> @@ -334,25 +448,26 @@ @ <li> A <div class="sideboxDescribed">%z(href("brlist?closed")) @ closed branch</a></div> is a branch with only @ <div class="sideboxDescribed">%z(href("leaves?closed")) @ closed leaves</a></div>. @ Closed branches are fixed and do not change (unless they are first - @ reopened)</li> + @ reopened).</li> @ </ol> style_sidebox_end(); +#endif - branch_prepare_list_query(&q, showAll?1:(showClosed?-1:0)); + branch_prepare_list_query(&q, brFlags); cnt = 0; while( db_step(&q)==SQLITE_ROW ){ const char *zBr = db_column_text(&q, 0); if( cnt==0 ){ if( colorTest ){ @ <h2>Default background colors for all branches:</h2> + }else if( showClosed ){ + @ <h2>Closed Branches:</h2> }else if( showAll ){ @ <h2>All Branches:</h2> - }else if( showClosed ){ - @ <h2>Closed Branches:</h2> }else{ @ <h2>Open Branches:</h2> } @ <ul> cnt++; @@ -360,23 +475,17 @@ if( colorTest ){ const char *zColor = hash_color(zBr); @ <li><span style="background-color: %s(zColor)"> @ %h(zBr) → %s(zColor)</span></li> }else{ - @ <li>%z(href("%R/timeline?r=%T",zBr))%h(zBr)</a></li> + @ <li>%z(href("%R/timeline?r=%T&n=200",zBr))%h(zBr)</a></li> } } if( cnt ){ @ </ul> } db_finalize(&q); - @ <script type="text/JavaScript"> - @ function xin(id){ - @ } - @ function xout(id){ - @ } - @ </script> style_footer(); } /* ** This routine is called while for each check-in that is rendered by @@ -394,11 +503,11 @@ " AND tag.tagname GLOB 'sym-*'", rid ); while( db_step(&q)==SQLITE_ROW ){ const char *zTagName = db_column_text(&q, 0); - @ %z(href("%R/timeline?r=%T",zTagName))[timeline]</a> + @ %z(href("%R/timeline?r=%T&n=200",zTagName))[timeline]</a> } db_finalize(&q); } /* @@ -408,11 +517,11 @@ */ void brtimeline_page(void){ Stmt q; login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } style_header("Branches"); style_submenu_element("List", "List", "brlist"); login_anonymous_available(); @ <h2>The initial check-in for each branch:</h2> @@ -420,15 +529,9 @@ "%s AND blob.rid IN (SELECT rid FROM tagxref" " WHERE tagtype>0 AND tagid=%d AND srcid!=0)" " ORDER BY event.mtime DESC", timeline_query_for_www(), TAG_BRANCH ); - www_print_timeline(&q, 0, 0, 0, brtimeline_extra); + www_print_timeline(&q, 0, 0, 0, 0, brtimeline_extra); db_finalize(&q); - @ <script type="text/JavaScript"> - @ function xin(id){ - @ } - @ function xout(id){ - @ } - @ </script> style_footer(); } Index: src/browse.c ================================================================== --- src/browse.c +++ src/browse.c @@ -71,23 +71,29 @@ ** There is no hyperlink on the file element of the path. ** ** The computed string is appended to the pOut blob. pOut should ** have already been initialized. */ -void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){ +void hyperlinked_path( + const char *zPath, /* Path to render */ + Blob *pOut, /* Write into this blob */ + const char *zCI, /* check-in name, or NULL */ + const char *zURI, /* "dir" or "tree" */ + const char *zREx /* Extra query parameters */ +){ int i, j; char *zSep = ""; for(i=0; zPath[i]; i=j){ for(j=i; zPath[j] && zPath[j]!='/'; j++){} if( zPath[j] && g.perm.Hyperlink ){ if( zCI ){ - char *zLink = href("%R/dir?ci=%S&name=%#T", zCI, j, zPath); + char *zLink = href("%R/%s?name=%#T%s&ci=%!S", zURI, j, zPath, zREx,zCI); blob_appendf(pOut, "%s%z%#h</a>", zSep, zLink, j-i, &zPath[i]); }else{ - char *zLink = href("%R/dir?name=%#T", j, zPath); + char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx); blob_appendf(pOut, "%s%z%#h</a>", zSep, zLink, j-i, &zPath[i]); } }else{ blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]); @@ -101,11 +107,11 @@ /* ** WEBPAGE: dir ** ** Query parameters: ** -** name=PATH Directory to display. Required. +** name=PATH Directory to display. Optional. Top-level if missing ** ci=LABEL Show only files in this check-in. Optional. */ void page_dir(void){ char *zD = fossil_strdup(P("name")); int nD = zD ? strlen(zD)+1 : 0; @@ -118,18 +124,24 @@ int rid = 0; char *zUuid = 0; Blob dirname; Manifest *pM = 0; const char *zSubdirLink; - int linkTrunk = 1, linkTip = 1; + int linkTrunk = 1; + int linkTip = 1; + HQuery sURI; + if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; } login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } style_header("File List"); + style_adunit_config(ADUNIT_RIGHT_OK); sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, pathelementFunc, 0, 0); + url_initialize(&sURI, "dir"); + cgi_query_parameters_to_url(&sURI); /* If the name= parameter is an empty string, make it a NULL pointer */ if( zD && strlen(zD)==0 ){ zD = 0; } /* If a specific check-in is requested, fetch and parse it. If the @@ -150,49 +162,43 @@ /* Compute the title of the page */ blob_zero(&dirname); if( zD ){ blob_append(&dirname, "in directory ", -1); - hyperlinked_path(zD, &dirname, zCI); + hyperlinked_path(zD, &dirname, zCI, "dir", ""); zPrefix = mprintf("%s/", zD); - if( linkTrunk ){ - style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk", - zD); - } - if ( linkTip ){ - style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD); - } + style_submenu_element("Top-Level", "Top-Level", "%s", + url_render(&sURI, "name", 0, 0, 0)); }else{ blob_append(&dirname, "in the top-level directory", -1); zPrefix = ""; - if( linkTrunk ){ - style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk"); - } - if ( linkTip ){ - style_submenu_element("Tip", "Tip", "%R/dir?ci=tip"); - } + } + if( linkTrunk ){ + style_submenu_element("Trunk", "Trunk", "%s", + url_render(&sURI, "ci", "trunk", 0, 0)); + } + if( linkTip ){ + style_submenu_element("Tip", "Tip", "%s", + url_render(&sURI, "ci", "tip", 0, 0)); } if( zCI ){ - char zShort[20]; - memcpy(zShort, zUuid, 10); - zShort[10] = 0; - @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>] + @ <h2>Files of check-in [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>] @ %s(blob_str(&dirname))</h2> - zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix); - if( zD ){ - style_submenu_element("Top", "Top", "%R/dir?ci=%S", zUuid); - style_submenu_element("All", "All", "%R/dir?name=%t", zD); - }else{ - style_submenu_element("All", "All", "%R/dir"); - style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%S", + zSubdirLink = mprintf("%R/dir?ci=%!S&name=%T", zUuid, zPrefix); + if( nD==0 ){ + style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%!S", zUuid); } }else{ @ <h2>The union of all files from all check-ins @ %s(blob_str(&dirname))</h2> zSubdirLink = mprintf("%R/dir?name=%T", zPrefix); } + style_submenu_element("All", "All", "%s", + url_render(&sURI, "ci", 0, 0, 0)); + style_submenu_element("Tree-View", "Tree-View", "%s", + url_render(&sURI, "type", "tree", 0, 0)); /* Compute the temporary table "localfiles" containing the names ** of all files and subdirectories in the zD[] directory. ** ** Subdirectory names begin with "/". This causes them to sort @@ -275,11 +281,11 @@ @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li> }else{ const char *zLink; if( zCI ){ const char *zUuid = db_column_text(&q, 1); - zLink = href("%R/artifact/%s",zUuid); + zLink = href("%R/artifact/%!S",zUuid); }else{ zLink = href("%R/finfo?name=%T%T",zPrefix,zFN); } @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li> } @@ -287,10 +293,569 @@ db_finalize(&q); manifest_destroy(pM); @ </ul></td></tr></table> style_footer(); } + +/* +** Objects used by the "tree" webpage. +*/ +typedef struct FileTreeNode FileTreeNode; +typedef struct FileTree FileTree; + +/* +** A single line of the file hierarchy +*/ +struct FileTreeNode { + FileTreeNode *pNext; /* Next entry in an ordered list of them all */ + FileTreeNode *pParent; /* Directory containing this entry */ + FileTreeNode *pSibling; /* Next element in the same subdirectory */ + FileTreeNode *pChild; /* List of child nodes */ + FileTreeNode *pLastChild; /* Last child on the pChild list */ + char *zName; /* Name of this entry. The "tail" */ + char *zFullName; /* Full pathname of this entry */ + char *zUuid; /* SHA1 hash of this file. May be NULL. */ + double mtime; /* Modification time for this entry */ + unsigned nFullName; /* Length of zFullName */ + unsigned iLevel; /* Levels of parent directories */ +}; + +/* +** A complete file hierarchy +*/ +struct FileTree { + FileTreeNode *pFirst; /* First line of the list */ + FileTreeNode *pLast; /* Last line of the list */ + FileTreeNode *pLastTop; /* Last top-level node */ +}; + +/* +** Add one or more new FileTreeNodes to the FileTree object so that the +** leaf object zPathname is at the end of the node list. +** +** The caller invokes this routine once for each leaf node (each file +** as opposed to each directory). This routine fills in any missing +** intermediate nodes automatically. +** +** When constructing a list of FileTreeNodes, all entries that have +** a common directory prefix must be added consecutively in order for +** the tree to be constructed properly. +*/ +static void tree_add_node( + FileTree *pTree, /* Tree into which nodes are added */ + const char *zPath, /* The full pathname of file to add */ + const char *zUuid, /* UUID of the file. Might be NULL. */ + double mtime /* Modification time for this entry */ +){ + int i; + FileTreeNode *pParent; /* Parent (directory) of the next node to insert */ + + /* Make pParent point to the most recent ancestor of zPath, or + ** NULL if there are no prior entires that are a container for zPath. + */ + pParent = pTree->pLast; + while( pParent!=0 && + ( strncmp(pParent->zFullName, zPath, pParent->nFullName)!=0 + || zPath[pParent->nFullName]!='/' ) + ){ + pParent = pParent->pParent; + } + i = pParent ? pParent->nFullName+1 : 0; + while( zPath[i] ){ + FileTreeNode *pNew; + int iStart = i; + int nByte; + while( zPath[i] && zPath[i]!='/' ){ i++; } + nByte = sizeof(*pNew) + i + 1; + if( zUuid!=0 && zPath[i]==0 ) nByte += UUID_SIZE+1; + pNew = fossil_malloc( nByte ); + memset(pNew, 0, sizeof(*pNew)); + pNew->zFullName = (char*)&pNew[1]; + memcpy(pNew->zFullName, zPath, i); + pNew->zFullName[i] = 0; + pNew->nFullName = i; + if( zUuid!=0 && zPath[i]==0 ){ + pNew->zUuid = pNew->zFullName + i + 1; + memcpy(pNew->zUuid, zUuid, UUID_SIZE+1); + } + pNew->zName = pNew->zFullName + iStart; + if( pTree->pLast ){ + pTree->pLast->pNext = pNew; + }else{ + pTree->pFirst = pNew; + } + pTree->pLast = pNew; + pNew->pParent = pParent; + if( pParent ){ + if( pParent->pChild ){ + pParent->pLastChild->pSibling = pNew; + }else{ + pParent->pChild = pNew; + } + pNew->iLevel = pParent->iLevel + 1; + pParent->pLastChild = pNew; + }else{ + if( pTree->pLastTop ) pTree->pLastTop->pSibling = pNew; + pTree->pLastTop = pNew; + } + pNew->mtime = mtime; + while( zPath[i]=='/' ){ i++; } + pParent = pNew; + } + while( pParent && pParent->pParent ){ + if( pParent->pParent->mtime < pParent->mtime ){ + pParent->pParent->mtime = pParent->mtime; + } + pParent = pParent->pParent; + } +} + +/* Comparison function for two FileTreeNode objects. Sort first by +** mtime (larger numbers first) and then by zName (smaller names first). +** +** Return negative if pLeft<pRight. +** Return positive if pLeft>pRight. +** Return zero if pLeft==pRight. +*/ +static int compareNodes(FileTreeNode *pLeft, FileTreeNode *pRight){ + if( pLeft->mtime>pRight->mtime ) return -1; + if( pLeft->mtime<pRight->mtime ) return +1; + return fossil_stricmp(pLeft->zName, pRight->zName); +} + +/* Merge together two sorted lists of FileTreeNode objects */ +static FileTreeNode *mergeNodes(FileTreeNode *pLeft, FileTreeNode *pRight){ + FileTreeNode *pEnd; + FileTreeNode base; + pEnd = &base; + while( pLeft && pRight ){ + if( compareNodes(pLeft,pRight)<=0 ){ + pEnd = pEnd->pSibling = pLeft; + pLeft = pLeft->pSibling; + }else{ + pEnd = pEnd->pSibling = pRight; + pRight = pRight->pSibling; + } + } + if( pLeft ){ + pEnd->pSibling = pLeft; + }else{ + pEnd->pSibling = pRight; + } + return base.pSibling; +} + +/* Sort a list of FileTreeNode objects in mtime order. */ +static FileTreeNode *sortNodesByMtime(FileTreeNode *p){ + FileTreeNode *a[30]; + FileTreeNode *pX; + int i; + + memset(a, 0, sizeof(a)); + while( p ){ + pX = p; + p = pX->pSibling; + pX->pSibling = 0; + for(i=0; i<count(a)-1 && a[i]!=0; i++){ + pX = mergeNodes(a[i], pX); + a[i] = 0; + } + a[i] = mergeNodes(a[i], pX); + } + pX = 0; + for(i=0; i<count(a); i++){ + pX = mergeNodes(a[i], pX); + } + return pX; +} + +/* Sort an entire FileTreeNode tree by mtime +** +** This routine invalidates the following fields: +** +** FileTreeNode.pLastChild +** FileTreeNode.pNext +** +** Use relinkTree to reconnect the pNext pointers. +*/ +static FileTreeNode *sortTreeByMtime(FileTreeNode *p){ + FileTreeNode *pX; + for(pX=p; pX; pX=pX->pSibling){ + if( pX->pChild ) pX->pChild = sortTreeByMtime(pX->pChild); + } + return sortNodesByMtime(p); +} + +/* Reconstruct the FileTree by reconnecting the FileTreeNode.pNext +** fields in sequential order. +*/ +static void relinkTree(FileTree *pTree, FileTreeNode *pRoot){ + while( pRoot ){ + if( pTree->pLast ){ + pTree->pLast->pNext = pRoot; + }else{ + pTree->pFirst = pRoot; + } + pTree->pLast = pRoot; + if( pRoot->pChild ) relinkTree(pTree, pRoot->pChild); + pRoot = pRoot->pSibling; + } + if( pTree->pLast ) pTree->pLast->pNext = 0; +} + + +/* +** WEBPAGE: tree +** +** Query parameters: +** +** name=PATH Directory to display. Optional +** ci=LABEL Show only files in this check-in. Optional. +** re=REGEXP Show only files matching REGEXP. Optional. +** expand Begin with the tree fully expanded. +** nofiles Show directories (folders) only. Omit files. +** mtime Order directory elements by decreasing mtime +*/ +void page_tree(void){ + char *zD = fossil_strdup(P("name")); + int nD = zD ? strlen(zD)+1 : 0; + const char *zCI = P("ci"); + int rid = 0; + char *zUuid = 0; + Blob dirname; + Manifest *pM = 0; + double rNow = 0; + char *zNow = 0; + int useMtime = atoi(PD("mtime","0")); + int nFile = 0; /* Number of files (or folders with "nofiles") */ + int linkTrunk = 1; /* include link to "trunk" */ + int linkTip = 1; /* include link to "tip" */ + const char *zRE; /* the value for the re=REGEXP query parameter */ + const char *zObjType; /* "files" by default or "folders" for "nofiles" */ + char *zREx = ""; /* Extra parameters for path hyperlinks */ + ReCompiled *pRE = 0; /* Compiled regular expression */ + FileTreeNode *p; /* One line of the tree */ + FileTree sTree; /* The complete tree of files */ + HQuery sURI; /* Hyperlink */ + int startExpanded; /* True to start out with the tree expanded */ + int showDirOnly; /* Show directories only. Omit files */ + int nDir = 0; /* Number of directories. Used for ID attributes */ + char *zProjectName = db_get("project-name", 0); + + if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; } + memset(&sTree, 0, sizeof(sTree)); + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } + sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, + pathelementFunc, 0, 0); + url_initialize(&sURI, "tree"); + cgi_query_parameters_to_url(&sURI); + if( PB("nofiles") ){ + showDirOnly = 1; + style_header("Folder Hierarchy"); + }else{ + showDirOnly = 0; + style_header("File Tree"); + } + style_adunit_config(ADUNIT_RIGHT_OK); + if( PB("expand") ){ + startExpanded = 1; + }else{ + startExpanded = 0; + } + + /* If a regular expression is specified, compile it */ + zRE = P("re"); + if( zRE ){ + re_compile(&pRE, zRE, 0); + zREx = mprintf("&re=%T", zRE); + } + + /* If the name= parameter is an empty string, make it a NULL pointer */ + if( zD && strlen(zD)==0 ){ zD = 0; } + + /* If a specific check-in is requested, fetch and parse it. If the + ** specific check-in does not exist, clear zCI. zCI==0 will cause all + ** files from all check-ins to be displayed. + */ + if( zCI ){ + pM = manifest_get_by_name(zCI, &rid); + if( pM ){ + int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); + linkTrunk = trunkRid && rid != trunkRid; + linkTip = rid != symbolic_name_to_rid("tip", "ci"); + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); + zNow = db_text("", "SELECT datetime(mtime,'localtime')" + " FROM event WHERE objid=%d", rid); + }else{ + zCI = 0; + } + } + if( zCI==0 ){ + rNow = db_double(0.0, "SELECT max(mtime) FROM event"); + zNow = db_text("", "SELECT datetime(max(mtime),'localtime') FROM event"); + } + + /* Compute the title of the page */ + blob_zero(&dirname); + if( zD ){ + blob_append(&dirname, "within directory ", -1); + hyperlinked_path(zD, &dirname, zCI, "tree", zREx); + if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE); + style_submenu_element("Top-Level", "Top-Level", "%s", + url_render(&sURI, "name", 0, 0, 0)); + }else{ + if( zRE ){ + blob_appendf(&dirname, "matching \"%s\"", zRE); + } + } + style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0); + if( zCI ){ + style_submenu_element("All", "All", "%s", + url_render(&sURI, "ci", 0, 0, 0)); + if( nD==0 && !showDirOnly ){ + style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s", + zUuid); + } + } + if( linkTrunk ){ + style_submenu_element("Trunk", "Trunk", "%s", + url_render(&sURI, "ci", "trunk", 0, 0)); + } + if( linkTip ){ + style_submenu_element("Tip", "Tip", "%s", + url_render(&sURI, "ci", "tip", 0, 0)); + } + style_submenu_element("Flat-View", "Flat-View", "%s", + url_render(&sURI, "type", "flat", 0, 0)); + + /* Compute the file hierarchy. + */ + if( zCI ){ + Stmt q; + compute_fileage(rid, 0); + db_prepare(&q, + "SELECT filename.name, blob.uuid, fileage.mtime\n" + " FROM fileage, filename, blob\n" + " WHERE filename.fnid=fileage.fnid\n" + " AND blob.rid=fileage.fid\n" + " ORDER BY filename.name COLLATE nocase;" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zFile = db_column_text(&q,0); + const char *zUuid = db_column_text(&q,1); + double mtime = db_column_double(&q,2); + if( nD>0 && (fossil_strncmp(zFile, zD, nD-1)!=0 || zFile[nD-1]!='/') ){ + continue; + } + if( pRE && re_match(pRE, (const unsigned char*)zFile, -1)==0 ) continue; + tree_add_node(&sTree, zFile, zUuid, mtime); + nFile++; + } + db_finalize(&q); + }else{ + Stmt q; + db_prepare(&q, + "SELECT filename.name, blob.uuid, max(event.mtime)\n" + " FROM filename, mlink, blob, event\n" + " WHERE mlink.fnid=filename.fnid\n" + " AND event.objid=mlink.mid\n" + " AND blob.rid=mlink.fid\n" + " GROUP BY 1 ORDER BY 1 COLLATE nocase"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q, 0); + const char *zUuid = db_column_text(&q,1); + double mtime = db_column_double(&q,2); + if( nD>0 && (fossil_strncmp(zName, zD, nD-1)!=0 || zName[nD-1]!='/') ){ + continue; + } + if( pRE && re_match(pRE, (const u8*)zName, -1)==0 ) continue; + tree_add_node(&sTree, zName, zUuid, mtime); + nFile++; + } + db_finalize(&q); + } + + if( showDirOnly ){ + for(nFile=0, p=sTree.pFirst; p; p=p->pNext){ + if( p->pChild!=0 && p->nFullName>nD ) nFile++; + } + zObjType = "Folders"; + style_submenu_element("Files","Files","%s", + url_render(&sURI,"nofiles",0,0,0)); + }else{ + zObjType = "Files"; + style_submenu_element("Folders","Folders","%s", + url_render(&sURI,"nofiles","1",0,0)); + } + + if( zCI ){ + @ <h2>%s(zObjType) from + if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){ + @ "%h(zCI)" + } + @ [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname)) + }else{ + int n = db_int(0, "SELECT count(*) FROM plink"); + @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname)) + } + if( useMtime ){ + @ sorted by modification time</h2> + }else{ + @ sorted by filename</h2> + } + + + /* Generate tree of lists. + ** + ** Each file and directory is a list element: <li>. Files have class=file + ** and if the filename as the suffix "xyz" the file also has class=file-xyz. + ** Directories have class=dir. The directory specfied by the name= query + ** parameter (or the top-level directory if there is no name= query parameter) + ** adds class=subdir. + ** + ** The <li> element for directories also contains a sublist <ul> + ** for the contents of that directory. + */ + @ <div class="filetree"><ul> + if( nD ){ + @ <li class="dir last"> + }else{ + @ <li class="dir subdir last"> + } + @ <div class="filetreeline"> + @ %z(href("%s",url_render(&sURI,"name",0,0,0)))%h(zProjectName)</a> + if( zNow ){ + @ <div class="filetreeage">%s(zNow)</div> + } + @ </div> + @ <ul> + if( useMtime ){ + p = sortTreeByMtime(sTree.pFirst); + memset(&sTree, 0, sizeof(sTree)); + relinkTree(&sTree, p); + } + for(p=sTree.pFirst, nDir=0; p; p=p->pNext){ + const char *zLastClass = p->pSibling==0 ? " last" : ""; + if( p->pChild ){ + const char *zSubdirClass = p->nFullName==nD-1 ? " subdir" : ""; + @ <li class="dir%s(zSubdirClass)%s(zLastClass)"><div class="filetreeline"> + @ %z(href("%s",url_render(&sURI,"name",p->zFullName,0,0)))%h(p->zName)</a> + if( p->mtime>0.0 ){ + char *zAge = human_readable_age(rNow - p->mtime); + @ <div class="filetreeage">%s(zAge)</div> + } + @ </div> + if( startExpanded || p->nFullName<=nD ){ + @ <ul id="dir%d(nDir)"> + }else{ + @ <ul id="dir%d(nDir)" class="collapsed"> + } + nDir++; + }else if( !showDirOnly ){ + const char *zFileClass = fileext_class(p->zName); + char *zLink; + if( zCI ){ + zLink = href("%R/artifact/%!S",p->zUuid); + }else{ + zLink = href("%R/finfo?name=%T",p->zFullName); + } + @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline"> + @ %z(zLink)%h(p->zName)</a> + if( p->mtime>0 ){ + char *zAge = human_readable_age(rNow - p->mtime); + @ <div class="filetreeage">%s(zAge)</div> + } + @ </div> + } + if( p->pSibling==0 ){ + int nClose = p->iLevel - (p->pNext ? p->pNext->iLevel : 0); + while( nClose-- > 0 ){ + @ </ul> + } + } + } + @ </ul> + @ </ul></div> + @ <script>(function(){ + @ function isExpanded(ul){ + @ return ul.className==''; + @ } + @ + @ function toggleDir(ul, useInitValue){ + @ if( !useInitValue ){ + @ expandMap[ul.id] = !isExpanded(ul); + @ history.replaceState(expandMap, ''); + @ } + @ ul.className = expandMap[ul.id] ? '' : 'collapsed'; + @ } + @ + @ function toggleAll(tree, useInitValue){ + @ var lists = tree.querySelectorAll('.subdir > ul > li ul'); + @ if( !useInitValue ){ + @ var expand = true; /* Default action: make all sublists visible */ + @ for( var i=0; lists[i]; i++ ){ + @ if( isExpanded(lists[i]) ){ + @ expand = false; /* Any already visible - make them all hidden */ + @ break; + @ } + @ } + @ expandMap = {'*': expand}; + @ history.replaceState(expandMap, ''); + @ } + @ var className = expandMap['*'] ? '' : 'collapsed'; + @ for( var i=0; lists[i]; i++ ){ + @ lists[i].className = className; + @ } + @ } + @ + @ function checkState(){ + @ expandMap = history.state || {}; + @ if( '*' in expandMap ) toggleAll(outer_ul, true); + @ for( var id in expandMap ){ + @ if( id!=='*' ) toggleDir(gebi(id), true); + @ } + @ } + @ + @ function belowSubdir(node){ + @ do{ + @ node = node.parentNode; + @ if( node==subdir ) return true; + @ } while( node && node!=outer_ul ); + @ return false; + @ } + @ + @ var history = window.history || {}; + @ if( !history.replaceState ) history.replaceState = function(){}; + @ var outer_ul = document.querySelector('.filetree > ul'); + @ var subdir = outer_ul.querySelector('.subdir'); + @ var expandMap = {}; + @ checkState(); + @ outer_ul.onclick = function(e){ + @ e = e || window.event; + @ var a = e.target || e.srcElement; + @ if( a.nodeName!='A' ) return true; + @ if( a.parentNode.parentNode==subdir ){ + @ toggleAll(outer_ul); + @ return false; + @ } + @ if( !belowSubdir(a) ) return true; + @ var ul = a.parentNode.nextSibling; + @ while( ul && ul.nodeName!='UL' ) ul = ul.nextSibling; + @ if( !ul ) return true; /* This is a file link, not a directory */ + @ toggleDir(ul); + @ return false; + @ } + @ }())</script> + style_footer(); + + /* We could free memory used by sTree here if we needed to. But + ** the process is about to exit, so doing so would not really accomplish + ** anything useful. */ +} /* ** Return a CSS class name based on the given filename's extension. ** Result must be freed by the caller. **/ @@ -299,162 +864,235 @@ const char *zExt = strrchr(zFilename, '.'); int isExt = zExt && zExt!=zFilename && zExt[1]; int i; for( i=1; isExt && zExt[i]; i++ ) isExt &= fossil_isalnum(zExt[i]); if( isExt ){ - zClass = mprintf("file-%s", zExt+1); - for ( i=5; zClass[i]; i++ ) zClass[i] = fossil_tolower(zClass[i]); + zClass = mprintf("file file-%s", zExt+1); + for( i=5; zClass[i]; i++ ) zClass[i] = fossil_tolower(zClass[i]); }else{ zClass = mprintf("file"); } return zClass; } + +/* +** SQL used to compute the age of all files in check-in :ckin whose +** names match :glob +*/ +static const char zComputeFileAgeSetup[] = +@ CREATE TABLE IF NOT EXISTS temp.fileage( +@ fnid INTEGER PRIMARY KEY, +@ fid INTEGER, +@ mid INTEGER, +@ mtime DATETIME, +@ pathname TEXT +@ ); +@ CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin; +; + +static const char zComputeFileAgeRun[] = +@ WITH RECURSIVE +@ ckin(x,m) AS (SELECT objid, mtime FROM event WHERE objid=:ckin +@ UNION +@ SELECT plink.pid, event.mtime +@ FROM ckin, plink, event +@ WHERE plink.cid=ckin.x AND event.objid=plink.pid +@ ORDER BY 2 DESC) +@ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname) +@ SELECT filename.fnid, mlink.fid, mlink.mid, event.mtime, filename.name +@ FROM foci, filename, blob, mlink, event +@ WHERE foci.checkinID=:ckin +@ AND foci.filename GLOB :glob +@ AND filename.name=foci.filename +@ AND blob.uuid=foci.uuid +@ AND mlink.fid=blob.rid +@ AND mlink.fid!=mlink.pid +@ AND mlink.mid IN (SELECT x FROM ckin) +@ AND event.objid=mlink.mid +@ ORDER BY event.mtime ASC; +; /* ** Look at all file containing in the version "vid". Construct a ** temporary table named "fileage" that contains the file-id for each ** files, the pathname, the check-in where the file was added, and the -** mtime on that checkin. +** mtime on that check-in. If zGlob and *zGlob then only files matching +** the given glob are computed. +*/ +int compute_fileage(int vid, const char* zGlob){ + Stmt q; + db_multi_exec(zComputeFileAgeSetup /*works-like:"constant"*/); + db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/); + db_bind_int(&q, ":ckin", vid); + db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*"); + db_exec(&q); + db_finalize(&q); + return 0; +} + +/* +** Render the number of days in rAge as a more human-readable time span. +** Different units (seconds, minutes, hours, days, months, years) are +** selected depending on the magnitude of rAge. +** +** The string returned is obtained from fossil_malloc() and should be +** freed by the caller. */ -int compute_fileage(int vid){ - Manifest *pManifest; - ManifestFile *pFile; - int nFile = 0; - double vmtime; - Stmt ins; - Stmt q1, q2, q3; - Stmt upd; - db_multi_exec( - /*"DROP TABLE IF EXISTS temp.fileage;"*/ - "CREATE TEMP TABLE fileage(" - " fid INTEGER," - " mid INTEGER," - " mtime DATETIME," - " pathname TEXT" - ");" - "CREATE INDEX fileage_fid ON fileage(fid);" - ); - pManifest = manifest_get(vid, CFTYPE_MANIFEST, 0); - if( pManifest==0 ) return 1; - manifest_file_rewind(pManifest); - db_prepare(&ins, - "INSERT INTO temp.fileage(fid, pathname)" - " SELECT rid, :path FROM blob WHERE uuid=:uuid" - ); - while( (pFile = manifest_file_next(pManifest, 0))!=0 ){ - db_bind_text(&ins, ":uuid", pFile->zUuid); - db_bind_text(&ins, ":path", pFile->zName); - db_step(&ins); - db_reset(&ins); - nFile++; - } - db_finalize(&ins); - manifest_destroy(pManifest); - db_prepare(&q1,"SELECT fid FROM mlink WHERE mid=:mid"); - db_prepare(&upd, "UPDATE fileage SET mid=:mid, mtime=:vmtime" - " WHERE fid=:fid AND mid IS NULL"); - db_prepare(&q2,"SELECT pid FROM plink WHERE cid=:vid AND isprim"); - db_prepare(&q3,"SELECT mtime FROM event WHERE objid=:vid"); - while( nFile>0 && vid>0 ){ - db_bind_int(&q3, ":vid", vid); - if( db_step(&q3)==SQLITE_ROW ){ - vmtime = db_column_double(&q3, 0); +char *human_readable_age(double rAge){ + if( rAge*86400.0<120 ){ + if( rAge*86400.0<1.0 ){ + return mprintf("current"); }else{ - break; - } - db_reset(&q3); - db_bind_int(&q1, ":mid", vid); - db_bind_int(&upd, ":mid", vid); - db_bind_double(&upd, ":vmtime", vmtime); - while( db_step(&q1)==SQLITE_ROW ){ - db_bind_int(&upd, ":fid", db_column_int(&q1, 0)); - db_step(&upd); - nFile -= db_changes(); - db_reset(&upd); - } - db_reset(&q1); - db_bind_int(&q2, ":vid", vid); - if( db_step(&q2)!=SQLITE_ROW ) break; - vid = db_column_int(&q2, 0); - db_reset(&q2); - } - db_finalize(&q1); - db_finalize(&upd); - db_finalize(&q2); - db_finalize(&q3); - return 0; + return mprintf("%d seconds", (int)(rAge*86400.0)); + } + }else if( rAge*1440.0<90 ){ + return mprintf("%.1f minutes", rAge*1440.0); + }else if( rAge*24.0<36 ){ + return mprintf("%.1f hours", rAge*24.0); + }else if( rAge<365.0 ){ + return mprintf("%.1f days", rAge); + }else{ + return mprintf("%.2f years", rAge/365.0); + } +} + +/* +** COMMAND: test-fileage +** +** Usage: %fossil test-fileage CHECKIN +*/ +void test_fileage_cmd(void){ + int mid; + Stmt q; + const char *zGlob = find_option("glob",0,1); + db_find_and_open_repository(0,0); + verify_all_options(); + if( g.argc!=3 ) usage("test-fileage CHECKIN"); + mid = name_to_typed_rid(g.argv[2],"ci"); + compute_fileage(mid, zGlob); + db_prepare(&q, + "SELECT fid, mid, julianday('now') - mtime, pathname" + " FROM fileage" + ); + while( db_step(&q)==SQLITE_ROW ){ + char *zAge = human_readable_age(db_column_double(&q,2)); + fossil_print("%8d %8d %16s %s\n", + db_column_int(&q,0), + db_column_int(&q,1), + zAge, + db_column_text(&q,3)); + fossil_free(zAge); + } + db_finalize(&q); } /* ** WEBPAGE: fileage ** ** Parameters: -** name=VERSION +** name=VERSION Selects the check-in version (default=tip). +** glob=STRING Only shows files matching this glob pattern +** (e.g. *.c or *.txt). +** showid Show RID values for debugging */ void fileage_page(void){ int rid; const char *zName; - char *zBaseTime; - Stmt q; + const char *zGlob; + const char *zUuid; + const char *zNow; /* Time of check-in */ + int showId = PB("showid"); + Stmt q1, q2; double baseTime; - int lastMid = -1; - login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } zName = P("name"); if( zName==0 ) zName = "tip"; rid = symbolic_name_to_rid(zName, "ci"); if( rid==0 ){ fossil_fatal("not a valid check-in: %s", zName); } - style_header("File Ages", zName); - compute_fileage(rid); - baseTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); - zBaseTime = db_text("","SELECT datetime(%.20g,'localtime')", baseTime); - @ <h2>File Ages For Check-in - @ %z(href("%R/info?name=%T",zName))%h(zName)</a></h2> - @ - @ <p>The times given are relative to - @ %z(href("%R/timeline?c=%T",zBaseTime))%s(zBaseTime)</a>, which is the - @ check-in time for - @ %z(href("%R/info?name=%T",zName))%h(zName)</a></p> - @ - @ <table border=0 cellspacing=0 cellpadding=0> - db_prepare(&q, - "SELECT mtime, (SELECT uuid FROM blob WHERE rid=fid), mid, pathname" - " FROM fileage" - " ORDER BY mtime DESC, mid, pathname" - ); - while( db_step(&q)==SQLITE_ROW ){ - double age = baseTime - db_column_double(&q, 0); - int mid = db_column_int(&q, 2); - const char *zFUuid = db_column_text(&q, 1); - char zAge[200]; - if( lastMid!=mid ){ - @ <tr><td colspan=3><hr></tr> - lastMid = mid; - if( age*86400.0<120 ){ - sqlite3_snprintf(sizeof(zAge), zAge, "%d seconds", (int)(age*86400.0)); - }else if( age*1440.0<90 ){ - sqlite3_snprintf(sizeof(zAge), zAge, "%.1f minutes", age*1440.0); - }else if( age*24.0<36 ){ - sqlite3_snprintf(sizeof(zAge), zAge, "%.1f hours", age*24.0); - }else if( age<365.0 ){ - sqlite3_snprintf(sizeof(zAge), zAge, "%.1f days", age); - }else{ - sqlite3_snprintf(sizeof(zAge), zAge, "%.2f years", age/365.0); - } - }else{ - zAge[0] = 0; - } - @ <tr> - @ <td>%s(zAge) - @ <td width="25"> - @ <td>%z(href("%R/artifact/%S?ln", zFUuid))%h(db_column_text(&q, 3))</a> - @ </tr> - @ - } - @ <tr><td colspan=3><hr></tr> - @ </table> - db_finalize(&q); + zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); + baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid); + zNow = db_text("", "SELECT datetime(mtime,'localtime') FROM event" + " WHERE objid=%d", rid); + style_submenu_element("Tree-View", "Tree-View", + "%R/tree?ci=%T&mtime=1&type=tree", + zName); + style_header("File Ages"); + zGlob = P("glob"); + compute_fileage(rid,zGlob); + db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);"); + + @ <h2>Files in + @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a> + if( zGlob && zGlob[0] ){ + @ that match "%h(zGlob)" and + } + @ ordered by check-in time</h2> + @ + @ <p>Times are relative to the check-in time for + @ %z(href("%R/ci/%!S",zUuid))[%S(zUuid)]</a> which is + @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p> + @ + @ <div class='fileage'><table> + @ <tr><th>Time</th><th>Files</th><th>Check-in</th></tr> + db_prepare(&q1, + "SELECT event.mtime, event.objid, blob.uuid,\n" + " coalesce(event.ecomment,event.comment),\n" + " coalesce(event.euser,event.user),\n" + " coalesce((SELECT value FROM tagxref\n" + " WHERE tagtype>0 AND tagid=%d\n" + " AND rid=event.objid),'trunk')\n" + " FROM event, blob\n" + " WHERE event.objid IN (SELECT mid FROM fileage)\n" + " AND blob.rid=event.objid\n" + " ORDER BY event.mtime DESC;", + TAG_BRANCH + ); + db_prepare(&q2, + "SELECT blob.uuid, filename.name, fileage.fid\n" + " FROM fileage, blob, filename\n" + " WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid" + " AND blob.rid=fileage.fid;" + ); + while( db_step(&q1)==SQLITE_ROW ){ + double age = baseTime - db_column_double(&q1, 0); + int mid = db_column_int(&q1, 1); + const char *zUuid = db_column_text(&q1, 2); + const char *zComment = db_column_text(&q1, 3); + const char *zUser = db_column_text(&q1, 4); + const char *zBranch = db_column_text(&q1, 5); + char *zAge = human_readable_age(age); + @ <tr><td>%s(zAge)</td> + @ <td> + db_bind_int(&q2, ":mid", mid); + while( db_step(&q2)==SQLITE_ROW ){ + const char *zFUuid = db_column_text(&q2,0); + const char *zFile = db_column_text(&q2,1); + int fid = db_column_int(&q2,2); + if( showId ){ + @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a> (%d(fid))<br> + }else{ + @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a><br> + } + } + db_reset(&q2); + @ </td> + @ <td> + @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a> + if( showId ){ + @ (%d(mid)) + } + @ %W(zComment) (user: + @ %z(href("%R/timeline?u=%t&c=%!S&nd&n=200",zUser,zUuid))%h(zUser)</a>, + @ branch: + @ %z(href("%R/timeline?r=%t&c=%!S&nd&n=200",zBranch,zUuid))%h(zBranch)</a>) + @ </td></tr> + @ + fossil_free(zAge); + } + @ </table></div> + db_finalize(&q1); + db_finalize(&q2); style_footer(); } ADDED src/builtin.c Index: src/builtin.c ================================================================== --- /dev/null +++ src/builtin.c @@ -0,0 +1,89 @@ +/* +** Copyright (c) 2014 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains built-in string and BLOB resources packaged as +** byte arrays. +*/ +#include "config.h" +#include "builtin.h" +#include <assert.h> + +/* +** The resources provided by this file are packaged by the "mkbuiltin.c" +** utility program during the built process and stored in the +** builtin_data.h file. Include that information here: +*/ +#include "builtin_data.h" + +/* +** Return a pointer to built-in content +*/ +const unsigned char *builtin_file(const char *zFilename, int *piSize){ + int lwr, upr, i, c; + lwr = 0; + upr = sizeof(aBuiltinFiles)/sizeof(aBuiltinFiles[0]) - 1; + while( upr>=lwr ){ + i = (upr+lwr)/2; + c = strcmp(aBuiltinFiles[i].zName,zFilename); + if( c<0 ){ + lwr = i+1; + }else if( c>0 ){ + upr = i-1; + }else{ + if( piSize ) *piSize = aBuiltinFiles[i].nByte; + return aBuiltinFiles[i].pData; + } + } + if( piSize ) *piSize = 0; + return 0; +} +const char *builtin_text(const char *zFilename){ + return (char*)builtin_file(zFilename, 0); +} + +/* +** COMMAND: test-builtin-list +** +** List the names and sizes of all built-in resources +*/ +void test_builtin_list(void){ + int i; + for(i=0; i<sizeof(aBuiltinFiles)/sizeof(aBuiltinFiles[0]); i++){ + fossil_print("%-30s %6d\n", aBuiltinFiles[i].zName,aBuiltinFiles[i].nByte); + } +} + +/* +** COMMAND: test-builtin-get +** +** Usage: %fossil test-builtin-get NAME ?OUTPUT-FILE? +*/ +void test_builtin_get(void){ + const unsigned char *pData; + int nByte; + Blob x; + if( g.argc!=3 && g.argc!=4 ){ + usage("NAME ?OUTPUT-FILE?"); + } + pData = builtin_file(g.argv[2], &nByte); + if( pData==0 ){ + fossil_fatal("no such built-in file: [%s]", g.argv[2]); + } + blob_init(&x, (const char*)pData, nByte); + blob_write_to_file(&x, g.argc==4 ? g.argv[3] : "-"); + blob_reset(&x); +} ADDED src/bundle.c Index: src/bundle.c ================================================================== --- /dev/null +++ src/bundle.c @@ -0,0 +1,818 @@ +/* +** Copyright (c) 2014 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code used to implement and manage a "bundle" file. +*/ +#include "config.h" +#include "bundle.h" +#include <assert.h> + +/* +** SQL code used to initialize the schema of a bundle. +** +** The bblob.delta field can be an integer, a text string, or NULL. +** If an integer, then the corresponding blobid is the delta basis. +** If a text string, then that string is a SHA1 hash for the delta +** basis, which is presumably in the master repository. If NULL, then +** data contains contain without delta compression. +*/ +static const char zBundleInit[] = +@ CREATE TABLE IF NOT EXISTS "%w".bconfig( +@ bcname TEXT, +@ bcvalue ANY +@ ); +@ CREATE TABLE IF NOT EXISTS "%w".bblob( +@ blobid INTEGER PRIMARY KEY, -- Blob ID +@ uuid TEXT NOT NULL, -- SHA1 hash of expanded blob +@ sz INT NOT NULL, -- Size of blob after expansion +@ delta ANY, -- Delta compression basis, or NULL +@ notes TEXT, -- Description of content +@ data BLOB -- compressed content +@ ); +; + +/* +** Attach a bundle file to the current database connection using the +** attachment name zBName. +*/ +static void bundle_attach_file( + const char *zFile, /* Name of the file that contains the bundle */ + const char *zBName, /* Attachment name */ + int doInit /* Initialize a new bundle, if true */ +){ + int rc; + char *zErrMsg = 0; + char *zSql; + if( !doInit && file_size(zFile)<0 ){ + fossil_fatal("no such file: %s", zFile); + } + assert( g.db ); + zSql = sqlite3_mprintf("ATTACH %Q AS %Q", zFile, zBName); + if( zSql==0 ) fossil_fatal("out of memory"); + rc = sqlite3_exec(g.db, zSql, 0, 0, &zErrMsg); + sqlite3_free(zSql); + if( rc!=SQLITE_OK || zErrMsg ){ + if( zErrMsg==0 ) zErrMsg = (char*)sqlite3_errmsg(g.db); + fossil_fatal("not a valid bundle: %s", zFile); + } + if( doInit ){ + db_multi_exec(zBundleInit /*works-like:"%w%w"*/, zBName, zBName); + }else{ + sqlite3_stmt *pStmt; + zSql = sqlite3_mprintf("SELECT bcname, bcvalue" + " FROM \"%w\".bconfig", zBName); + if( zSql==0 ) fossil_fatal("out of memory"); + rc = sqlite3_prepare(g.db, zSql, -1, &pStmt, 0); + if( rc ) fossil_fatal("not a valid bundle: %s", zFile); + sqlite3_free(zSql); + sqlite3_finalize(pStmt); + zSql = sqlite3_mprintf("SELECT blobid, uuid, sz, delta, notes, data" + " FROM \"%w\".bblob", zBName); + if( zSql==0 ) fossil_fatal("out of memory"); + rc = sqlite3_prepare(g.db, zSql, -1, &pStmt, 0); + if( rc ) fossil_fatal("not a valid bundle: %s", zFile); + sqlite3_free(zSql); + sqlite3_finalize(pStmt); + } +} + +/* +** fossil bundle ls BUNDLE ?OPTIONS? +** +** Display the content of a bundle in human-readable form. +*/ +static void bundle_ls_cmd(void){ + Stmt q; + sqlite3_int64 sumSz = 0; + sqlite3_int64 sumLen = 0; + int bDetails = find_option("details","l",0)!=0; + verify_all_options(); + if( g.argc!=4 ) usage("ls BUNDLE ?OPTIONS?"); + bundle_attach_file(g.argv[3], "b1", 0); + db_prepare(&q, + "SELECT bcname, bcvalue FROM bconfig" + " WHERE typeof(bcvalue)='text'" + " AND bcvalue NOT GLOB char(0x2a,0x0a,0x2a);" + ); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%s: %s\n", db_column_text(&q,0), db_column_text(&q,1)); + } + db_finalize(&q); + fossil_print("%.78c\n",'-'); + if( bDetails ){ + db_prepare(&q, + "SELECT blobid, substr(uuid,1,10), coalesce(substr(delta,1,10),'')," + " sz, length(data), notes" + " FROM bblob" + ); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%4d %10s %10s %8d %8d %s\n", + db_column_int(&q,0), + db_column_text(&q,1), + db_column_text(&q,2), + db_column_int(&q,3), + db_column_int(&q,4), + db_column_text(&q,5)); + sumSz += db_column_int(&q,3); + sumLen += db_column_int(&q,4); + } + db_finalize(&q); + fossil_print("%27s %8lld %8lld\n", "Total:", sumSz, sumLen); + }else{ + db_prepare(&q, + "SELECT substr(uuid,1,16), notes FROM bblob" + ); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%16s %s\n", + db_column_text(&q,0), + db_column_text(&q,1)); + } + db_finalize(&q); + } +} + +/* +** Implement the "fossil bundle append BUNDLE FILE..." command. Add +** the named files into the BUNDLE. Create the BUNDLE if it does not +** alraedy exist. +*/ +static void bundle_append_cmd(void){ + Blob content, hash; + int i; + Stmt q; + + verify_all_options(); + bundle_attach_file(g.argv[3], "b1", 1); + db_prepare(&q, + "INSERT INTO bblob(blobid, uuid, sz, delta, data, notes) " + "VALUES(NULL, $uuid, $sz, NULL, $data, $filename)"); + db_begin_transaction(); + for(i=4; i<g.argc; i++){ + int sz; + blob_read_from_file(&content, g.argv[i]); + sz = blob_size(&content); + sha1sum_blob(&content, &hash); + blob_compress(&content, &content); + db_bind_text(&q, "$uuid", blob_str(&hash)); + db_bind_int(&q, "$sz", sz); + db_bind_blob(&q, "$data", &content); + db_bind_text(&q, "$filename", g.argv[i]); + db_step(&q); + db_reset(&q); + blob_reset(&content); + blob_reset(&hash); + } + db_end_transaction(0); + db_finalize(&q); +} + +/* +** Identify a subsection of the check-in tree using command-line switches. +** There must be one of the following switch available: +** +** --branch BRANCHNAME All check-ins on the most recent +** instance of BRANCHNAME +** --from TAG1 [--to TAG2] Check-in TAG1 and all primary descendants +** up to and including TAG2 +** --checkin TAG Check-in TAG only +** +** Store the RIDs for all applicable check-ins in the zTab table that +** should already exist. Invoke fossil_fatal() if any kind of error is +** seen. +*/ +void subtree_from_arguments(const char *zTab){ + const char *zBr; + const char *zFrom; + const char *zTo; + const char *zCkin; + int rid = 0, endRid; + + zBr = find_option("branch",0,1); + zFrom = find_option("from",0,1); + zTo = find_option("to",0,1); + zCkin = find_option("checkin",0,1); + if( zCkin ){ + if( zFrom ) fossil_fatal("cannot use both --checkin and --from"); + if( zBr ) fossil_fatal("cannot use both --checkin and --branch"); + rid = symbolic_name_to_rid(zCkin, "ci"); + endRid = rid; + }else{ + endRid = zTo ? name_to_typed_rid(zTo, "ci") : 0; + } + if( zFrom ){ + rid = name_to_typed_rid(zFrom, "ci"); + }else if( zBr ){ + rid = name_to_typed_rid(zBr, "br"); + }else if( zCkin==0 ){ + fossil_fatal("need one of: --branch, --from, --checkin"); + } + db_multi_exec("INSERT OR IGNORE INTO \"%w\" VALUES(%d)", zTab, rid); + if( rid!=endRid ){ + Blob sql; + blob_zero(&sql); + blob_appendf(&sql, + "WITH RECURSIVE child(rid) AS (VALUES(%d) UNION ALL " + " SELECT cid FROM plink, child" + " WHERE plink.pid=child.rid" + " AND plink.isPrim", rid); + if( endRid>0 ){ + double endTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", + endRid); + blob_appendf(&sql, + " AND child.rid!=%d" + " AND (SELECT mtime FROM event WHERE objid=plink.cid)<=%.17g", + endRid, endTime + ); + } + if( zBr ){ + blob_appendf(&sql, + " AND EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0" + " AND value=%Q and rid=plink.cid)", + TAG_BRANCH, zBr); + } + blob_appendf(&sql, ") INSERT OR IGNORE INTO \"%w\" SELECT rid FROM child;", + zTab); + db_multi_exec("%s", blob_str(&sql)/*safe-for-%s*/); + } +} + +/* +** COMMAND: test-subtree +** +** Usage: %fossil test-subtree ?OPTIONS? +** +** Show the subset of check-ins that match the supplied options. This +** command is used to test the subtree_from_options() subroutine in the +** implementation and does not really have any other practical use that +** we know of. +** +** Options: +** --branch BRANCH Include only check-ins on BRANCH +** --from TAG Start the subtree at TAG +** --to TAG End the subtree at TAG +** --checkin TAG The subtree is the single check-in TAG +** --all Include FILE and TAG artifacts +** --exclusive Include FILES exclusively on check-ins +*/ +void test_subtree_cmd(void){ + int bAll = find_option("all",0,0)!=0; + int bExcl = find_option("exclusive",0,0)!=0; + db_find_and_open_repository(0,0); + db_begin_transaction(); + db_multi_exec("CREATE TEMP TABLE tobundle(rid INTEGER PRIMARY KEY);"); + subtree_from_arguments("tobundle"); + verify_all_options(); + if( bAll ) find_checkin_associates("tobundle",bExcl); + describe_artifacts_to_stdout("IN tobundle", 0); + db_end_transaction(1); +} + +/* fossil bundle export BUNDLE ?OPTIONS? +** +** OPTIONS: +** --branch BRANCH --from TAG --to TAG +** --checkin TAG +** --standalone +*/ +static void bundle_export_cmd(void){ + int bStandalone = find_option("standalone",0,0)!=0; + int mnToBundle; /* Minimum RID in the bundle */ + Stmt q; + + /* Decode the arguments (like --branch) that specify which artifacts + ** should be in the bundle */ + db_multi_exec("CREATE TEMP TABLE tobundle(rid INTEGER PRIMARY KEY);"); + subtree_from_arguments("tobundle"); + find_checkin_associates("tobundle", 0); + verify_all_options(); + describe_artifacts("IN tobundle"); + + if( g.argc!=4 ) usage("export BUNDLE ?OPTIONS?"); + /* Create the new bundle */ + bundle_attach_file(g.argv[3], "b1", 1); + db_begin_transaction(); + + /* Add 'mtime' and 'project-code' entries to the bconfig table */ + db_multi_exec( + "INSERT INTO bconfig(bcname,bcvalue)" + " VALUES('mtime',datetime('now'));" + ); + db_multi_exec( + "INSERT INTO bconfig(bcname,bcvalue)" + " SELECT name, value FROM config" + " WHERE name IN ('project-code');" + ); + + /* Directly copy content from the repository into the bundle as long + ** as the repository content is a delta from some other artifact that + ** is also in the bundle. + */ + db_multi_exec( + "REPLACE INTO bblob(blobid,uuid,sz,delta,data,notes) " + " SELECT" + " tobundle.rid," + " blob.uuid," + " blob.size," + " delta.srcid," + " blob.content," + " (SELECT summary FROM description WHERE rid=blob.rid)" + " FROM tobundle, blob, delta" + " WHERE blob.rid=tobundle.rid" + " AND delta.rid=tobundle.rid" + " AND delta.srcid IN tobundle;" + ); + + /* For all the remaining artifacts, we need to construct their deltas + ** manually. + */ + mnToBundle = db_int(0,"SELECT min(rid) FROM tobundle"); + db_prepare(&q, + "SELECT rid FROM tobundle" + " WHERE rid NOT IN (SELECT blobid FROM bblob)" + " ORDER BY +rid;" + ); + while( db_step(&q)==SQLITE_ROW ){ + Blob content; + int rid = db_column_int(&q,0); + int deltaFrom = 0; + + /* Get the raw, uncompressed content of the artifact into content */ + content_get(rid, &content); + + /* Try to find another artifact, not within the bundle, that is a + ** plausible candidate for being a delta basis for the content. Set + ** deltaFrom to the RID of that other artifact. Leave deltaFrom set + ** to zero if the content should not be delta-compressed + */ + if( !bStandalone ){ + if( db_exists("SELECT 1 FROM plink WHERE cid=%d",rid) ){ + deltaFrom = db_int(0, + "SELECT max(cid) FROM plink" + " WHERE cid<%d", mnToBundle); + }else{ + deltaFrom = db_int(0, + "SELECT max(fid) FROM mlink" + " WHERE fnid=(SELECT fnid FROM mlink WHERE fid=%d)" + " AND fid<%d", rid, mnToBundle); + } + } + + /* Try to insert the insert the artifact as a delta + */ + if( deltaFrom ){ + Blob basis, delta; + content_get(deltaFrom, &basis); + blob_delta_create(&basis, &content, &delta); + if( blob_size(&delta)>0.9*blob_size(&content) ){ + deltaFrom = 0; + }else{ + Stmt ins; + blob_compress(&delta, &delta); + db_prepare(&ins, + "REPLACE INTO bblob(blobid,uuid,sz,delta,data,notes)" + " SELECT %d, uuid, size, (SELECT uuid FROM blob WHERE rid=%d)," + " :delta, (SELECT summary FROM description WHERE rid=blob.rid)" + " FROM blob WHERE rid=%d", rid, deltaFrom, rid); + db_bind_blob(&ins, ":delta", &delta); + db_step(&ins); + db_finalize(&ins); + } + blob_reset(&basis); + blob_reset(&delta); + } + + /* If unable to insert the artifact as a delta, insert full-text */ + if( deltaFrom==0 ){ + Stmt ins; + blob_compress(&content, &content); + db_prepare(&ins, + "REPLACE INTO bblob(blobid,uuid,sz,delta,data,notes)" + " SELECT rid, uuid, size, NULL, :content," + " (SELECT summary FROM description WHERE rid=blob.rid)" + " FROM blob WHERE rid=%d", rid); + db_bind_blob(&ins, ":content", &content); + db_step(&ins); + db_finalize(&ins); + } + blob_reset(&content); + } + db_finalize(&q); + + db_end_transaction(0); +} + + +/* +** There is a TEMP table bix(blobid,delta) containing a set of purgeitems +** that need to be transferred to the BLOB table. This routine does +** all items that have srcid=iSrc. The pBasis blob holds the content +** of the source document if iSrc>0. +*/ +static void bundle_import_elements(int iSrc, Blob *pBasis, int isPriv){ + Stmt q; + static Bag busy; + assert( pBasis!=0 || iSrc==0 ); + if( iSrc>0 ){ + if( bag_find(&busy, iSrc) ){ + fossil_fatal("delta loop while uncompressing bundle artifacts"); + } + bag_insert(&busy, iSrc); + } + db_prepare(&q, + "SELECT uuid, data, bblob.delta, bix.blobid" + " FROM bix, bblob" + " WHERE bix.delta=%d" + " AND bix.blobid=bblob.blobid;", + iSrc + ); + while( db_step(&q)==SQLITE_ROW ){ + Blob h1, h2, c1, c2; + int rid; + blob_zero(&h1); + db_column_blob(&q, 0, &h1); + blob_zero(&c1); + db_column_blob(&q, 1, &c1); + blob_uncompress(&c1, &c1); + blob_zero(&c2); + if( db_column_type(&q,2)==SQLITE_TEXT && db_column_bytes(&q,2)==40 ){ + Blob basis; + rid = db_int(0,"SELECT rid FROM blob WHERE uuid=%Q", + db_column_text(&q,2)); + content_get(rid, &basis); + blob_delta_apply(&basis, &c1, &c2); + blob_reset(&basis); + blob_reset(&c1); + }else if( pBasis ){ + blob_delta_apply(pBasis, &c1, &c2); + blob_reset(&c1); + }else{ + c2 = c1; + } + sha1sum_blob(&c2, &h2); + if( blob_compare(&h1, &h2)!=0 ){ + fossil_fatal("SHA1 hash mismatch - wanted %s, got %s", + blob_str(&h1), blob_str(&h2)); + } + blob_reset(&h2); + rid = content_put_ex(&c2, blob_str(&h1), 0, 0, isPriv); + if( rid==0 ){ + fossil_fatal("%s", g.zErrMsg); + }else{ + if( !isPriv ) content_make_public(rid); + content_get(rid, &c1); + manifest_crosslink(rid, &c1, MC_NO_ERRORS); + db_multi_exec("INSERT INTO got(rid) VALUES(%d)",rid); + } + bundle_import_elements(db_column_int(&q,3), &c2, isPriv); + blob_reset(&c2); + } + db_finalize(&q); + if( iSrc>0 ) bag_remove(&busy, iSrc); +} + +/* +** Extract an item from content from the bundle +*/ +static void bundle_extract_item( + int blobid, /* ID of the item to extract */ + Blob *pOut /* Write the content into this blob */ +){ + Stmt q; + Blob x, basis, h1, h2; + static Bag busy; + + db_prepare(&q, "SELECT uuid, delta, data FROM bblob" + " WHERE blobid=%d", blobid); + if( db_step(&q)!=SQLITE_ROW ){ + db_finalize(&q); + fossil_fatal("no such item: %d", blobid); + } + if( bag_find(&busy, blobid) ) fossil_fatal("delta loop"); + blob_zero(&x); + db_column_blob(&q, 2, &x); + blob_uncompress(&x, &x); + if( db_column_type(&q,1)==SQLITE_INTEGER ){ + bundle_extract_item(db_column_int(&q,1), &basis); + blob_delta_apply(&basis, &x, pOut); + blob_reset(&basis); + blob_reset(&x); + }else if( db_column_type(&q,1)==SQLITE_TEXT ){ + int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", + db_column_text(&q,1)); + if( rid==0 ){ + fossil_fatal("cannot find delta basis %s", db_column_text(&q,1)); + } + content_get(rid, &basis); + db_column_blob(&q, 2, &x); + blob_delta_apply(&basis, &x, pOut); + blob_reset(&basis); + blob_reset(&x); + }else{ + *pOut = x; + } + blob_zero(&h1); + db_column_blob(&q, 0, &h1); + sha1sum_blob(pOut, &h2); + if( blob_compare(&h1, &h2)!=0 ){ + fossil_fatal("SHA1 hash mismatch - wanted %s, got %s", + blob_str(&h1), blob_str(&h2)); + } + blob_reset(&h1); + blob_reset(&h2); + bag_remove(&busy, blobid); + db_finalize(&q); +} + +/* fossil bundle cat BUNDLE UUID... +** +** Write elements of a bundle on standard output +*/ +static void bundle_cat_cmd(void){ + int i; + Blob x; + verify_all_options(); + if( g.argc<5 ) usage("cat BUNDLE UUID..."); + bundle_attach_file(g.argv[3], "b1", 1); + blob_zero(&x); + for(i=4; i<g.argc; i++){ + int blobid = db_int(0,"SELECT blobid FROM bblob WHERE uuid LIKE '%q%%'", + g.argv[i]); + if( blobid==0 ){ + fossil_fatal("no such artifact in bundle: %s", g.argv[i]); + } + bundle_extract_item(blobid, &x); + blob_write_to_file(&x, "-"); + blob_reset(&x); + } +} + + +/* fossil bundle import BUNDLE ?OPTIONS? +** +** Attempt to import the changes contained in BUNDLE. Make the change +** private so that they do not sync. +** +** OPTIONS: +** --force Import even if the project-code does not match +** --publish Imported changes are not private +*/ +static void bundle_import_cmd(void){ + int forceFlag = find_option("force","f",0)!=0; + int isPriv = find_option("publish",0,0)==0; + char *zMissingDeltas; + verify_all_options(); + if ( g.argc!=4 ) usage("import BUNDLE ?OPTIONS?"); + bundle_attach_file(g.argv[3], "b1", 1); + + /* Only import a bundle that was generated from a repo with the same + ** project code, unless the --force flag is true */ + if( !forceFlag ){ + if( !db_exists("SELECT 1 FROM config, bconfig" + " WHERE config.name='project-code'" + " AND bconfig.bcname='project-code'" + " AND config.value=bconfig.bcvalue;") + ){ + fossil_fatal("project-code in the bundle does not match the " + "repository project code. (override with --force)."); + } + } + + /* If the bundle contains deltas with a basis that is external to the + ** bundle and those external basis files are missing from the local + ** repo, then the delta encodings cannot be decoded and the bundle cannot + ** be extracted. */ + zMissingDeltas = db_text(0, + "SELECT group_concat(substr(delta,1,10),' ')" + " FROM bblob" + " WHERE typeof(delta)='text' AND length(delta)=40" + " AND NOT EXISTS(SELECT 1 FROM blob WHERE uuid=bblob.delta)"); + if( zMissingDeltas && zMissingDeltas[0] ){ + fossil_fatal("delta basis artifacts not found in repository: %s", + zMissingDeltas); + } + + db_begin_transaction(); + db_multi_exec( + "CREATE TEMP TABLE bix(" + " blobid INTEGER PRIMARY KEY," + " delta INTEGER" + ");" + "CREATE INDEX bixdelta ON bix(delta);" + "INSERT INTO bix(blobid,delta)" + " SELECT blobid," + " CASE WHEN typeof(delta)=='integer'" + " THEN delta ELSE 0 END" + " FROM bblob" + " WHERE NOT EXISTS(SELECT 1 FROM blob WHERE uuid=bblob.uuid AND size>=0);" + "CREATE TEMP TABLE got(rid INTEGER PRIMARY KEY ON CONFLICT IGNORE);" + ); + manifest_crosslink_begin(); + bundle_import_elements(0, 0, isPriv); + manifest_crosslink_end(0); + describe_artifacts_to_stdout("IN got", "Imported content:"); + db_end_transaction(0); +} + +/* fossil bundle purge BUNDLE +** +** Try to undo a prior "bundle import BUNDLE". +** +** If the --force option is omitted, then this will only work if +** there have been no check-ins or tags added that use the import. +** +** This routine never removes content that is not already in the bundle +** so the bundle serves as a backup. The purge can be undone using +** "fossil bundle import BUNDLE". +*/ +static void bundle_purge_cmd(void){ + int bForce = find_option("force",0,0)!=0; + int bTest = find_option("test",0,0)!=0; /* Undocumented --test option */ + const char *zFile = g.argv[3]; + verify_all_options(); + if ( g.argc!=4 ) usage("purge BUNDLE ?OPTIONS?"); + bundle_attach_file(zFile, "b1", 0); + db_begin_transaction(); + + /* Find all check-ins of the bundle */ + db_multi_exec( + "CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY);" + "INSERT OR IGNORE INTO ok SELECT blob.rid FROM bblob, blob, plink" + " WHERE bblob.uuid=blob.uuid" + " AND plink.cid=blob.rid;" + ); + + /* Check to see if new check-ins have been committed to check-ins in + ** the bundle. Do not allow the purge if that is true and if --force + ** is omitted. + */ + if( !bForce ){ + Stmt q; + int n = 0; + db_prepare(&q, + "SELECT cid FROM plink WHERE pid IN ok AND cid NOT IN ok" + ); + while( db_step(&q)==SQLITE_ROW ){ + whatis_rid(db_column_int(&q,0),0); + fossil_print("%.78c\n", '-'); + n++; + } + db_finalize(&q); + if( n>0 ){ + fossil_fatal("check-ins above are derived from check-ins in the bundle."); + } + } + + /* Find all files associated with those check-ins that are used + ** nowhere else. */ + find_checkin_associates("ok", 1); + + /* Check to see if any associated files are not in the bundle. Issue + ** an error if there are any, unless --force is used. + */ + if( !bForce ){ + db_multi_exec( + "CREATE TEMP TABLE err1(rid INTEGER PRIMARY KEY);" + "INSERT INTO err1 " + " SELECT blob.rid FROM ok CROSS JOIN blob" + " WHERE blob.rid=ok.rid" + " AND blob.uuid NOT IN (SELECT uuid FROM bblob);" + ); + if( db_changes() ){ + describe_artifacts_to_stdout("IN err1", 0); + fossil_fatal("artifacts above associated with bundle check-ins " + " are not in the bundle"); + }else{ + db_multi_exec("DROP TABLE err1;"); + } + } + + if( bTest ){ + describe_artifacts_to_stdout( + "IN (SELECT blob.rid FROM ok, blob, bblob" + " WHERE blob.rid=ok.rid AND blob.uuid=bblob.uuid)", + "Purged artifacts found in the bundle:"); + describe_artifacts_to_stdout( + "IN (SELECT blob.rid FROM ok, blob" + " WHERE blob.rid=ok.rid " + " AND blob.uuid NOT IN (SELECT uuid FROM bblob))", + "Purged artifacts NOT in the bundle:"); + describe_artifacts_to_stdout( + "IN (SELECT blob.rid FROM bblob, blob" + " WHERE blob.uuid=bblob.uuid " + " AND blob.rid NOT IN ok)", + "Artifacts in the bundle but not purged:"); + }else{ + purge_artifact_list("ok",0,0); + } + db_end_transaction(0); +} + +/* +** COMMAND: bundle +** +** Usage: %fossil bundle SUBCOMMAND ARGS... +** +** fossil bundle append BUNDLE FILE... +** +** Add files named on the command line to BUNDLE. This subcommand has +** little practical use and is mostly intended for testing. +** +** fossil bundle cat BUNDLE UUID... +** +** Extract one or more artifacts from the bundle and write them +** consecutively on standard output. This subcommand was designed +** for testing and introspection of bundles and is not something +** commonly used. +** +** fossil bundle export BUNDLE ?OPTIONS? +** +** Generate a new bundle, in the file named BUNDLE, that contains a +** subset of the check-ins in the repository (usually a single branch) +** described by the --branch, --from, --to, and/or --checkin options, +** at least one of which is required. If BUNDLE already exists, the +** specified content is added to the bundle. +** +** --branch BRANCH Package all check-ins on BRANCH. +** --from TAG1 --to TAG2 Package check-ins between TAG1 and TAG2. +** --checkin TAG Package the single check-in TAG +** --standalone Do no use delta-encoding against +** artifacts not in the bundle +** +** fossil bundle extend BUNDLE +** +** The BUNDLE must already exist. This subcommand adds to the bundle +** any check-ins that are descendants of check-ins already in the bundle, +** and any tags that apply to artifacts in the bundle. +** +** fossil bundle import BUNDLE ?--publish? +** +** Import all content from BUNDLE into the repository. By default, the +** imported files are private and will not sync. Use the --publish +** option makes the import public. +** +** fossil bundle ls BUNDLE +** +** List the contents of BUNDLE on standard output +** +** fossil bundle purge BUNDLE +** +** Remove from the repository all files that are used exclusively +** by check-ins in BUNDLE. This has the effect of undoing a +** "fossil bundle import". +** +** SUMMARY: +** fossil bundle append BUNDLE FILE... Add files to BUNDLE +** fossil bundle cat BUNDLE UUID... Extract file from BUNDLE +** fossil bundle export BUNDLE ?OPTIONS? Create a new BUNDLE +** --branch BRANCH --from TAG1 --to TAG2 Check-ins to include +** --checkin TAG Use only check-in TAG +** --standalone Omit dependencies +** fossil bundle extend BUNDLE Update with newer content +** fossil bundle import BUNDLE ?OPTIONS? Import a bundle +** --publish Publish the import +** --force Cross-repo import +** fossil bundle ls BUNDLE List content of a bundle +** fossil bundle purge BUNDLE Undo an import +** +** See also: publish +*/ +void bundle_cmd(void){ + const char *zSubcmd; + int n; + if( g.argc<4 ) usage("SUBCOMMAND BUNDLE ?OPTIONS?"); + zSubcmd = g.argv[2]; + db_find_and_open_repository(0,0); + n = (int)strlen(zSubcmd); + if( strncmp(zSubcmd, "append", n)==0 ){ + bundle_append_cmd(); + }else if( strncmp(zSubcmd, "cat", n)==0 ){ + bundle_cat_cmd(); + }else if( strncmp(zSubcmd, "export", n)==0 ){ + bundle_export_cmd(); + }else if( strncmp(zSubcmd, "extend", n)==0 ){ + fossil_fatal("not yet implemented"); + }else if( strncmp(zSubcmd, "import", n)==0 ){ + bundle_import_cmd(); + }else if( strncmp(zSubcmd, "ls", n)==0 ){ + bundle_ls_cmd(); + }else if( strncmp(zSubcmd, "purge", n)==0 ){ + bundle_purge_cmd(); + }else{ + fossil_fatal("unknown subcommand for bundle: %s", zSubcmd); + } +} ADDED src/cache.c Index: src/cache.c ================================================================== --- /dev/null +++ src/cache.c @@ -0,0 +1,402 @@ +/* +** Copyright (c) 2014 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) +** +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@sqlite.org +** +******************************************************************************* +** +** This file implements a cache for expense operations such as +** /zip and /tarball. +*/ +#include "config.h" +#include <sqlite3.h> +#include "cache.h" + +/* +** Construct the name of the repository cache file +*/ +static char *cacheName(void){ + int i; + int n; + + if( g.zRepositoryName==0 ) return 0; + n = (int)strlen(g.zRepositoryName); + for(i=n-1; i>=0; i--){ + if( g.zRepositoryName[i]=='/' ){ i = n; break; } + if( g.zRepositoryName[i]=='.' ) break; + } + if( i<0 ) i = n; + return mprintf("%.*s.cache", i, g.zRepositoryName); +} + +/* +** Attempt to open the cache database, if such a database exists. +** Make sure the cache table exists within that database. +*/ +static sqlite3 *cacheOpen(int bForce){ + char *zDbName; + sqlite3 *db = 0; + int rc; + i64 sz; + + zDbName = cacheName(); + if( zDbName==0 ) return 0; + if( bForce==0 ){ + sz = file_size(zDbName); + if( sz<=0 ){ + fossil_free(zDbName); + return 0; + } + } + rc = sqlite3_open(zDbName, &db); + fossil_free(zDbName); + if( rc ){ + sqlite3_close(db); + return 0; + } + rc = sqlite3_exec(db, + "PRAGMA page_size=8192;" + "CREATE TABLE IF NOT EXISTS blob(id INTEGER PRIMARY KEY, data BLOB);" + "CREATE TABLE IF NOT EXISTS cache(" + "key TEXT PRIMARY KEY," /* Key used to access the cache */ + "id INT REFERENCES blob," /* The cache content */ + "sz INT," /* Size of content in bytes */ + "tm INT," /* Last access time (unix timestampe) */ + "nref INT" /* Number of uses */ + ");" + "CREATE TRIGGER IF NOT EXISTS cacheDel AFTER DELETE ON cache BEGIN" + " DELETE FROM blob WHERE id=OLD.id;" + "END;", + 0, 0, 0); + if( rc!=SQLITE_OK ){ + sqlite3_close(db); + return 0; + } + return db; +} + +/* +** Attempt to construct a prepared statement for the cache database. +*/ +static sqlite3_stmt *cacheStmt(sqlite3 *db, const char *zSql){ + sqlite3_stmt *pStmt = 0; + int rc; + + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc ){ + sqlite3_finalize(pStmt); + pStmt = 0; + } + return pStmt; +} + +/* +** This routine implements an SQL function that renders a large integer +** compactly: ex: 12.3MB +*/ +static void cache_sizename( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + char zBuf[30]; + double v, x; + assert( argc==1 ); + v = sqlite3_value_double(argv[0]); + x = v<0.0 ? -v : v; + if( x>=1e9 ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%.1fGB", v/1e9); + }else if( x>=1e6 ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%.1fMB", v/1e6); + }else if( x>=1e3 ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%.1fKB", v/1e3); + }else{ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%gB", v); + } + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); +} + +/* +** Register the sizename() SQL function with the SQLite database +** connection. +*/ +static void cache_register_sizename(sqlite3 *db){ + sqlite3_create_function(db, "sizename", 1, SQLITE_UTF8, 0, + cache_sizename, 0, 0); +} + +/* +** Attempt to write pContent into the cache. If the cache file does +** not exist, then this routine is a no-op. Older cache entries might +** be deleted. +*/ +void cache_write(Blob *pContent, const char *zKey){ + sqlite3 *db; + sqlite3_stmt *pStmt; + int rc = 0; + int nKeep; + + db = cacheOpen(0); + if( db==0 ) return; + sqlite3_busy_timeout(db, 10000); + sqlite3_exec(db, "BEGIN IMMEDIATE", 0, 0, 0); + pStmt = cacheStmt(db, "INSERT INTO blob(data) VALUES(?1)"); + if( pStmt==0 ) goto cache_write_end; + sqlite3_bind_blob(pStmt, 1, blob_buffer(pContent), blob_size(pContent), + SQLITE_STATIC); + if( sqlite3_step(pStmt)!=SQLITE_DONE ) goto cache_write_end; + sqlite3_finalize(pStmt); + pStmt = cacheStmt(db, + "INSERT OR IGNORE INTO cache(key,sz,tm,nref,id)" + "VALUES(?1,?2,strftime('%s','now'),1,?3)" + ); + if( pStmt==0 ) goto cache_write_end; + sqlite3_bind_text(pStmt, 1, zKey, -1, SQLITE_STATIC); + sqlite3_bind_int(pStmt, 2, blob_size(pContent)); + sqlite3_bind_int(pStmt, 3, sqlite3_last_insert_rowid(db)); + if( sqlite3_step(pStmt)!=SQLITE_DONE) goto cache_write_end; + rc = sqlite3_changes(db); + + /* If the write was successful, truncate the cache to keep at most + ** max-cache-entry entries in the cache */ + if( rc ){ + nKeep = db_get_int("max-cache-entry",10); + sqlite3_finalize(pStmt); + pStmt = cacheStmt(db, + "DELETE FROM cache WHERE rowid IN (" + "SELECT rowid FROM cache ORDER BY tm DESC" + " LIMIT -1 OFFSET ?1)"); + if( pStmt ){ + sqlite3_bind_int(pStmt, 1, nKeep); + sqlite3_step(pStmt); + } + } + +cache_write_end: + sqlite3_finalize(pStmt); + sqlite3_exec(db, rc ? "COMMIT" : "ROLLBACK", 0, 0, 0); + sqlite3_close(db); +} + +/* +** Attempt to read content out of the cache with the given zKey. Return +** non-zero on success and zero if unable to locate the content. +** +** Possible reasons for returning zero: +** (1) This server does not implement a cache +** (2) The requested element is not in the cache +*/ +int cache_read(Blob *pContent, const char *zKey){ + sqlite3 *db; + sqlite3_stmt *pStmt; + int rc = 0; + + db = cacheOpen(0); + if( db==0 ) return 0; + sqlite3_busy_timeout(db, 10000); + sqlite3_exec(db, "BEGIN IMMEDIATE", 0, 0, 0); + pStmt = cacheStmt(db, + "SELECT blob.data FROM cache, blob" + " WHERE cache.key=?1 AND cache.id=blob.id"); + if( pStmt==0 ) goto cache_read_done; + sqlite3_bind_text(pStmt, 1, zKey, -1, SQLITE_STATIC); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + blob_append(pContent, sqlite3_column_blob(pStmt, 0), + sqlite3_column_bytes(pStmt, 0)); + rc = 1; + sqlite3_reset(pStmt); + pStmt = cacheStmt(db, + "UPDATE cache SET nref=nref+1, tm=strftime('%s','now')" + " WHERE key=?1"); + if( pStmt ){ + sqlite3_bind_text(pStmt, 1, zKey, -1, SQLITE_STATIC); + sqlite3_step(pStmt); + } + } + sqlite3_finalize(pStmt); +cache_read_done: + sqlite3_exec(db, "COMMIT", 0, 0, 0); + sqlite3_close(db); + return rc; +} + +/* +** Create a cache database for the current repository if no such +** database already exists. +*/ +void cache_initialize(void){ + sqlite3_close(cacheOpen(1)); +} + +/* +** COMMAND: cache* +** Usage: %fossil cache SUBCOMMAND +** +** Manage the cache used for potentially expensive web pages such as +** /zip and /tarball. SUBCOMMAND an be: +** +** clear Remove all entries from the cache. +** +** init Create the cache file if it does not already exists. +** +** list|ls List the keys and content sizes and other stats for +** all entries currently in the cache +** +** status Show a summary of cache status. +** +** The cache is stored in a file that is distinct from the repository +** but that is held in the same directory as the repository. To cache +** file can be deleted in order to completely disable the cache. +*/ +void cache_cmd(void){ + const char *zCmd; + int nCmd; + sqlite3 *db; + sqlite3_stmt *pStmt; + + db_find_and_open_repository(0,0); + zCmd = g.argc>=3 ? g.argv[2] : ""; + nCmd = (int)strlen(zCmd); + if( nCmd<=1 ){ + fossil_fatal("Usage: %s cache SUBCOMMAND", g.argv[0]); + } + if( strncmp(zCmd, "init", nCmd)==0 ){ + db = cacheOpen(0); + sqlite3_close(db); + if( db ){ + fossil_print("cache already exists in file %z\n", cacheName()); + }else{ + db = cacheOpen(1); + sqlite3_close(db); + if( db ){ + fossil_print("cache created in file %z\n", cacheName()); + }else{ + fossil_fatal("unable to create cache file %z", cacheName()); + } + } + }else if( strncmp(zCmd, "clear", nCmd)==0 ){ + db = cacheOpen(0); + if( db ){ + sqlite3_exec(db, "DELETE FROM cache; DELETE FROM blob; VACUUM;",0,0,0); + sqlite3_close(db); + fossil_print("cache cleared\n"); + }else{ + fossil_print("nothing to clear; cache does not exist\n"); + } + }else if(( strncmp(zCmd, "list", nCmd)==0 ) || ( strncmp(zCmd, "ls", nCmd)==0 )){ + db = cacheOpen(0); + if( db==0 ){ + fossil_print("cache does not exist\n"); + }else{ + int nEntry = 0; + char *zDbName = cacheName(); + cache_register_sizename(db); + pStmt = cacheStmt(db, + "SELECT key, sizename(sz), nRef, datetime(tm,'unixepoch')" + " FROM cache" + " ORDER BY tm DESC" + ); + if( pStmt ){ + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + fossil_print("%s %4d %8s %s\n", + sqlite3_column_text(pStmt, 3), + sqlite3_column_int(pStmt, 2), + sqlite3_column_text(pStmt, 1), + sqlite3_column_text(pStmt, 0)); + nEntry++; + } + sqlite3_finalize(pStmt); + } + sqlite3_close(db); + fossil_print("Entries: %d Cache-file Size: %lld\n", + nEntry, file_size(zDbName)); + fossil_free(zDbName); + } + }else if( strncmp(zCmd, "status", nCmd)==0 ){ + fossil_print("TBD...\n"); + }else{ + fossil_fatal("Unknown subcommand \"%s\"." + " Should be one of: clear init list status", zCmd); + } +} + +/* +** WEBPAGE: cachestat +** +** Show information about the webpage cache +*/ +void cache_page(void){ + sqlite3 *db; + sqlite3_stmt *pStmt; + char zBuf[100]; + + login_check_credentials(); + if( !g.perm.Setup ){ login_needed(0); return; } + style_header("Web Cache Status"); + db = cacheOpen(0); + if( db==0 ){ + @ The web-page cache is disabled for this repository + }else{ + char *zDbName = cacheName(); + cache_register_sizename(db); + pStmt = cacheStmt(db, + "SELECT key, sizename(sz), nRef, datetime(tm,'unixepoch')" + " FROM cache" + " ORDER BY tm DESC" + ); + if( pStmt ){ + @ <ol> + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const unsigned char *zName = sqlite3_column_text(pStmt,0); + @ <li><p>%z(href("%R/cacheget?key=%T",zName))%h(zName)</a><br> + @ size: %s(sqlite3_column_text(pStmt,1)) + @ hit-count: %d(sqlite3_column_int(pStmt,2)) + @ last-access: %s(sqlite3_column_text(pStmt,3))</p></li> + } + sqlite3_finalize(pStmt); + @ </ol> + } + zDbName = cacheName(); + bigSizeName(sizeof(zBuf), zBuf, file_size(zDbName)); + @ <p>cache-file name: %h(zDbName)</p> + @ <p>cache-file size: %s(zBuf)</p> + fossil_free(zDbName); + sqlite3_close(db); + } + style_footer(); +} + +/* +** WEBPAGE: cacheget +** +** Usage: /cacheget?key=KEY +** +** Download a single entry for the cache, identified by KEY. +** This page is normally a hyperlink from the /cachestat page. +*/ +void cache_getpage(void){ + const char *zKey; + Blob content; + + login_check_credentials(); + if( !g.perm.Setup ){ login_needed(0); return; } + zKey = PD("key",""); + blob_zero(&content); + if( cache_read(&content, zKey)==0 ){ + style_header("Cache Download Error"); + @ The cache does not contain any entry with this key: "%h(zKey)" + style_footer(); + return; + } + cgi_set_content(&content); + cgi_set_content_type("application/x-compressed"); +} Index: src/captcha.c ================================================================== --- src/captcha.c +++ src/captcha.c @@ -17,22 +17,22 @@ ** ** This file contains code to a simple text-based CAPTCHA. Though easily ** defeated by a sophisticated attacker, this CAPTCHA does at least make ** scripting attacks more difficult. */ -#include <assert.h> #include "config.h" +#include <assert.h> #include "captcha.h" #if INTERFACE #define CAPTCHA 3 /* Which captcha rendering to use */ #endif /* ** Convert a hex digit into a value between 0 and 15 */ -static int hexValue(char c){ +int hex_digit_value(char c){ if( c>='0' && c<='9' ){ return c - '0'; }else if( c>='a' && c<='f' ){ return c - 'a' + 10; }else if( c>='A' && c<='F' ){ @@ -75,11 +75,11 @@ int i, j, k, m; k = 0; for(i=0; i<6; i++){ for(j=0; zPw[j]; j++){ - unsigned char v = hexValue(zPw[j]); + unsigned char v = hex_digit_value(zPw[j]); v = (aFont1[v] >> ((5-i)*4)) & 0xf; for(m=8; m>=1; m = m>>1){ if( v & m ){ z[k++] = 'X'; z[k++] = 'X'; @@ -92,11 +92,11 @@ z[k++] = ' '; } z[k++] = '\n'; } z[k] = 0; - return z; + return z; } #endif /* CAPTCHA==1 */ #if CAPTCHA==2 @@ -148,11 +148,11 @@ "|__ |", " / / ", " /_/ ", /* 8 */ - " ___ ", + " ___ ", "( _ )", "/ _ \\", "\\___/", /* 9 */ @@ -209,20 +209,20 @@ const char *zChar; k = 0; for(i=0; i<4; i++){ for(j=0; zPw[j]; j++){ - unsigned char v = hexValue(zPw[j]); + unsigned char v = hex_digit_value(zPw[j]); zChar = azFont2[4*v + i]; for(m=0; zChar[m]; m++){ z[k++] = zChar[m]; } } z[k++] = '\n'; } z[k] = 0; - return z; + return z; } #endif /* CAPTCHA==2 */ #if CAPTCHA==3 static const char *const azFont3[] = { @@ -231,123 +231,123 @@ " / _ \\ ", "| | | |", "| | | |", "| |_| |", " \\___/ ", - + /* 1 */ " __ ", "/_ |", " | |", " | |", " | |", " |_|", - + /* 2 */ " ___ ", "|__ \\ ", " ) |", " / / ", " / /_ ", "|____|", - + /* 3 */ " ____ ", "|___ \\ ", " __) |", " |__ < ", " ___) |", "|____/ ", - + /* 4 */ " _ _ ", "| || | ", "| || |_ ", "|__ _|", " | | ", " |_| ", - + /* 5 */ " _____ ", "| ____|", "| |__ ", "|___ \\ ", " ___) |", "|____/ ", - + /* 6 */ " __ ", " / / ", " / /_ ", "| '_ \\ ", "| (_) |", " \\___/ ", - + /* 7 */ " ______ ", "|____ |", " / / ", " / / ", " / / ", " /_/ ", - + /* 8 */ " ___ ", " / _ \\ ", "| (_) |", " > _ < ", "| (_) |", " \\___/ ", - + /* 9 */ " ___ ", " / _ \\ ", "| (_) |", " \\__, |", " / / ", " /_/ ", - + /* A */ " ", " /\\ ", " / \\ ", " / /\\ \\ ", " / ____ \\ ", "/_/ \\_\\", - + /* B */ " ____ ", "| _ \\ ", "| |_) |", "| _ < ", "| |_) |", "|____/ ", - + /* C */ " _____ ", " / ____|", "| | ", "| | ", "| |____ ", " \\_____|", - + /* D */ " _____ ", "| __ \\ ", "| | | |", "| | | |", "| |__| |", "|_____/ ", - + /* E */ " ______ ", "| ____|", "| |__ ", "| __| ", "| |____ ", "|______|", - + /* F */ " ______ ", "| ____|", "| |__ ", "| __| ", @@ -369,11 +369,11 @@ k = 0; for(i=0; i<6; i++){ x = 0; for(j=0; zPw[j]; j++){ - unsigned char v = hexValue(zPw[j]); + unsigned char v = hex_digit_value(zPw[j]); x = (x<<4) + v; switch( x ){ case 0x7a: case 0xfa: y = 3; @@ -408,11 +408,11 @@ } } z[k++] = '\n'; } z[k] = 0; - return z; + return z; } #endif /* CAPTCHA==3 */ /* ** COMMAND: test-captcha @@ -448,11 +448,11 @@ /* ** Translate a captcha seed value into the captcha password string. ** The returned string is static and overwritten on each call to ** this function. */ -char const *captcha_decode(unsigned int seed){ +const char *captcha_decode(unsigned int seed){ const char *zSecret; const char *z; Blob b; static char zRes[20]; @@ -478,16 +478,15 @@ ** Return true if a CAPTCHA is required for editing wiki or tickets or for ** adding attachments. ** ** A CAPTCHA is required in those cases if the user is not logged in (if they ** are user "nobody") and if the "require-captcha" setting is true. The -** "require-captcha" setting is controlled on the Admin/Access page. It +** "require-captcha" setting is controlled on the Admin/Access page. It ** defaults to true. */ int captcha_needed(void){ - if( g.zLogin!=0 ) return 0; - return db_get_boolean("require-captcha", 1); + return login_is_nobody() && db_get_boolean("require-captcha", 1); } /* ** If a captcha is required but the correct captcha code is not supplied ** in the query parameters, then return false (0). @@ -518,11 +517,11 @@ char c = zEntered[i]; if( c>='A' && c<='F' ) c += 'a' - 'A'; if( c=='O' ) c = '0'; z[i] = c; } - if( memcmp(zDecode,z,8)!=0 ) return 0; + if( strncmp(zDecode,z,8)!=0 ) return 0; return 1; } /* ** Generate a captcha display together with the necessary hidden parameter Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -52,18 +52,26 @@ */ #define P(x) cgi_parameter((x),0) #define PD(x,y) cgi_parameter((x),(y)) #define PT(x) cgi_parameter_trimmed((x),0) #define PDT(x,y) cgi_parameter_trimmed((x),(y)) +#define PB(x) cgi_parameter_boolean(x) /* ** Destinations for output text. */ #define CGI_HEADER 0 #define CGI_BODY 1 +/* +** Flags for SSH HTTP clients +*/ +#define CGI_SSH_CLIENT 0x0001 /* Client is SSH */ +#define CGI_SSH_COMPAT 0x0002 /* Compat for old SSH transport */ +#define CGI_SSH_FOSSIL 0x0004 /* Use new Fossil SSH transport */ + #endif /* INTERFACE */ /* ** The HTTP reply is generated in two pieces: the header and the body. ** These pieces are generated separately because they are not necessary @@ -101,10 +109,13 @@ ** if it does and false if it does not. */ int cgi_header_contains(const char *zNeedle){ return strstr(blob_str(&cgiContent[0]), zNeedle)!=0; } +int cgi_body_contains(const char *zNeedle){ + return strstr(blob_str(&cgiContent[1]), zNeedle)!=0; +} /* ** Append reply content to what already exists. */ void cgi_append_content(const char *zData, int nAmt){ @@ -259,14 +270,24 @@ zTok = strtok_r(0, ",\"",&zPos)){} fossil_free(zBuf); if(zTok) return 1; } } - + return 0; } #endif + +/* +** Return true if the response should be sent with Content-Encoding: gzip. +*/ +static int is_gzippable(void){ + if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0; + return strncmp(zContentType, "text/", 5)==0 + || sqlite3_strglob("application/*xml", zContentType)==0 + || sqlite3_strglob("application/*javascript", zContentType)==0; +} /* ** Do a normal HTTP reply */ void cgi_reply(void){ @@ -289,10 +310,11 @@ if( g.fullHttpReply ){ fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus); fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0))); fprintf(g.httpOut, "Connection: close\r\n"); + fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n"); }else{ fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus); } if( blob_size(&extraHeader)>0 ){ @@ -324,13 +346,11 @@ ** case of most /getfile calls for specific versions, the only way the ** content changes is if someone breaks the SCM. And if that happens, a ** stale cache is the least of the problem. So we provide an Expires ** header set to a reasonable period (default: one week). */ - /*time_t expires = time(0) + atoi(db_config("constant_expires","604800"));*/ - time_t expires = time(0) + 604800; - fprintf(g.httpOut, "Expires: %s\r\n", cgi_rfc822_datestamp(expires)); + fprintf(g.httpOut, "Cache-control: max-age=28800\r\n"); }else{ fprintf(g.httpOut, "Cache-control: no-cache\r\n"); } /* Content intended for logged in users should only be cached in @@ -341,10 +361,22 @@ cgi_combine_header_and_body(); blob_compress(&cgiContent[0], &cgiContent[0]); } if( iReplyStatus != 304 ) { + if( is_gzippable() ){ + int i; + gzip_begin(0); + for( i=0; i<2; i++ ){ + int size = blob_size(&cgiContent[i]); + if( size>0 ) gzip_step(blob_buffer(&cgiContent[i]), size); + blob_reset(&cgiContent[i]); + } + gzip_finish(&cgiContent[0]); + fprintf(g.httpOut, "Content-Encoding: gzip\r\n"); + fprintf(g.httpOut, "Vary: Accept-Encoding\r\n"); + } total_size = blob_size(&cgiContent[0]) + blob_size(&cgiContent[1]); fprintf(g.httpOut, "Content-Length: %d\r\n", total_size); }else{ total_size = 0; } @@ -405,11 +437,12 @@ static int seqQP = 0; /* Sequence numbers */ static struct QParam { /* One entry for each query parameter or cookie */ const char *zName; /* Parameter or cookie name */ const char *zValue; /* Value of the query parameter or cookie */ int seq; /* Order of insertion */ - int isQP; /* True for query parameters */ + char isQP; /* True for query parameters */ + char cTag; /* Tag on query parameters */ } *aParamQP; /* An array of all parameters and cookies */ /* ** Add another query parameter or cookie to the parameter set. ** zName is the name of the query parameter or cookie and zValue @@ -432,10 +465,11 @@ if( g.fHttpTrace ){ fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue); } aParamQP[nUsedQP].seq = seqQP++; aParamQP[nUsedQP].isQP = isQP; + aParamQP[nUsedQP].cTag = 0; nUsedQP++; sortQP = 1; } /* @@ -460,19 +494,30 @@ return; } } cgi_set_parameter_nocopy(zName, zValue, 0); } +void cgi_replace_query_parameter(const char *zName, const char *zValue){ + int i; + for(i=0; i<nUsedQP; i++){ + if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ + aParamQP[i].zValue = zValue; + assert( aParamQP[i].isQP ); + return; + } + } + cgi_set_parameter_nocopy(zName, zValue, 1); +} /* ** Add a query parameter. The zName portion is fixed but a copy ** must be made of zValue. */ void cgi_setenv(const char *zName, const char *zValue){ cgi_set_parameter_nocopy(zName, mprintf("%s",zValue), 0); } - + /* ** Add a list of query parameters or cookies to the parameter set. ** ** Each parameter is of the form NAME=VALUE. Both the NAME and the @@ -586,11 +631,11 @@ break; } } *pz = &z[i]; get_line_from_string(pz, pLen); - return z; + return z; } /* ** Tokenize a line of text into as many as nArg tokens. Make ** azArg[] point to the start of each token. @@ -693,11 +738,11 @@ cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z, 1); } } } } - } + } } #ifdef FOSSIL_ENABLE_JSON /* @@ -721,11 +766,11 @@ else { CgiPostReadState * st = (CgiPostReadState *)state; if( st->pos >= st->len ){ *n = 0; return 0; - } else if( !*n || ((st->pos + *n) > st->len) ){ + }else if( !*n || ((st->pos + *n) > st->len) ){ return cson_rc.RangeError; }else{ unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh ); if( ! rsz ){ *n = rsz; @@ -823,26 +868,50 @@ /* ** Initialize the query parameter database. Information is pulled from ** the QUERY_STRING environment variable (if it exists), from standard ** input if there is POST data, and from HTTP_COOKIE. +** +** REQUEST_URI, PATH_INFO, and SCRIPT_NAME are related as follows: +** +** REQUEST_URI == SCRIPT_NAME + PATH_INFO +** +** Where "+" means concatenate. Fossil requires SCRIPT_NAME. If +** REQUEST_URI is provided but PATH_INFO is not, then PATH_INFO is +** computed from REQUEST_URI and SCRIPT_NAME. If PATH_INFO is provided +** but REQUEST_URI is not, then compute REQUEST_URI from PATH_INFO and +** SCRIPT_NAME. If neither REQUEST_URI nor PATH_INFO are provided, then +** assume that PATH_INFO is an empty string and set REQUEST_URI equal +** to PATH_INFO. +** +** SCGI typically omits PATH_INFO. CGI sometimes omits REQUEST_URI and +** PATH_INFO when it is empty. */ void cgi_init(void){ char *z; const char *zType; int len; const char *zRequestUri = cgi_parameter("REQUEST_URI",0); const char *zScriptName = cgi_parameter("SCRIPT_NAME",0); + const char *zPathInfo = cgi_parameter("PATH_INFO",0); #ifdef FOSSIL_ENABLE_JSON json_main_bootstrap(); #endif g.isHTTP = 1; cgi_destination(CGI_BODY); - if( zRequestUri==0 ) malformed_request("missing REQUEST_URI"); if( zScriptName==0 ) malformed_request("missing SCRIPT_NAME"); - if( cgi_parameter("PATH_INFO",0)==0 ){ + if( zRequestUri==0 ){ + const char *z = zPathInfo; + if( zPathInfo==0 ){ + malformed_request("missing PATH_INFO and/or REQUEST_URI"); + } + if( z[0]=='/' ) z++; + zRequestUri = mprintf("%s/%s", zScriptName, z); + cgi_set_parameter("REQUEST_URI", zRequestUri); + } + if( zPathInfo==0 ){ int i, j; for(i=0; zRequestUri[i]==zScriptName[i] && zRequestUri[i]; i++){} for(j=i; zRequestUri[j] && zRequestUri[j]!='?'; j++){} cgi_set_parameter("PATH_INFO", mprintf("%.*s", j-i, zRequestUri+i)); } @@ -850,11 +919,11 @@ z = (char*)P("HTTP_COOKIE"); if( z ){ z = mprintf("%s",z); add_param_list(z, ';'); } - + z = (char*)P("QUERY_STRING"); if( z ){ z = mprintf("%s",z); add_param_list(z, '&'); } @@ -866,11 +935,11 @@ len = atoi(PD("CONTENT_LENGTH", "0")); g.zContentType = zType = P("CONTENT_TYPE"); blob_zero(&g.cgiIn); if( len>0 && zType ){ - if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0 + if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0 || strncmp(zType,"multipart/form-data",19)==0 ){ z = fossil_malloc( len+1 ); len = fread(z, 1, len, g.httpIn); z[len] = 0; cgi_trace(z); @@ -896,11 +965,11 @@ /* FIXMEs: - See if fossil really needs g.cgiIn to be set for this purpose (i don't think it does). If it does then fill g.cgiIn and refactor to parse the JSON from there. - + - After parsing POST JSON, copy the "first layer" of keys/values to cgi_setenv(), honoring the upper-case distinction used in add_param_list(). However... - If we do that then we might get a disconnect in precedence of @@ -1009,10 +1078,20 @@ zOut = fossil_strdup(zIn); for(i=0; zOut[i]; i++){} while( i>0 && fossil_isspace(zOut[i-1]) ) zOut[--i] = 0; return zOut; } + +/* +** Return true if the CGI parameter zName exists and is not equal to 0, +** or "no" or "off". +*/ +int cgi_parameter_boolean(const char *zName){ + const char *zIn = cgi_parameter(zName, 0); + if( zIn==0 ) return 0; + return zIn[0]==0 || is_truth(zIn); +} /* ** Return the name of the i-th CGI parameter. Return NULL if there ** are fewer than i registered CGI parameters. */ @@ -1087,23 +1166,51 @@ cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue); } } /* -** Export all query parameters (but not cookies or environment variables) -** as hidden values of a form. +** Export all untagged query parameters (but not cookies or environment +** variables) as hidden values of a form. */ void cgi_query_parameters_to_hidden(void){ int i; const char *zN, *zV; for(i=0; i<nUsedQP; i++){ - if( aParamQP[i].isQP==0 ) continue; + if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue; zN = aParamQP[i].zName; zV = aParamQP[i].zValue; @ <input type="hidden" name="%h(zN)" value="%h(zV)"> } } + +/* +** Export all untagged query parameters (but not cookies or environment +** variables) to the HQuery object. +*/ +void cgi_query_parameters_to_url(HQuery *p){ + int i; + for(i=0; i<nUsedQP; i++){ + if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue; + url_add_parameter(p, aParamQP[i].zName, aParamQP[i].zValue); + } +} + +/* +** Tag query parameter zName so that it is not exported by +** cgi_query_parameters_to_hidden(). Or if zName==0, then +** untag all query parameters. +*/ +void cgi_tag_query_parameter(const char *zName){ + int i; + if( zName==0 ){ + for(i=0; i<nUsedQP; i++) aParamQP[i].cTag = 0; + }else{ + for(i=0; i<nUsedQP; i++){ + if( strcmp(zName,aParamQP[i].zName)==0 ) aParamQP[i].cTag = 1; + } + } +} /* ** This routine works like "printf" except that it has the ** extra formatting capabilities such as %h and %t. */ @@ -1172,11 +1279,11 @@ ** loaded into g.zIpAddr. */ static const char *cgi_accept_forwarded_for(const char *z){ int i; if( fossil_strcmp(g.zIpAddr, "127.0.0.1")!=0 ) return 0; - + i = strlen(z)-1; while( i>=0 && z[i]!=',' && !fossil_isspace(z[i]) ) i--; return &z[++i]; } @@ -1244,20 +1351,20 @@ for(i=0; zToken[i] && zToken[i]!='?'; i++){} if( zToken[i] ) zToken[i++] = 0; cgi_setenv("PATH_INFO", zToken); cgi_setenv("QUERY_STRING", &zToken[i]); if( zIpAddr==0 && - getpeername(fileno(g.httpIn), (struct sockaddr*)&remoteName, + getpeername(fileno(g.httpIn), (struct sockaddr*)&remoteName, &size)>=0 ){ zIpAddr = inet_ntoa(remoteName.sin_addr); } - if( zIpAddr ){ + if( zIpAddr ){ cgi_setenv("REMOTE_ADDR", zIpAddr); g.zIpAddr = mprintf("%s", zIpAddr); } - + /* Get all the optional fields that follow the first line. */ while( fgets(zLine,sizeof(zLine),g.httpIn) ){ char *zFieldName; char *zVal; @@ -1271,11 +1378,13 @@ while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; } zVal[i] = 0; for(i=0; zFieldName[i]; i++){ zFieldName[i] = fossil_tolower(zFieldName[i]); } - if( fossil_strcmp(zFieldName,"content-length:")==0 ){ + if( fossil_strcmp(zFieldName,"accept-encoding:")==0 ){ + cgi_setenv("HTTP_ACCEPT_ENCODING", zVal); + }else if( fossil_strcmp(zFieldName,"content-length:")==0 ){ cgi_setenv("CONTENT_LENGTH", zVal); }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){ cgi_setenv("CONTENT_TYPE", zVal); }else if( fossil_strcmp(zFieldName,"cookie:")==0 ){ cgi_setenv("HTTP_COOKIE", zVal); @@ -1285,14 +1394,12 @@ cgi_setenv("HTTP_HOST", zVal); }else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){ cgi_setenv("HTTP_IF_NONE_MATCH", zVal); }else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){ cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal); -#if 0 }else if( fossil_strcmp(zFieldName,"referer:")==0 ){ cgi_setenv("HTTP_REFERER", zVal); -#endif }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ cgi_setenv("HTTP_USER_AGENT", zVal); }else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){ const char *zIpAddr = cgi_accept_forwarded_for(zVal); if( zIpAddr!=0 ){ @@ -1302,10 +1409,234 @@ } } cgi_init(); cgi_trace(0); } + +/* +** This routine handles a single HTTP request from an SSH client which is +** coming in on g.httpIn and which replies on g.httpOut +** +** Once all the setup is finished, this procedure returns +** and subsequent code handles the actual generation of the webpage. +** +** It is called in a loop so some variables will need to be replaced +*/ +void cgi_handle_ssh_http_request(const char *zIpAddr){ + static int nCycles = 0; + static char *zCmd = 0; + char *z, *zToken; + const char *zType = 0; + int i, content_length = 0; + char zLine[2000]; /* A single line of input. */ + + if( zIpAddr ){ + if( nCycles==0 ){ + cgi_setenv("REMOTE_ADDR", zIpAddr); + g.zIpAddr = mprintf("%s", zIpAddr); + } + }else{ + fossil_panic("missing SSH IP address"); + } + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ + malformed_request("missing HTTP header"); + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken==0 ){ + malformed_request("malformed HTTP header"); + } + + if( fossil_strcmp(zToken, "echo")==0 ){ + /* start looking for probes to complete transport_open */ + zCmd = cgi_handle_ssh_probes(zLine, sizeof(zLine), z, zToken); + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ + malformed_request("missing HTTP header"); + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken==0 ){ + malformed_request("malformed HTTP header"); + } + }else if( zToken && strlen(zToken)==0 && zCmd ){ + /* transport_flip request and continued transport_open */ + cgi_handle_ssh_transport(zCmd); + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ + malformed_request("missing HTTP header"); + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken==0 ){ + malformed_request("malformed HTTP header"); + } + } + + if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0 + && fossil_strcmp(zToken,"HEAD")!=0 ){ + malformed_request("unsupported HTTP method"); + } + + if( nCycles==0 ){ + cgi_setenv("GATEWAY_INTERFACE","CGI/1.0"); + cgi_setenv("REQUEST_METHOD",zToken); + } + + zToken = extract_token(z, &z); + if( zToken==0 ){ + malformed_request("malformed URL in HTTP header"); + } + if( nCycles==0 ){ + cgi_setenv("REQUEST_URI", zToken); + cgi_setenv("SCRIPT_NAME", ""); + } + + for(i=0; zToken[i] && zToken[i]!='?'; i++){} + if( zToken[i] ) zToken[i++] = 0; + if( nCycles==0 ){ + cgi_setenv("PATH_INFO", zToken); + }else{ + cgi_replace_parameter("PATH_INFO", mprintf("%s",zToken)); + } + + /* Get all the optional fields that follow the first line. + */ + while( fgets(zLine,sizeof(zLine),g.httpIn) ){ + char *zFieldName; + char *zVal; + + cgi_trace(zLine); + zFieldName = extract_token(zLine,&zVal); + if( zFieldName==0 || *zFieldName==0 ) break; + while( fossil_isspace(*zVal) ){ zVal++; } + i = strlen(zVal); + while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; } + zVal[i] = 0; + for(i=0; zFieldName[i]; i++){ + zFieldName[i] = fossil_tolower(zFieldName[i]); + } + if( fossil_strcmp(zFieldName,"content-length:")==0 ){ + content_length = atoi(zVal); + }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){ + g.zContentType = zType = mprintf("%s", zVal); + }else if( fossil_strcmp(zFieldName,"host:")==0 ){ + if( nCycles==0 ){ + cgi_setenv("HTTP_HOST", zVal); + } + }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ + if( nCycles==0 ){ + cgi_setenv("HTTP_USER_AGENT", zVal); + } + }else if( fossil_strcmp(zFieldName,"x-fossil-transport:")==0 ){ + if( fossil_strnicmp(zVal, "ssh", 3)==0 ){ + if( nCycles==0 ){ + g.fSshClient |= CGI_SSH_FOSSIL; + g.fullHttpReply = 0; + } + } + } + } + + if( nCycles==0 ){ + if( ! ( g.fSshClient & CGI_SSH_FOSSIL ) ){ + /* did not find new fossil ssh transport */ + g.fSshClient &= ~CGI_SSH_CLIENT; + g.fullHttpReply = 1; + cgi_replace_parameter("REMOTE_ADDR", "127.0.0.1"); + } + } + + cgi_reset_content(); + cgi_destination(CGI_BODY); + + if( content_length>0 && zType ){ + blob_zero(&g.cgiIn); + if( fossil_strcmp(zType, "application/x-fossil")==0 ){ + blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); + blob_uncompress(&g.cgiIn, &g.cgiIn); + }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){ + blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); + }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){ + blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); + } + } + cgi_trace(0); + nCycles++; +} + +/* +** This routine handles the old fossil SSH probes +*/ +char *cgi_handle_ssh_probes(char *zLine, int zSize, char *z, char *zToken){ + /* Start looking for probes */ + while( fossil_strcmp(zToken, "echo")==0 ){ + zToken = extract_token(z, &z); + if( zToken==0 ){ + malformed_request("malformed probe"); + } + if( fossil_strncmp(zToken, "test", 4)==0 || + fossil_strncmp(zToken, "probe-", 6)==0 ){ + fprintf(g.httpOut, "%s\n", zToken); + fflush(g.httpOut); + }else{ + malformed_request("malformed probe"); + } + if( fgets(zLine, zSize, g.httpIn)==0 ){ + malformed_request("malformed probe"); + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken==0 ){ + malformed_request("malformed probe"); + } + } + + /* Got all probes now first transport_open is completed + ** so return the command that was requested + */ + g.fSshClient |= CGI_SSH_COMPAT; + return mprintf("%s", zToken); +} + +/* +** This routine handles the old fossil SSH transport_flip +** and transport_open communications if detected. +*/ +void cgi_handle_ssh_transport(const char *zCmd){ + char *z, *zToken; + char zLine[2000]; /* A single line of input. */ + + /* look for second newline of transport_flip */ + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ + malformed_request("incorrect transport_flip"); + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken && strlen(zToken)==0 ){ + /* look for path to fossil */ + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ + if( zCmd==0 ){ + malformed_request("missing fossil command"); + }else{ + /* no new command so exit */ + fossil_exit(0); + } + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken==0 ){ + malformed_request("malformed fossil command"); + } + /* see if we've seen the command */ + if( zCmd && zCmd[0] && fossil_strcmp(zToken, zCmd)==0 ){ + return; + }else{ + malformed_request("transport_open failed"); + } + }else{ + malformed_request("transport_flip failed"); + } +} /* ** This routine handles a single SCGI request which is coming in on ** g.httpIn and which replies on g.httpOut ** @@ -1318,15 +1649,14 @@ void cgi_handle_scgi_request(void){ char *zHdr; char *zToFree; int nHdr = 0; int nRead; - int n, m; - char c; + int c, n, m; - while( (c = fgetc(g.httpIn))!=EOF && fossil_isdigit(c) ){ - nHdr = nHdr*10 + c - '0'; + while( (c = fgetc(g.httpIn))!=EOF && fossil_isdigit((char)c) ){ + nHdr = nHdr*10 + (char)c - '0'; } if( nHdr<16 ) malformed_request("SCGI header too short"); zToFree = zHdr = fossil_malloc(nHdr); nRead = (int)fread(zHdr, 1, nHdr, g.httpIn); if( nRead<nHdr ) malformed_request("cannot read entire SCGI header"); @@ -1344,15 +1674,18 @@ cgi_init(); } #if INTERFACE -/* +/* ** Bitmap values for the flags parameter to cgi_http_server(). */ #define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */ #define HTTP_SERVER_SCGI 0x0002 /* SCGI instead of HTTP */ +#define HTTP_SERVER_HAD_REPOSITORY 0x0004 /* Was the repository open? */ +#define HTTP_SERVER_HAD_CHECKOUT 0x0008 /* Was a checkout open? */ +#define HTTP_SERVER_REPOLIST 0x0010 /* Allow repo listing */ #endif /* INTERFACE */ /* ** Maximum number of child processes that we can have running @@ -1429,20 +1762,19 @@ " port in the range %d..%d", mnPort, mxPort); } } if( iPort>mxPort ) return 1; listen(listener,10); - if( iPort>mnPort ){ - fossil_print("Listening for %s requests on TCP port %d\n", - (flags & HTTP_SERVER_SCGI)!=0?"SCGI":"HTTP", iPort); - fflush(stdout); - } + fossil_print("Listening for %s requests on TCP port %d\n", + (flags & HTTP_SERVER_SCGI)!=0?"SCGI":"HTTP", iPort); + fflush(stdout); if( zBrowser ){ - zBrowser = mprintf(zBrowser, iPort); + assert( strstr(zBrowser,"%d")!=0 ); + zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); #if defined(__CYGWIN__) /* On Cygwin, we can do better than "echo" */ - if( memcmp(zBrowser, "echo ", 5)==0 ){ + if( strncmp(zBrowser, "echo ", 5)==0 ){ wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5); wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */ if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){ fossil_warning("cannot start browser\n"); } @@ -1477,11 +1809,11 @@ fd = dup(connection); if( fd!=0 ) nErr++; close(1); fd = dup(connection); if( fd!=1 ) nErr++; - if( !g.fHttpTrace && !g.fSqlTrace ){ + if( !g.fAnyTrace ){ close(2); fd = dup(connection); if( fd!=2 ) nErr++; } close(connection); @@ -1492,11 +1824,11 @@ /* Bury dead children */ while( waitpid(0, 0, WNOHANG)>0 ){ nchildren--; } } - /* NOT REACHED */ + /* NOT REACHED */ fossil_exit(1); #endif /* NOT REACHED */ return 0; } @@ -1580,11 +1912,11 @@ p->tm_mon %= 12; } isLeapYr = p->tm_year%4==0 && (p->tm_year%100!=0 || (p->tm_year+300)%400==0); p->tm_yday = priorDays[p->tm_mon] + p->tm_mday - 1; if( isLeapYr && p->tm_mon>1 ) p->tm_yday++; - nDay = (p->tm_year-70)*365 + (p->tm_year-69)/4 -p->tm_year/100 + + nDay = (p->tm_year-70)*365 + (p->tm_year-69)/4 -p->tm_year/100 + (p->tm_year+300)/400 + p->tm_yday; t = ((nDay*24 + p->tm_hour)*60 + p->tm_min)*60 + p->tm_sec; return t; } @@ -1600,5 +1932,23 @@ cgi_set_status(304,"Not Modified"); cgi_reset_content(); cgi_reply(); fossil_exit(0); } + +/* +** Check to see if the remote client is SSH and return +** its IP or return default +*/ +const char *cgi_ssh_remote_addr(const char *zDefault){ + char *zIndex; + const char *zSshConn = fossil_getenv("SSH_CONNECTION"); + + if( zSshConn && zSshConn[0] ){ + char *zSshClient = mprintf("%s",zSshConn); + if( (zIndex = strchr(zSshClient,' '))!=0 ){ + zSshClient[zIndex-zSshClient] = '\0'; + return zSshClient; + } + } + return zDefault; +} Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -52,23 +52,25 @@ zName = blob_str(&fname); if( fossil_strcmp(zName, ".")==0 ) { blob_reset(&where); break; } - blob_appendf(&where, " %s (pathname=%Q %s) " - "OR (pathname>'%q/' %s AND pathname<'%q0' %s)", - (blob_size(&where)>0) ? "OR" : "AND", zName, - filename_collation(), zName, filename_collation(), - zName, filename_collation()); + blob_append_sql(&where, + " %s (pathname=%Q %s) " + "OR (pathname>'%q/' %s AND pathname<'%q0' %s)", + (blob_size(&where)>0) ? "OR" : "AND", zName, + filename_collation(), zName, filename_collation(), + zName, filename_collation() + ); } db_prepare(&q, "SELECT pathname, deleted, chnged, rid, coalesce(origname!=pathname,0)" " FROM vfile " " WHERE is_selected(id) %s" - " AND (chnged OR deleted OR rid=0 OR pathname!=origname) ORDER BY 1", - blob_str(&where) + " AND (chnged OR deleted OR rid=0 OR pathname!=origname) ORDER BY 1 /*scan*/", + blob_sql_text(&where) ); blob_zero(&rewrittenPathname); while( db_step(&q)==SQLITE_ROW ){ const char *zPathname = db_column_text(&q,0); const char *zDisplayName = zPathname; @@ -86,11 +88,11 @@ } blob_append(report, zPrefix, nPrefix); if( isDeleted ){ blob_appendf(report, "DELETED %s\n", zDisplayName); }else if( !file_wd_isfile_or_link(zFullName) ){ - if( file_access(zFullName, 0)==0 ){ + if( file_access(zFullName, F_OK)==0 ){ blob_appendf(report, "NOT_A_FILE %s\n", zDisplayName); if( missingIsFatal ){ fossil_warning("not a file: %s", zDisplayName); nErr++; } @@ -101,12 +103,10 @@ nErr++; } } }else if( isNew ){ blob_appendf(report, "ADDED %s\n", zDisplayName); - }else if( isDeleted ){ - blob_appendf(report, "DELETED %s\n", zDisplayName); }else if( isChnged ){ if( isChnged==2 ){ blob_appendf(report, "UPDATED_BY_MERGE %s\n", zDisplayName); }else if( isChnged==3 ){ blob_appendf(report, "ADDED_BY_MERGE %s\n", zDisplayName); @@ -159,10 +159,35 @@ int relPathOption = find_option("rel-paths", 0, 0)!=0; if( absPathOption ){ relativePaths = 0; } if( relPathOption ){ relativePaths = 1; } return relativePaths; } + +void print_changes( + int useSha1sum, /* Verify file status using SHA1 hashing rather + than relying on file mtimes. */ + int showHdr, /* Identify the repository if there are changes */ + int verboseFlag, /* Say "(none)" if there are no changes */ + int cwdRelative /* Report relative to the current working dir */ +){ + Blob report; + int vid; + blob_zero(&report); + + vid = db_lget_int("checkout", 0); + vfile_check_signature(vid, useSha1sum ? CKSIG_SHA1 : 0); + status_report(&report, "", 0, cwdRelative); + if( verboseFlag && blob_size(&report)==0 ){ + blob_append(&report, " (none)\n", -1); + } + if( showHdr && blob_size(&report)>0 ){ + fossil_print("Changes for %s at %s:\n", db_get("project-name","???"), + g.zLocalRoot); + } + blob_write_to_file(&report, "-"); + blob_reset(&report); +} /* ** COMMAND: changes ** ** Usage: %fossil changes ?OPTIONS? @@ -179,34 +204,24 @@ ** --sha1sum Verify file status using SHA1 hashing rather ** than relying on file mtimes. ** --header Identify the repository if there are changes ** -v|--verbose Say "(none)" if there are no changes ** -** See also: extra, ls, status +** See also: extras, ls, status */ void changes_cmd(void){ - Blob report; - int vid; int useSha1sum = find_option("sha1sum", 0, 0)!=0; int showHdr = find_option("header",0,0)!=0; int verboseFlag = find_option("verbose","v",0)!=0; int cwdRelative = 0; db_must_be_within_tree(); cwdRelative = determine_cwd_relative_option(); - blob_zero(&report); - vid = db_lget_int("checkout", 0); - vfile_check_signature(vid, useSha1sum ? CKSIG_SHA1 : 0); - status_report(&report, "", 0, cwdRelative); - if( verboseFlag && blob_size(&report)==0 ){ - blob_append(&report, " (none)\n", -1); - } - if( showHdr && blob_size(&report)>0 ){ - fossil_print("Changes for %s at %s:\n", db_get("project-name","???"), - g.zLocalRoot); - } - blob_write_to_file(&report, "-"); - blob_reset(&report); + + /* We should be done with options.. */ + verify_all_options(); + + print_changes(useSha1sum, showHdr, verboseFlag, cwdRelative); } /* ** COMMAND: status ** @@ -223,16 +238,25 @@ ** --rel-paths Display pathnames relative to the current working ** directory. ** --sha1sum Verify file status using SHA1 hashing rather ** than relying on file mtimes. ** -** See also: changes, extra, ls +** See also: changes, extras, ls */ void status_cmd(void){ int vid; + int useSha1sum = find_option("sha1sum", 0, 0)!=0; + int showHdr = find_option("header",0,0)!=0; + int verboseFlag = find_option("verbose","v",0)!=0; + int cwdRelative = 0; db_must_be_within_tree(); /* 012345678901234 */ + cwdRelative = determine_cwd_relative_option(); + + /* We should be done with options.. */ + verify_all_options(); + fossil_print("repository: %s\n", db_repository_filename()); fossil_print("local-root: %s\n", g.zLocalRoot); if( g.zConfigDbName ){ fossil_print("config-db: %s\n", g.zConfigDbName); } @@ -239,46 +263,136 @@ vid = db_lget_int("checkout", 0); if( vid ){ show_common_info(vid, "checkout:", 1, 1); } db_record_repository_filename(0); - changes_cmd(); + print_changes(useSha1sum, showHdr, verboseFlag, cwdRelative); +} + +/* +** Take care of -r version of ls command +*/ +static void ls_cmd_rev( + const char *zRev, /* Revision string given */ + int verboseFlag, /* Verbose flag given */ + int showAge, /* Age flag given */ + int timeOrder /* Order by time flag given */ +){ + Stmt q; + char *zOrderBy = "pathname COLLATE nocase"; + char *zName; + Blob where; + int rid; + int i; + + /* Handle given file names */ + blob_zero(&where); + for(i=2; i<g.argc; i++){ + Blob fname; + file_tree_name(g.argv[i], &fname, 1); + zName = blob_str(&fname); + if( fossil_strcmp(zName, ".")==0 ) { + blob_reset(&where); + break; + } + blob_append_sql(&where, + " %s (pathname=%Q %s) " + "OR (pathname>'%q/' %s AND pathname<'%q0' %s)", + (blob_size(&where)>0) ? "OR" : "AND (", zName, + filename_collation(), zName, filename_collation(), + zName, filename_collation() + ); + } + if( blob_size(&where)>0 ){ + blob_append_sql(&where, ")"); + } + + rid = symbolic_name_to_rid(zRev, "ci"); + if( rid==0 ){ + fossil_fatal("not a valid check-in: %s", zRev); + } + + if( timeOrder ){ + zOrderBy = "mtime DESC"; + } + + compute_fileage(rid,0); + db_prepare(&q, + "SELECT datetime(fileage.mtime, 'localtime'), fileage.pathname,\n" + " blob.size\n" + " FROM fileage, blob\n" + " WHERE blob.rid=fileage.fid %s\n" + " ORDER BY %s;", blob_sql_text(&where), zOrderBy /*safe-for-%s*/ + ); + blob_reset(&where); + + while( db_step(&q)==SQLITE_ROW ){ + const char *zTime = db_column_text(&q,0); + const char *zFile = db_column_text(&q,1); + int size = db_column_int(&q,2); + if( verboseFlag ){ + fossil_print("%s %7d %s\n", zTime, size, zFile); + }else if( showAge ){ + fossil_print("%s %s\n", zTime, zFile); + }else{ + fossil_print("%s\n", zFile); + } + } + db_finalize(&q); } /* ** COMMAND: ls ** -** Usage: %fossil ls ?OPTIONS? ?VERSION? ?FILENAMES? +** Usage: %fossil ls ?OPTIONS? ?FILENAMES? ** ** Show the names of all files in the current checkout. The -v provides ** extra information about each file. If FILENAMES are included, only ** the files listed (or their children if they are directories) are shown. ** +** If -r is given a specific check-in is listed. In this case -R can be +** given to query another repository. +** ** Options: -** --age Show when each file was committed -** -v|--verbose Provide extra information about each file. +** --age Show when each file was committed +** -v|--verbose Provide extra information about each file. +** -t Sort output in time order. +** -r VERSION The specific check-in to list +** -R|--repository FILE Extract info from repository FILE ** -** See also: changes, extra, status +** See also: changes, extras, status */ void ls_cmd(void){ int vid; Stmt q; int verboseFlag; int showAge; + int timeOrder; char *zOrderBy = "pathname"; Blob where; int i; const char *zName; + const char *zRev; verboseFlag = find_option("verbose","v", 0)!=0; if( !verboseFlag ){ verboseFlag = find_option("l","l", 0)!=0; /* deprecated */ } showAge = find_option("age",0,0)!=0; + zRev = find_option("r","r",1); + timeOrder = find_option("t","t",0)!=0; + + if( zRev!=0 ){ + db_find_and_open_repository(0, 0); + verify_all_options(); + ls_cmd_rev(zRev,verboseFlag,showAge,timeOrder); + return; + } + db_must_be_within_tree(); vid = db_lget_int("checkout", 0); - if( find_option("t","t",0)!=0 ){ + if( timeOrder ){ if( showAge ){ zOrderBy = mprintf("checkin_mtime(%d,rid) DESC", vid); }else{ zOrderBy = "mtime DESC"; } @@ -291,29 +405,32 @@ zName = blob_str(&fname); if( fossil_strcmp(zName, ".")==0 ) { blob_reset(&where); break; } - blob_appendf(&where, " %s (pathname=%Q %s) " - "OR (pathname>'%q/' %s AND pathname<'%q0' %s)", - (blob_size(&where)>0) ? "OR" : "WHERE", zName, - filename_collation(), zName, filename_collation(), - zName, filename_collation()); + blob_append_sql(&where, + " %s (pathname=%Q %s) " + "OR (pathname>'%q/' %s AND pathname<'%q0' %s)", + (blob_size(&where)>0) ? "OR" : "WHERE", zName, + filename_collation(), zName, filename_collation(), + zName, filename_collation() + ); } vfile_check_signature(vid, 0); if( showAge ){ db_prepare(&q, "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)," - " datetime(checkin_mtime(%d,rid),'unixepoch','localtime')" + " datetime(checkin_mtime(%d,rid),'unixepoch'%s)" " FROM vfile %s" - " ORDER BY %s", vid, blob_str(&where), zOrderBy + " ORDER BY %s", + vid, timeline_utc(), blob_sql_text(&where), zOrderBy /*safe-for-%s*/ ); }else{ db_prepare(&q, "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)" " FROM vfile %s" - " ORDER BY %s", blob_str(&where), zOrderBy + " ORDER BY %s", blob_sql_text(&where), zOrderBy /*safe-for-%s*/ ); } blob_reset(&where); while( db_step(&q)==SQLITE_ROW ){ const char *zPathname = db_column_text(&q,0); @@ -327,11 +444,11 @@ if( isNew ){ type = "ADDED "; }else if( isDeleted ){ type = "DELETED "; }else if( !file_wd_isfile_or_link(zFullName) ){ - if( file_access(zFullName, 0)==0 ){ + if( file_access(zFullName, F_OK)==0 ){ type = "NOT_A_FILE "; }else{ type = "MISSING "; } }else if( chnged ){ @@ -433,29 +550,36 @@ ** ** Options: ** --abs-paths Display absolute pathnames. ** --case-sensitive <BOOL> override case-sensitive setting ** --dotfiles include files beginning with a dot (".") +** --header Identify the repository if there are extras ** --ignore <CSG> ignore files matching patterns from the argument ** --rel-paths Display pathnames relative to the current working ** directory. ** ** See also: changes, clean, status */ -void extra_cmd(void){ +void extras_cmd(void){ Stmt q; const char *zIgnoreFlag = find_option("ignore",0,1); unsigned scanFlags = find_option("dotfiles",0,0)!=0 ? SCAN_ALL : 0; + int showHdr = find_option("header",0,0)!=0; int cwdRelative = 0; Glob *pIgnore; Blob rewrittenPathname; const char *zPathname, *zDisplayName; if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP; - capture_case_sensitive_option(); db_must_be_within_tree(); cwdRelative = determine_cwd_relative_option(); + + if( db_get_boolean("dotfiles", 0) ) scanFlags |= SCAN_ALL; + + /* We should be done with options.. */ + verify_all_options(); + if( zIgnoreFlag==0 ){ zIgnoreFlag = db_get("ignore-glob", 0); } pIgnore = glob_create(zIgnoreFlag); locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore, 0); @@ -466,10 +590,11 @@ " ORDER BY 1", fossil_all_reserved_names(0) ); db_multi_exec("DELETE FROM sfile WHERE x IN (SELECT pathname FROM vfile)"); blob_zero(&rewrittenPathname); + g.allowSymlinks = 1; /* Report on symbolic links */ while( db_step(&q)==SQLITE_ROW ){ zDisplayName = zPathname = db_column_text(&q, 0); if( cwdRelative ) { char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); file_relative_name(zFullName, &rewrittenPathname, 0); @@ -477,10 +602,15 @@ zDisplayName = blob_str(&rewrittenPathname); if( zDisplayName[0]=='.' && zDisplayName[1]=='/' ){ zDisplayName += 2; /* no unnecessary ./ prefix */ } } + if( showHdr ){ + showHdr = 0; + fossil_print("Extras for %s at %s:\n", db_get("project-name","???"), + g.zLocalRoot); + } fossil_print("%s\n", zDisplayName); } blob_reset(&rewrittenPathname); db_finalize(&q); } @@ -526,10 +656,11 @@ ** argument. Matching files, if any, are removed ** prior to checking for any empty directories; ** therefore, directories that contain only files ** that were removed will be removed as well. ** -f|--force Remove files without prompting. +** --verily Shorthand for: -f --emptydirs --dotfiles ** --clean <CSG> Never prompt for files matching this ** comma separated list of glob patterns. ** --ignore <CSG> Ignore files matching patterns from the ** comma separated list of glob patterns. ** --keep <CSG> Keep files matching this comma separated @@ -536,11 +667,11 @@ ** list of glob patterns. ** -n|--dry-run If given, display instead of run actions. ** --temp Remove only Fossil-generated temporary files. ** -v|--verbose Show all files as they are removed. ** -** See also: addremove, extra, status +** See also: addremove, extras, status */ void clean_cmd(void){ int allFileFlag, allDirFlag, dryRunFlag, verboseFlag; int emptyDirsFlag, dirsOnlyFlag; unsigned scanFlags = 0; @@ -550,10 +681,13 @@ dryRunFlag = find_option("dry-run","n",0)!=0; if( !dryRunFlag ){ dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ } + if( !dryRunFlag ){ + dryRunFlag = find_option("whatif",0,0)!=0; + } allFileFlag = allDirFlag = find_option("force","f",0)!=0; dirsOnlyFlag = find_option("dirsonly",0,0)!=0; emptyDirsFlag = find_option("emptydirs","d",0)!=0 || dirsOnlyFlag; if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL; if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP; @@ -560,30 +694,36 @@ if( find_option("allckouts",0,0)!=0 ) scanFlags |= SCAN_NESTED; zIgnoreFlag = find_option("ignore",0,1); verboseFlag = find_option("verbose","v",0)!=0; zKeepFlag = find_option("keep",0,1); zCleanFlag = find_option("clean",0,1); - capture_case_sensitive_option(); db_must_be_within_tree(); + if( find_option("verily",0,0)!=0 ){ + allFileFlag = allDirFlag = 1; + emptyDirsFlag = 1; + scanFlags |= SCAN_ALL; + } if( zIgnoreFlag==0 ){ zIgnoreFlag = db_get("ignore-glob", 0); } if( zKeepFlag==0 ){ zKeepFlag = db_get("keep-glob", 0); } if( zCleanFlag==0 ){ zCleanFlag = db_get("clean-glob", 0); } + if( db_get_boolean("dotfiles", 0) ) scanFlags |= SCAN_ALL; verify_all_options(); pIgnore = glob_create(zIgnoreFlag); pKeep = glob_create(zKeepFlag); pClean = glob_create(zCleanFlag); nRoot = (int)strlen(g.zLocalRoot); + g.allowSymlinks = 1; /* Find symlinks too */ if( !dirsOnlyFlag ){ Stmt q; Blob repo; - locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore, pKeep); + locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore, 0); db_prepare(&q, "SELECT %Q || x FROM sfile" " WHERE x NOT IN (%s)" " ORDER BY 1", g.zLocalRoot, fossil_all_reserved_names(0) @@ -592,16 +732,22 @@ db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); } db_multi_exec("DELETE FROM sfile WHERE x IN (SELECT pathname FROM vfile)"); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); + if( glob_match(pKeep, zName+nRoot) ){ + if( verboseFlag ){ + fossil_print("KEPT file \"%s\" not removed (due to --keep" + " or \"keep-glob\")\n", zName+nRoot); + } + continue; + } if( !allFileFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){ Blob ans; char cReply; char *prompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ", zName+nRoot); - blob_zero(&ans); prompt_user(prompt, &ans); cReply = blob_str(&ans)[0]; if( cReply=='a' || cReply=='A' ){ allFileFlag = 1; }else if( cReply!='y' && cReply!='Y' ){ @@ -623,27 +769,33 @@ if( emptyDirsFlag ){ Glob *pEmptyDirs = glob_create(db_get("empty-dirs", 0)); Stmt q; Blob root; blob_init(&root, g.zLocalRoot, nRoot - 1); - vfile_dir_scan(&root, blob_size(&root), scanFlags, pIgnore, pKeep, - pEmptyDirs); + vfile_dir_scan(&root, blob_size(&root), scanFlags, pIgnore, + pEmptyDirs, 0); blob_reset(&root); db_prepare(&q, "SELECT %Q || x FROM dscan_temp" " WHERE x NOT IN (%s) AND y = 0" " ORDER BY 1 DESC", g.zLocalRoot, fossil_all_reserved_names(0) ); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); + if( glob_match(pKeep, zName+nRoot) ){ + if( verboseFlag ){ + fossil_print("KEPT directory \"%s\" not removed (due to --keep" + " or \"keep-glob\")\n", zName+nRoot); + } + continue; + } if( !allDirFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){ Blob ans; char cReply; char *prompt = mprintf("Remove empty directory \"%s\" (a=all/y/N)? ", zName+nRoot); - blob_zero(&ans); prompt_user(prompt, &ans); cReply = blob_str(&ans)[0]; if( cReply=='a' || cReply=='A' ){ allDirFlag = 1; }else if( cReply!='y' && cReply!='Y' ){ @@ -797,27 +949,47 @@ #else blob_init(&prompt, zInit, -1); #endif blob_append(&prompt, "\n" - "# Enter comments on this check-in. Lines beginning with # are ignored.\n" + "# Enter a commit message for this check-in. Lines beginning with # are ignored.\n" "#\n", -1 ); - blob_appendf(&prompt, "# user: %s\n", p->zUserOvrd ? p->zUserOvrd : g.zLogin); + blob_appendf(&prompt, "# user: %s\n", p->zUserOvrd ? p->zUserOvrd : login_name()); if( p->zBranch && p->zBranch[0] ){ blob_appendf(&prompt, "# tags: %s\n#\n", p->zBranch); }else{ char *zTags = info_tags_of_checkin(parent_rid, 1); - if( zTags ) blob_appendf(&prompt, "# tags: %z\n#\n", zTags); + if( zTags || p->azTag ){ + blob_append(&prompt, "# tags: ", 8); + if(zTags){ + blob_appendf(&prompt, "%z%s", zTags, p->azTag ? ", " : ""); + } + if(p->azTag){ + int i = 0; + for( ; p->azTag[i]; ++i ){ + blob_appendf(&prompt, "%s%s", p->azTag[i], + p->azTag[i+1] ? ", " : ""); + } + } + blob_appendf(&prompt, "\n#\n"); + } } status_report(&prompt, "# ", 1, 0); if( g.markPrivate ){ blob_append(&prompt, "# PRIVATE BRANCH: This check-in will be private and will not sync to\n" "# repositories.\n" "#\n", -1 ); + } + if( p->integrateFlag ){ + blob_append(&prompt, + "#\n" + "# All merged-in branches will be closed due to the --integrate flag\n" + "#\n", -1 + ); } prompt_for_user_comment(pComment, &prompt); blob_reset(&prompt); } @@ -842,14 +1014,12 @@ assert( g.aCommitFile==0 ); if( g.argc>2 ){ int ii, jj=0; Blob fname; Stmt q; - const char *zCollate; Bag toCommit; - zCollate = filename_collation(); blob_zero(&fname); bag_init(&toCommit); for(ii=2; ii<g.argc; ii++){ int cnt = 0; file_tree_name(g.argv[ii], &fname, 1); @@ -858,12 +1028,12 @@ return result; } db_prepare(&q, "SELECT id FROM vfile WHERE pathname=%Q %s" " OR (pathname>'%q/' %s AND pathname<'%q0' %s)", - blob_str(&fname), zCollate, blob_str(&fname), - zCollate, blob_str(&fname), zCollate); + blob_str(&fname), filename_collation(), blob_str(&fname), + filename_collation(), blob_str(&fname), filename_collation()); while( db_step(&q)==SQLITE_ROW ){ cnt++; bag_insert(&toCommit, db_column_int(&q, 0)); } db_finalize(&q); @@ -899,11 +1069,11 @@ " WHERE datetime(mtime)>=%Q" " AND type='ci' AND objid=%d", zDate, rid ); if( b ){ - fossil_fatal("ancestor check-in [%.10s] (%s) is not older (clock skew?)" + fossil_fatal("ancestor check-in [%S] (%s) is not older (clock skew?)" " Use --allow-older to override.", zUuid, zDate); } #endif } @@ -952,13 +1122,14 @@ struct CheckinInfo { Blob *pComment; /* Check-in comment text */ const char *zMimetype; /* Mimetype of check-in command. May be NULL */ int verifyDate; /* Verify that child is younger */ int closeFlag; /* Close the branch being committed */ + int integrateFlag; /* Close merged-in branches */ Blob *pCksum; /* Repository checksum. May be 0 */ const char *zDateOvrd; /* Date override. If 0 then use 'now' */ - const char *zUserOvrd; /* User override. If 0 then use g.zLogin */ + const char *zUserOvrd; /* User override. If 0 then use login_name() */ const char *zBranch; /* Branch name. May be 0 */ const char *zColor; /* One-time background color. May be 0 */ const char *zBrClr; /* Persistent branch color. May be 0 */ const char **azTag; /* Tags to apply to this check-in */ }; @@ -974,11 +1145,11 @@ int vid, /* BLOB.id for the parent check-in */ CheckinInfo *p, /* Information about the check-in */ int *pnFBcard /* OUT: Number of generated B- and F-cards */ ){ char *zDate; /* Date of the check-in */ - char *zParentUuid; /* UUID of parent check-in */ + char *zParentUuid = 0; /* UUID of parent check-in */ Blob filename; /* A single filename */ int nBasename; /* Size of base filename */ Stmt q; /* Various queries */ Blob mcksum; /* Manifest checksum */ ManifestFile *pFile; /* File from the baseline */ @@ -987,11 +1158,19 @@ const char *zColor; /* Modified value of p->zColor */ assert( pBaseline==0 || pBaseline->zBaseline==0 ); assert( pBaseline==0 || zBaselineUuid!=0 ); blob_zero(pOut); - zParentUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); + if( vid ){ + zParentUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d AND " + "EXISTS(SELECT 1 FROM event WHERE event.type='ci' and event.objid=%d)", + vid, vid); + if( !zParentUuid ){ + fossil_fatal("Could not find a valid check-in for RID %d. " + "Possible checkout/repo mismatch.", vid); + } + } if( pBaseline ){ blob_appendf(pOut, "B %s\n", zBaselineUuid); manifest_file_rewind(pBaseline); pFile = manifest_file_next(pBaseline, 0); nFBcard++; @@ -1082,28 +1261,30 @@ nFBcard++; } if( p->zMimetype && p->zMimetype[0] ){ blob_appendf(pOut, "N %F\n", p->zMimetype); } - blob_appendf(pOut, "P %s", zParentUuid); - if( p->verifyDate ) checkin_verify_younger(vid, zParentUuid, zDate); - free(zParentUuid); - db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0 OR id<-2"); - while( db_step(&q)==SQLITE_ROW ){ - char *zMergeUuid; - int mid = db_column_int(&q, 0); - if( (!g.markPrivate && content_is_private(mid)) || (mid == vid) ) continue; - zMergeUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); - if( zMergeUuid ){ - blob_appendf(pOut, " %s", zMergeUuid); - if( p->verifyDate ) checkin_verify_younger(mid, zMergeUuid, zDate); - free(zMergeUuid); - } - } - db_finalize(&q); - free(zDate); - blob_appendf(pOut, "\n"); + if( vid ){ + blob_appendf(pOut, "P %s", zParentUuid); + if( p->verifyDate ) checkin_verify_younger(vid, zParentUuid, zDate); + free(zParentUuid); + db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0 OR id<-2"); + while( db_step(&q)==SQLITE_ROW ){ + char *zMergeUuid; + int mid = db_column_int(&q, 0); + if( (!g.markPrivate && content_is_private(mid)) || (mid == vid) ) continue; + zMergeUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); + if( zMergeUuid ){ + blob_appendf(pOut, " %s", zMergeUuid); + if( p->verifyDate ) checkin_verify_younger(mid, zMergeUuid, zDate); + free(zMergeUuid); + } + } + db_finalize(&q); + blob_appendf(pOut, "\n"); + } + free(zDate); db_prepare(&q, "SELECT CASE vmerge.id WHEN -1 THEN '+' ELSE '-' END || blob.uuid, merge" " FROM vmerge, blob" " WHERE (vmerge.id=-1 OR vmerge.id=-2)" @@ -1135,11 +1316,12 @@ } if( p->closeFlag ){ blob_appendf(pOut, "T +closed *\n"); } db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid" - " WHERE id=-4 ORDER BY 1"); + " WHERE id %s ORDER BY 1", + p->integrateFlag ? "IN(0,-4)" : "=(-4)"); while( db_step(&q)==SQLITE_ROW ){ const char *zIntegrateUuid = db_column_text(&q, 0); int rid = db_column_int(&q, 1); if( is_a_leaf(rid) && !db_exists("SELECT 1 FROM tagxref " " WHERE tagid=%d AND rid=%d AND tagtype>0", TAG_CLOSED, rid)){ @@ -1169,11 +1351,11 @@ const char *zBrTag = db_column_text(&q, 0); blob_appendf(pOut, "T -%F *\n", zBrTag); } db_finalize(&q); } - blob_appendf(pOut, "U %F\n", p->zUserOvrd ? p->zUserOvrd : g.zLogin); + blob_appendf(pOut, "U %F\n", p->zUserOvrd ? p->zUserOvrd : login_name()); md5sum_blob(pOut, &mcksum); blob_appendf(pOut, "Z %b\n", &mcksum); if( pnFBcard ) *pnFBcard = nFBcard; } @@ -1198,10 +1380,11 @@ int fBinary; /* does the blob content appear to be binary? */ int lookFlags; /* output flags from looks_like_utf8/utf16() */ int fHasAnyCr; /* the blob contains one or more CR chars */ int fHasLoneCrOnly; /* all detected line endings are CR only */ int fHasCrLfOnly; /* all detected line endings are CR/LF pairs */ + int fHasInvalidUtf8 = 0;/* contains byte-sequence which is invalid for UTF-8 */ char *zMsg; /* Warning message */ Blob fname; /* Relative pathname of the file */ static int allOk = 0; /* Set to true to disable this routine */ if( allOk ) return 0; @@ -1208,16 +1391,19 @@ fUnicode = could_be_utf16(p, &bReverse); if( fUnicode ){ lookFlags = looks_like_utf16(p, bReverse, LOOK_NUL); }else{ lookFlags = looks_like_utf8(p, LOOK_NUL); + if( !(lookFlags & LOOK_BINARY) && invalid_utf8(p) ){ + fHasInvalidUtf8 = 1; + } } fHasAnyCr = (lookFlags & LOOK_CR); fBinary = (lookFlags & LOOK_BINARY); fHasLoneCrOnly = ((lookFlags & LOOK_EOL) == LOOK_LONE_CR); fHasCrLfOnly = ((lookFlags & LOOK_EOL) == LOOK_CRLF); - if( fUnicode || fHasAnyCr || fBinary ){ + if( fUnicode || fHasAnyCr || fBinary || fHasInvalidUtf8){ const char *zWarning; const char *zDisable; const char *zConvert = "c=convert/"; Blob ans; char cReply; @@ -1246,10 +1432,16 @@ zWarning = "CR/NL line endings and Unicode"; }else{ zWarning = "mixed line endings and Unicode"; } zDisable = "\"crnl-glob\" and \"encoding-glob\" settings"; + }else if( fHasInvalidUtf8 ){ + if( encodingOk ){ + return 0; /* We don't want encoding warnings for this file. */ + } + zWarning = "invalid UTF-8"; + zDisable = "\"encoding-glob\" setting"; }else if( fHasAnyCr ){ if( crnlOk ){ return 0; /* We don't want CR/NL warnings for this file. */ } if( fHasLoneCrOnly ){ @@ -1263,17 +1455,13 @@ }else{ if( encodingOk ){ return 0; /* We don't want encoding warnings for this file. */ } zWarning = "Unicode"; -#if !defined(_WIN32) && !defined(__CYGWIN__) - zConvert = ""; /* On Unix, we cannot easily convert Unicode files. */ -#endif zDisable = "\"encoding-glob\" setting"; } file_relative_name(zFilename, &fname, 0); - blob_zero(&ans); zMsg = mprintf( "%s contains %s. Use --no-warnings or the %s to disable this warning.\n" "Commit anyhow (a=all/%sy/N)? ", blob_str(&fname), zWarning, zDisable, zConvert); prompt_user(zMsg, &ans); @@ -1285,21 +1473,27 @@ char *zOrig = file_newname(zFilename, "original", 1); FILE *f; blob_write_to_file(p, zOrig); fossil_free(zOrig); f = fossil_fopen(zFilename, "wb"); - if( fUnicode ) { - int bomSize; - const unsigned char *bom = get_utf8_bom(&bomSize); - fwrite(bom, 1, bomSize, f); - blob_to_utf8_no_bom(p, 0); - } - if( fHasAnyCr ){ - blob_to_lf_only(p); - } - fwrite(blob_buffer(p), 1, blob_size(p), f); - fclose(f); + if( f==0 ){ + fossil_warning("cannot open %s for writing", zFilename); + }else{ + if( fUnicode ) { + int bomSize; + const unsigned char *bom = get_utf8_bom(&bomSize); + fwrite(bom, 1, bomSize, f); + blob_to_utf8_no_bom(p, 0); + }else if( fHasInvalidUtf8 ){ + blob_cp1252_to_utf8(p); + } + if( fHasAnyCr ){ + blob_to_lf_only(p); + } + fwrite(blob_buffer(p), 1, blob_size(p), f); + fclose(f); + } return 1; }else if( cReply!='y' && cReply!='Y' ){ fossil_fatal("Abandoning commit due to %s in %s", zWarning, blob_str(&fname)); } @@ -1340,11 +1534,11 @@ ** passed to the --branch option. ** ** Use the --branchcolor option followed by a color name (ex: ** '#ffc0c0') to specify the background color of entries in the new ** branch when shown in the web timeline interface. The use of -** the --branchcolor option is not recommend. Instead, let Fossil +** the --branchcolor option is not recommended. Instead, let Fossil ** choose the branch color automatically. ** ** The --bgcolor option works like --branchcolor but only sets the ** background color for a single check-in. Subsequent check-ins revert ** to the default color. @@ -1361,14 +1555,21 @@ ** unless the interactive user chooses to proceed. If there is no ** interactive user or these warnings should be skipped for some other ** reason, the --no-warnings option may be used. A check-in is not ** allowed against a closed leaf. ** +** If a commit message is blank, you will be prompted: +** ("continue (y/N)?") to confirm you really want to commit with a +** blank commit message. The default value is "N", do not commit. +** ** The --private option creates a private check-in that is never synced. ** Children of private check-ins are automatically private. ** -** the --tag option applies the symbolic tag name to the check-in. +** The --tag option applies the symbolic tag name to the check-in. +** +** The --sha1sum option detects edited files by computing each file's +** SHA1 hash rather than just checking for changes to its size or mtime. ** ** Options: ** --allow-conflict allow unresolved merge conflicts ** --allow-empty allow a commit with no changes ** --allow-fork allow the commit to fork @@ -1377,20 +1578,23 @@ ** --bgcolor COLOR apply COLOR to this one check-in only ** --branch NEW-BRANCH-NAME check in to this new branch ** --branchcolor COLOR apply given COLOR to the branch ** --close close the branch being committed ** --delta use a delta manifest in the commit process +** --integrate close all merged-in branches ** -m|--comment COMMENT-TEXT use COMMENT-TEXT as commit comment ** -M|--message-file FILE read the commit comment from given file ** --mimetype MIMETYPE mimetype of check-in comment ** -n|--dry-run If given, display instead of run actions ** --no-warnings omit all warnings about file contents ** --nosign do not attempt to sign this commit with gpg ** --private do not sync changes and their descendants -** --tag TAG-NAME assign given tag TAG-NAME to the checkin +** --sha1sum verify file status using SHA1 hashing rather +** than relying on file mtimes +** --tag TAG-NAME assign given tag TAG-NAME to the check-in ** -** See also: branch, changes, checkout, extra, sync +** See also: branch, changes, checkout, extras, sync */ void commit_cmd(void){ int hasChanges; /* True if unsaved changes exist */ int vid; /* blob-id of parent version */ int nrid; /* blob-id of a modified file */ @@ -1397,10 +1601,11 @@ int nvid; /* Blob-id of the new check-in */ Blob comment; /* Check-in comment */ const char *zComment; /* Check-in comment */ Stmt q; /* Various queries */ char *zUuid; /* UUID of the new check-in */ + int useSha1sum = 0; /* True to verify file status using SHA1 hashing */ int noSign = 0; /* True to omit signing the manifest using GPG */ int isAMerge = 0; /* True if checking in a merge */ int noWarningFlag = 0; /* True if skipping all warnings */ int forceFlag = 0; /* Undocumented: Disables all checks */ int forceDelta = 0; /* Force a delta-manifest */ @@ -1428,10 +1633,11 @@ Blob ans; char cReply; memset(&sCiInfo, 0, sizeof(sCiInfo)); url_proxy_options(); + useSha1sum = find_option("sha1sum", 0, 0)!=0; noSign = find_option("nosign",0,0)!=0; forceDelta = find_option("delta",0,0)!=0; forceBaseline = find_option("baseline",0,0)!=0; if( forceDelta && forceBaseline ){ fossil_fatal("cannot use --delta and --baseline together"); @@ -1449,10 +1655,11 @@ noWarningFlag = find_option("no-warnings", 0, 0)!=0; sCiInfo.zBranch = find_option("branch","b",1); sCiInfo.zColor = find_option("bgcolor",0,1); sCiInfo.zBrClr = find_option("branchcolor",0,1); sCiInfo.closeFlag = find_option("close",0,0)!=0; + sCiInfo.integrateFlag = find_option("integrate",0,0)!=0; sCiInfo.zMimetype = find_option("mimetype",0,1); while( (zTag = find_option("tag",0,1))!=0 ){ if( zTag[0]==0 ) continue; sCiInfo.azTag = fossil_realloc((void*)sCiInfo.azTag, sizeof(char*)*(nTag+2)); sCiInfo.azTag[nTag++] = zTag; @@ -1492,20 +1699,21 @@ forceBaseline = 1; } /* Get the ID of the parent manifest artifact */ vid = db_lget_int("checkout", 0); - if( content_is_private(vid) ){ + if( vid==0 ){ + useCksum = 1; + }else if( content_is_private(vid) ){ g.markPrivate = 1; } /* ** Autosync if autosync is enabled and this is not a private check-in. */ if( !g.markPrivate ){ - if( autosync(SYNC_PULL) ){ - blob_zero(&ans); + if( autosync_loop(SYNC_PULL, db_get_int("autosync-tries", 1)) ){ prompt_user("continue in spite of sync failure (y/N)? ", &ans); cReply = blob_str(&ans)[0]; if( cReply!='y' && cReply!='Y' ){ fossil_exit(1); } @@ -1514,11 +1722,10 @@ /* Require confirmation to continue with the check-in if there is ** clock skew */ if( g.clockSkewSeen ){ - blob_zero(&ans); prompt_user("continue in spite of time skew (y/N)? ", &ans); cReply = blob_str(&ans)[0]; if( cReply!='y' && cReply!='Y' ){ fossil_exit(1); } @@ -1533,14 +1740,13 @@ ** array is allocated to contain the "id" field from the vfile table ** for each file to be committed. Or, if aCommitFile is NULL, all files ** should be committed. */ if( select_commit_files() ){ - blob_zero(&ans); prompt_user("continue (y/N)? ", &ans); cReply = blob_str(&ans)[0]; - if( cReply!='y' && cReply!='Y' ) fossil_exit(1);; + if( cReply!='y' && cReply!='Y' ) fossil_exit(1); } isAMerge = db_exists("SELECT 1 FROM vmerge WHERE id=0 OR id<-2"); if( g.aCommitFile && isAMerge ){ fossil_fatal("cannot do a partial commit of a merge"); } @@ -1573,11 +1779,11 @@ */ if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ fossil_fatal("no such user: %s", g.zLogin); } - hasChanges = unsaved_changes(); + hasChanges = unsaved_changes(useSha1sum ? CKSIG_SHA1 : 0); db_begin_transaction(); db_record_repository_filename(0); if( hasChanges==0 && !isAMerge && !allowEmpty && !forceFlag ){ fossil_fatal("nothing has changed; use --allow-empty to override"); } @@ -1599,23 +1805,42 @@ } /* ** Do not allow a commit that will cause a fork unless the --allow-fork ** or --force flags is used, or unless this is a private check-in. + ** The initial commit MUST have tags "trunk" and "sym-trunk". */ - if( sCiInfo.zBranch==0 && allowFork==0 && forceFlag==0 + if( !vid ){ + if( sCiInfo.zBranch==0 ){ + if( allowFork==0 && forceFlag==0 && g.markPrivate==0 + && db_exists("SELECT 1 from event where type='ci'") ){ + fossil_fatal("would fork. \"update\" first or use --allow-fork."); + } + sCiInfo.zBranch = db_get("main-branch", "trunk"); + } + }else if( sCiInfo.zBranch==0 && allowFork==0 && forceFlag==0 && g.markPrivate==0 && !is_a_leaf(vid) ){ fossil_fatal("would fork. \"update\" first or use --allow-fork."); } /* - ** Do not allow a commit against a closed leaf + ** Do not allow a commit against a closed leaf unless the commit + ** ends up on a different branch. */ - if( db_exists("SELECT 1 FROM tagxref" + if( + /* parent check-in has the "closed" tag... */ + db_exists("SELECT 1 FROM tagxref" " WHERE tagid=%d AND rid=%d AND tagtype>0", - TAG_CLOSED, vid) ){ + TAG_CLOSED, vid) + /* ... and the new check-in has no --branch option or the --branch + ** option does not actually change the branch */ + && (sCiInfo.zBranch==0 + || db_exists("SELECT 1 FROM tagxref" + " WHERE tagid=%d AND rid=%d AND tagtype>0" + " AND value=%Q", TAG_BRANCH, vid, sCiInfo.zBranch)) + ){ fossil_fatal("cannot commit against a closed leaf"); } if( useCksum ) vfile_aggregate_checksum_disk(vid, &cksum1); if( zComment ){ @@ -1629,20 +1854,18 @@ blob_zero(&comment); }else{ char *zInit = db_text(0, "SELECT value FROM vvar WHERE name='ci-comment'"); prepare_commit_comment(&comment, zInit, &sCiInfo, vid); if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){ - blob_zero(&ans); prompt_user("unchanged check-in comment. continue (y/N)? ", &ans); cReply = blob_str(&ans)[0]; - if( cReply!='y' && cReply!='Y' ) fossil_exit(1);; + if( cReply!='y' && cReply!='Y' ) fossil_exit(1); } free(zInit); } if( blob_size(&comment)==0 ){ if( !dryRunFlag ){ - blob_zero(&ans); prompt_user("empty check-in comment. continue (y/N)? ", &ans); cReply = blob_str(&ans)[0]; if( cReply!='y' && cReply!='Y' ){ fossil_exit(1); } @@ -1743,11 +1966,11 @@ if( pBaseline ){ Blob delta; create_manifest(&delta, zBaselineUuid, pBaseline, vid, &sCiInfo, &szD); /* ** At this point, two manifests have been constructed, either of - ** which would work for this checkin. The first manifest (held + ** which would work for this check-in. The first manifest (held ** in the "manifest" variable) is a baseline manifest and the second ** (held in variable named "delta") is a delta manifest. The ** question now is: which manifest should we use? ** ** Let B be the number of F-cards in the baseline manifest and @@ -1771,11 +1994,10 @@ }else if( forceDelta ){ fossil_fatal("unable to find a baseline-manifest for the delta"); } } if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){ - blob_zero(&ans); prompt_user("unable to sign manifest. continue (y/N)? ", &ans); cReply = blob_str(&ans)[0]; if( cReply!='y' && cReply!='Y' ){ fossil_exit(1); } @@ -1798,11 +2020,14 @@ nvid = content_put(&manifest); if( nvid==0 ){ fossil_fatal("trouble committing manifest: %s", g.zErrMsg); } db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid); - manifest_crosslink(nvid, &manifest); + if( manifest_crosslink(nvid, &manifest, + dryRunFlag ? MC_NONE : MC_PERMIT_HOOKS)==0 ){ + fossil_fatal("%s\n", g.zErrMsg); + } assert( blob_is_reset(&manifest) ); content_deltify(vid, nvid, 0); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid); db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid" @@ -1839,11 +2064,11 @@ ); db_lset_int("checkout", nvid); if( useCksum ){ /* Verify that the repository checksum matches the expected checksum - ** calculated before the checkin started (and stored as the R record + ** calculated before the check-in started (and stored as the R record ** of the manifest file). */ vfile_aggregate_checksum_repository(nvid, &cksum2); if( blob_compare(&cksum1, &cksum2) ){ vfile_compare_repository_to_disk(nvid); @@ -1883,11 +2108,11 @@ exit(1); } db_end_transaction(0); if( !g.markPrivate ){ - autosync(SYNC_PUSH|SYNC_PULL); + autosync_loop(SYNC_PUSH|SYNC_PULL, db_get_int("autosync-tries", 1)); } if( count_nonbranch_children(vid)>1 ){ fossil_print("**** warning: a fork has occurred *****\n"); } } Index: src/checkout.c ================================================================== --- src/checkout.c +++ src/checkout.c @@ -26,29 +26,28 @@ ** Check to see if there is an existing checkout that has been ** modified. Return values: ** ** 0: There is an existing checkout but it is unmodified ** 1: There is a modified checkout - there are unsaved changes -** 2: There is no existing checkout */ -int unsaved_changes(void){ +int unsaved_changes(unsigned int cksigFlags){ int vid; db_must_be_within_tree(); vid = db_lget_int("checkout",0); - if( vid==0 ) return 2; - vfile_check_signature(vid, CKSIG_ENOTFILE); + vfile_check_signature(vid, cksigFlags|CKSIG_ENOTFILE); return db_exists("SELECT 1 FROM vfile WHERE chnged" " OR coalesce(origname!=pathname,0)"); } /* ** Undo the current check-out. Unlink all files from the disk. ** Clear the VFILE table. */ void uncheckout(int vid){ - if( vid==0 ) return; - vfile_unlink(vid); + if( vid>0 ){ + vfile_unlink(vid); + } db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid); } /* @@ -55,26 +54,28 @@ ** Given the abbreviated UUID name of a version, load the content of that ** version in the VFILE table. Return the VID for the version. ** ** If anything goes wrong, panic. */ -int load_vfile(const char *zName){ +int load_vfile(const char *zName, int forceMissingFlag){ Blob uuid; int vid; blob_init(&uuid, zName, -1); if( name_to_uuid(&uuid, 1, "ci") ){ - fossil_fatal(g.zErrMsg); + fossil_fatal("%s", g.zErrMsg); } vid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &uuid); if( vid==0 ){ fossil_fatal("no such check-in: %s", g.argv[2]); } if( !is_a_version(vid) ){ - fossil_fatal("object [%.10s] is not a check-in", blob_str(&uuid)); + fossil_fatal("object [%S] is not a check-in", blob_str(&uuid)); } - load_vfile_from_rid(vid); + if( load_vfile_from_rid(vid) && !forceMissingFlag ){ + fossil_fatal("missing content, unable to checkout"); + }; return vid; } /* ** Set or clear the vfile.isexe flag for a file. @@ -136,14 +137,15 @@ if( db_get_boolean("manifest",0) ){ blob_zero(&manifest); content_get(vid, &manifest); zManFile = mprintf("%smanifest", g.zLocalRoot); - blob_write_to_file(&manifest, zManFile); - free(zManFile); blob_zero(&hash); sha1sum_blob(&manifest, &hash); + sterilize_manifest(&manifest); + blob_write_to_file(&manifest, zManFile); + free(zManFile); zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); blob_append(&hash, "\n", 1); blob_write_to_file(&hash, zManFile); free(zManFile); blob_reset(&hash); @@ -157,11 +159,11 @@ zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); file_delete(zManFile); free(zManFile); } } - + } /* ** COMMAND: checkout* ** COMMAND: co* @@ -175,36 +177,43 @@ ** leaves files on disk unchanged, except the manifest and manifest.uuid ** files. ** ** The --latest flag can be used in place of VERSION to checkout the ** latest version in the repository. -** +** ** Options: -** --force Ignore edited files in the current checkout -** --keep Only update the manifest and manifest.uuid files +** --force Ignore edited files in the current checkout +** --keep Only update the manifest and manifest.uuid files +** --force-missing Force checkout even if content is missing ** ** See also: update */ void checkout_cmd(void){ int forceFlag; /* Force checkout even if edits exist */ + int forceMissingFlag; /* Force checkout even if missing content */ int keepFlag; /* Do not change any files on disk */ int latestFlag; /* Checkout the latest version */ char *zVers; /* Version to checkout */ int promptFlag; /* True to prompt before overwriting */ int vid, prior; Blob cksum1, cksum1b, cksum2; - + db_must_be_within_tree(); db_begin_transaction(); forceFlag = find_option("force","f",0)!=0; + forceMissingFlag = find_option("force-missing",0,0)!=0; keepFlag = find_option("keep",0,0)!=0; latestFlag = find_option("latest",0,0)!=0; promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0; + + /* We should be done with options.. */ + verify_all_options(); + if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){ usage("VERSION|--latest ?--force? ?--keep?"); } - if( !forceFlag && unsaved_changes()==1 ){ + if( !forceFlag && unsaved_changes(0) ){ fossil_fatal("there are unsaved changes in the current checkout"); } if( forceFlag ){ db_multi_exec("DELETE FROM vfile"); prior = 0; @@ -220,16 +229,16 @@ zVers = db_text(0, "SELECT uuid FROM event, blob" " WHERE event.objid=blob.rid AND event.type='ci'" " ORDER BY event.mtime DESC"); } if( zVers==0 ){ - fossil_fatal("cannot locate \"latest\" checkout"); + return; } }else{ zVers = g.argv[2]; } - vid = load_vfile(zVers); + vid = load_vfile(zVers, forceMissingFlag); if( prior==vid ){ return; } if( !keepFlag ){ uncheckout(prior); @@ -288,22 +297,27 @@ ** See also: open */ void close_cmd(void){ int forceFlag = find_option("force","f",0)!=0; db_must_be_within_tree(); - if( !forceFlag && unsaved_changes()==1 ){ + + /* We should be done with options.. */ + verify_all_options(); + + if( !forceFlag && unsaved_changes(0) ){ fossil_fatal("there are unsaved changes in the current checkout"); } if( !forceFlag - && db_exists("SELECT 1 FROM %s.sqlite_master WHERE name='stash'", - db_name("localdb")) + && db_table_exists("localdb","stash") && db_exists("SELECT 1 FROM %s.stash", db_name("localdb")) ){ fossil_fatal("closing the checkout will delete your stash"); } if( db_is_writeable("repository") ){ - db_multi_exec("DELETE FROM config WHERE name='ckout:%q'", g.zLocalRoot); + char *zUnset = mprintf("ckout:%q", g.zLocalRoot); + db_unset(zUnset, 1); + fossil_free(zUnset); } unlink_local_database(1); db_close(1); unlink_local_database(0); } Index: src/clone.c ================================================================== --- src/clone.c +++ src/clone.c @@ -83,11 +83,11 @@ ** COMMAND: clone ** ** Usage: %fossil clone ?OPTIONS? URL FILENAME ** ** Make a clone of a repository specified by URL in the local -** file named FILENAME. +** file named FILENAME. ** ** URL must be in one of the following form: ([...] mean optional) ** HTTP/HTTPS protocol: ** http[s]://[userid[:password]@]host[:port][/path] ** @@ -96,50 +96,65 @@ ** [?fossil=path/to/fossil.exe] ** ** Filesystem: ** [file://]path/to/repo.fossil ** -** Note: For ssh and filesystem, path must have an extra leading +** Note: For ssh and filesystem, path must have an extra leading ** '/' to use an absolute path. ** ** By default, your current login name is used to create the default ** admin user. This can be overridden using the -A|--admin-user ** parameter. ** ** Options: ** --admin-user|-A USERNAME Make USERNAME the administrator -** --private Also clone private branches +** --once Don't save url. +** --private Also clone private branches ** --ssl-identity=filename Use the SSL identity if requested by the server +** --ssh-command|-c 'command' Use this SSH command +** --httpauth|-B 'user:pass' Add HTTP Basic Authorization to requests +** --verbose Show more statistics in output ** ** See also: init */ void clone_cmd(void){ char *zPassword; const char *zDefaultUser; /* Optional name of the default user */ + const char *zHttpAuth; /* HTTP Authorization user:pass information */ int nErr = 0; - int bPrivate = 0; /* Also clone private branches */ + int urlFlags = URL_PROMPT_PW | URL_REMEMBER; + int syncFlags = SYNC_CLONE; - if( find_option("private",0,0)!=0 ) bPrivate = SYNC_PRIVATE; + /* Also clone private branches */ + if( find_option("private",0,0)!=0 ) syncFlags |= SYNC_PRIVATE; + if( find_option("once",0,0)!=0) urlFlags &= ~URL_REMEMBER; + if( find_option("verbose",0,0)!=0) syncFlags |= SYNC_VERBOSE; + zHttpAuth = find_option("httpauth","B",1); + zDefaultUser = find_option("admin-user","A",1); + clone_ssh_find_options(); url_proxy_options(); + + /* We should be done with options.. */ + verify_all_options(); + if( g.argc < 4 ){ usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY"); } db_open_config(0); - if( file_size(g.argv[3])>0 ){ + if( -1 != file_size(g.argv[3]) ){ fossil_fatal("file already exists: %s", g.argv[3]); } - zDefaultUser = find_option("admin-user","A",1); - - url_parse(g.argv[2], URL_PROMPT_PW|URL_ASK_REMEMBER_PW); - if( g.urlIsFile ){ - file_copy(g.urlName, g.argv[3]); + url_parse(g.argv[2], urlFlags); + if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user; + if( g.url.isFile ){ + file_copy(g.url.name, g.argv[3]); db_close(1); db_open_repository(g.argv[3]); db_record_repository_filename(g.argv[3]); url_remember(); - if( !bPrivate ) delete_private_content(); + if( !(syncFlags & SYNC_PRIVATE) ) delete_private_content(); shun_artifacts(); db_create_default_users(1, zDefaultUser); if( zDefaultUser ){ g.zLogin = zDefaultUser; }else{ @@ -149,14 +164,16 @@ }else{ db_create_repository(g.argv[3]); db_open_repository(g.argv[3]); db_begin_transaction(); db_record_repository_filename(g.argv[3]); - db_initial_setup(0, 0, zDefaultUser, 0); + db_initial_setup(0, 0, zDefaultUser); user_select(); db_set("content-schema", CONTENT_SCHEMA, 0); - db_set("aux-schema", AUX_SCHEMA, 0); + db_set("aux-schema", AUX_SCHEMA_MAX, 0); + db_set("rebuilt", get_version(), 0); + remember_or_get_http_auth(zHttpAuth, urlFlags & URL_REMEMBER, g.argv[2]); url_remember(); if( g.zSSLIdentity!=0 ){ /* If the --ssl-identity option was specified, store it as a setting */ Blob fn; blob_zero(&fn); @@ -165,15 +182,17 @@ blob_reset(&fn); } db_multi_exec( "REPLACE INTO config(name,value,mtime)" " VALUES('server-code', lower(hex(randomblob(20))), now());" + "DELETE FROM config WHERE name='project-code';" ); url_enable_proxy(0); + clone_ssh_db_set_options(); url_get_password_if_needed(); g.xlinkClusterOnly = 1; - nErr = client_sync(SYNC_CLONE | bPrivate,CONFIGSET_ALL,0); + nErr = client_sync(syncFlags,CONFIGSET_ALL,0); g.xlinkClusterOnly = 0; verify_cancel(); db_end_transaction(0); db_close(1); if( nErr ){ @@ -183,10 +202,89 @@ db_open_repository(g.argv[3]); } db_begin_transaction(); fossil_print("Rebuilding repository meta-data...\n"); rebuild_db(0, 1, 0); - fossil_print("project-id: %s\n", db_get("project-code", 0)); + fossil_print("Extra delta compression... "); fflush(stdout); + extra_deltification(); + db_end_transaction(0); + fossil_print("\nVacuuming the database... "); fflush(stdout); + if( db_int(0, "PRAGMA page_count")>1000 + && db_int(0, "PRAGMA page_size")<8192 ){ + db_multi_exec("PRAGMA page_size=8192;"); + } + db_multi_exec("VACUUM"); + fossil_print("\nproject-id: %s\n", db_get("project-code", 0)); + fossil_print("server-id: %s\n", db_get("server-code", 0)); zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword); - db_end_transaction(0); +} + +/* +** If user chooses to use HTTP Authentication over unencrypted HTTP, +** remember decision. Otherwise, if the URL is being changed and no +** preference has been indicated, err on the safe side and revert the +** decision. Set the global preference if the URL is not being changed. +*/ +void remember_or_get_http_auth( + const char *zHttpAuth, /* Credentials in the form "user:password" */ + int fRemember, /* True to remember credentials for later reuse */ + const char *zUrl /* URL for which these credentials apply */ +){ + char *zKey = mprintf("http-auth:%s", g.url.canonical); + if( zHttpAuth && zHttpAuth[0] ){ + g.zHttpAuth = mprintf("%s", zHttpAuth); + } + if( fRemember ){ + if( g.zHttpAuth && g.zHttpAuth[0] ){ + set_httpauth(g.zHttpAuth); + }else if( zUrl && zUrl[0] ){ + db_unset(zKey, 0); + }else{ + g.zHttpAuth = get_httpauth(); + } + }else if( g.zHttpAuth==0 && zUrl==0 ){ + g.zHttpAuth = get_httpauth(); + } + free(zKey); +} + +/* +** Get the HTTP Authorization preference from db. +*/ +char *get_httpauth(void){ + char *zKey = mprintf("http-auth:%s", g.url.canonical); + char * rc = unobscure(db_get(zKey, 0)); + free(zKey); + return rc; +} + +/* +** Set the HTTP Authorization preference in db. +*/ +void set_httpauth(const char *zHttpAuth){ + char *zKey = mprintf("http-auth:%s", g.url.canonical); + db_set(zKey, obscure(zHttpAuth), 0); + free(zKey); +} + +/* +** Look for SSH clone command line options and setup in globals. +*/ +void clone_ssh_find_options(void){ + const char *zSshCmd; /* SSH command string */ + + zSshCmd = find_option("ssh-command","c",1); + if( zSshCmd && zSshCmd[0] ){ + g.zSshCmd = mprintf("%s", zSshCmd); + } +} + +/* +** Set SSH options discovered in global variables (set from command line +** options). +*/ +void clone_ssh_db_set_options(void){ + if( g.zSshCmd && g.zSshCmd[0] ){ + db_set("ssh-command", g.zSshCmd, 0); + } } ADDED src/codecheck1.c Index: src/codecheck1.c ================================================================== --- /dev/null +++ src/codecheck1.c @@ -0,0 +1,564 @@ +/* +** Copyright (c) 2014 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) +** +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This program reads Fossil source code files and tries to verify that +** printf-style format strings are correct. +** +** This program implements a compile-time validation step on the Fossil +** source code. Running this program is entirely optional. Its role is +** similar to the -Wall compiler switch on gcc, or the scan-build utility +** of clang, or other static analyzers. The purpose is to try to identify +** problems in the source code at compile-time. The difference is that this +** static checker is specifically designed for the particular printf formatter +** implementation used by Fossil. +** +** Checks include: +** +** * Verify that vararg formatting routines like blob_printf() or +** db_multi_exec() have the correct number of arguments for their +** format string. +** +** * For routines designed to generate SQL, warn about the use of %s +** which might allow SQL injection. +*/ +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <assert.h> + +/* +** Malloc, aborting if it fails. +*/ +void *safe_malloc(int nByte){ + void *x = malloc(nByte); + if( x==0 ){ + fprintf(stderr, "failed to allocate %d bytes\n", nByte); + exit(1); + } + return x; +} +void *safe_realloc(void *pOld, int nByte){ + void *x = realloc(pOld, nByte); + if( x==0 ){ + fprintf(stderr, "failed to allocate %d bytes\n", nByte); + exit(1); + } + return x; +} + +/* +** Read the entire content of the file named zFilename into memory obtained +** from malloc(). Add a zero-terminator to the end. +** Return a pointer to that memory. +*/ +static char *read_file(const char *zFilename){ + FILE *in; + char *z; + int nByte; + int got; + in = fopen(zFilename, "rb"); + if( in==0 ){ + return 0; + } + fseek(in, 0, SEEK_END); + nByte = ftell(in); + fseek(in, 0, SEEK_SET); + z = safe_malloc( nByte+1 ); + got = fread(z, 1, nByte, in); + z[got] = 0; + fclose(in); + return z; +} + +/* +** When parsing the input file, the following token types are recognized. +*/ +#define TK_SPACE 1 /* Whitespace or comments */ +#define TK_ID 2 /* An identifier */ +#define TK_STR 3 /* A string literal in double-quotes */ +#define TK_OTHER 4 /* Any other token */ +#define TK_EOF 99 /* End of file */ + +/* +** Determine the length and type of the token beginning at z[0] +*/ +static int token_length(const char *z, int *pType, int *pLN){ + int i; + if( z[0]==0 ){ + *pType = TK_EOF; + return 0; + } + if( z[0]=='"' || z[0]=='\'' ){ + for(i=1; z[i] && z[i]!=z[0]; i++){ + if( z[i]=='\\' && z[i+1]!=0 ){ + if( z[i+1]=='\n' ) (*pLN)++; + i++; + } + } + if( z[i]!=0 ) i++; + *pType = z[0]=='"' ? TK_STR : TK_OTHER; + return i; + } + if( isalnum(z[0]) || z[0]=='_' ){ + for(i=1; isalnum(z[i]) || z[i]=='_'; i++){} + *pType = isalpha(z[0]) || z[0]=='_' ? TK_ID : TK_OTHER; + return i; + } + if( isspace(z[0]) ){ + if( z[0]=='\n' ) (*pLN)++; + for(i=1; isspace(z[i]); i++){ + if( z[i]=='\n' ) (*pLN)++; + } + *pType = TK_SPACE; + return i; + } + if( z[0]=='/' && z[1]=='*' ){ + for(i=2; z[i] && (z[i]!='*' || z[i+1]!='/'); i++){ + if( z[i]=='\n' ) (*pLN)++; + } + if( z[i] ) i += 2; + *pType = TK_SPACE; + return i; + } + if( z[0]=='/' && z[1]=='/' ){ + for(i=2; z[i] && z[i]!='\n'; i++){} + if( z[i] ){ + (*pLN)++; + i++; + } + *pType = TK_SPACE; + return i; + } + *pType = TK_OTHER; + return 1; +} + +/* +** Return the next non-whitespace token +*/ +const char *next_non_whitespace(const char *z, int *pLen, int *pType){ + int len; + int eType; + int ln = 0; + while( (len = token_length(z, &eType, &ln))>0 && eType==TK_SPACE ){ + z += len; + } + *pLen = len; + *pType = eType; + return z; +} + +/* +** Return index into z[] for the first balanced TK_OTHER token with +** value cValue. +*/ +static int distance_to(const char *z, char cVal){ + int len; + int dist = 0; + int eType; + int nNest = 0; + int ln = 0; + while( z[0] && (len = token_length(z, &eType, &ln))>0 ){ + if( eType==TK_OTHER ){ + if( z[0]==cVal && nNest==0 ){ + break; + }else if( z[0]=='(' ){ + nNest++; + }else if( z[0]==')' ){ + nNest--; + } + } + dist += len; + z += len; + } + return dist; +} + +/* +** Return the first non-whitespace characters in z[] +*/ +static const char *skip_space(const char *z){ + while( isspace(z[0]) ){ z++; } + return z; +} + +/* +** Return true if the input is a string literal. +*/ +static int is_string_lit(const char *z){ + int nu1, nu2; + z = next_non_whitespace(z, &nu1, &nu2); + return z[0]=='"'; +} + +/* +** Return true if the input is an expression of string literals: +** +** EXPR ? "..." : "..." +*/ +static int is_string_expr(const char *z){ + int len = 0, eType; + const char *zOrig = z; + len = distance_to(z, '?'); + if( z[len]==0 && skip_space(z)[0]=='(' ){ + z = skip_space(z) + 1; + len = distance_to(z, '?'); + } + z += len; + if( z[0]=='?' ){ + z++; + z = next_non_whitespace(z, &len, &eType); + if( eType==TK_STR ){ + z += len; + z = next_non_whitespace(z, &len, &eType); + if( eType==TK_OTHER && z[0]==':' ){ + z += len; + z = next_non_whitespace(z, &len, &eType); + if( eType==TK_STR ){ + z += len; + z = next_non_whitespace(z, &len, &eType); + if( eType==TK_EOF ) return 1; + if( eType==TK_OTHER && z[0]==')' && skip_space(zOrig)[0]=='(' ){ + z += len; + z = next_non_whitespace(z, &len, &eType); + if( eType==TK_EOF ) return 1; + } + } + } + } + } + return 0; +} + +/* +** A list of functions that return strings that are safe to insert into +** SQL using %s. +*/ +static const char *azSafeFunc[] = { + "filename_collation", + "db_name", + "timeline_utc", + "leaf_is_closed_sql", + "timeline_query_for_www", + "timeline_query_for_tty", + "blob_sql_text", + "glob_expr", + "fossil_all_reserved_names", + "configure_inop_rhs", + "db_setting_inop_rhs", +}; + +/* +** Return true if the input is an argument that is safe to use with %s +** while building an SQL statement. +*/ +static int is_s_safe(const char *z){ + int len, eType; + int i; + + /* A string literal is safe for use with %s */ + if( is_string_lit(z) ) return 1; + + /* Certain functions are guaranteed to return a string that is safe + ** for use with %s */ + z = next_non_whitespace(z, &len, &eType); + for(i=0; i<sizeof(azSafeFunc)/sizeof(azSafeFunc[0]); i++){ + if( eType==TK_ID + && strncmp(z, azSafeFunc[i], len)==0 + && strlen(azSafeFunc[i])==len + ){ + return 1; + } + } + + /* Expressions of the form: EXPR ? "..." : "...." can count as + ** a string literal. */ + if( is_string_expr(z) ) return 1; + + /* If the "safe-for-%s" comment appears in the argument, then + ** let it through */ + if( strstr(z, "/*safe-for-%s*/")!=0 ) return 1; + + return 0; +} + +/* +** Processing flags +*/ +#define FMT_NO_S 0x00001 /* Do not allow %s substitutions */ + +/* +** A list of internal Fossil interfaces that take a printf-style format +** string. +*/ +struct { + const char *zFName; /* Name of the function */ + int iFmtArg; /* Index of format argument. Leftmost is 1. */ + unsigned fmtFlags; /* Processing flags */ +} aFmtFunc[] = { + { "admin_log", 1, 0 }, + { "blob_append_sql", 2, FMT_NO_S }, + { "blob_appendf", 2, 0 }, + { "cgi_panic", 1, 0 }, + { "cgi_redirectf", 1, 0 }, + { "db_blob", 2, FMT_NO_S }, + { "db_double", 2, FMT_NO_S }, + { "db_err", 1, 0 }, + { "db_exists", 1, FMT_NO_S }, + { "db_int", 2, FMT_NO_S }, + { "db_int64", 2, FMT_NO_S }, + { "db_multi_exec", 1, FMT_NO_S }, + { "db_optional_sql", 2, FMT_NO_S }, + { "db_prepare", 2, FMT_NO_S }, + { "db_prepare_ignore_error", 2, FMT_NO_S }, + { "db_static_prepare", 2, FMT_NO_S }, + { "db_text", 2, FMT_NO_S }, + { "form_begin", 2, 0 }, + { "fossil_error", 2, 0 }, + { "fossil_errorlog", 1, 0 }, + { "fossil_fatal", 1, 0 }, + { "fossil_fatal_recursive", 1, 0 }, + { "fossil_panic", 1, 0 }, + { "fossil_print", 1, 0 }, + { "fossil_trace", 1, 0 }, + { "fossil_warning", 1, 0 }, + { "href", 1, 0 }, + { "json_new_string_f", 1, 0 }, + { "mprintf", 1, 0 }, + { "socket_set_errmsg", 1, 0 }, + { "ssl_set_errmsg", 1, 0 }, + { "style_header", 1, 0 }, + { "style_set_current_page", 1, 0 }, + { "webpage_error", 1, 0 }, + { "xhref", 2, 0 }, +}; + +/* +** Determine if the indentifier zIdent of length nIndent is a Fossil +** internal interface that uses a printf-style argument. Return zero if not. +** Return the index of the format string if true with the left-most +** argument having an index of 1. +*/ +static int isFormatFunc(const char *zIdent, int nIdent, unsigned *pFlags){ + int upr, lwr; + lwr = 0; + upr = sizeof(aFmtFunc)/sizeof(aFmtFunc[0]) - 1; + while( lwr<=upr ){ + unsigned x = (lwr + upr)/2; + int c = strncmp(zIdent, aFmtFunc[x].zFName, nIdent); + if( c==0 ){ + if( aFmtFunc[x].zFName[nIdent]==0 ){ + *pFlags = aFmtFunc[x].fmtFlags; + return aFmtFunc[x].iFmtArg; + } + c = -1; + } + if( c<0 ){ + upr = x - 1; + }else{ + lwr = x + 1; + } + } + *pFlags = 0; + return 0; +} + +/* +** Return the expected number of arguments for the format string. +** Return -1 if the value cannot be computed. +** +** For each argument less than nType, store the conversion character +** for that argument in cType[i]. +*/ +static int formatArgCount(const char *z, int nType, char *cType){ + int nArg = 0; + int i, k; + int len; + int eType; + int ln = 0; + while( z[0] ){ + len = token_length(z, &eType, &ln); + if( eType==TK_STR ){ + for(i=1; i<len-1; i++){ + if( z[i]!='%' ) continue; + if( z[i+1]=='%' ){ i++; continue; } + for(k=i+1; k<len && !isalpha(z[k]); k++){ + if( z[k]=='*' || z[k]=='#' ){ + if( nArg<nType ) cType[nArg] = z[k]; + nArg++; + } + } + if( z[k]!='R' ){ + if( nArg<nType ) cType[nArg] = z[k]; + nArg++; + } + } + } + z += len; + } + return nArg; +} + +/* +** The function call that begins at zFCall[0] (which is on line lnFCall of the +** original file) is a function that uses a printf-style format string +** on argument number fmtArg. It has processings flags fmtFlags. Do +** compile-time checking on this function, output any errors, and return +** the number of errors. +*/ +static int checkFormatFunc( + const char *zFilename, /* Name of the file being processed */ + const char *zFCall, /* Pointer to start of function call */ + int lnFCall, /* Line number that holds z[0] */ + int fmtArg, /* Format string should be this argument */ + int fmtFlags /* Extra processing flags */ +){ + int szFName; + int eToken; + int ln = lnFCall; + int len; + const char *zStart; + char *z; + char *zCopy; + int nArg = 0; + const char **azArg = 0; + int i, k; + int nErr = 0; + char *acType; + + szFName = token_length(zFCall, &eToken, &ln); + zStart = next_non_whitespace(zFCall+szFName, &len, &eToken); + assert( zStart[0]=='(' && len==1 ); + len = distance_to(zStart+1, ')'); + zCopy = safe_malloc( len + 1 ); + memcpy(zCopy, zStart+1, len); + zCopy[len] = 0; + azArg = 0; + nArg = 0; + z = zCopy; + while( z[0] ){ + len = distance_to(z, ','); + azArg = safe_realloc((char*)azArg, (sizeof(azArg[0])+1)*(nArg+1)); + azArg[nArg++] = skip_space(z); + if( z[len]==0 ) break; + z[len] = 0; + for(i=len-1; i>0 && isspace(z[i]); i--){ z[i] = 0; } + z += len + 1; + } + acType = (char*)&azArg[nArg]; + if( fmtArg>nArg ){ + printf("%s:%d: too few arguments to %.*s()\n", + zFilename, lnFCall, szFName, zFCall); + nErr++; + }else{ + const char *zFmt = azArg[fmtArg-1]; + const char *zOverride = strstr(zFmt, "/*works-like:"); + if( zOverride ) zFmt = zOverride + sizeof("/*works-like:")-1; + if( !is_string_lit(zFmt) ){ + printf("%s:%d: %.*s() has non-constant format string\n", + zFilename, lnFCall, szFName, zFCall); + nErr++; + }else if( (k = formatArgCount(zFmt, nArg, acType))>=0 + && nArg!=fmtArg+k ){ + printf("%s:%d: too %s arguments to %.*s() " + "- got %d and expected %d\n", + zFilename, lnFCall, (nArg<fmtArg+k ? "few" : "many"), + szFName, zFCall, nArg, fmtArg+k); + nErr++; + }else if( fmtFlags & FMT_NO_S ){ + for(i=0; i<nArg && i<k; i++){ + if( (acType[i]=='s' || acType[i]=='z' || acType[i]=='b') + && !is_s_safe(azArg[fmtArg+i]) + ){ + printf("%s:%d: Argument %d to %.*s() not safe for SQL\n", + zFilename, lnFCall, i+fmtArg, szFName, zFCall); + nErr++; + } + } + } + } + if( nErr ){ + for(i=0; i<nArg; i++){ + printf(" arg[%d]: %s\n", i, azArg[i]); + } + } + + free((char*)azArg); + free(zCopy); + return nErr; +} + + +/* +** Do a design-rule check of format strings for the file named zName +** with content zContent. Write errors on standard output. Return +** the number of errors. +*/ +static int scan_file(const char *zName, const char *zContent){ + const char *z; + int ln = 0; + int szToken; + int eToken; + const char *zPrev; + int ePrev; + int szPrev; + int lnPrev; + int nCurly = 0; + int x; + unsigned fmtFlags = 0; + int nErr = 0; + + if( zContent==0 ){ + printf("cannot read file: %s\n", zName); + return 1; + } + for(z=zContent; z[0]; z += szToken){ + szToken = token_length(z, &eToken, &ln); + if( eToken==TK_SPACE ) continue; + if( eToken==TK_OTHER ){ + if( z[0]=='{' ){ + nCurly++; + }else if( z[0]=='}' ){ + nCurly--; + }else if( nCurly>0 && z[0]=='(' && ePrev==TK_ID + && (x = isFormatFunc(zPrev,szPrev,&fmtFlags))>0 ){ + nErr += checkFormatFunc(zName, zPrev, lnPrev, x, fmtFlags); + } + } + zPrev = z; + ePrev = eToken; + szPrev = szToken; + lnPrev = ln; + } + return nErr; +} + +/* +** Check for format-string design rule violations on all files listed +** on the command-line. +*/ +int main(int argc, char **argv){ + int i; + int nErr = 0; + for(i=1; i<argc; i++){ + char *zFile = read_file(argv[i]); + nErr += scan_file(argv[i], zFile); + free(zFile); + } + return nErr; +} Index: src/comformat.c ================================================================== --- src/comformat.c +++ src/comformat.c @@ -19,38 +19,282 @@ ** text on a TTY. */ #include "config.h" #include "comformat.h" #include <assert.h> +#ifdef _WIN32 +# include <windows.h> +#else +# include <termios.h> +# if defined(TIOCGWINSZ) +# include <sys/ioctl.h> +# endif +#endif + +#if INTERFACE +#define COMMENT_PRINT_NONE ((u32)0x00000000) /* No flags. */ +#define COMMENT_PRINT_LEGACY ((u32)0x00000001) /* Use legacy algorithm. */ +#define COMMENT_PRINT_TRIM_CRLF ((u32)0x00000002) /* Trim leading CR/LF. */ +#define COMMENT_PRINT_TRIM_SPACE ((u32)0x00000004) /* Trim leading/trailing. */ +#define COMMENT_PRINT_WORD_BREAK ((u32)0x00000008) /* Break lines on words. */ +#define COMMENT_PRINT_ORIG_BREAK ((u32)0x00000010) /* Break before original. */ +#define COMMENT_PRINT_DEFAULT (COMMENT_PRINT_LEGACY) /* Defaults. */ +#endif + +/* +** This is the previous value used by most external callers when they +** needed to specify a default maximum line length to be used with the +** comment_print() function. +*/ +#ifndef COMMENT_LEGACY_LINE_LENGTH +# define COMMENT_LEGACY_LINE_LENGTH (78) +#endif + +/* +** This is the number of spaces to print when a tab character is seen. +*/ +#ifndef COMMENT_TAB_WIDTH +# define COMMENT_TAB_WIDTH (8) +#endif + +/* +** This function sets the maximum number of characters to print per line +** based on the detected terminal line width, if available; otherwise, it +** uses the legacy default terminal line width minus the amount to indent. +** +** Zero is returned to indicate any failure. One is returned to indicate +** the successful detection of the terminal line width. Negative one is +** returned to indicate the terminal line width is using the hard-coded +** legacy default value. +*/ +static int comment_set_maxchars( + int indent, + int *pMaxChars +){ +#if defined(_WIN32) + CONSOLE_SCREEN_BUFFER_INFO csbi; + memset(&csbi, 0, sizeof(CONSOLE_SCREEN_BUFFER_INFO)); + if( GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) ){ + *pMaxChars = csbi.srWindow.Right - csbi.srWindow.Left - indent; + return 1; + } + return 0; +#elif defined(TIOCGWINSZ) + struct winsize w; + memset(&w, 0, sizeof(struct winsize)); + if( ioctl(0, TIOCGWINSZ, &w)!=-1 ){ + *pMaxChars = w.ws_col - indent; + return 1; + } + return 0; +#else + /* + ** Fallback to using more-or-less the "legacy semantics" of hard-coding + ** the maximum line length to a value reasonable for the vast majority + ** of supported systems. + */ + *pMaxChars = COMMENT_LEGACY_LINE_LENGTH - indent; + return -1; +#endif +} + +/* +** This function checks the current line being printed against the original +** comment text. Upon matching, it emits a new line and updates the provided +** character and line counts, if applicable. +*/ +static int comment_check_orig( + const char *zOrigText, /* [in] Original comment text ONLY, may be NULL. */ + const char *zLine, /* [in] The comment line to print. */ + int *pCharCnt, /* [in/out] Pointer to the line character count. */ + int *pLineCnt /* [in/out] Pointer to the total line count. */ +){ + if( zOrigText && fossil_strcmp(zLine, zOrigText)==0 ){ + fossil_print("\n"); + if( pCharCnt ) *pCharCnt = 0; + if( pLineCnt ) (*pLineCnt)++; + return 1; + } + return 0; +} + +/* +** This function scans the specified comment line starting just after the +** initial index and returns the index of the next spacing character -OR- +** zero if such a character cannot be found. For the purposes of this +** algorithm, the NUL character is treated the same as a spacing character. +*/ +static int comment_next_space( + const char *zLine, /* [in] The comment line being printed. */ + int index /* [in] The current character index being handled. */ +){ + int nextIndex = index + 1; + for(;;){ + char c = zLine[nextIndex]; + if( c==0 || fossil_isspace(c) ){ + return nextIndex; + } + nextIndex++; + } + return 0; /* NOT REACHED */ +} + +/* +** This function is called when printing a logical comment line to perform +** the necessary indenting. +*/ +static void comment_print_indent( + const char *zLine, /* [in] The comment line being printed. */ + int indent, /* [in] Number of spaces to indent, zero for none. */ + int trimCrLf, /* [in] Non-zero to trim leading/trailing CR/LF. */ + int trimSpace, /* [in] Non-zero to trim leading/trailing spaces. */ + int *piIndex /* [in/out] Pointer to first non-space character. */ +){ + if( indent>0 ){ + fossil_print("%*s", indent, ""); + } + if( zLine && piIndex ){ + int index = *piIndex; + if( trimCrLf ){ + while( zLine[index]=='\r' || zLine[index]=='\n' ){ index++; } + } + if( trimSpace ){ + while( fossil_isspace(zLine[index]) ){ index++; } + } + *piIndex = index; + } +} + +/* +** This function prints one logical line of a comment, stopping when it hits +** a new line -OR- runs out of space on the logical line. +*/ +static void comment_print_line( + const char *zOrigText, /* [in] Original comment text ONLY, may be NULL. */ + const char *zLine, /* [in] The comment line to print. */ + int origIndent, /* [in] Number of spaces to indent before the original + ** comment. */ + int indent, /* [in] Number of spaces to indent, before the line + ** to print. */ + int lineChars, /* [in] Maximum number of characters to print. */ + int trimCrLf, /* [in] Non-zero to trim leading/trailing CR/LF. */ + int trimSpace, /* [in] Non-zero to trim leading/trailing spaces. */ + int wordBreak, /* [in] Non-zero to try breaking on word boundaries. */ + int origBreak, /* [in] Non-zero to break before original comment. */ + int *pLineCnt, /* [in/out] Pointer to the total line count. */ + const char **pzLine /* [out] Pointer to the end of the logical line. */ +){ + int index = 0, charCnt = 0, lineCnt = 0, maxChars; + if( !zLine ) return; + if( lineChars<=0 ) return; + comment_print_indent(zLine, indent, trimCrLf, trimSpace, &index); + maxChars = lineChars; + for(;;){ + int useChars = 1; + char c = zLine[index]; + if( c==0 ){ + break; + }else{ + if( origBreak && index>0 ){ + const char *zCurrent = &zLine[index]; + if( comment_check_orig(zOrigText, zCurrent, &charCnt, &lineCnt) ){ + comment_print_indent(zCurrent, origIndent, trimCrLf, trimSpace, + &index); + maxChars = lineChars; + } + } + index++; + } + if( c=='\n' ){ + lineCnt++; + charCnt = 0; + useChars = 0; + }else if( c=='\t' ){ + int nextIndex = comment_next_space(zLine, index); + if( nextIndex<=0 || (nextIndex-index)>maxChars ){ + break; + } + charCnt++; + useChars = COMMENT_TAB_WIDTH; + if( maxChars<useChars ){ + fossil_print(" "); + break; + } + }else if( wordBreak && fossil_isspace(c) ){ + int nextIndex = comment_next_space(zLine, index); + if( nextIndex<=0 || (nextIndex-index)>maxChars ){ + break; + } + charCnt++; + }else{ + charCnt++; + } + assert( c!='\n' || charCnt==0 ); + fossil_print("%c", c); + maxChars -= useChars; + if( maxChars==0 ) break; + assert( maxChars>0 ); + if( c=='\n' ) break; + } + if( charCnt>0 ){ + fossil_print("\n"); + lineCnt++; + } + if( pLineCnt ){ + *pLineCnt += lineCnt; + } + if( pzLine ){ + *pzLine = zLine + index; + } +} /* -** Given a comment string zText, format that string for printing -** on a TTY. Assume that the output cursors is indent spaces from -** the left margin and that a single line can contain no more than -** lineLength characters. Indent all subsequent lines by indent. -** -** lineLength must be less than 400. -** -** Return the number of newlines that are output. +** This is the legacy comment printing algorithm. It is being retained +** for backward compatibility. +** +** Given a comment string, format that string for printing on a TTY. +** Assume that the output cursors is indent spaces from the left margin +** and that a single line can contain no more than 'width' characters. +** Indent all subsequent lines by 'indent'. +** +** Returns the number of new lines emitted. */ -int comment_print(const char *zText, int indent, int lineLength){ - int tlen = lineLength - indent; +static int comment_print_legacy( + const char *zText, /* The comment text to be printed. */ + int indent, /* Number of spaces to indent each non-initial line. */ + int width /* Maximum number of characters per line. */ +){ + int maxChars = width - indent; int si, sk, i, k; int doIndent = 0; - char zBuf[400]; - int lineCnt = 0; + char *zBuf; + char zBuffer[400]; + int lineCnt = 0; + if( width<0 ){ + comment_set_maxchars(indent, &maxChars); + } + if( zText==0 ) zText = "(NULL)"; + if( maxChars<=0 ){ + maxChars = strlen(zText); + } + if( maxChars >= (sizeof(zBuffer)) ){ + zBuf = fossil_malloc(maxChars+1); + }else{ + zBuf = zBuffer; + } for(;;){ while( fossil_isspace(zText[0]) ){ zText++; } if( zText[0]==0 ){ if( doIndent==0 ){ fossil_print("\n"); lineCnt = 1; } + if( zBuf!=zBuffer) fossil_free(zBuf); return lineCnt; } - for(sk=si=i=k=0; zText[i] && k<tlen; i++){ + for(sk=si=i=k=0; zText[i] && k<maxChars; i++){ char c = zText[i]; if( fossil_isspace(c) ){ si = i; sk = k; if( k==0 || zBuf[k-1]!=' ' ){ @@ -69,32 +313,207 @@ fossil_print("%*s", indent, ""); } doIndent = 1; if( sk>0 && zText[i] ){ zText += si; - zBuf[sk++] = '\n'; zBuf[sk] = 0; - fossil_print("%s", zBuf); }else{ zText += i; - zBuf[k++] = '\n'; zBuf[k] = 0; - fossil_print("%s", zBuf); } + fossil_print("%s\n", zBuf); + lineCnt++; + } +} + +/* +** This is the comment printing function. The comment printing algorithm +** contained within it attempts to preserve the formatting present within +** the comment string itself while honoring line width limitations. There +** are several flags that modify the default behavior of this function: +** +** COMMENT_PRINT_LEGACY: Forces use of the legacy comment printing +** algorithm. For backward compatibility, +** this is the default. +** +** COMMENT_PRINT_TRIM_CRLF: Trims leading and trailing carriage-returns +** and line-feeds where they do not materially +** impact pre-existing formatting (i.e. at the +** start of the comment string -AND- right +** before line indentation). This flag does +** not apply to the legacy comment printing +** algorithm. This flag may be combined with +** COMMENT_PRINT_TRIM_SPACE. +** +** COMMENT_PRINT_TRIM_SPACE: Trims leading and trailing spaces where they +** do not materially impact the pre-existing +** formatting (i.e. at the start of the comment +** string -AND- right before line indentation). +** This flag does not apply to the legacy +** comment printing algorithm. This flag may +** be combined with COMMENT_PRINT_TRIM_CRLF. +** +** COMMENT_PRINT_WORD_BREAK: Attempts to break lines on word boundaries +** while honoring the logical line length. +** If this flag is not specified, honoring the +** logical line length may result in breaking +** lines in the middle of words. This flag +** does not apply to the legacy comment +** printing algorithm. +** +** COMMENT_PRINT_ORIG_BREAK: Looks for the original comment text within +** the text being printed. Upon matching, a +** new line will be emitted, thus preserving +** more of the pre-existing formatting. +** +** Given a comment string, format that string for printing on a TTY. +** Assume that the output cursors is indent spaces from the left margin +** and that a single line can contain no more than 'width' characters. +** Indent all subsequent lines by 'indent'. +** +** Returns the number of new lines emitted. +*/ +int comment_print( + const char *zText, /* The comment text to be printed. */ + const char *zOrigText, /* Original comment text ONLY, may be NULL. */ + int indent, /* Spaces to indent each non-initial line. */ + int width, /* Maximum number of characters per line. */ + int flags /* Zero or more "COMMENT_PRINT_*" flags. */ +){ + int maxChars = width - indent; + int legacy = flags & COMMENT_PRINT_LEGACY; + int trimCrLf = flags & COMMENT_PRINT_TRIM_CRLF; + int trimSpace = flags & COMMENT_PRINT_TRIM_SPACE; + int wordBreak = flags & COMMENT_PRINT_WORD_BREAK; + int origBreak = flags & COMMENT_PRINT_ORIG_BREAK; + int lineCnt = 0; + const char *zLine; + + if( legacy ){ + return comment_print_legacy(zText, indent, width); + } + if( width<0 ){ + comment_set_maxchars(indent, &maxChars); + } + if( zText==0 ) zText = "(NULL)"; + if( maxChars<=0 ){ + maxChars = strlen(zText); + } + if( trimSpace ){ + while( fossil_isspace(zText[0]) ){ zText++; } + } + if( zText[0]==0 ){ + fossil_print("\n"); lineCnt++; + return lineCnt; + } + zLine = zText; + for(;;){ + comment_print_line(zOrigText, zLine, indent, zLine>zText ? indent : 0, + maxChars, trimCrLf, trimSpace, wordBreak, origBreak, + &lineCnt, &zLine); + if( !zLine || !zLine[0] ) break; } + return lineCnt; } /* -** Test the comment printing ** ** COMMAND: test-comment-format +** +** Usage: %fossil test-comment-format ?OPTIONS? PREFIX TEXT ?ORIGTEXT? +** +** Test comment formatting and printing. Use for testing only. +** +** Options: +** --file The comment text is really just a file name to +** read it from. +** --decode Decode the text using the same method used when +** handling the value of a C-card from a manifest. +** --legacy Use the legacy comment printing algorithm. +** --trimcrlf Enable trimming of leading/trailing CR/LF. +** --trimspace Enable trimming of leading/trailing spaces. +** --wordbreak Attempt to break lines on word boundaries. +** --origbreak Attempt to break when the original comment text +** is detected. +** --indent Number of spaces to indent (default (-1) is to +** auto-detect). Zero means no indent. +** -W|--width <num> Width of lines (default (-1) is to auto-detect). +** Zero means no limit. */ void test_comment_format(void){ - int indent; - if( g.argc!=4 ){ - usage("PREFIX TEXT"); + const char *zWidth; + const char *zIndent; + const char *zPrefix; + char *zText; + char *zOrigText; + int indent, width; + int fromFile = find_option("file", 0, 0)!=0; + int decode = find_option("decode", 0, 0)!=0; + int flags = COMMENT_PRINT_NONE; + if( find_option("legacy", 0, 0) ){ + flags |= COMMENT_PRINT_LEGACY; + } + if( find_option("trimcrlf", 0, 0) ){ + flags |= COMMENT_PRINT_TRIM_CRLF; + } + if( find_option("trimspace", 0, 0) ){ + flags |= COMMENT_PRINT_TRIM_SPACE; + } + if( find_option("wordbreak", 0, 0) ){ + flags |= COMMENT_PRINT_WORD_BREAK; + } + if( find_option("origbreak", 0, 0) ){ + flags |= COMMENT_PRINT_ORIG_BREAK; + } + zWidth = find_option("width","W",1); + if( zWidth ){ + width = atoi(zWidth); + }else{ + width = -1; /* automatic */ + } + zIndent = find_option("indent",0,1); + if( zIndent ){ + indent = atoi(zIndent); + }else{ + indent = -1; /* automatic */ + } + if( g.argc!=4 && g.argc!=5 ){ + usage("?OPTIONS? PREFIX TEXT ?ORIGTEXT?"); + } + zPrefix = g.argv[2]; + zText = g.argv[3]; + if( g.argc==5 ){ + zOrigText = g.argv[4]; + }else{ + zOrigText = 0; + } + if( fromFile ){ + Blob fileData; + blob_read_from_file(&fileData, zText); + zText = mprintf("%s", blob_str(&fileData)); + blob_reset(&fileData); + if( zOrigText ){ + blob_read_from_file(&fileData, zOrigText); + zOrigText = mprintf("%s", blob_str(&fileData)); + blob_reset(&fileData); + } + } + if( decode ){ + zText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zText); + defossilize(zText); + if( zOrigText ){ + zOrigText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zOrigText); + defossilize(zOrigText); + } + } + if( indent<0 ){ + indent = strlen(zPrefix); + } + if( zPrefix && *zPrefix ){ + fossil_print("%s", zPrefix); } - indent = strlen(g.argv[2]) + 1; - fossil_print("%s ", g.argv[2]); - fossil_print("(%d lines output)\n", comment_print(g.argv[3], indent, 79)); + fossil_print("(%d lines output)\n", + comment_print(zText, zOrigText, indent, width, flags)); + if( zOrigText && zOrigText!=g.argv[4] ) fossil_free(zOrigText); + if( zText && zText!=g.argv[3] ) fossil_free(zText); } Index: src/config.h ================================================================== --- src/config.h +++ src/config.h @@ -24,10 +24,20 @@ #define _LARGE_FILE 1 #ifndef _FILE_OFFSET_BITS # define _FILE_OFFSET_BITS 64 #endif #define _LARGEFILE_SOURCE 1 + +/* Needed for various definitions... */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +/* Make sure that in Win32 MinGW builds, _USE_32BIT_TIME_T is always defined. */ +#if defined(_WIN32) && !defined(_WIN64) && !defined(_MSC_VER) && !defined(_USE_32BIT_TIME_T) +# define _USE_32BIT_TIME_T +#endif #ifdef HAVE_AUTOCONFIG_H #include "autoconfig.h" #endif @@ -57,36 +67,99 @@ # include <sys/types.h> # include <signal.h> # include <pwd.h> #endif +/* +** Utility macro to wrap an argument with double quotes. +*/ +#if !defined(COMPILER_STRINGIFY) +# define COMPILER_STRINGIFY(x) COMPILER_STRINGIFY1(x) +# define COMPILER_STRINGIFY1(x) #x +#endif + /* ** Define the compiler variant, used to compile the project */ #if !defined(COMPILER_NAME) # if defined(__DMC__) -# define COMPILER_NAME "dmc" +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "dmc-" COMPILER_VERSION +# else +# define COMPILER_NAME "dmc" +# endif # elif defined(__POCC__) # if defined(_M_X64) -# define COMPILER_NAME "pellesc64" +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "pellesc64-" COMPILER_VERSION +# else +# define COMPILER_NAME "pellesc64" +# endif # else -# define COMPILER_NAME "pellesc32" +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "pellesc32-" COMPILER_VERSION +# else +# define COMPILER_NAME "pellesc32" +# endif # endif # elif defined(_MSC_VER) -# define COMPILER_NAME "msc" +# if !defined(COMPILER_VERSION) +# define COMPILER_VERSION COMPILER_STRINGIFY(_MSC_VER) +# endif +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "msc-" COMPILER_VERSION +# else +# define COMPILER_NAME "msc" +# endif # elif defined(__MINGW32__) -# define COMPILER_NAME "mingw32" +# if !defined(COMPILER_VERSION) +# if defined(__MINGW_VERSION) +# if defined(__GNUC__) +# if defined(__VERSION__) +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW_VERSION) "-gcc-" __VERSION__ +# else +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW_VERSION) "-gcc" +# endif +# else +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW_VERSION) +# endif +# elif defined(__MINGW32_VERSION) +# if defined(__GNUC__) +# if defined(__VERSION__) +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW32_VERSION) "-gcc-" __VERSION__ +# else +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW32_VERSION) "-gcc" +# endif +# else +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW32_VERSION) +# endif +# endif +# endif +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "mingw32-" COMPILER_VERSION +# else +# define COMPILER_NAME "mingw32" +# endif # elif defined(_WIN32) # define COMPILER_NAME "win32" # elif defined(__GNUC__) -# define COMPILER_NAME "gcc-" __VERSION__ +# if !defined(COMPILER_VERSION) +# if defined(__VERSION__) +# define COMPILER_VERSION __VERSION__ +# endif +# endif +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "gcc-" COMPILER_VERSION +# else +# define COMPILER_NAME "gcc" +# endif # else # define COMPILER_NAME "unknown" # endif #endif -#ifndef _RC_COMPILE_ +#if !defined(_RC_COMPILE_) && !defined(SQLITE_AMALGAMATION) #include "sqlite3.h" /* ** On Solaris, getpass() will only return up to 8 characters. getpassphrase() returns up to 257. @@ -118,11 +191,11 @@ ** The following macros are used to cast pointers to integers and ** integers to pointers. The way you do this varies from one compiler ** to the next, so we have developed the following set of #if statements ** to generate appropriate macros for a wide range of compilers. ** -** The correct "ANSI" way to do this is to use the intptr_t type. +** The correct "ANSI" way to do this is to use the intptr_t type. ** Unfortunately, that typedef is not available on all compilers, or ** if it is available, it requires an #include of specific headers ** that vary from one machine to the next. */ #if defined(__PTRDIFF_TYPE__) /* This case should work for GCC */ Index: src/configure.c ================================================================== --- src/configure.c +++ src/configure.c @@ -41,10 +41,17 @@ #define CONFIGSET_ALL 0x0000ff /* Everything */ #define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */ #define CONFIGSET_OLDFORMAT 0x200000 /* Use the legacy format */ +/* +** This mask is used for the common TH1 configuration settings (i.e. those +** that are not specific to one particular subsystem, such as the transfer +** subsystem). +*/ +#define CONFIGSET_TH1 (CONFIGSET_SKIN|CONFIGSET_TKT|CONFIGSET_XFER) + #endif /* INTERFACE */ /* ** Names of the configuration sets */ @@ -79,39 +86,50 @@ int groupMask; /* Which config groups is it part of */ } aConfig[] = { { "css", CONFIGSET_CSS }, { "header", CONFIGSET_SKIN }, { "footer", CONFIGSET_SKIN }, + { "details", CONFIGSET_SKIN }, { "logo-mimetype", CONFIGSET_SKIN }, { "logo-image", CONFIGSET_SKIN }, { "background-mimetype", CONFIGSET_SKIN }, { "background-image", CONFIGSET_SKIN }, - { "index-page", CONFIGSET_SKIN }, { "timeline-block-markup", CONFIGSET_SKIN }, { "timeline-max-comment", CONFIGSET_SKIN }, { "timeline-plaintext", CONFIGSET_SKIN }, { "adunit", CONFIGSET_SKIN }, { "adunit-omit-if-admin", CONFIGSET_SKIN }, { "adunit-omit-if-user", CONFIGSET_SKIN }, - { "th1-setup", CONFIGSET_ALL }, + +#ifdef FOSSIL_ENABLE_TH1_DOCS + { "th1-docs", CONFIGSET_TH1 }, +#endif +#ifdef FOSSIL_ENABLE_TH1_HOOKS + { "th1-hooks", CONFIGSET_TH1 }, +#endif + { "th1-setup", CONFIGSET_TH1 }, + { "th1-uri-regexp", CONFIGSET_TH1 }, #ifdef FOSSIL_ENABLE_TCL - { "tcl", CONFIGSET_SKIN|CONFIGSET_TKT|CONFIGSET_XFER }, - { "tcl-setup", CONFIGSET_SKIN|CONFIGSET_TKT|CONFIGSET_XFER }, + { "tcl", CONFIGSET_TH1 }, + { "tcl-setup", CONFIGSET_TH1 }, #endif { "project-name", CONFIGSET_PROJ }, + { "short-project-name", CONFIGSET_PROJ }, { "project-description", CONFIGSET_PROJ }, + { "index-page", CONFIGSET_PROJ }, { "manifest", CONFIGSET_PROJ }, { "binary-glob", CONFIGSET_PROJ }, { "clean-glob", CONFIGSET_PROJ }, { "ignore-glob", CONFIGSET_PROJ }, { "keep-glob", CONFIGSET_PROJ }, { "crnl-glob", CONFIGSET_PROJ }, { "encoding-glob", CONFIGSET_PROJ }, { "empty-dirs", CONFIGSET_PROJ }, { "allow-symlinks", CONFIGSET_PROJ }, + { "dotfiles", CONFIGSET_PROJ }, { "ticket-table", CONFIGSET_TKT }, { "ticket-common", CONFIGSET_TKT }, { "ticket-change", CONFIGSET_TKT }, { "ticket-newpage", CONFIGSET_TKT }, @@ -130,10 +148,12 @@ { "@shun", CONFIGSET_SHUN }, { "xfer-common-script", CONFIGSET_XFER }, { "xfer-push-script", CONFIGSET_XFER }, + { "xfer-commit-script", CONFIGSET_XFER }, + { "xfer-ticket-script", CONFIGSET_XFER }, }; static int iConfig = 0; /* @@ -177,19 +197,19 @@ Blob x; int i; const char *zSep = ""; blob_zero(&x); - blob_append(&x, "(", 1); + blob_append_sql(&x, "("); for(i=0; i<count(aConfig); i++){ if( (aConfig[i].groupMask & iMask)==0 ) continue; if( aConfig[i].zName[0]=='@' ) continue; - blob_appendf(&x, "%s'%s'", zSep, aConfig[i].zName); + blob_append_sql(&x, "%s'%q'", zSep/*safe-for-%s*/, aConfig[i].zName); zSep = ","; } - blob_append(&x, ")", 1); - return blob_str(&x); + blob_append_sql(&x, ")"); + return blob_sql_text(&x); } /* ** Return the mask for the named configuration parameter if it can be ** safely exported. Return 0 if the parameter is not safe to export. @@ -205,11 +225,11 @@ if( n>2 && zName[0]=='\'' && zName[n-1]=='\'' ){ zName++; n -= 2; } for(i=0; i<count(aConfig); i++){ - if( memcmp(zName, aConfig[i].zName, n)==0 && aConfig[i].zName[n]==0 ){ + if( strncmp(zName, aConfig[i].zName, n)==0 && aConfig[i].zName[n]==0 ){ int m = aConfig[i].groupMask; if( !g.perm.Admin ){ m &= ~CONFIGSET_USER; } if( !g.perm.RdAddr ){ @@ -344,11 +364,12 @@ @ INSERT INTO _xfer_reportfmt @ SELECT rn,owner,title,cols,sqlcode FROM reportfmt; @ INSERT INTO _xfer_user @ SELECT uid,login,pw,cap,cookie,ipaddr,cexpire,info,photo FROM user; ; - db_multi_exec(zSQL1); + assert( strchr(zSQL1,'%')==0 ); + db_multi_exec(zSQL1 /*works-like:""*/); /* When the replace flag is set, add triggers that run the first time ** that new data is seen. The triggers run only once and delete all the ** existing data. */ @@ -373,11 +394,12 @@ sqlite3_create_function(g.db, "config_is_reset", 1, SQLITE_UTF8, 0, config_is_reset_function, 0, 0); sqlite3_create_function(g.db, "config_reset", 1, SQLITE_UTF8, 0, config_reset_function, 0, 0); configHasBeenReset = 0; - db_multi_exec(zSQL2); + assert( strchr(zSQL2,'%')==0 ); + db_multi_exec(zSQL2 /*works-like:""*/); } } /* ** After receiving configuration data, call this routine to transfer @@ -390,11 +412,27 @@ @ DELETE FROM reportfmt; @ INSERT INTO reportfmt SELECT * FROM _xfer_reportfmt; @ DROP TABLE _xfer_user; @ DROP TABLE _xfer_reportfmt; ; - db_multi_exec(zSQL); + assert( strchr(zSQL,'%')==0 ); + db_multi_exec(zSQL /*works-like:""*/); +} + +/* +** Mask of modified configuration sets +*/ +static int rebuildMask = 0; + +/* +** Rebuild auxiliary tables as required by configuration changes. +*/ +void configure_rebuild(void){ + if( rebuildMask & CONFIGSET_TKT ){ + ticket_rebuild(); + } + rebuildMask = 0; } /* ** Return true if z[] is not a "safe" SQL token. A safe token is one of: ** @@ -531,37 +569,42 @@ if( (thisMask & groupMask)==0 ) return; blob_zero(&sql); if( groupMask & CONFIGSET_OVERWRITE ){ if( (thisMask & configHasBeenReset)==0 && aType[ii].zName[0]!='/' ){ - db_multi_exec("DELETE FROM %s", &aType[ii].zName[1]); + db_multi_exec("DELETE FROM \"%w\"", &aType[ii].zName[1]); configHasBeenReset |= thisMask; } - blob_append(&sql, "REPLACE INTO ", -1); - }else{ - blob_append(&sql, "INSERT OR IGNORE INTO ", -1); - } - blob_appendf(&sql, "%s(%s, mtime", &zName[1], aType[ii].zPrimKey); - for(jj=2; jj<nToken; jj+=2){ - blob_appendf(&sql, ",%s", azToken[jj]); - } - blob_appendf(&sql,") VALUES(%s,%s", azToken[1], azToken[0]); - for(jj=2; jj<nToken; jj+=2){ - blob_appendf(&sql, ",%s", azToken[jj+1]); - } - db_multi_exec("%s)", blob_str(&sql)); + blob_append_sql(&sql, "REPLACE INTO "); + }else{ + blob_append_sql(&sql, "INSERT OR IGNORE INTO "); + } + blob_append_sql(&sql, "\"%w\"(\"%w\", mtime", &zName[1], aType[ii].zPrimKey); + for(jj=2; jj<nToken; jj+=2){ + blob_append_sql(&sql, ",\"%w\"", azToken[jj]); + } + blob_append_sql(&sql,") VALUES(%s,%s", + azToken[1] /*safe-for-%s*/, azToken[0] /*safe-for-%s*/); + for(jj=2; jj<nToken; jj+=2){ + blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/); + } + db_multi_exec("%s)", blob_sql_text(&sql)); if( db_changes()==0 ){ blob_reset(&sql); - blob_appendf(&sql, "UPDATE %s SET mtime=%s", &zName[1], azToken[0]); + blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s", + &zName[1], azToken[0]/*safe-for-%s*/); for(jj=2; jj<nToken; jj+=2){ - blob_appendf(&sql, ", %s=%s", azToken[jj], azToken[jj+1]); + blob_append_sql(&sql, ", \"%w\"=%s", + azToken[jj], azToken[jj+1]/*safe-for-%s*/); } - blob_appendf(&sql, " WHERE %s=%s AND mtime<%s", - aType[ii].zPrimKey, azToken[1], azToken[0]); - db_multi_exec("%s", blob_str(&sql)); + blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s", + aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/, + azToken[0]/*safe-for-%s*/); + db_multi_exec("%s", blob_sql_text(&sql)); } blob_reset(&sql); + rebuildMask |= thisMask; }else{ /* Otherwise, the old format */ if( (configure_is_exportable(zName) & groupMask)==0 ) return; if( fossil_strcmp(zName, "logo-image")==0 ){ Stmt ins; @@ -576,11 +619,11 @@ /* Notice that we are evaluating arbitrary SQL received from the ** client. But this can only happen if the client has authenticated ** as an administrator, so presumably we trust the client at this ** point. */ - db_multi_exec("%s", blob_str(pContent)); + db_multi_exec("%s", blob_str(pContent) /*safe-for-%s*/); }else{ db_multi_exec( "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now())", zName, blob_str(pContent) ); @@ -816,11 +859,11 @@ ** ** Push the local configuration into the remote server identified ** by URL. Admin privilege is required on the remote server for ** this to work. When the same record exists both locally and on ** the remote end, the one that was most recently changed wins. -** Use the --legacy flag when talking to holder servers. +** Use the --legacy flag when talking to older servers. ** ** %fossil configuration reset AREA ** ** Restore the configuration to the default. AREA as above. ** @@ -897,11 +940,11 @@ mask = configure_name_to_mask(g.argv[3], 1); if( g.argc==5 ){ zServer = g.argv[4]; } url_parse(zServer, URL_PROMPT_PW); - if( g.urlProtocol==0 ) fossil_fatal("no server URL specified"); + if( g.url.protocol==0 ) fossil_fatal("no server URL specified"); user_select(); url_enable_proxy("via proxy: "); if( legacyFlag ) mask |= CONFIGSET_OLDFORMAT; if( overwriteFlag ) mask |= CONFIGSET_OVERWRITE; if( strncmp(zMethod, "push", n)==0 ){ @@ -933,18 +976,212 @@ db_multi_exec("DELETE FROM concealed"); }else if( fossil_strcmp(zName,"@shun")==0 ){ db_multi_exec("DELETE FROM shun"); }else if( fossil_strcmp(zName,"@reportfmt")==0 ){ db_multi_exec("DELETE FROM reportfmt"); - db_multi_exec(zRepositorySchemaDefaultReports); + assert( strchr(zRepositorySchemaDefaultReports,'%')==0 ); + db_multi_exec(zRepositorySchemaDefaultReports /*works-like:""*/); } } db_end_transaction(0); fossil_print("Configuration reset to factory defaults.\n"); fossil_print("To recover, use: %s %s import %s\n", g.argv[0], g.argv[1], zBackup); + rebuildMask |= mask; }else { fossil_fatal("METHOD should be one of:" " export import merge pull push reset"); } + configure_rebuild(); +} + + +/* +** COMMAND: test-var-list +** +** Usage: %fossil test-var-list ?PATTERN? ?--unset? ?--mtime? +** +** Show the content of the CONFIG table in a repository. If PATTERN is +** specified, then only show the entries that match that glob pattern. +** Last modification time is shown if the --mtime option is present. +** +** If the --unset option is included, then entries are deleted rather than +** being displayed. WARNING! This cannot be undone. Be sure you know what +** you are doing! The --unset option only works if there is a PATTERN. +** Probably you should run the command once without --unset to make sure +** you know exactly what is being deleted. +** +** If not in an open check-out, use the -R REPO option to specify a +** a repository. +*/ +void test_var_list_cmd(void){ + Stmt q; + int i, j; + const char *zPattern = 0; + int doUnset; + int showMtime; + Blob sql; + Blob ans; + unsigned char zTrans[1000]; + + doUnset = find_option("unset",0,0)!=0; + showMtime = find_option("mtime",0,0)!=0; + db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); + verify_all_options(); + if( g.argc>=3 ){ + zPattern = g.argv[2]; + } + blob_init(&sql,0,0); + blob_appendf(&sql, "SELECT name, value, datetime(mtime,'unixepoch')" + " FROM config"); + if( zPattern ){ + blob_appendf(&sql, " WHERE name GLOB %Q", zPattern); + } + if( showMtime ){ + blob_appendf(&sql, " ORDER BY mtime, name"); + }else{ + blob_appendf(&sql, " ORDER BY name"); + } + db_prepare(&q, "%s", blob_str(&sql)/*safe-for-%s*/); + blob_reset(&sql); +#define MX_VAL 40 +#define MX_NM 28 +#define MX_LONGNM 60 + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q,0); + int nName = db_column_bytes(&q,0); + const char *zValue = db_column_text(&q,1); + int szValue = db_column_bytes(&q,1); + const char *zMTime = db_column_text(&q,2); + for(i=j=0; j<MX_VAL && zValue[i]; i++){ + unsigned char c = (unsigned char)zValue[i]; + if( c>=' ' && c<='~' ){ + zTrans[j++] = c; + }else{ + zTrans[j++] = '\\'; + if( c=='\n' ){ + zTrans[j++] = 'n'; + }else if( c=='\r' ){ + zTrans[j++] = 'r'; + }else if( c=='\t' ){ + zTrans[j++] = 't'; + }else{ + zTrans[j++] = '0' + ((c>>6)&7); + zTrans[j++] = '0' + ((c>>3)&7); + zTrans[j++] = '0' + (c&7); + } + } + } + zTrans[j] = 0; + if( i<szValue ){ + sqlite3_snprintf(sizeof(zTrans)-j, (char*)zTrans+j, "...+%d", szValue-i); + j += (int)strlen((char*)zTrans+j); + } + if( showMtime ){ + fossil_print("%s:%*s%s\n", zName, 58-nName, "", zMTime); + }else if( nName<MX_NM-2 ){ + fossil_print("%s:%*s%s\n", zName, MX_NM-1-nName, "", zTrans); + }else if( nName<MX_LONGNM-2 && j<10 ){ + fossil_print("%s:%*s%s\n", zName, MX_LONGNM-1-nName, "", zTrans); + }else{ + fossil_print("%s:\n%*s%s\n", zName, MX_NM, "", zTrans); + } + } + db_finalize(&q); + if( zPattern && doUnset ){ + prompt_user("Delete all of the above? (y/N)? ", &ans); + if( blob_str(&ans)[0]=='y' || blob_str(&ans)[0]=='Y' ){ + db_multi_exec("DELETE FROM config WHERE name GLOB %Q", zPattern); + } + blob_reset(&ans); + } +} + +/* +** COMMAND: test-var-get +** +** Usage: %fossil test-var-get VAR ?FILE? +** +** Write the text of the VAR variable into FILE. If FILE is "-" +** or is omitted then output goes to standard output. VAR can be a +** GLOB pattern. +** +** If not in an open check-out, use the -R REPO option to specify a +** a repository. +*/ +void test_var_get_cmd(void){ + const char *zVar; + const char *zFile; + int n; + Blob x; + db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); + verify_all_options(); + if( g.argc<3 ){ + usage("VAR ?FILE?"); + } + zVar = g.argv[2]; + zFile = g.argc>=4 ? g.argv[3] : "-"; + n = db_int(0, "SELECT count(*) FROM config WHERE name GLOB %Q", zVar); + if( n==0 ){ + fossil_fatal("no match for %Q", zVar); + } + if( n>1 ){ + fossil_fatal("multiple matches: %s", + db_text(0, "SELECT group_concat(quote(name),', ') FROM (" + " SELECT name FROM config WHERE name GLOB %Q ORDER BY 1)", + zVar)); + } + blob_init(&x,0,0); + db_blob(&x, "SELECT value FROM config WHERE name GLOB %Q", zVar); + blob_write_to_file(&x, zFile); +} + +/* +** COMMAND: test-var-set +** +** Usage: %fossil test-var-set VAR ?VALUE? ?--file FILE? +** +** Store VALUE or the content of FILE (exactly one of which must be +** supplied) into variable VAR. Use a FILE of "-" to read from +** standard input. +** +** WARNING: changing the value of a variable can interfere with the +** operation of Fossil. Be sure you know what you are doing. +** +** Use "--blob FILE" instead of "--file FILE" to load a binary blob +** such as a GIF. +*/ +void test_var_set_cmd(void){ + const char *zVar; + const char *zFile; + const char *zBlob; + Blob x; + Stmt ins; + zFile = find_option("file",0,1); + zBlob = find_option("blob",0,1); + db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); + verify_all_options(); + if( g.argc<3 || (zFile==0 && zBlob==0 && g.argc<4) ){ + usage("VAR ?VALUE? ?--file FILE?"); + } + zVar = g.argv[2]; + if( zFile ){ + if( zBlob ) fossil_fatal("cannot do both --file or --blob"); + blob_read_from_file(&x, zFile); + }else if( zBlob ){ + blob_read_from_file(&x, zBlob); + }else{ + blob_init(&x,g.argv[3],-1); + } + db_prepare(&ins, + "REPLACE INTO config(name,value,mtime)" + "VALUES(%Q,:val,now())", zVar); + if( zBlob ){ + db_bind_blob(&ins, ":val", &x); + }else{ + db_bind_text(&ins, ":val", blob_str(&x)); + } + db_step(&ins); + db_finalize(&ins); + blob_reset(&x); } Index: src/content.c ================================================================== --- src/content.c +++ src/content.c @@ -114,11 +114,11 @@ contentCache.n = 0; contentCache.szTotal = 0; } /* -** Return the srcid associated with rid. Or return 0 if rid is +** Return the srcid associated with rid. Or return 0 if rid is ** original content and not a delta. */ static int findSrcid(int rid){ static Stmt q; int srcid; @@ -154,11 +154,11 @@ ** a phantom. */ int content_is_available(int rid){ int srcid; int depth = 0; /* Limit to recursion depth */ - while( depth++ < 10000000 ){ + while( depth++ < 10000000 ){ if( bag_find(&contentCache.missing, rid) ){ return 0; } if( bag_find(&contentCache.available, rid) ){ return 1; @@ -388,11 +388,11 @@ int i; /* Parse the object rid itself */ if( linkFlag ){ content_get(rid, &content); - manifest_crosslink(rid, &content); + manifest_crosslink(rid, &content, MC_NONE); assert( blob_is_reset(&content) ); } /* Parse all delta-manifests that depend on baseline-manifest rid */ db_prepare(&q, "SELECT rid FROM orphan WHERE baseline=%d", rid); @@ -405,11 +405,11 @@ aChild[nChildUsed++] = child; } db_finalize(&q); for(i=0; i<nChildUsed; i++){ content_get(aChild[i], &content); - manifest_crosslink(aChild[i], &content); + manifest_crosslink(aChild[i], &content, MC_NONE); assert( blob_is_reset(&content) ); } if( nChildUsed ){ db_multi_exec("DELETE FROM orphan WHERE baseline=%d", rid); } @@ -416,11 +416,11 @@ /* Recursively dephantomize all artifacts that are derived by ** delta from artifact rid and which have not already been ** cross-linked. */ nChildUsed = 0; - db_prepare(&q, + db_prepare(&q, "SELECT rid FROM delta" " WHERE srcid=%d" " AND NOT EXISTS(SELECT 1 FROM mlink WHERE mid=delta.rid)", rid ); @@ -455,11 +455,11 @@ /* ** Write content into the database. Return the record ID. If the ** content is already in the database, just return the record ID. ** ** If srcId is specified, then pBlob is delta content from -** the srcId record. srcId might be a phantom. +** the srcId record. srcId might be a phantom. ** ** pBlob is normally uncompressed text. But if nBlob>0 then the ** pBlob value has already been compressed and nBlob is its uncompressed ** size. If nBlob>0 then zUuid must be valid. ** @@ -486,11 +486,11 @@ Stmt s1; Blob cmpr; Blob hash; int markAsUnclustered = 0; int isDephantomize = 0; - + assert( g.repositoryOpen ); assert( pBlob!=0 ); assert( srcId==0 || zUuid!=0 ); if( zUuid==0 ){ assert( nBlob==0 ); @@ -558,40 +558,40 @@ } }else{ /* We are creating a new entry */ db_prepare(&s1, "INSERT INTO blob(rcvid,size,uuid,content)" - "VALUES(%d,%d,'%b',:data)", - g.rcvid, size, &hash + "VALUES(%d,%d,'%q',:data)", + g.rcvid, size, blob_str(&hash) ); db_bind_blob(&s1, ":data", &cmpr); db_exec(&s1); rid = db_last_insert_rowid(); if( !pBlob ){ db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid); } - if( g.markPrivate || isPrivate ){ - db_multi_exec("INSERT INTO private VALUES(%d)", rid); - markAsUnclustered = 0; - } + } + if( g.markPrivate || isPrivate ){ + db_multi_exec("INSERT INTO private VALUES(%d)", rid); + markAsUnclustered = 0; } if( nBlob==0 ) blob_reset(&cmpr); /* If the srcId is specified, then the data we just added is ** really a delta. Record this fact in the delta table. */ if( srcId ){ db_multi_exec("REPLACE INTO delta(rid,srcid) VALUES(%d,%d)", rid, srcId); } - if( !isDephantomize && bag_find(&contentCache.missing, rid) && + if( !isDephantomize && bag_find(&contentCache.missing, rid) && (srcId==0 || content_is_available(srcId)) ){ content_mark_available(rid); } if( isDephantomize ){ after_dephantomize(rid, 0); } - + /* Add the element to the unclustered table if has never been ** previously seen. */ if( markAsUnclustered ){ db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d)", rid); @@ -628,11 +628,11 @@ ** Create a new phantom with the given UUID and return its artifact ID. */ int content_new(const char *zUuid, int isPrivate){ int rid; static Stmt s1, s2, s3; - + assert( g.repositoryOpen ); db_begin_transaction(); if( uuid_is_shunned(zUuid) ){ db_end_transaction(0); return 0; @@ -727,15 +727,15 @@ "SELECT 1 FROM private WHERE rid=:rid" ); db_bind_int(&s1, ":rid", rid); rc = db_step(&s1); db_reset(&s1); - return rc==SQLITE_ROW; + return rc==SQLITE_ROW; } /* -** Make sure an artifact is public. +** Make sure an artifact is public. */ void content_make_public(int rid){ static Stmt s1; db_static_prepare(&s1, "DELETE FROM private WHERE rid=:rid" @@ -758,11 +758,11 @@ ** ** If srcid is a delta that depends on rid, then srcid is ** converted to undeltaed text. ** ** If either rid or srcid contain less than 50 bytes, or if the -** resulting delta does not achieve a compression of at least 25% +** resulting delta does not achieve a compression of at least 25% ** the rid is left untouched. ** ** Return 1 if a delta is made and 0 if no delta occurs. */ int content_deltify(int rid, int srcid, int force){ @@ -883,11 +883,11 @@ zId, rid, zSrc, srcid ); nErr++; } db_finalize(&q); - + db_prepare(&q, "SELECT rid, uuid, size FROM blob ORDER BY rid"); total = db_int(0, "SELECT max(rid) FROM blob"); while( db_step(&q)==SQLITE_ROW ){ int rid = db_column_int(&q, 0); const char *zUuid = db_column_text(&q, 1); @@ -945,12 +945,12 @@ } db_finalize(&q); fossil_print("%d non-phantom blobs (out of %d total) checked: %d errors\n", n2, n1, nErr); if( bParse ){ - const char *azType[] = { 0, "manifest", "cluster", "control", "wiki", - "ticket", "attachment", "event" }; + static const char *const azType[] = { 0, "manifest", "cluster", + "control", "wiki", "ticket", "attachment", "event" }; int i; fossil_print("%d total control artifacts\n", nCA); for(i=1; i<count(azType); i++){ if( anCA[i] ) fossil_print(" %d %ss\n", anCA[i], azType[i]); } @@ -1019,11 +1019,11 @@ db_reset(&q); if( rc ){ const char *zCFType = "control artifact"; char *zSrc; char *zDate; - char *zErrType = "MISSING"; + const char *zErrType = "MISSING"; if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ if( flags & MISSING_SHUNNED ) return 0; zErrType = "SHUNNED"; } switch( p->type ){ @@ -1050,11 +1050,11 @@ if( zDetail && zDetail[0] ){ fossil_print(" %s\n", zDetail); } fossil_free(zSrc); fossil_free(zDate); - rc = 1; + rc = 1; } return rc; } /* @@ -1097,11 +1097,11 @@ if( p ){ nArtifact++; nErr += check_exists(p->zBaseline, flags, p, "baseline of", 0); nErr += check_exists(p->zAttachSrc, flags, p, "file of", 0); for(i=0; i<p->nFile; i++){ - nErr += check_exists(p->aFile[i].zUuid, flags, p, "file of", + nErr += check_exists(p->aFile[i].zUuid, flags, p, "file of", p->aFile[i].zName); } for(i=0; i<p->nParent; i++){ nErr += check_exists(p->azParent[i], flags, p, "parent of", 0); } @@ -1115,14 +1115,14 @@ nErr += check_exists(p->azCChild[i], flags, p, "in", 0); } for(i=0; i<p->nTag; i++){ nErr += check_exists(p->aTag[i].zUuid, flags, p, "target of", 0); } - manifest_destroy(p); + manifest_destroy(p); } } db_finalize(&q); if( nErr>0 || quietFlag==0 ){ fossil_print("%d missing or shunned references in %d control artifacts\n", nErr, nArtifact); } } Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -44,11 +44,11 @@ ** An single SQL statement is represented as an instance of the following ** structure. */ struct Stmt { Blob sql; /* The SQL for this statement */ - sqlite3_stmt *pStmt; /* The results of sqlite3_prepare() */ + sqlite3_stmt *pStmt; /* The results of sqlite3_prepare_v2() */ Stmt *pNext, *pPrev; /* List of all unfinalized statements */ int nStep; /* Number of sqlite3_step() calls */ }; /* @@ -65,14 +65,10 @@ */ static void db_err(const char *zFormat, ...){ va_list ap; char *z; int rc = 1; - static const char zRebuildMsg[] = - "If you have recently updated your fossil executable, you might\n" - "need to run \"fossil all rebuild\" to bring the repository\n" - "schemas up to date.\n"; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); #ifdef FOSSIL_ENABLE_JSON if( g.json.isJsonMode ){ @@ -88,15 +84,14 @@ @ error Database\serror:\s%F(z) cgi_reply(); } else if( g.cgiOutput ){ g.cgiOutput = 0; - cgi_printf("<h1>Database Error</h1>\n" - "<pre>%h</pre>\n<p>%s</p>\n", z, zRebuildMsg); + cgi_printf("<h1>Database Error</h1>\n<p>%h</p>\n", z); cgi_reply(); }else{ - fprintf(stderr, "%s: %s\n\n%s", g.argv[0], z, zRebuildMsg); + fprintf(stderr, "%s: %s\n", g.argv[0], z); } free(z); db_force_rollback(); fossil_exit(rc); } @@ -108,11 +103,11 @@ static struct DbLocalData { int nBegin; /* Nesting depth of BEGIN */ int doRollback; /* True to force a rollback */ int nCommitHook; /* Number of commit hooks */ Stmt *pAllStmt; /* List of all unfinalized statements */ - int nPrepare; /* Number of calls to sqlite3_prepare() */ + int nPrepare; /* Number of calls to sqlite3_prepare_v2() */ int nDeleteOnFail; /* Number of entries in azDeleteOnFail[] */ struct sCommitHook { int (*xHook)(void); /* Functions to call at db_end_transaction() */ int sequence; /* Call functions in sequence order */ } aHook[5]; @@ -164,24 +159,26 @@ if( rollbackFlag ) db.doRollback = 1; db.nBegin--; if( db.nBegin==0 ){ int i; if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){ + i = 0; while( db.nBeforeCommit ){ db.nBeforeCommit--; - sqlite3_exec(g.db, db.azBeforeCommit[db.nBeforeCommit], 0, 0, 0); - sqlite3_free(db.azBeforeCommit[db.nBeforeCommit]); + sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0); + sqlite3_free(db.azBeforeCommit[i]); + i++; } leaf_do_pending_checks(); } for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){ db.doRollback |= db.aHook[i].xHook(); } while( db.pAllStmt ){ db_finalize(db.pAllStmt); } - db_multi_exec(db.doRollback ? "ROLLBACK" : "COMMIT"); + db_multi_exec("%s", db.doRollback ? "ROLLBACK" : "COMMIT"); db.doRollback = 0; } } /* @@ -315,10 +312,14 @@ return sqlite3_bind_double(pStmt->pStmt, paramIdx(pStmt, zParamName), rValue); } int db_bind_text(Stmt *pStmt, const char *zParamName, const char *zValue){ return sqlite3_bind_text(pStmt->pStmt, paramIdx(pStmt, zParamName), zValue, -1, SQLITE_STATIC); +} +int db_bind_text16(Stmt *pStmt, const char *zParamName, const char *zValue){ + return sqlite3_bind_text16(pStmt->pStmt, paramIdx(pStmt, zParamName), zValue, + -1, SQLITE_STATIC); } int db_bind_null(Stmt *pStmt, const char *zParamName){ return sqlite3_bind_null(pStmt->pStmt, paramIdx(pStmt, zParamName)); } int db_bind_blob(Stmt *pStmt, const char *zParamName, Blob *pBlob){ @@ -343,10 +344,72 @@ int rc; rc = sqlite3_step(pStmt->pStmt); pStmt->nStep++; return rc; } + +/* +** Steps the SQL statement until there are no more rows. Returns the +** total number of rows processed by this function. If the pazValue1 +** parameter is non-zero, captures the iCol1'th column value from each +** row (as text) and stores the resulting final array pointer into it. +** If the pazValue2 parameter is non-zero, captures the iCol2'th column +** value from each row (as text) and stores the resulting final array +** pointer into it. The caller of this function is responsible for +** calling the db_all_column_free() function later, passing it the +** result of this function along with the values for both the pazValue1 +** and pazValue2 paramters. +*/ +int db_all_column_text( + Stmt *pStmt, /* The statement handle. */ + int iCol1, /* The first column number to fetch from the results. */ + char ***pazValue1, /* Array of iCol1'th column values from query. */ + int iCol2, /* The second column number to fetch from the results. */ + char ***pazValue2 /* Array of iCol2'th column values from query. */ +){ + int count = 0; + char **azValue1 = 0; + char **azValue2 = 0; + while( db_step(pStmt)==SQLITE_ROW ){ + count++; + if( pazValue1 ){ + azValue1 = fossil_realloc(azValue1, count * sizeof(char*)); + azValue1[count - 1] = fossil_strdup(db_column_text(pStmt, iCol1)); + } + if( pazValue2 ){ + azValue2 = fossil_realloc(azValue2, count * sizeof(char*)); + azValue2[count - 1] = fossil_strdup(db_column_text(pStmt, iCol2)); + } + } + if( pazValue1 ){ + *pazValue1 = azValue1; + } + if( pazValue2 ){ + *pazValue2 = azValue2; + } + return count; +} + +/* +** This function frees all the storage that was allocated by the +** db_all_column_text() function for a particular column. +*/ +void db_all_column_free( + int count, /* Number of string elements in the arrays. */ + char ***pazValue /* Array of column values from query. */ +){ + if( pazValue ){ + char **azValue = *pazValue; + int i; + for(i=0; i<count; i++){ + fossil_free(azValue[i]); + azValue[i] = 0; + } + fossil_free(azValue); + *pazValue = 0; + } +} /* ** Print warnings if a query is inefficient. */ static void db_stats(Stmt *pStmt){ @@ -420,10 +483,13 @@ /* ** Extract text, integer, or blob values from the N-th column of the ** current row. */ +int db_column_type(Stmt *pStmt, int N){ + return sqlite3_column_type(pStmt->pStmt, N); +} int db_column_bytes(Stmt *pStmt, int N){ return sqlite3_column_bytes(pStmt->pStmt, N); } int db_column_int(Stmt *pStmt, int N){ return sqlite3_column_int(pStmt->pStmt, N); @@ -482,10 +548,50 @@ while( (rc = db_step(pStmt))==SQLITE_ROW ){} rc = db_reset(pStmt); db_check_result(rc); return rc; } + +/* +** Print the output of one or more SQL queries on standard output. +** This routine is used for debugging purposes only. +*/ +int db_debug(const char *zSql, ...){ + Blob sql; + int rc = SQLITE_OK; + va_list ap; + const char *z, *zEnd; + sqlite3_stmt *pStmt; + blob_init(&sql, 0, 0); + va_start(ap, zSql); + blob_vappendf(&sql, zSql, ap); + va_end(ap); + z = blob_str(&sql); + while( rc==SQLITE_OK && z[0] ){ + pStmt = 0; + rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd); + if( rc!=SQLITE_OK ) break; + if( pStmt ){ + int nRow = 0; + db.nPrepare++; + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + int i, n; + if( nRow++ > 0 ) fossil_print("\n"); + n = sqlite3_column_count(pStmt); + for(i=0; i<n; i++){ + fossil_print("%s = %s\n", sqlite3_column_name(pStmt, i), + sqlite3_column_text(pStmt,i)); + } + } + rc = sqlite3_finalize(pStmt); + if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z); + } + z = zEnd; + } + blob_reset(&sql); + return rc; +} /* ** Execute multiple SQL statements. */ int db_multi_exec(const char *zSql, ...){ @@ -500,12 +606,13 @@ va_end(ap); z = blob_str(&sql); while( rc==SQLITE_OK && z[0] ){ pStmt = 0; rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd); - if( rc!=SQLITE_OK ) break; - if( pStmt ){ + if( rc ){ + db_err("%s: {%s}", sqlite3_errmsg(g.db), z); + }else if( pStmt ){ db.nPrepare++; while( sqlite3_step(pStmt)==SQLITE_ROW ){} rc = sqlite3_finalize(pStmt); if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z); } @@ -624,11 +731,11 @@ ** Execute a query. Return the first column of the first row ** of the result set as a string. Space to hold the string is ** obtained from malloc(). If the result set is empty, return ** zDefault instead. */ -char *db_text(char const *zDefault, const char *zSql, ...){ +char *db_text(const char *zDefault, const char *zSql, ...){ va_list ap; Stmt s; char *z; va_start(ap, zSql); db_vprepare(&s, 0, zSql, ap); @@ -660,17 +767,17 @@ db = db_open(zFileName); sqlite3_exec(db, "BEGIN EXCLUSIVE", 0, 0, 0); rc = sqlite3_exec(db, zSchema, 0, 0, 0); if( rc!=SQLITE_OK ){ - db_err(sqlite3_errmsg(db)); + db_err("%s", sqlite3_errmsg(db)); } va_start(ap, zSchema); while( (zSql = va_arg(ap, const char*))!=0 ){ rc = sqlite3_exec(db, zSql, 0, 0, 0); if( rc!=SQLITE_OK ){ - db_err(sqlite3_errmsg(db)); + db_err("%s", sqlite3_errmsg(db)); } } va_end(ap); sqlite3_exec(db, "COMMIT", 0, 0, 0); sqlite3_close(db); @@ -688,10 +795,18 @@ sqlite3_result_int64(context, time(0)); } /* ** Function to return the check-in time for a file. +** +** checkin_mtime(CKINID,RID) +** +** CKINID: The RID for the manifest for a check-in. +** RID: The RID of a file in CKINID for which the check-in time +** is desired. +** +** Returns: The check-in time in seconds since 1970. */ void db_checkin_mtime_function( sqlite3_context *context, int argc, sqlite3_value **argv @@ -702,68 +817,114 @@ if( rc==0 ){ sqlite3_result_int64(context, mtime); } } +/* +** SQL wrapper around the symbolic_name_to_rid() C-language API. +** Examples: +** +** symbolic_name_to_rid('trunk'); +** symbolic_name_to_rid('trunk','w'); +** +*/ +void db_sym2rid_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *arg; + const char *type; + if(1 != argc && 2 != argc){ + sqlite3_result_error(context, "Expecting one or two arguments", -1); + return; + } + arg = (const char*)sqlite3_value_text(argv[0]); + if(!arg){ + sqlite3_result_error(context, "Expecting a STRING argument", -1); + }else{ + int rid; + type = (2==argc) ? (const char*)sqlite3_value_text(argv[1]) : 0; + if(!type) type = "ci"; + rid = symbolic_name_to_rid( arg, type ); + if(rid<0){ + sqlite3_result_error(context, "Symbolic name is ambiguous.", -1); + }else if(0==rid){ + sqlite3_result_null(context); + }else{ + sqlite3_result_int64(context, rid); + } + } +} + +/* +** Register the SQL functions that are useful both to the internal +** representation and to the "fossil sql" command. +*/ +void db_add_aux_functions(sqlite3 *db){ + sqlite3_create_function(db, "checkin_mtime", 2, SQLITE_UTF8, 0, + db_checkin_mtime_function, 0, 0); + sqlite3_create_function(db, "symbolic_name_to_rid", 1, SQLITE_UTF8, 0, + db_sym2rid_function, 0, 0); + sqlite3_create_function(db, "symbolic_name_to_rid", 2, SQLITE_UTF8, 0, + db_sym2rid_function, 0, 0); + sqlite3_create_function(db, "now", 0, SQLITE_UTF8, 0, + db_now_function, 0, 0); +} + /* ** Open a database file. Return a pointer to the new database ** connection. An error results in process abort. */ LOCAL sqlite3 *db_open(const char *zDbName){ int rc; - const char *zVfs; sqlite3 *db; -#if defined(__CYGWIN__) - zDbName = fossil_utf8_to_filename(zDbName); -#endif if( g.fSqlTrace ) fossil_trace("-- sqlite3_open: [%s]\n", zDbName); - zVfs = fossil_getenv("FOSSIL_VFS"); rc = sqlite3_open_v2( zDbName, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, - zVfs + g.zVfsName ); if( rc!=SQLITE_OK ){ db_err("[%s]: %s", zDbName, sqlite3_errmsg(db)); } sqlite3_busy_timeout(db, 5000); sqlite3_wal_autocheckpoint(db, 1); /* Set to checkpoint frequently */ - sqlite3_create_function(db, "now", 0, SQLITE_ANY, 0, db_now_function, 0, 0); - sqlite3_create_function(db, "checkin_mtime", 2, SQLITE_ANY, 0, - db_checkin_mtime_function, 0, 0); - sqlite3_create_function(db, "user", 0, SQLITE_ANY, 0, db_sql_user, 0, 0); - sqlite3_create_function(db, "cgi", 1, SQLITE_ANY, 0, db_sql_cgi, 0, 0); - sqlite3_create_function(db, "cgi", 2, SQLITE_ANY, 0, db_sql_cgi, 0, 0); + sqlite3_create_function(db, "user", 0, SQLITE_UTF8, 0, db_sql_user, 0, 0); + sqlite3_create_function(db, "cgi", 1, SQLITE_UTF8, 0, db_sql_cgi, 0, 0); + sqlite3_create_function(db, "cgi", 2, SQLITE_UTF8, 0, db_sql_cgi, 0, 0); sqlite3_create_function(db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0); sqlite3_create_function( db, "is_selected", 1, SQLITE_UTF8, 0, file_is_selected,0,0 ); sqlite3_create_function( db, "if_selected", 3, SQLITE_UTF8, 0, file_is_selected,0,0 ); if( g.fSqlTrace ) sqlite3_trace(db, db_sql_trace, 0); - re_add_sql_func(db); + db_add_aux_functions(db); + re_add_sql_func(db); /* The REGEXP operator */ + foci_register(db); /* The "files_of_checkin" virtual table */ sqlite3_exec(db, "PRAGMA foreign_keys=OFF;", 0, 0, 0); return db; } /* ** Detaches the zLabel database. */ void db_detach(const char *zLabel){ - db_multi_exec("DETACH DATABASE %s", zLabel); + db_multi_exec("DETACH DATABASE %Q", zLabel); } /* ** zDbName is the name of a database file. Attach zDbName using ** the name zLabel. */ void db_attach(const char *zDbName, const char *zLabel){ - db_multi_exec("ATTACH DATABASE %Q AS %s", zDbName, zLabel); + db_multi_exec("ATTACH DATABASE %Q AS %Q", zDbName, zLabel); } /* ** zDbName is the name of a database file. If no other database ** file is open, then open this one. If another database file is @@ -783,10 +944,33 @@ assert( g.zMainDbType!=0 ); db_attach(zDbName, zLabel); if( pWasAttached ) *pWasAttached = 1; } } + +/* +** Close the user database. +*/ +void db_close_config(){ + if( g.useAttach ){ + db_detach("configdb"); + g.useAttach = 0; + g.zConfigDbName = 0; + }else if( g.dbConfig ){ + sqlite3_wal_checkpoint(g.dbConfig, 0); + sqlite3_close(g.dbConfig); + g.dbConfig = 0; + g.zConfigDbType = 0; + g.zConfigDbName = 0; + }else if( g.db && fossil_strcmp(g.zMainDbType, "configdb")==0 ){ + sqlite3_wal_checkpoint(g.db, 0); + sqlite3_close(g.db); + g.db = 0; + g.zMainDbType = 0; + g.zConfigDbName = 0; + } +} /* ** Open the user database in "~/.fossil". Create the database anew if ** it does not already exist. ** @@ -799,53 +983,59 @@ ** case, invoke this routine with useAttach as 1. */ void db_open_config(int useAttach){ char *zDbName; char *zHome; - if( g.zConfigDbName ) return; -#if defined(_WIN32) || defined(__CYGWIN__) - zHome = fossil_getenv("LOCALAPPDATA"); - if( zHome==0 ){ - zHome = fossil_getenv("APPDATA"); - if( zHome==0 ){ - char *zDrive = fossil_getenv("HOMEDRIVE"); - zHome = fossil_getenv("HOMEPATH"); - if( zDrive && zHome ) zHome = mprintf("%s%s", zDrive, zHome); + if( g.zConfigDbName ){ + if( useAttach==g.useAttach ) return; + db_close_config(); + } + zHome = fossil_getenv("FOSSIL_HOME"); +#if defined(_WIN32) || defined(__CYGWIN__) + if( zHome==0 ){ + zHome = fossil_getenv("LOCALAPPDATA"); + if( zHome==0 ){ + zHome = fossil_getenv("APPDATA"); + if( zHome==0 ){ + char *zDrive = fossil_getenv("HOMEDRIVE"); + char *zPath = fossil_getenv("HOMEPATH"); + if( zDrive && zPath ) zHome = mprintf("%s%s", zDrive, zPath); + } } } if( zHome==0 ){ - fossil_fatal("cannot locate home directory - " - "please set the LOCALAPPDATA or APPDATA or HOMEPATH " - "environment variables"); + fossil_fatal("cannot locate home directory - please set the " + "FOSSIL_HOME, LOCALAPPDATA, APPDATA, or HOMEPATH " + "environment variables"); } #else - zHome = fossil_getenv("HOME"); + if( zHome==0 ){ + zHome = fossil_getenv("HOME"); + } if( zHome==0 ){ - fossil_fatal("cannot locate home directory - " - "please set the HOME environment variable"); + fossil_fatal("cannot locate home directory - please set the " + "FOSSIL_HOME or HOME environment variables"); } #endif if( file_isdir(zHome)!=1 ){ fossil_fatal("invalid home directory: %s", zHome); } #if defined(_WIN32) || defined(__CYGWIN__) /* . filenames give some window systems problems and many apps problems */ zDbName = mprintf("%//_fossil", zHome); #else - if( file_access(zHome, W_OK) ){ - fossil_fatal("home directory %s must be writeable", zHome); - } zDbName = mprintf("%s/.fossil", zHome); #endif if( file_size(zDbName)<1024*3 ){ + if( file_access(zHome, W_OK) ){ + fossil_fatal("home directory %s must be writeable", zHome); + } db_init_database(zDbName, zConfigSchema, (char*)0); } -#if defined(_WIN32) || defined(__CYGWIN__) if( file_access(zDbName, W_OK) ){ fossil_fatal("configuration file %s must be writeable", zDbName); } -#endif if( useAttach ){ db_open_or_attach(zDbName, "configdb", &g.useAttach); g.dbConfig = 0; g.zConfigDbType = 0; }else{ @@ -854,30 +1044,47 @@ g.zConfigDbType = "configdb"; } g.zConfigDbName = zDbName; } +/* +** Return TRUE if zTable exists. +*/ +int db_table_exists( + const char *zDb, /* One of: NULL, "configdb", "localdb", "repository" */ + const char *zTable /* Name of table */ +){ + return sqlite3_table_column_metadata(g.db, + zDb ? db_name(zDb) : 0, zTable, 0, + 0, 0, 0, 0, 0)==SQLITE_OK; +} + +/* +** Return TRUE if zTable exists and contains column zColumn. +** Return FALSE if zTable does not exist or if zTable exists +** but lacks zColumn. +*/ +int db_table_has_column( + const char *zDb, /* One of: NULL, "config", "localdb", "repository" */ + const char *zTable, /* Name of table */ + const char *zColumn /* Name of column in table */ +){ + return sqlite3_table_column_metadata(g.db, + zDb ? db_name(zDb) : 0, zTable, zColumn, + 0, 0, 0, 0, 0)==SQLITE_OK; +} /* ** Returns TRUE if zTable exists in the local database but lacks column ** zColumn */ static int db_local_table_exists_but_lacks_column( const char *zTable, const char *zColumn ){ - char *zDef = db_text(0, "SELECT sql FROM %s.sqlite_master" - " WHERE name=='%s' /*scan*/", - db_name("localdb"), zTable); - int rc = 0; - if( zDef ){ - char *zPattern = mprintf("* %s *", zColumn); - rc = strglob(zPattern, zDef)==0; - fossil_free(zPattern); - fossil_free(zDef); - } - return rc; + return db_table_exists(db_name("localdb"), zTable) + && !db_table_has_column(db_name("localdb"), zTable, zColumn); } /* ** If zDbName is a valid local database file, open it and return ** true. If it is not a valid local database file, return 0. @@ -896,19 +1103,19 @@ /* If the "isexe" column is missing from the vfile table, then ** add it now. This code added on 2010-03-06. After all users have ** upgraded, this code can be safely deleted. */ - if( !strglob("* isexe *", zVFileDef) ){ + if( sqlite3_strglob("* isexe *", zVFileDef)!=0 ){ db_multi_exec("ALTER TABLE vfile ADD COLUMN isexe BOOLEAN DEFAULT 0"); } /* If "islink"/"isLink" columns are missing from tables, then ** add them now. This code added on 2011-01-17 and 2011-08-27. ** After all users have upgraded, this code can be safely deleted. */ - if( !strglob("* islink *", zVFileDef) ){ + if( sqlite3_strglob("* islink *", zVFileDef)!=0 ){ db_multi_exec("ALTER TABLE vfile ADD COLUMN islink BOOLEAN DEFAULT 0"); if( db_local_table_exists_but_lacks_column("stashfile", "isLink") ){ db_multi_exec("ALTER TABLE stashfile ADD COLUMN isLink BOOL DEFAULT 0"); } if( db_local_table_exists_but_lacks_column("undo", "isLink") ){ @@ -939,23 +1146,23 @@ ** is found, it is attached to the open database connection too. */ int db_open_local(const char *zDbName){ int i, n; char zPwd[2000]; - static const char aDbName[][10] = { "_FOSSIL_", ".fslckout", ".fos" }; + static const char *(aDbName[]) = { "_FOSSIL_", ".fslckout", ".fos" }; - if( g.localOpen) return 1; + if( g.localOpen ) return 1; file_getcwd(zPwd, sizeof(zPwd)-20); n = strlen(zPwd); - if( n==1 && zPwd[0]=='/' ) zPwd[0] = '.'; while( n>0 ){ for(i=0; i<count(aDbName); i++){ sqlite3_snprintf(sizeof(zPwd)-n, &zPwd[n], "/%s", aDbName[i]); if( isValidLocalDb(zPwd) ){ /* Found a valid checkout database file */ + g.zLocalDbName = mprintf("%s", zPwd); zPwd[n] = 0; - while( n>1 && zPwd[n-1]=='/' ){ + while( n>0 && zPwd[n-1]=='/' ){ n--; zPwd[n] = 0; } g.zLocalRoot = mprintf("%s/", zPwd); g.localOpen = 1; @@ -963,12 +1170,12 @@ db_open_repository(zDbName); return 1; } } n--; - while( n>0 && zPwd[n]!='/' ){ n--; } - while( n>0 && zPwd[n-1]=='/' ){ n--; } + while( n>1 && zPwd[n]!='/' ){ n--; } + while( n>1 && zPwd[n-1]=='/' ){ n--; } zPwd[n] = 0; } /* A checkout database file could not be found */ return 0; @@ -1005,11 +1212,11 @@ if( zDbName==0 ){ db_err("unable to find the name of a repository database"); } } if( file_access(zDbName, R_OK) || file_size(zDbName)<1024 ){ - if( file_access(zDbName, 0) ){ + if( file_access(zDbName, F_OK) ){ #ifdef FOSSIL_ENABLE_JSON g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND; #endif fossil_panic("repository does not exist or" " is in an unreadable directory: %s", zDbName); @@ -1023,19 +1230,42 @@ g.json.resultCode = FSL_JSON_E_DB_NOT_VALID; #endif fossil_panic("not a valid repository: %s", zDbName); } } -#if defined(__CYGWIN__) - g.zRepositoryName = fossil_utf8_to_filename(zDbName); -#else g.zRepositoryName = mprintf("%s", zDbName); -#endif db_open_or_attach(g.zRepositoryName, "repository", 0); g.repositoryOpen = 1; /* Cache "allow-symlinks" option, because we'll need it on every stat call */ g.allowSymlinks = db_get_boolean("allow-symlinks", 0); + g.zAuxSchema = db_get("aux-schema",""); + + /* Verify that the PLINK table has a new column added by the + ** 2014-11-28 schema change. Create it if necessary. This code + ** can be removed in the future, once all users have upgraded to the + ** 2014-11-28 or later schema. + */ + if( !db_table_has_column("repository","plink","baseid") ){ + db_multi_exec( + "ALTER TABLE %s.plink ADD COLUMN baseid;", db_name("repository") + ); + } + + /* Verify that the MLINK table has the newer columns added by the + ** 2015-01-24 schema change. Create them if necessary. This code + ** can be removed in the future, once all users have upgraded to the + ** 2015-01-24 or later schema. + */ + if( !db_table_has_column("repository","mlink","isaux") ){ + db_begin_transaction(); + db_multi_exec( + "ALTER TABLE %s.mlink ADD COLUMN pmid INTEGER DEFAULT 0;" + "ALTER TABLE %s.mlink ADD COLUMN isaux BOOLEAN DEFAULT 0;", + db_name("repository"), db_name("repository") + ); + db_end_transaction(0); + } } /* ** Flags for the db_find_and_open_repository() function. */ @@ -1050,11 +1280,14 @@ ** use the repository of the open checkout if there is one. ** ** Error out if the repository cannot be opened. */ void db_find_and_open_repository(int bFlags, int nArgUsed){ - const char *zRep = find_option("repository", "R", 1); + const char *zRep = find_repository_option(); + if( zRep && file_isdir(zRep)==1 ){ + goto rep_not_found; + } if( zRep==0 && nArgUsed && g.argc==nArgUsed+1 ){ zRep = g.argv[nArgUsed]; } if( zRep==0 ){ if( db_open_local(0)==0 ){ @@ -1096,13 +1329,12 @@ /* ** Return TRUE if the schema is out-of-date */ int db_schema_is_outofdate(void){ - return db_exists("SELECT 1 FROM config" - " WHERE name='aux-schema'" - " AND value<>'%s'", AUX_SCHEMA); + return strcmp(g.zAuxSchema,AUX_SCHEMA_MIN)<0 + || strcmp(g.zAuxSchema,AUX_SCHEMA_MAX)>0; } /* ** Return true if the database is writeable */ @@ -1117,14 +1349,14 @@ void db_verify_schema(void){ if( db_schema_is_outofdate() ){ #ifdef FOSSIL_ENABLE_JSON g.json.resultCode = FSL_JSON_E_DB_NEEDS_REBUILD; #endif - fossil_warning("incorrect repository schema version"); - fossil_warning("your repository has schema version \"%s\" " - "but this binary expects version \"%s\"", - db_get("aux-schema",0), AUX_SCHEMA); + fossil_warning("incorrect repository schema version: " + "current repository schema version is \"%s\" " + "but need versions between \"%s\" and \"%s\".", + g.zAuxSchema, AUX_SCHEMA_MIN, AUX_SCHEMA_MAX); fossil_fatal("run \"fossil rebuild\" to fix this problem"); } } @@ -1143,19 +1375,20 @@ if( g.argc!=3 ){ usage("PATHNAME"); } file_canonical_name(g.argv[2], &repo, 0); zRepo = blob_str(&repo); - if( file_access(zRepo, 0) ){ + if( file_access(zRepo, F_OK) ){ fossil_fatal("no such file: %s", zRepo); } if( db_open_local(zRepo)==0 ){ fossil_fatal("not in a local checkout"); return; } db_open_or_attach(zRepo, "test_repo", 0); db_lset("repository", blob_str(&repo)); + db_record_repository_filename(blob_str(&repo)); db_close(1); } /* @@ -1207,27 +1440,41 @@ while( db.pAllStmt ){ db_finalize(db.pAllStmt); } db_end_transaction(1); pStmt = 0; - if( reportErrors ){ - while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){ - fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt)); + db_close_config(); + + /* If the localdb (the check-out database) is open and if it has + ** a lot of unused free space, then VACUUM it as we shut down. + */ + if( g.localOpen && strcmp(db_name("localdb"),"main")==0 ){ + int nFree = db_int(0, "PRAGMA main.freelist_count"); + int nTotal = db_int(0, "PRAGMA main.page_count"); + if( nFree>nTotal/4 ){ + db_multi_exec("VACUUM;"); + } + } + + if( g.db ){ + int rc; + sqlite3_wal_checkpoint(g.db, 0); + rc = sqlite3_close(g.db); + if( rc==SQLITE_BUSY && reportErrors ){ + while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){ + fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt)); + } } + g.db = 0; + g.zMainDbType = 0; } g.repositoryOpen = 0; g.localOpen = 0; - g.zConfigDbName = NULL; - sqlite3_wal_checkpoint(g.db, 0); - sqlite3_close(g.db); - g.db = 0; - g.zMainDbType = 0; - if( g.dbConfig ){ - sqlite3_close(g.dbConfig); - g.dbConfig = 0; - g.zConfigDbType = 0; - } + assert( g.dbConfig==0 ); + assert( g.useAttach==0 ); + assert( g.zConfigDbName==0 ); + assert( g.zConfigDbType==0 ); } /* ** Create a new empty repository database with the given name. @@ -1279,13 +1526,13 @@ " WHERE login=%Q", zUser ); if( !setupUserOnly ){ db_multi_exec( "INSERT OR IGNORE INTO user(login,pw,cap,info)" - " VALUES('anonymous',hex(randomblob(8)),'hmncz','Anon');" + " VALUES('anonymous',hex(randomblob(8)),'hmnc','Anon');" "INSERT OR IGNORE INTO user(login,pw,cap,info)" - " VALUES('nobody','','gjor','Nobody');" + " VALUES('nobody','','gjorz','Nobody');" "INSERT OR IGNORE INTO user(login,pw,cap,info)" " VALUES('developer','','dei','Dev');" "INSERT OR IGNORE INTO user(login,pw,cap,info)" " VALUES('reader','','kptw','Reader');" ); @@ -1301,17 +1548,17 @@ Blob x; int i; const char *zSep = ""; blob_zero(&x); - blob_append(&x, "(", 1); - for(i=0; ctrlSettings[i].name; i++){ - blob_appendf(&x, "%s'%s'", zSep, ctrlSettings[i].name); + blob_append_sql(&x, "("); + for(i=0; aSetting[i].name; i++){ + blob_append_sql(&x, "%s%Q", zSep/*safe-for-%s*/, aSetting[i].name); zSep = ","; } - blob_append(&x, ")", 1); - return blob_str(&x); + blob_append_sql(&x, ")"); + return blob_sql_text(&x); } /* ** Fill an empty repository database with the basic information for a ** repository. This function is shared between 'create_repository_cmd' @@ -1331,27 +1578,25 @@ ** not server and project codes are invented for this repository. */ void db_initial_setup( const char *zTemplate, /* Repository from which to copy settings. */ const char *zInitialDate, /* Initial date of repository. (ex: "now") */ - const char *zDefaultUser, /* Default user for the repository */ - int makeServerCodes /* True to make new server & project codes */ + const char *zDefaultUser /* Default user for the repository */ ){ char *zDate; Blob hash; Blob manifest; db_set("content-schema", CONTENT_SCHEMA, 0); - db_set("aux-schema", AUX_SCHEMA, 0); - if( makeServerCodes ){ - db_multi_exec( + db_set("aux-schema", AUX_SCHEMA_MAX, 0); + db_set("rebuilt", get_version(), 0); + db_multi_exec( "INSERT INTO config(name,value,mtime)" " VALUES('server-code', lower(hex(randomblob(20))),now());" "INSERT INTO config(name,value,mtime)" " VALUES('project-code', lower(hex(randomblob(20))),now());" - ); - } + ); if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0); if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0); if( !db_is_global("timeline-plaintext") ){ db_set_int("timeline-plaintext", 1, 0); } @@ -1365,11 +1610,12 @@ */ db_multi_exec( "INSERT OR REPLACE INTO config" " SELECT name,value,mtime FROM settingSrc.config" " WHERE (name IN %s OR name IN %s)" - " AND name NOT GLOB 'project-*';", + " AND name NOT GLOB 'project-*'" + " AND name NOT GLOB 'short-project-*';", configure_inop_rhs(CONFIGSET_ALL), db_setting_inop_rhs() ); db_multi_exec( "REPLACE INTO reportfmt SELECT * FROM settingSrc.reportfmt;" @@ -1412,11 +1658,11 @@ blob_appendf(&manifest, "U %F\n", g.zLogin); md5sum_blob(&manifest, &hash); blob_appendf(&manifest, "Z %b\n", &hash); blob_reset(&hash); rid = content_put(&manifest); - manifest_crosslink(rid, &manifest); + manifest_crosslink(rid, &manifest, MC_NONE); } } /* ** COMMAND: new* @@ -1443,11 +1689,11 @@ ** associated permissions will be copied. ** ** Options: ** --template FILE copy settings from repository file ** --admin-user|-A USERNAME select given USERNAME as admin user -** --date-override DATETIME use DATETIME as time of the initial checkin +** --date-override DATETIME use DATETIME as time of the initial check-in ** ** See also: clone */ void create_repository_cmd(void){ char *zPassword; @@ -1456,20 +1702,28 @@ const char *zDefaultUser; /* Optional name of the default user */ zTemplate = find_option("template",0,1); zDate = find_option("date-override",0,1); zDefaultUser = find_option("admin-user","A",1); - if( zDate==0 ) zDate = "now"; + /* We should be done with options.. */ + verify_all_options(); + if( g.argc!=3 ){ usage("REPOSITORY-NAME"); } + + if( -1 != file_size(g.argv[2]) ){ + fossil_fatal("file already exists: %s", g.argv[2]); + } + db_create_repository(g.argv[2]); db_open_repository(g.argv[2]); db_open_config(0); if( zTemplate ) db_attach(zTemplate, "settingSrc"); db_begin_transaction(); - db_initial_setup(zTemplate, zDate, zDefaultUser, 1); + if( zDate==0 ) zDate = "now"; + db_initial_setup(zTemplate, zDate, zDefaultUser); db_end_transaction(0); if( zTemplate ) db_detach("settingSrc"); fossil_print("project-id: %s\n", db_get("project-code", 0)); fossil_print("server-id: %s\n", db_get("server-code", 0)); zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); @@ -1689,16 +1943,21 @@ g.zConfigDbType = zTempDbType; } } /* -** Logic for reading potentially versioned settings from -** .fossil-settings/<name> , and emits warnings if necessary. -** Returns the non-versioned value without modification if there is no -** versioned value. +** Try to read a versioned setting string from .fossil-settings/<name>. +** +** Return the text of the string if it is found. Return NULL if not +** found. +** +** If the zNonVersionedSetting parameter is not NULL then it holds the +** non-versioned value for this setting. If both a versioned and ad +** non-versioned value exist and are not equal, then a warning message +** might be generated. */ -char *db_get_do_versionable(const char *zName, char *zNonVersionedSetting){ +char *db_get_versioned(const char *zName, char *zNonVersionedSetting){ char *zVersionedSetting = 0; int noWarn = 0; struct _cacheEntry { struct _cacheEntry *next; const char *zName, *zValue; @@ -1769,37 +2028,50 @@ /* ** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the ** repository and local databases. +** +** If no such variable exists, return zDefault. Or, if zName is the name +** of a setting, then the zDefault is ignored and the default value of the +** setting is returned instead. If zName is a versioned setting, then +** versioned value takes priority. */ char *db_get(const char *zName, char *zDefault){ char *z = 0; - int i; - const struct stControlSettings *ctrlSetting = 0; - /* Is this a setting? */ - for(i=0; ctrlSettings[i].name; i++){ - if( strcmp(ctrlSettings[i].name, zName)==0 ){ - ctrlSetting = &(ctrlSettings[i]); - break; - } - } + const Setting *pSetting = db_find_setting(zName, 0); if( g.repositoryOpen ){ z = db_text(0, "SELECT value FROM config WHERE name=%Q", zName); } if( z==0 && g.zConfigDbName ){ db_swap_connections(); z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName); db_swap_connections(); } - if( ctrlSetting!=0 && ctrlSetting->versionable ){ + if( pSetting!=0 && pSetting->versionable ){ /* This is a versionable setting, try and get the info from a ** checked out file */ - z = db_get_do_versionable(zName, z); + z = db_get_versioned(zName, z); + } + if( z==0 ){ + if( zDefault==0 && pSetting && pSetting->def[0] ){ + z = fossil_strdup(pSetting->def); + }else{ + z = zDefault; + } + } + return z; +} +char *db_get_mtime(const char *zName, char *zFormat, char *zDefault){ + char *z = 0; + if( g.repositoryOpen ){ + z = db_text(0, "SELECT mtime FROM config WHERE name=%Q", zName); } if( z==0 ){ z = zDefault; + }else if( zFormat!=0 ){ + z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z); } return z; } void db_set(const char *zName, const char *zValue, int globalFlag){ db_begin_transaction(); @@ -1893,31 +2165,10 @@ } void db_lset_int(const char *zName, int value){ db_multi_exec("REPLACE INTO vvar(name,value) VALUES(%Q,%d)", zName, value); } -/* -** Returns non-0 if the database (which must be open) table identified -** by zTableName has a column named zColName (case-sensitive), else -** returns 0. -*/ -int db_table_has_column( char const *zTableName, char const *zColName ){ - Stmt q = empty_Stmt; - int rc = 0; - db_prepare( &q, "PRAGMA table_info(%Q)", zTableName ); - while(SQLITE_ROW == db_step(&q)){ - /* Columns: (cid, name, type, notnull, dflt_value, pk) */ - char const * zCol = db_column_text(&q, 1); - if(0==fossil_strcmp(zColName, zCol)){ - rc = 1; - break; - } - } - db_finalize(&q); - return rc; -} - /* ** Record the name of a local repository in the global_config() database. ** The repository filename %s is recorded as an entry with a "name" field ** of the following form: ** @@ -1931,36 +2182,55 @@ ** ckout:%s ** ** Where %s is the checkout root. The value is the repository file. */ void db_record_repository_filename(const char *zName){ + char *zRepoSetting; + char *zCkoutSetting; Blob full; if( zName==0 ){ if( !g.localOpen ) return; zName = db_repository_filename(); } file_canonical_name(zName, &full, 0); + (void)filename_collation(); /* Initialize before connection swap */ db_swap_connections(); + zRepoSetting = mprintf("repo:%q", blob_str(&full)); + db_multi_exec( + "DELETE FROM global_config WHERE name %s = %Q;", + filename_collation(), zRepoSetting + ); db_multi_exec( "INSERT OR IGNORE INTO global_config(name,value)" - "VALUES('repo:%q',1)", - blob_str(&full) + "VALUES(%Q,1);", + zRepoSetting ); + fossil_free(zRepoSetting); if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){ Blob localRoot; file_canonical_name(g.zLocalRoot, &localRoot, 1); + zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot)); + db_multi_exec( + "DELETE FROM global_config WHERE name %s = %Q;", + filename_collation(), zCkoutSetting + ); db_multi_exec( "REPLACE INTO global_config(name, value)" - "VALUES('ckout:%q','%q');", - blob_str(&localRoot), blob_str(&full) + "VALUES(%Q,%Q);", + zCkoutSetting, blob_str(&full) ); db_swap_connections(); + db_optional_sql("repository", + "DELETE FROM config WHERE name %s = %Q;", + filename_collation(), zCkoutSetting + ); db_optional_sql("repository", "REPLACE INTO config(name,value,mtime)" - "VALUES('ckout:%q',1,now())", - blob_str(&localRoot) + "VALUES(%Q,1,now());", + zCkoutSetting ); + fossil_free(zCkoutSetting); blob_reset(&localRoot); }else{ db_swap_connections(); } blob_reset(&full); @@ -1976,24 +2246,37 @@ ** If VERSION is specified then that version is checked out. Otherwise ** the latest version is checked out. No files other than "manifest" ** and "manifest.uuid" are modified if the --keep option is present. ** ** Options: -** --keep Only modify the manifest and manifest.uuid files -** --nested Allow opening a repository inside an opened checkout +** --empty Initialize checkout as being empty, but still connected +** with the local repository. If you commit this checkout, +** it will become a new "initial" commit in the repository. +** --keep Only modify the manifest and manifest.uuid files +** --nested Allow opening a repository inside an opened checkout +** --force-missing Force opening a repository with missing content ** ** See also: close */ void cmd_open(void){ - int vid; + int emptyFlag; int keepFlag; + int forceMissingFlag; int allowNested; - static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0 }; + char **oldArgv; + int oldArgc; + static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 }; url_proxy_options(); + emptyFlag = find_option("empty",0,0)!=0; keepFlag = find_option("keep",0,0)!=0; + forceMissingFlag = find_option("force-missing",0,0)!=0; allowNested = find_option("nested",0,0)!=0; + + /* We should be done with options.. */ + verify_all_options(); + if( g.argc!=3 && g.argc!=4 ){ usage("REPOSITORY-FILENAME ?VERSION?"); } if( !allowNested && db_open_local(0) ){ fossil_fatal("already within an open tree rooted at %s", g.zLocalRoot); @@ -2011,146 +2294,194 @@ (char*)0); db_delete_on_failure(LOCALDB_NAME); db_open_local(0); db_lset("repository", g.argv[2]); db_record_repository_filename(g.argv[2]); - vid = db_int(0, "SELECT pid FROM plink y" - " WHERE NOT EXISTS(SELECT 1 FROM plink x WHERE x.cid=y.pid)"); - if( vid==0 ){ - db_lset_int("checkout", 1); - }else{ - char **oldArgv = g.argv; - int oldArgc = g.argc; - db_lset_int("checkout", vid); - azNewArgv[0] = g.argv[0]; - g.argv = azNewArgv; + db_lset_int("checkout", 0); + oldArgv = g.argv; + oldArgc = g.argc; + azNewArgv[0] = g.argv[0]; + g.argv = azNewArgv; + if( !emptyFlag ){ g.argc = 3; if( oldArgc==4 ){ azNewArgv[g.argc-1] = oldArgv[3]; + }else if( !db_exists("SELECT 1 FROM event WHERE type='ci'") ){ + azNewArgv[g.argc-1] = "--latest"; }else{ azNewArgv[g.argc-1] = db_get("main-branch", "trunk"); } if( keepFlag ){ azNewArgv[g.argc++] = "--keep"; } + if( forceMissingFlag ){ + azNewArgv[g.argc++] = "--force-missing"; + } checkout_cmd(); - g.argc = 2; - info_cmd(); } + g.argc = 2; + info_cmd(); } /* -** Print the value of a setting named zName +** Print the current value of a setting identified by the pSetting +** pointer. */ -static void print_setting( - const struct stControlSettings *ctrlSetting, - int localOpen -){ +static void print_setting(const Setting *pSetting){ Stmt q; if( g.repositoryOpen ){ db_prepare(&q, "SELECT '(local)', value FROM config WHERE name=%Q" " UNION ALL " "SELECT '(global)', value FROM global_config WHERE name=%Q", - ctrlSetting->name, ctrlSetting->name + pSetting->name, pSetting->name ); }else{ db_prepare(&q, "SELECT '(global)', value FROM global_config WHERE name=%Q", - ctrlSetting->name + pSetting->name ); } if( db_step(&q)==SQLITE_ROW ){ - fossil_print("%-20s %-8s %s\n", ctrlSetting->name, db_column_text(&q, 0), + fossil_print("%-20s %-8s %s\n", pSetting->name, db_column_text(&q, 0), db_column_text(&q, 1)); }else{ - fossil_print("%-20s\n", ctrlSetting->name); + fossil_print("%-20s\n", pSetting->name); } - if( ctrlSetting->versionable && localOpen ){ + if( pSetting->versionable && g.localOpen ){ /* Check to see if this is overridden by a versionable settings file */ Blob versionedPathname; blob_zero(&versionedPathname); blob_appendf(&versionedPathname, "%s/.fossil-settings/%s", - g.zLocalRoot, ctrlSetting->name); + g.zLocalRoot, pSetting->name); if( file_size(blob_str(&versionedPathname))>=0 ){ fossil_print(" (overridden by contents of file .fossil-settings/%s)\n", - ctrlSetting->name); + pSetting->name); } } db_finalize(&q); } +#if INTERFACE /* -** define all settings, which can be controlled via the set/unset -** command. var is the name of the internal configuration name for db_(un)set. +** Define all settings, which can be controlled via the set/unset +** command. +** +** var is the name of the internal configuration name for db_(un)set. ** If var is 0, the settings name is used. +** ** width is the length for the edit field on the behavior page, 0 ** is used for on/off checkboxes. +** ** The behaviour page doesn't use a special layout. It lists all ** set-commands and displays the 'set'-help as info. */ -#if INTERFACE -struct stControlSettings { - char const *name; /* Name of the setting */ - char const *var; /* Internal variable name used by db_set() */ - int width; /* Width of display. 0 for boolean values */ +struct Setting { + const char *name; /* Name of the setting */ + const char *var; /* Internal variable name used by db_set() */ + int width; /* Width of display. 0 for boolean values. */ int versionable; /* Is this setting versionable? */ - char const *def; /* Default value */ + int forceTextArea; /* Force using a text area for display? */ + const char *def; /* Default value */ }; #endif /* INTERFACE */ -struct stControlSettings const ctrlSettings[] = { - { "access-log", 0, 0, 0, "off" }, - { "allow-symlinks",0, 0, 1, "off" }, - { "auto-captcha", "autocaptcha", 0, 0, "on" }, - { "auto-hyperlink",0, 0, 0, "on", }, - { "auto-shun", 0, 0, 0, "on" }, - { "autosync", 0, 0, 0, "on" }, - { "binary-glob", 0, 40, 1, "" }, - { "clearsign", 0, 0, 0, "off" }, -#if defined(_WIN32) || defined(__CYGWIN__) || defined(__DARWIN__) || defined(__APPLE__) - { "case-sensitive",0, 0, 0, "off" }, + +const Setting aSetting[] = { + { "access-log", 0, 0, 0, 0, "off" }, + { "admin-log", 0, 0, 0, 0, "off" }, + { "allow-symlinks", 0, 0, 1, 0, "off" }, + { "auto-captcha", "autocaptcha", 0, 0, 0, "on" }, + { "auto-hyperlink", 0, 0, 0, 0, "on", }, + { "auto-shun", 0, 0, 0, 0, "on" }, + { "autosync", 0, 0, 0, 0, "on" }, + { "autosync-tries", 0, 16, 0, 0, "1" }, + { "binary-glob", 0, 40, 1, 0, "" }, +#if defined(_WIN32) || defined(__CYGWIN__) || defined(__DARWIN__) || \ + defined(__APPLE__) + { "case-sensitive", 0, 0, 0, 0, "off" }, #else - { "case-sensitive",0, 0, 0, "on" }, + { "case-sensitive", 0, 0, 0, 0, "on" }, #endif - { "clean-glob", 0, 40, 1, "" }, - { "crnl-glob", 0, 40, 1, "" }, - { "default-perms", 0, 16, 0, "u" }, - { "diff-binary", 0, 0, 0, "on" }, - { "diff-command", 0, 40, 0, "" }, - { "dont-push", 0, 0, 0, "off" }, - { "editor", 0, 32, 0, "" }, - { "empty-dirs", 0, 40, 1, "" }, - { "encoding-glob", 0, 40, 1, "" }, - { "gdiff-command", 0, 40, 0, "gdiff" }, - { "gmerge-command",0, 40, 0, "" }, - { "http-port", 0, 16, 0, "8080" }, - { "https-login", 0, 0, 0, "off" }, - { "ignore-glob", 0, 40, 1, "" }, - { "keep-glob", 0, 40, 1, "" }, - { "localauth", 0, 0, 0, "off" }, - { "main-branch", 0, 40, 0, "trunk" }, - { "manifest", 0, 0, 1, "off" }, - { "max-upload", 0, 25, 0, "250000" }, - { "mtime-changes", 0, 0, 0, "on" }, - { "pgp-command", 0, 40, 0, "gpg --clearsign -o " }, - { "proxy", 0, 32, 0, "off" }, - { "relative-paths",0, 0, 0, "on" }, - { "repo-cksum", 0, 0, 0, "on" }, - { "self-register", 0, 0, 0, "off" }, - { "ssh-command", 0, 40, 0, "" }, - { "ssl-ca-location",0, 40, 0, "" }, - { "ssl-identity", 0, 40, 0, "" }, + { "clean-glob", 0, 40, 1, 0, "" }, + { "clearsign", 0, 0, 0, 0, "off" }, + { "crnl-glob", 0, 40, 1, 0, "" }, + { "default-perms", 0, 16, 0, 0, "u" }, + { "diff-binary", 0, 0, 0, 0, "on" }, + { "diff-command", 0, 40, 0, 0, "" }, + { "dont-push", 0, 0, 0, 0, "off" }, + { "dotfiles", 0, 0, 1, 0, "off" }, + { "editor", 0, 32, 0, 0, "" }, + { "empty-dirs", 0, 40, 1, 0, "" }, + { "encoding-glob", 0, 40, 1, 0, "" }, + { "gdiff-command", 0, 40, 0, 0, "gdiff" }, + { "gmerge-command", 0, 40, 0, 0, "" }, + { "hash-digits", 0, 5, 0, 0, "10" }, + { "http-port", 0, 16, 0, 0, "8080" }, + { "https-login", 0, 0, 0, 0, "off" }, + { "ignore-glob", 0, 40, 1, 0, "" }, + { "keep-glob", 0, 40, 1, 0, "" }, + { "localauth", 0, 0, 0, 0, "off" }, + { "main-branch", 0, 40, 0, 0, "trunk" }, + { "manifest", 0, 0, 1, 0, "off" }, + { "max-loadavg", 0, 25, 0, 0, "0.0" }, + { "max-upload", 0, 25, 0, 0, "250000" }, + { "mtime-changes", 0, 0, 0, 0, "on" }, + { "pgp-command", 0, 40, 0, 0, "gpg --clearsign -o " }, + { "proxy", 0, 32, 0, 0, "off" }, + { "relative-paths", 0, 0, 0, 0, "on" }, + { "repo-cksum", 0, 0, 0, 0, "on" }, + { "self-register", 0, 0, 0, 0, "off" }, + { "ssh-command", 0, 40, 0, 0, "" }, + { "ssl-ca-location", 0, 40, 0, 0, "" }, + { "ssl-identity", 0, 40, 0, 0, "" }, #ifdef FOSSIL_ENABLE_TCL - { "tcl", 0, 0, 0, "off" }, - { "tcl-setup", 0, 40, 0, "" }, + { "tcl", 0, 0, 0, 0, "off" }, + { "tcl-setup", 0, 40, 1, 1, "" }, +#endif +#ifdef FOSSIL_ENABLE_TH1_DOCS + { "th1-docs", 0, 0, 0, 0, "off" }, +#endif +#ifdef FOSSIL_ENABLE_TH1_HOOKS + { "th1-hooks", 0, 0, 0, 0, "off" }, #endif - { "th1-setup", 0, 40, 0, "" }, - { "web-browser", 0, 32, 0, "" }, - { "white-foreground", 0, 0, 0, "off" }, - { 0,0,0,0,0 } + { "th1-setup", 0, 40, 1, 1, "" }, + { "th1-uri-regexp", 0, 40, 1, 0, "" }, + { "web-browser", 0, 32, 0, 0, "" }, + { 0,0,0,0,0,0 } }; + +/* +** Look up a control setting by its name. Return a pointer to the Setting +** object, or NULL if there is no such setting. +** +** If allowPrefix is true, then the Setting returned is the first one for +** which zName is a prefix of the Setting name. +*/ +const Setting *db_find_setting(const char *zName, int allowPrefix){ + int lwr, mid, upr, c; + int n = (int)strlen(zName) + !allowPrefix; + lwr = 0; + upr = ArraySize(aSetting)-2; + while( upr>=lwr ){ + mid = (upr+lwr)/2; + c = fossil_strncmp(zName, aSetting[mid].name, n); + if( c<0 ){ + upr = mid - 1; + }else if( c>0 ){ + lwr = mid + 1; + }else{ + if( allowPrefix ){ + while( mid>lwr && fossil_strncmp(zName, aSetting[mid-1].name, n)==0 ){ + mid--; + } + } + return &aSetting[mid]; + } + } + return 0; +} /* ** COMMAND: settings ** COMMAND: unset* ** @@ -2168,10 +2499,13 @@ ** The "unset" command clears a property setting. ** ** ** access-log If enabled, record successful and failed login attempts ** in the "accesslog" table. Default: off +** +** admin-log If enabled, record configuration changes in the +** "admin_log" table. Default: off ** ** allow-symlinks If enabled, don't follow symlinks, and instead treat ** (versionable) them as symlinks on Unix. Has no effect on Windows ** (existing links in repository created on Unix become ** plain-text files with link destination path inside). @@ -2192,10 +2526,15 @@ ** autosync If enabled, automatically pull prior to commit ** or update and automatically push after commit or ** tag or branch creation. If the value is "pullonly" ** then only pull operations occur automatically. ** Default: on +** +** autosync-tries If autosync is enabled setting this to a value greater +** than zero will cause autosync to try no more than this +** number of attempts if there is a sync failure. +** Default: 1 ** ** binary-glob The VALUE is a comma or newline-separated list of ** (versionable) GLOB patterns that should be treated as binary files ** for committing and merging purposes. Example: *.jpg ** @@ -2228,10 +2567,13 @@ ** diff-command External command to run when performing a diff. ** If undefined, the internal text diff will be used. ** ** dont-push Prevent this repository from pushing from client to ** server. Useful when setting up a private branch. +** +** dotfiles Include --dotfiles option for all compatible commands. +** (versionable) ** ** editor Text editor command used for check-in comments. ** ** empty-dirs A comma or newline-separated list of pathnames. On ** (versionable) update and checkout commands, if no file or directory @@ -2250,10 +2592,13 @@ ** gmerge-command A graphical merge conflict resolver command operating ** on four files. ** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output" ** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output" ** Ex: meld "%baseline" "%original" "%merge" "%output" +** +** hash-digits The number of hexadecimal digits of the SHA1 hash to +** display. (Default: 10; Minimum: 6) ** ** http-port The TCP/IP port number to use by the "server" ** and "ui" commands. Default: 8080 ** ** https-login Send login credentials using HTTPS instead of HTTP @@ -2276,10 +2621,17 @@ ** main-branch The primary branch for the project. Default: trunk ** ** manifest If enabled, automatically create files "manifest" and ** (versionable) "manifest.uuid" in every checkout. The SQLite and ** Fossil repositories both require this. Default: off. +** +** max-loadavg Some CPU-intensive web pages (ex: /zip, /tarball, /blame) +** are disallowed if the system load average goes above this +** value. "0.0" means no limit. This only works on unix. +** Only local settings of this value make a difference since +** when running as a web-server, Fossil does not open the +** global configuration database. ** ** max-upload A limit on the size of uplink HTTP requests. The ** default is 250000 bytes. ** ** mtime-changes Use file modification times (mtimes) to detect when @@ -2333,16 +2685,34 @@ ** scripts to be evaluated from TH1. Additionally, the Tcl ** interpreter will be able to evaluate arbitrary TH1 ** expressions and scripts. Default: off. ** ** tcl-setup This is the setup script to be evaluated after creating -** and initializing the Tcl interpreter. By default, this +** (versionable) and initializing the Tcl interpreter. By default, this ** is empty and no extra setup is performed. +** +** th1-docs WARNING: If enabled (and Fossil was compiled with TH1 +** support for embedded documentation files), this allows +** embedded documentation files to contain arbitrary TH1 +** scripts that are evaluated on the server. If native +** Tcl integration is also enabled, this setting has the +** potential to allow anybody with check-in privileges to +** do almost anything that the associated operating system +** user account could do. Extreme caution should be used +** when enabling this setting. Default: off. +** +** th1-hooks If enabled (and Fossil was compiled with support for TH1 +** hooks), special TH1 commands will be called before and +** after any Fossil command or web page. Default: off. ** ** th1-setup This is the setup script to be evaluated after creating -** and initializing the TH1 interpreter. By default, this +** (versionable) and initializing the TH1 interpreter. By default, this ** is empty and no extra setup is performed. +** +** th1-uri-regexp Specify which URI's are allowed in HTTP requests from +** (versionable) TH1 scripts. If empty, no HTTP requests are allowed +** whatsoever. The default is an empty string. ** ** web-browser A shell command used to launch your preferred ** web browser when given a URL as an argument. ** Defaults to "start" on windows, "open" on Mac, ** and "firefox" on Unix. @@ -2365,39 +2735,66 @@ globalFlag = 1; } if( unsetFlag && g.argc!=3 ){ usage("PROPERTY ?-global?"); } + + /* Verify that the aSetting[] entries are in sorted order. This is + ** necessary for the binary search in db_find_setting() to work correctly. + */ + for(i=1; aSetting[i].name; i++){ + if( fossil_strcmp(aSetting[i-1].name, aSetting[i].name)>=0 ){ + fossil_panic("Internal Error: aSetting[] entries for \"%s\"" + " and \"%s\" are out of order.", + aSetting[i-1].name, aSetting[i].name); + } + } + if( g.argc==2 ){ - int openLocal = db_open_local(0); - for(i=0; ctrlSettings[i].name; i++){ - print_setting(&ctrlSettings[i], openLocal); + for(i=0; aSetting[i].name; i++){ + print_setting(&aSetting[i]); } }else if( g.argc==3 || g.argc==4 ){ const char *zName = g.argv[2]; - int isManifest; - int n = strlen(zName); - for(i=0; ctrlSettings[i].name; i++){ - if( strncmp(ctrlSettings[i].name, zName, n)==0 ) break; - } - if( !ctrlSettings[i].name ){ + int n = (int)strlen(zName); + const Setting *pSetting = db_find_setting(zName, 1); + if( pSetting==0 ){ fossil_fatal("no such setting: %s", zName); } - isManifest = fossil_strcmp(ctrlSettings[i].name, "manifest")==0; - if( isManifest && globalFlag ){ + if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){ fossil_fatal("cannot set 'manifest' globally"); } - if( unsetFlag ){ - db_unset(ctrlSettings[i].name, globalFlag); - }else if( g.argc==4 ){ - db_set(ctrlSettings[i].name, g.argv[3], globalFlag); + if( unsetFlag || g.argc==4 ){ + int isManifest = fossil_strcmp(pSetting->name, "manifest")==0; + if( n!=strlen(pSetting[0].name) && pSetting[1].name && + fossil_strncmp(pSetting[1].name, zName, n)==0 ){ + Blob x; + int i; + blob_init(&x,0,0); + for(i=0; pSetting[i].name; i++){ + if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break; + blob_appendf(&x, " %s", pSetting[i].name); + } + fossil_fatal("ambiguous setting \"%s\" - might be:%s", + zName, blob_str(&x)); + } + if( globalFlag && isManifest ){ + fossil_fatal("cannot set 'manifest' globally"); + } + if( unsetFlag ){ + db_unset(pSetting->name, globalFlag); + }else{ + db_set(pSetting->name, g.argv[3], globalFlag); + } + if( isManifest && g.localOpen ){ + manifest_to_disk(db_lget_int("checkout", 0)); + } }else{ - isManifest = 0; - print_setting(&ctrlSettings[i], db_open_local(0)); - } - if( isManifest && g.localOpen ){ - manifest_to_disk(db_lget_int("checkout", 0)); + while( pSetting->name && fossil_strncmp(pSetting->name,zName,n)==0 ){ + print_setting(pSetting); + pSetting++; + } } }else{ usage("?PROPERTY? ?VALUE? ?-global?"); } } @@ -2442,5 +2839,113 @@ rDiff = db_double(0.0, "SELECT julianday('now') - julianday(%Q)", g.argv[2]); fossil_print("Time differences: %s\n", db_timespan_name(rDiff)); sqlite3_close(g.db); g.db = 0; } + +/* +** COMMAND: test-without-rowid +** %fossil test-without-rowid FILENAME... +** +** Change the Fossil repository FILENAME to make use of the WITHOUT ROWID +** optimization. FILENAME can also be the ~/.fossil file or a local +** .fslckout or _FOSSIL_ file. +** +** The purpose of this command is for testing the WITHOUT ROWID capabilities +** of SQLite. There is no big advantage to using WITHOUT ROWID in Fossil. +** +** Options: +** --dryrun | -n No changes. Just print what would happen. +*/ +void test_without_rowid(void){ + int i, j; + Stmt q; + Blob allSql; + int dryRun = find_option("dry-run", "n", 0)!=0; + for(i=2; i<g.argc; i++){ + db_open_or_attach(g.argv[i], "main", 0); + blob_init(&allSql, "BEGIN;\n", -1); + db_prepare(&q, + "SELECT name, sql FROM main.sqlite_master " + " WHERE type='table' AND sql NOT LIKE '%%WITHOUT ROWID%%'" + " AND name IN ('global_config','shun','concealed','config'," + " 'plink','tagxref','backlink','vcache');" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zTName = db_column_text(&q, 0); + const char *zOrigSql = db_column_text(&q, 1); + Blob newSql; + blob_init(&newSql, 0, 0); + for(j=0; zOrigSql[j]; j++){ + if( fossil_strnicmp(zOrigSql+j,"unique",6)==0 ){ + blob_append(&newSql, zOrigSql, j); + blob_append(&newSql, "PRIMARY KEY", -1); + zOrigSql += j+6; + j = -1; + } + } + blob_append(&newSql, zOrigSql, -1); + blob_append_sql(&allSql, + "ALTER TABLE \"%w\" RENAME TO \"x_%w\";\n" + "%s WITHOUT ROWID;\n" + "INSERT INTO \"%w\" SELECT * FROM \"x_%w\";\n" + "DROP TABLE \"x_%w\";\n", + zTName, zTName, blob_sql_text(&newSql), zTName, zTName, zTName + ); + fossil_print("Converting table %s of %s to WITHOUT ROWID.\n", + zTName, g.argv[i]); + blob_reset(&newSql); + } + blob_append_sql(&allSql, "COMMIT;\n"); + db_finalize(&q); + if( dryRun ){ + fossil_print("SQL that would have been evaluated:\n"); + fossil_print("%.78c\n", '-'); + fossil_print("%s", blob_sql_text(&allSql)); + }else{ + db_multi_exec("%s", blob_sql_text(&allSql)); + } + blob_reset(&allSql); + db_close(1); + } +} + +/* +** Make sure the adminlog table exists. Create it if it does not +*/ +void create_admin_log_table(void){ + static int once = 0; + if( once ) return; + once = 1; + db_multi_exec( + "CREATE TABLE IF NOT EXISTS \"%w\".admin_log(\n" + " id INTEGER PRIMARY KEY,\n" + " time INTEGER, -- Seconds since 1970\n" + " page TEXT, -- path of page\n" + " who TEXT, -- User who made the change\n " + " what TEXT -- What changed\n" + ")", db_name("repository") + ); +} + +/* +** Write a message into the admin_event table, if admin logging is +** enabled via the admin-log configuration option. +*/ +void admin_log(const char *zFormat, ...){ + Blob what = empty_blob; + va_list ap; + if( !db_get_boolean("admin-log", 0) ){ + /* Potential leak here (on %z params) but + the alternative is to let blob_vappendf() + do it below. */ + return; + } + create_admin_log_table(); + va_start(ap,zFormat); + blob_vappendf( &what, zFormat, ap ); + va_end(ap); + db_multi_exec("INSERT INTO admin_log(time,page,who,what)" + " VALUES(now(), %Q, %Q, %B)", + g.zPath, g.zLogin, &what); + blob_reset(&what); +} Index: src/delta.c ================================================================== --- src/delta.c +++ src/delta.c @@ -20,10 +20,11 @@ ** Though developed specifically for fossil, the code in this file ** is generally applicable and is thus easily separated from the ** fossil source code base. Nothing in this file depends on anything ** else in fossil. */ +#include "config.h" #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <string.h> #include "delta.h" @@ -64,11 +65,11 @@ ** The "u32" type must be an unsigned 32-bit integer. Adjust this */ typedef unsigned int u32; /* -** Must be a 16-bit value +** Must be a 16-bit value */ typedef short int s16; typedef unsigned short int u16; #endif /* INTERFACE */ @@ -81,11 +82,11 @@ /* ** The current state of the rolling hash. ** ** z[] holds the values that have been hashed. z[] is a circular buffer. -** z[i] is the first entry and z[(i+NHASH-1)%NHASH] is the last entry of +** z[i] is the first entry and z[(i+NHASH-1)%NHASH] is the last entry of ** the window. ** ** Hash.a is the sum of all elements of hash.z[]. Hash.b is a weighted ** sum. Hash.b is z[i]*NHASH + z[i+1]*(NHASH-1) + ... + z[i+NHASH-1]*1. ** (Each index for z[] should be module NHASH, of course. The %NHASH operator @@ -134,11 +135,11 @@ /* ** Write an base-64 integer into the given buffer. */ static void putInt(unsigned int v, char **pz){ - static const char zDigits[] = + static const char zDigits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"; /* 123456789 123456789 123456789 123456789 123456789 123456789 123 */ int i, j; char zBuf[20]; if( v==0 ){ @@ -228,11 +229,11 @@ } /* ** Create a new delta. ** -** The delta is written into a preallocated buffer, zDelta, which +** The delta is written into a preallocated buffer, zDelta, which ** should be at least 60 bytes longer than the target file, zOut. ** The delta string will be NUL-terminated, but it might also contain ** embedded NUL characters if either the zSrc or zOut files are ** binary. This function returns the length of the delta string ** in bytes, excluding the final NUL terminator character. @@ -244,11 +245,11 @@ ** delta file z, a program can compute the size of the output file ** simply by reading the first line and decoding the base-64 number ** found there. The delta_output_size() routine does exactly this. ** ** After the initial size number, the delta consists of a series of -** literal text segments and commands to copy from the SOURCE file. +** literal text segments and commands to copy from the SOURCE file. ** A copy command looks like this: ** ** NNN@MMM, ** ** where NNN is the number of bytes to be copied and MMM is the offset @@ -282,11 +283,11 @@ ** search for a matching section in the source file. When a match ** is found, a copy command is added to the delta. An effort is ** made to extend the matching section to regions that come before ** and after the 16-byte hash window. A copy command is only issued ** if the result would use less space that just quoting the text -** literally. Literal text is added to the delta for sections that +** literally. Literal text is added to the delta for sections that ** do not match or which can not be encoded efficiently using copy ** commands. */ int delta_create( const char *zSrc, /* The source or pattern file */ @@ -355,13 +356,13 @@ hv = hash_32bit(&h) % nHash; DEBUG2( printf("LOOKING: %4d [%s]\n", base+i, print16(&zOut[base+i])); ) iBlock = landmark[hv]; while( iBlock>=0 && (limit--)>0 ){ /* - ** The hash window has identified a potential match against + ** The hash window has identified a potential match against ** landmark block iBlock. But we need to investigate further. - ** + ** ** Look for a region in zOut that matches zSrc. Anchor the search ** at zSrc[iSrc] and zOut[base+i]. Do not include anything prior to ** zOut[base] or after zOut[outLen] nor anything after zSrc[srcLen]. ** ** Set cnt equal to the length of the match and set ofst so that @@ -466,17 +467,17 @@ zDelta += lenOut-base; } /* Output the final checksum record. */ putInt(checksum(zOut, lenOut), &zDelta); *(zDelta++) = ';'; - free(collide); - return zDelta - zOrigDelta; + fossil_free(collide); + return zDelta - zOrigDelta; } /* ** Return the size (in bytes) of the output from applying -** a delta. +** a delta. ** ** This routine is provided so that an procedure that is able ** to call delta_apply() can learn how much space is required ** for the output and hence allocate nor more space that is really ** needed. Index: src/deltacmd.c ================================================================== --- src/deltacmd.c +++ src/deltacmd.c @@ -17,11 +17,11 @@ ** ** This module implements the interface to the delta generator. */ #include "config.h" #include "deltacmd.h" - + /* ** Create a delta that describes the change from pOriginal to pTarget ** and put that delta in pDelta. The pDelta blob is assumed to be ** uninitialized. */ Index: src/descendants.c ================================================================== --- src/descendants.c +++ src/descendants.c @@ -30,11 +30,11 @@ ** check-in iBase. ** ** A "leaf" is a check-in that has no children in the same branch. ** There is a separate permanent table LEAF that contains all leaves ** in the tree. This routine is used to compute a subset of that -** table consisting of leaves that are descended from a single checkin. +** table consisting of leaves that are descended from a single check-in. ** ** The closeMode flag determines behavior associated with the "closed" ** tag: ** ** closeMode==0 Show all leaves regardless of the "closed" tag. @@ -130,10 +130,15 @@ db_finalize(&ins); db_finalize(&isBr); db_finalize(&q1); bag_clear(&pending); bag_clear(&seen); + }else{ + db_multi_exec( + "INSERT INTO leaves" + " SELECT leaf.rid FROM leaf" + ); } if( closeMode==1 ){ db_multi_exec( "DELETE FROM leaves WHERE rid IN" " (SELECT leaves.rid FROM leaves, tagxref" @@ -157,42 +162,25 @@ /* ** Load the record ID rid and up to N-1 closest ancestors into ** the "ok" table. */ void compute_ancestors(int rid, int N, int directOnly){ - Bag seen; - PQueue queue; - Stmt ins; - Stmt q; - bag_init(&seen); - pqueuex_init(&queue); - bag_insert(&seen, rid); - pqueuex_insert(&queue, rid, 0.0, 0); - db_prepare(&ins, "INSERT OR IGNORE INTO ok VALUES(:rid)"); - db_prepare(&q, - "SELECT a.pid, b.mtime FROM plink a LEFT JOIN plink b ON b.cid=a.pid" - " WHERE a.cid=:rid %s", - directOnly ? " AND a.isprim" : "" + db_multi_exec( + "WITH RECURSIVE " + " ancestor(rid, mtime) AS (" + " SELECT %d, mtime FROM event WHERE objid=%d " + " UNION " + " SELECT plink.pid, event.mtime" + " FROM ancestor, plink, event" + " WHERE plink.cid=ancestor.rid" + " AND event.objid=plink.pid %s" + " ORDER BY mtime DESC LIMIT %d" + " )" + "INSERT INTO ok" + " SELECT rid FROM ancestor;", + rid, rid, directOnly ? "AND plink.isPrim" : "", N ); - while( (N--)>0 && (rid = pqueuex_extract(&queue, 0))!=0 ){ - db_bind_int(&ins, ":rid", rid); - db_step(&ins); - db_reset(&ins); - db_bind_int(&q, ":rid", rid); - while( db_step(&q)==SQLITE_ROW ){ - int pid = db_column_int(&q, 0); - double mtime = db_column_double(&q, 1); - if( bag_insert(&seen, pid) ){ - pqueuex_insert(&queue, pid, -mtime, 0); - } - } - db_reset(&q); - } - bag_clear(&seen); - pqueuex_clear(&queue); - db_finalize(&ins); - db_finalize(&q); } /* ** Compute up to N direct ancestors (merge ancestors do not count) ** for the check-in rid and put them in a table named "ancestor". @@ -301,25 +289,42 @@ } /* ** COMMAND: descendants* ** -** Usage: %fossil descendants ?BASELINE-ID? ?OPTIONS? +** Usage: %fossil descendants ?CHECKIN? ?OPTIONS? ** -** Find all leaf descendants of the baseline specified or if the argument -** is omitted, of the baseline currently checked out. +** Find all leaf descendants of the check-in specified or if the argument +** is omitted, of the check-in currently checked out. ** ** Options: ** -R|--repository FILE Extract info from repository FILE +** -W|--width <num> Width of lines (default is to auto-detect). +** Must be >20 or 0 (= no limit, resulting in a +** single line per entry). ** ** See also: finfo, info, leaves */ void descendants_cmd(void){ Stmt q; - int base; + int base, width; + const char *zWidth; db_find_and_open_repository(0,0); + zWidth = find_option("width","W",1); + if( zWidth ){ + width = atoi(zWidth); + if( (width!=0) && (width<=20) ){ + fossil_fatal("-W|--width value must be >20 or 0"); + } + }else{ + width = -1; + } + + /* We should be done with options.. */ + verify_all_options(); + if( g.argc==2 ){ base = db_lget_int("checkout", 0); }else{ base = name_to_typed_rid(g.argv[2], "ci"); } @@ -329,11 +334,11 @@ "%s" " AND event.objid IN (SELECT rid FROM leaves)" " ORDER BY event.mtime DESC", timeline_query_for_tty() ); - print_timeline(&q, 20, 0); + print_timeline(&q, 0, width, 0); db_finalize(&q); } /* ** COMMAND: leaves* @@ -346,14 +351,17 @@ ** ** The --recompute flag causes the content of the "leaf" table in the ** repository database to be recomputed. ** ** Options: -** -a|--all show ALL leaves -** -c|--closed show only closed leaves -** --bybranch order output by branch name -** --recompute recompute the "leaf" table in the repository DB +** -a|--all show ALL leaves +** -c|--closed show only closed leaves +** --bybranch order output by branch name +** --recompute recompute the "leaf" table in the repository DB +** -W|--width <num> Width of lines (default is to auto-detect). Must be +** >39 or 0 (= no limit, resulting in a single line per +** entry). ** ** See also: descendants, finfo, info, branch */ void leaves_cmd(void){ Stmt q; @@ -360,30 +368,43 @@ Blob sql; int showAll = find_option("all", "a", 0)!=0; int showClosed = find_option("closed", "c", 0)!=0; int recomputeFlag = find_option("recompute",0,0)!=0; int byBranch = find_option("bybranch",0,0)!=0; + const char *zWidth = find_option("width","W",1); char *zLastBr = 0; - int n; + int n, width; char zLineNo[10]; + if( zWidth ){ + width = atoi(zWidth); + if( (width!=0) && (width<=39) ){ + fossil_fatal("-W|--width value must be >39 or 0"); + } + }else{ + width = -1; + } db_find_and_open_repository(0,0); + + /* We should be done with options.. */ + verify_all_options(); + if( recomputeFlag ) leaf_rebuild(); blob_zero(&sql); blob_append(&sql, timeline_query_for_tty(), -1); - blob_appendf(&sql, " AND blob.rid IN leaf"); + blob_append_sql(&sql, " AND blob.rid IN leaf"); if( showClosed ){ - blob_appendf(&sql," AND %z", leaf_is_closed_sql("blob.rid")); + blob_append_sql(&sql," AND %z", leaf_is_closed_sql("blob.rid")); }else if( !showAll ){ - blob_appendf(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid")); + blob_append_sql(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid")); } if( byBranch ){ db_prepare(&q, "%s ORDER BY nullif(branch,'trunk') COLLATE nocase," " event.mtime DESC", - blob_str(&sql)); + blob_sql_text(&sql)); }else{ - db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_str(&sql)); + db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql)); } blob_reset(&sql); n = 0; while( db_step(&q)==SQLITE_ROW ){ const char *zId = db_column_text(&q, 1); @@ -398,12 +419,12 @@ zLastBr = fossil_strdup(zBr); } n++; sqlite3_snprintf(sizeof(zLineNo), zLineNo, "(%d)", n); fossil_print("%6s ", zLineNo); - z = mprintf("%s [%.10s] %s", zDate, zId, zCom); - comment_print(z, 7, 79); + z = mprintf("%s [%S] %s", zDate, zId, zCom); + comment_print(z, zCom, 7, width, g.comFmtFlags); fossil_free(z); } fossil_free(zLastBr); db_finalize(&q); } @@ -418,11 +439,11 @@ Stmt q; int showAll = P("all")!=0; int showClosed = P("closed")!=0; login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } if( !showAll ){ style_submenu_element("All", "All", "leaves?all"); } if( !showClosed ){ @@ -431,10 +452,11 @@ if( showClosed || showAll ){ style_submenu_element("Open", "Open", "leaves"); } style_header("Leaves"); login_anonymous_available(); +#if 0 style_sidebox_begin("Nomenclature:", "33%"); @ <ol> @ <li> A <div class="sideboxDescribed">leaf</div> @ is a check-in with no descendants in the same branch.</li> @ <li> An <div class="sideboxDescribed">open leaf</div> @@ -443,10 +465,11 @@ @ <li> A <div class="sideboxDescribed">closed leaf</div> @ has a "closed" tag and is thus assumed to @ be historical and no longer in active use.</li> @ </ol> style_sidebox_end(); +#endif if( showAll ){ @ <h1>All leaves, both open and closed:</h1> }else if( showClosed ){ @ <h1>Closed leaves:</h1> @@ -453,27 +476,21 @@ }else{ @ <h1>Open leaves:</h1> } blob_zero(&sql); blob_append(&sql, timeline_query_for_www(), -1); - blob_appendf(&sql, " AND blob.rid IN leaf"); + blob_append_sql(&sql, " AND blob.rid IN leaf"); if( showClosed ){ - blob_appendf(&sql," AND %z", leaf_is_closed_sql("blob.rid")); + blob_append_sql(&sql," AND %z", leaf_is_closed_sql("blob.rid")); }else if( !showAll ){ - blob_appendf(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid")); + blob_append_sql(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid")); } - db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_str(&sql)); + db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql)); blob_reset(&sql); - www_print_timeline(&q, TIMELINE_LEAFONLY, 0, 0, 0); + www_print_timeline(&q, TIMELINE_LEAFONLY, 0, 0, 0, 0); db_finalize(&q); @ <br /> - @ <script type="text/JavaScript"> - @ function xin(id){ - @ } - @ function xout(id){ - @ } - @ </script> style_footer(); } #if INTERFACE /* Flag parameters to compute_uses_file() */ @@ -493,11 +510,11 @@ Stmt q; int rid; bag_init(&seen); bag_init(&pending); - db_prepare(&ins, "INSERT OR IGNORE INTO \"%s\" VALUES(:rid)", zTab); + db_prepare(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES(:rid)", zTab); db_prepare(&q, "SELECT mid FROM mlink WHERE fid=%d", fid); while( db_step(&q)==SQLITE_ROW ){ int mid = db_column_int(&q, 0); bag_insert(&pending, mid); bag_insert(&seen, mid); @@ -516,11 +533,11 @@ db_step(&ins); db_reset(&ins); } } db_finalize(&q); - db_prepare(&q, "SELECT cid FROM plink WHERE pid=:rid"); + db_prepare(&q, "SELECT cid FROM plink WHERE pid=:rid AND isprim"); while( (rid = bag_first(&pending))!=0 ){ bag_remove(&pending, rid); db_bind_int(&q, ":rid", rid); while( db_step(&q)==SQLITE_ROW ){ Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -29,21 +29,21 @@ ** of the diff output. */ #define DIFF_CONTEXT_MASK ((u64)0x0000ffff) /* Lines of context. Default if 0 */ #define DIFF_WIDTH_MASK ((u64)0x00ff0000) /* side-by-side column width */ #define DIFF_IGNORE_EOLWS ((u64)0x01000000) /* Ignore end-of-line whitespace */ -#define DIFF_SIDEBYSIDE ((u64)0x02000000) /* Generate a side-by-side diff */ -#define DIFF_VERBOSE ((u64)0x04000000) /* Missing shown as empty files */ -#define DIFF_BRIEF ((u64)0x08000000) /* Show filenames only */ -#define DIFF_INLINE ((u64)0x00000000) /* Inline (not side-by-side) diff */ -#define DIFF_HTML ((u64)0x10000000) /* Render for HTML */ -#define DIFF_LINENO ((u64)0x20000000) /* Show line numbers */ -#define DIFF_WS_WARNING ((u64)0x40000000) /* Warn about whitespace */ +#define DIFF_IGNORE_ALLWS ((u64)0x03000000) /* Ignore all whitespace */ +#define DIFF_SIDEBYSIDE ((u64)0x04000000) /* Generate a side-by-side diff */ +#define DIFF_VERBOSE ((u64)0x08000000) /* Missing shown as empty files */ +#define DIFF_BRIEF ((u64)0x10000000) /* Show filenames only */ +#define DIFF_HTML ((u64)0x20000000) /* Render for HTML */ +#define DIFF_LINENO ((u64)0x40000000) /* Show line numbers */ #define DIFF_NOOPT (((u64)0x01)<<32) /* Suppress optimizations (debug) */ #define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */ #define DIFF_CONTEXT_EX (((u64)0x04)<<32) /* Use context even if zero */ #define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */ +#define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */ /* ** These error messages are shared in multiple locations. They are defined ** here for consistency. */ @@ -54,10 +54,13 @@ "cannot compute difference between symlink and regular file\n" #define DIFF_TOO_MANY_CHANGES \ "more than 10,000 changes\n" +#define DIFF_WHITESPACE_ONLY \ + "whitespace changes only\n" + /* ** Maximum length of a line in a text file, in bytes. (2**13 = 8192 bytes) */ #define LENGTH_MASK_SZ 13 #define LENGTH_MASK ((1<<LENGTH_MASK_SZ)-1) @@ -73,10 +76,12 @@ */ typedef struct DLine DLine; struct DLine { const char *z; /* The text of the line */ unsigned int h; /* Hash of the line */ + unsigned short indent; /* Indent of the line. Only !=0 with -w/-Z option */ + unsigned short n; /* number of bytes */ unsigned int iNext; /* 1+(Index of next line with same the same hash) */ /* an array of DLine elements serves two purposes. The fields ** above are one per line of input text. But each entry is also ** a bucket in a hash table, as follows: */ @@ -84,11 +89,11 @@ }; /* ** Length of a dline */ -#define LENGTH(X) ((X)->h & LENGTH_MASK) +#define LENGTH(X) ((X)->n) /* ** A context for running a raw diff. ** ** The aEdit[] array describes the raw diff. Each triple of integers in @@ -108,10 +113,11 @@ int nEditAlloc; /* Space allocated for aEdit[] */ DLine *aFrom; /* File on left side of the diff */ int nFrom; /* Number of lines in aFrom[] */ DLine *aTo; /* File on right side of the diff */ int nTo; /* Number of lines in aTo[] */ + int (*same_fn)(const DLine *, const DLine *); /* Function to be used for comparing */ }; /* ** Return an array of DLine objects containing a pointer to the ** start of each line and a hash of that line. The lower @@ -125,12 +131,12 @@ ** too long. ** ** Profiling show that in most cases this routine consumes the bulk of ** the CPU time on a diff. */ -static DLine *break_into_lines(const char *z, int n, int *pnLine, int ignoreWS){ - int nLine, i, j, k, x; +static DLine *break_into_lines(const char *z, int n, int *pnLine, u64 diffFlags){ + int nLine, i, j, k, s, x; unsigned int h, h2; DLine *a; /* Count the number of lines. Allocate space to hold ** the returned array. @@ -158,18 +164,39 @@ return a; } /* Fill in the array */ for(i=0; i<nLine; i++){ - a[i].z = z; for(j=0; z[j] && z[j]!='\n'; j++){} + a[i].z = z; k = j; - while( ignoreWS && k>0 && fossil_isspace(z[k-1]) ){ k--; } - for(h=0, x=0; x<=k; x++){ - h = h ^ (h<<2) ^ z[x]; + if( diffFlags & DIFF_STRIP_EOLCR ){ + if( k>0 && z[k-1]=='\r' ){ k--; } + } + a[i].n = k; + s = 0; + if( diffFlags & DIFF_IGNORE_EOLWS ){ + while( k>0 && fossil_isspace(z[k-1]) ){ k--; } + } + if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){ + int numws = 0; + while( s<k && fossil_isspace(z[s]) ){ s++; } + for(h=0, x=s; x<k; x++){ + if( fossil_isspace(z[x]) ){ + ++numws; + }else{ + h = h ^ (h<<2) ^ z[x]; + } + } + k -= numws; + }else{ + for(h=0, x=s; x<k; x++){ + h = h ^ (h<<2) ^ z[x]; + } } - a[i].h = h = (h<<LENGTH_MASK_SZ) | k;; + a[i].indent = s; + a[i].h = h = (h<<LENGTH_MASK_SZ) | (k-s); h2 = h % nLine; a[i].iNext = a[h2].iHash; a[h2].iHash = i+1; z += j+1; } @@ -180,12 +207,31 @@ } /* ** Return true if two DLine elements are identical. */ -static int same_dline(DLine *pA, DLine *pB){ - return pA->h==pB->h && memcmp(pA->z,pB->z,pA->h & LENGTH_MASK)==0; +static int same_dline(const DLine *pA, const DLine *pB){ + return pA->h==pB->h && memcmp(pA->z,pB->z, pA->h&LENGTH_MASK)==0; +} + +/* +** Return true if two DLine elements are identical, ignoring +** all whitespace. The indent field of pA/pB already points +** to the first non-space character in the string. +*/ + +static int same_dline_ignore_allws(const DLine *pA, const DLine *pB){ + int a = pA->indent, b = pB->indent; + if( pA->h==pB->h ){ + while( a<pA->n || b<pB->n ){ + if( a<pA->n && b<pB->n && pA->z[a++] != pB->z[b++] ) return 0; + while( a<pA->n && fossil_isspace(pA->z[a])) ++a; + while( b<pB->n && fossil_isspace(pB->z[b])) ++b; + } + return pA->n-a == pB->n-b; + } + return 0; } /* ** Return true if the regular expression *pRe matches any of the ** N dlines @@ -221,16 +267,16 @@ }else if( cPrefix=='+' ){ blob_append(pOut, "<span class=\"diffadd\">", -1); }else if( cPrefix=='-' ){ blob_append(pOut, "<span class=\"diffrm\">", -1); } - htmlize_to_blob(pOut, pLine->z, (pLine->h & LENGTH_MASK)); + htmlize_to_blob(pOut, pLine->z, pLine->n); if( cPrefix!=' ' ){ blob_append(pOut, "</span>", -1); } }else{ - blob_append(pOut, pLine->z, pLine->h & LENGTH_MASK); + blob_append(pOut, pLine->z, pLine->n); } blob_append(pOut, "\n", 1); } /* @@ -402,11 +448,11 @@ b += m; if( i<nr-1 ){ m = R[r+i*3+3]; for(j=0; j<m; j++){ if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html); - appendDiffLine(pOut, ' ', &B[b+j], html, 0); + appendDiffLine(pOut, ' ', &A[a+j], html, 0); } b += m; a += m; } } @@ -415,11 +461,11 @@ assert( nr==i ); m = R[r+nr*3]; if( m>nContext ) m = nContext; for(j=0; j<m; j++){ if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html); - appendDiffLine(pOut, ' ', &B[b+j], html, 0); + appendDiffLine(pOut, ' ', &A[a+j], html, 0); } } } /* @@ -476,11 +522,11 @@ ** This comment contains multibyte unicode characters (ü, Æ, ð) in order ** to test the ability of the diff code to handle such characters. */ static void sbsWriteText(SbsLine *p, DLine *pLine, int col){ Blob *pCol = p->apCols[col]; - int n = pLine->h & LENGTH_MASK; + int n = pLine->n; int i; /* Number of input characters consumed */ int k; /* Cursor position */ int needEndSpan = 0; const char *zIn = pLine->z; int w = p->width; @@ -548,11 +594,11 @@ "<td><div class=\"diff%scol\">\n" "<pre>\n" "%s" "</pre>\n" "</div></td>\n", - col % 3 ? (col == SBS_MKR ? "mkr" : "txt") : "ln", + (col % 3) ? (col == SBS_MKR ? "mkr" : "txt") : "ln", blob_str(pCol) ); } /* @@ -720,13 +766,13 @@ int aLCS[4]; /* Bounds of common middle segment */ static const char zClassRm[] = "<span class=\"diffrm\">"; static const char zClassAdd[] = "<span class=\"diffadd\">"; static const char zClassChng[] = "<span class=\"diffchng\">"; - nLeft = pLeft->h & LENGTH_MASK; + nLeft = pLeft->n; zLeft = pLeft->z; - nRight = pRight->h & LENGTH_MASK; + nRight = pRight->n; zRight = pRight->z; nShort = nLeft<nRight ? nLeft : nRight; nPrefix = 0; while( nPrefix<nShort && zLeft[nPrefix]==zRight[nPrefix] ){ @@ -803,11 +849,11 @@ p->zStart = zClassChng; } p->iStart2 = nPrefix + aLCS[1]; p->iEnd2 = nLeft - nSuffix; p->zStart2 = aLCS[3]==nRightDiff ? zClassRm : zClassChng; - sbsSimplifyLine(p, zLeft+nPrefix); + sbsSimplifyLine(p, zLeft); sbsWriteText(p, pLeft, SBS_TXTA); sbsWriteMarker(p, " | ", "|"); sbsWriteLineno(p, lnRight, SBS_LNB); p->iStart = nPrefix; p->iEnd = nPrefix + aLCS[2]; @@ -818,11 +864,11 @@ p->zStart = zClassChng; } p->iStart2 = nPrefix + aLCS[3]; p->iEnd2 = nRight - nSuffix; p->zStart2 = aLCS[1]==nLeftDiff ? zClassAdd : zClassChng; - sbsSimplifyLine(p, zRight+nPrefix); + sbsSimplifyLine(p, zRight); sbsWriteText(p, pRight, SBS_TXTB); return; } /* If all else fails, show a single big change between left and right */ @@ -868,12 +914,12 @@ unsigned char aFirst[256]; /* aFirst[X] = index in zB[] of first char X */ unsigned char aNext[252]; /* aNext[i] = index in zB[] of next zB[i] char */ zA = pA->z; zB = pB->z; - nA = pA->h & LENGTH_MASK; - nB = pB->h & LENGTH_MASK; + nA = pA->n; + nB = pB->n; while( nA>0 && fossil_isspace(zA[0]) ){ nA--; zA++; } while( nA>0 && fossil_isspace(zA[nA-1]) ){ nA--; } while( nB>0 && fossil_isspace(zB[0]) ){ nB--; zB++; } while( nB>0 && fossil_isspace(zB[nB-1]) ){ nB--; } if( nA>250 ) nA = 250; @@ -1305,11 +1351,11 @@ sbsWriteMarker(&s, " ", ""); sbsWriteLineno(&s, b+j, SBS_LNB); sbsWriteText(&s, &B[b+j], SBS_TXTB); } } - + if( s.escHtml && blob_size(s.apCols[SBS_LNA])>0 ){ blob_append(pOut, "<table class=\"sbsdiffcols\"><tr>\n", -1); for(i=SBS_LNA; i<=SBS_TXTB; i++){ sbsWriteColumn(pOut, s.apCols[i], i); blob_reset(s.apCols[i]); @@ -1337,16 +1383,16 @@ int iSXb = iS1; /* Best match so far */ int iSYb = iS2; /* Best match so far */ for(i=iS1; i<iE1-mxLength; i++){ for(j=iS2; j<iE2-mxLength; j++){ - if( !same_dline(&p->aFrom[i], &p->aTo[j]) ) continue; - if( mxLength && !same_dline(&p->aFrom[i+mxLength], &p->aTo[j+mxLength]) ){ + if( !p->same_fn(&p->aFrom[i], &p->aTo[j]) ) continue; + if( mxLength && !p->same_fn(&p->aFrom[i+mxLength], &p->aTo[j+mxLength]) ){ continue; } k = 1; - while( i+k<iE1 && j+k<iE2 && same_dline(&p->aFrom[i+k],&p->aTo[j+k]) ){ + while( i+k<iE1 && j+k<iE2 && p->same_fn(&p->aFrom[i+k],&p->aTo[j+k]) ){ k++; } if( k>mxLength ){ iSXb = i; iSYb = j; @@ -1408,11 +1454,11 @@ mid = (iE1 + iS1)/2; for(i=iS1; i<iE1; i++){ int limit = 0; j = p->aTo[p->aFrom[i].h % p->nTo].iHash; while( j>0 - && (j-1<iS2 || j>=iE2 || !same_dline(&p->aFrom[i], &p->aTo[j-1])) + && (j-1<iS2 || j>=iE2 || !p->same_fn(&p->aFrom[i], &p->aTo[j-1])) ){ if( limit++ > 10 ){ j = 0; break; } @@ -1425,19 +1471,19 @@ iSX = i; iSY = j-1; pA = &p->aFrom[iSX-1]; pB = &p->aTo[iSY-1]; n = minInt(iSX-iS1, iSY-iS2); - for(k=0; k<n && same_dline(pA,pB); k++, pA--, pB--){} + for(k=0; k<n && p->same_fn(pA,pB); k++, pA--, pB--){} iSX -= k; iSY -= k; iEX = i+1; iEY = j; pA = &p->aFrom[iEX]; pB = &p->aTo[iEY]; n = minInt(iE1-iEX, iE2-iEY); - for(k=0; k<n && same_dline(pA,pB); k++, pA++, pB++){} + for(k=0; k<n && p->same_fn(pA,pB); k++, pA++, pB++){} iEX += k; iEY += k; skew = (iSX-iS1) - (iSY-iS2); if( skew<0 ) skew = -skew; dist = (iSX+iEX)/2 - mid; @@ -1574,16 +1620,16 @@ int mnE, iS, iE1, iE2; /* Carve off the common header and footer */ iE1 = p->nFrom; iE2 = p->nTo; - while( iE1>0 && iE2>0 && same_dline(&p->aFrom[iE1-1], &p->aTo[iE2-1]) ){ + while( iE1>0 && iE2>0 && p->same_fn(&p->aFrom[iE1-1], &p->aTo[iE2-1]) ){ iE1--; iE2--; } mnE = iE1<iE2 ? iE1 : iE2; - for(iS=0; iS<mnE && same_dline(&p->aFrom[iS],&p->aTo[iS]); iS++){} + for(iS=0; iS<mnE && p->same_fn(&p->aFrom[iS],&p->aTo[iS]); iS++){} /* do the difference */ if( iS>0 ){ appendTriple(p, iS, 0, 0); } @@ -1648,11 +1694,11 @@ /* Shift insertions toward the beginning of the file */ while( cpy>0 && del==0 && ins>0 ){ DLine *pTop = &p->aFrom[lnFrom-1]; /* Line before start of insert */ DLine *pBtm = &p->aTo[lnTo+ins-1]; /* Last line inserted */ - if( same_dline(pTop, pBtm)==0 ) break; + if( p->same_fn(pTop, pBtm)==0 ) break; if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break; lnFrom--; lnTo--; p->aEdit[r]--; p->aEdit[r+3]++; @@ -1661,11 +1707,11 @@ /* Shift insertions toward the end of the file */ while( r+3<p->nEdit && p->aEdit[r+3]>0 && del==0 && ins>0 ){ DLine *pTop = &p->aTo[lnTo]; /* First line inserted */ DLine *pBtm = &p->aTo[lnTo+ins]; /* First line past end of insert */ - if( same_dline(pTop, pBtm)==0 ) break; + if( p->same_fn(pTop, pBtm)==0 ) break; if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop+1)+LENGTH(pBtm) ) break; lnFrom++; lnTo++; p->aEdit[r]++; p->aEdit[r+3]--; @@ -1674,11 +1720,11 @@ /* Shift deletions toward the beginning of the file */ while( cpy>0 && del>0 && ins==0 ){ DLine *pTop = &p->aFrom[lnFrom-1]; /* Line before start of delete */ DLine *pBtm = &p->aFrom[lnFrom+del-1]; /* Last line deleted */ - if( same_dline(pTop, pBtm)==0 ) break; + if( p->same_fn(pTop, pBtm)==0 ) break; if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break; lnFrom--; lnTo--; p->aEdit[r]--; p->aEdit[r+3]++; @@ -1687,11 +1733,11 @@ /* Shift deletions toward the end of the file */ while( r+3<p->nEdit && p->aEdit[r+3]>0 && del>0 && ins==0 ){ DLine *pTop = &p->aFrom[lnFrom]; /* First line deleted */ DLine *pBtm = &p->aFrom[lnFrom+del]; /* First line past end of delete */ - if( same_dline(pTop, pBtm)==0 ) break; + if( p->same_fn(pTop, pBtm)==0 ) break; if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop)+LENGTH(pBtm) ) break; lnFrom++; lnTo++; p->aEdit[r]++; p->aEdit[r+3]--; @@ -1753,26 +1799,33 @@ Blob *pB_Blob, /* TO file */ Blob *pOut, /* Write diff here if not NULL */ ReCompiled *pRe, /* Only output changes where this Regexp matches */ u64 diffFlags /* DIFF_* flags defined above */ ){ - int ignoreEolWs; /* Ignore whitespace at the end of lines */ + int ignoreWs; /* Ignore whitespace */ DContext c; if( diffFlags & DIFF_INVERT ){ Blob *pTemp = pA_Blob; pA_Blob = pB_Blob; pB_Blob = pTemp; } - ignoreEolWs = (diffFlags & DIFF_IGNORE_EOLWS)!=0; + ignoreWs = (diffFlags & DIFF_IGNORE_ALLWS)!=0; + blob_to_utf8_no_bom(pA_Blob, 0); + blob_to_utf8_no_bom(pB_Blob, 0); /* Prepare the input files */ memset(&c, 0, sizeof(c)); + if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){ + c.same_fn = same_dline_ignore_allws; + }else{ + c.same_fn = same_dline; + } c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob), - &c.nFrom, ignoreEolWs); + &c.nFrom, diffFlags); c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), - &c.nTo, ignoreEolWs); + &c.nTo, diffFlags); if( c.aFrom==0 || c.aTo==0 ){ fossil_free(c.aFrom); fossil_free(c.aTo); if( pOut ){ diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, diffFlags); @@ -1780,20 +1833,27 @@ return 0; } /* Compute the difference */ diff_all(&c); + if( ignoreWs && c.nEdit==6 && c.aEdit[1]==0 && c.aEdit[2]==0 ){ + fossil_free(c.aFrom); + fossil_free(c.aTo); + fossil_free(c.aEdit); + if( pOut ) diff_errmsg(pOut, DIFF_WHITESPACE_ONLY, diffFlags); + return 0; + } if( (diffFlags & DIFF_NOTTOOBIG)!=0 ){ int i, m, n; int *a = c.aEdit; int mx = c.nEdit; for(i=m=n=0; i<mx; i+=3){ m += a[i]; n += a[i+1]+a[i+2]; } if( n>10000 ){ fossil_free(c.aFrom); fossil_free(c.aTo); fossil_free(c.aEdit); - diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, diffFlags); + if( pOut ) diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, diffFlags); return 0; } } if( (diffFlags & DIFF_NOOPT)==0 ){ diff_optimize(&c); @@ -1822,24 +1882,36 @@ /* ** Process diff-related command-line options and return an appropriate ** "diffFlags" integer. ** -** --brief Show filenames only DIFF_BRIEF -** --context|-c N N lines of context. DIFF_CONTEXT_MASK -** --html Format for HTML DIFF_HTML -** --invert Invert the diff DIFF_INVERT -** --linenum|-n Show line numbers DIFF_LINENO -** --noopt Disable optimization DIFF_NOOPT -** --side-by-side|-y Side-by-side diff. DIFF_SIDEBYSIDE -** --unified Unified diff. ~DIFF_SIDEBYSIDE -** --width|-W N N character lines. DIFF_WIDTH_MASK +** --brief Show filenames only DIFF_BRIEF +** -c|--context N N lines of context. DIFF_CONTEXT_MASK +** --html Format for HTML DIFF_HTML +** --invert Invert the diff DIFF_INVERT +** -n|--linenum Show line numbers DIFF_LINENO +** --noopt Disable optimization DIFF_NOOPT +** --strip-trailing-cr Strip trailing CR DIFF_STRIP_EOLCR +** --unified Unified diff. ~DIFF_SIDEBYSIDE +** -w|--ignore-all-space Ignore all whitespaces DIFF_IGNORE_ALLWS +** -W|--width N N character lines. DIFF_WIDTH_MASK +** -y|--side-by-side Side-by-side diff. DIFF_SIDEBYSIDE +** -Z|--ignore-trailing-space Ignore eol-whitespaces DIFF_IGNORE_EOLWS */ u64 diff_options(void){ u64 diffFlags = 0; const char *z; int f; + if( find_option("ignore-trailing-space","Z",0)!=0 ){ + diffFlags = DIFF_IGNORE_EOLWS; + } + if( find_option("ignore-all-space","w",0)!=0 ){ + diffFlags = DIFF_IGNORE_ALLWS; /* stronger than DIFF_IGNORE_EOLWS */ + } + if( find_option("strip-trailing-cr",0,0)!=0 ){ + diffFlags |= DIFF_STRIP_EOLCR; + } if( find_option("side-by-side","y",0)!=0 ) diffFlags |= DIFF_SIDEBYSIDE; if( find_option("unified",0,0)!=0 ) diffFlags &= ~DIFF_SIDEBYSIDE; if( (z = find_option("context","c",1))!=0 && (f = atoi(z))>=0 ){ if( f > DIFF_CONTEXT_MASK ) f = DIFF_CONTEXT_MASK; diffFlags |= f + DIFF_CONTEXT_EX; @@ -1928,11 +2000,11 @@ typedef struct Annotator Annotator; struct Annotator { DContext c; /* The diff-engine context */ struct AnnLine { /* Lines of the original files... */ const char *z; /* The text of the line */ - short int n; /* Number of bytes (omitting trailing space and \n) */ + short int n; /* Number of bytes (omitting trailing \n) */ short int iVers; /* Level at which tag was set */ } *aOrig; int nOrig; /* Number of elements in aOrig[] */ int nVers; /* Number of versions analyzed */ int bLimit; /* True if the iLimit was reached */ @@ -1939,10 +2011,11 @@ struct AnnVers { const char *zFUuid; /* File being analyzed */ const char *zMUuid; /* Check-in containing the file */ const char *zDate; /* Date of the check-in */ const char *zBgColor; /* Suggested background color */ + const char *zUser; /* Name of user who did the check-in */ unsigned cnt; /* Number of lines contributed by this check-in */ } *aVers; /* For each check-in analyzed */ char **azVers; /* Names of versions analyzed */ }; @@ -1949,22 +2022,28 @@ /* ** Initialize the annotation process by specifying the file that is ** to be annotated. The annotator takes control of the input Blob and ** will release it when it is finished with it. */ -static int annotation_start(Annotator *p, Blob *pInput){ +static int annotation_start(Annotator *p, Blob *pInput, u64 diffFlags){ int i; memset(p, 0, sizeof(*p)); - p->c.aTo = break_into_lines(blob_str(pInput), blob_size(pInput),&p->c.nTo,1); + if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){ + p->c.same_fn = same_dline_ignore_allws; + }else{ + p->c.same_fn = same_dline; + } + p->c.aTo = break_into_lines(blob_str(pInput), blob_size(pInput),&p->c.nTo, + diffFlags); if( p->c.aTo==0 ){ return 1; } p->aOrig = fossil_malloc( sizeof(p->aOrig[0])*p->c.nTo ); for(i=0; i<p->c.nTo; i++){ p->aOrig[i].z = p->c.aTo[i].z; - p->aOrig[i].n = p->c.aTo[i].h & LENGTH_MASK; + p->aOrig[i].n = p->c.aTo[i].n; p->aOrig[i].iVers = -1; } p->nOrig = p->c.nTo; return 0; } @@ -1974,17 +2053,17 @@ ** being annotated. Do another step of the annotation. Return true ** if additional annotation is required. zPName is the tag to insert ** on each line of the file being annotated that was contributed by ** pParent. Memory to hold zPName is leaked. */ -static int annotation_step(Annotator *p, Blob *pParent, int iVers){ +static int annotation_step(Annotator *p, Blob *pParent, int iVers, u64 diffFlags){ int i, j; int lnTo; /* Prepare the parent file to be diffed */ p->c.aFrom = break_into_lines(blob_str(pParent), blob_size(pParent), - &p->c.nFrom, 1); + &p->c.nFrom, diffFlags); if( p->c.aFrom==0 ){ return 1; } /* Compute the differences going from pParent to the file being @@ -2017,13 +2096,13 @@ /* Return no errors */ return 0; } -/* Annotation flags */ -#define ANN_FILE_VERS 0x01 /* Show file vers rather than commit vers */ -#define ANN_FILE_ANCEST 0x02 /* Prefer check-ins in the ANCESTOR table */ +/* Annotation flags (any DIFF flag can be used as Annotation flag as well) */ +#define ANN_FILE_VERS (((u64)0x20)<<32) /* Show file vers rather than commit vers */ +#define ANN_FILE_ANCEST (((u64)0x40)<<32) /* Prefer check-ins in the ANCESTOR table */ /* ** Compute a complete annotation on a file. The file is identified ** by its filename number (filename.fnid) and the baseline in which ** it was checked in (mlink.mid). @@ -2031,11 +2110,11 @@ static void annotate_file( Annotator *p, /* The annotator */ int fnid, /* The name of the file to be annotated */ int mid, /* Use the version of the file in this check-in */ int iLimit, /* Limit the number of levels if greater than zero */ - int annFlags /* Flags to alter the annotation */ + u64 annFlags /* Flags to alter the annotation */ ){ Blob toAnnotate; /* Text of the final (mid) version of the file */ Blob step; /* Text of previous revision */ int rid; /* Artifact ID of the file being annotated */ Stmt q; /* Query returning all ancestor versions */ @@ -2049,11 +2128,12 @@ } if( !content_get(rid, &toAnnotate) ){ fossil_fatal("unable to retrieve content of artifact #%d", rid); } if( iLimit<=0 ) iLimit = 1000000000; - annotation_start(p, &toAnnotate); + blob_to_utf8_no_bom(&toAnnotate, 0); + annotation_start(p, &toAnnotate, annFlags); db_begin_transaction(); db_multi_exec( "CREATE TEMP TABLE IF NOT EXISTS vseen(rid INTEGER PRIMARY KEY);" "DELETE FROM vseen;" ); @@ -2061,10 +2141,11 @@ db_prepare(&ins, "INSERT OR IGNORE INTO vseen(rid) VALUES(:rid)"); db_prepare(&q, "SELECT (SELECT uuid FROM blob WHERE rid=mlink.fid)," " (SELECT uuid FROM blob WHERE rid=mlink.mid)," " date(event.mtime)," + " coalesce(event.euser,event.user)," " mlink.pid" " FROM mlink, event" " WHERE mlink.fid=:rid" " AND event.objid=mlink.mid" " AND mlink.pid NOT IN vseen" @@ -2074,18 +2155,20 @@ ); db_bind_int(&q, ":rid", rid); if( iLimit==0 ) iLimit = 1000000000; while( rid && iLimit>cnt && db_step(&q)==SQLITE_ROW ){ - int prevId = db_column_int(&q, 3); + int prevId = db_column_int(&q, 4); p->aVers = fossil_realloc(p->aVers, (p->nVers+1)*sizeof(p->aVers[0])); p->aVers[p->nVers].zFUuid = fossil_strdup(db_column_text(&q, 0)); p->aVers[p->nVers].zMUuid = fossil_strdup(db_column_text(&q, 1)); p->aVers[p->nVers].zDate = fossil_strdup(db_column_text(&q, 2)); + p->aVers[p->nVers].zUser = fossil_strdup(db_column_text(&q, 3)); if( p->nVers ){ content_get(rid, &step); - annotation_step(p, &step, p->nVers-1); + blob_to_utf8_no_bom(&step, 0); + annotation_step(p, &step, p->nVers-1, annFlags); blob_reset(&step); } p->nVers++; db_bind_int(&ins, ":rid", rid); db_step(&ins); @@ -2106,10 +2189,11 @@ */ unsigned gradient_color(unsigned c1, unsigned c2, int n, int i){ unsigned c; /* Result color */ unsigned x1, x2; if( i==0 || n==0 ) return c1; + else if(i>=n) return c2; x1 = (c1>>16)&0xff; x2 = (c2>>16)&0xff; c = (x1*(n-i) + x2*i)/n<<16 & 0xff0000; x1 = (c1>>8)&0xff; x2 = (c2>>8)&0xff; @@ -2120,10 +2204,12 @@ return c; } /* ** WEBPAGE: annotate +** WEBPAGE: blame +** WEBPAGE: praise ** ** Query parameters: ** ** checkin=ID The manifest ID at which to start the annotation ** filename=FILENAME The filename. @@ -2134,30 +2220,35 @@ void annotation_page(void){ int mid; int fnid; int i; int iLimit; /* Depth limit */ - int annFlags = ANN_FILE_ANCEST; + u64 annFlags = (ANN_FILE_ANCEST|DIFF_STRIP_EOLCR); int showLog = 0; /* True to display the log */ + int ignoreWs = 0; /* Ignore whitespace */ const char *zFilename; /* Name of file to annotate */ const char *zCI; /* The check-in containing zFilename */ Annotator ann; HQuery url; struct AnnVers *p; unsigned clr1, clr2, clr; + int bBlame = g.zPath[0]!='a';/* True for BLAME output. False for ANNOTATE. */ /* Gather query parameters */ showLog = atoi(PD("log","1")); login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } if( exclude_spiders("annotate") ) return; + load_control(); mid = name_to_typed_rid(PD("checkin","0"),"ci"); zFilename = P("filename"); fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename); if( mid==0 || fnid==0 ){ fossil_redirect_home(); } iLimit = atoi(PD("limit","20")); if( P("filevers") ) annFlags |= ANN_FILE_VERS; + ignoreWs = P("w")!=0; + if( ignoreWs ) annFlags |= DIFF_IGNORE_ALLWS; if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid) ){ fossil_redirect_home(); } /* compute the annotation */ @@ -2165,56 +2256,68 @@ annotate_file(&ann, fnid, mid, iLimit, annFlags); zCI = ann.aVers[0].zMUuid; /* generate the web page */ style_header("Annotation For %h", zFilename); - url_initialize(&url, "annotate"); + if( bBlame ){ + url_initialize(&url, "blame"); + }else{ + url_initialize(&url, "annotate"); + } url_add_parameter(&url, "checkin", P("checkin")); url_add_parameter(&url, "filename", zFilename); if( iLimit!=20 ){ url_add_parameter(&url, "limit", sqlite3_mprintf("%d", iLimit)); } url_add_parameter(&url, "log", showLog ? "1" : "0"); + if( ignoreWs ){ + url_add_parameter(&url, "w", ""); + style_submenu_element("Show Whitespace Changes", "Show Whitespace Changes", + "%s", url_render(&url, "w", 0, 0, 0)); + }else{ + style_submenu_element("Ignore Whitespace", "Ignore Whitespace", + "%s", url_render(&url, "w", "", 0, 0)); + } if( showLog ){ style_submenu_element("Hide Log", "Hide Log", - url_render(&url, "log", "0", 0, 0)); + "%s", url_render(&url, "log", "0", 0, 0)); }else{ style_submenu_element("Show Log", "Show Log", - url_render(&url, "log", "1", 0, 0)); + "%s", url_render(&url, "log", "1", 0, 0)); } if( ann.bLimit ){ char *z1, *z2; style_submenu_element("All Ancestors", "All Ancestors", - url_render(&url, "limit", "-1", 0, 0)); + "%s", url_render(&url, "limit", "-1", 0, 0)); z1 = sqlite3_mprintf("%d Ancestors", iLimit+20); z2 = sqlite3_mprintf("%d", iLimit+20); - style_submenu_element(z1, z1, url_render(&url, "limit", z2, 0, 0)); + style_submenu_element(z1, z1, "%s", url_render(&url, "limit", z2, 0, 0)); } if( iLimit>20 ){ style_submenu_element("20 Ancestors", "20 Ancestors", - url_render(&url, "limit", "20", 0, 0)); + "%s", url_render(&url, "limit", "20", 0, 0)); } - if( db_get_boolean("white-foreground", 0) ){ + if( skin_detail_boolean("white-foreground") ){ clr1 = 0xa04040; clr2 = 0x4059a0; }else{ clr1 = 0xffb5b5; /* Recent changes: red (hot) */ clr2 = 0xb5e0ff; /* Older changes: blue (cold) */ } for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ clr = gradient_color(clr1, clr2, ann.nVers-1, i); ann.aVers[i].zBgColor = mprintf("#%06x", clr); - } + } if( showLog ){ - char *zLink = href("%R/finfo?name=%t&ci=%S",zFilename,zCI); + char *zLink = href("%R/finfo?name=%t&ci=%!S",zFilename,zCI); @ <h2>Ancestors of %z(zLink)%h(zFilename)</a> analyzed:</h2> @ <ol> for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ @ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate) - @ check-in %z(href("%R/info/%S",p->zMUuid))%.10s(p->zMUuid)</a> - @ artifact %z(href("%R/artifact/%S",p->zFUuid))%.10s(p->zFUuid)</a> + @ check-in %z(href("%R/info/%!S",p->zMUuid))%S(p->zMUuid)</a> + @ artifact %z(href("%R/artifact/%!S",p->zFUuid))%S(p->zFUuid)</a> @ </span> #if 0 if( i>0 ){ char *zLink = xhref("target='infowindow'", "%R/fdiff?v1=%S&v2=%S&sbs=1", @@ -2231,37 +2334,52 @@ } @ </ol> @ <hr> } if( !ann.bLimit ){ - @ <h2>Origin for each line in - @ %z(href("%R/finfo?name=%h&ci=%S", zFilename, zCI))%h(zFilename)</a> - @ from check-in %z(href("%R/info/%S",zCI))%S(zCI)</a>:</h2> + @ <h2>Origin for each line in + @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a> + @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2> iLimit = ann.nVers+10; }else{ @ <h2>Lines added by the %d(iLimit) most recent ancestors of - @ %z(href("%R/finfo?name=%h&ci=%S", zFilename, zCI))%h(zFilename)</a> - @ from check-in %z(href("%R/info/%S",zCI))%S(zCI)</a>:</h2> + @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a> + @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2> } @ <pre> for(i=0; i<ann.nOrig; i++){ int iVers = ann.aOrig[i].iVers; char *z = (char*)ann.aOrig[i].z; int n = ann.aOrig[i].n; char zPrefix[300]; z[n] = 0; if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1; - if( iVers>=0 ){ - struct AnnVers *p = ann.aVers+iVers; - char *zLink = xhref("target='infowindow'", "%R/info/%S", p->zMUuid); - sqlite3_snprintf(sizeof(zPrefix), zPrefix, - "<span style='background-color:%s'>" - "%s%.10s</a> %s</span> %4d:", - p->zBgColor, zLink, p->zMUuid, p->zDate, i+1); - fossil_free(zLink); + + if( bBlame ){ + if( iVers>=0 ){ + struct AnnVers *p = ann.aVers+iVers; + char *zLink = xhref("target='infowindow'", "%R/info/%!S", p->zMUuid); + sqlite3_snprintf(sizeof(zPrefix), zPrefix, + "<span style='background-color:%s'>" + "%s%.10s</a> %s</span> %13.13s:", + p->zBgColor, zLink, p->zMUuid, p->zDate, p->zUser); + fossil_free(zLink); + }else{ + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%36s", ""); + } }else{ - sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%22s%4d:", "", i+1); + if( iVers>=0 ){ + struct AnnVers *p = ann.aVers+iVers; + char *zLink = xhref("target='infowindow'", "%R/info/%!S", p->zMUuid); + sqlite3_snprintf(sizeof(zPrefix), zPrefix, + "<span style='background-color:%s'>" + "%s%.10s</a> %s</span> %4d:", + p->zBgColor, zLink, p->zMUuid, p->zDate, i+1); + fossil_free(zLink); + }else{ + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%22s%4d:", "", i+1); + } } @ %s(zPrefix) %h(z) } @ </pre> @@ -2268,20 +2386,26 @@ style_footer(); } /* ** COMMAND: annotate +** COMMAND: blame +** COMMAND: praise ** -** %fossil annotate ?OPTIONS? FILENAME +** %fossil (annotate|blame|praise) ?OPTIONS? FILENAME ** ** Output the text of a file with markings to show when each line of -** the file was last modified. +** the file was last modified. The "annotate" command shows line numbers +** and omits the username. The "blame" and "praise" commands show the user +** who made each check-in and omits the line number. ** ** Options: -** --filevers Show file version numbers rather than check-in versions -** -l|--log List all versions analyzed -** -n|--limit N Only look backwards in time by N versions +** --filevers Show file version numbers rather than check-in versions +** -l|--log List all versions analyzed +** -n|--limit N Only look backwards in time by N versions +** -w|--ignore-all-space Ignore white space when comparing lines +** -Z|--ignore-trailing-space Ignore whitespace at line end ** ** See also: info, finfo, timeline */ void annotate_cmd(void){ int fnid; /* Filename ID */ @@ -2294,18 +2418,30 @@ int i; /* Loop counter */ const char *zLimit; /* The value to the -n|--limit option */ int iLimit; /* How far back in time to look */ int showLog; /* True to show the log */ int fileVers; /* Show file version instead of check-in versions */ - int annFlags = 0; /* Flags to control annotation properties */ + u64 annFlags = 0; /* Flags to control annotation properties */ + int bBlame = 0; /* True for BLAME output. False for ANNOTATE. */ + bBlame = g.argv[1][0]!='a'; zLimit = find_option("limit","n",1); if( zLimit==0 || zLimit[0]==0 ) zLimit = "-1"; iLimit = atoi(zLimit); showLog = find_option("log","l",0)!=0; + if( find_option("ignore-trailing-space","Z",0)!=0 ){ + annFlags = DIFF_IGNORE_EOLWS; + } + if( find_option("ignore-all-space","w",0)!=0 ){ + annFlags = DIFF_IGNORE_ALLWS; /* stronger than DIFF_IGNORE_EOLWS */ + } fileVers = find_option("filevers",0,0)!=0; db_must_be_within_tree(); + + /* We should be done with options.. */ + verify_all_options(); + if( g.argc<3 ) { usage("FILENAME"); } file_tree_name(g.argv[2], &treename, 1); zFilename = blob_str(&treename); @@ -2328,32 +2464,40 @@ " ORDER BY ancestor.generation ASC LIMIT 1", fid, fnid); if( mid==0 ){ fossil_fatal("unable to find manifest"); } - annFlags |= ANN_FILE_ANCEST; + annFlags |= (ANN_FILE_ANCEST|DIFF_STRIP_EOLCR); annotate_file(&ann, fnid, mid, iLimit, annFlags); if( showLog ){ struct AnnVers *p; for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ - fossil_print("version %3d: %s %.10s file %.10s\n", + fossil_print("version %3d: %s %S file %S\n", i+1, p->zDate, p->zMUuid, p->zFUuid); } fossil_print("---------------------------------------------------\n"); } for(i=0; i<ann.nOrig; i++){ int iVers = ann.aOrig[i].iVers; char *z = (char*)ann.aOrig[i].z; int n = ann.aOrig[i].n; - char zPrefix[200]; - z[n] = 0; + struct AnnVers *p; if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1; - if( iVers>=0 ){ - struct AnnVers *p = ann.aVers+iVers; - sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%.10s %s", - fileVers ? p->zFUuid : p->zMUuid, p->zDate); + p = ann.aVers + iVers; + if( bBlame ){ + if( iVers>=0 ){ + fossil_print("%S %s %13.13s: %.*s\n", + fileVers ? p->zFUuid : p->zMUuid, p->zDate, p->zUser, n, z); + }else{ + fossil_print("%35s %.*s\n", "", n, z); + } }else{ - zPrefix[0] = 0; + if( iVers>=0 ){ + fossil_print("%S %s %5d: %.*s\n", + fileVers ? p->zFUuid : p->zMUuid, p->zDate, i+1, n, z); + }else{ + fossil_print("%21s %5d: %.*s\n", + "", i+1, n, z); + } } - fossil_print("%21s %4d: %.*s\n", zPrefix, i+1, n, z); } } ADDED src/diff.tcl Index: src/diff.tcl ================================================================== --- /dev/null +++ src/diff.tcl @@ -0,0 +1,397 @@ +# The "diff --tk" command outputs prepends a "set fossilcmd {...}" line +# to this file, then runs this file using "tclsh" in order to display the +# graphical diff in a separate window. A typical "set fossilcmd" line +# looks like this: +# +# set fossilcmd {| "./fossil" diff --html -y -i -v} +# +# This header comment is stripped off by the "mkbuiltin.c" program. +# +set prog { +package require Tk + +array set CFG { + TITLE {Fossil Diff} + LN_COL_BG #dddddd + LN_COL_FG #444444 + TXT_COL_BG #ffffff + TXT_COL_FG #000000 + MKR_COL_BG #444444 + MKR_COL_FG #dddddd + CHNG_BG #d0d0ff + ADD_BG #c0ffc0 + RM_BG #ffc0c0 + HR_FG #888888 + HR_PAD_TOP 4 + HR_PAD_BTM 8 + FN_BG #444444 + FN_FG #ffffff + FN_PAD 5 + ERR_FG #ee0000 + PADX 5 + WIDTH 80 + HEIGHT 45 + LB_HEIGHT 25 +} + +if {![namespace exists ttk]} { + interp alias {} ::ttk::scrollbar {} ::scrollbar + interp alias {} ::ttk::menubutton {} ::menubutton +} + +proc dehtml {x} { + set x [regsub -all {<[^>]*>} $x {}] + return [string map {& & < < > > ' ' " \"} $x] +} + +proc cols {} { + return [list .lnA .txtA .mkr .lnB .txtB] +} + +proc colType {c} { + regexp {[a-z]+} $c type + return $type +} + +proc getLine {difftxt N iivar} { + upvar $iivar ii + if {$ii>=$N} {return -1} + set x [lindex $difftxt $ii] + incr ii + return $x +} + +proc readDiffs {fossilcmd} { + global difftxt + if {![info exists difftxt]} { + set in [open $fossilcmd r] + fconfigure $in -encoding utf-8 + set difftxt [split [read $in] \n] + close $in + } + set N [llength $difftxt] + set ii 0 + set nDiffs 0 + array set widths {txt 0 ln 0 mkr 0} + while {[set line [getLine $difftxt $N ii]] != -1} { + set fn2 {} + if {![regexp {^=+ (.*?) =+ versus =+ (.*?) =+$} $line all fn fn2] + && ![regexp {^=+ (.*?) =+$} $line all fn] + } { + continue + } + set errMsg "" + set line [getLine $difftxt $N ii] + if {[string compare -length 6 $line "<table"] + && ![regexp {<p[^>]*>(.+)} $line - errMsg]} { + continue + } + incr nDiffs + set idx [expr {$nDiffs > 1 ? [.txtA index end] : "1.0"}] + .wfiles.lb insert end $fn + + foreach c [cols] { + if {$nDiffs > 1} { + $c insert end \n - + } + if {[colType $c] eq "txt"} { + $c insert end $fn\n fn + if {$fn2!=""} {set fn $fn2} + } else { + $c insert end \n fn + } + $c insert end \n - + + if {$errMsg ne ""} continue + while {[getLine $difftxt $N ii] ne "<pre>"} continue + set type [colType $c] + set str {} + while {[set line [getLine $difftxt $N ii]] ne "</pre>"} { + set len [string length [dehtml $line]] + if {$len > $widths($type)} { + set widths($type) $len + } + append str $line\n + } + + set re {<span class="diff([a-z]+)">([^<]*)</span>} + # Use \r as separator since it can't appear in the diff output (it gets + # converted to a space). + set str [regsub -all $re $str "\r\\1\r\\2\r"] + foreach {pre class mid} [split $str \r] { + if {$class ne ""} { + $c insert end [dehtml $pre] - [dehtml $mid] [list $class -] + } else { + $c insert end [dehtml $pre] - + } + } + } + + if {$errMsg ne ""} { + foreach c {.txtA .txtB} {$c insert end [string trim $errMsg] err} + foreach c [cols] {$c insert end \n -} + } + } + + foreach c [cols] { + set type [colType $c] + if {$type ne "txt"} { + $c config -width $widths($type) + } + $c config -state disabled + } + if {$nDiffs <= [.wfiles.lb cget -height]} { + .wfiles.lb config -height $nDiffs + grid remove .wfiles.sb + } + + return $nDiffs +} + +proc viewDiff {idx} { + .txtA yview $idx + .txtA xview moveto 0 +} + +proc cycleDiffs {{reverse 0}} { + if {$reverse} { + set range [.txtA tag prevrange fn @0,0 1.0] + if {$range eq ""} { + viewDiff {fn.last -1c} + } else { + viewDiff [lindex $range 0] + } + } else { + set range [.txtA tag nextrange fn {@0,0 +1c} end] + if {$range eq "" || [lindex [.txtA yview] 1] == 1} { + viewDiff fn.first + } else { + viewDiff [lindex $range 0] + } + } +} + +proc xvis {col} { + set view [$col xview] + return [expr {[lindex $view 1]-[lindex $view 0]}] +} + +proc scroll-x {args} { + set c .txt[expr {[xvis .txtA] < [xvis .txtB] ? "A" : "B"}] + eval $c xview $args +} + +interp alias {} scroll-y {} .txtA yview + +proc noop {args} {} + +proc enableSync {axis} { + update idletasks + interp alias {} sync-$axis {} + rename _sync-$axis sync-$axis +} + +proc disableSync {axis} { + rename sync-$axis _sync-$axis + interp alias {} sync-$axis {} noop +} + +proc sync-x {col first last} { + disableSync x + $col xview moveto [expr {$first*[xvis $col]/($last-$first)}] + foreach side {A B} { + set sb .sbx$side + set xview [.txt$side xview] + if {[lindex $xview 0] > 0 || [lindex $xview 1] < 1} { + grid $sb + eval $sb set $xview + } else { + grid remove $sb + } + } + enableSync x +} + +proc sync-y {first last} { + disableSync y + foreach c [cols] { + $c yview moveto $first + } + if {$first > 0 || $last < 1} { + grid .sby + .sby set $first $last + } else { + grid remove .sby + } + enableSync y +} + +wm withdraw . +wm title . $CFG(TITLE) +wm iconname . $CFG(TITLE) +bind . <q> exit +bind . <Destroy> {after 0 exit} +bind . <Tab> {cycleDiffs; break} +bind . <<PrevWindow>> {cycleDiffs 1; break} +bind . <Return> { + event generate .bb.files <1> + event generate .bb.files <ButtonRelease-1> + break +} +foreach {key axis args} { + Up y {scroll -5 units} + k y {scroll -5 units} + Down y {scroll 5 units} + j y {scroll 5 units} + Left x {scroll -5 units} + h x {scroll -5 units} + Right x {scroll 5 units} + l x {scroll 5 units} + Prior y {scroll -1 page} + b y {scroll -1 page} + Next y {scroll 1 page} + space y {scroll 1 page} + Home y {moveto 0} + g y {moveto 0} + End y {moveto 1} +} { + bind . <$key> "scroll-$axis $args; break" + bind . <Shift-$key> continue +} + +frame .bb +::ttk::menubutton .bb.files -text "Files" +toplevel .wfiles +wm withdraw .wfiles +update idletasks +wm transient .wfiles . +wm overrideredirect .wfiles 1 +listbox .wfiles.lb -width 0 -height $CFG(LB_HEIGHT) -activestyle none \ + -yscroll {.wfiles.sb set} +::ttk::scrollbar .wfiles.sb -command {.wfiles.lb yview} +grid .wfiles.lb .wfiles.sb -sticky ns +bind .bb.files <1> { + set x [winfo rootx %W] + set y [expr {[winfo rooty %W]+[winfo height %W]}] + wm geometry .wfiles +$x+$y + wm deiconify .wfiles + focus .wfiles.lb +} +bind .wfiles <FocusOut> {wm withdraw .wfiles} +bind .wfiles <Escape> {focus .} +foreach evt {1 Return} { + bind .wfiles.lb <$evt> { + catch { + set idx [lindex [.txtA tag ranges fn] [expr {[%W curselection]*2}]] + viewDiff $idx + } + focus . + break + } +} +bind .wfiles.lb <Motion> { + %W selection clear 0 end + %W selection set @%x,%y +} + +foreach {side syncCol} {A .txtB B .txtA} { + set ln .ln$side + text $ln + $ln tag config - -justify right + + set txt .txt$side + text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \ + -xscroll "sync-x $syncCol" + catch {$txt config -tabstyle wordprocessor} ;# Required for Tk>=8.5 + foreach tag {add rm chng} { + $txt tag config $tag -background $CFG([string toupper $tag]_BG) + $txt tag lower $tag + } + $txt tag config fn -background $CFG(FN_BG) -foreground $CFG(FN_FG) \ + -justify center + $txt tag config err -foreground $CFG(ERR_FG) +} +text .mkr + +foreach c [cols] { + set keyPrefix [string toupper [colType $c]]_COL_ + if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}} + $c config -bg $CFG(${keyPrefix}BG) -fg $CFG(${keyPrefix}FG) -borderwidth 0 \ + -padx $CFG(PADX) -yscroll sync-y + $c tag config hr -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \ + -foreground $CFG(HR_FG) + $c tag config fn -spacing1 $CFG(FN_PAD) -spacing3 $CFG(FN_PAD) + bindtags $c ". $c Text all" + bind $c <1> {focus %W} +} + +::ttk::scrollbar .sby -command {.txtA yview} -orient vertical +::ttk::scrollbar .sbxA -command {.txtA xview} -orient horizontal +::ttk::scrollbar .sbxB -command {.txtB xview} -orient horizontal +frame .spacer + +if {[readDiffs $fossilcmd] == 0} { + tk_messageBox -type ok -title $CFG(TITLE) -message "No changes" + exit +} +update idletasks + +proc saveDiff {} { + set fn [tk_getSaveFile] + if {$fn==""} return + set out [open $fn wb] + puts $out "#!/usr/bin/tclsh\n#\n# Run this script using 'tclsh' or 'wish'" + puts $out "# to see the graphical diff.\n#" + puts $out "set fossilcmd {}" + puts $out "set prog [list $::prog]" + puts $out "set difftxt \173" + foreach e $::difftxt {puts $out [list $e]} + puts $out "\175" + puts $out "eval \$prog" + close $out +} +proc invertDiff {} { + global CFG + array set x [grid info .txtA] + if {$x(-column)==1} { + grid config .lnB -column 0 + grid config .txtB -column 1 + .txtB tag config add -background $CFG(RM_BG) + grid config .lnA -column 3 + grid config .txtA -column 4 + .txtA tag config rm -background $CFG(ADD_BG) + } else { + grid config .lnA -column 0 + grid config .txtA -column 1 + .txtA tag config rm -background $CFG(RM_BG) + grid config .lnB -column 3 + grid config .txtB -column 4 + .txtB tag config add -background $CFG(ADD_BG) + } + .mkr config -state normal + set clt [.mkr search -all < 1.0 end] + set cgt [.mkr search -all > 1.0 end] + foreach c $clt {.mkr replace $c "$c +1 chars" >} + foreach c $cgt {.mkr replace $c "$c +1 chars" <} + .mkr config -state disabled +} +::ttk::button .bb.quit -text {Quit} -command exit +::ttk::button .bb.invert -text {Invert} -command invertDiff +::ttk::button .bb.save -text {Save As...} -command saveDiff +pack .bb.quit .bb.invert -side left +if {$fossilcmd!=""} {pack .bb.save -side left} +pack .bb.files -side left +grid rowconfigure . 1 -weight 1 +grid columnconfigure . 1 -weight 1 +grid columnconfigure . 4 -weight 1 +grid .bb -row 0 -columnspan 6 +eval grid [cols] -row 1 -sticky nsew +grid .sby -row 1 -column 5 -sticky ns +grid .sbxA -row 2 -columnspan 2 -sticky ew +grid .spacer -row 2 -column 2 +grid .sbxB -row 2 -column 3 -columnspan 2 -sticky ew + +.spacer config -height [winfo height .sbxA] +wm deiconify . +} +eval $prog Index: src/diffcmd.c ================================================================== --- src/diffcmd.c +++ src/diffcmd.c @@ -28,10 +28,15 @@ # define NULL_DEVICE "NUL" #else # define NULL_DEVICE "/dev/null" #endif +/* +** Used when the name for the diff is unknown. +*/ +#define DIFF_NO_NAME "(unknown)" + /* ** Print the "Index:" message that patches wants to see at the top of a diff. */ void diff_print_index(const char *zFile, u64 diffFlags){ if( (diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF))==0 ){ @@ -49,15 +54,25 @@ if( diffFlags & DIFF_BRIEF ){ /* no-op */ }else if( diffFlags & DIFF_SIDEBYSIDE ){ int w = diff_width(diffFlags); int n1 = strlen(zLeft); + int n2 = strlen(zRight); int x; - if( n1>w*2 ) n1 = w*2; - x = w*2+17 - (n1+2); - z = mprintf("%.*c %.*s %.*c\n", - x/2, '=', n1, zLeft, (x+1)/2, '='); + if( n1==n2 && fossil_strcmp(zLeft,zRight)==0 ){ + if( n1>w*2 ) n1 = w*2; + x = w*2+17 - (n1+2); + z = mprintf("%.*c %.*s %.*c\n", + x/2, '=', n1, zLeft, (x+1)/2, '='); + }else{ + if( w<20 ) w = 20; + if( n1>w-10 ) n1 = w - 10; + if( n2>w-10 ) n2 = w - 10; + z = mprintf("%.*c %.*s %.*c versus %.*c %.*s %.*c\n", + (w-n1+10)/2, '=', n1, zLeft, (w-n1+1)/2, '=', + (w-n2)/2, '=', n2, zRight, (w-n2+1)/2, '='); + } }else{ z = mprintf("--- %s\n+++ %s\n", zLeft, zRight); } fossil_print("%s", z); fossil_free(z); @@ -94,11 +109,11 @@ /* Read content of zFile2 into memory */ blob_zero(&file2); if( file_wd_size(zFile2)<0 ){ zName2 = NULL_DEVICE; }else{ - if( file_wd_islink(zFile2) ){ + if( file_wd_islink(0) ){ blob_read_link(&file2, zFile2); }else{ blob_read_from_file(&file2, zFile2); } zName2 = zName; @@ -127,32 +142,32 @@ Blob cmd; /* Text of command to run */ if( !fIncludeBinary ){ Blob file2; if( isBin1 ){ - fossil_print(DIFF_CANNOT_COMPUTE_BINARY); + fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); return; } if( zBinGlob ){ Glob *pBinary = glob_create(zBinGlob); if( glob_match(pBinary, zName) ){ - fossil_print(DIFF_CANNOT_COMPUTE_BINARY); + fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); glob_free(pBinary); return; } glob_free(pBinary); } blob_zero(&file2); if( file_wd_size(zFile2)>=0 ){ - if( file_wd_islink(zFile2) ){ + if( file_wd_islink(0) ){ blob_read_link(&file2, zFile2); }else{ blob_read_from_file(&file2, zFile2); } } if( looks_like_binary(&file2) ){ - fossil_print(DIFF_CANNOT_COMPUTE_BINARY); + fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); blob_reset(&file2); return; } blob_reset(&file2); } @@ -161,11 +176,11 @@ ** zFile2 */ blob_zero(&nameFile1); do{ blob_reset(&nameFile1); blob_appendf(&nameFile1, "%s~%d", zFile2, cnt++); - }while( file_access(blob_str(&nameFile1),0)==0 ); + }while( file_access(blob_str(&nameFile1),F_OK)==0 ); blob_write_to_file(pFile1, blob_str(&nameFile1)); /* Construct the external diff command */ blob_zero(&cmd); blob_appendf(&cmd, "%s ", zDiffCmd); @@ -223,17 +238,17 @@ char zTemp1[300]; char zTemp2[300]; if( !fIncludeBinary ){ if( isBin1 || isBin2 ){ - fossil_print(DIFF_CANNOT_COMPUTE_BINARY); + fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); return; } if( zBinGlob ){ Glob *pBinary = glob_create(zBinGlob); if( glob_match(pBinary, zName) ){ - fossil_print(DIFF_CANNOT_COMPUTE_BINARY); + fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); glob_free(pBinary); return; } glob_free(pBinary); } @@ -287,11 +302,11 @@ int isBin; file_tree_name(zFileTreeName, &fname, 1); historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, fIncludeBinary ? 0 : &isBin, 0); if( !isLink != !file_wd_islink(zFrom) ){ - fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK); + fossil_print("%s",DIFF_CANNOT_COMPUTE_SYMLINK); }else{ diff_file(&content, isBin, zFileTreeName, zFileTreeName, zDiffCmd, zBinGlob, fIncludeBinary, diffFlags); } blob_reset(&content); @@ -331,11 +346,11 @@ int rid = name_to_typed_rid(zFrom, "ci"); if( !is_a_version(rid) ){ fossil_fatal("no such check-in: %s", zFrom); } load_vfile_from_rid(rid); - blob_appendf(&sql, + blob_append_sql(&sql, "SELECT v2.pathname, v2.deleted, v2.chnged, v2.rid==0, v1.rid, v1.islink" " FROM vfile v1, vfile v2 " " WHERE v1.pathname=v2.pathname AND v1.vid=%d AND v2.vid=%d" " AND (v2.deleted OR v2.chnged OR v1.mrid!=v2.rid)" "UNION " @@ -348,38 +363,38 @@ "SELECT pathname, 0, 0, 1, 0, islink" " FROM vfile v2" " WHERE v2.vid=%d" " AND NOT EXISTS(SELECT 1 FROM vfile v1" " WHERE v1.vid=%d AND v1.pathname=v2.pathname)" - " ORDER BY 1", + " ORDER BY 1 /*scan*/", rid, vid, rid, vid, vid, rid ); }else{ - blob_appendf(&sql, + blob_append_sql(&sql, "SELECT pathname, deleted, chnged , rid==0, rid, islink" " FROM vfile" " WHERE vid=%d" " AND (deleted OR chnged OR rid==0)" - " ORDER BY pathname", + " ORDER BY pathname /*scan*/", vid ); } - db_prepare(&q, blob_str(&sql)); + db_prepare(&q, "%s", blob_sql_text(&sql)); while( db_step(&q)==SQLITE_ROW ){ const char *zPathname = db_column_text(&q,0); int isDeleted = db_column_int(&q, 1); int isChnged = db_column_int(&q,2); int isNew = db_column_int(&q,3); int srcid = db_column_int(&q, 4); int isLink = db_column_int(&q, 5); - char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); - char *zToFree = zFullName; + char *zToFree = mprintf("%s%s", g.zLocalRoot, zPathname); + const char *zFullName = zToFree; int showDiff = 1; if( isDeleted ){ fossil_print("DELETED %s\n", zPathname); if( !asNewFile ){ showDiff = 0; zFullName = NULL_DEVICE; } - }else if( file_access(zFullName, 0) ){ + }else if( file_access(zFullName, F_OK) ){ fossil_print("MISSING %s\n", zPathname); if( !asNewFile ){ showDiff = 0; } }else if( isNew ){ fossil_print("ADDED %s\n", zPathname); srcid = 0; @@ -393,11 +408,11 @@ Blob content; int isBin; if( !isLink != !file_wd_islink(zFullName) ){ diff_print_index(zPathname, diffFlags); diff_print_filenames(zPathname, zPathname, diffFlags); - fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK); + fossil_print("%s",DIFF_CANNOT_COMPUTE_SYMLINK); continue; } if( srcid>0 ){ content_get(srcid, &content); }else{ @@ -447,11 +462,11 @@ fIncludeBinary ? 0 : &isBin1, 0); historical_version_of_file(zTo, zName, &v2, &isLink2, 0, fIncludeBinary ? 0 : &isBin2, 0); if( isLink1 != isLink2 ){ diff_print_filenames(zName, zName, diffFlags); - fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK); + fossil_print("%s",DIFF_CANNOT_COMPUTE_SYMLINK); }else{ diff_file_mem(&v1, &v2, isBin1, isBin2, zName, zDiffCmd, zBinGlob, fIncludeBinary, diffFlags); } blob_reset(&v1); @@ -479,11 +494,18 @@ u64 diffFlags ){ Blob f1, f2; int isBin1, isBin2; int rid; - const char *zName = pFrom ? pFrom->zName : pTo->zName; + const char *zName; + if( pFrom ){ + zName = pFrom->zName; + }else if( pTo ){ + zName = pTo->zName; + }else{ + zName = DIFF_NO_NAME; + } if( diffFlags & DIFF_BRIEF ) return; diff_print_index(zName, diffFlags); if( pFrom ){ rid = uuid_to_rid(pFrom->zUuid, 0); content_get(rid, &f1); @@ -595,351 +617,73 @@ zName = "diff-command"; } return db_get(zName, zDefault); } -/* A Tcl/Tk script used to render diff output. -*/ -static const char zDiffScript[] = -@ package require Tk -@ -@ array set CFG { -@ TITLE {Fossil Diff} -@ LN_COL_BG #dddddd -@ LN_COL_FG #444444 -@ TXT_COL_BG #ffffff -@ TXT_COL_FG #000000 -@ MKR_COL_BG #444444 -@ MKR_COL_FG #dddddd -@ CHNG_BG #d0d0ff -@ ADD_BG #c0ffc0 -@ RM_BG #ffc0c0 -@ HR_FG #888888 -@ HR_PAD_TOP 4 -@ HR_PAD_BTM 8 -@ FN_BG #444444 -@ FN_FG #ffffff -@ FN_PAD 5 -@ PADX 5 -@ WIDTH 80 -@ HEIGHT 45 -@ LB_HEIGHT 25 -@ } -@ -@ if {![namespace exists ttk]} { -@ interp alias {} ::ttk::scrollbar {} ::scrollbar -@ interp alias {} ::ttk::menubutton {} ::menubutton -@ } -@ -@ proc dehtml {x} { -@ set x [regsub -all {<[^>]*>} $x {}] -@ return [string map {& & < < > > ' ' " \"} $x] -@ } -@ -@ proc cols {} { -@ return [list .lnA .txtA .mkr .lnB .txtB] -@ } -@ -@ proc colType {c} { -@ regexp {[a-z]+} $c type -@ return $type -@ } -@ -@ proc readDiffs {fossilcmd} { -@ set in [open $fossilcmd r] -@ fconfigure $in -encoding utf-8 -@ set nDiffs 0 -@ array set widths {txt 0 ln 0 mkr 0} -@ while {[gets $in line] != -1} { -@ if {![regexp {^=+\s+(.*?)\s+=+$} $line all fn]} { -@ continue -@ } -@ if {[string compare -length 6 [gets $in] "<table"]} { -@ continue -@ } -@ incr nDiffs -@ set idx [expr {$nDiffs > 1 ? [.txtA index end] : "1.0"}] -@ .wfiles.lb insert end $fn -@ -@ foreach c [cols] { -@ while {[gets $in] ne "<pre>"} continue -@ -@ if {$nDiffs > 1} { -@ $c insert end \n - -@ } -@ if {[colType $c] eq "txt"} { -@ $c insert end $fn\n fn -@ } else { -@ $c insert end \n fn -@ } -@ $c insert end \n - -@ -@ set type [colType $c] -@ set str {} -@ while {[set line [gets $in]] ne "</pre>"} { -@ set len [string length [dehtml $line]] -@ if {$len > $widths($type)} { -@ set widths($type) $len -@ } -@ append str $line\n -@ } -@ -@ set re {<span class="diff([a-z]+)">([^<]*)</span>} -@ # Use \r as separator since it can't appear in the diff output (it gets -@ # converted to a space). -@ set str [regsub -all $re $str "\r\\1\r\\2\r"] -@ foreach {pre class mid} [split $str \r] { -@ if {$class ne ""} { -@ $c insert end [dehtml $pre] - [dehtml $mid] [list $class -] -@ } else { -@ $c insert end [dehtml $pre] - -@ } -@ } -@ } -@ } -@ close $in -@ -@ foreach c [cols] { -@ set type [colType $c] -@ if {$type ne "txt"} { -@ $c config -width $widths($type) -@ } -@ $c config -state disabled -@ } -@ if {$nDiffs <= [.wfiles.lb cget -height]} { -@ .wfiles.lb config -height $nDiffs -@ grid remove .wfiles.sb -@ } -@ -@ return $nDiffs -@ } -@ -@ proc viewDiff {idx} { -@ .txtA yview $idx -@ .txtA xview moveto 0 -@ } -@ -@ proc cycleDiffs {{reverse 0}} { -@ if {$reverse} { -@ set range [.txtA tag prevrange fn @0,0 1.0] -@ if {$range eq ""} { -@ viewDiff {fn.last -1c} -@ } else { -@ viewDiff [lindex $range 0] -@ } -@ } else { -@ set range [.txtA tag nextrange fn {@0,0 +1c} end] -@ if {$range eq "" || [lindex [.txtA yview] 1] == 1} { -@ viewDiff fn.first -@ } else { -@ viewDiff [lindex $range 0] -@ } -@ } -@ } -@ -@ proc xvis {col} { -@ set view [$col xview] -@ return [expr {[lindex $view 1]-[lindex $view 0]}] -@ } -@ -@ proc scroll-x {args} { -@ set c .txt[expr {[xvis .txtA] < [xvis .txtB] ? "A" : "B"}] -@ eval $c xview $args -@ } -@ -@ interp alias {} scroll-y {} .txtA yview -@ -@ proc noop {args} {} -@ -@ proc enableSync {axis} { -@ update idletasks -@ interp alias {} sync-$axis {} -@ rename _sync-$axis sync-$axis -@ } -@ -@ proc disableSync {axis} { -@ rename sync-$axis _sync-$axis -@ interp alias {} sync-$axis {} noop -@ } -@ -@ proc sync-x {col first last} { -@ disableSync x -@ $col xview moveto [expr {$first*[xvis $col]/($last-$first)}] -@ foreach side {A B} { -@ set sb .sbx$side -@ set xview [.txt$side xview] -@ if {[lindex $xview 0] > 0 || [lindex $xview 1] < 1} { -@ grid $sb -@ eval $sb set $xview -@ } else { -@ grid remove $sb -@ } -@ } -@ enableSync x -@ } -@ -@ proc sync-y {first last} { -@ disableSync y -@ foreach c [cols] { -@ $c yview moveto $first -@ } -@ if {$first > 0 || $last < 1} { -@ grid .sby -@ .sby set $first $last -@ } else { -@ grid remove .sby -@ } -@ enableSync y -@ } -@ -@ wm withdraw . -@ wm title . $CFG(TITLE) -@ wm iconname . $CFG(TITLE) -@ bind . <q> exit -@ bind . <Tab> {cycleDiffs; break} -@ bind . <<PrevWindow>> {cycleDiffs 1; break} -@ bind . <Return> { -@ event generate .files <1> -@ event generate .files <ButtonRelease-1> -@ break -@ } -@ foreach {key axis args} { -@ Up y {scroll -5 units} -@ Down y {scroll 5 units} -@ Left x {scroll -5 units} -@ Right x {scroll 5 units} -@ Prior y {scroll -1 page} -@ Next y {scroll 1 page} -@ Home y {moveto 0} -@ End y {moveto 1} -@ } { -@ bind . <$key> "scroll-$axis $args; break" -@ bind . <Shift-$key> continue -@ } -@ -@ ::ttk::menubutton .files -text "Files" -@ toplevel .wfiles -@ wm withdraw .wfiles -@ update idletasks -@ wm transient .wfiles . -@ wm overrideredirect .wfiles 1 -@ listbox .wfiles.lb -width 0 -height $CFG(LB_HEIGHT) -activestyle none \ -@ -yscroll {.wfiles.sb set} -@ ::ttk::scrollbar .wfiles.sb -command {.wfiles.lb yview} -@ grid .wfiles.lb .wfiles.sb -sticky ns -@ bind .files <1> { -@ set x [winfo rootx %W] -@ set y [expr {[winfo rooty %W]+[winfo height %W]}] -@ wm geometry .wfiles +$x+$y -@ wm deiconify .wfiles -@ focus .wfiles.lb -@ } -@ bind .wfiles <FocusOut> {wm withdraw .wfiles} -@ bind .wfiles <Escape> {focus .} -@ foreach evt {1 Return} { -@ bind .wfiles.lb <$evt> { -@ catch { -@ set idx [lindex [.txtA tag ranges fn] [expr {[%W curselection]*2}]] -@ viewDiff $idx -@ } -@ focus . -@ break -@ } -@ } -@ bind .wfiles.lb <Motion> { -@ %W selection clear 0 end -@ %W selection set @%x,%y -@ } -@ -@ foreach {side syncCol} {A .txtB B .txtA} { -@ set ln .ln$side -@ text $ln -@ $ln tag config - -justify right -@ -@ set txt .txt$side -@ text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \ -@ -xscroll "sync-x $syncCol" -@ catch {$txt config -tabstyle wordprocessor} ;# Required for Tk>=8.5 -@ foreach tag {add rm chng} { -@ $txt tag config $tag -background $CFG([string toupper $tag]_BG) -@ $txt tag lower $tag -@ } -@ $txt tag config fn -background $CFG(FN_BG) -foreground $CFG(FN_FG) \ -@ -justify center -@ } -@ text .mkr -@ -@ foreach c [cols] { -@ set keyPrefix [string toupper [colType $c]]_COL_ -@ if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}} -@ $c config -bg $CFG(${keyPrefix}BG) -fg $CFG(${keyPrefix}FG) -borderwidth 0 \ -@ -padx $CFG(PADX) -yscroll sync-y -@ $c tag config hr -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \ -@ -foreground $CFG(HR_FG) -@ $c tag config fn -spacing1 $CFG(FN_PAD) -spacing3 $CFG(FN_PAD) -@ bindtags $c ". $c Text all" -@ bind $c <1> {focus %W} -@ } -@ -@ ::ttk::scrollbar .sby -command {.txtA yview} -orient vertical -@ ::ttk::scrollbar .sbxA -command {.txtA xview} -orient horizontal -@ ::ttk::scrollbar .sbxB -command {.txtB xview} -orient horizontal -@ frame .spacer -@ -@ if {[readDiffs $fossilcmd] == 0} { -@ tk_messageBox -type ok -title $CFG(TITLE) -message "No changes" -@ exit -@ } -@ update idletasks -@ -@ grid rowconfigure . 1 -weight 1 -@ grid columnconfigure . 1 -weight 1 -@ grid columnconfigure . 4 -weight 1 -@ grid .files -row 0 -columnspan 6 -@ eval grid [cols] -row 1 -sticky nsew -@ grid .sby -row 1 -column 5 -sticky ns -@ grid .sbxA -row 2 -columnspan 2 -sticky ew -@ grid .spacer -row 2 -column 2 -@ grid .sbxB -row 2 -column 3 -columnspan 2 -sticky ew -@ -@ .spacer config -height [winfo height .sbxA] -@ wm deiconify . -; - /* ** Show diff output in a Tcl/Tk window, in response to the --tk option ** to the diff command. -** -** Steps: +** +** If fossil has direct access to a Tcl interpreter (either loaded +** dynamically through stubs or linked in statically), we can use it +** directly. Otherwise: ** (1) Write the Tcl/Tk script used for rendering into a temp file. -** (2) Invoke "wish" on the temp file using fossil_system(). +** (2) Invoke "tclsh" on the temp file using fossil_system(). ** (3) Delete the temp file. */ void diff_tk(const char *zSubCmd, int firstArg){ int i; Blob script; - char *zTempFile; + const char *zTempFile = 0; char *zCmd; blob_zero(&script); blob_appendf(&script, "set fossilcmd {| \"%/\" %s --html -y -i -v", g.nameOfExe, zSubCmd); + find_option("html",0,0); + find_option("side-by-side","y",0); + find_option("internal","i",0); + find_option("verbose","v",0); + /* The undocumented --script FILENAME option causes the Tk script to + ** be written into the FILENAME instead of being run. This is used + ** for testing and debugging. */ + zTempFile = find_option("script",0,1); for(i=firstArg; i<g.argc; i++){ const char *z = g.argv[i]; - if( z[0]=='-' ){ - if( strglob("*-html",z) ) continue; - if( strglob("*-y",z) ) continue; - if( strglob("*-i",z) ) continue; - } - blob_append(&script, " ", 1); - shell_escape(&script, z); - } - blob_appendf(&script, "}\n%s", zDiffScript); - zTempFile = write_blob_to_temp_file(&script); - zCmd = mprintf("tclsh \"%s\"", zTempFile); - fossil_system(zCmd); - file_delete(zTempFile); - fossil_free(zCmd); + if( sqlite3_strglob("*}*",z) ){ + blob_appendf(&script, " {%/}", z); + }else{ + int j; + blob_append(&script, " ", 1); + for(j=0; z[j]; j++) blob_appendf(&script, "\\%03o", (unsigned char)z[j]); + } + } + blob_appendf(&script, "}\n%s", builtin_file("diff.tcl", 0)); + if( zTempFile ){ + blob_write_to_file(&script, zTempFile); + fossil_print("To see diff, run: tclsh \"%s\"\n", zTempFile); + }else{ +#if defined(FOSSIL_ENABLE_TCL) + Th_FossilInit(TH_INIT_DEFAULT); + if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script), + blob_size(&script), 1, 0)==TCL_OK ){ + blob_reset(&script); + return; + } + /* + * If evaluation of the Tcl script fails, the reason may be that Tk + * could not be found by the loaded Tcl, or that Tcl cannot be loaded + * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback + * to using the external "tclsh", if available. + */ +#endif + zTempFile = write_blob_to_temp_file(&script); + zCmd = mprintf("tclsh \"%s\"", zTempFile); + fossil_system(zCmd); + file_delete(zTempFile); + fossil_free(zCmd); + } + blob_reset(&script); } /* ** Returns non-zero if files that may be binary should be used with external ** diff programs. @@ -974,11 +718,11 @@ ** specified (as they exist on disk) and that same file as it was checked ** out. Or if the FILE arguments are omitted, show the unsaved changed ** currently in the working check-out. ** ** If the "--from VERSION" or "-r VERSION" option is used it specifies -** the source check-in for the diff operation. If not specified, the +** the source check-in for the diff operation. If not specified, the ** source check-in is the base check-in for the current check-out. ** ** If the "--to VERSION" option appears, it specifies the check-in from ** which the second version of the file or files is taken. If there is ** no "--to" option then the (possibly edited) files in the current check-out @@ -998,23 +742,26 @@ ** The "--binary" option causes files matching the glob PATTERN to be treated ** as binary when considering if they should be used with external diff program. ** This option overrides the "binary-glob" setting. ** ** Options: -** --binary PATTERN Treat files that match the glob PATTERN as binary -** --branch BRANCH Show diff of all changes on BRANCH -** --brief Show filenames only -** --context|-c N Use N lines of context -** --diff-binary BOOL Include binary files when using external commands -** --from|-r VERSION select VERSION as source for the diff -** --internal|-i use internal diff logic -** --side-by-side|-y side-by-side diff -** --tk Launch a Tcl/Tk GUI for display -** --to VERSION select VERSION as target for the diff -** --unified unified diff -** -v|--verbose output complete text of added or deleted files -** -W|--width Width of lines in side-by-side diff +** --binary PATTERN Treat files that match the glob PATTERN as binary +** --branch BRANCH Show diff of all changes on BRANCH +** --brief Show filenames only +** --context|-c N Use N lines of context +** --diff-binary BOOL Include binary files when using external commands +** --from|-r VERSION select VERSION as source for the diff +** --internal|-i use internal diff logic +** --side-by-side|-y side-by-side diff +** --strip-trailing-cr Strip trailing CR +** --tk Launch a Tcl/Tk GUI for display +** --to VERSION select VERSION as target for the diff +** --unified unified diff +** -v|--verbose output complete text of added or deleted files +** -w|--ignore-all-space Ignore white space when comparing lines +** -W|--width <num> Width of lines in side-by-side diff +** -Z|--ignore-trailing-space Ignore changes to end-of-line whitespace */ void diff_cmd(void){ int isGDiff; /* True for gdiff. False for normal diff */ int isInternDiff; /* True for internal diff */ int verboseFlag; /* True if -v or --verbose flag is used */ @@ -1040,11 +787,10 @@ verboseFlag = find_option("verbose","v",0)!=0; if( !verboseFlag ){ verboseFlag = find_option("new-file","N",0)!=0; /* deprecated */ } if( verboseFlag ) diffFlags |= DIFF_VERBOSE; - if( zBranch ){ if( zTo || zFrom ){ fossil_fatal("cannot use --from or --to with --branch"); } zTo = zBranch; @@ -1095,11 +841,11 @@ */ void vpatch_page(void){ const char *zFrom = P("from"); const char *zTo = P("to"); login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } if( zFrom==0 || zTo==0 ) fossil_redirect_home(); cgi_set_content_type("text/plain"); diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_VERBOSE); } Index: src/doc.c ================================================================== --- src/doc.c +++ src/doc.c @@ -26,11 +26,11 @@ ** Try to guess the mimetype from content. ** ** If the content is pure text, return NULL. ** ** For image types, attempt to return an appropriate mimetype -** name like "image/gif" or "image/jpeg". +** name like "image/gif" or "image/jpeg". ** ** For any other binary type, return "unknown/unknown". */ const char *mimetype_from_content(Blob *pBlob){ int i; @@ -65,17 +65,251 @@ } } if( i>=n ){ return 0; /* Plain text */ } - for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){ + for(i=0; i<ArraySize(aMime); i++){ if( n>=aMime[i].size && memcmp(x, aMime[i].zPrefix, aMime[i].size)==0 ){ return aMime[i].zMimetype; } } return "unknown/unknown"; } + +/* A table of mimetypes based on file suffixes. +** Suffixes must be in sorted order so that we can do a binary +** search to find the mime-type +*/ +static const struct { + const char *zSuffix; /* The file suffix */ + int size; /* Length of the suffix */ + const char *zMimetype; /* The corresponding mimetype */ +} aMime[] = { + { "ai", 2, "application/postscript" }, + { "aif", 3, "audio/x-aiff" }, + { "aifc", 4, "audio/x-aiff" }, + { "aiff", 4, "audio/x-aiff" }, + { "arj", 3, "application/x-arj-compressed" }, + { "asc", 3, "text/plain" }, + { "asf", 3, "video/x-ms-asf" }, + { "asx", 3, "video/x-ms-asx" }, + { "au", 2, "audio/ulaw" }, + { "avi", 3, "video/x-msvideo" }, + { "bat", 3, "application/x-msdos-program" }, + { "bcpio", 5, "application/x-bcpio" }, + { "bin", 3, "application/octet-stream" }, + { "c", 1, "text/plain" }, + { "cc", 2, "text/plain" }, + { "ccad", 4, "application/clariscad" }, + { "cdf", 3, "application/x-netcdf" }, + { "class", 5, "application/octet-stream" }, + { "cod", 3, "application/vnd.rim.cod" }, + { "com", 3, "application/x-msdos-program" }, + { "cpio", 4, "application/x-cpio" }, + { "cpt", 3, "application/mac-compactpro" }, + { "csh", 3, "application/x-csh" }, + { "css", 3, "text/css" }, + { "csv", 3, "text/csv" }, + { "dcr", 3, "application/x-director" }, + { "deb", 3, "application/x-debian-package" }, + { "dir", 3, "application/x-director" }, + { "dl", 2, "video/dl" }, + { "dms", 3, "application/octet-stream" }, + { "doc", 3, "application/msword" }, + { "docx", 4, "application/vnd.openxmlformats-" + "officedocument.wordprocessingml.document"}, + { "dot", 3, "application/msword" }, + { "dotx", 4, "application/vnd.openxmlformats-" + "officedocument.wordprocessingml.template"}, + { "drw", 3, "application/drafting" }, + { "dvi", 3, "application/x-dvi" }, + { "dwg", 3, "application/acad" }, + { "dxf", 3, "application/dxf" }, + { "dxr", 3, "application/x-director" }, + { "eps", 3, "application/postscript" }, + { "etx", 3, "text/x-setext" }, + { "exe", 3, "application/octet-stream" }, + { "ez", 2, "application/andrew-inset" }, + { "f", 1, "text/plain" }, + { "f90", 3, "text/plain" }, + { "fli", 3, "video/fli" }, + { "flv", 3, "video/flv" }, + { "gif", 3, "image/gif" }, + { "gl", 2, "video/gl" }, + { "gtar", 4, "application/x-gtar" }, + { "gz", 2, "application/x-gzip" }, + { "h", 1, "text/plain" }, + { "hdf", 3, "application/x-hdf" }, + { "hh", 2, "text/plain" }, + { "hqx", 3, "application/mac-binhex40" }, + { "htm", 3, "text/html" }, + { "html", 4, "text/html" }, + { "ice", 3, "x-conference/x-cooltalk" }, + { "ief", 3, "image/ief" }, + { "iges", 4, "model/iges" }, + { "igs", 3, "model/iges" }, + { "ips", 3, "application/x-ipscript" }, + { "ipx", 3, "application/x-ipix" }, + { "jad", 3, "text/vnd.sun.j2me.app-descriptor" }, + { "jar", 3, "application/java-archive" }, + { "jpe", 3, "image/jpeg" }, + { "jpeg", 4, "image/jpeg" }, + { "jpg", 3, "image/jpeg" }, + { "js", 2, "application/x-javascript" }, + { "kar", 3, "audio/midi" }, + { "latex", 5, "application/x-latex" }, + { "lha", 3, "application/octet-stream" }, + { "lsp", 3, "application/x-lisp" }, + { "lzh", 3, "application/octet-stream" }, + { "m", 1, "text/plain" }, + { "m3u", 3, "audio/x-mpegurl" }, + { "man", 3, "application/x-troff-man" }, + { "markdown", 8, "text/x-markdown" }, + { "md", 2, "text/x-markdown" }, + { "me", 2, "application/x-troff-me" }, + { "mesh", 4, "model/mesh" }, + { "mid", 3, "audio/midi" }, + { "midi", 4, "audio/midi" }, + { "mif", 3, "application/x-mif" }, + { "mime", 4, "www/mime" }, + { "mkd", 3, "text/x-markdown" }, + { "mov", 3, "video/quicktime" }, + { "movie", 5, "video/x-sgi-movie" }, + { "mp2", 3, "audio/mpeg" }, + { "mp3", 3, "audio/mpeg" }, + { "mp4", 3, "video/mp4" }, + { "mpe", 3, "video/mpeg" }, + { "mpeg", 4, "video/mpeg" }, + { "mpg", 3, "video/mpeg" }, + { "mpga", 4, "audio/mpeg" }, + { "ms", 2, "application/x-troff-ms" }, + { "msh", 3, "model/mesh" }, + { "nc", 2, "application/x-netcdf" }, + { "oda", 3, "application/oda" }, + { "odp", 3, "application/vnd.oasis.opendocument.presentation" }, + { "ods", 3, "application/vnd.oasis.opendocument.spreadsheet" }, + { "odt", 3, "application/vnd.oasis.opendocument.text" }, + { "ogg", 3, "application/ogg" }, + { "ogm", 3, "application/ogg" }, + { "pbm", 3, "image/x-portable-bitmap" }, + { "pdb", 3, "chemical/x-pdb" }, + { "pdf", 3, "application/pdf" }, + { "pgm", 3, "image/x-portable-graymap" }, + { "pgn", 3, "application/x-chess-pgn" }, + { "pgp", 3, "application/pgp" }, + { "pl", 2, "application/x-perl" }, + { "pm", 2, "application/x-perl" }, + { "png", 3, "image/png" }, + { "pnm", 3, "image/x-portable-anymap" }, + { "pot", 3, "application/mspowerpoint" }, + { "potx", 4, "application/vnd.openxmlformats-" + "officedocument.presentationml.template"}, + { "ppm", 3, "image/x-portable-pixmap" }, + { "pps", 3, "application/mspowerpoint" }, + { "ppsx", 4, "application/vnd.openxmlformats-" + "officedocument.presentationml.slideshow"}, + { "ppt", 3, "application/mspowerpoint" }, + { "pptx", 4, "application/vnd.openxmlformats-" + "officedocument.presentationml.presentation"}, + { "ppz", 3, "application/mspowerpoint" }, + { "pre", 3, "application/x-freelance" }, + { "prt", 3, "application/pro_eng" }, + { "ps", 2, "application/postscript" }, + { "qt", 2, "video/quicktime" }, + { "ra", 2, "audio/x-realaudio" }, + { "ram", 3, "audio/x-pn-realaudio" }, + { "rar", 3, "application/x-rar-compressed" }, + { "ras", 3, "image/cmu-raster" }, + { "rgb", 3, "image/x-rgb" }, + { "rm", 2, "audio/x-pn-realaudio" }, + { "roff", 4, "application/x-troff" }, + { "rpm", 3, "audio/x-pn-realaudio-plugin" }, + { "rtf", 3, "text/rtf" }, + { "rtx", 3, "text/richtext" }, + { "scm", 3, "application/x-lotusscreencam" }, + { "set", 3, "application/set" }, + { "sgm", 3, "text/sgml" }, + { "sgml", 4, "text/sgml" }, + { "sh", 2, "application/x-sh" }, + { "shar", 4, "application/x-shar" }, + { "silo", 4, "model/mesh" }, + { "sit", 3, "application/x-stuffit" }, + { "skd", 3, "application/x-koan" }, + { "skm", 3, "application/x-koan" }, + { "skp", 3, "application/x-koan" }, + { "skt", 3, "application/x-koan" }, + { "smi", 3, "application/smil" }, + { "smil", 4, "application/smil" }, + { "snd", 3, "audio/basic" }, + { "sol", 3, "application/solids" }, + { "spl", 3, "application/x-futuresplash" }, + { "src", 3, "application/x-wais-source" }, + { "step", 4, "application/STEP" }, + { "stl", 3, "application/SLA" }, + { "stp", 3, "application/STEP" }, + { "sv4cpio", 7, "application/x-sv4cpio" }, + { "sv4crc", 6, "application/x-sv4crc" }, + { "svg", 3, "image/svg+xml" }, + { "swf", 3, "application/x-shockwave-flash" }, + { "t", 1, "application/x-troff" }, + { "tar", 3, "application/x-tar" }, + { "tcl", 3, "application/x-tcl" }, + { "tex", 3, "application/x-tex" }, + { "texi", 4, "application/x-texinfo" }, + { "texinfo", 7, "application/x-texinfo" }, + { "tgz", 3, "application/x-tar-gz" }, + { "th1", 3, "application/x-th1" }, + { "tif", 3, "image/tiff" }, + { "tiff", 4, "image/tiff" }, + { "tr", 2, "application/x-troff" }, + { "tsi", 3, "audio/TSP-audio" }, + { "tsp", 3, "application/dsptype" }, + { "tsv", 3, "text/tab-separated-values" }, + { "txt", 3, "text/plain" }, + { "unv", 3, "application/i-deas" }, + { "ustar", 5, "application/x-ustar" }, + { "vcd", 3, "application/x-cdlink" }, + { "vda", 3, "application/vda" }, + { "viv", 3, "video/vnd.vivo" }, + { "vivo", 4, "video/vnd.vivo" }, + { "vrml", 4, "model/vrml" }, + { "wav", 3, "audio/x-wav" }, + { "wax", 3, "audio/x-ms-wax" }, + { "wiki", 4, "text/x-fossil-wiki" }, + { "wma", 3, "audio/x-ms-wma" }, + { "wmv", 3, "video/x-ms-wmv" }, + { "wmx", 3, "video/x-ms-wmx" }, + { "wrl", 3, "model/vrml" }, + { "wvx", 3, "video/x-ms-wvx" }, + { "xbm", 3, "image/x-xbitmap" }, + { "xlc", 3, "application/vnd.ms-excel" }, + { "xll", 3, "application/vnd.ms-excel" }, + { "xlm", 3, "application/vnd.ms-excel" }, + { "xls", 3, "application/vnd.ms-excel" }, + { "xlsx", 4, "application/vnd.openxmlformats-" + "officedocument.spreadsheetml.sheet"}, + { "xlw", 3, "application/vnd.ms-excel" }, + { "xml", 3, "text/xml" }, + { "xpm", 3, "image/x-xpixmap" }, + { "xwd", 3, "image/x-xwindowdump" }, + { "xyz", 3, "chemical/x-pdb" }, + { "zip", 3, "application/zip" }, +}; + +/* +** Verify that all entries in the aMime[] table are in sorted order. +** Abort with a fatal error if any is out-of-order. +*/ +static void mimetype_verify(void){ + int i; + for(i=1; i<ArraySize(aMime); i++){ + if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){ + fossil_fatal("mimetypes out of sequence: %s before %s", + aMime[i-1].zSuffix, aMime[i].zSuffix); + } + } +} /* ** Guess the mime-type of a document based on its name. */ const char *mimetype_from_name(const char *zName){ @@ -83,230 +317,17 @@ int i; int first, last; int len; char zSuffix[20]; - /* A table of mimetypes based on file suffixes. - ** Suffixes must be in sorted order so that we can do a binary - ** search to find the mime-type - */ - static const struct { - const char *zSuffix; /* The file suffix */ - int size; /* Length of the suffix */ - const char *zMimetype; /* The corresponding mimetype */ - } aMime[] = { - { "ai", 2, "application/postscript" }, - { "aif", 3, "audio/x-aiff" }, - { "aifc", 4, "audio/x-aiff" }, - { "aiff", 4, "audio/x-aiff" }, - { "arj", 3, "application/x-arj-compressed" }, - { "asc", 3, "text/plain" }, - { "asf", 3, "video/x-ms-asf" }, - { "asx", 3, "video/x-ms-asx" }, - { "au", 2, "audio/ulaw" }, - { "avi", 3, "video/x-msvideo" }, - { "bat", 3, "application/x-msdos-program" }, - { "bcpio", 5, "application/x-bcpio" }, - { "bin", 3, "application/octet-stream" }, - { "c", 1, "text/plain" }, - { "cc", 2, "text/plain" }, - { "ccad", 4, "application/clariscad" }, - { "cdf", 3, "application/x-netcdf" }, - { "class", 5, "application/octet-stream" }, - { "cod", 3, "application/vnd.rim.cod" }, - { "com", 3, "application/x-msdos-program" }, - { "cpio", 4, "application/x-cpio" }, - { "cpt", 3, "application/mac-compactpro" }, - { "csh", 3, "application/x-csh" }, - { "css", 3, "text/css" }, - { "dcr", 3, "application/x-director" }, - { "deb", 3, "application/x-debian-package" }, - { "dir", 3, "application/x-director" }, - { "dl", 2, "video/dl" }, - { "dms", 3, "application/octet-stream" }, - { "doc", 3, "application/msword" }, - { "docx", 4, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, - { "dot", 3, "application/msword" }, - { "dotx", 4, "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, - { "drw", 3, "application/drafting" }, - { "dvi", 3, "application/x-dvi" }, - { "dwg", 3, "application/acad" }, - { "dxf", 3, "application/dxf" }, - { "dxr", 3, "application/x-director" }, - { "eps", 3, "application/postscript" }, - { "etx", 3, "text/x-setext" }, - { "exe", 3, "application/octet-stream" }, - { "ez", 2, "application/andrew-inset" }, - { "f", 1, "text/plain" }, - { "f90", 3, "text/plain" }, - { "fli", 3, "video/fli" }, - { "flv", 3, "video/flv" }, - { "gif", 3, "image/gif" }, - { "gl", 2, "video/gl" }, - { "gtar", 4, "application/x-gtar" }, - { "gz", 2, "application/x-gzip" }, - { "h", 1, "text/plain" }, - { "hdf", 3, "application/x-hdf" }, - { "hh", 2, "text/plain" }, - { "hqx", 3, "application/mac-binhex40" }, - { "htm", 3, "text/html" }, - { "html", 4, "text/html" }, - { "ice", 3, "x-conference/x-cooltalk" }, - { "ief", 3, "image/ief" }, - { "iges", 4, "model/iges" }, - { "igs", 3, "model/iges" }, - { "ips", 3, "application/x-ipscript" }, - { "ipx", 3, "application/x-ipix" }, - { "jad", 3, "text/vnd.sun.j2me.app-descriptor" }, - { "jar", 3, "application/java-archive" }, - { "jpe", 3, "image/jpeg" }, - { "jpeg", 4, "image/jpeg" }, - { "jpg", 3, "image/jpeg" }, - { "js", 2, "application/x-javascript" }, - { "kar", 3, "audio/midi" }, - { "latex", 5, "application/x-latex" }, - { "lha", 3, "application/octet-stream" }, - { "lsp", 3, "application/x-lisp" }, - { "lzh", 3, "application/octet-stream" }, - { "m", 1, "text/plain" }, - { "m3u", 3, "audio/x-mpegurl" }, - { "man", 3, "application/x-troff-man" }, - { "markdown", 8, "text/x-markdown" }, - { "md", 2, "text/x-markdown" }, - { "me", 2, "application/x-troff-me" }, - { "mesh", 4, "model/mesh" }, - { "mid", 3, "audio/midi" }, - { "midi", 4, "audio/midi" }, - { "mif", 3, "application/x-mif" }, - { "mime", 4, "www/mime" }, - { "mkd", 3, "text/x-markdown" }, - { "mov", 3, "video/quicktime" }, - { "movie", 5, "video/x-sgi-movie" }, - { "mp2", 3, "audio/mpeg" }, - { "mp3", 3, "audio/mpeg" }, - { "mp4", 3, "video/mp4" }, - { "mpe", 3, "video/mpeg" }, - { "mpeg", 4, "video/mpeg" }, - { "mpg", 3, "video/mpeg" }, - { "mpga", 4, "audio/mpeg" }, - { "ms", 2, "application/x-troff-ms" }, - { "msh", 3, "model/mesh" }, - { "nc", 2, "application/x-netcdf" }, - { "oda", 3, "application/oda" }, - { "ogg", 3, "application/ogg" }, - { "ogm", 3, "application/ogg" }, - { "pbm", 3, "image/x-portable-bitmap" }, - { "pdb", 3, "chemical/x-pdb" }, - { "pdf", 3, "application/pdf" }, - { "pgm", 3, "image/x-portable-graymap" }, - { "pgn", 3, "application/x-chess-pgn" }, - { "pgp", 3, "application/pgp" }, - { "pl", 2, "application/x-perl" }, - { "pm", 2, "application/x-perl" }, - { "png", 3, "image/png" }, - { "pnm", 3, "image/x-portable-anymap" }, - { "pot", 3, "application/mspowerpoint" }, - { "potx", 4, "application/vnd.openxmlformats-officedocument.presentationml.template"}, - { "ppm", 3, "image/x-portable-pixmap" }, - { "pps", 3, "application/mspowerpoint" }, - { "ppsx", 4, "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, - { "ppt", 3, "application/mspowerpoint" }, - { "pptx", 4, "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, - { "ppz", 3, "application/mspowerpoint" }, - { "pre", 3, "application/x-freelance" }, - { "prt", 3, "application/pro_eng" }, - { "ps", 2, "application/postscript" }, - { "qt", 2, "video/quicktime" }, - { "ra", 2, "audio/x-realaudio" }, - { "ram", 3, "audio/x-pn-realaudio" }, - { "rar", 3, "application/x-rar-compressed" }, - { "ras", 3, "image/cmu-raster" }, - { "rgb", 3, "image/x-rgb" }, - { "rm", 2, "audio/x-pn-realaudio" }, - { "roff", 4, "application/x-troff" }, - { "rpm", 3, "audio/x-pn-realaudio-plugin" }, - { "rtf", 3, "text/rtf" }, - { "rtx", 3, "text/richtext" }, - { "scm", 3, "application/x-lotusscreencam" }, - { "set", 3, "application/set" }, - { "sgm", 3, "text/sgml" }, - { "sgml", 4, "text/sgml" }, - { "sh", 2, "application/x-sh" }, - { "shar", 4, "application/x-shar" }, - { "silo", 4, "model/mesh" }, - { "sit", 3, "application/x-stuffit" }, - { "skd", 3, "application/x-koan" }, - { "skm", 3, "application/x-koan" }, - { "skp", 3, "application/x-koan" }, - { "skt", 3, "application/x-koan" }, - { "smi", 3, "application/smil" }, - { "smil", 4, "application/smil" }, - { "snd", 3, "audio/basic" }, - { "sol", 3, "application/solids" }, - { "spl", 3, "application/x-futuresplash" }, - { "src", 3, "application/x-wais-source" }, - { "step", 4, "application/STEP" }, - { "stl", 3, "application/SLA" }, - { "stp", 3, "application/STEP" }, - { "sv4cpio", 7, "application/x-sv4cpio" }, - { "sv4crc", 6, "application/x-sv4crc" }, - { "svg", 3, "image/svg+xml" }, - { "swf", 3, "application/x-shockwave-flash" }, - { "t", 1, "application/x-troff" }, - { "tar", 3, "application/x-tar" }, - { "tcl", 3, "application/x-tcl" }, - { "tex", 3, "application/x-tex" }, - { "texi", 4, "application/x-texinfo" }, - { "texinfo", 7, "application/x-texinfo" }, - { "tgz", 3, "application/x-tar-gz" }, - { "tif", 3, "image/tiff" }, - { "tiff", 4, "image/tiff" }, - { "tr", 2, "application/x-troff" }, - { "tsi", 3, "audio/TSP-audio" }, - { "tsp", 3, "application/dsptype" }, - { "tsv", 3, "text/tab-separated-values" }, - { "txt", 3, "text/plain" }, - { "unv", 3, "application/i-deas" }, - { "ustar", 5, "application/x-ustar" }, - { "vcd", 3, "application/x-cdlink" }, - { "vda", 3, "application/vda" }, - { "viv", 3, "video/vnd.vivo" }, - { "vivo", 4, "video/vnd.vivo" }, - { "vrml", 4, "model/vrml" }, - { "wav", 3, "audio/x-wav" }, - { "wax", 3, "audio/x-ms-wax" }, - { "wiki", 4, "text/x-fossil-wiki" }, - { "wma", 3, "audio/x-ms-wma" }, - { "wmv", 3, "video/x-ms-wmv" }, - { "wmx", 3, "video/x-ms-wmx" }, - { "wrl", 3, "model/vrml" }, - { "wvx", 3, "video/x-ms-wvx" }, - { "xbm", 3, "image/x-xbitmap" }, - { "xlc", 3, "application/vnd.ms-excel" }, - { "xll", 3, "application/vnd.ms-excel" }, - { "xlm", 3, "application/vnd.ms-excel" }, - { "xls", 3, "application/vnd.ms-excel" }, - { "xlsx", 4, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, - { "xlw", 3, "application/vnd.ms-excel" }, - { "xml", 3, "text/xml" }, - { "xpm", 3, "image/x-xpixmap" }, - { "xwd", 3, "image/x-xwindowdump" }, - { "xyz", 3, "chemical/x-pdb" }, - { "zip", 3, "application/zip" }, - }; #ifdef FOSSIL_DEBUG /* This is test code to make sure the table above is in the correct ** order */ if( fossil_strcmp(zName, "mimetype-test")==0 ){ - for(i=1; i<sizeof(aMime)/sizeof(aMime[0]); i++){ - if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){ - fossil_fatal("mimetypes out of sequence: %s before %s", - aMime[i-1].zSuffix, aMime[i].zSuffix); - } - } + mimetype_verify(); return "ok"; } #endif z = zName; @@ -316,11 +337,11 @@ len = strlen(z); if( len<sizeof(zSuffix)-1 ){ sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z); for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]); first = 0; - last = sizeof(aMime)/sizeof(aMime[0]) - 1; + last = ArraySize(aMime) - 1; while( first<=last ){ int c; i = (first+last)/2; c = fossil_strcmp(zSuffix, aMime[i].zSuffix); if( c==0 ) return aMime[i].zMimetype; @@ -345,269 +366,378 @@ ** filename is special and verifies the integrity of the mimetype table. ** It should return "ok". */ void mimetype_test_cmd(void){ int i; + mimetype_verify(); for(i=2; i<g.argc; i++){ fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i])); } } + +/* +** WEBPAGE: mimetype_list +** +** Show the built-in table used to guess embedded document mimetypes +** from file suffixes. +*/ +void mimetype_list_page(void){ + int i; + mimetype_verify(); + style_header("Mimetype List"); + @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename + @ suffixes and the following table to guess at the appropriate mimetype + @ for each document.</p> + @ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'> + @ <thead> + @ <tr><th>Suffix<th>Mimetype + @ </thead> + @ <tbody> + for(i=0; i<ArraySize(aMime); i++){ + @ <tr><td>%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr> + } + @ </tbody></table> + output_table_sorting_javascript("mimeTable","tt",1); + style_footer(); +} + +/* +** Check to see if the file in the pContent blob is "embedded HTML". Return +** true if it is, and fill pTitle with the document title. +** +** An "embedded HTML" file is HTML that lacks a header and a footer. The +** standard Fossil header is prepended and the standard Fossil footer is +** appended. Otherwise, the file is displayed without change. +** +** Embedded HTML must be contained in a <div class='fossil-doc'> element. +** If that <div> also contains a data-title attribute, then the +** value of that attribute is extracted into pTitle and becomes the title +** of the document. +*/ +int doc_is_embedded_html(Blob *pContent, Blob *pTitle){ + const char *zIn = blob_str(pContent); + const char *zAttr; + const char *zValue; + int nAttr, nValue; + int seenClass = 0; + int seenTitle = 0; + + while( fossil_isspace(zIn[0]) ) zIn++; + if( fossil_strnicmp(zIn,"<div",4)!=0 ) return 0; + zIn += 4; + while( zIn[0] ){ + if( fossil_isspace(zIn[0]) ) zIn++; + if( zIn[0]=='>' ) return 0; + zAttr = zIn; + while( fossil_isalnum(zIn[0]) || zIn[0]=='-' ) zIn++; + nAttr = (int)(zIn - zAttr); + while( fossil_isspace(zIn[0]) ) zIn++; + if( zIn[0]!='=' ) continue; + zIn++; + while( fossil_isspace(zIn[0]) ) zIn++; + if( zIn[0]=='"' || zIn[0]=='\'' ){ + char cDelim = zIn[0]; + zIn++; + zValue = zIn; + while( zIn[0] && zIn[0]!=cDelim ) zIn++; + if( zIn[0]==0 ) return 0; + nValue = (int)(zIn - zValue); + zIn++; + }else{ + zValue = zIn; + while( zIn[0]!=0 && zIn[0]!='>' && zIn[0]!='/' + && !fossil_isspace(zIn[0]) ) zIn++; + if( zIn[0]==0 ) return 0; + nValue = (int)(zIn - zValue); + } + if( nAttr==5 && fossil_strnicmp(zAttr,"class",5)==0 ){ + if( nValue!=10 || fossil_strnicmp(zValue,"fossil-doc",10)!=0 ) return 0; + seenClass = 1; + if( seenTitle ) return 1; + } + if( nAttr==10 && fossil_strnicmp(zAttr,"data-title",10)==0 ){ + blob_append(pTitle, zValue, nValue); + seenTitle = 1; + if( seenClass ) return 1; + } + } + return seenClass; +} + +/* +** Look for a file named zName in the check-in with RID=vid. Load the content +** of that file into pContent and return the RID for the file. Or return 0 +** if the file is not found or could not be loaded. +*/ +int doc_load_content(int vid, const char *zName, Blob *pContent){ + int rid; /* The RID of the file being loaded */ + if( !db_table_exists("repository","vcache") ){ + db_multi_exec( + "CREATE TABLE IF NOT EXISTS vcache(\n" + " vid INTEGER, -- check-in ID\n" + " fname TEXT, -- filename\n" + " rid INTEGER, -- artifact ID\n" + " PRIMARY KEY(vid,fname)\n" + ") WITHOUT ROWID" + ); + } + if( !db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){ + db_multi_exec( + "DELETE FROM vcache;\n" + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;\n" + "INSERT INTO vcache(vid,fname,rid)" + " SELECT checkinID, filename, blob.rid FROM foci, blob" + " WHERE blob.uuid=foci.uuid" + " AND foci.checkinID=%d;", + vid + ); + } + rid = db_int(0, "SELECT rid FROM vcache" + " WHERE vid=%d AND fname=%Q", vid, zName); + if( rid && content_get(rid, pContent)==0 ){ + rid = 0; + } + return rid; +} /* ** WEBPAGE: doc -** URL: /doc?name=BASELINE/PATH -** URL: /doc/BASELINE/PATH -** -** BASELINE can be either a baseline uuid prefix or magic words "tip" -** to mean the most recently checked in baseline or "ckout" to mean the -** content of the local checkout, if any. PATH is the relative pathname -** of some file. This method returns the file content. -** -** If PATH matches the patterns *.wiki or *.txt then formatting content -** is added before returning the file. For all other names, the content -** is returned straight without any interpretation or processing. +** URL: /doc?name=CHECKIN/FILE +** URL: /doc/CHECKIN/FILE +** +** CHECKIN can be either tag or SHA1 hash or timestamp identifying a +** particular check, or the name of a branch (meaning the most recent +** check-in on that branch) or one of various magic words: +** +** "tip" means the most recent check-in +** +** "ckout" means the current check-out, if the server is run from +** within a check-out, otherwise it is the same as "tip" +** +** FILE is the name of a file to delivered up as a webpage. FILE is relative +** to the root of the source tree of the repository. The FILE must +** be a part of CHECKIN, except when CHECKIN=="ckout" when FILE is read +** directly from disk and need not be a managed file. +** +** The "ckout" CHECKIN is intended for development - to provide a mechanism +** for looking at what a file will look like using the /doc webpage after +** it gets checked in. +** +** The file extension is used to decide how to render the file. +** +** If FILE ends in "/" then names "FILE/index.html", "FILE/index.wiki", +** and "FILE/index.md" are in that order. If none of those are found, +** then FILE is completely replaced by "404.md" and tried. If that is +** not found, then a default 404 screen is generated. */ void doc_page(void){ const char *zName; /* Argument to the /doc page */ + const char *zOrigName = "?"; /* Original document name */ const char *zMime; /* Document MIME type */ - int vid = 0; /* Artifact of baseline */ + char *zCheckin = "tip"; /* The check-in holding the document */ + int vid = 0; /* Artifact of check-in */ int rid = 0; /* Artifact of file */ int i; /* Loop counter */ Blob filebody; /* Content of the documentation file */ - char zBaseline[UUID_SIZE+1]; /* Baseline UUID */ - - login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } - zName = PD("name", "tip/index.wiki"); - for(i=0; zName[i] && zName[i]!='/'; i++){} - if( zName[i]==0 || i>UUID_SIZE ){ - zName = "index.html"; - goto doc_not_found; - } - g.zPath = mprintf("%s/%s", g.zPath, zName); - memcpy(zBaseline, zName, i); - zBaseline[i] = 0; - zName += i; - while( zName[0]=='/' ){ zName++; } - if( !file_is_simple_pathname(zName, 1) ){ - int n = strlen(zName); - if( n>0 && zName[n-1]=='/' ){ - zName = mprintf("%sindex.html", zName); - if( !file_is_simple_pathname(zName, 1) ){ - goto doc_not_found; - } - }else{ - goto doc_not_found; - } - } - if( fossil_strcmp(zBaseline,"ckout")==0 && db_open_local(0)==0 ){ - sqlite3_snprintf(sizeof(zBaseline), zBaseline, "tip"); - } - if( fossil_strcmp(zBaseline,"ckout")==0 ){ - /* Read from the local checkout */ - char *zFullpath; - db_must_be_within_tree(); - zFullpath = mprintf("%s/%s", g.zLocalRoot, zName); - if( !file_isfile(zFullpath) ){ - goto doc_not_found; - } - if( blob_read_from_file(&filebody, zFullpath)<0 ){ - goto doc_not_found; - } - }else{ - db_begin_transaction(); - if( fossil_strcmp(zBaseline,"tip")==0 ){ - vid = db_int(0, "SELECT objid FROM event WHERE type='ci'" - " ORDER BY mtime DESC LIMIT 1"); - }else{ - vid = name_to_typed_rid(zBaseline, "ci"); - } - - /* Create the baseline cache if it does not already exist */ - db_multi_exec( - "CREATE TABLE IF NOT EXISTS vcache(\n" - " vid INTEGER, -- baseline ID\n" - " fname TEXT, -- filename\n" - " rid INTEGER, -- artifact ID\n" - " UNIQUE(vid,fname,rid)\n" - ")" - ); - - - - /* Check to see if the documentation file artifact ID is contained - ** in the baseline cache */ - rid = db_int(0, "SELECT rid FROM vcache" - " WHERE vid=%d AND fname=%Q", vid, zName); - if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){ - goto doc_not_found; - } - - if( rid==0 ){ - Stmt s; - Manifest *pM; - ManifestFile *pFile; - - /* Add the vid baseline to the cache */ - if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){ - db_multi_exec("DELETE FROM vcache"); - } - pM = manifest_get(vid, CFTYPE_MANIFEST, 0); - if( pM==0 ){ - goto doc_not_found; - } - db_prepare(&s, - "INSERT INTO vcache(vid,fname,rid)" - " SELECT %d, :fname, rid FROM blob" - " WHERE uuid=:uuid", - vid - ); - manifest_file_rewind(pM); - while( (pFile = manifest_file_next(pM,0))!=0 ){ - db_bind_text(&s, ":fname", pFile->zName); - db_bind_text(&s, ":uuid", pFile->zUuid); - db_step(&s); - db_reset(&s); - } - db_finalize(&s); - manifest_destroy(pM); - - /* Try again to find the file */ - rid = db_int(0, "SELECT rid FROM vcache" - " WHERE vid=%d AND fname=%Q", vid, zName); - } - if( rid==0 ){ - goto doc_not_found; - } - - /* Get the file content */ - if( content_get(rid, &filebody)==0 ){ - goto doc_not_found; - } - db_end_transaction(0); - } - - /* The file is now contained in the filebody blob. Deliver the - ** file to the user - */ - zMime = P("mimetype"); + Blob title; /* Document title */ + int nMiss = (-1); /* Failed attempts to find the document */ + static const char *const azSuffix[] = { + "index.html", "index.wiki", "index.md" + }; + + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + blob_init(&title, 0, 0); + db_begin_transaction(); + while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){ + zName = PD("name", "tip/index.wiki"); + for(i=0; zName[i] && zName[i]!='/'; i++){} + zCheckin = mprintf("%.*s", i, zName); + if( fossil_strcmp(zCheckin,"ckout")==0 && db_open_local(0)==0 ){ + zCheckin = "tip"; + } + if( nMiss==ArraySize(azSuffix) ){ + zName = "404.md"; + }else if( zName[i]==0 ){ + assert( nMiss>=0 && nMiss<ArraySize(azSuffix) ); + zName = azSuffix[nMiss]; + }else{ + zName += i; + } + while( zName[0]=='/' ){ zName++; } + g.zPath = mprintf("%s/%s/%s", g.zPath, zCheckin, zName); + if( nMiss==0 ) zOrigName = zName; + if( !file_is_simple_pathname(zName, 1) ){ + if( sqlite3_strglob("*/", zName)==0 ){ + assert( nMiss>=0 && nMiss<ArraySize(azSuffix) ); + zName = mprintf("%s%s", zName, azSuffix[nMiss]); + if( !file_is_simple_pathname(zName, 1) ){ + goto doc_not_found; + } + }else{ + goto doc_not_found; + } + } + if( fossil_strcmp(zCheckin,"ckout")==0 ){ + /* Read from the local checkout */ + char *zFullpath; + db_must_be_within_tree(); + zFullpath = mprintf("%s/%s", g.zLocalRoot, zName); + if( file_isfile(zFullpath) + && blob_read_from_file(&filebody, zFullpath)>0 ){ + rid = 1; /* Fake RID just to get the loop to end */ + } + fossil_free(zFullpath); + }else{ + vid = name_to_typed_rid(zCheckin, "ci"); + rid = doc_load_content(vid, zName, &filebody); + } + } + if( rid==0 ) goto doc_not_found; + blob_to_utf8_no_bom(&filebody, 0); + + /* The file is now contained in the filebody blob. Deliver the + ** file to the user + */ + zMime = nMiss==0 ? P("mimetype") : 0; if( zMime==0 ){ zMime = mimetype_from_name(zName); } Th_Store("doc_name", zName); Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" " FROM blob WHERE rid=%d", vid)); Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" " WHERE objid=%d AND type='ci'", vid)); if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){ - Blob title, tail; + Blob tail; + style_adunit_config(ADUNIT_RIGHT_OK); if( wiki_find_title(&filebody, &title, &tail) ){ - style_header(blob_str(&title)); + style_header("%s", blob_str(&title)); wiki_convert(&tail, 0, WIKI_BUTTONS); }else{ style_header("Documentation"); wiki_convert(&filebody, 0, WIKI_BUTTONS); } style_footer(); }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){ - Blob title = BLOB_INITIALIZER; Blob tail = BLOB_INITIALIZER; markdown_to_html(&filebody, &title, &tail); if( blob_size(&title)>0 ){ - style_header(blob_str(&title)); + style_header("%s", blob_str(&title)); }else{ - style_header("Documentation"); + style_header("%s", nMiss>=ArraySize(azSuffix)? + "Not Found" : "Documentation"); } blob_append(cgi_output_blob(), blob_buffer(&tail), blob_size(&tail)); style_footer(); }else if( fossil_strcmp(zMime, "text/plain")==0 ){ style_header("Documentation"); @ <blockquote><pre> @ %h(blob_str(&filebody)) @ </pre></blockquote> style_footer(); + }else if( fossil_strcmp(zMime, "text/html")==0 + && doc_is_embedded_html(&filebody, &title) ){ + if( blob_size(&title)==0 ) blob_append(&title,zName,-1); + style_header("%s", blob_str(&title)); + blob_append(cgi_output_blob(), blob_buffer(&filebody),blob_size(&filebody)); + style_footer(); +#ifdef FOSSIL_ENABLE_TH1_DOCS + }else if( Th_AreDocsEnabled() && + fossil_strcmp(zMime, "application/x-th1")==0 ){ + style_header("%h", zName); + Th_Render(blob_str(&filebody)); + style_footer(); +#endif }else{ cgi_set_content_type(zMime); cgi_set_content(&filebody); } + if( nMiss>=ArraySize(azSuffix) ) cgi_set_status(404, "Not Found"); + db_end_transaction(0); return; -doc_not_found: /* Jump here when unable to locate the document */ +doc_not_found: db_end_transaction(0); - style_header("Document Not Found"); - @ <p>No such document: %h(zName)</p> + cgi_set_status(404, "Not Found"); + style_header("Not Found"); + @ <p>Document %h(zOrigName) not found + if( fossil_strcmp(zCheckin,"ckout")!=0 ){ + @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a> + } style_footer(); - return; + db_end_transaction(0); + return; } /* ** The default logo. */ static const unsigned char aLogo[] = { - 71, 73, 70, 56, 55, 97, 62, 0, 71, 0, 244, 0, 0, 85, - 129, 149, 95, 136, 155, 99, 139, 157, 106, 144, 162, 113, 150, 166, - 116, 152, 168, 127, 160, 175, 138, 168, 182, 148, 176, 188, 159, 184, - 195, 170, 192, 202, 180, 199, 208, 184, 202, 210, 191, 207, 215, 201, - 215, 221, 212, 223, 228, 223, 231, 235, 226, 227, 226, 226, 234, 237, - 233, 239, 241, 240, 244, 246, 244, 247, 248, 255, 255, 255, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, - 0, 0, 62, 0, 71, 0, 0, 5, 255, 96, 100, 141, 100, 105, - 158, 168, 37, 41, 132, 192, 164, 112, 44, 207, 102, 99, 0, 56, - 16, 84, 116, 239, 199, 141, 65, 110, 232, 248, 25, 141, 193, 161, - 82, 113, 108, 202, 32, 55, 229, 210, 73, 61, 41, 164, 88, 102, - 181, 10, 41, 96, 179, 91, 106, 35, 240, 5, 135, 143, 137, 242, - 87, 123, 246, 33, 190, 81, 108, 163, 237, 198, 14, 30, 113, 233, - 131, 78, 115, 72, 11, 115, 87, 101, 19, 124, 51, 66, 74, 8, - 19, 16, 67, 100, 74, 133, 50, 15, 101, 135, 56, 11, 74, 6, - 143, 49, 126, 106, 56, 8, 145, 67, 9, 152, 48, 139, 155, 5, - 22, 13, 74, 115, 161, 41, 147, 101, 13, 130, 57, 132, 170, 40, - 167, 155, 0, 94, 57, 3, 178, 48, 183, 181, 57, 160, 186, 40, - 19, 141, 189, 0, 69, 192, 40, 16, 195, 155, 185, 199, 41, 201, - 189, 191, 205, 193, 188, 131, 210, 49, 175, 88, 209, 214, 38, 19, - 3, 11, 19, 111, 127, 60, 219, 39, 55, 204, 19, 11, 6, 100, - 5, 10, 227, 228, 37, 163, 0, 239, 117, 56, 238, 243, 49, 195, - 177, 247, 48, 158, 56, 251, 50, 216, 254, 197, 56, 128, 107, 158, - 2, 125, 171, 114, 92, 218, 246, 96, 66, 3, 4, 50, 134, 176, - 145, 6, 97, 64, 144, 24, 19, 136, 108, 91, 177, 160, 0, 194, - 19, 253, 0, 216, 107, 214, 224, 192, 129, 5, 16, 83, 255, 244, - 43, 213, 195, 24, 159, 27, 169, 64, 230, 88, 208, 227, 129, 182, - 54, 4, 89, 158, 24, 181, 163, 199, 1, 155, 52, 233, 8, 130, - 176, 83, 24, 128, 137, 50, 18, 32, 48, 48, 114, 11, 173, 137, - 19, 110, 4, 64, 105, 1, 194, 30, 140, 68, 15, 24, 24, 224, - 50, 76, 70, 0, 11, 171, 54, 26, 160, 181, 194, 149, 148, 40, - 174, 148, 122, 64, 180, 208, 161, 17, 207, 112, 164, 1, 128, 96, - 148, 78, 18, 21, 194, 33, 229, 51, 247, 65, 133, 97, 5, 250, - 69, 229, 100, 34, 220, 128, 166, 116, 190, 62, 8, 167, 195, 170, - 47, 163, 0, 130, 90, 152, 11, 160, 173, 170, 27, 154, 26, 91, - 232, 151, 171, 18, 14, 162, 253, 98, 170, 18, 70, 171, 64, 219, - 10, 67, 136, 134, 187, 116, 75, 180, 46, 179, 174, 135, 4, 189, - 229, 231, 78, 40, 10, 62, 226, 164, 172, 64, 240, 167, 170, 10, - 18, 124, 188, 10, 107, 65, 193, 94, 11, 93, 171, 28, 248, 17, - 239, 46, 140, 78, 97, 34, 25, 153, 36, 99, 65, 130, 7, 203, - 183, 168, 51, 34, 136, 25, 140, 10, 6, 16, 28, 255, 145, 241, - 230, 140, 10, 66, 178, 167, 112, 48, 192, 128, 129, 9, 31, 141, - 84, 138, 63, 163, 162, 2, 203, 206, 240, 56, 55, 98, 192, 188, - 15, 185, 50, 160, 6, 0, 125, 62, 33, 214, 195, 33, 5, 24, - 184, 25, 231, 14, 201, 245, 144, 23, 126, 104, 228, 0, 145, 2, - 13, 140, 244, 212, 17, 21, 20, 176, 159, 17, 95, 225, 160, 128, - 16, 1, 32, 224, 142, 32, 227, 125, 87, 64, 0, 16, 54, 129, - 205, 2, 141, 76, 53, 130, 103, 37, 166, 64, 144, 107, 78, 196, - 5, 192, 0, 54, 50, 229, 9, 141, 49, 84, 194, 35, 12, 196, - 153, 48, 192, 137, 57, 84, 24, 7, 87, 159, 249, 240, 215, 143, - 105, 241, 118, 149, 9, 139, 4, 64, 203, 141, 35, 140, 129, 131, - 16, 222, 125, 231, 128, 2, 238, 17, 152, 66, 3, 5, 56, 224, - 159, 103, 16, 76, 25, 75, 5, 11, 164, 215, 96, 9, 14, 16, - 36, 225, 15, 11, 40, 144, 192, 156, 41, 10, 178, 199, 3, 66, - 64, 80, 193, 3, 124, 90, 48, 129, 129, 102, 177, 18, 192, 154, - 49, 84, 240, 208, 92, 22, 149, 96, 39, 9, 31, 74, 17, 94, - 3, 8, 177, 199, 72, 59, 85, 76, 25, 216, 8, 139, 194, 197, - 138, 163, 69, 96, 115, 0, 147, 72, 72, 84, 28, 14, 79, 86, - 233, 230, 23, 113, 26, 160, 128, 3, 10, 58, 129, 103, 14, 159, - 214, 163, 146, 117, 238, 213, 154, 128, 151, 109, 84, 64, 217, 13, - 27, 10, 228, 39, 2, 235, 164, 168, 74, 8, 0, 59, + 71, 73, 70, 56, 55, 97, 62, 0, 71, 0, 244, 0, 0, 85, + 129, 149, 95, 136, 155, 99, 139, 157, 106, 144, 162, 113, 150, 166, + 116, 152, 168, 127, 160, 175, 138, 168, 182, 148, 176, 188, 159, 184, + 195, 170, 192, 202, 180, 199, 208, 184, 202, 210, 191, 207, 215, 201, + 215, 221, 212, 223, 228, 223, 231, 235, 226, 227, 226, 226, 234, 237, + 233, 239, 241, 240, 244, 246, 244, 247, 248, 255, 255, 255, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, + 0, 0, 62, 0, 71, 0, 0, 5, 255, 96, 100, 141, 100, 105, + 158, 168, 37, 41, 132, 192, 164, 112, 44, 207, 102, 99, 0, 56, + 16, 84, 116, 239, 199, 141, 65, 110, 232, 248, 25, 141, 193, 161, + 82, 113, 108, 202, 32, 55, 229, 210, 73, 61, 41, 164, 88, 102, + 181, 10, 41, 96, 179, 91, 106, 35, 240, 5, 135, 143, 137, 242, + 87, 123, 246, 33, 190, 81, 108, 163, 237, 198, 14, 30, 113, 233, + 131, 78, 115, 72, 11, 115, 87, 101, 19, 124, 51, 66, 74, 8, + 19, 16, 67, 100, 74, 133, 50, 15, 101, 135, 56, 11, 74, 6, + 143, 49, 126, 106, 56, 8, 145, 67, 9, 152, 48, 139, 155, 5, + 22, 13, 74, 115, 161, 41, 147, 101, 13, 130, 57, 132, 170, 40, + 167, 155, 0, 94, 57, 3, 178, 48, 183, 181, 57, 160, 186, 40, + 19, 141, 189, 0, 69, 192, 40, 16, 195, 155, 185, 199, 41, 201, + 189, 191, 205, 193, 188, 131, 210, 49, 175, 88, 209, 214, 38, 19, + 3, 11, 19, 111, 127, 60, 219, 39, 55, 204, 19, 11, 6, 100, + 5, 10, 227, 228, 37, 163, 0, 239, 117, 56, 238, 243, 49, 195, + 177, 247, 48, 158, 56, 251, 50, 216, 254, 197, 56, 128, 107, 158, + 2, 125, 171, 114, 92, 218, 246, 96, 66, 3, 4, 50, 134, 176, + 145, 6, 97, 64, 144, 24, 19, 136, 108, 91, 177, 160, 0, 194, + 19, 253, 0, 216, 107, 214, 224, 192, 129, 5, 16, 83, 255, 244, + 43, 213, 195, 24, 159, 27, 169, 64, 230, 88, 208, 227, 129, 182, + 54, 4, 89, 158, 24, 181, 163, 199, 1, 155, 52, 233, 8, 130, + 176, 83, 24, 128, 137, 50, 18, 32, 48, 48, 114, 11, 173, 137, + 19, 110, 4, 64, 105, 1, 194, 30, 140, 68, 15, 24, 24, 224, + 50, 76, 70, 0, 11, 171, 54, 26, 160, 181, 194, 149, 148, 40, + 174, 148, 122, 64, 180, 208, 161, 17, 207, 112, 164, 1, 128, 96, + 148, 78, 18, 21, 194, 33, 229, 51, 247, 65, 133, 97, 5, 250, + 69, 229, 100, 34, 220, 128, 166, 116, 190, 62, 8, 167, 195, 170, + 47, 163, 0, 130, 90, 152, 11, 160, 173, 170, 27, 154, 26, 91, + 232, 151, 171, 18, 14, 162, 253, 98, 170, 18, 70, 171, 64, 219, + 10, 67, 136, 134, 187, 116, 75, 180, 46, 179, 174, 135, 4, 189, + 229, 231, 78, 40, 10, 62, 226, 164, 172, 64, 240, 167, 170, 10, + 18, 124, 188, 10, 107, 65, 193, 94, 11, 93, 171, 28, 248, 17, + 239, 46, 140, 78, 97, 34, 25, 153, 36, 99, 65, 130, 7, 203, + 183, 168, 51, 34, 136, 25, 140, 10, 6, 16, 28, 255, 145, 241, + 230, 140, 10, 66, 178, 167, 112, 48, 192, 128, 129, 9, 31, 141, + 84, 138, 63, 163, 162, 2, 203, 206, 240, 56, 55, 98, 192, 188, + 15, 185, 50, 160, 6, 0, 125, 62, 33, 214, 195, 33, 5, 24, + 184, 25, 231, 14, 201, 245, 144, 23, 126, 104, 228, 0, 145, 2, + 13, 140, 244, 212, 17, 21, 20, 176, 159, 17, 95, 225, 160, 128, + 16, 1, 32, 224, 142, 32, 227, 125, 87, 64, 0, 16, 54, 129, + 205, 2, 141, 76, 53, 130, 103, 37, 166, 64, 144, 107, 78, 196, + 5, 192, 0, 54, 50, 229, 9, 141, 49, 84, 194, 35, 12, 196, + 153, 48, 192, 137, 57, 84, 24, 7, 87, 159, 249, 240, 215, 143, + 105, 241, 118, 149, 9, 139, 4, 64, 203, 141, 35, 140, 129, 131, + 16, 222, 125, 231, 128, 2, 238, 17, 152, 66, 3, 5, 56, 224, + 159, 103, 16, 76, 25, 75, 5, 11, 164, 215, 96, 9, 14, 16, + 36, 225, 15, 11, 40, 144, 192, 156, 41, 10, 178, 199, 3, 66, + 64, 80, 193, 3, 124, 90, 48, 129, 129, 102, 177, 18, 192, 154, + 49, 84, 240, 208, 92, 22, 149, 96, 39, 9, 31, 74, 17, 94, + 3, 8, 177, 199, 72, 59, 85, 76, 25, 216, 8, 139, 194, 197, + 138, 163, 69, 96, 115, 0, 147, 72, 72, 84, 28, 14, 79, 86, + 233, 230, 23, 113, 26, 160, 128, 3, 10, 58, 129, 103, 14, 159, + 214, 163, 146, 117, 238, 213, 154, 128, 151, 109, 84, 64, 217, 13, + 27, 10, 228, 39, 2, 235, 164, 168, 74, 8, 0, 59, }; /* ** WEBPAGE: logo ** @@ -660,5 +790,18 @@ } cgi_set_content_type(zMime); cgi_set_content(&bgimg); g.isConst = 1; } + + +/* +** WEBPAGE: /docsrch +** +** Search for documents that match a user-supplied pattern. +*/ +void doc_search_page(void){ + login_check_credentials(); + style_header("Document Search"); + search_screen(SRCH_DOC, 0); + style_footer(); +} Index: src/encode.c ================================================================== --- src/encode.c +++ src/encode.c @@ -47,30 +47,30 @@ } i = 0; zOut = fossil_malloc( count+1 ); while( n-->0 && (c = *zIn)!=0 ){ switch( c ){ - case '<': + case '<': zOut[i++] = '&'; zOut[i++] = 'l'; zOut[i++] = 't'; zOut[i++] = ';'; break; - case '>': + case '>': zOut[i++] = '&'; zOut[i++] = 'g'; zOut[i++] = 't'; zOut[i++] = ';'; break; - case '&': + case '&': zOut[i++] = '&'; zOut[i++] = 'a'; zOut[i++] = 'm'; zOut[i++] = 'p'; zOut[i++] = ';'; break; - case '"': + case '"': zOut[i++] = '&'; zOut[i++] = 'q'; zOut[i++] = 'u'; zOut[i++] = 'o'; zOut[i++] = 't'; @@ -181,11 +181,11 @@ /* ** Convert the input string into a form that is suitable for use as ** a token in the HTTP protocol. Spaces are encoded as '+' and special ** characters are encoded as "%HH" where HH is a two-digit hexidecimal ** representation of the character. The "/" character is not encoded -** by this routine. +** by this routine. */ char *urlize(const char *z, int n){ return EncodeHttp(z, n, 0); } @@ -327,11 +327,11 @@ /* ** The characters used for HTTP base64 encoding. */ -static unsigned char zBase[] = +static unsigned char zBase[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /* ** Encode a string using a base-64 encoding. ** The encoding can be reversed using the <b>decode64</b> function. @@ -366,11 +366,11 @@ z64[n] = 0; return z64; } /* -** COMMAND: test-encode64 +** COMMAND: test-encode64 ** Usage: %fossil test-encode64 STRING */ void test_encode64_cmd(void){ char *z; int i; @@ -431,11 +431,11 @@ *pnByte = j; return zData; } /* -** COMMAND: test-decode64 +** COMMAND: test-decode64 ** Usage: %fossil test-decode64 STRING */ void test_decode64_cmd(void){ char *z; int i, n; @@ -454,11 +454,11 @@ */ /* ** The array used for encoding */ /* 123456789 12345 */ -static const char zEncode[] = "0123456789abcdef"; +static const char zEncode[] = "0123456789abcdef"; /* ** Encode a N-digit base-256 in base-16. Return zero on success ** and non-zero if there is an error. */ @@ -473,33 +473,33 @@ } /* ** An array for translating single base-16 characters into a value. ** Disallowed input characters have a value of 64. Upper and lower -** case is the same. +** case is the same. */ static const char zDecode[] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 64, 64, 64, 64, 64, 64, 64, 10, 11, 12, 13, 14, 15, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 10, 11, 12, 13, 14, 15, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, }; /* -** Decode a N-character base-16 number into base-256. N must be a +** Decode a N-character base-16 number into base-256. N must be a ** multiple of 2. The output buffer must be at least N/2 characters ** in length */ int decode16(const unsigned char *zIn, unsigned char *pOut, int N){ int i, j; @@ -545,11 +545,11 @@ /* Randomness used for XOR-ing by the obscure() and unobscure() routines */ static const unsigned char aObscurer[16] = { 0xa7, 0x21, 0x31, 0xe3, 0x2a, 0x50, 0x2c, 0x86, 0x4c, 0xa4, 0x52, 0x25, 0xff, 0x49, 0x35, 0x85 }; - + /* ** Obscure plain text so that it is not easily readable. ** ** This is used for storing sensitive information (such as passwords) in a @@ -562,11 +562,11 @@ */ char *obscure(const char *zIn){ int n, i; unsigned char salt; char *zOut; - + if( zIn==0 ) return 0; n = strlen(zIn); zOut = fossil_malloc( n*2+3 ); sqlite3_randomness(1, &salt); zOut[n+1] = (char)salt; @@ -578,17 +578,17 @@ /* ** Undo the obscuring of text performed by obscure(). Or, if the input is ** not hexadecimal (meaning the input is not the output of obscure()) then ** do the equivalent of strdup(). ** -** The result is memory obtained from malloc that should be freed by the caller. +** The result is memory obtained from malloc that should be freed by the caller. */ char *unobscure(const char *zIn){ int n, i; unsigned char salt; char *zOut; - + if( zIn==0 ) return 0; n = strlen(zIn); zOut = fossil_malloc( n + 1 ); if( n<2 || decode16((unsigned char*)zIn, &salt, 2) Index: src/event.c ================================================================== --- src/event.c +++ src/event.c @@ -15,78 +15,90 @@ ** ******************************************************************************* ** ** This file contains code to do formatting of event messages: ** +** Technical Notes ** Milestones ** Blog posts ** New articles ** Process checkpoints ** Announcements -*/ -#include <assert.h> -#include <ctype.h> -#include "config.h" -#include "event.h" - -/* -** Output a hyperlink to an event given its tagid. -*/ -void hyperlink_to_event_tagid(int tagid){ - char *zEventId; - zEventId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d", - tagid); - @ [%z(href("%R/event/%s",zEventId))%S(zEventId)</a>] - free(zEventId); -} - -/* -** WEBPAGE: event -** URL: /event -** PARAMETERS: -** -** name=EVENTID // Identify the event to display EVENTID must be complete -** aid=ARTIFACTID // Which specific version of the event. Optional. -** v=BOOLEAN // Show details if TRUE. Default is FALSE. Optional. +** +** Do not confuse "event" artifacts with the "event" table in the +** repository database. An "event" artifact is a technical-note: a +** wiki- or blog-like essay that appears on the timeline. The "event" +** table records all entries on the timeline, including tech-notes. +** +** (2015-02-14): Changing the name to "tech-note" most everywhere. +*/ +#include "config.h" +#include <assert.h> +#include <ctype.h> +#include "event.h" + +/* +** Output a hyperlink to an technote given its tagid. +*/ +void hyperlink_to_event_tagid(int tagid){ + char *zId; + zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d", + tagid); + @ [%z(href("%R/technote/%s",zId))%S(zId)</a>] + free(zId); +} + +/* +** WEBPAGE: technote +** WEBPAGE: event +** +** Display a "technical note" or "tech-note" (formerly called an "event"). +** +** PARAMETERS: +** +** name=ID // Identify the tech-note to display. ID must be complete +** aid=ARTIFACTID // Which specific version of the tech-note. Optional. +** v=BOOLEAN // Show details if TRUE. Default is FALSE. Optional. ** ** Display an existing event identified by EVENTID */ void event_page(void){ int rid = 0; /* rid of the event artifact */ char *zUuid; /* UUID corresponding to rid */ - const char *zEventId; /* Event identifier */ + const char *zId; /* Event identifier */ const char *zVerbose; /* Value of verbose option */ - char *zETime; /* Time of the event */ + char *zETime; /* Time of the tech-note */ char *zATime; /* Time the artifact was created */ int specRid; /* rid specified by aid= parameter */ - int prevRid, nextRid; /* Previous or next edits of this event */ - Manifest *pEvent; /* Parsed event artifact */ - Blob fullbody; /* Complete content of the event body */ - Blob title; /* Title extracted from the event body */ + int prevRid, nextRid; /* Previous or next edits of this tech-note */ + Manifest *pTNote; /* Parsed technote artifact */ + Blob fullbody; /* Complete content of the technote body */ + Blob title; /* Title extracted from the technote body */ Blob tail; /* Event body that comes after the title */ - Stmt q1; /* Query to search for the event */ + Stmt q1; /* Query to search for the technote */ int verboseFlag; /* True to show details */ + const char *zMimetype = 0; /* Mimetype of the document */ - /* wiki-read privilege is needed in order to read events. + /* wiki-read privilege is needed in order to read tech-notes. */ login_check_credentials(); if( !g.perm.RdWiki ){ - login_needed(); + login_needed(g.anon.RdWiki); return; } - zEventId = P("name"); - if( zEventId==0 ){ fossil_redirect_home(); return; } + zId = P("name"); + if( zId==0 ){ fossil_redirect_home(); return; } zUuid = (char*)P("aid"); specRid = zUuid ? uuid_to_rid(zUuid, 0) : 0; rid = nextRid = prevRid = 0; db_prepare(&q1, "SELECT rid FROM tagxref" " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB 'event-%q*')" " ORDER BY mtime DESC", - zEventId + zId ); while( db_step(&q1)==SQLITE_ROW ){ nextRid = rid; rid = db_column_int(&q1, 0); if( specRid==0 || specRid==rid ){ @@ -96,12 +108,12 @@ break; } } db_finalize(&q1); if( rid==0 || (specRid!=0 && specRid!=rid) ){ - style_header("No Such Event"); - @ Cannot locate specified event + style_header("No Such Tech-Note"); + @ Cannot locate a technical note called <b>%h(zId)</b>. style_footer(); return; } zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); zVerbose = P("v"); @@ -113,161 +125,194 @@ } verboseFlag = (zVerbose!=0) && !is_false(zVerbose); /* Extract the event content. */ - pEvent = manifest_get(rid, CFTYPE_EVENT, 0); - if( pEvent==0 ){ - fossil_fatal("Object #%d is not an event", rid); + pTNote = manifest_get(rid, CFTYPE_EVENT, 0); + if( pTNote==0 ){ + fossil_fatal("Object #%d is not a tech-note", rid); } - blob_init(&fullbody, pEvent->zWiki, -1); - if( wiki_find_title(&fullbody, &title, &tail) ){ - style_header(blob_str(&title)); + zMimetype = wiki_filter_mimetypes(PD("mimetype",pTNote->zMimetype)); + blob_init(&fullbody, pTNote->zWiki, -1); + blob_init(&title, 0, 0); + blob_init(&tail, 0, 0); + if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ + if( !wiki_find_title(&fullbody, &title, &tail) ){ + blob_appendf(&title, "Tech-note %S", zId); + tail = fullbody; + } + }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ + markdown_to_html(&fullbody, &title, &tail); + if( blob_size(&title)==0 ){ + blob_appendf(&title, "Tech-note %S", zId); + } }else{ - style_header("Event %S", zEventId); + blob_appendf(&title, "Tech-note %S", zId); tail = fullbody; } + style_header("%s", blob_str(&title)); if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){ - style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s", - g.zTop, zEventId); + style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId); } - zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); - style_submenu_element("Context", "Context", "%s/timeline?c=%T", - g.zTop, zETime); + zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate); + style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId); if( g.perm.Hyperlink ){ if( verboseFlag ){ - style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", - g.zTop, zEventId, zUuid); + style_submenu_element("Plain", 0, + "%R/technote?name=%!S&aid=%s&mimetype=text/plain", + zId, zUuid); if( nextRid ){ char *zNext; zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid); - style_submenu_element("Next", "Next", - "%s/event?name=%s&aid=%s&v", - g.zTop, zEventId, zNext); + style_submenu_element("Next", 0,"%R/technote?name=%!S&aid=%s&v", + zId, zNext); free(zNext); } if( prevRid ){ char *zPrev; zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid); - style_submenu_element("Prev", "Prev", - "%s/event?name=%s&aid=%s&v", - g.zTop, zEventId, zPrev); + style_submenu_element("Prev", 0, "%R/technote?name=%!S&aid=%s&v", + zId, zPrev); free(zPrev); } }else{ - style_submenu_element("Detail", "Detail", - "%s/event?name=%s&aid=%s&v", - g.zTop, zEventId, zUuid); + style_submenu_element("Detail", 0, "%R/technote?name=%!S&aid=%s&v", + zId, zUuid); } } if( verboseFlag && g.perm.Hyperlink ){ int i; const char *zClr = 0; Blob comment; - zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate); - @ <p>Event [%z(href("%R/artifact/%s",zUuid))%S(zUuid)</a>] at + zATime = db_text(0, "SELECT datetime(%.17g)", pTNote->rDate); + @ <p>Tech-note [%z(href("%R/artifact/%!S",zUuid))%S(zUuid)</a>] at @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>] - @ entered by user <b>%h(pEvent->zUser)</b> on + @ entered by user <b>%h(pTNote->zUser)</b> on @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p> @ <blockquote> - for(i=0; i<pEvent->nTag; i++){ - if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){ - zClr = pEvent->aTag[i].zValue; + for(i=0; i<pTNote->nTag; i++){ + if( fossil_strcmp(pTNote->aTag[i].zName,"+bgcolor")==0 ){ + zClr = pTNote->aTag[i].zValue; } } if( zClr && zClr[0]==0 ) zClr = 0; if( zClr ){ @ <div style="background-color: %h(zClr);"> }else{ @ <div> } - blob_init(&comment, pEvent->zComment, -1); + blob_init(&comment, pTNote->zComment, -1); wiki_convert(&comment, 0, WIKI_INLINE); blob_reset(&comment); @ </div> @ </blockquote><hr /> - } + } - wiki_convert(&tail, 0, 0); + if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ + wiki_convert(&fullbody, 0, 0); + }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ + cgi_append_content(blob_buffer(&tail), blob_size(&tail)); + }else{ + @ <pre> + @ %h(blob_str(&fullbody)) + @ </pre> + } style_footer(); - manifest_destroy(pEvent); + manifest_destroy(pTNote); } /* +** WEBPAGE: technoteedit ** WEBPAGE: eventedit -** URL: /eventedit?name=EVENTID +** +** Revise or create a technical note (formerly called an 'event'). +** +** Parameters: ** -** Edit an event. If name is omitted, create a new event. +** name=ID Hex hash ID of the tech-note. If omitted, a new +** tech-note is created. */ void eventedit_page(void){ char *zTag; int rid = 0; Blob event; - const char *zEventId; - char *zHtmlPageName; + const char *zId; int n; const char *z; char *zBody = (char*)P("w"); char *zETime = (char*)P("t"); const char *zComment = P("c"); const char *zTags = P("g"); const char *zClr; + const char *zMimetype = P("mimetype"); + int isNew = 0; if( zBody ){ zBody = mprintf("%s", zBody); } login_check_credentials(); - zEventId = P("name"); - if( zEventId==0 ){ - zEventId = db_text(0, "SELECT lower(hex(randomblob(20)))"); + zId = P("name"); + if( zId==0 ){ + zId = db_text(0, "SELECT lower(hex(randomblob(20)))"); + isNew = 1; }else{ - int nEventId = strlen(zEventId); - if( nEventId!=40 || !validate16(zEventId, 40) ){ + int nId = strlen(zId); + if( !validate16(zId, nId) ){ fossil_redirect_home(); return; } } - zTag = mprintf("event-%s", zEventId); - rid = db_int(0, + zTag = mprintf("event-%s", zId); + rid = db_int(0, "SELECT rid FROM tagxref" - " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" + " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB '%q*')" " ORDER BY mtime DESC", zTag ); + if( rid && strlen(zId)<40 ){ + zId = db_text(0, + "SELECT substr(tagname,7) FROM tag WHERE tagname GLOB '%q*'", + zTag + ); + } free(zTag); /* Need both check-in and wiki-write or wiki-create privileges in order ** to edit/create an event. */ if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){ - login_needed(); + login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki)); return; } /* Figure out the color */ if( rid ){ - zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid); + zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid); }else{ zClr = ""; + isNew = 1; } zClr = PD("clr",zClr); if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr",""); /* If editing an existing event, extract the key fields to use as ** a starting point for the edit. */ - if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){ - Manifest *pEvent; - pEvent = manifest_get(rid, CFTYPE_EVENT, 0); - if( pEvent && pEvent->type==CFTYPE_EVENT ){ - if( zBody==0 ) zBody = pEvent->zWiki; + if( rid + && (zBody==0 || zETime==0 || zComment==0 || zTags==0 || zMimetype==0) + ){ + Manifest *pTNote; + pTNote = manifest_get(rid, CFTYPE_EVENT, 0); + if( pTNote && pTNote->type==CFTYPE_EVENT ){ + if( zBody==0 ) zBody = pTNote->zWiki; if( zETime==0 ){ - zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); + zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate); } - if( zComment==0 ) zComment = pEvent->zComment; + if( zComment==0 ) zComment = pTNote->zComment; + if( zMimetype==0 ) zMimetype = pTNote->zMimetype; } if( zTags==0 ){ zTags = db_text(0, "SELECT group_concat(substr(tagname,5),', ')" " FROM tagxref, tag" @@ -281,11 +326,11 @@ zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime); if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){ char *zDate; Blob cksum; int nrid, n; - blob_zero(&event); + blob_init(&event, 0, 0); db_begin_transaction(); login_verify_csrf_secret(); while( fossil_isspace(zComment[0]) ) zComment++; n = strlen(zComment); while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; } @@ -294,17 +339,20 @@ } zDate = date_in_standard_format("now"); blob_appendf(&event, "D %s\n", zDate); free(zDate); zETime[10] = 'T'; - blob_appendf(&event, "E %s %s\n", zETime, zEventId); + blob_appendf(&event, "E %s %s\n", zETime, zId); zETime[10] = ' '; if( rid ){ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); blob_appendf(&event, "P %s\n", zUuid); free(zUuid); } + if( zMimetype && zMimetype[0] ){ + blob_appendf(&event, "N %s\n", zMimetype); + } if( zClr && zClr[0] ){ blob_appendf(&event, "T +bgcolor * %F\n", zClr); } if( zTags && zTags[0] ){ Blob tags, one; @@ -312,11 +360,11 @@ Stmt q; char *zBlob; /* Load the tags string into a blob */ blob_zero(&tags); - blob_append(&tags, zTags, -1); + blob_append(&tags, zTags, -1); /* Collapse all sequences of whitespace and "," characters into ** a single space character */ zBlob = blob_str(&tags); for(i=j=0; zBlob[i]; i++, j++){ @@ -341,37 +389,47 @@ db_prepare(&q, "SELECT x FROM newtags ORDER BY x"); while( db_step(&q)==SQLITE_ROW ){ blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0)); } db_finalize(&q); - } - if( g.zLogin ){ - blob_appendf(&event, "U %F\n", g.zLogin); + } + if( !login_is_nobody() ){ + blob_appendf(&event, "U %F\n", login_name()); } blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody); md5sum_blob(&event, &cksum); blob_appendf(&event, "Z %b\n", &cksum); blob_reset(&cksum); nrid = content_put(&event); db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); - manifest_crosslink(nrid, &event); + if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){ + db_end_transaction(1); + style_header("Error"); + @ Internal error: Fossil tried to make an invalid artifact for + @ the edited technode. + style_footer(); + return; + } assert( blob_is_reset(&event) ); content_deltify(rid, nrid, 0); db_end_transaction(0); - cgi_redirectf("event?name=%T", zEventId); + cgi_redirectf("technote?name=%T", zId); } if( P("cancel")!=0 ){ - cgi_redirectf("event?name=%T", zEventId); + cgi_redirectf("technote?name=%T", zId); return; } if( zBody==0 ){ - zBody = mprintf("<i>Event Text</i>"); + zBody = mprintf("Insert new content here..."); } - zHtmlPageName = mprintf("Edit Event %S", zEventId); - style_header(zHtmlPageName); + if( isNew ){ + style_header("New Tech-note %S", zId); + }else{ + style_header("Edit Tech-note %S", zId); + } if( P("preview")!=0 ){ - Blob title, tail, com; + Blob com; @ <p><b>Timeline comment preview:</b></p> @ <blockquote> @ <table border="0"> if( zClr && zClr[0] ){ @ <tr><td style="background-color: %h(zClr);"> @@ -383,55 +441,55 @@ wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS); @ </td></tr></table> @ </blockquote> @ <p><b>Page content preview:</b><p> @ <blockquote> - blob_zero(&event); + blob_init(&event, 0, 0); blob_append(&event, zBody, -1); - if( wiki_find_title(&event, &title, &tail) ){ - @ <h2 align="center">%h(blob_str(&title))</h2> - wiki_convert(&tail, 0, 0); - }else{ - wiki_convert(&event, 0, 0); - } + wiki_render_by_mimetype(&event, zMimetype); @ </blockquote><hr /> blob_reset(&event); } for(n=2, z=zBody; z[0]; z++){ if( z[0]=='\n' ) n++; } if( n<20 ) n = 20; if( n>40 ) n = 40; - @ <form method="post" action="%s(g.zTop)/eventedit"><div> + @ <form method="post" action="%R/technoteedit"><div> login_insert_csrf_secret(); - @ <input type="hidden" name="name" value="%h(zEventId)" /> + @ <input type="hidden" name="name" value="%h(zId)" /> @ <table border="0" cellspacing="10"> - @ <tr><th align="right" valign="top">Event Time (UTC):</th> + @ <tr><th align="right" valign="top">Timestamp (UTC):</th> @ <td valign="top"> @ <input type="text" name="t" size="25" value="%h(zETime)" /> @ </td></tr> - @ <tr><th align="right" valign="top">Timeline Comment:</th> + @ <tr><th align="right" valign="top">Timeline Comment:</th> @ <td valign="top"> - @ <textarea name="c" class="eventedit" cols="80" + @ <textarea name="c" class="technoteedit" cols="80" @ rows="3" wrap="virtual">%h(zComment)</textarea> @ </td></tr> - @ <tr><th align="right" valign="top">Background Color:</th> + @ <tr><th align="right" valign="top">Timeline Background Color:</th> @ <td valign="top"> render_color_chooser(0, zClr, 0, "clr", "cclr"); @ </td></tr> - + @ <tr><th align="right" valign="top">Tags:</th> @ <td valign="top"> @ <input type="text" name="g" size="40" value="%h(zTags)" /> @ </td></tr> - + + @ <tr><th align="right" valign="top">Markup Style:</th> + @ <td valign="top"> + mimetype_option_menu(zMimetype); + @ </td></tr> + @ <tr><th align="right" valign="top">Page Content:</th> @ <td valign="top"> - @ <textarea name="w" class="eventedit" cols="80" + @ <textarea name="w" class="technoteedit" cols="80" @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> @ </td></tr> @ <tr><td colspan="2"> @ <input type="submit" name="preview" value="Preview Your Changes" /> Index: src/export.c ================================================================== --- src/export.c +++ src/export.c @@ -53,25 +53,41 @@ zName[j] = 0; printf(" %s <%s>", zName, zUser); free(zName); return; } + /* + ** We have contact information. + ** It may or may not contain an email address. + */ zContact = db_column_text(&q, 0); for(i=0; zContact[i] && zContact[i]!='>' && zContact[i]!='<'; i++){} if( zContact[i]==0 ){ + /* No email address found. Take as user info if not empty */ printf(" %s <%s>", zContact[0] ? zContact : zUser, zUser); db_reset(&q); return; } if( zContact[i]=='<' ){ + /* + ** Found beginning of email address. Look for the end and extract + ** the part. + */ zEmail = mprintf("%s", &zContact[i]); - for(i=0; zEmail[i] && zEmail[i]!='>'; i++){} - if( zEmail[i]=='>' ) zEmail[i+1] = 0; + for(j=0; zEmail[j] && zEmail[j]!='>'; j++){} + if( zEmail[j]=='>' ) zEmail[j+1] = 0; }else{ + /* + ** Found an end marker for email, but nothing else. + */ zEmail = mprintf("<%s>", zUser); } - zName = mprintf("%.*s", i, zContact); + /* + ** Here zContact[i] either '<' or '>'. Extract the string _before_ + ** either as user name. + */ + zName = mprintf("%.*s", i-1, zContact); for(i=j=0; zName[i]; i++){ if( zName[i]!='"' ) zName[j++] = zName[i]; } zName[j] = 0; printf(" %s %s", zName, zEmail); @@ -88,18 +104,18 @@ ** ** Usage: %fossil export --git ?OPTIONS? ?REPOSITORY? ** ** Write an export of all check-ins to standard output. The export is ** written in the git-fast-export file format assuming the --git option is -** provided. The git-fast-export format is currently the only VCS +** provided. The git-fast-export format is currently the only VCS ** interchange format supported, though other formats may be added in ** the future. ** ** Run this command within a checkout. Or use the -R or --repository ** option to specify a Fossil repository to be exported. ** -** Only check-ins are exported using --git. Git does not support tickets +** Only check-ins are exported using --git. Git does not support tickets ** or wiki or events or attachments, so none of those are exported. ** ** If the "--import-marks FILE" option is used, it contains a list of ** rids to skip. ** @@ -108,11 +124,11 @@ ** ** Options: ** --export-marks FILE export rids of exported data to FILE ** --import-marks FILE read rids of data to ignore from FILE ** --repository|-R REPOSITORY export the given REPOSITORY -** +** ** See also: import */ void export_cmd(void){ Stmt q, q2, q3; int i; @@ -163,11 +179,11 @@ db_finalize(&qc); fclose(f); } /* Step 1: Generate "blob" records for every artifact that is part - ** of a check-in + ** of a check-in */ fossil_binary_mode(stdout); db_multi_exec("CREATE TEMP TABLE newblob(rid INTEGER KEY, srcid INTEGER)"); db_multi_exec("CREATE INDEX newblob_src ON newblob(srcid)"); db_multi_exec( @@ -216,12 +232,12 @@ db_finalize(&q3); /* Output the commit records. */ db_prepare(&q, - "SELECT strftime('%%s',mtime), objid, coalesce(comment,ecomment)," - " coalesce(user,euser)," + "SELECT strftime('%%s',mtime), objid, coalesce(ecomment,comment)," + " coalesce(euser,user)," " (SELECT value FROM tagxref WHERE rid=objid AND tagid=%d)" " FROM event" " WHERE type='ci' AND NOT EXISTS (SELECT 1 FROM oldcommit WHERE objid=rid)" " ORDER BY mtime ASC", TAG_BRANCH Index: src/file.c ================================================================== --- src/file.c +++ src/file.c @@ -39,46 +39,60 @@ # include <sys/utime.h> #else # include <sys/time.h> #endif -/* -** The file status information from the most recent stat() call. -** -** Use _stati64 rather than stat on windows, in order to handle files -** larger than 2GB. -*/ +#if INTERFACE + +#include <dirent.h> +#if defined(_WIN32) +# define DIR _WDIR +# define dirent _wdirent +# define opendir _wopendir +# define readdir _wreaddir +# define closedir _wclosedir +#endif /* _WIN32 */ + #if defined(_WIN32) && (defined(__MSVCRT__) || defined(_MSC_VER)) -# undef stat -# define stat _stati64 +struct fossilStat { + i64 st_size; + i64 st_mtime; + int st_mode; +}; +#endif + +#endif /* INTERFACE */ + +#if !defined(_WIN32) || !(defined(__MSVCRT__) || defined(_MSC_VER)) +# define fossilStat stat #endif + /* ** On Windows S_ISLNK always returns FALSE. */ #if !defined(S_ISLNK) # define S_ISLNK(x) (0) #endif static int fileStatValid = 0; -static struct stat fileStat; +static struct fossilStat fileStat; /* ** Fill stat buf with information received from stat() or lstat(). ** lstat() is called on Unix if isWd is TRUE and allow-symlinks setting is on. ** */ -static int fossil_stat(const char *zFilename, struct stat *buf, int isWd){ +static int fossil_stat(const char *zFilename, struct fossilStat *buf, int isWd){ int rc; + void *zMbcs = fossil_utf8_to_filename(zFilename); #if !defined(_WIN32) - char *zMbcs = fossil_utf8_to_filename(zFilename); if( isWd && g.allowSymlinks ){ rc = lstat(zMbcs, buf); }else{ rc = stat(zMbcs, buf); } #else - wchar_t *zMbcs = fossil_utf8_to_filename(zFilename); - rc = _wstati64(zMbcs, buf); + rc = win32_stat(zMbcs, buf, isWd); #endif fossil_filename_free(zMbcs); return rc; } @@ -218,33 +232,24 @@ blob_reset(&content); } /* ** Return file permissions (normal, executable, or symlink): -** - PERM_EXE if file is executable; +** - PERM_EXE on Unix if file is executable; ** - PERM_LNK on Unix if file is symlink and allow-symlinks option is on; ** - PERM_REG for all other cases (regular file, directory, fifo, etc). */ int file_wd_perm(const char *zFilename){ - if( getStat(zFilename, 1) ) return PERM_REG; -#if defined(_WIN32) -# ifndef S_IXUSR -# define S_IXUSR _S_IEXEC -# endif - if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 ) - return PERM_EXE; - else - return PERM_REG; -#else - if( S_ISREG(fileStat.st_mode) && - ((S_IXUSR|S_IXGRP|S_IXOTH)&fileStat.st_mode)!=0 ) - return PERM_EXE; - else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) ) - return PERM_LNK; - else - return PERM_REG; +#if !defined(_WIN32) + if( !getStat(zFilename, 1) ){ + if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 ) + return PERM_EXE; + else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) ) + return PERM_LNK; + } #endif + return PERM_REG; } /* ** Return TRUE if the named file is an executable. Return false ** for directories, devices, fifos, symlinks, etc. @@ -302,16 +307,16 @@ /* ** Wrapper around the access() system call. */ int file_access(const char *zFilename, int flags){ + int rc; + void *zMbcs = fossil_utf8_to_filename(zFilename); #ifdef _WIN32 - wchar_t *zMbcs = fossil_utf8_to_filename(zFilename); - int rc = _waccess(zMbcs, flags); + rc = win32_access(zMbcs, flags); #else - char *zMbcs = fossil_utf8_to_filename(zFilename); - int rc = access(zMbcs, flags); + rc = access(zMbcs, flags); #endif fossil_filename_free(zMbcs); return rc; } @@ -319,16 +324,16 @@ ** Wrapper around the chdir() system call. ** If bChroot=1, do a chroot to this dir as well ** (UNIX only) */ int file_chdir(const char *zChDir, int bChroot){ + int rc; + void *zPath = fossil_utf8_to_filename(zChDir); #ifdef _WIN32 - wchar_t *zPath = fossil_utf8_to_filename(zChDir); - int rc = _wchdir(zPath); + rc = win32_chdir(zPath, bChroot); #else - char *zPath = fossil_utf8_to_filename(zChDir); - int rc = chdir(zPath); + rc = chdir(zPath); if( !rc && bChroot ){ rc = chroot(zPath); if( !rc ) rc = chdir("/"); } #endif @@ -381,18 +386,35 @@ FILE *in, *out; int got; char zBuf[8192]; in = fossil_fopen(zFrom, "rb"); if( in==0 ) fossil_fatal("cannot open \"%s\" for reading", zFrom); + file_mkfolder(zTo, 0); out = fossil_fopen(zTo, "wb"); if( out==0 ) fossil_fatal("cannot open \"%s\" for writing", zTo); while( (got=fread(zBuf, 1, sizeof(zBuf), in))>0 ){ fwrite(zBuf, 1, got, out); } fclose(in); fclose(out); } + +/* +** COMMAND: test-file-copy +** +** Usage: %fossil test-file-copy SOURCE DESTINATION +** +** Make a copy of the file at SOURCE into a new name DESTINATION. Any +** directories in the path leading up to DESTINATION that do not already +** exist are created automatically. +*/ +void test_file_copy(void){ + if( g.argc!=4 ){ + fossil_fatal("Usage: %s test-file-copy SOURCE DESTINATION", g.argv[0]); + } + file_copy(g.argv[2], g.argv[3]); +} /* ** Set or clear the execute bit on a file. Return true if a change ** occurred and false if this routine is a no-op. */ @@ -401,16 +423,16 @@ #if !defined(_WIN32) struct stat buf; if( fossil_stat(zFilename, &buf, 1)!=0 || S_ISLNK(buf.st_mode) ) return 0; if( onoff ){ int targetMode = (buf.st_mode & 0444)>>2; - if( (buf.st_mode & 0111)!=targetMode ){ + if( (buf.st_mode & 0100) == 0 ){ chmod(zFilename, buf.st_mode | targetMode); rc = 1; } }else{ - if( (buf.st_mode & 0111)!=0 ){ + if( (buf.st_mode & 0100) != 0 ){ chmod(zFilename, buf.st_mode & ~0111); rc = 1; } } #endif /* _WIN32 */ @@ -420,15 +442,16 @@ /* ** Set the mtime for a file. */ void file_set_mtime(const char *zFilename, i64 newMTime){ #if !defined(_WIN32) + char *zMbcs; struct timeval tv[2]; memset(tv, 0, sizeof(tv[0])*2); tv[0].tv_sec = newMTime; tv[1].tv_sec = newMTime; - char *zMbcs = fossil_utf8_to_filename(zFilename); + zMbcs = fossil_utf8_to_filename(zFilename); utimes(zMbcs, tv); #else struct _utimbuf tb; wchar_t *zMbcs = fossil_utf8_to_filename(zFilename); tb.actime = newMTime; @@ -503,10 +526,45 @@ fossil_filename_free(zMbcs); return rc; } return 0; } + +/* +** Create the tree of directories in which zFilename belongs, if that sequence +** of directories does not already exist. +*/ +void file_mkfolder(const char *zFilename, int forceFlag){ + int i, nName; + char *zName; + + nName = strlen(zFilename); + zName = mprintf("%s", zFilename); + nName = file_simplify_name(zName, nName, 0); + for(i=1; i<nName; i++){ + if( zName[i]=='/' ){ + zName[i] = 0; +#if defined(_WIN32) || defined(__CYGWIN__) + /* + ** On Windows, local path looks like: C:/develop/project/file.txt + ** The if stops us from trying to create a directory of a drive letter + ** C: in this example. + */ + if( !(i==2 && zName[1]==':') ){ +#endif + if( file_mkdir(zName, forceFlag) && file_isdir(zName)!=1 ){ + fossil_fatal_recursive("unable to create directory %s", zName); + return; + } +#if defined(_WIN32) || defined(__CYGWIN__) + } +#endif + zName[i] = '/'; + } + } + free(zName); +} /* ** Removes the directory named in the argument, if it exists. The directory ** must be empty and cannot be the current directory or the root directory. ** @@ -631,10 +689,11 @@ } /* ** Simplify a filename by ** +** * Remove extended path prefix on windows and cygwin ** * Convert all \ into / on windows and cygwin ** * removing any trailing and duplicate / ** * removing /./ ** * removing /A/../ ** @@ -641,17 +700,27 @@ ** Changes are made in-place. Return the new name length. ** If the slash parameter is non-zero, the trailing slash, if any, ** is retained. */ int file_simplify_name(char *z, int n, int slash){ - int i, j; + int i = 1, j; if( n<0 ) n = strlen(z); - /* On windows and cygwin convert all \ characters to / */ + /* On windows and cygwin convert all \ characters to / + * and remove extended path prefix if present */ #if defined(_WIN32) || defined(__CYGWIN__) - for(i=0; i<n; i++){ - if( z[i]=='\\' ) z[i] = '/'; + for(j=0; j<n; j++){ + if( z[j]=='\\' ) z[j] = '/'; + } + if( n>3 && !memcmp(z, "//?/", 4) ){ + if( fossil_strnicmp(z+4,"UNC", 3) ){ + i += 4; + z[0] = z[4]; + }else{ + i += 6; + z[0] = '/'; + } } #endif /* Removing trailing "/" characters */ if( !slash ){ @@ -658,11 +727,11 @@ while( n>1 && z[n-1]=='/' ){ n--; } } /* Remove duplicate '/' characters. Except, two // at the beginning ** of a pathname is allowed since this is important on windows. */ - for(i=j=1; i<n; i++){ + for(j=1; i<n; i++){ z[j++] = z[i]; while( z[i]=='/' && i<n-1 && z[i+1]=='/' ) i++; } n = j; @@ -690,11 +759,11 @@ } } if( j>=0 ) z[j] = z[i]; j++; } - if( j==0 ) z[j++] = '.'; + if( j==0 ) z[j++] = '/'; z[j] = 0; return j; } /* @@ -723,25 +792,11 @@ ** characters are converted to '/'. No conversions are needed on ** unix. */ void file_getcwd(char *zBuf, int nBuf){ #ifdef _WIN32 - char *zPwdUtf8; - int nPwd; - int i; - wchar_t zPwd[2000]; - if( _wgetcwd(zPwd, sizeof(zPwd)/sizeof(zPwd[0])-1)==0 ){ - fossil_fatal("cannot find the current working directory."); - } - zPwdUtf8 = fossil_filename_to_utf8(zPwd); - nPwd = strlen(zPwdUtf8); - if( nPwd > nBuf-1 ){ - fossil_fatal("pwd too big: max %d\n", nBuf-1); - } - for(i=0; zPwdUtf8[i]; i++) if( zPwdUtf8[i]=='\\' ) zPwdUtf8[i] = '/'; - memcpy(zBuf, zPwdUtf8, nPwd+1); - fossil_filename_free(zPwdUtf8); + win32_getcwd(zBuf, nBuf); #else if( getcwd(zBuf, nBuf-1)==0 ){ if( errno==ERANGE ){ fossil_fatal("pwd too big: max %d\n", nBuf-1); }else{ @@ -759,11 +814,11 @@ int file_is_absolute_path(const char *zPath){ if( zPath[0]=='/' #if defined(_WIN32) || defined(__CYGWIN__) || zPath[0]=='\\' || (fossil_isalpha(zPath[0]) && zPath[1]==':' - && (zPath[2]=='\\' || zPath[2]=='/')) + && (zPath[2]=='\\' || zPath[2]=='/' || zPath[2]=='\0')) #endif ){ return 1; }else{ return 0; @@ -778,39 +833,40 @@ ** Convert /A/../ to just / ** If the slash parameter is non-zero, the trailing slash, if any, ** is retained. */ void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){ + blob_zero(pOut); if( file_is_absolute_path(zOrigName) ){ -#if defined(_WIN32) || defined(__CYGWIN__) - char *zOut; -#endif - blob_set(pOut, zOrigName); - blob_materialize(pOut); + blob_appendf(pOut, "%/", zOrigName); + }else{ + char zPwd[2000]; + file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); + if( zPwd[0]=='/' && strlen(zPwd)==1 ){ + /* when on '/', don't add an extra '/' */ + if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){ + /* '.' when on '/' mean '/' */ + blob_appendf(pOut, "%/", zPwd); + }else{ + blob_appendf(pOut, "%/%/", zPwd, zOrigName); + } + }else{ + blob_appendf(pOut, "%//%/", zPwd, zOrigName); + } + } #if defined(_WIN32) || defined(__CYGWIN__) + { + char *zOut; /* ** On Windows/cygwin, normalize the drive letter to upper case. */ zOut = blob_str(pOut); - if( fossil_islower(zOut[0]) && zOut[1]==':' ){ + if( fossil_islower(zOut[0]) && zOut[1]==':' && zOut[2]=='/' ){ zOut[0] = fossil_toupper(zOut[0]); } -#endif - }else{ - char zPwd[2000]; - file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); -#if defined(_WIN32) - /* - ** On Windows, normalize the drive letter to upper case. - */ - if( fossil_islower(zPwd[0]) && zPwd[1]==':' ){ - zPwd[0] = fossil_toupper(zPwd[0]); - } -#endif - blob_zero(pOut); - blob_appendf(pOut, "%//%/", zPwd, zOrigName); - } + } +#endif blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut), slash)); } /* @@ -914,10 +970,12 @@ for(j=i+1; zPwd[j]; j++){ if( zPwd[j]=='/' ){ blob_append(pOut, "/..", 3); } } + while( i>0 && (zPwd[i]!='/')) --i; + blob_append(pOut, zPath+i, j-i); } if( slash && i>0 && zPath[strlen(zPath)-1]=='/'){ blob_append(pOut, "/", 1); } blob_reset(&tmp); @@ -929,11 +987,16 @@ blob_append(pOut, &zPath[i+1], -1); blob_reset(&tmp); return; } while( zPath[i-1]!='/' ){ i--; } - blob_set(&tmp, "../"); + if( zPwd[0]=='/' && strlen(zPwd)==1 ){ + /* If on '/', don't go to higher level */ + blob_zero(&tmp); + }else{ + blob_set(&tmp, "../"); + } for(j=i; zPwd[j]; j++){ if( zPwd[j]=='/' ){ blob_append(&tmp, "../", 3); } } @@ -976,11 +1039,14 @@ int nFull; char *zFull; int (*xCmp)(const char*,const char*,int); blob_zero(pOut); - db_must_be_within_tree(); + if( !g.localOpen ){ + blob_appendf(pOut, "%s", zOrigName); + return 1; + } file_canonical_name(g.zLocalRoot, &localRoot, 1); nLocalRoot = blob_size(&localRoot); zLocalRoot = blob_buffer(&localRoot); assert( nLocalRoot>0 && zLocalRoot[nLocalRoot-1]=='/' ); file_canonical_name(zOrigName, &full, 0); @@ -991,11 +1057,12 @@ }else{ xCmp = fossil_strnicmp; } /* Special case. zOrigName refers to g.zLocalRoot directory. */ - if( nFull==nLocalRoot-1 && xCmp(zLocalRoot, zFull, nFull)==0 ){ + if( (nFull==nLocalRoot-1 && xCmp(zLocalRoot, zFull, nFull)==0) + || (nFull==1 && zFull[0]=='/' && nLocalRoot==1 && zLocalRoot[0]=='/') ){ blob_append(pOut, ".", 1); blob_reset(&localRoot); blob_reset(&full); return 1; } @@ -1024,12 +1091,12 @@ ** a boolean: "yes", "no", "true", "false", etc. */ void cmd_test_tree_name(void){ int i; Blob x; + db_find_and_open_repository(0,0); blob_zero(&x); - capture_case_sensitive_option(); for(i=2; i<g.argc; i++){ if( file_tree_name(g.argv[i], &x, 1) ){ fossil_print("%s\n", blob_buffer(&x)); blob_reset(&x); } @@ -1171,26 +1238,10 @@ rc = blob_compare(&onDisk, pContent); blob_reset(&onDisk); return rc==0; } -/* -** Portable unicode implementation of opendir() -*/ -#if INTERFACE - -#include <dirent.h> -#if defined(_WIN32) -# define DIR _WDIR -# define dirent _wdirent -# define opendir _wopendir -# define readdir _wreaddir -# define closedir _wclosedir -#endif /* _WIN32 */ - -#endif /* INTERFACE */ - /* ** Return the value of an environment variable as UTF8. ** Use fossil_filename_free() to release resources. */ char *fossil_getenv(const char *zName){ @@ -1202,10 +1253,29 @@ char *zValue = getenv(zName); #endif if( zValue ) zValue = fossil_filename_to_utf8(zValue); return zValue; } + +/* +** Sets the value of an environment variable as UTF8. +*/ +int fossil_setenv(const char *zName, const char *zValue){ + int rc; + char *zString = mprintf("%s=%s", zName, zValue); +#ifdef _WIN32 + wchar_t *uString = fossil_utf8_to_unicode(zString); + rc = _wputenv(uString); + fossil_unicode_free(uString); + fossil_free(zString); +#else + rc = putenv(zString); + /* NOTE: Cannot free the string on POSIX. */ + /* fossil_free(zString); */ +#endif + return rc; +} /* ** Like fopen() but always takes a UTF8 argument. */ FILE *fossil_fopen(const char *zName, const char *zMode){ Index: src/finfo.c ================================================================== --- src/finfo.c +++ src/finfo.c @@ -43,27 +43,33 @@ ** Options: ** -b|--brief display a brief (one line / revision) summary ** --case-sensitive B Enable or disable case-sensitive filenames. B is a ** boolean: "yes", "no", "true", "false", etc. ** -l|--log select log mode (the default) -** -n|--limit N display the first N changes +** -n|--limit N Display the first N changes (default unlimited). +** N<=0 means no limit. ** --offset P skip P changes ** -p|--print select print mode ** -r|--revision R print the given revision (or ckout, if none is given) ** to stdout (only in print mode) ** -s|--status select status mode (print a status indicator for FILE) +** -W|--width <num> Width of lines (default is to auto-detect). Must be +** >22 or 0 (= no limit, resulting in a single line per +** entry). ** ** See also: artifact, cat, descendants, info, leaves */ void finfo_cmd(void){ - capture_case_sensitive_option(); db_must_be_within_tree(); if( find_option("status","s",0) ){ Stmt q; Blob line; Blob fname; int vid; + + /* We should be done with options.. */ + verify_all_options(); if( g.argc!=3 ) usage("-s|--status FILENAME"); vid = db_lget_int("checkout", 0); if( vid==0 ){ fossil_fatal("no checkout to finfo files in"); @@ -112,10 +118,13 @@ blob_reset(&line); }else if( find_option("print","p",0) ){ Blob record; Blob fname; const char *zRevision = find_option("revision", "r", 1); + + /* We should be done with options.. */ + verify_all_options(); file_tree_name(g.argv[2], &fname, 1); if( zRevision ){ historical_version_of_file(zRevision, blob_str(&fname), &record, 0,0,0,0); }else{ @@ -134,21 +143,38 @@ Stmt q; Blob fname; int rid; const char *zFilename; const char *zLimit; + const char *zWidth; const char *zOffset; - int iLimit, iOffset, iBrief; + int iLimit, iOffset, iBrief, iWidth; if( find_option("log","l",0) ){ /* this is the default, no-op */ } zLimit = find_option("limit","n",1); + zWidth = find_option("width","W",1); iLimit = zLimit ? atoi(zLimit) : -1; zOffset = find_option("offset",0,1); iOffset = zOffset ? atoi(zOffset) : 0; iBrief = (find_option("brief","b",0) == 0); + if( iLimit==0 ){ + iLimit = -1; + } + if( zWidth ){ + iWidth = atoi(zWidth); + if( (iWidth!=0) && (iWidth<=22) ){ + fossil_fatal("-W|--width value must be >22 or 0"); + } + }else{ + iWidth = -1; + } + + /* We should be done with options.. */ + verify_all_options(); + if( g.argc!=3 ){ usage("?-l|--log? ?-b|--brief? FILENAME"); } file_tree_name(g.argv[2], &fname, 1); rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s", @@ -156,11 +182,11 @@ if( rid==0 ){ fossil_fatal("no history for file: %b", &fname); } zFilename = blob_str(&fname); db_prepare(&q, - "SELECT b.uuid, ci.uuid, date(event.mtime,'localtime')," + "SELECT b.uuid, ci.uuid, date(event.mtime%s)," " coalesce(event.ecomment, event.comment)," " coalesce(event.euser, event.user)," " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0" " AND tagxref.rid=mlink.mid)" /* Tags */ " FROM mlink, blob b, event, blob ci, filename" @@ -168,11 +194,12 @@ " AND mlink.fnid=filename.fnid" " AND b.rid=mlink.fid" " AND event.objid=mlink.mid" " AND event.objid=ci.rid" " ORDER BY event.mtime DESC LIMIT %d OFFSET %d", - TAG_BRANCH, zFilename, filename_collation(), iLimit, iOffset + timeline_utc(), TAG_BRANCH, zFilename, filename_collation(), + iLimit, iOffset ); blob_zero(&line); if( iBrief ){ fossil_print("History of %s\n", blob_str(&fname)); } @@ -185,23 +212,23 @@ const char *zBr = db_column_text(&q, 5); char *zOut; if( zBr==0 ) zBr = "trunk"; if( iBrief ){ fossil_print("%s ", zDate); - zOut = sqlite3_mprintf( - "[%.10s] %s (user: %s, artifact: [%.10s], branch: %s)", + zOut = mprintf( + "[%S] %s (user: %s, artifact: [%S], branch: %s)", zCiUuid, zCom, zUser, zFileUuid, zBr); - comment_print(zOut, 11, 79); - sqlite3_free(zOut); + comment_print(zOut, zCom, 11, iWidth, g.comFmtFlags); + fossil_free(zOut); }else{ blob_reset(&line); - blob_appendf(&line, "%.10s ", zCiUuid); + blob_appendf(&line, "%S ", zCiUuid); blob_appendf(&line, "%.10s ", zDate); blob_appendf(&line, "%8.8s ", zUser); blob_appendf(&line, "%8.8s ", zBr); blob_appendf(&line,"%-39.39s", zCom ); - comment_print(blob_str(&line), 0, 79); + comment_print(blob_str(&line), zCom, 0, iWidth, g.comFmtFlags); } } db_finalize(&q); blob_reset(&fname); } @@ -227,10 +254,14 @@ int rc; Blob content, fname; const char *zRev; db_find_and_open_repository(0, 0); zRev = find_option("r","r",1); + + /* We should be done with options.. */ + verify_all_options(); + for(i=2; i<g.argc; i++){ file_tree_name(g.argv[i], &fname, 1); blob_zero(&content); rc = historical_version_of_file(zRev, blob_str(&fname), &content, 0,0,0,0); if( rc==0 ){ @@ -257,126 +288,120 @@ ** b=DATE Only show changes before DATE ** n=NUM Show the first NUM changes only ** brbg Background color by branch name ** ubg Background color by user name ** ci=UUID Ancestors of a particular check-in -** fco=BOOL Show only first occurrence of each version if true (default) */ void finfo_page(void){ Stmt q; const char *zFilename; char zPrevDate[20]; const char *zA; const char *zB; int n; int baseCheckin; - + int fnid; + Bag ancestor; Blob title; Blob sql; HQuery url; GraphContext *pGraph; int brBg = P("brbg")!=0; int uBg = P("ubg")!=0; - int firstChngOnly = atoi(PD("fco","1"))!=0; int fDebug = atoi(PD("debug","0")); + int fShowId = P("showid")!=0; login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } style_header("File History"); login_anonymous_available(); url_initialize(&url, "finfo"); if( brBg ) url_add_parameter(&url, "brbg", 0); if( uBg ) url_add_parameter(&url, "ubg", 0); baseCheckin = name_to_rid_www("ci"); - if( baseCheckin ) firstChngOnly = 1; - if( firstChngOnly ) url_add_parameter(&url, "fco", "0"); - zPrevDate[0] = 0; zFilename = PD("name",""); + fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename); + if( fnid==0 ){ + @ No such file: %h(zFilename) + style_footer(); + return; + } + if( baseCheckin ){ + int baseFid = db_int(0, + "SELECT fid FROM mlink WHERE fnid=%d AND mid=%d", + fnid, baseCheckin + ); + bag_init(&ancestor); + if( baseFid ) bag_insert(&ancestor, baseFid); + } url_add_parameter(&url, "name", zFilename); blob_zero(&sql); - blob_appendf(&sql, + blob_append_sql(&sql, "SELECT" - " datetime(event.mtime,'localtime')," /* Date of change */ + " datetime(min(event.mtime)%s)," /* Date of change */ " coalesce(event.ecomment, event.comment)," /* Check-in comment */ " coalesce(event.euser, event.user)," /* User who made chng */ " mlink.pid," /* Parent file rid */ " mlink.fid," /* File rid */ " (SELECT uuid FROM blob WHERE rid=mlink.pid)," /* Parent file uuid */ " (SELECT uuid FROM blob WHERE rid=mlink.fid)," /* Current file uuid */ " (SELECT uuid FROM blob WHERE rid=mlink.mid)," /* Check-in uuid */ " event.bgcolor," /* Background color */ " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0" - " AND tagxref.rid=mlink.mid)," /* Tags */ + " AND tagxref.rid=mlink.mid)," /* Branchname */ " mlink.mid," /* check-in ID */ - " mlink.pfnid", /* Previous filename */ - TAG_BRANCH - ); - if( firstChngOnly ){ -#if 0 - blob_appendf(&sql, ", min(event.mtime)"); -#else - blob_appendf(&sql, - ", min(CASE (SELECT value FROM tagxref" - " WHERE tagtype>0 AND tagid=%d" - " AND tagxref.rid=mlink.mid)" - " WHEN 'trunk' THEN event.mtime-10000 ELSE event.mtime END)", - TAG_BRANCH); -#endif - } - blob_appendf(&sql, - " FROM mlink, event" - " WHERE mlink.fnid IN (SELECT fnid FROM filename WHERE name=%Q)" - " AND event.objid=mlink.mid", - zFilename - ); - if( baseCheckin ){ - compute_direct_ancestors(baseCheckin, 10000000); - blob_appendf(&sql," AND mlink.mid IN (SELECT rid FROM ancestor)"); - } - if( (zA = P("a"))!=0 ){ - blob_appendf(&sql, " AND event.mtime>=julianday('%q')", zA); + " mlink.pfnid" /* Previous filename */ + " FROM mlink, event" + " WHERE mlink.fnid=%d" + " AND event.objid=mlink.mid", + timeline_utc(), TAG_BRANCH, fnid + ); + if( (zA = P("a"))!=0 ){ + blob_append_sql(&sql, " AND event.mtime>=julianday('%q')", zA); url_add_parameter(&url, "a", zA); } if( (zB = P("b"))!=0 ){ - blob_appendf(&sql, " AND event.mtime<=julianday('%q')", zB); + blob_append_sql(&sql, " AND event.mtime<=julianday('%q')", zB); url_add_parameter(&url, "b", zB); } - if( firstChngOnly ){ - blob_appendf(&sql, " GROUP BY mlink.fid"); - } - blob_appendf(&sql," ORDER BY event.mtime DESC /*sort*/"); + /* We only want each version of a file to appear on the graph once, + ** at its earliest appearance. All the other times that it gets merged + ** into this or that branch can be ignored. An exception is for when + ** files are deleted (when they have mlink.fid==0). If the same file + ** is deleted in multiple places, we want to show each deletion, so + ** use a "fake fid" which is derived from the parent-fid for grouping. + ** The same fake-fid must be used on the graph. + */ + blob_append_sql(&sql, + " GROUP BY" + " CASE WHEN mlink.fid>0 THEN mlink.fid ELSE mlink.pid+1000000000 END" + " ORDER BY event.mtime DESC /*sort*/" + ); if( (n = atoi(PD("n","0")))>0 ){ - blob_appendf(&sql, " LIMIT %d", n); + blob_append_sql(&sql, " LIMIT %d", n); url_add_parameter(&url, "n", P("n")); } - if( baseCheckin==0 ){ - if( firstChngOnly ){ - style_submenu_element("Full", "Show all changes","%s", - url_render(&url, "fco", "0", 0, 0)); - }else{ - style_submenu_element("Simplified", - "Show only first use of a change","%s", - url_render(&url, "fco", "1", 0, 0)); - } - } - db_prepare(&q, blob_str(&sql)); + db_prepare(&q, "%s", blob_sql_text(&sql)); if( P("showsql")!=0 ){ @ <p>SQL: %h(blob_str(&sql))</p> } blob_reset(&sql); blob_zero(&title); if( baseCheckin ){ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin); - char *zLink = href("%R/info/%S", zUuid); + char *zLink = href("%R/info/%!S", zUuid); blob_appendf(&title, "Ancestors of file "); - hyperlinked_path(zFilename, &title, zUuid); - blob_appendf(&title, " from check-in %z%.10s</a>", zLink, zUuid); + hyperlinked_path(zFilename, &title, zUuid, "tree", ""); + if( fShowId ) blob_appendf(&title, " (%d)", fnid); + blob_appendf(&title, " from check-in %z%S</a>", zLink, zUuid); + if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin); fossil_free(zUuid); }else{ blob_appendf(&title, "History of files named "); - hyperlinked_path(zFilename, &title, 0); + hyperlinked_path(zFilename, &title, 0, "tree", ""); + if( fShowId ) blob_appendf(&title, " (%d)", fnid); } @ <h2>%b(&title)</h2> blob_reset(&title); pGraph = graph_init(); @ <div id="canvas" style="position:relative;width:1px;height:1px;" @@ -395,24 +420,42 @@ const char *zBr = db_column_text(&q, 9); int fmid = db_column_int(&q, 10); int pfnid = db_column_int(&q, 11); int gidx; char zTime[10]; - char zShort[20]; - char zShortCkin[20]; + int nParent = 0; + int aParent[GR_MAX_RAIL]; + static Stmt qparent; + + if( baseCheckin && frid && !bag_find(&ancestor, frid) ) continue; + db_static_prepare(&qparent, + "SELECT DISTINCT pid FROM mlink" + " WHERE fid=:fid AND mid=:mid AND pid>0 AND fnid=:fnid" + " ORDER BY isaux /*sort*/" + ); + db_bind_int(&qparent, ":fid", frid); + db_bind_int(&qparent, ":mid", fmid); + db_bind_int(&qparent, ":fnid", fnid); + while( db_step(&qparent)==SQLITE_ROW && nParent<ArraySize(aParent) ){ + aParent[nParent] = db_column_int(&qparent, 0); + if( baseCheckin ) bag_insert(&ancestor, aParent[nParent]); + nParent++; + } + db_reset(&qparent); if( zBr==0 ) zBr = "trunk"; if( uBg ){ zBgClr = hash_color(zUser); }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){ zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr); } - gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr, + gidx = graph_add_row(pGraph, frid>0 ? frid : fpid+1000000000, + nParent, aParent, zBr, zBgClr, zUuid, 0); - if( memcmp(zDate, zPrevDate, 10) ){ + if( strncmp(zDate, zPrevDate, 10) ){ sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate); @ <tr><td> - @ <div class="divider">%s(zPrevDate)</div> + @ <div class="divider timelineDate">%s(zPrevDate)</div> @ </td><td></td><td></td></tr> } memcpy(zTime, &zDate[11], 5); zTime[5] = 0; @ <tr><td class="timelineTime"> @@ -421,22 +464,24 @@ if( zBgClr && zBgClr[0] ){ @ <td class="timelineTableCell" style="background-color: %h(zBgClr);"> }else{ @ <td class="timelineTableCell"> } - sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid); - sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin); if( zUuid ){ - if( fpid==0 ){ + if( nParent==0 ){ @ <b>Added</b> }else if( pfnid ){ char *zPrevName = db_text(0, "SELECT name FROM filename WHERE fnid=%d", pfnid); @ <b>Renamed</b> from @ %z(href("%R/finfo?name=%t", zPrevName))%h(zPrevName)</a> } - @ %z(href("%R/artifact/%s",zUuid))[%S(zUuid)]</a> part of check-in + @ %z(href("%R/artifact/%!S",zUuid))[%S(zUuid)]</a> + if( fShowId ){ + @ (%d(frid)) + } + @ part of check-in }else{ char *zNewName; zNewName = db_text(0, "SELECT name FROM filename WHERE fnid = " " (SELECT fnid FROM mlink" @@ -449,45 +494,56 @@ fossil_free(zNewName); }else{ @ <b>Deleted</b> by check-in } } - hyperlink_to_uuid(zShortCkin); - @ %w(zCom) (user: + hyperlink_to_uuid(zCkin); + if( fShowId ){ + @ (%d(fmid)) + } + @ %W(zCom) (user: hyperlink_to_user(zUser, zDate, ""); - @ branch: %h(zBr)) + @ branch: %z(href("%R/timeline?t=%T&n=200",zBr))%h(zBr)</a>) if( g.perm.Hyperlink && zUuid ){ const char *z = zFilename; - if( fpid ){ - @ %z(href("%R/fdiff?v1=%S&v2=%S&sbs=1",zPUuid,zUuid))[diff]</a> - } - @ %z(href("%R/annotate?checkin=%S&filename=%h",zCkin,z)) + @ %z(href("%R/annotate?filename=%h&checkin=%s",z,zCkin)) @ [annotate]</a> - @ %z(href("%R/timeline?n=200&uf=%S",zUuid))[checkins using]</a> + @ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin)) + @ [blame]</a> + @ %z(href("%R/timeline?n=200&uf=%!S",zUuid))[check-ins using]</a> + if( fpid ){ + @ %z(href("%R/fdiff?sbs=1&v1=%!S&v2=%!S",zPUuid,zUuid))[diff]</a> + } } if( fDebug & FINFO_DEBUG_MLINK ){ - int srcid = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", frid); - int sz = db_int(0, "SELECT length(content) FROM blob WHERE rid=%d", frid); - @ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid) sz=%d(sz) - if( srcid ){ - @ srcid=%d(srcid) + int ii; + char *zAncLink; + @ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid) + if( nParent>0 ){ + @ parents=%d(aParent[0]) + for(ii=1; ii<nParent; ii++){ + @ %d(aParent[ii]) + } } + zAncLink = href("%R/finfo?name=%T&ci=%!S&debug=1",zFilename,zCkin); + @ %z(zAncLink)[ancestry]</a> } + tag_private_status(frid); @ </td></tr> } db_finalize(&q); if( pGraph ){ - graph_finish(pGraph, 0); + graph_finish(pGraph, 1); if( pGraph->nErr ){ graph_free(pGraph); pGraph = 0; }else{ - int w = (pGraph->mxRail+1)*pGraph->iRailPitch + 10; + int w = pGraph->mxRail*pGraph->iRailPitch + 28; @ <tr><td></td><td> @ <div id="grbtm" style="width:%d(w)px;"></div> @ </td><td></td></tr> } } @ </table> timeline_output_graph_javascript(pGraph, 0, 1); style_footer(); } ADDED src/foci.c Index: src/foci.c ================================================================== --- /dev/null +++ src/foci.c @@ -0,0 +1,243 @@ +/* +** Copyright (c) 2014 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This routine implements an SQLite virtual table that gives all of the +** files associated with a single check-in. +** +** The filename "foci" is short for "Files of Check-in". +** +** Usage example: +** +** CREATE VIRTUAL TABLE temp.foci USING files_of_checkin; +** -- ^^^^--- important! +** SELECT * FROM foci WHERE checkinID=symbolic_name_to_rid('trunk'); +** +** The symbolic_name_to_rid('trunk') function finds the BLOB.RID value +** corresponding to the 'trunk' tag. Then the files_of_checkin virtual table +** decodes the manifest defined by that BLOB and returns all files described +** by that manifest. The "schema" for the temp.foci table is: +** +** CREATE TABLE files_of_checkin( +** checkinID INTEGER, -- RID for the check-in manifest +** filename TEXT, -- Name of a file +** uuid TEXT, -- SHA1 hash of the file +** previousName TEXT, -- Name of the file in previous check-in +** perm TEXT -- Permissions on the file +** ); +** +*/ +#include "config.h" +#include "foci.h" +#include <assert.h> + +/* +** The schema for the virtual table: +*/ +static const char zFociSchema[] = +@ CREATE TABLE files_of_checkin( +@ checkinID INTEGER, -- RID for the check-in manifest +@ filename TEXT, -- Name of a file +@ uuid TEXT, -- SHA1 hash of the file +@ previousName TEXT, -- Name of the file in previous check-in +@ perm TEXT -- Permissions on the file +@ ); +; + +#if INTERFACE +/* +** The subclasses of sqlite3_vtab and sqlite3_vtab_cursor tables +** that implement the files_of_checkin virtual table. +*/ +struct FociTable { + sqlite3_vtab base; /* Base class - must be first */ +}; +struct FociCursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + Manifest *pMan; /* Current manifest */ + ManifestFile *pFile; /* Current file */ + int iFile; /* File index */ +}; +#endif /* INTERFACE */ + + +/* +** Connect to or create a foci virtual table. +*/ +static int fociConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + FociTable *pTab; + + pTab = (FociTable *)sqlite3_malloc(sizeof(FociTable)); + memset(pTab, 0, sizeof(FociTable)); + sqlite3_declare_vtab(db, zFociSchema); + *ppVtab = &pTab->base; + return SQLITE_OK; +} + +/* +** Disconnect from or destroy a focivfs virtual table. +*/ +static int fociDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Available scan methods: +** +** (0) A full scan. Visit every manifest in the repo. (Slow) +** (1) checkinID=?. visit only the single manifest specifed. +*/ +static int fociBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + int i; + pIdxInfo->estimatedCost = 10000.0; + for(i=0; i<pIdxInfo->nConstraint; i++){ + if( pIdxInfo->aConstraint[i].iColumn==0 + && pIdxInfo->aConstraint[i].op==SQLITE_INDEX_CONSTRAINT_EQ ){ + pIdxInfo->idxNum = 1; + pIdxInfo->estimatedCost = 1.0; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + pIdxInfo->aConstraintUsage[i].omit = 1; + break; + } + } + return SQLITE_OK; +} + +/* +** Open a new focivfs cursor. +*/ +static int fociOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + FociCursor *pCsr; + pCsr = (FociCursor *)sqlite3_malloc(sizeof(FociCursor)); + memset(pCsr, 0, sizeof(FociCursor)); + pCsr->base.pVtab = pVTab; + *ppCursor = (sqlite3_vtab_cursor *)pCsr; + return SQLITE_OK; +} + +/* +** Close a focivfs cursor. +*/ +static int fociClose(sqlite3_vtab_cursor *pCursor){ + FociCursor *pCsr = (FociCursor *)pCursor; + manifest_destroy(pCsr->pMan); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** Move a focivfs cursor to the next entry in the file. +*/ +static int fociNext(sqlite3_vtab_cursor *pCursor){ + FociCursor *pCsr = (FociCursor *)pCursor; + pCsr->pFile = manifest_file_next(pCsr->pMan, 0); + pCsr->iFile++; + return SQLITE_OK; +} + +static int fociEof(sqlite3_vtab_cursor *pCursor){ + FociCursor *pCsr = (FociCursor *)pCursor; + return pCsr->pFile==0; +} + +static int fociFilter( + sqlite3_vtab_cursor *pCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + FociCursor *pCur = (FociCursor *)pCursor; + manifest_destroy(pCur->pMan); + if( idxNum ){ + pCur->pMan = manifest_get(sqlite3_value_int(argv[0]), CFTYPE_MANIFEST, 0); + pCur->iFile = 0; + manifest_file_rewind(pCur->pMan); + pCur->pFile = manifest_file_next(pCur->pMan, 0); + }else{ + pCur->pMan = 0; + pCur->iFile = 0; + } + return SQLITE_OK; +} + +static int fociColumn( + sqlite3_vtab_cursor *pCursor, + sqlite3_context *ctx, + int i +){ + FociCursor *pCsr = (FociCursor *)pCursor; + switch( i ){ + case 0: /* checkinID */ + sqlite3_result_int(ctx, pCsr->pMan->rid); + break; + case 1: /* filename */ + sqlite3_result_text(ctx, pCsr->pFile->zName, -1, + SQLITE_TRANSIENT); + break; + case 2: /* uuid */ + sqlite3_result_text(ctx, pCsr->pFile->zUuid, -1, + SQLITE_TRANSIENT); + break; + case 3: /* previousName */ + sqlite3_result_text(ctx, pCsr->pFile->zPrior, -1, + SQLITE_TRANSIENT); + break; + case 4: /* perm */ + sqlite3_result_text(ctx, pCsr->pFile->zPerm, -1, + SQLITE_TRANSIENT); + break; + } + return SQLITE_OK; +} + +static int fociRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ + FociCursor *pCsr = (FociCursor *)pCursor; + *pRowid = pCsr->iFile; + return SQLITE_OK; +} + +int foci_register(sqlite3 *db){ + static sqlite3_module foci_module = { + 0, /* iVersion */ + fociConnect, /* xCreate */ + fociConnect, /* xConnect */ + fociBestIndex, /* xBestIndex */ + fociDisconnect, /* xDisconnect */ + fociDisconnect, /* xDestroy */ + fociOpen, /* xOpen - open a cursor */ + fociClose, /* xClose - close a cursor */ + fociFilter, /* xFilter - configure scan constraints */ + fociNext, /* xNext - advance a cursor */ + fociEof, /* xEof - check for end of scan */ + fociColumn, /* xColumn - read data */ + fociRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + }; + sqlite3_create_module(db, "files_of_checkin", &foci_module, 0); + return SQLITE_OK; +} ADDED src/fusefs.c Index: src/fusefs.c ================================================================== --- /dev/null +++ src/fusefs.c @@ -0,0 +1,344 @@ +/* +** Copyright (c) 2014 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) +** +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@sqlite.org +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This module implements the userspace side of a Fuse Filesystem that +** contains all check-ins for a fossil repository. +** +** This module is a mostly a no-op unless compiled with -DFOSSIL_HAVE_FUSEFS. +** The FOSSIL_HAVE_FUSEFS should be omitted on systems that lack support for +** the Fuse Filesystem, of course. +*/ +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include "fusefs.h" +#ifdef FOSSIL_HAVE_FUSEFS + +#define FUSE_USE_VERSION 26 +#include <fuse.h> + +/* +** Global state information about the archive +*/ +static struct sGlobal { + /* A cache of a single check-in manifest */ + int rid; /* rid for the cached manifest */ + char *zSymName; /* Symbolic name corresponding to rid */ + Manifest *pMan; /* The cached manifest */ + /* A cache of a single file within a single check-in */ + int iFileRid; /* Check-in ID for the cached file */ + ManifestFile *pFile; /* Name of a cached file */ + Blob content; /* Content of the cached file */ + /* Parsed path */ + char *az[3]; /* 0=type, 1=id, 2=path */ +} fusefs; + +/* +** Clear the fusefs.sz[] array. +*/ +static void fusefs_clear_path(void){ + int i; + for(i=0; i<count(fusefs.az); i++){ + fossil_free(fusefs.az[i]); + fusefs.az[i] = 0; + } +} + +/* +** Split of the input path into 0, 1, 2, or 3 elements in fusefs.az[]. +** Return the number of elements. +** +** Any prior path parse is deleted. +*/ +static int fusefs_parse_path(const char *zPath){ + int i, j; + fusefs_clear_path(); + if( strcmp(zPath,"/")==0 ) return 0; + for(i=0, j=1; i<2 && zPath[j]; i++){ + int jStart = j; + while( zPath[j] && zPath[j]!='/' ){ j++; } + fusefs.az[i] = mprintf("%.*s", j-jStart, &zPath[jStart]); + if( zPath[j] ) j++; + } + if( zPath[j] ) fusefs.az[i++] = fossil_strdup(&zPath[j]); + return i; +} + +/* +** Reclaim memory used by the fusefs local variable. +*/ +static void fusefs_reset(void){ + blob_reset(&fusefs.content); + manifest_destroy(fusefs.pMan); + fusefs.pMan = 0; + fossil_free(fusefs.zSymName); + fusefs.zSymName = 0; + fusefs.pFile = 0; +} + +/* +** Load manifest rid into the cache. +*/ +static void fusefs_load_rid(int rid, const char *zSymName){ + if( fusefs.rid==rid && fusefs.pMan!=0 ) return; + fusefs_reset(); + fusefs.zSymName = fossil_strdup(zSymName); + fusefs.pMan = manifest_get(rid, CFTYPE_MANIFEST, 0); + fusefs.rid = rid; +} + +/* +** Locate the rid corresponding to a symbolic name +*/ +static int fusefs_name_to_rid(const char *zSymName){ + if( fusefs.rid>0 && strcmp(zSymName, fusefs.zSymName)==0 ){ + return fusefs.rid; + }else{ + return symbolic_name_to_rid(zSymName, "ci"); + } +} + + +/* +** Implementation of stat() +*/ +static int fusefs_getattr(const char *zPath, struct stat *stbuf){ + int n, rid; + ManifestFile *pFile; + char *zDir; + stbuf->st_uid = getuid(); + stbuf->st_gid = getgid(); + n = fusefs_parse_path(zPath); + if( n==0 ){ + stbuf->st_mode = S_IFDIR | 0555; + stbuf->st_nlink = 2; + return 0; + } + if( strcmp(fusefs.az[0],"checkins")!=0 ) return -ENOENT; + if( n==1 ){ + stbuf->st_mode = S_IFDIR | 0111; + stbuf->st_nlink = 2; + return 0; + } + rid = fusefs_name_to_rid(fusefs.az[1]); + if( rid<=0 ) return -ENOENT; + if( n==2 ){ + stbuf->st_mode = S_IFDIR | 0555; + stbuf->st_nlink = 2; + return 0; + } + fusefs_load_rid(rid, fusefs.az[1]); + if( fusefs.pMan==0 ) return -ENOENT; + stbuf->st_mtime = (fusefs.pMan->rDate - 2440587.5)*86400.0; + pFile = manifest_file_seek(fusefs.pMan, fusefs.az[2], 0); + if( pFile ){ + static Stmt q; + stbuf->st_mode = S_IFREG | + (manifest_file_mperm(pFile)==PERM_EXE ? 0555 : 0444); + stbuf->st_nlink = 1; + db_static_prepare(&q, "SELECT size FROM blob WHERE uuid=$uuid"); + db_bind_text(&q, "$uuid", pFile->zUuid); + if( db_step(&q)==SQLITE_ROW ){ + stbuf->st_size = db_column_int(&q, 0); + } + db_reset(&q); + return 0; + } + zDir = mprintf("%s/", fusefs.az[2]); + pFile = manifest_file_seek(fusefs.pMan, zDir, 1); + fossil_free(zDir); + if( pFile==0 ) return -ENOENT; + n = (int)strlen(fusefs.az[2]); + if( strncmp(fusefs.az[2], pFile->zName, n)!=0 ) return -ENOENT; + if( pFile->zName[n]!='/' ) return -ENOENT; + stbuf->st_mode = S_IFDIR | 0555; + stbuf->st_nlink = 2; + return 0; +} + +/* +** Implementation of readdir() +*/ +static int fusefs_readdir( + const char *zPath, + void *buf, + fuse_fill_dir_t filler, + off_t offset, + struct fuse_file_info *fi +){ + int n, rid; + ManifestFile *pFile; + const char *zPrev = ""; + int nPrev = 0; + char *z; + int cnt = 0; + n = fusefs_parse_path(zPath); + if( n==0 ){ + filler(buf, ".", NULL, 0); + filler(buf, "..", NULL, 0); + filler(buf, "checkins", NULL, 0); + return 0; + } + if( strcmp(fusefs.az[0],"checkins")!=0 ) return -ENOENT; + if( n==1 ) return -ENOENT; + rid = fusefs_name_to_rid(fusefs.az[1]); + if( rid<=0 ) return -ENOENT; + fusefs_load_rid(rid, fusefs.az[1]); + if( fusefs.pMan==0 ) return -ENOENT; + filler(buf, ".", NULL, 0); + filler(buf, "..", NULL, 0); + manifest_file_rewind(fusefs.pMan); + if( n==2 ){ + while( (pFile = manifest_file_next(fusefs.pMan, 0))!=0 ){ + if( nPrev>0 && strncmp(pFile->zName, zPrev, nPrev)==0 ) continue; + zPrev = pFile->zName; + for(nPrev=0; zPrev[nPrev] && zPrev[nPrev]!='/'; nPrev++){} + z = mprintf("%.*s", nPrev, zPrev); + filler(buf, z, NULL, 0); + fossil_free(z); + cnt++; + } + }else{ + char *zBase = mprintf("%s/", fusefs.az[2]); + int nBase = (int)strlen(zBase); + while( (pFile = manifest_file_next(fusefs.pMan, 0))!=0 ){ + if( strcmp(pFile->zName, zBase)>=0 ) break; + } + while( pFile && strncmp(zBase, pFile->zName, nBase)==0 ){ + if( nPrev==0 || strncmp(pFile->zName+nBase, zPrev, nPrev)!=0 ){ + zPrev = pFile->zName+nBase; + for(nPrev=0; zPrev[nPrev] && zPrev[nPrev]!='/'; nPrev++){} + if( zPrev[nPrev]=='/' ){ + z = mprintf("%.*s", nPrev, zPrev); + filler(buf, z, NULL, 0); + fossil_free(z); + }else{ + filler(buf, zPrev, NULL, 0); + nPrev = 0; + } + cnt++; + } + pFile = manifest_file_next(fusefs.pMan, 0); + } + fossil_free(zBase); + } + return cnt>0 ? 0 : -ENOENT; +} + + +/* +** Implementation of read() +*/ +static int fusefs_read( + const char *zPath, + char *buf, + size_t size, + off_t offset, + struct fuse_file_info *fi +){ + int n, rid; + n = fusefs_parse_path(zPath); + if( n<3 ) return -ENOENT; + if( strcmp(fusefs.az[0], "checkins")!=0 ) return -ENOENT; + rid = fusefs_name_to_rid(fusefs.az[1]); + if( rid<=0 ) return -ENOENT; + fusefs_load_rid(rid, fusefs.az[1]); + if( fusefs.pFile!=0 && strcmp(fusefs.az[2], fusefs.pFile->zName)!=0 ){ + fusefs.pFile = 0; + blob_reset(&fusefs.content); + } + fusefs.pFile = manifest_file_seek(fusefs.pMan, fusefs.az[2], 0); + if( fusefs.pFile==0 ) return -ENOENT; + rid = uuid_to_rid(fusefs.pFile->zUuid, 0); + blob_reset(&fusefs.content); + content_get(rid, &fusefs.content); + if( offset>blob_size(&fusefs.content) ) return 0; + if( offset+size>blob_size(&fusefs.content) ){ + size = blob_size(&fusefs.content) - offset; + } + memcpy(buf, blob_buffer(&fusefs.content)+offset, size); + return size; +} + +static struct fuse_operations fusefs_methods = { + .getattr = fusefs_getattr, + .readdir = fusefs_readdir, + .read = fusefs_read, +}; +#endif /* FOSSIL_HAVE_FUSEFS */ + +/* +** COMMAND: fusefs +** +** Usage: %fossil fusefs [--debug] DIRECTORY +** +** This command uses the Fuse Filesystem to mount a directory at +** DIRECTORY that contains the content of all check-ins in the +** repository. The names of files are DIRECTORY/checkins/VERSION/PATH +** where DIRECTORY is the root of the mount, VERSION is any valid +** check-in name (examples: "trunk" or "tip" or a tag or any unique +** prefix of a SHA1 hash, etc) and PATH is the pathname of the file +** in the check-in. If DIRECTORY does not exist, then an attempt is +** made to create it. +** +** The DIRECTORY/checkins directory is not searchable so one cannot +** do "ls DIRECTORY/checkins" to get a listing of all possible check-in +** names. There are countless variations on check-in names and it is +** impractical to list them all. But all other directories are searchable +** and so the "ls" command will work everywhere else in the fusefs +** file hierarchy. +** +** The FuseFS typically only works on Linux, and then only on Linux +** systems that have the right kernel drivers and have install the +** appropriate support libraries. +** +** After stopping the "fossil fusefs" command, it might also be necessary +** to run "fusermount -u DIRECTORY" to reset the FuseFS before using it +** again. +*/ +void fusefs_cmd(void){ +#ifndef FOSSIL_HAVE_FUSEFS + fossil_fatal("this build of fossil does not support the fuse filesystem"); +#else + char *zMountPoint; + char *azNewArgv[5]; + int doDebug = find_option("debug","d",0)!=0; + + db_find_and_open_repository(0,0); + verify_all_options(); + blob_init(&fusefs.content, 0, 0); + if( g.argc!=3 ) usage("DIRECTORY"); + zMountPoint = g.argv[2]; + if( file_mkdir(zMountPoint, 0) ){ + fossil_fatal("cannot make directory [%s]", zMountPoint); + } + azNewArgv[0] = g.argv[0]; + azNewArgv[1] = doDebug ? "-d" : "-f"; + azNewArgv[2] = "-s"; + azNewArgv[3] = zMountPoint; + azNewArgv[4] = 0; + g.localOpen = 0; /* Prevent tags like "current" and "prev" */ + fuse_main(4, azNewArgv, &fusefs_methods, NULL); + fusefs_reset(); + fusefs_clear_path(); +#endif +} Index: src/glob.c ================================================================== --- src/glob.c +++ src/glob.c @@ -39,16 +39,16 @@ ** This routine makes no effort to free the memory space it uses, which ** currently consists of a blob object and its contents. */ char *glob_expr(const char *zVal, const char *zGlobList){ Blob expr; - char *zSep = "("; + const char *zSep = "("; int nTerm = 0; int i; int cTerm; - if( zGlobList==0 || zGlobList[0]==0 ) return "0"; + if( zGlobList==0 || zGlobList[0]==0 ) return fossil_strdup("0"); blob_zero(&expr); while( zGlobList[0] ){ while( fossil_isspace(zGlobList[0]) || zGlobList[0]==',' ){ zGlobList++; /* Skip leading commas, spaces, and newlines */ } @@ -73,11 +73,11 @@ } if( nTerm ){ blob_appendf(&expr, ")"); return blob_str(&expr); }else{ - return "0"; + return fossil_strdup("0"); } } #if INTERFACE /* @@ -138,89 +138,10 @@ z += i+1; } return p; } -/* -** Return non-zero if string z matches glob pattern zGlob and zero if the -** pattern does not match. -** -** Globbing rules: -** -** '*' Matches any sequence of zero or more characters. -** -** '?' Matches exactly one character. -** -** [...] Matches one character from the enclosed list of -** characters. -** -** [^...] Matches one character not in the enclosed list. -*/ -int strglob(const char *zGlob, const char *z){ - int c, c2; - int invert; - int seen; - - while( (c = (*(zGlob++)))!=0 ){ - if( c=='*' ){ - while( (c=(*(zGlob++))) == '*' || c=='?' ){ - if( c=='?' && (*(z++))==0 ) return 0; - } - if( c==0 ){ - return 1; - }else if( c=='[' ){ - while( *z && strglob(zGlob-1,z)==0 ){ - z++; - } - return (*z)!=0; - } - while( (c2 = (*(z++)))!=0 ){ - while( c2!=c ){ - c2 = *(z++); - if( c2==0 ) return 0; - } - if( strglob(zGlob,z) ) return 1; - } - return 0; - }else if( c=='?' ){ - if( (*(z++))==0 ) return 0; - }else if( c=='[' ){ - int prior_c = 0; - seen = 0; - invert = 0; - c = *(z++); - if( c==0 ) return 0; - c2 = *(zGlob++); - if( c2=='^' ){ - invert = 1; - c2 = *(zGlob++); - } - if( c2==']' ){ - if( c==']' ) seen = 1; - c2 = *(zGlob++); - } - while( c2 && c2!=']' ){ - if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ - c2 = *(zGlob++); - if( c>=prior_c && c<=c2 ) seen = 1; - prior_c = 0; - }else{ - if( c==c2 ){ - seen = 1; - } - prior_c = c2; - } - c2 = *(zGlob++); - } - if( c2==0 || (seen ^ invert)==0 ) return 0; - }else{ - if( c!=(*(z++)) ) return 0; - } - } - return *z==0; -} - /* ** Return true (non-zero) if zString matches any of the patterns in ** the Glob. The value returned is actually a 1-based index of the pattern ** that matched. Return 0 if none of the patterns match zString. ** @@ -228,11 +149,11 @@ */ int glob_match(Glob *pGlob, const char *zString){ int i; if( pGlob==0 ) return 0; for(i=0; i<pGlob->nPattern; i++){ - if( strglob(pGlob->azPattern[i], zString) ) return i+1; + if( sqlite3_strglob(pGlob->azPattern[i], zString)==0 ) return i+1; } return 0; } /* Index: src/graph.c ================================================================== --- src/graph.c +++ src/graph.c @@ -34,24 +34,24 @@ int rid; /* The rid for the check-in */ i8 nParent; /* Number of parents */ int *aParent; /* Array of parents. 0 element is primary .*/ char *zBranch; /* Branch name */ char *zBgClr; /* Background Color */ - char zUuid[17]; /* Check-in for file ID */ + char zUuid[41]; /* Check-in for file ID */ GraphRow *pNext; /* Next row down in the list of all rows */ GraphRow *pPrev; /* Previous row */ - + int idx; /* Row index. First is 1. 0 used for "none" */ int idxTop; /* Direct descendent highest up on the graph */ GraphRow *pChild; /* Child immediately above this node */ u8 isDup; /* True if this is duplicate of a prior entry */ u8 isLeaf; /* True if this is a leaf node */ u8 timeWarp; /* Child is earlier in time */ u8 bDescender; /* True if riser from bottom of graph to here. */ i8 iRail; /* Which rail this check-in appears on. 0-based.*/ - i8 mergeOut; /* Merge out to this rail. -1 if no merge-out */ + i8 mergeOut; /* Merge out on rail mergeOut/4. -1 for none */ u8 mergeIn[GR_MAX_RAIL]; /* Merge in from non-zero rails */ int aiRiser[GR_MAX_RAIL]; /* Risers from this node to a higher row. */ int mergeUpto; /* Draw the mergeOut rail up to this level */ u64 mergeDown; /* Draw merge lines up from bottom of graph */ @@ -154,11 +154,11 @@ ** Return the canonical pointer for a given branch name. ** Multiple calls to this routine with equivalent strings ** will return the same pointer. ** ** The returned value is a pointer to a (readonly) string that -** has the useful property that strings can be checked for +** has the useful property that strings can be checked for ** equality by comparing pointers. ** ** Note: also used for background color names. */ static char *persistBranchName(GraphContext *p, const char *zBranch){ @@ -214,11 +214,11 @@ return pRow->idx; } /* ** Return the index of a rail currently not in use for any row between -** top and bottom, inclusive. +** top and bottom, inclusive. */ static int findFreeRail( GraphContext *p, /* The graph context */ int top, int btm, /* Span of rows for which the rail is needed */ u64 inUseMask, /* Mask or rails already in use */ @@ -363,11 +363,11 @@ /* Purge merge-parents that are out-of-graph if descenders are not ** drawn. ** ** Each node has one primary parent and zero or more "merge" parents. - ** A merge parent is a prior checkin from which changes were merged into + ** A merge parent is a prior check-in from which changes were merged into ** the current check-in. If a merge parent is not in the visible section ** of this graph, then no arrows will be drawn for it, so remove it from ** the aParent[] array. */ if( omitDescenders ){ @@ -379,12 +379,33 @@ } } } } + /* If the primary parent is in a different branch, but there are + ** other parents in the same branch, reorder the parents to make + ** the parent from the same branch the primary parent. + */ + for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ + if( pRow->isDup ) continue; + if( pRow->nParent<2 ) continue; /* Not a fork */ + pParent = hashFind(p, pRow->aParent[0]); + if( pParent==0 ) continue; /* Parent off-screen */ + if( pParent->zBranch==pRow->zBranch ) continue; /* Same branch */ + for(i=1; i<pRow->nParent; i++){ + pParent = hashFind(p, pRow->aParent[i]); + if( pParent && pParent->zBranch==pRow->zBranch ){ + int t = pRow->aParent[0]; + pRow->aParent[0] = pRow->aParent[i]; + pRow->aParent[i] = t; + break; + } + } + } + - /* Find the pChild pointer for each node. + /* Find the pChild pointer for each node. ** ** The pChild points to the node directly above on the same rail. ** The pChild must be in the same branch. Leaf nodes have a NULL ** pChild. ** @@ -533,11 +554,11 @@ } } } /* - ** Insert merge rails from primaries to duplicates. + ** Insert merge rails from primaries to duplicates. */ if( hasDup ){ int dupRail; int mxRail; find_max_rail(p); @@ -563,9 +584,12 @@ /* ** Find the maximum rail number. */ find_max_rail(p); - p->iRailPitch = 18 - (p->mxRail/3); - if( p->iRailPitch<12 ) p->iRailPitch = 12; + p->iRailPitch = atoi(PD("railpitch","0")); + if( p->iRailPitch<=0 ){ + p->iRailPitch = 18 - (p->mxRail/3); + if( p->iRailPitch<11 ) p->iRailPitch = 11; + } p->nErr = 0; } Index: src/gzip.c ================================================================== --- src/gzip.c +++ src/gzip.c @@ -19,13 +19,18 @@ ** file. The GZIP format is described in RFC-1952. ** ** State information is stored in static variables, so this implementation ** can only be building up a single GZIP file at a time. */ +#include "config.h" #include <assert.h> -#include <zlib.h> -#include "config.h" +#if defined(FOSSIL_ENABLE_MINIZ) +# define MINIZ_HEADER_FILE_ONLY +# include "miniz.c" +#else +# include <zlib.h> +#endif #include "gzip.h" /* ** State information for the GZIP file under construction. */ @@ -55,11 +60,11 @@ blob_zero(&gzip.out); aHdr[0] = 0x1f; aHdr[1] = 0x8b; aHdr[2] = 8; aHdr[3] = 0; - if( now==0 ){ + if( now==-1 ){ now = db_int64(0, "SELECT (julianday('now') - 2440587.5)*86400.0"); } put32(&aHdr[4], now&0xffffffff); aHdr[8] = 2; aHdr[9] = 255; @@ -73,11 +78,11 @@ */ #define GZIP_BUFSZ 100000 void gzip_step(const char *pIn, int nIn){ char *zOutBuf; int nOut; - + nOut = nIn + nIn/10 + 100; if( nOut<100000 ) nOut = 100000; zOutBuf = fossil_malloc(nOut); gzip.stream.avail_in = nIn; gzip.stream.next_in = (unsigned char*)pIn; @@ -126,15 +131,15 @@ void test_gzip_cmd(void){ Blob b; char *zOut; if( g.argc!=3 ) usage("FILENAME"); sqlite3_open(":memory:", &g.db); - gzip_begin(0); + gzip_begin(-1); blob_read_from_file(&b, g.argv[2]); zOut = mprintf("%s.gz", g.argv[2]); gzip_step(blob_buffer(&b), blob_size(&b)); blob_reset(&b); gzip_finish(&b); blob_write_to_file(&b, zOut); blob_reset(&b); fossil_free(zOut); } Index: src/http.c ================================================================== --- src/http.c +++ src/http.c @@ -19,10 +19,26 @@ */ #include "config.h" #include "http.h" #include <assert.h> +#ifdef _WIN32 +#include <io.h> +#ifndef isatty +#define isatty(d) _isatty(d) +#endif +#ifndef fileno +#define fileno(s) _fileno(s) +#endif +#endif + +/* Maximum number of HTTP Authorization attempts */ +#define MAX_HTTP_AUTH 2 + +/* Keep track of HTTP Basic Authorization failures */ +static int fSeenHttpAuth = 0; + /* ** Construct the "login" card with the client credentials. ** ** login LOGIN NONCE SIGNATURE ** @@ -39,41 +55,35 @@ const char *zPw; /* The user password */ Blob pw; /* The nonce with user password appended */ Blob sig; /* The signature field */ blob_zero(pLogin); - if( g.urlUser==0 || fossil_strcmp(g.urlUser, "anonymous")==0 ){ + if( g.url.user==0 || fossil_strcmp(g.url.user, "anonymous")==0 ){ return; /* If no login card for users "nobody" and "anonymous" */ } - if( g.urlIsSsh ){ + if( g.url.isSsh ){ return; /* If no login card for SSH: */ } blob_zero(&nonce); blob_zero(&pw); sha1sum_blob(pPayload, &nonce); blob_copy(&pw, &nonce); - zLogin = g.urlUser; - if( g.urlPasswd ){ - zPw = g.urlPasswd; + zLogin = g.url.user; + if( g.url.passwd ){ + zPw = g.url.passwd; }else if( g.cgiOutput ){ /* Password failure while doing a sync from the web interface */ cgi_printf("*** incorrect or missing password for user %h\n", zLogin); zPw = 0; }else{ /* Password failure while doing a sync from the command-line interface */ url_prompt_for_password(); - zPw = g.urlPasswd; - } - - /* If the first character of the password is "#", then that character is - ** not really part of the password - it is an indicator that we should - ** use Basic Authentication. So skip that character. - */ - if( zPw && zPw[0]=='#' ) zPw++; + zPw = g.url.passwd; + } /* The login card wants the SHA1 hash of the password, so convert the - ** password to its SHA1 hash it it isn't already a SHA1 hash. + ** password to its SHA1 hash if it isn't already a SHA1 hash. */ /* fossil_print("\nzPw=[%s]\n", zPw); // TESTING ONLY */ if( zPw && zPw[0] ) zPw = sha1_shared_secret(zPw, zLogin, 0); blob_append(&pw, zPw, -1); @@ -92,37 +102,97 @@ static void http_build_header(Blob *pPayload, Blob *pHdr){ int i; const char *zSep; blob_zero(pHdr); - i = strlen(g.urlPath); - if( i>0 && g.urlPath[i-1]=='/' ){ + i = strlen(g.url.path); + if( i>0 && g.url.path[i-1]=='/' ){ zSep = ""; }else{ zSep = "/"; } - blob_appendf(pHdr, "POST %s%sxfer/xfer HTTP/1.0\r\n", g.urlPath, zSep); - if( g.urlProxyAuth ){ - blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.urlProxyAuth); + blob_appendf(pHdr, "POST %s%sxfer/xfer HTTP/1.0\r\n", g.url.path, zSep); + if( g.url.proxyAuth ){ + blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth); } - if( g.urlPasswd && g.urlUser && g.urlPasswd[0]=='#' ){ - char *zCredentials = mprintf("%s:%s", g.urlUser, &g.urlPasswd[1]); + if( g.zHttpAuth && g.zHttpAuth[0] ){ + const char *zCredentials = g.zHttpAuth; char *zEncoded = encode64(zCredentials, -1); blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded); fossil_free(zEncoded); - fossil_free(zCredentials); } - blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname); - blob_appendf(pHdr, "User-Agent: Fossil/" RELEASE_VERSION - " (" MANIFEST_DATE " " MANIFEST_VERSION ")\r\n"); + blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname); + blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent()); + if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n"); if( g.fHttpTrace ){ blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n"); }else{ blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n"); } blob_appendf(pHdr, "Content-Length: %d\r\n\r\n", blob_size(pPayload)); } + +/* +** Use Fossil credentials for HTTP Basic Authorization prompt +*/ +static int use_fossil_creds_for_httpauth_prompt(void){ + Blob x; + char c; + prompt_user("Use Fossil username and password (y/N)? ", &x); + c = blob_str(&x)[0]; + blob_reset(&x); + return ( c=='y' || c=='Y' ); +} + +/* +** Prompt to save HTTP Basic Authorization information +*/ +static int save_httpauth_prompt(void){ + Blob x; + char c; + if( (g.url.flags & URL_REMEMBER)==0 ) return 0; + prompt_user("Remember Basic Authorization credentials (Y/n)? ", &x); + c = blob_str(&x)[0]; + blob_reset(&x); + return ( c!='n' && c!='N' ); +} + +/* +** Get the HTTP Basic Authorization credentials from the user +** when 401 is received. +*/ +char *prompt_for_httpauth_creds(void){ + Blob x; + char *zUser; + char *zPw; + char *zPrompt; + char *zHttpAuth = 0; + if( !isatty(fileno(stdin)) ) return 0; + zPrompt = mprintf("\n%s authorization required by\n%s\n", + g.url.isHttps==1 ? "Encrypted HTTPS" : "Unencrypted HTTP", g.url.canonical); + fossil_print("%s", zPrompt); + free(zPrompt); + if ( g.url.user && g.url.passwd && use_fossil_creds_for_httpauth_prompt() ){ + zHttpAuth = mprintf("%s:%s", g.url.user, g.url.passwd); + }else{ + prompt_user("Basic Authorization user: ", &x); + zUser = mprintf("%b", &x); + zPrompt = mprintf("HTTP password for %b: ", &x); + blob_reset(&x); + prompt_for_password(zPrompt, &x, 1); + zPw = mprintf("%b", &x); + zHttpAuth = mprintf("%s:%s", zUser, zPw); + free(zUser); + free(zPw); + free(zPrompt); + blob_reset(&x); + } + if( save_httpauth_prompt() ){ + set_httpauth(zHttpAuth); + } + return zHttpAuth; +} /* ** Sign the content in pSend, compress it, and send it to the server ** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply ** in pRecv. pRecv is assumed to be uninitialized when @@ -135,20 +205,21 @@ int http_exchange(Blob *pSend, Blob *pReply, int useLogin, int maxRedirect){ Blob login; /* The login card */ Blob payload; /* The complete payload including login card */ Blob hdr; /* The HTTP request header */ int closeConnection; /* True to close the connection when done */ - int iLength; /* Length of the reply payload */ + int iLength; /* Expected length of the reply payload */ + int iRecvLen; /* Received length of the reply payload */ int rc = 0; /* Result code */ int iHttpVersion; /* Which version of HTTP protocol server uses */ char *zLine; /* A single line of the reply header */ int i; /* Loop counter */ int isError = 0; /* True if the reply is an error message */ int isCompressed = 1; /* True if the reply is compressed */ - if( transport_open() ){ - fossil_warning(transport_errmsg()); + if( transport_open(&g.url) ){ + fossil_warning("%s", transport_errmsg(&g.url)); return 1; } /* Construct the login card and prepare the complete payload */ blob_zero(&login); @@ -190,26 +261,36 @@ } /* ** Send the request to the server. */ - transport_send(&hdr); - transport_send(&payload); + transport_send(&g.url, &hdr); + transport_send(&g.url, &payload); blob_reset(&hdr); blob_reset(&payload); - transport_flip(); - + transport_flip(&g.url); + /* ** Read and interpret the server reply */ closeConnection = 1; iLength = -1; - while( (zLine = transport_receive_line())!=0 && zLine[0]!=0 ){ + while( (zLine = transport_receive_line(&g.url))!=0 && zLine[0]!=0 ){ /* printf("[%s]\n", zLine); fflush(stdout); */ if( fossil_strnicmp(zLine, "http/1.", 7)==0 ){ if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err; - if( rc!=200 && rc!=302 ){ + if( rc==401 ){ + if( fSeenHttpAuth++ < MAX_HTTP_AUTH ){ + if( g.zHttpAuth ){ + if( g.zHttpAuth ) free(g.zHttpAuth); + } + g.zHttpAuth = prompt_for_httpauth_creds(); + transport_close(&g.url); + return http_exchange(pSend, pReply, useLogin, maxRedirect); + } + } + if( rc!=200 && rc!=301 && rc!=302 ){ int ii; for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){} while( zLine[ii]==' ' ) ii++; fossil_warning("server says: %s", &zLine[ii]); goto write_err; @@ -217,10 +298,20 @@ if( iHttpVersion==0 ){ closeConnection = 1; }else{ closeConnection = 0; } + }else if( g.url.isSsh && fossil_strnicmp(zLine, "status:", 7)==0 ){ + if( sscanf(zLine, "Status: %d", &rc)!=1 ) goto write_err; + if( rc!=200 && rc!=301 && rc!=302 ){ + int ii; + for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){} + while( zLine[ii]==' ' ) ii++; + fossil_warning("server says: %s", &zLine[ii]); + goto write_err; + } + closeConnection = 0; }else if( fossil_strnicmp(zLine, "content-length:", 15)==0 ){ for(i=15; fossil_isspace(zLine[i]); i++){} iLength = atoi(&zLine[i]); }else if( fossil_strnicmp(zLine, "connection:", 11)==0 ){ char c; @@ -229,53 +320,66 @@ if( c=='c' || c=='C' ){ closeConnection = 1; }else if( c=='k' || c=='K' ){ closeConnection = 0; } - }else if( rc==302 && fossil_strnicmp(zLine, "location:", 9)==0 ){ + }else if( ( rc==301 || rc==302 ) && + fossil_strnicmp(zLine, "location:", 9)==0 ){ int i, j; if ( --maxRedirect == 0){ - fossil_fatal("redirect limit exceeded"); + fossil_warning("redirect limit exceeded"); + goto write_err; } for(i=9; zLine[i] && zLine[i]==' '; i++){} - if( zLine[i]==0 ) fossil_fatal("malformed redirect: %s", zLine); - j = strlen(zLine) - 1; + if( zLine[i]==0 ){ + fossil_warning("malformed redirect: %s", zLine); + goto write_err; + } + j = strlen(zLine) - 1; while( j>4 && fossil_strcmp(&zLine[j-4],"/xfer")==0 ){ j -= 4; zLine[j] = 0; } - fossil_print("redirect to %s\n", &zLine[i]); + transport_close(&g.url); + transport_global_shutdown(&g.url); + fossil_print("redirect with status %d to %s\n", rc, &zLine[i]); url_parse(&zLine[i], 0); - transport_close(); + fSeenHttpAuth = 0; + if( g.zHttpAuth ) free(g.zHttpAuth); + g.zHttpAuth = get_httpauth(); return http_exchange(pSend, pReply, useLogin, maxRedirect); }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){ if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){ isCompressed = 0; - }else if( fossil_strnicmp(&zLine[14], + }else if( fossil_strnicmp(&zLine[14], "application/x-fossil-uncompressed", -1)==0 ){ isCompressed = 0; }else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){ isError = 1; } } } if( iLength<0 ){ - fossil_fatal("server did not reply"); + fossil_warning("server did not reply"); goto write_err; } if( rc!=200 ){ - fossil_warning("\"location:\" missing from 302 redirect reply"); + fossil_warning("\"location:\" missing from %d redirect reply", rc); goto write_err; } /* ** Extract the reply payload that follows the header */ blob_zero(pReply); blob_resize(pReply, iLength); - iLength = transport_receive(blob_buffer(pReply), iLength); + iRecvLen = transport_receive(&g.url, blob_buffer(pReply), iLength); + if( iRecvLen != iLength ){ + fossil_warning("response truncated: got %d bytes of %d", iRecvLen, iLength); + goto write_err; + } blob_resize(pReply, iLength); if( isError ){ char *z; int i, j; z = blob_str(pReply); @@ -285,31 +389,34 @@ if( z[i]==0 ) break; } z[j] = z[i]; } z[j] = 0; - fossil_fatal("server sends error: %s", z); + fossil_warning("server sends error: %s", z); + goto write_err; } if( isCompressed ) blob_uncompress(pReply, pReply); /* ** Close the connection to the server if appropriate. ** ** FIXME: There is some bug in the lower layers that prevents the ** connection from remaining open. The easiest fix for now is to ** simply close and restart the connection for each round-trip. + ** + ** For SSH we will leave the connection open. */ - closeConnection = 1; /* FIX ME */ + if( ! g.url.isSsh ) closeConnection = 1; /* FIX ME */ if( closeConnection ){ - transport_close(); + transport_close(&g.url); }else{ - transport_rewind(); + transport_rewind(&g.url); } return 0; - /* + /* ** Jump to here if an error is seen. */ write_err: - transport_close(); - return 1; + transport_close(&g.url); + return 1; } Index: src/http_socket.c ================================================================== --- src/http_socket.c +++ src/http_socket.c @@ -20,17 +20,23 @@ ** ** This file implements a singleton. A single client socket may be active ** at a time. State information is stored in static variables. The identity ** of the server is held in global variables that are set by url_parse(). ** -** Low-level sockets are abstracted out into this module because they +** Low-level sockets are abstracted out into this module because they ** are handled different on Unix and windows. */ +#ifndef __EXTENSIONS__ +# define __EXTENSIONS__ 1 /* IPv6 won't compile on Solaris without this */ +#endif #include "config.h" #include "http_socket.h" #if defined(_WIN32) +# if !defined(_WIN32_WINNT) +# define _WIN32_WINNT 0x0501 +# endif # include <winsock2.h> # include <ws2tcpip.h> #else # include <netinet/in.h> # include <arpa/inet.h> @@ -63,11 +69,11 @@ } /* ** Set the socket error message. */ -void socket_set_errmsg(char *zFormat, ...){ +void socket_set_errmsg(const char *zFormat, ...){ va_list ap; socket_clear_errmsg(); va_start(ap, zFormat); socketErrMsg = vmprintf(zFormat, ap); va_end(ap); @@ -124,61 +130,65 @@ } } /* ** Open a socket connection. The identify of the server is determined -** by global variables that are set using url_parse(): +** by pUrlData ** -** g.urlName Name of the server. Ex: www.fossil-scm.org -** g.urlPort TCP/IP port to use. Ex: 80 +** pUrlDAta->name Name of the server. Ex: www.fossil-scm.org +** pUrlDAta->port TCP/IP port to use. Ex: 80 ** ** Return the number of errors. */ -int socket_open(void){ - static struct sockaddr_in addr; /* The server address */ - static int addrIsInit = 0; /* True once addr is initialized */ +int socket_open(UrlData *pUrlData){ + int rc = 0; + struct addrinfo *ai = 0; + struct addrinfo *p; + struct addrinfo hints; + char zPort[30]; + char zRemote[NI_MAXHOST]; socket_global_init(); - if( !addrIsInit ){ - addr.sin_family = AF_INET; - addr.sin_port = htons(g.urlPort); - *(int*)&addr.sin_addr = inet_addr(g.urlName); - if( -1 == *(int*)&addr.sin_addr ){ -#ifndef FOSSIL_STATIC_LINK - struct hostent *pHost; - pHost = gethostbyname(g.urlName); - if( pHost!=0 ){ - memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length); - }else -#endif - { - socket_set_errmsg("can't resolve host name: %s", g.urlName); - return 1; - } - } - addrIsInit = 1; - - /* Set the Global.zIpAddr variable to the server we are talking to. - ** This is used to populate the ipaddr column of the rcvfrom table, - ** if any files are received from the server. - */ - g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr)); - } - iSocket = socket(AF_INET,SOCK_STREAM,0); - if( iSocket<0 ){ - socket_set_errmsg("cannot create a socket"); - return 1; - } - if( connect(iSocket,(struct sockaddr*)&addr,sizeof(addr))<0 ){ - socket_set_errmsg("cannot connect to host %s:%d", g.urlName, g.urlPort); - socket_close(); - return 1; + memset(&hints, 0, sizeof(struct addrinfo)); + assert( iSocket<0 ); + hints.ai_family = g.fIPv4 ? AF_INET : AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + sqlite3_snprintf(sizeof(zPort),zPort,"%d", pUrlData->port); + rc = getaddrinfo(pUrlData->name, zPort, &hints, &ai); + if( rc ){ + socket_set_errmsg("getaddrinfo() fails: %s", gai_strerror(rc)); + goto end_socket_open; + } + for(p=ai; p; p=p->ai_next){ + iSocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if( iSocket<0 ) continue; + if( connect(iSocket,p->ai_addr,p->ai_addrlen)<0 ){ + socket_close(); + continue; + } + rc = getnameinfo(p->ai_addr, p->ai_addrlen, zRemote, sizeof(zRemote), + 0, 0, NI_NUMERICHOST); + if( rc ){ + socket_set_errmsg("getnameinfo() failed: %s", gai_strerror(rc)); + goto end_socket_open; + } + g.zIpAddr = mprintf("%s", zRemote); + break; + } + if( p==0 ){ + socket_set_errmsg("cannot connect to host %s:%d", pUrlData->name, + pUrlData->port); + rc = 1; } #if !defined(_WIN32) signal(SIGPIPE, SIG_IGN); #endif - return 0; +end_socket_open: + if( rc && iSocket>=0 ) socket_close(); + if( ai ) freeaddrinfo(ai); + return rc; } /* ** Send content out over the open socket connection. */ @@ -209,5 +219,30 @@ N -= (size_t)got; pContent = (void*)&((char*)pContent)[got]; } return total; } + +/* +** Attempt to resolve pUrlData->name to an IP address and setup g.zIpAddr +** so rcvfrom gets populated. For hostnames with more than one IP (or +** if overridden in ~/.ssh/config) the rcvfrom may not match the host +** to which we connect. +*/ +void socket_ssh_resolve_addr(UrlData *pUrlData){ + struct addrinfo *ai = 0; + struct addrinfo hints; + char zRemote[NI_MAXHOST]; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + if( getaddrinfo(pUrlData->name, NULL, &hints, &ai)==0 + && ai!=0 + && getnameinfo(ai->ai_addr, ai->ai_addrlen, zRemote, + sizeof(zRemote), 0, 0, NI_NUMERICHOST)==0 ){ + g.zIpAddr = mprintf("%s (%s)", zRemote, pUrlData->name); + } + if( ai ) freeaddrinfo(ai); + if( g.zIpAddr==0 ){ + g.zIpAddr = mprintf("%s", pUrlData->name); + } +} Index: src/http_ssl.c ================================================================== --- src/http_ssl.c +++ src/http_ssl.c @@ -41,11 +41,11 @@ ** There can only be a single OpenSSL IO connection open at a time. ** State information about that IO is stored in the following ** local variables: */ static int sslIsInit = 0; /* True after global initialization */ -static BIO *iBio; /* OpenSSL I/O abstraction */ +static BIO *iBio = 0; /* OpenSSL I/O abstraction */ static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */ static SSL_CTX *sslCtx; /* SSL context */ static SSL *ssl; @@ -58,11 +58,11 @@ } /* ** Set the SSL error message. */ -void ssl_set_errmsg(char *zFormat, ...){ +void ssl_set_errmsg(const char *zFormat, ...){ va_list ap; ssl_clear_errmsg(); va_start(ap, zFormat); sslErrMsg = vmprintf(zFormat, ap); va_end(ap); @@ -82,30 +82,30 @@ static int ssl_client_cert_callback(SSL *ssl, X509 **x509, EVP_PKEY **pkey){ fossil_warning("The remote server requested a client certificate for " "authentication. Specify the pathname to a file containing the PEM " "encoded certificate and private key with the --ssl-identity option " "or the ssl-identity setting."); - return 0; /* no cert available */ + return 0; /* no cert available */ } /* ** Call this routine once before any other use of the SSL interface. ** This routine does initial configuration of the SSL module. */ void ssl_global_init(void){ const char *zCaSetting = 0, *zCaFile = 0, *zCaDirectory = 0; const char *identityFile; - + if( sslIsInit==0 ){ SSL_library_init(); SSL_load_error_strings(); ERR_load_BIO_strings(); - OpenSSL_add_all_algorithms(); + OpenSSL_add_all_algorithms(); sslCtx = SSL_CTX_new(SSLv23_client_method()); - /* Disable SSLv2 */ - SSL_CTX_set_options(sslCtx, SSL_OP_NO_SSLv2); - + /* Disable SSLv2 and SSLv3 */ + SSL_CTX_set_options(sslCtx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3); + /* Set up acceptable CA root certificates */ zCaSetting = db_get("ssl-ca-location", 0); if( zCaSetting==0 || zCaSetting[0]=='\0' ){ /* CA location not specified, use platform's default certificate store */ X509_STORE_set_default_paths(SSL_CTX_get_cert_store(sslCtx)); @@ -129,11 +129,11 @@ if( SSL_CTX_load_verify_locations(sslCtx, zCaFile, zCaDirectory)==0 ){ fossil_fatal("Failed to use CA root certificates from " "ssl-ca-location '%s'", zCaSetting); } } - + /* Load client SSL identity, preferring the filename specified on the ** command line */ if( g.zSSLIdentity!=0 ){ identityFile = g.zSSLIdentity; }else{ @@ -164,30 +164,79 @@ sslIsInit = 0; } } /* -** Close the currently open SSL connection. If no connection is open, +** Close the currently open SSL connection. If no connection is open, ** this routine is a no-op. */ void ssl_close(void){ if( iBio!=NULL ){ (void)BIO_reset(iBio); BIO_free_all(iBio); + iBio = NULL; + } +} + +/* See RFC2817 for details */ +static int establish_proxy_tunnel(UrlData *pUrlData, BIO *bio){ + int rc, httpVerMin; + char *bbuf; + Blob snd, reply; + int done=0,end=0; + blob_zero(&snd); + blob_appendf(&snd, "CONNECT %s:%d HTTP/1.1\r\n", pUrlData->hostname, + pUrlData->proxyOrigPort); + blob_appendf(&snd, "Host: %s:%d\r\n", pUrlData->hostname, pUrlData->proxyOrigPort); + if( pUrlData->proxyAuth ){ + blob_appendf(&snd, "Proxy-Authorization: %s\r\n", pUrlData->proxyAuth); } + blob_append(&snd, "Proxy-Connection: keep-alive\r\n", -1); + blob_appendf(&snd, "User-Agent: %s\r\n", get_user_agent()); + blob_append(&snd, "\r\n", 2); + BIO_write(bio, blob_buffer(&snd), blob_size(&snd)); + blob_reset(&snd); + + /* Wait for end of reply */ + blob_zero(&reply); + do{ + int len; + char buf[256]; + len = BIO_read(bio, buf, sizeof(buf)); + blob_append(&reply, buf, len); + + bbuf = blob_buffer(&reply); + len = blob_size(&reply); + while(end < len) { + if(bbuf[end] == '\r') { + if(len - end < 4) { + /* need more data */ + break; + } + if(memcmp(&bbuf[end], "\r\n\r\n", 4) == 0) { + done = 1; + break; + } + } + end++; + } + }while(!done); + sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc); + blob_reset(&reply); + return rc; } /* ** Open an SSL connection. The identify of the server is determined -** by global variables that are set using url_parse(): +** as follows: ** -** g.urlName Name of the server. Ex: www.fossil-scm.org -** g.urlPort TCP/IP port to use. Ex: 80 +** g.url.name Name of the server. Ex: www.fossil-scm.org +** pUrlData->port TCP/IP port to use. Ex: 80 ** ** Return the number of errors. */ -int ssl_open(void){ +int ssl_open(UrlData *pUrlData){ X509 *cert; int hasSavedCertificate = 0; int trusted = 0; unsigned long e; @@ -194,47 +243,75 @@ ssl_global_init(); /* Get certificate for current server from global config and * (if we have it in config) add it to certificate store. */ - cert = ssl_get_certificate(&trusted); + cert = ssl_get_certificate(pUrlData, &trusted); if ( cert!=NULL ){ X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert); X509_free(cert); hasSavedCertificate = 1; } - iBio = BIO_new_ssl_connect(sslCtx); + if( pUrlData->useProxy ){ + int rc; + BIO *sBio; + char *connStr; + connStr = mprintf("%s:%d", g.url.name, pUrlData->port); + sBio = BIO_new_connect(connStr); + free(connStr); + if( BIO_do_connect(sBio)<=0 ){ + ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)", + pUrlData->name, pUrlData->port, ERR_reason_error_string(ERR_get_error())); + ssl_close(); + return 1; + } + rc = establish_proxy_tunnel(pUrlData, sBio); + if( rc<200||rc>299 ){ + ssl_set_errmsg("SSL: proxy connect failed with HTTP status code %d", rc); + return 1; + } + + pUrlData->path = pUrlData->proxyUrlPath; + + iBio = BIO_new_ssl(sslCtx, 1); + BIO_push(iBio, sBio); + }else{ + iBio = BIO_new_ssl_connect(sslCtx); + } + if( iBio==NULL ) { + ssl_set_errmsg("SSL: cannot open SSL (%s)", + ERR_reason_error_string(ERR_get_error())); + return 1; + } BIO_get_ssl(iBio, &ssl); #if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT) - if( !SSL_set_tlsext_host_name(ssl, g.urlName) ){ + if( !SSL_set_tlsext_host_name(ssl, (pUrlData->useProxy?pUrlData->hostname:pUrlData->name)) ){ fossil_warning("WARNING: failed to set server name indication (SNI), " "continuing without it.\n"); } #endif SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); - if( iBio==NULL ) { - ssl_set_errmsg("SSL: cannot open SSL (%s)", - ERR_reason_error_string(ERR_get_error())); - return 1; - } - - BIO_set_conn_hostname(iBio, g.urlName); - BIO_set_conn_int_port(iBio, &g.urlPort); - - if( BIO_do_connect(iBio)<=0 ){ - ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)", - g.urlName, g.urlPort, ERR_reason_error_string(ERR_get_error())); - ssl_close(); - return 1; - } - + + if( !pUrlData->useProxy ){ + BIO_set_conn_hostname(iBio, pUrlData->name); + BIO_set_conn_int_port(iBio, &pUrlData->port); + if( BIO_do_connect(iBio)<=0 ){ + ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)", + pUrlData->name, pUrlData->port, ERR_reason_error_string(ERR_get_error())); + ssl_close(); + return 1; + } + } + if( BIO_do_handshake(iBio)<=0 ) { - ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)", - g.urlName, g.urlPort, ERR_reason_error_string(ERR_get_error())); + ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)", + pUrlData->useProxy?pUrlData->hostname:pUrlData->name, + pUrlData->useProxy?pUrlData->proxyOrigPort:pUrlData->port, + ERR_reason_error_string(ERR_get_error())); ssl_close(); return 1; } /* Check if certificate is valid */ cert = SSL_get_peer_certificate(ssl); @@ -245,17 +322,17 @@ return 1; } if( trusted<=0 && (e = SSL_get_verify_result(ssl)) != X509_V_OK ){ char *desc, *prompt; - char *warning = ""; + const char *warning = ""; Blob ans; char cReply; BIO *mem; unsigned char md[32]; unsigned int mdLength = 31; - + mem = BIO_new(BIO_s_mem()); X509_NAME_print_ex(mem, X509_get_subject_name(cert), 2, XN_FLAG_MULTILINE); BIO_puts(mem, "\n\nIssued By:\n\n"); X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 2, XN_FLAG_MULTILINE); BIO_puts(mem, "\n\nSHA1 Fingerprint:\n\n "); @@ -265,11 +342,11 @@ BIO_printf(mem, " %02x", md[j]); } } BIO_write(mem, "", 1); /* nul-terminate mem buffer */ BIO_get_mem_data(mem, &desc); - + if( hasSavedCertificate ){ warning = "WARNING: Certificate doesn't match the " "saved certificate for this host!"; } prompt = mprintf("\nSSL verification failed: %s\n" @@ -281,11 +358,11 @@ " certificates list\n\n" "If you are not expecting this message, answer no and " "contact your server\nadministrator.\n\n" "Accept certificate for host %s (a=always/y/N)? ", X509_verify_cert_error_string(e), desc, warning, - g.urlName); + pUrlData->useProxy?pUrlData->hostname:pUrlData->name); BIO_free(mem); prompt_user(prompt, &ans); free(prompt); cReply = blob_str(&ans)[0]; @@ -302,11 +379,11 @@ &ans); cReply = blob_str(&ans)[0]; trusted = ( cReply=='a' || cReply=='A' ); blob_reset(&ans); } - ssl_save_certificate(cert, trusted); + ssl_save_certificate(pUrlData, cert, trusted); } } /* Set the Global.zIpAddr variable to the server we are talking to. ** This is used to populate the ipaddr column of the rcvfrom table, @@ -323,53 +400,55 @@ } /* ** Save certificate to global config. */ -void ssl_save_certificate(X509 *cert, int trusted){ +void ssl_save_certificate(UrlData *pUrlData, X509 *cert, int trusted){ BIO *mem; char *zCert, *zHost; mem = BIO_new(BIO_s_mem()); PEM_write_bio_X509(mem, cert); BIO_write(mem, "", 1); /* nul-terminate mem buffer */ BIO_get_mem_data(mem, &zCert); - zHost = mprintf("cert:%s", g.urlName); + zHost = mprintf("cert:%s", pUrlData->useProxy?pUrlData->hostname:pUrlData->name); db_set(zHost, zCert, 1); free(zHost); - zHost = mprintf("trusted:%s", g.urlName); + zHost = mprintf("trusted:%s", pUrlData->useProxy?pUrlData->hostname:pUrlData->name); db_set_int(zHost, trusted, 1); free(zHost); - BIO_free(mem); + BIO_free(mem); } /* -** Get certificate for g.urlName from global config. +** Get certificate for pUrlData->urlName from global config. ** Return NULL if no certificate found. */ -X509 *ssl_get_certificate(int *pTrusted){ +X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){ char *zHost, *zCert; BIO *mem; X509 *cert; - zHost = mprintf("cert:%s", g.urlName); + zHost = mprintf("cert:%s", + pUrlData->useProxy ? pUrlData->hostname : pUrlData->name); zCert = db_get(zHost, NULL); free(zHost); if ( zCert==NULL ) return NULL; if ( pTrusted!=0 ){ - zHost = mprintf("trusted:%s", g.urlName); + zHost = mprintf("trusted:%s", + pUrlData->useProxy ? pUrlData->hostname : pUrlData->name); *pTrusted = db_get_int(zHost, 0); free(zHost); } mem = BIO_new(BIO_s_mem()); BIO_puts(mem, zCert); cert = PEM_read_bio_X509(mem, NULL, 0, NULL); free(zCert); - BIO_free(mem); + BIO_free(mem); return cert; } /* ** Send content out over the SSL connection. Index: src/http_transport.c ================================================================== --- src/http_transport.c +++ src/http_transport.c @@ -52,13 +52,13 @@ /* ** Return the current transport error message. */ -const char *transport_errmsg(void){ +const char *transport_errmsg(UrlData *pUrlData){ #ifdef FOSSIL_ENABLE_SSL - if( g.urlIsHttps ){ + if( pUrlData->isHttps ){ return ssl_errmsg(); } #endif return socket_errmsg(); } @@ -74,242 +74,97 @@ transport.nSent = 0; transport.nRcvd = 0; } } -/* -** Read text from sshIn. Zero-terminate and remove trailing -** whitespace. -*/ -static void sshin_read(char *zBuf, int szBuf){ - int got; - zBuf[0] = 0; - got = read(sshIn, zBuf, szBuf-1); - while( got>=0 ){ - zBuf[got] = 0; - if( got==0 || !fossil_isspace(zBuf[got-1]) ) break; - got--; - } -} - /* ** Default SSH command */ -#ifdef __MINGW32__ -static char zDefaultSshCmd[] = "ssh -T"; +#ifdef _WIN32 +static char zDefaultSshCmd[] = "plink -ssh -T"; #else static char zDefaultSshCmd[] = "ssh -e none -T"; #endif /* -** Generate a random SSH link problem keyword -*/ -static int random_probe(char *zProbe, int nProbe){ - unsigned r[4]; - sqlite3_randomness(sizeof(r), r); - sqlite3_snprintf(nProbe, zProbe, "probe-%08x%08x%08x%08x", - r[0], r[1], r[2], r[3]); - return (int)strlen(zProbe); -} - -/* -** Bring up an SSH link. This involves sending some "echo" commands and -** get back appropriate responses. The point is to move past the MOTD and -** verify that the link is working. -*/ -static void transport_ssh_startup(void){ - char *zIn; /* An input line received back from remote */ - int nWait; /* Number of times waiting for the MOTD */ - char zProbe[40]; /* Text of the random probe */ - int nProbe; /* Size of probe message */ - int nIn; /* Size of input */ - static const int nBuf = 10000; /* Size of input buffer */ - - zIn = fossil_malloc(nBuf); - nProbe = random_probe(zProbe, sizeof(zProbe)); - fprintf(sshOut, "echo %s\n", zProbe); - fflush(sshOut); - if( g.fSshTrace ){ - printf("Sent: [echo %s]\n", zProbe); - fflush(stdout); - } - memset(zIn, '*', nProbe); - for(nWait=1; nWait<=10; nWait++){ - sshin_read(zIn+nProbe, nBuf-nProbe); - if( g.fSshTrace ){ - printf("Got back-----------------------------------------------\n" - "%s\n" - "-------------------------------------------------------\n", - zIn+nProbe); - } - if( strstr(zIn, zProbe) ) break; - sqlite3_sleep(100*nWait); - nIn = (int)strlen(zIn); - memcpy(zIn, zIn+(nIn-nProbe), nProbe); - if( g.fSshTrace ){ - printf("Fetching more text. Looking for [%s]...\n", zProbe); - fflush(stdout); - } - } - nProbe = random_probe(zProbe, sizeof(zProbe)); - fprintf(sshOut, "echo %s\n", zProbe); - fflush(sshOut); - if( g.fSshTrace ){ - printf("Sent: [echo %s]\n", zProbe); - fflush(stdout); - } - sshin_read(zIn, nBuf); - if( zIn[0]==0 ){ - sqlite3_sleep(250); - sshin_read(zIn, nBuf); - } - if( g.fSshTrace ){ - printf("Got back-----------------------------------------------\n" - "%s\n" - "-------------------------------------------------------\n", zIn); - } - if( memcmp(zIn, zProbe, nProbe)!=0 ){ - pclose2(sshIn, sshOut, sshPid); - fossil_fatal("ssh connection failed: [%s]", zIn); - } - fossil_free(zIn); -} - -/* -** Global initialization of the transport layer -*/ -void transport_global_startup(void){ - if( g.urlIsSsh ){ - /* Only SSH requires a global initialization. For SSH we need to create - ** and run an SSH command to talk to the remote machine. - */ - const char *zSsh; /* The base SSH command */ - Blob zCmd; /* The SSH command */ - char *zHost; /* The host name to contact */ - int n; /* Size of prefix string */ - - zSsh = db_get("ssh-command", zDefaultSshCmd); - blob_init(&zCmd, zSsh, -1); - if( g.urlPort!=g.urlDfltPort ){ -#ifdef __MINGW32__ - blob_appendf(&zCmd, " -P %d", g.urlPort); -#else - blob_appendf(&zCmd, " -p %d", g.urlPort); -#endif - } - fossil_force_newline(); - fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */ - if( g.urlUser && g.urlUser[0] ){ - zHost = mprintf("%s@%s", g.urlUser, g.urlName); -#ifdef __MINGW32__ - /* Only win32 (and specifically PLINK.EXE) support the -pw option */ - if( g.urlPasswd && g.urlPasswd[0] ){ - Blob pw; - blob_zero(&pw); - if( g.urlPasswd[0]=='*' ){ - char *zPrompt; - zPrompt = mprintf("Password for [%s]: ", zHost); - prompt_for_password(zPrompt, &pw, 0); - free(zPrompt); - }else{ - blob_init(&pw, g.urlPasswd, -1); - } - blob_append(&zCmd, " -pw ", -1); - shell_escape(&zCmd, blob_str(&pw)); - blob_reset(&pw); - fossil_print(" -pw ********"); /* Do not show the password text */ - } -#endif - }else{ - zHost = mprintf("%s", g.urlName); - } - n = blob_size(&zCmd); - blob_append(&zCmd, " ", 1); - shell_escape(&zCmd, zHost); - if( g.urlShell ){ - blob_appendf(&zCmd, " %s", g.urlShell); - }else{ -#if defined(FOSSIL_ENABLE_SSH_FAR_SIDE) - /* The following works. But only if the fossil on the remote side - ** is recent enough to support the test-ssh-far-side command. That - ** command was added on 2013-02-06. We will leave this turned off - ** until most fossil servers have upgraded to that version or a later - ** version. The sync will still work as long as the shell on the far - ** side is bash and not tcsh. And if the default far side shell is - ** tcsh, then the shell=/bin/bash query parameter can be used as a - ** work-around. Enable this code after about a year... - */ - blob_appendf(&zCmd, " exec %s test-ssh-far-side", g.urlFossil); -#endif - } - fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */ - free(zHost); - popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid); - if( sshPid==0 ){ - fossil_fatal("cannot start ssh tunnel using [%b]", &zCmd); - } - blob_reset(&zCmd); - transport_ssh_startup(); - } -} - -/* -** COMMAND: test-ssh-far-side -** -** Read lines of input text, one by one, and evaluate each line using -** system(). The ssh: sync protocol uses this on the far side of the -** SSH link. -*/ -void test_ssh_far_side_cmd(void){ - int i = 0; - int got; - char zLine[5000]; - while( i<sizeof(zLine) ){ - got = read(0, zLine+i, 1); - if( got==0 ) return; - if( zLine[i]=='\n' ){ - zLine[i] = 0; - system(zLine); - i = 0; - }else{ - i++; - } - } +** SSH initialization of the transport layer +*/ +int transport_ssh_open(UrlData *pUrlData){ + /* For SSH we need to create and run SSH fossil http + ** to talk to the remote machine. + */ + const char *zSsh; /* The base SSH command */ + Blob zCmd; /* The SSH command */ + char *zHost; /* The host name to contact */ + int n; /* Size of prefix string */ + + socket_ssh_resolve_addr(pUrlData); + zSsh = db_get("ssh-command", zDefaultSshCmd); + blob_init(&zCmd, zSsh, -1); + if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){ +#ifdef _WIN32 + blob_appendf(&zCmd, " -P %d", pUrlData->port); +#else + blob_appendf(&zCmd, " -p %d", pUrlData->port); +#endif + } + if( g.fSshTrace ){ + fossil_force_newline(); + fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */ + } + if( pUrlData->user && pUrlData->user[0] ){ + zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name); + }else{ + zHost = mprintf("%s", pUrlData->name); + } + n = blob_size(&zCmd); + blob_append(&zCmd, " ", 1); + shell_escape(&zCmd, zHost); + blob_append(&zCmd, " ", 1); + shell_escape(&zCmd, mprintf("%s", pUrlData->fossil)); + blob_append(&zCmd, " test-http", 10); + if( pUrlData->path && pUrlData->path[0] ){ + blob_append(&zCmd, " ", 1); + shell_escape(&zCmd, mprintf("%s", pUrlData->path)); + } + if( g.fSshTrace ){ + fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */ + } + free(zHost); + popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid); + if( sshPid==0 ){ + socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd); + } + blob_reset(&zCmd); + return sshPid==0; } /* ** Open a connection to the server. The server is defined by the following -** global variables: +** variables: ** -** g.urlName Name of the server. Ex: www.fossil-scm.org -** g.urlPort TCP/IP port. Ex: 80 -** g.urlIsHttps Use TLS for the connection +** pUrlData->name Name of the server. Ex: www.fossil-scm.org +** pUrlData->port TCP/IP port. Ex: 80 +** pUrlData->isHttps Use TLS for the connection ** ** Return the number of errors. */ -int transport_open(void){ +int transport_open(UrlData *pUrlData){ int rc = 0; if( transport.isOpen==0 ){ - if( g.urlIsSsh ){ - Blob cmd; - blob_zero(&cmd); - shell_escape(&cmd, g.urlFossil); - blob_append(&cmd, " test-http ", -1); - shell_escape(&cmd, g.urlPath); - fprintf(sshOut, "%s || true\n", blob_str(&cmd)); - fflush(sshOut); - if( g.fSshTrace ) printf("Sent: [%s]\n", blob_str(&cmd)); - blob_reset(&cmd); - }else if( g.urlIsHttps ){ + if( pUrlData->isSsh ){ + rc = transport_ssh_open(pUrlData); + if( rc==0 ) transport.isOpen = 1; + }else if( pUrlData->isHttps ){ #ifdef FOSSIL_ENABLE_SSL - rc = ssl_open(); + rc = ssl_open(pUrlData); if( rc==0 ) transport.isOpen = 1; #else socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support"); rc = 1; #endif - }else if( g.urlIsFile ){ + }else if( pUrlData->isFile ){ sqlite3_uint64 iRandId; sqlite3_randomness(sizeof(iRandId), &iRandId); transport.zOutFile = mprintf("%s-%llu-out.http", g.zRepositoryName, iRandId); transport.zInFile = mprintf("%s-%llu-in.http", @@ -318,21 +173,21 @@ if( transport.pFile==0 ){ fossil_fatal("cannot output temporary file: %s", transport.zOutFile); } transport.isOpen = 1; }else{ - rc = socket_open(); + rc = socket_open(pUrlData); if( rc==0 ) transport.isOpen = 1; } } return rc; } /* ** Close the current connection */ -void transport_close(void){ +void transport_close(UrlData *pUrlData){ if( transport.isOpen ){ free(transport.pBuf); transport.pBuf = 0; transport.nAlloc = 0; transport.nUsed = 0; @@ -339,17 +194,17 @@ transport.iCursor = 0; if( transport.pLog ){ fclose(transport.pLog); transport.pLog = 0; } - if( g.urlIsSsh ){ - /* No-op */ - }else if( g.urlIsHttps ){ + if( pUrlData->isSsh ){ + transport_ssh_close(); + }else if( pUrlData->isHttps ){ #ifdef FOSSIL_ENABLE_SSL ssl_close(); #endif - }else if( g.urlIsFile ){ + }else if( pUrlData->isFile ){ if( transport.pFile ){ fclose(transport.pFile); transport.pFile = 0; } file_delete(transport.zInFile); @@ -364,28 +219,28 @@ } /* ** Send content over the wire. */ -void transport_send(Blob *toSend){ +void transport_send(UrlData *pUrlData, Blob *toSend){ char *z = blob_buffer(toSend); int n = blob_size(toSend); transport.nSent += n; - if( g.urlIsSsh ){ + if( pUrlData->isSsh ){ fwrite(z, 1, n, sshOut); fflush(sshOut); - }else if( g.urlIsHttps ){ + }else if( pUrlData->isHttps ){ #ifdef FOSSIL_ENABLE_SSL int sent; while( n>0 ){ sent = ssl_send(0, z, n); /* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */ if( sent<=0 ) break; n -= sent; } #endif - }else if( g.urlIsFile ){ + }else if( pUrlData->isFile ){ fwrite(z, 1, n, transport.pFile); }else{ int sent; while( n>0 ){ sent = socket_send(0, z, n); @@ -398,18 +253,16 @@ /* ** This routine is called when the outbound message is complete and ** it is time to being receiving a reply. */ -void transport_flip(void){ - if( g.urlIsSsh ){ - fprintf(sshOut, "\n\n"); - }else if( g.urlIsFile ){ +void transport_flip(UrlData *pUrlData){ + if( pUrlData->isFile ){ char *zCmd; fclose(transport.pFile); - zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1 --localauth", - g.nameOfExe, g.urlName, transport.zOutFile, transport.zInFile + zCmd = mprintf("\"%s\" http \"%s\" \"%s\" 127.0.0.1 \"%s\" --localauth", + g.nameOfExe, transport.zOutFile, transport.zInFile, pUrlData->name ); fossil_system(zCmd); free(zCmd); transport.pFile = fossil_fopen(transport.zInFile, "rb"); } @@ -429,21 +282,21 @@ /* ** This routine is called when the inbound message has been received ** and it is time to start sending again. */ -void transport_rewind(void){ - if( g.urlIsFile ){ - transport_close(); +void transport_rewind(UrlData *pUrlData){ + if( pUrlData->isFile ){ + transport_close(pUrlData); } } /* ** Read N bytes of content directly from the wire and write into ** the buffer. */ -static int transport_fetch(char *zBuf, int N){ +static int transport_fetch(UrlData *pUrlData, char *zBuf, int N){ int got; if( sshIn ){ int x; int wanted = N; got = 0; @@ -451,17 +304,17 @@ x = read(sshIn, &zBuf[got], wanted); if( x<=0 ) break; got += x; wanted -= x; } - }else if( g.urlIsHttps ){ + }else if( pUrlData->isHttps ){ #ifdef FOSSIL_ENABLE_SSL got = ssl_receive(0, zBuf, N); #else got = 0; #endif - }else if( g.urlIsFile ){ + }else if( pUrlData->isFile ){ got = fread(zBuf, 1, N, transport.pFile); }else{ got = socket_receive(0, zBuf, N); } /* printf("received %d of %d bytes\n", got, N); fflush(stdout); */ @@ -474,11 +327,11 @@ /* ** Read N bytes of content from the wire and store in the supplied buffer. ** Return the number of bytes actually received. */ -int transport_receive(char *zBuf, int N){ +int transport_receive(UrlData *pUrlData, char *zBuf, int N){ int onHand; /* Bytes current held in the transport buffer */ int nByte = 0; /* Bytes of content received */ onHand = transport.nUsed - transport.iCursor; if( g.fSshTrace){ @@ -498,11 +351,11 @@ N -= toMove; zBuf += toMove; nByte += toMove; } if( N>0 ){ - int got = transport_fetch(zBuf, N); + int got = transport_fetch(pUrlData, zBuf, N); if( got>0 ){ nByte += got; transport.nRcvd += got; } } @@ -513,11 +366,11 @@ /* ** Load up to N new bytes of content into the transport.pBuf buffer. ** The buffer itself might be moved. And the transport.iCursor value ** might be reset to 0. */ -static void transport_load_buffer(int N){ +static void transport_load_buffer(UrlData *pUrlData, int N){ int i, j; if( transport.nAlloc==0 ){ transport.nAlloc = N; transport.pBuf = fossil_malloc( N ); transport.iCursor = 0; @@ -535,11 +388,11 @@ transport.nAlloc = transport.nUsed + N; pNew = fossil_realloc(transport.pBuf, transport.nAlloc); transport.pBuf = pNew; } if( N>0 ){ - i = transport_fetch(&transport.pBuf[transport.nUsed], N); + i = transport_fetch(pUrlData, &transport.pBuf[transport.nUsed], N); if( i>0 ){ transport.nRcvd += i; transport.nUsed += i; } } @@ -551,18 +404,18 @@ ** from the received line and zero-terminate the result. Return a pointer ** to the line. ** ** Each call to this routine potentially overwrites the returned buffer. */ -char *transport_receive_line(void){ +char *transport_receive_line(UrlData *pUrlData){ int i; int iStart; i = iStart = transport.iCursor; while(1){ if( i >= transport.nUsed ){ - transport_load_buffer(g.urlIsSsh ? 2 : 1000); + transport_load_buffer(pUrlData, pUrlData->isSsh ? 2 : 1000); i -= iStart; iStart = 0; if( i >= transport.nUsed ){ transport.pBuf[i] = 0; transport.iCursor = i; @@ -581,20 +434,32 @@ } if( g.fSshTrace ) printf("Got line: [%s]\n", &transport.pBuf[iStart]); return &transport.pBuf[iStart]; } -void transport_global_shutdown(void){ - if( g.urlIsSsh && sshPid ){ - /*printf("Closing SSH tunnel: ");*/ - fflush(stdout); - pclose2(sshIn, sshOut, sshPid); - sshPid = 0; - } - if( g.urlIsHttps ){ +/* +** Global transport shutdown +*/ +void transport_global_shutdown(UrlData *pUrlData){ + if( pUrlData->isSsh ){ + transport_ssh_close(); + } + if( pUrlData->isHttps ){ #ifdef FOSSIL_ENABLE_SSL ssl_global_shutdown(); #endif }else{ socket_global_shutdown(); } } + +/* +** Close SSH transport. +*/ +void transport_ssh_close(void){ + if( sshPid ){ + /*printf("Closing SSH tunnel: ");*/ + fflush(stdout); + pclose2(sshIn, sshOut, sshPid); + sshPid = 0; + } +} Index: src/import.c ================================================================== --- src/import.c +++ src/import.c @@ -12,12 +12,12 @@ ** Author contact information: ** drh@sqlite.org ** ******************************************************************************* ** -** This file contains code used to import the content of a Git -** repository in the git-fast-import format as a new Fossil +** This file contains code used to import the content of a Git/SVN +** repository in the git-fast-import/svn-dump formats as a new Fossil ** repository. */ #include "config.h" #include "import.h" #include <assert.h> @@ -214,12 +214,12 @@ /* ** Compare two strings for sorting. */ static int string_cmp(const void *pLeft, const void *pRight){ - const char *zLeft = *(char const **)pLeft; - const char *zRight = *(char const **)pRight; + const char *zLeft = *(const char **)pLeft; + const char *zRight = *(const char **)pRight; return fossil_strcmp(zLeft, zRight); } /* Forward reference */ static void import_prior_files(void); @@ -443,11 +443,11 @@ static ImportFile *import_find_file(const char *zName, int *pI, int mx){ int i = *pI; int nName = strlen(zName); while( i<mx ){ const char *z = gg.aFile[i].zName; - if( memcmp(zName, z, nName)==0 && (z[nName]==0 || z[nName]=='/') ){ + if( strncmp(zName, z, nName)==0 && (z[nName]==0 || z[nName]=='/') ){ *pI = i+1; return &gg.aFile[i]; } i++; } @@ -488,15 +488,15 @@ char zLine[1000]; gg.xFinish = finish_noop; while( fgets(zLine, sizeof(zLine), pIn) ){ if( zLine[0]=='\n' || zLine[0]=='#' ) continue; - if( memcmp(zLine, "blob", 4)==0 ){ + if( strncmp(zLine, "blob", 4)==0 ){ gg.xFinish(); gg.xFinish = finish_blob; }else - if( memcmp(zLine, "commit ", 7)==0 ){ + if( strncmp(zLine, "commit ", 7)==0 ){ gg.xFinish(); gg.xFinish = finish_commit; trim_newline(&zLine[7]); z = &zLine[7]; @@ -516,42 +516,42 @@ ** last commit that holds that tag. ** ** None of the above is explained in the git-fast-export ** documentation. We had to figure it out via trial and error. */ - for(i=strlen(z)-1; i>=0 && z[i]!='/'; i--){} - gg.tagCommit = memcmp(&z[i-4], "tags", 4)==0; /* True for pattern B */ + for(i=5; i<strlen(z) && z[i]!='/'; i++){} + gg.tagCommit = strncmp(&z[5], "tags", 4)==0; /* True for pattern B */ if( z[i+1]!=0 ) z += i+1; if( fossil_strcmp(z, "master")==0 ) z = "trunk"; gg.zBranch = fossil_strdup(z); gg.fromLoaded = 0; }else - if( memcmp(zLine, "tag ", 4)==0 ){ + if( strncmp(zLine, "tag ", 4)==0 ){ gg.xFinish(); gg.xFinish = finish_tag; trim_newline(&zLine[4]); gg.zTag = fossil_strdup(&zLine[4]); }else - if( memcmp(zLine, "reset ", 4)==0 ){ + if( strncmp(zLine, "reset ", 4)==0 ){ + gg.xFinish(); + }else + if( strncmp(zLine, "checkpoint", 10)==0 ){ gg.xFinish(); }else - if( memcmp(zLine, "checkpoint", 10)==0 ){ + if( strncmp(zLine, "feature", 7)==0 ){ gg.xFinish(); }else - if( memcmp(zLine, "feature", 7)==0 ){ + if( strncmp(zLine, "option", 6)==0 ){ gg.xFinish(); }else - if( memcmp(zLine, "option", 6)==0 ){ - gg.xFinish(); - }else - if( memcmp(zLine, "progress ", 9)==0 ){ + if( strncmp(zLine, "progress ", 9)==0 ){ gg.xFinish(); trim_newline(&zLine[9]); fossil_print("%s\n", &zLine[9]); fflush(stdout); }else - if( memcmp(zLine, "data ", 5)==0 ){ + if( strncmp(zLine, "data ", 5)==0 ){ fossil_free(gg.aData); gg.aData = 0; gg.nData = atoi(&zLine[5]); if( gg.nData ){ int got; gg.aData = fossil_malloc( gg.nData+1 ); @@ -565,19 +565,19 @@ gg.aData = 0; gg.nData = 0; } } }else - if( memcmp(zLine, "author ", 7)==0 ){ + if( strncmp(zLine, "author ", 7)==0 ){ /* No-op */ }else - if( memcmp(zLine, "mark ", 5)==0 ){ + if( strncmp(zLine, "mark ", 5)==0 ){ trim_newline(&zLine[5]); fossil_free(gg.zMark); gg.zMark = fossil_strdup(&zLine[5]); }else - if( memcmp(zLine, "tagger ", 7)==0 || memcmp(zLine, "committer ",10)==0 ){ + if( strncmp(zLine, "tagger ", 7)==0 || strncmp(zLine, "committer ",10)==0 ){ sqlite3_int64 secSince1970; for(i=0; zLine[i] && zLine[i]!='<'; i++){} if( zLine[i]==0 ) goto malformed_line; z = &zLine[i+1]; for(i=i+1; zLine[i] && zLine[i]!='>'; i++){} @@ -591,27 +591,27 @@ } fossil_free(gg.zDate); gg.zDate = db_text(0, "SELECT datetime(%lld, 'unixepoch')", secSince1970); gg.zDate[10] = 'T'; }else - if( memcmp(zLine, "from ", 5)==0 ){ + if( strncmp(zLine, "from ", 5)==0 ){ trim_newline(&zLine[5]); fossil_free(gg.zFromMark); gg.zFromMark = fossil_strdup(&zLine[5]); fossil_free(gg.zFrom); gg.zFrom = resolve_committish(&zLine[5]); }else - if( memcmp(zLine, "merge ", 6)==0 ){ + if( strncmp(zLine, "merge ", 6)==0 ){ trim_newline(&zLine[6]); if( gg.nMerge>=gg.nMergeAlloc ){ gg.nMergeAlloc = gg.nMergeAlloc*2 + 10; gg.azMerge = fossil_realloc(gg.azMerge, gg.nMergeAlloc*sizeof(char*)); } gg.azMerge[gg.nMerge] = resolve_committish(&zLine[6]); if( gg.azMerge[gg.nMerge] ) gg.nMerge++; }else - if( memcmp(zLine, "M ", 2)==0 ){ + if( strncmp(zLine, "M ", 2)==0 ){ import_prior_files(); z = &zLine[2]; zPerm = next_token(&z); zUuid = next_token(&z); zName = rest_of_line(&z); @@ -626,11 +626,11 @@ pFile->isLink = (fossil_strcmp(zPerm, "120000")==0); fossil_free(pFile->zUuid); pFile->zUuid = resolve_committish(zUuid); pFile->isFrom = 0; }else - if( memcmp(zLine, "D ", 2)==0 ){ + if( strncmp(zLine, "D ", 2)==0 ){ import_prior_files(); z = &zLine[2]; zName = rest_of_line(&z); dequote_git_filename(zName); i = 0; @@ -641,11 +641,11 @@ fossil_free(pFile->zUuid); *pFile = gg.aFile[--gg.nFile]; i--; } }else - if( memcmp(zLine, "C ", 2)==0 ){ + if( strncmp(zLine, "C ", 2)==0 ){ int nFrom; import_prior_files(); z = &zLine[2]; zFrom = next_token(&z); zTo = rest_of_line(&z); @@ -665,11 +665,11 @@ pNew->isLink = pFile->isLink; pNew->zUuid = fossil_strdup(pFile->zUuid); pNew->isFrom = 0; } }else - if( memcmp(zLine, "R ", 2)==0 ){ + if( strncmp(zLine, "R ", 2)==0 ){ int nFrom; import_prior_files(); z = &zLine[2]; zFrom = next_token(&z); zTo = rest_of_line(&z); @@ -693,14 +693,14 @@ *pFile = *pNew; memset(pNew, 0, sizeof(*pNew)); } fossil_fatal("cannot handle R records, use --full-tree"); }else - if( memcmp(zLine, "deleteall", 9)==0 ){ + if( strncmp(zLine, "deleteall", 9)==0 ){ gg.fromLoaded = 1; }else - if( memcmp(zLine, "N ", 2)==0 ){ + if( strncmp(zLine, "N ", 2)==0 ){ /* No-op */ }else { goto malformed_line; @@ -716,43 +716,822 @@ malformed_line: trim_newline(zLine); fossil_fatal("bad fast-import line: [%s]", zLine); return; } + +static struct{ + int rev; /* SVN revision number */ + char *zDate; /* Date/time stamp */ + char *zUser; /* User name */ + char *zComment; /* Comment of a commit */ + const char *zTrunk; /* Name of trunk folder in repo root */ + int lenTrunk; /* String length of zTrunk */ + const char *zBranches; /* Name of branches folder in repo root */ + int lenBranches; /* String length of zBranches */ + const char *zTags; /* Name of tags folder in repo root */ + int lenTags; /* String length of zTags */ + Bag newBranches; /* Branches that were created in this revision */ + int incrFlag; /* Add svn-rev-nn tags on every checkin */ +} gsvn; +typedef struct { + char *zKey; + char *zVal; +} KeyVal; +typedef struct { + KeyVal *aHeaders; + int nHeaders; + char *pRawProps; + KeyVal *aProps; + int nProps; + Blob content; + int contentFlag; +} SvnRecord; + +#define svn_find_header(rec, zHeader) \ + svn_find_keyval((rec).aHeaders, (rec).nHeaders, (zHeader)) +#define svn_find_prop(rec, zProp) \ + svn_find_keyval((rec).aProps, (rec).nProps, (zProp)) +static char *svn_find_keyval( + KeyVal *aKeyVal, + int nKeyVal, + const char *zKey +){ + int i; + for(i=0; i<nKeyVal; i++){ + if( fossil_strcmp(aKeyVal[i].zKey, zKey)==0 ){ + return aKeyVal[i].zVal; + } + } + return 0; +} + +static void svn_free_rec(SvnRecord *rec){ + int i; + for(i=0; i<rec->nHeaders; i++){ + fossil_free(rec->aHeaders[i].zKey); + } + fossil_free(rec->aHeaders); + fossil_free(rec->aProps); + fossil_free(rec->pRawProps); + blob_reset(&rec->content); +} + +static int svn_read_headers(FILE *pIn, SvnRecord *rec){ + char zLine[1000]; + + rec->aHeaders = 0; + rec->nHeaders = 0; + while( fgets(zLine, sizeof(zLine), pIn) ){ + if( zLine[0]!='\n' ) break; + } + if( feof(pIn) ) return 0; + do{ + char *sep; + if( zLine[0]=='\n' ) break; + rec->nHeaders += 1; + rec->aHeaders = fossil_realloc(rec->aHeaders, + sizeof(rec->aHeaders[0])*rec->nHeaders); + rec->aHeaders[rec->nHeaders-1].zKey = mprintf("%s", zLine); + sep = strchr(rec->aHeaders[rec->nHeaders-1].zKey, ':'); + if( !sep ){ + trim_newline(zLine); + fossil_fatal("bad header line: [%s]", zLine); + } + *sep = 0; + rec->aHeaders[rec->nHeaders-1].zVal = sep+1; + sep = strchr(rec->aHeaders[rec->nHeaders-1].zVal, '\n'); + *sep = 0; + while(rec->aHeaders[rec->nHeaders-1].zVal + && fossil_isspace(*(rec->aHeaders[rec->nHeaders-1].zVal)) ) + { + rec->aHeaders[rec->nHeaders-1].zVal++; + } + }while( fgets(zLine, sizeof(zLine), pIn) ); + if( zLine[0]!='\n' ){ + trim_newline(zLine); + fossil_fatal("svn-dump data ended unexpectedly"); + } + return 1; +} + +static void svn_read_props(FILE *pIn, SvnRecord *rec){ + int nRawProps = 0; + char *pRawProps; + const char *zLen; + + rec->pRawProps = 0; + rec->aProps = 0; + rec->nProps = 0; + zLen = svn_find_header(*rec, "Prop-content-length"); + if( zLen ){ + nRawProps = atoi(zLen); + } + if( nRawProps ){ + int got; + char *zLine; + rec->pRawProps = pRawProps = fossil_malloc( nRawProps ); + got = fread(rec->pRawProps, 1, nRawProps, pIn); + if( got!=nRawProps ){ + fossil_fatal("short read: got %d of %d bytes", got, nRawProps); + } + if( memcmp(&pRawProps[got-10], "PROPS-END\n", 10)!=0 ){ + fossil_fatal("svn-dump data ended unexpectedly"); + } + zLine = pRawProps; + while( zLine<(pRawProps+nRawProps-10) ){ + char *eol; + int propLen; + if( zLine[0]=='D' ){ + propLen = atoi(&zLine[2]); + eol = strchr(zLine, '\n'); + zLine = eol+1+propLen+1; + }else{ + if( zLine[0]!='K' ){ + fossil_fatal("svn-dump data format broken"); + } + propLen = atoi(&zLine[2]); + eol = strchr(zLine, '\n'); + zLine = eol+1; + eol = zLine+propLen; + if( *eol!='\n' ){ + fossil_fatal("svn-dump data format broken"); + } + *eol = 0; + rec->nProps += 1; + rec->aProps = fossil_realloc(rec->aProps, + sizeof(rec->aProps[0])*rec->nProps); + rec->aProps[rec->nProps-1].zKey = zLine; + zLine = eol+1; + if( zLine[0]!='V' ){ + fossil_fatal("svn-dump data format broken"); + } + propLen = atoi(&zLine[2]); + eol = strchr(zLine, '\n'); + zLine = eol+1; + eol = zLine+propLen; + if( *eol!='\n' ){ + fossil_fatal("svn-dump data format broken"); + } + *eol = 0; + rec->aProps[rec->nProps-1].zVal = zLine; + zLine = eol+1; + } + } + } +} + +static int svn_read_rec(FILE *pIn, SvnRecord *rec){ + const char *zLen; + int nLen = 0; + if( svn_read_headers(pIn, rec)==0 ) return 0; + svn_read_props(pIn, rec); + blob_zero(&rec->content); + zLen = svn_find_header(*rec, "Text-content-length"); + if( zLen ){ + rec->contentFlag = 1; + nLen = atoi(zLen); + blob_read_from_channel(&rec->content, pIn, nLen); + if( blob_size(&rec->content)!=nLen ){ + fossil_fatal("short read: got %d of %d bytes", + blob_size(&rec->content), nLen + ); + } + }else{ + rec->contentFlag = 0; + } + return 1; +} + +/* +** Returns the UUID for the RID, or NULL if not found. +** The returned string is allocated via db_text() and must be +** free()d by the caller. +*/ +char * rid_to_uuid(int rid) +{ + return db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); +} + +#define SVN_UNKNOWN 0 +#define SVN_TRUNK 1 +#define SVN_BRANCH 2 +#define SVN_TAG 3 + +#define MAX_INT_32 (0x7FFFFFFFL) + +static void svn_finish_revision(){ + Blob manifest; + static Stmt getChanges; + static Stmt getFiles; + static Stmt setRid; + Blob mcksum; + + blob_zero(&manifest); + db_static_prepare(&getChanges, "SELECT tid, tname, ttype, tparent" + " FROM xrevisions, xbranches ON (tbranch=tid)" + " WHERE trid ISNULL"); + db_static_prepare(&getFiles, "SELECT tpath, tuuid, tperm FROM xfiles" + " WHERE tbranch=:branch ORDER BY tpath"); + db_prepare(&setRid, "UPDATE xrevisions SET trid=:rid" + " WHERE trev=%d AND tbranch=:branch", gsvn.rev); + while( db_step(&getChanges)==SQLITE_ROW ){ + int branchId = db_column_int(&getChanges, 0); + const char *zBranch = db_column_text(&getChanges, 1); + int branchType = db_column_int(&getChanges, 2); + int parentRid = db_column_int(&getChanges, 3); + int mergeRid = parentRid; + Manifest *pParentManifest = 0; + ManifestFile *pParentFile = 0; + int sameAsParent = 1; + int parentBranch = 0; + if( !bag_find(&gsvn.newBranches, branchId) ){ + parentRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions" + " WHERE trev<%d AND tbranch=%d", + gsvn.rev, branchId); + } + if( parentRid>0 ){ + pParentManifest = manifest_get(parentRid, CFTYPE_MANIFEST, 0); + pParentFile = manifest_file_next(pParentManifest, 0); + parentBranch = db_int(0, "SELECT tbranch FROM xrevisions WHERE trid=%d", + parentRid); + if( parentBranch!=branchId && branchType!=SVN_TAG ){ + sameAsParent = 0; + } + } + if( mergeRid<MAX_INT_32 ){ + if( gsvn.zComment ){ + blob_appendf(&manifest, "C %F\n", gsvn.zComment); + }else{ + blob_append(&manifest, "C (no\\scomment)\n", 16); + } + blob_appendf(&manifest, "D %s\n", gsvn.zDate); + db_bind_int(&getFiles, ":branch", branchId); + while( db_step(&getFiles)==SQLITE_ROW ){ + const char *zFile = db_column_text(&getFiles, 0); + const char *zUuid = db_column_text(&getFiles, 1); + const char *zPerm = db_column_text(&getFiles, 2); + if( zPerm ){ + blob_appendf(&manifest, "F %F %s %s\n", zFile, zUuid, zPerm); + }else{ + blob_appendf(&manifest, "F %F %s\n", zFile, zUuid); + } + if( sameAsParent ){ + if( !pParentFile + || fossil_strcmp(pParentFile->zName,zFile)!=0 + || fossil_strcmp(pParentFile->zUuid,zUuid)!=0 + || fossil_strcmp(pParentFile->zPerm,zPerm)!=0 + ){ + sameAsParent = 0; + }else{ + pParentFile = manifest_file_next(pParentManifest, 0); + } + } + } + if( pParentFile ){ + sameAsParent = 0; + } + db_reset(&getFiles); + if( !sameAsParent ){ + if( parentRid>0 ){ + char *zParentUuid = rid_to_uuid(parentRid); + if( parentRid==mergeRid || mergeRid==0){ + char *zParentBranch = + db_text(0, "SELECT tname FROM xbranches WHERE tid=%d", + parentBranch + ); + blob_appendf(&manifest, "P %s\n", zParentUuid); + blob_appendf(&manifest, "T *branch * %F\n", zBranch); + blob_appendf(&manifest, "T *sym-%F *\n", zBranch); + if( gsvn.incrFlag ){ + blob_appendf(&manifest, "T +sym-svn-rev-%d *\n", gsvn.rev); + } + blob_appendf(&manifest, "T -sym-%F *\n", zParentBranch); + fossil_free(zParentBranch); + }else{ + char *zMergeUuid = rid_to_uuid(mergeRid); + blob_appendf(&manifest, "P %s %s\n", zParentUuid, zMergeUuid); + if( gsvn.incrFlag ){ + blob_appendf(&manifest, "T +sym-svn-rev-%d *\n", gsvn.rev); + } + fossil_free(zMergeUuid); + } + fossil_free(zParentUuid); + }else{ + blob_appendf(&manifest, "T *branch * %F\n", zBranch); + blob_appendf(&manifest, "T *sym-%F *\n", zBranch); + if( gsvn.incrFlag ){ + blob_appendf(&manifest, "T +sym-svn-rev-%d *\n", gsvn.rev); + } + } + }else if( branchType==SVN_TAG ){ + char *zParentUuid = rid_to_uuid(parentRid); + blob_reset(&manifest); + blob_appendf(&manifest, "D %s\n", gsvn.zDate); + blob_appendf(&manifest, "T +sym-%F %s\n", zBranch, zParentUuid); + fossil_free(zParentUuid); + } + }else{ + char *zParentUuid = rid_to_uuid(parentRid); + blob_appendf(&manifest, "D %s\n", gsvn.zDate); + if( branchType!=SVN_TAG ){ + blob_appendf(&manifest, "T +closed %s\n", zParentUuid); + }else{ + blob_appendf(&manifest, "T -sym-%F %s\n", zBranch, zParentUuid); + } + fossil_free(zParentUuid); + } + if( gsvn.zUser ){ + blob_appendf(&manifest, "U %F\n", gsvn.zUser); + }else{ + const char *zUserOvrd = find_option("user-override",0,1); + blob_appendf(&manifest, "U %F\n", zUserOvrd ? zUserOvrd : login_name()); + } + md5sum_blob(&manifest, &mcksum); + blob_appendf(&manifest, "Z %b\n", &mcksum); + blob_reset(&mcksum); + if( !sameAsParent ){ + int rid = content_put(&manifest); + db_bind_int(&setRid, ":branch", branchId); + db_bind_int(&setRid, ":rid", rid); + db_step(&setRid); + db_reset(&setRid); + }else if( branchType==SVN_TAG ){ + content_put(&manifest); + db_bind_int(&setRid, ":branch", branchId); + db_bind_int(&setRid, ":rid", parentRid); + db_step(&setRid); + db_reset(&setRid); + }else if( mergeRid==MAX_INT_32 ){ + content_put(&manifest); + db_multi_exec("DELETE FROM xrevisions WHERE tbranch=%d AND trev=%d", + branchId, gsvn.rev); + }else{ + db_multi_exec("DELETE FROM xrevisions WHERE tbranch=%d AND trev=%d", + branchId, gsvn.rev); + } + blob_reset(&manifest); + manifest_destroy(pParentManifest); + } + db_reset(&getChanges); + db_finalize(&setRid); +} + +static u64 svn_get_varint(const char **pz){ + unsigned int v = 0; + do{ + v = (v<<7) | ((*pz)[0]&0x7f); + }while( (*pz)++[0]&0x80 ); + return v; +} + +static void svn_apply_svndiff(Blob *pDiff, Blob *pSrc, Blob *pOut){ + const char *zDiff = blob_buffer(pDiff); + char *zOut; + if( blob_size(pDiff)<4 || memcmp(zDiff, "SVN", 4)!=0 ){ + fossil_fatal("Invalid svndiff0 format"); + } + zDiff += 4; + blob_zero(pOut); + while( zDiff<(blob_buffer(pDiff)+blob_size(pDiff)) ){ + u64 lenOut, lenInst, lenData, lenOld; + const char *zInst; + const char *zData; + + u64 offSrc = svn_get_varint(&zDiff); + /*lenSrc =*/ svn_get_varint(&zDiff); + lenOut = svn_get_varint(&zDiff); + lenInst = svn_get_varint(&zDiff); + lenData = svn_get_varint(&zDiff); + zInst = zDiff; + zData = zInst+lenInst; + lenOld = blob_size(pOut); + blob_resize(pOut, lenOut+lenOld); + zOut = blob_buffer(pOut)+lenOld; + while( zDiff<zInst+lenInst ){ + u64 lenCpy = (*zDiff)&0x3f; + const char *zCpy; + switch( (*zDiff)&0xC0 ){ + case 0x00: zCpy = blob_buffer(pSrc)+offSrc; break; + case 0x40: zCpy = blob_buffer(pOut); break; + case 0x80: zCpy = zData; break; + default: fossil_fatal("Invalid svndiff0 instruction"); + } + zDiff++; + if( lenCpy==0 ){ + lenCpy = svn_get_varint(&zDiff); + } + if( zCpy!=zData ){ + zCpy += svn_get_varint(&zDiff); + }else{ + zData += lenCpy; + } + while( lenCpy-- > 0 ){ + *zOut++ = *zCpy++; + } + } + zDiff += lenData; + } +} + +/* +** Extract the branch or tag that the given path is on. Return the branch ID. + */ +static int svn_parse_path(char *zPath, char **zFile, int *type){ + char *zBranch = 0; + int branchId = 0; + *type = SVN_UNKNOWN; + *zFile = 0; + if( gsvn.lenTrunk==0 ){ + zBranch = "trunk"; + *zFile = zPath; + *type = SVN_TRUNK; + }else + if( strncmp(zPath, gsvn.zTrunk, gsvn.lenTrunk-1)==0 ){ + if( zPath[gsvn.lenTrunk-1]=='/' || zPath[gsvn.lenTrunk-1]==0 ){ + zBranch = "trunk"; + *zFile = zPath+gsvn.lenTrunk; + *type = SVN_TRUNK; + }else{ + zBranch = 0; + *type = SVN_UNKNOWN; + } + }else{ + if( strncmp(zPath, gsvn.zBranches, gsvn.lenBranches)==0 ){ + *zFile = zBranch = zPath+gsvn.lenBranches; + *type = SVN_BRANCH; + }else + if( strncmp(zPath, gsvn.zTags, gsvn.lenTags)==0 ){ + *zFile = zBranch = zPath+gsvn.lenTags; + *type = SVN_TAG; + }else{ /* Not a branch, tag or trunk */ + return 0; + } + while( **zFile && **zFile!='/' ){ (*zFile)++; } + if( **zFile ){ + **zFile = '\0'; + (*zFile)++; + } + } + if( *type!=SVN_UNKNOWN ){ + branchId = db_int(0, + "SELECT tid FROM xbranches WHERE tname=%Q AND ttype=%d", + zBranch, *type); + if( branchId==0 ){ + db_multi_exec("INSERT INTO xbranches (tname, ttype) VALUES(%Q, %d)", + zBranch, *type); + branchId = db_last_insert_rowid(); + } + } + return branchId; +} + +/* +** Read the svn-dump format from pIn and insert the corresponding +** content into the database. +*/ +static void svn_dump_import(FILE *pIn){ + SvnRecord rec; + int ver; + char *zTemp; + const char *zUuid; + Stmt addFile; + Stmt delPath; + Stmt addRev; + Stmt cpyPath; + Stmt cpyRoot; + Stmt revSrc; + + /* version */ + if( svn_read_rec(pIn, &rec) + && (zTemp = svn_find_header(rec, "SVN-fs-dump-format-version")) ){ + ver = atoi(zTemp); + if( ver!=2 && ver!=3 ){ + fossil_fatal("Unknown svn-dump format version: %d", ver); + } + }else{ + fossil_fatal("Input is not an svn-dump!"); + } + svn_free_rec(&rec); + /* UUID */ + if( !svn_read_rec(pIn, &rec) || !(zUuid = svn_find_header(rec, "UUID")) ){ + /* Removed the following line since UUID is not actually used + fossil_fatal("Missing UUID!"); */ + } + svn_free_rec(&rec); + + /* content */ + db_prepare(&addFile, + "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)" + " VALUES(:path, :branch, (SELECT uuid FROM blob WHERE rid=:rid), :perm)" + ); + db_prepare(&delPath, + "DELETE FROM xfiles" + " WHERE (tpath=:path OR (tpath>:path||'/' AND tpath<:path||'0'))" + " AND tbranch=:branch" + ); + db_prepare(&addRev, + "INSERT OR IGNORE INTO xrevisions (trev, tbranch) VALUES(:rev, :branch)" + ); + db_prepare(&cpyPath, + "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)" + " SELECT :path||:sep||substr(filename, length(:srcpath)+2), :branch, uuid, perm" + " FROM xfoci" + " WHERE checkinID=:rid" + " AND filename>:srcpath||'/'" + " AND filename<:srcpath||'0'" + ); + db_prepare(&cpyRoot, + "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)" + " SELECT :path||:sep||filename, :branch, uuid, perm" + " FROM xfoci" + " WHERE checkinID=:rid" + ); + db_prepare(&revSrc, + "UPDATE xrevisions SET tparent=:parent" + " WHERE trev=:rev AND tbranch=:branch AND tparent<:parent" + ); + gsvn.rev = -1; + bag_init(&gsvn.newBranches); + while( svn_read_rec(pIn, &rec) ){ + if( (zTemp = svn_find_header(rec, "Revision-number")) ){ /* revision node */ + /* finish previous revision */ + char *zDate = NULL; + if( gsvn.rev>=0 ){ + svn_finish_revision(); + fossil_free(gsvn.zUser); + fossil_free(gsvn.zComment); + fossil_free(gsvn.zDate); + bag_clear(&gsvn.newBranches); + } + /* start new revision */ + gsvn.rev = atoi(zTemp); + gsvn.zUser = mprintf("%s", svn_find_prop(rec, "svn:author")); + gsvn.zComment = mprintf("%s", svn_find_prop(rec, "svn:log")); + zDate = svn_find_prop(rec, "svn:date"); + if( zDate ){ + gsvn.zDate = date_in_standard_format(zDate); + }else{ + gsvn.zDate = date_in_standard_format("now"); + } + db_bind_int(&addRev, ":rev", gsvn.rev); + fossil_print("\rImporting SVN revision: %d", gsvn.rev); + }else + if( (zTemp = svn_find_header(rec, "Node-path")) ){ /* file/dir node */ + char *zFile; + int branchType; + int branchId = svn_parse_path(zTemp, &zFile, &branchType); + char *zAction = svn_find_header(rec, "Node-action"); + char *zKind = svn_find_header(rec, "Node-kind"); + char *zPerm = svn_find_prop(rec, "svn:executable") ? "x" : 0; + int deltaFlag = 0; + int srcRev = 0; + if( branchId==0 ){ + svn_free_rec(&rec); + continue; + } + if( (zTemp = svn_find_header(rec, "Text-delta")) ){ + deltaFlag = strncmp(zTemp, "true", 4)==0; + } + if( strncmp(zAction, "delete", 6)==0 + || strncmp(zAction, "replace", 7)==0 ) + { + db_bind_int(&addRev, ":branch", branchId); + db_step(&addRev); + db_reset(&addRev); + if( zFile[0]!=0 ){ + db_bind_text(&delPath, ":path", zFile); + db_bind_int(&delPath, ":branch", branchId); + db_step(&delPath); + db_reset(&delPath); + }else{ + db_multi_exec("DELETE FROM xfiles WHERE tbranch=%d", branchId); + db_bind_int(&revSrc, ":parent", MAX_INT_32); + db_bind_int(&revSrc, ":rev", gsvn.rev); + db_bind_int(&revSrc, ":branch", branchId); + db_step(&revSrc); + db_reset(&revSrc); + } + } /* no 'else' here since 'replace' does both a 'delete' and an 'add' */ + if( strncmp(zAction, "add", 3)==0 + || strncmp(zAction, "replace", 7)==0 ) + { + char *zSrcPath = svn_find_header(rec, "Node-copyfrom-path"); + char *zSrcFile; + int srcRid = 0; + if( zSrcPath ){ + int srcBranch; + zTemp = svn_find_header(rec, "Node-copyfrom-rev"); + if( zTemp ){ + srcRev = atoi(zTemp); + }else{ + fossil_fatal("Missing copyfrom-rev"); + } + srcBranch = svn_parse_path(zSrcPath, &zSrcFile, &branchType); + if( srcBranch==0 ){ + fossil_fatal("Copy from path outside the import paths"); + } + srcRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions" + " WHERE trev<=%d AND tbranch=%d", + srcRev, srcBranch); + if( srcRid>0 && srcBranch!=branchId ){ + db_bind_int(&addRev, ":branch", branchId); + db_step(&addRev); + db_reset(&addRev); + db_bind_int(&revSrc, ":parent", srcRid); + db_bind_int(&revSrc, ":rev", gsvn.rev); + db_bind_int(&revSrc, ":branch", branchId); + db_step(&revSrc); + db_reset(&revSrc); + } + } + if( zKind==0 ){ + fossil_fatal("Missing Node-kind"); + }else if( strncmp(zKind, "dir", 3)==0 ){ + if( zSrcPath ){ + if( srcRid>0 ){ + if( zSrcFile[0]==0 ){ + db_bind_text(&cpyRoot, ":path", zFile); + if( zFile[0]!=0 ){ + db_bind_text(&cpyRoot, ":sep", "/"); + }else{ + db_bind_text(&cpyRoot, ":sep", ""); + } + db_bind_int(&cpyRoot, ":branch", branchId); + db_bind_int(&cpyRoot, ":rid", srcRid); + db_step(&cpyRoot); + db_reset(&cpyRoot); + }else{ + db_bind_text(&cpyPath, ":path", zFile); + if( zFile[0]!=0 ){ + db_bind_text(&cpyPath, ":sep", "/"); + }else{ + db_bind_text(&cpyPath, ":sep", ""); + } + db_bind_int(&cpyPath, ":branch", branchId); + db_bind_text(&cpyPath, ":srcpath", zSrcFile); + db_bind_int(&cpyPath, ":rid", srcRid); + db_step(&cpyPath); + db_reset(&cpyPath); + } + } + } + if( zFile[0]==0 ){ + bag_insert(&gsvn.newBranches, branchId); + } + }else{ + int rid = 0; + if( zSrcPath ){ + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=(" + " SELECT uuid FROM xfoci" + " WHERE checkinID=%d AND filename=%Q" + ")", + srcRid, zSrcFile); + } + if( deltaFlag ){ + Blob deltaSrc; + Blob target; + if( rid!=0 ){ + content_get(rid, &deltaSrc); + }else{ + blob_zero(&deltaSrc); + } + svn_apply_svndiff(&rec.content, &deltaSrc, &target); + rid = content_put(&target); + }else if( rec.contentFlag ){ + rid = content_put(&rec.content); + } + db_bind_text(&addFile, ":path", zFile); + db_bind_int(&addFile, ":branch", branchId); + db_bind_int(&addFile, ":rid", rid); + db_bind_text(&addFile, ":perm", zPerm); + db_step(&addFile); + db_reset(&addFile); + db_bind_int(&addRev, ":branch", branchId); + db_step(&addRev); + db_reset(&addRev); + } + }else + if( strncmp(zAction, "change", 6)==0 ){ + int rid = 0; + if( zKind==0 ){ + fossil_fatal("Missing Node-kind"); + } + if( strncmp(zKind, "dir", 3)!=0 ){ + if( deltaFlag ){ + Blob deltaSrc; + Blob target; + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=(" + " SELECT uuid FROM xfiles" + " WHERE tpath=%Q AND tbranch=%d" + ")", zFile, branchId); + content_get(rid, &deltaSrc); + svn_apply_svndiff(&rec.content, &deltaSrc, &target); + rid = content_put(&target); + }else{ + rid = content_put(&rec.content); + } + db_bind_text(&addFile, ":path", zFile); + db_bind_int(&addFile, ":branch", branchId); + db_bind_int(&addFile, ":rid", rid); + db_bind_text(&addFile, ":perm", zPerm); + db_step(&addFile); + db_reset(&addFile); + db_bind_int(&addRev, ":branch", branchId); + db_step(&addRev); + db_reset(&addRev); + } + }else + if( strncmp(zAction, "delete", 6)!=0 ){ /* already did this one above */ + fossil_fatal("Unknown Node-action"); + } + }else{ + fossil_fatal("Unknown record type"); + } + svn_free_rec(&rec); + } + svn_finish_revision(); + fossil_free(gsvn.zUser); + fossil_free(gsvn.zComment); + fossil_free(gsvn.zDate); + db_finalize(&addFile); + db_finalize(&delPath); + db_finalize(&addRev); + db_finalize(&cpyPath); + db_finalize(&cpyRoot); + db_finalize(&revSrc); + fossil_print(" Done!\n"); +} /* ** COMMAND: import ** -** Usage: %fossil import --git ?OPTIONS? NEW-REPOSITORY +** Usage: %fossil import ?--git? ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE? +** or: %fossil import --svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE? ** -** Read text generated by the git-fast-export command and use it to +** Read interchange format generated by another VCS and use it to ** construct a new Fossil repository named by the NEW-REPOSITORY -** argument. The git-fast-export text is read from standard input. +** argument. If no input file is supplied the interchange format +** data is read from standard input. +** +** The following formats are currently understood by this command +** +** --git Import from the git-fast-export file format (default) +** +** --svn Import from the svnadmin-dump file format. The default +** behaviour (unless overridden by --flat) is to treat 3 +** folders in the SVN root as special, following the +** common layout of SVN repositories. These are (by +** default) trunk/, branches/ and tags/ +** Options: +** --trunk FOLDER Name of trunk folder +** --branches FOLDER Name of branches folder +** --tags FOLDER Name of tags folder +** --base PATH Path to project root in repository +** --flat The whole dump is a single branch ** -** The git-fast-export file format is currently the only VCS interchange -** format that is understood, though other interchange formats may be added -** in the future. +** Common Options: +** -i|--incremental allow importing into an existing repository +** -f|--force overwrite repository if already exist ** ** The --incremental option allows an existing repository to be extended ** with new content. ** -** Options: -** --incremental allow importing into an existing repository ** ** See also: export */ -void git_import_cmd(void){ +void import_cmd(void){ char *zPassword; FILE *pIn; Stmt q; int forceFlag = find_option("force", "f", 0)!=0; + int svnFlag = find_option("svn", 0, 0)!=0; + + /* Options common to all input formats */ int incrFlag = find_option("incremental", "i", 0)!=0; - find_option("git",0,0); /* Skip the --git option for now */ + /* Options for --svn only */ + const char *zBase=""; + int flatFlag=0; + + if( svnFlag ){ + /* Get --svn related options here, so verify_all_options() fail when svn + * only option are specify with --git + */ + zBase = find_option("base", 0, 1); + flatFlag = find_option("flat", 0, 0)!=0; + gsvn.zTrunk = find_option("trunk", 0, 1); + gsvn.zBranches = find_option("branches", 0, 1); + gsvn.zTags = find_option("tags", 0, 1); + gsvn.incrFlag = incrFlag; + }else{ + find_option("git",0,0); /* Skip the --git option for now */ + } verify_all_options(); - if( g.argc!=3 && g.argc!=4 ){ - usage("REPOSITORY-NAME"); + + if( g.argc!=3 && g.argc!=4 ){ + usage("--git|--svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?"); } if( g.argc==4 ){ pIn = fossil_fopen(g.argv[3], "rb"); }else{ pIn = stdin; @@ -762,48 +1541,106 @@ if( forceFlag ) file_delete(g.argv[2]); db_create_repository(g.argv[2]); } db_open_repository(g.argv[2]); db_open_config(0); - - /* The following temp-tables are used to hold information needed for - ** the import. - ** - ** The XMARK table provides a mapping from fast-import "marks" and symbols - ** into artifact ids (UUIDs - the 40-byte hex SHA1 hash of artifacts). - ** Given any valid fast-import symbol, the corresponding fossil rid and - ** uuid can found by searching against the xmark.tname field. - ** - ** The XBRANCH table maps commit marks and symbols into the branch those - ** commits belong to. If xbranch.tname is a fast-import symbol for a - ** checkin then xbranch.brnm is the branch that checkin is part of. - ** - ** The XTAG table records information about tags that need to be applied - ** to various branches after the import finishes. The xtag.tcontent field - ** contains the text of an artifact that will add a tag to a check-in. - ** The git-fast-export file format might specify the same tag multiple - ** times but only the last tag should be used. And we do not know which - ** occurrence of the tag is the last until the import finishes. - */ - db_multi_exec( - "CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT, tuuid TEXT);" - "CREATE TEMP TABLE xbranch(tname TEXT UNIQUE, brnm TEXT);" - "CREATE TEMP TABLE xtag(tname TEXT UNIQUE, tcontent TEXT);" - ); - db_begin_transaction(); - if( !incrFlag ) db_initial_setup(0, 0, 0, 1); - git_fast_import(pIn); - db_prepare(&q, "SELECT tcontent FROM xtag"); - while( db_step(&q)==SQLITE_ROW ){ - Blob record; - db_ephemeral_blob(&q, 0, &record); - fast_insert_content(&record, 0, 0); - import_reset(0); - } - db_finalize(&q); + if( !incrFlag ) db_initial_setup(0, 0, 0); + + if( svnFlag ){ + db_multi_exec( + "CREATE TEMP TABLE xrevisions(" + " trev INTEGER, tbranch INT, trid INT, tparent INT DEFAULT 0," + " UNIQUE(tbranch, trev)" + ");" + "CREATE INDEX temp.i_xrevisions ON xrevisions(trid);" + "CREATE TEMP TABLE xfiles(" + " tpath TEXT, tbranch INT, tuuid TEXT, tperm TEXT," + " UNIQUE (tbranch, tpath) ON CONFLICT REPLACE" + ");" + "CREATE TEMP TABLE xbranches(" + " tid INTEGER PRIMARY KEY, tname TEXT, ttype INT," + " UNIQUE(tname, ttype)" + ");" + "CREATE VIRTUAL TABLE temp.xfoci USING files_of_checkin;" + ); + if( zBase==0 ){ zBase = ""; } + if( strlen(zBase)>0 ){ + if( zBase[strlen(zBase)-1]!='/' ){ + zBase = mprintf("%s/", zBase); + } + } + if( flatFlag ){ + gsvn.zTrunk = zBase; + gsvn.zBranches = 0; + gsvn.zTags = 0; + gsvn.lenTrunk = strlen(zBase); + gsvn.lenBranches = 0; + gsvn.lenTags = 0; + }else{ + if( gsvn.zTrunk==0 ){ gsvn.zTrunk = "trunk/"; } + if( gsvn.zBranches==0 ){ gsvn.zBranches = "branches/"; } + if( gsvn.zTags==0 ){ gsvn.zTags = "tags/"; } + gsvn.zTrunk = mprintf("%s%s", zBase, gsvn.zTrunk); + gsvn.zBranches = mprintf("%s%s", zBase, gsvn.zBranches); + gsvn.zTags = mprintf("%s%s", zBase, gsvn.zTags); + gsvn.lenTrunk = strlen(gsvn.zTrunk); + gsvn.lenBranches = strlen(gsvn.zBranches); + gsvn.lenTags = strlen(gsvn.zTags); + if( gsvn.zTrunk[gsvn.lenTrunk-1]!='/' ){ + gsvn.zTrunk = mprintf("%s/", gsvn.zTrunk); + gsvn.lenTrunk++; + } + if( gsvn.zBranches[gsvn.lenBranches-1]!='/' ){ + gsvn.zBranches = mprintf("%s/", gsvn.zBranches); + gsvn.lenBranches++; + } + if( gsvn.zTags[gsvn.lenTags-1]!='/' ){ + gsvn.zTags = mprintf("%s/", gsvn.zTags); + gsvn.lenTags++; + } + } + svn_dump_import(pIn); + }else{ + /* The following temp-tables are used to hold information needed for + ** the import. + ** + ** The XMARK table provides a mapping from fast-import "marks" and symbols + ** into artifact ids (UUIDs - the 40-byte hex SHA1 hash of artifacts). + ** Given any valid fast-import symbol, the corresponding fossil rid and + ** uuid can found by searching against the xmark.tname field. + ** + ** The XBRANCH table maps commit marks and symbols into the branch those + ** commits belong to. If xbranch.tname is a fast-import symbol for a + ** check-in then xbranch.brnm is the branch that check-in is part of. + ** + ** The XTAG table records information about tags that need to be applied + ** to various branches after the import finishes. The xtag.tcontent field + ** contains the text of an artifact that will add a tag to a check-in. + ** The git-fast-export file format might specify the same tag multiple + ** times but only the last tag should be used. And we do not know which + ** occurrence of the tag is the last until the import finishes. + */ + db_multi_exec( + "CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT, tuuid TEXT);" + "CREATE TEMP TABLE xbranch(tname TEXT UNIQUE, brnm TEXT);" + "CREATE TEMP TABLE xtag(tname TEXT UNIQUE, tcontent TEXT);" + ); + + git_fast_import(pIn); + db_prepare(&q, "SELECT tcontent FROM xtag"); + while( db_step(&q)==SQLITE_ROW ){ + Blob record; + db_ephemeral_blob(&q, 0, &record); + fast_insert_content(&record, 0, 0); + import_reset(0); + } + db_finalize(&q); + } + + verify_cancel(); db_end_transaction(0); db_begin_transaction(); fossil_print("Rebuilding repository meta-data...\n"); rebuild_db(0, 1, !incrFlag); verify_cancel(); Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -108,19 +108,35 @@ ); fossil_print("%-13s %s %s\n", zType, zUuid, zDate); free(zDate); } db_finalize(&q); + } + if( zUuid ){ + fossil_print("%-13s ", "leaf:"); + if(is_a_leaf(rid)){ + if(db_int(0, "SELECT 1 FROM tagxref AS tx" + " WHERE tx.rid=%d" + " AND tx.tagid=%d" + " AND tx.tagtype>0", + rid, TAG_CLOSED)){ + fossil_print("%s\n", "closed"); + }else{ + fossil_print("%s\n", "open"); + } + }else{ + fossil_print("no\n"); + } } zTags = info_tags_of_checkin(rid, 0); if( zTags && zTags[0] ){ fossil_print("tags: %s\n", zTags); } free(zTags); if( zComment ){ fossil_print("comment: "); - comment_print(zComment, 14, 79); + comment_print(zComment, 0, 14, -1, g.comFmtFlags); free(zComment); } } /* @@ -181,20 +197,22 @@ i64 fsize; int verboseFlag = find_option("verbose","v",0)!=0; if( !verboseFlag ){ verboseFlag = find_option("detail","l",0)!=0; /* deprecated */ } + if( g.argc==3 && (fsize = file_size(g.argv[2]))>0 && (fsize&0x1ff)==0 ){ db_open_config(0); - db_record_repository_filename(g.argv[2]); db_open_repository(g.argv[2]); + db_record_repository_filename(g.argv[2]); fossil_print("project-name: %s\n", db_get("project-name", "<unnamed>")); fossil_print("project-code: %s\n", db_get("project-code", "<none>")); extraRepoInfo(); return; } db_find_and_open_repository(0,0); + verify_all_options(); if( g.argc==2 ){ int vid; /* 012345678901234 */ db_record_repository_filename(0); fossil_print("project-name: %s\n", db_get("project-name", "<unnamed>")); @@ -209,11 +227,11 @@ fossil_print("project-code: %s\n", db_get("project-code", "")); vid = g.localOpen ? db_lget_int("checkout", 0) : 0; if( vid ){ show_common_info(vid, "checkout:", 1, 1); } - fossil_print("checkins: %d\n", + fossil_print("check-ins: %d\n", db_int(-1, "SELECT count(*) FROM event WHERE type='ci' /*scan*/")); }else{ int rid; rid = name_to_rid(g.argv[2]); if( rid==0 ){ @@ -222,23 +240,23 @@ show_common_info(rid, "uuid:", 1, 1); } } /* -** Show information about all tags on a given node. +** Show information about all tags on a given check-in. */ -static void showTags(int rid, const char *zNotGlob){ +static void showTags(int rid){ Stmt q; int cnt = 0; db_prepare(&q, "SELECT tag.tagid, tagname, " " (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d)," - " value, datetime(tagxref.mtime,'localtime'), tagtype," + " value, datetime(tagxref.mtime%s), tagtype," " (SELECT uuid FROM blob WHERE rid=tagxref.origid AND rid!=%d)" " FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid" - " WHERE tagxref.rid=%d AND tagname NOT GLOB '%q'" - " ORDER BY tagname /*sort*/", rid, rid, rid, zNotGlob + " WHERE tagxref.rid=%d" + " ORDER BY tagname /*sort*/", rid, timeline_utc(), rid, rid ); while( db_step(&q)==SQLITE_ROW ){ const char *zTagname = db_column_text(&q, 1); const char *zSrcUuid = db_column_text(&q, 2); const char *zValue = db_column_text(&q, 3); @@ -287,10 +305,33 @@ db_finalize(&q); if( cnt ){ @ </ul> } } + +/* +** Show the context graph (immediate parents and children) for +** check-in rid. +*/ +static void showContext(int rid){ + Blob sql; + Stmt q; + @ <div class="section">Context</div> + blob_zero(&sql); + blob_append(&sql, timeline_query_for_www(), -1); + db_multi_exec( + "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);" + "INSERT INTO ok VALUES(%d);" + "INSERT OR IGNORE INTO ok SELECT pid FROM plink WHERE cid=%d;" + "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;", + rid, rid, rid + ); + blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC"); + db_prepare(&q, "%s", blob_sql_text(&sql)); + www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH, 0, 0, rid, 0); + db_finalize(&q); +} /* ** Append the difference between artifacts to the output */ @@ -362,32 +403,32 @@ } }else{ if( zOld && zNew ){ if( fossil_strcmp(zOld, zNew)!=0 ){ @ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a> - @ from %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a> - @ to %z(href("%R/artifact/%s",zNew))[%S(zNew)].</a> + @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> + @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ @ <p>Name change @ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a> @ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>. }else{ @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a> } }else if( zOld ){ - @ <p>Deleted %z(href("%s/finfo?name=%T",g.zTop,zName))%h(zName)</a> - @ version %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a> + @ <p>Deleted %z(href("%R/finfo?name=%T",zName))%h(zName)</a> + @ version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> }else{ @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a> - @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a> + @ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a> } if( diffFlags ){ append_diff(zOld, zNew, diffFlags, pRe); }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ @    - @ %z(href("%R/fdiff?v1=%S&v2=%S&sbs=1",zOld,zNew))[diff]</a> + @ %z(href("%R/fdiff?v1=%!S&v2=%!S&sbs=1",zOld,zNew))[diff]</a> } } } /* @@ -427,37 +468,36 @@ /* ** Construct an appropriate diffFlag for text_diff() based on query ** parameters and the to boolean arguments. */ u64 construct_diff_flags(int verboseFlag, int sideBySide){ - u64 diffFlags; - if( verboseFlag==0 ){ - diffFlags = 0; /* Zero means do not show any diff */ - }else{ + u64 diffFlags = 0; /* Zero means do not show any diff */ + if( verboseFlag!=0 ){ int x; if( sideBySide ){ - diffFlags = DIFF_SIDEBYSIDE | DIFF_IGNORE_EOLWS; + diffFlags = DIFF_SIDEBYSIDE; /* "dw" query parameter determines width of each column */ x = atoi(PD("dw","80"))*(DIFF_CONTEXT_MASK+1); if( x<0 || x>DIFF_WIDTH_MASK ) x = DIFF_WIDTH_MASK; diffFlags += x; - }else{ - diffFlags = DIFF_INLINE | DIFF_IGNORE_EOLWS; } + if( P("w") ){ + diffFlags |= DIFF_IGNORE_ALLWS; + } /* "dc" query parameter determines lines of context */ x = atoi(PD("dc","7")); if( x<0 || x>DIFF_CONTEXT_MASK ) x = DIFF_CONTEXT_MASK; diffFlags += x; /* The "noopt" parameter disables diff optimization */ if( PD("noopt",0)!=0 ) diffFlags |= DIFF_NOOPT; + diffFlags |= DIFF_STRIP_EOLCR; } return diffFlags; } - /* ** WEBPAGE: vinfo ** WEBPAGE: ci ** URL: /ci?name=RID|ARTIFACTID @@ -477,18 +517,21 @@ int rid; int isLeaf; int verboseFlag; /* True to show diffs */ int sideBySide; /* True for side-by-side diffs */ u64 diffFlags; /* Flag parameter for text_diff() */ - const char *zName; /* Name of the checkin to be displayed */ + const char *zName; /* Name of the check-in to be displayed */ const char *zUuid; /* UUID of zName */ - const char *zParent; /* UUID of the parent checkin (if any) */ + const char *zParent; /* UUID of the parent check-in (if any) */ const char *zRe; /* regex parameter */ ReCompiled *pRe = 0; /* regex */ + const char *zW; /* URL param for ignoring whitespace */ + const char *zPage = "vinfo"; /* Page that shows diffs */ + const char *zPageHide = "ci"; /* Page that hides diffs */ login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } zName = P("name"); rid = name_to_rid_www("name"); if( rid==0 ){ style_header("Check-in Information Error"); @ No such object: %h(g.argv[2]) @@ -503,32 +546,31 @@ " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim", rid ); isLeaf = is_a_leaf(rid); db_prepare(&q1, - "SELECT uuid, datetime(mtime, 'localtime'), user, comment," - " datetime(omtime, 'localtime'), mtime" + "SELECT uuid, datetime(mtime%s), user, comment," + " datetime(omtime%s), mtime" " FROM blob, event" " WHERE blob.rid=%d" " AND event.objid=%d", - rid, rid + timeline_utc(), timeline_utc(), rid, rid ); sideBySide = !is_false(PD("sbs","1")); if( db_step(&q1)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q1, 0); - char *zTitle = mprintf("Check-in [%.10s]", zUuid); char *zEUser, *zEComment; const char *zUser; const char *zComment; const char *zDate; const char *zOrigDate; - style_header(zTitle); + style_header("Check-in [%S]", zUuid); login_anonymous_available(); - free(zTitle); zEUser = db_text(0, - "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d", + "SELECT value FROM tagxref" + " WHERE tagid=%d AND rid=%d AND tagtype>0", TAG_USER, rid); zEComment = db_text(0, "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d", TAG_COMMENT, rid); zUser = db_column_text(&q1, 2); @@ -556,14 +598,14 @@ }else{ @ <tr><th>User:</th><td> hyperlink_to_user(zUser,zDate,"</td></tr>"); } if( zEComment ){ - @ <tr><th>Edited Comment:</th><td class="infoComment">%!w(zEComment)</td></tr> - @ <tr><th>Original Comment:</th><td class="infoComment">%!w(zComment)</td></tr> + @ <tr><th>Edited Comment:</th><td class="infoComment">%!W(zEComment)</td></tr> + @ <tr><th>Original Comment:</th><td class="infoComment">%!W(zComment)</td></tr> }else{ - @ <tr><th>Comment:</th><td class="infoComment">%!w(zComment)</td></tr> + @ <tr><th>Comment:</th><td class="infoComment">%!W(zComment)</td></tr> } if( g.perm.Admin ){ db_prepare(&q2, "SELECT rcvfrom.ipaddr, user.login, datetime(rcvfrom.mtime)" " FROM blob JOIN rcvfrom USING(rcvid) LEFT JOIN user USING(uid)" @@ -579,132 +621,142 @@ @ <td>%h(zUser) @ %h(zIpAddr) on %s(zDate)</td></tr> } db_finalize(&q2); } if( g.perm.Hyperlink ){ - const char *zProjName = db_get("project-name", "unnamed"); + char *zPJ = db_get("short-project-name", 0); + Blob projName; + int jj; + if( zPJ==0 ) zPJ = db_get("project-name", "unnamed"); + blob_zero(&projName); + blob_append(&projName, zPJ, -1); + blob_trim(&projName); + zPJ = blob_str(&projName); + for(jj=0; zPJ[jj]; jj++){ + if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){ + zPJ[jj] = '_'; + } + } @ <tr><th>Timelines:</th><td> - @ %z(href("%R/timeline?f=%S",zUuid))family</a> + @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a> if( zParent ){ - @ | %z(href("%R/timeline?p=%S",zUuid))ancestors</a> + @ | %z(href("%R/timeline?p=%!S&unhide",zUuid))ancestors</a> } if( !isLeaf ){ - @ | %z(href("%R/timeline?d=%S",zUuid))descendants</a> + @ | %z(href("%R/timeline?d=%!S&unhide",zUuid))descendants</a> } if( zParent && !isLeaf ){ - @ | %z(href("%R/timeline?dp=%S",zUuid))both</a> + @ | %z(href("%R/timeline?dp=%!S&unhide",zUuid))both</a> } db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag " " WHERE rid=%d AND tagtype>0 " " AND tag.tagid=tagxref.tagid " " AND +tag.tagname GLOB 'sym-*'", rid); while( db_step(&q2)==SQLITE_ROW ){ const char *zTagName = db_column_text(&q2, 0); - @ | %z(href("%R/timeline?r=%T",zTagName))%h(zTagName)</a> + @ | %z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a> } db_finalize(&q2); /* The Download: line */ - if( g.perm.Zip ){ + if( g.anon.Zip ){ char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s", - zProjName, zUuid, zUuid); + zPJ, zUuid, zUuid); @ </td></tr> @ <tr><th>Downloads:</th><td> @ %z(href("%s",zUrl))Tarball</a> - @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zProjName,zUuid,zUuid)) + @ | %z(href("%R/zip/%t-%S.zip?uuid=%!S",zPJ,zUuid,zUuid)) @ ZIP archive</a> fossil_free(zUrl); } @ </td></tr> @ <tr><th>Other Links:</th> @ <td> - @ %z(href("%R/dir?ci=%S",zUuid))files</a> - @ | %z(href("%R/fileage?name=%S",zUuid))file ages</a> - @ | %z(href("%R/artifact/%S",zUuid))manifest</a> - if( g.perm.Write ){ - @ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a> + @ %z(href("%R/tree?ci=%!S",zUuid))files</a> + @ | %z(href("%R/fileage?name=%!S",zUuid))file ages</a> + @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a> + @ | %z(href("%R/artifact/%!S",zUuid))manifest</a> + if( g.anon.Write ){ + @ | %z(href("%R/ci_edit?r=%!S",zUuid))edit</a> } @ </td> @ </tr> + blob_reset(&projName); } @ </table> }else{ style_header("Check-in Information"); login_anonymous_available(); } db_finalize(&q1); - showTags(rid, ""); + showTags(rid); + showContext(rid); + @ <div class="section">Changes</div> + @ <div class="sectionmenu"> + verboseFlag = g.zPath[0]!='c'; + if( db_get_boolean("show-version-diffs", 0)==0 ){ + verboseFlag = !verboseFlag; + zPage = "ci"; + zPageHide = "vinfo"; + } + diffFlags = construct_diff_flags(verboseFlag, sideBySide); + zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; + if( verboseFlag ){ + @ %z(xhref("class='button'","%R/%s/%T",zPageHide,zName)) + @ Hide Diffs</a> + if( sideBySide ){ + @ %z(xhref("class='button'","%R/%s/%T?sbs=0%s",zPage,zName,zW)) + @ Unified Diffs</a> + }else{ + @ %z(xhref("class='button'","%R/%s/%T?sbs=1%s",zPage,zName,zW)) + @ Side-by-Side Diffs</a> + } + if( *zW ){ + @ %z(xhref("class='button'","%R/%s/%T?sbs=%d",zPage,zName,sideBySide)) + @ Show Whitespace Changes</a> + }else{ + @ %z(xhref("class='button'","%R/%s/%T?sbs=%d&w",zPage,zName,sideBySide)) + @ Ignore Whitespace</a> + } + }else{ + @ %z(xhref("class='button'","%R/%s/%T?sbs=0",zPage,zName)) + @ Show Unified Diffs</a> + @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName)) + @ Show Side-by-Side Diffs</a> + } if( zParent ){ - @ <div class="section">Changes</div> - @ <div class="sectionmenu"> - verboseFlag = g.zPath[0]!='c'; - if( db_get_boolean("show-version-diffs", 0)==0 ){ - verboseFlag = !verboseFlag; - if( verboseFlag ){ - @ %z(xhref("class='button'","%R/vinfo/%T",zName)) - @ hide diffs</a> - if( sideBySide ){ - @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName)) - @ unified diffs</a> - }else{ - @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName)) - @ side-by-side diffs</a> - } - }else{ - @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName)) - @ show unified diffs</a> - @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName)) - @ show side-by-side diffs</a> - } - }else{ - if( verboseFlag ){ - @ %z(xhref("class='button'","%R/ci/%T",zName))hide diffs</a> - if( sideBySide ){ - @ %z(xhref("class='button'","%R/info/%T?sbs=0",zName)) - @ unified diffs</a> - }else{ - @ %z(xhref("class='button'","%R/info/%T?sbs=1",zName)) - @ side-by-side diffs</a> - } - }else{ - @ %z(xhref("class='button'","%R/vinfo/%T?sbs=0",zName)) - @ show unified diffs</a> - @ %z(xhref("class='button'","%R/vinfo/%T?sbs=1",zName)) - @ show side-by-side diffs</a> - } - } - @ %z(xhref("class='button'","%R/vpatch?from=%S&to=%S",zParent,zUuid)) - @ patch</a></div> - if( pRe ){ - @ <p><b>Only differences that match regular expression "%h(zRe)" - @ are shown.</b></p> - } - db_prepare(&q3, - "SELECT name," - " mperm," - " (SELECT uuid FROM blob WHERE rid=mlink.pid)," - " (SELECT uuid FROM blob WHERE rid=mlink.fid)," - " (SELECT name FROM filename WHERE filename.fnid=mlink.pfnid)" - " FROM mlink JOIN filename ON filename.fnid=mlink.fnid" - " WHERE mlink.mid=%d" - " AND (mlink.fid>0" - " OR mlink.fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=%d))" - " ORDER BY name /*sort*/", - rid, rid - ); - diffFlags = construct_diff_flags(verboseFlag, sideBySide); - while( db_step(&q3)==SQLITE_ROW ){ - const char *zName = db_column_text(&q3,0); - int mperm = db_column_int(&q3, 1); - const char *zOld = db_column_text(&q3,2); - const char *zNew = db_column_text(&q3,3); - const char *zOldName = db_column_text(&q3, 4); - append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm); - } - db_finalize(&q3); - } + @ %z(xhref("class='button'","%R/vpatch?from=%!S&to=%!S",zParent,zUuid)) + @ Patch</a> + } + @</div> + if( pRe ){ + @ <p><b>Only differences that match regular expression "%h(zRe)" + @ are shown.</b></p> + } + db_prepare(&q3, + "SELECT name," + " mperm," + " (SELECT uuid FROM blob WHERE rid=mlink.pid)," + " (SELECT uuid FROM blob WHERE rid=mlink.fid)," + " (SELECT name FROM filename WHERE filename.fnid=mlink.pfnid)" + " FROM mlink JOIN filename ON filename.fnid=mlink.fnid" + " WHERE mlink.mid=%d AND NOT mlink.isaux" + " AND (mlink.fid>0" + " OR mlink.fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=%d))" + " ORDER BY name /*sort*/", + rid, rid + ); + while( db_step(&q3)==SQLITE_ROW ){ + const char *zName = db_column_text(&q3,0); + int mperm = db_column_int(&q3, 1); + const char *zOld = db_column_text(&q3,2); + const char *zNew = db_column_text(&q3,3); + const char *zOldName = db_column_text(&q3, 4); + append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm); + } + db_finalize(&q3); append_diff_javascript(sideBySide); style_footer(); } /* @@ -721,11 +773,11 @@ Blob wiki; int modPending; const char *zModAction; login_check_credentials(); - if( !g.perm.RdWiki ){ login_needed(); return; } + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } rid = name_to_rid_www("name"); if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){ style_header("Wiki Page Information Error"); @ No such object: %h(P("name")) style_footer(); @@ -732,30 +784,40 @@ return; } if( g.perm.ModWiki && (zModAction = P("modaction"))!=0 ){ if( strcmp(zModAction,"delete")==0 ){ moderation_disapprove(rid); - cgi_redirectf("%R/wiki?name=%T", pWiki->zWikiTitle); - /*NOTREACHED*/ + /* + ** Next, check if the wiki page still exists; if not, we cannot + ** redirect to it. + */ + if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" + " WHERE rid=%d AND tagname LIKE 'wiki-%%'", rid) ){ + cgi_redirectf("%R/wiki?name=%T", pWiki->zWikiTitle); + /*NOTREACHED*/ + }else{ + cgi_redirectf("%R/modreq"); + /*NOTREACHED*/ + } } if( strcmp(zModAction,"approve")==0 ){ moderation_approve(rid); } } style_header("Update of \"%h\"", pWiki->zWikiTitle); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); zDate = db_text(0, "SELECT datetime(%.17g)", pWiki->rDate); - style_submenu_element("Raw", "Raw", "artifact/%S", zUuid); + style_submenu_element("Raw", "Raw", "artifact/%s", zUuid); style_submenu_element("History", "History", "whistory?name=%t", pWiki->zWikiTitle); style_submenu_element("Page", "Page", "wiki?name=%t", pWiki->zWikiTitle); login_anonymous_available(); @ <div class="section">Overview</div> @ <p><table class="label-value"> @ <tr><th>Artifact ID:</th> - @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a> + @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a> if( g.perm.Setup ){ @ (%d(rid)) } modPending = moderation_pending(rid); if( modPending ){ @@ -765,16 +827,19 @@ @ <tr><th>Page Name:</th><td>%h(pWiki->zWikiTitle)</td></tr> @ <tr><th>Date:</th><td> hyperlink_to_date(zDate, "</td></tr>"); @ <tr><th>Original User:</th><td> hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>"); + if( pWiki->zMimetype ){ + @ <tr><th>Mimetype:</th><td>%h(pWiki->zMimetype)</td></tr> + } if( pWiki->nParent>0 ){ int i; @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td> for(i=0; i<pWiki->nParent; i++){ char *zParent = pWiki->azParent[i]; - @ %z(href("info/%S",zParent))%s(zParent)</a> + @ %z(href("info/%!S",zParent))%s(zParent)</a> } @ </td></tr> } @ </table> @@ -792,11 +857,11 @@ } @ <div class="section">Content</div> blob_init(&wiki, pWiki->zWiki, -1); - wiki_convert(&wiki, 0, 0); + wiki_render_by_mimetype(&wiki, pWiki->zMimetype); blob_reset(&wiki); manifest_destroy(pWiki); style_footer(); } @@ -814,11 +879,11 @@ @ <p>%h(z)</p> style_footer(); } /* -** Find an checkin based on query parameter zParam and parse its +** Find an check-in based on query parameter zParam and parse its ** manifest. Return the number of errors. */ static Manifest *vdiff_parse_manifest(const char *zParam, int *pRid){ int rid; @@ -831,11 +896,11 @@ webpage_error("No such artifact: \"%s\"", z); } return 0; } if( !is_a_version(rid) ){ - webpage_error("Artifact %s is not a checkin.", P(zParam)); + webpage_error("Artifact %s is not a check-in.", P(zParam)); return 0; } return manifest_get(rid, CFTYPE_MANIFEST, 0); } @@ -891,10 +956,11 @@ }else{ @ tags: %h(zTagList), } @ date: hyperlink_to_date(zDate, ")"); + tag_private_status(rid); } db_finalize(&q); } @@ -902,18 +968,19 @@ ** WEBPAGE: vdiff ** URL: /vdiff ** ** Query parameters: ** -** from=TAG -** to=TAG -** branch=TAG -** v=BOOLEAN -** sbs=BOOLEAN +** from=TAG Left side of the comparison +** to=TAG Right side of the comparison +** branch=TAG Show all changes on a particular branch +** v=BOOLEAN Default true. If false, only list files that have changed +** sbs=BOOLEAN Side-by-side diff if true. Unified diff if false +** glob=STRING only diff files matching this glob ** ** -** Show all differences between two checkins. +** Show all differences between two check-ins. */ void vdiff_page(void){ int ridFrom, ridTo; int verboseFlag = 0; int sideBySide = 0; @@ -922,17 +989,17 @@ ManifestFile *pFileFrom, *pFileTo; const char *zBranch; const char *zFrom; const char *zTo; const char *zRe; + const char *zW; const char *zVerbose; + const char *zGlob; ReCompiled *pRe = 0; - login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } login_anonymous_available(); - zRe = P("regex"); if( zRe ) re_compile(&pRe, zRe, 0); zBranch = P("branch"); if( zBranch && zBranch[0] ){ cgi_replace_parameter("from", mprintf("root:%s", zBranch)); @@ -950,25 +1017,63 @@ if( !zVerbose ){ zVerbose = P("detail"); /* deprecated */ } verboseFlag = (zVerbose!=0) && !is_false(zVerbose); if( !verboseFlag && sideBySide ) verboseFlag = 1; + zGlob = P("glob"); zFrom = P("from"); zTo = P("to"); + if(zGlob && !*zGlob){ + zGlob = NULL; + } + diffFlags = construct_diff_flags(verboseFlag, sideBySide); + zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; + style_submenu_element("Path","path", + "%R/timeline?me=%T&you=%T", zFrom, zTo); + if( sideBySide || verboseFlag ){ + style_submenu_element("Hide Diff", "hidediff", + "%R/vdiff?from=%T&to=%T&sbs=0%s%T%s", + zFrom, zTo, + zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW); + } if( !sideBySide ){ - style_submenu_element("Side-by-side Diff", "sbsdiff", - "%R/vdiff?from=%T&to=%T&sbs=1", - zFrom, zTo); + style_submenu_element("Side-by-Side Diff", "sbsdiff", + "%R/vdiff?from=%T&to=%T&sbs=1%s%T%s", + zFrom, zTo, + zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW); } if( sideBySide || !verboseFlag ) { style_submenu_element("Unified Diff", "udiff", - "%R/vdiff?from=%T&to=%T&sbs=0&v", - zFrom, zTo); + "%R/vdiff?from=%T&to=%T&sbs=0&v%s%T%s", + zFrom, zTo, + zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW); } style_submenu_element("Invert", "invert", - "%R/vdiff?from=%T&to=%T&sbs=%d%s", zTo, zFrom, - sideBySide, (verboseFlag && !sideBySide)?"&v":""); + "%R/vdiff?from=%T&to=%T&sbs=%d%s%s%T%s", zTo, zFrom, + sideBySide, (verboseFlag && !sideBySide)?"&v":"", + zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW); + if( zGlob ){ + style_submenu_element("Clear glob", "clearglob", + "%R/vdiff?from=%T&to=%T&sbs=%d%s%s", zFrom, zTo, + sideBySide, (verboseFlag && !sideBySide)?"&v":"", zW); + }else{ + style_submenu_element("Patch", "patch", + "%R/vpatch?from=%T&to=%T%s", zFrom, zTo, zW); + } + if( sideBySide || verboseFlag ){ + if( *zW ){ + style_submenu_element("Show Whitespace Differences", "whitespace", + "%R/vdiff?from=%T&to=%T&sbs=%d%s%s%T", zFrom, zTo, + sideBySide, (verboseFlag && !sideBySide)?"&v":"", + zGlob ? "&glob=" : "", zGlob ? zGlob : ""); + }else{ + style_submenu_element("Ignore Whitespace", "ignorews", + "%R/vdiff?from=%T&to=%T&sbs=%d%s%s%T&w", zFrom, zTo, + sideBySide, (verboseFlag && !sideBySide)?"&v":"", + zGlob ? "&glob=" : "", zGlob ? zGlob : ""); + } + } style_header("Check-in Differences"); @ <h2>Difference From:</h2><blockquote> checkin_description(ridFrom); @ </blockquote><h2>To:</h2><blockquote> checkin_description(ridTo); @@ -975,17 +1080,19 @@ @ </blockquote> if( pRe ){ @ <p><b>Only differences that match regular expression "%h(zRe)" @ are shown.</b></p> } + if( zGlob ){ + @ <p><b>Only files matching the glob "%h(zGlob)" are shown.</b></p> + } @<hr /><p> manifest_file_rewind(pFrom); pFileFrom = manifest_file_next(pFrom, 0); manifest_file_rewind(pTo); pFileTo = manifest_file_next(pTo, 0); - diffFlags = construct_diff_flags(verboseFlag, sideBySide); while( pFileFrom || pFileTo ){ int cmp; if( pFileFrom==0 ){ cmp = +1; }else if( pFileTo==0 ){ @@ -992,27 +1099,33 @@ cmp = -1; }else{ cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName); } if( cmp<0 ){ - append_file_change_line(pFileFrom->zName, - pFileFrom->zUuid, 0, 0, diffFlags, pRe, 0); + if( !zGlob || sqlite3_strglob(zGlob, pFileFrom->zName)==0 ){ + append_file_change_line(pFileFrom->zName, + pFileFrom->zUuid, 0, 0, diffFlags, pRe, 0); + } pFileFrom = manifest_file_next(pFrom, 0); }else if( cmp>0 ){ - append_file_change_line(pFileTo->zName, - 0, pFileTo->zUuid, 0, diffFlags, pRe, - manifest_file_mperm(pFileTo)); + if( !zGlob || sqlite3_strglob(zGlob, pFileTo->zName)==0 ){ + append_file_change_line(pFileTo->zName, + 0, pFileTo->zUuid, 0, diffFlags, pRe, + manifest_file_mperm(pFileTo)); + } pFileTo = manifest_file_next(pTo, 0); }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){ - /* No changes */ pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); }else{ - append_file_change_line(pFileFrom->zName, - pFileFrom->zUuid, - pFileTo->zUuid, 0, diffFlags, pRe, - manifest_file_mperm(pFileTo)); + if(!zGlob || (sqlite3_strglob(zGlob, pFileFrom->zName)==0 + || sqlite3_strglob(zGlob, pFileTo->zName)==0) ){ + append_file_change_line(pFileFrom->zName, + pFileFrom->zUuid, + pFileTo->zUuid, 0, diffFlags, pRe, + manifest_file_mperm(pFileTo)); + } pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); } } manifest_destroy(pFrom); @@ -1032,10 +1145,16 @@ #define OBJTYPE_ATTACHMENT 0x0010 #define OBJTYPE_EVENT 0x0020 #define OBJTYPE_TAG 0x0040 #define OBJTYPE_SYMLINK 0x0080 #define OBJTYPE_EXE 0x0100 + +/* +** Possible flags for the second parameter to +** object_description() +*/ +#define OBJDESC_DETAIL 0x0001 /* more detail */ #endif /* ** Write a description of an object to the www reply. ** @@ -1051,19 +1170,19 @@ ** * date of check-in ** * Comment & user */ int object_description( int rid, /* The artifact ID */ - int linkToView, /* Add viewer link if true */ + u32 objdescFlags, /* Flags to control display */ Blob *pDownloadName /* Fill with an appropriate download name */ ){ Stmt q; int cnt = 0; int nWiki = 0; int objType = 0; char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); - + int showDetail = (objdescFlags & OBJDESC_DETAIL)!=0; char *prevName = 0; db_prepare(&q, "SELECT filename.name, datetime(event.mtime)," " coalesce(event.ecomment,event.comment)," @@ -1087,12 +1206,20 @@ const char *zCom = db_column_text(&q, 2); const char *zUser = db_column_text(&q, 3); const char *zVers = db_column_text(&q, 4); int mPerm = db_column_int(&q, 5); const char *zBr = db_column_text(&q, 6); - if( !prevName || fossil_strcmp(zName, prevName) ) { - if( prevName ) { + int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0; + if( sameFilename && !showDetail ){ + if( cnt==1 ){ + @ %z(href("%R/whatis/%!S",zUuid))[more...]</a> + } + cnt++; + continue; + } + if( !sameFilename ){ + if( prevName && showDetail ) { @ </ul> } if( mPerm==PERM_LNK ){ @ <li>Symbolic link objType |= OBJTYPE_SYMLINK; @@ -1102,33 +1229,45 @@ }else{ @ <li>File } objType |= OBJTYPE_CONTENT; @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a> - @ <ul> + tag_private_status(rid); + if( showDetail ){ + @ <ul> + } prevName = fossil_strdup(zName); } - @ <li> - hyperlink_to_date(zDate,""); - @ - part of checkin - hyperlink_to_uuid(zVers); + if( showDetail ){ + @ <li> + hyperlink_to_date(zDate,""); + @ — part of check-in + hyperlink_to_uuid(zVers); + }else{ + @ — part of check-in + hyperlink_to_uuid(zVers); + @ at + hyperlink_to_date(zDate,""); + } if( zBr && zBr[0] ){ @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a> } - @ - %!w(zCom) (user: + @ — %!W(zCom) (user: hyperlink_to_user(zUser,zDate,")"); if( g.perm.Hyperlink ){ - @ %z(href("%R/annotate?checkin=%S&filename=%T",zVers,zName)) + @ %z(href("%R/finfo?name=%T&ci=%!S",zName,zVers))[ancestry]</a> + @ %z(href("%R/annotate?filename=%T&checkin=%!S",zName,zVers)) @ [annotate]</a> - @ %z(href("%R/finfo?name=%T&ci=%S",zName,zVers))[ancestry]</a> + @ %z(href("%R/blame?filename=%T&checkin=%!S",zName,zVers)) + @ [blame]</a> } cnt++; if( pDownloadName && blob_size(pDownloadName)==0 ){ blob_append(pDownloadName, zName, -1); } } - if( prevName ){ + if( prevName && showDetail ){ @ </ul> } @ </ul> free(prevName); db_finalize(&q); @@ -1196,16 +1335,17 @@ @ Control file referencing } if( zType[0]!='e' ){ hyperlink_to_uuid(zUuid); } - @ - %!w(zCom) by + @ - %!W(zCom) by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate, "."); if( pDownloadName && blob_size(pDownloadName)==0 ){ - blob_appendf(pDownloadName, "%.10s.txt", zUuid); + blob_appendf(pDownloadName, "%S.txt", zUuid); } + tag_private_status(rid); cnt++; } db_finalize(&q); } db_prepare(&q, @@ -1226,17 +1366,17 @@ }else{ @ Attachment "%h(zFilename)" to } objType |= OBJTYPE_ATTACHMENT; if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ - if( g.perm.Hyperlink && g.perm.RdTkt ){ - @ ticket [%z(href("%R/tktview?name=%S",zTarget))%S(zTarget)</a>] + if( g.perm.Hyperlink && g.anon.RdTkt ){ + @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>] }else{ @ ticket [%S(zTarget)] } }else{ - if( g.perm.Hyperlink && g.perm.RdWiki ){ + if( g.perm.Hyperlink && g.anon.RdWiki ){ @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>] }else{ @ wiki page [%h(zTarget)] } } @@ -1245,19 +1385,19 @@ hyperlink_to_date(zDate,"."); cnt++; if( pDownloadName && blob_size(pDownloadName)==0 ){ blob_append(pDownloadName, zFilename, -1); } + tag_private_status(rid); } db_finalize(&q); if( cnt==0 ){ @ Control artifact. if( pDownloadName && blob_size(pDownloadName)==0 ){ - blob_appendf(pDownloadName, "%.10s.txt", zUuid); + blob_appendf(pDownloadName, "%S.txt", zUuid); } - }else if( linkToView && g.perm.Hyperlink ){ - @ %z(href("%R/artifact/%S",zUuid))[view]</a> + tag_private_status(rid); } return objType; } @@ -1266,28 +1406,35 @@ ** URL: fdiff?v1=UUID&v2=UUID&patch&sbs=BOOLEAN®ex=REGEX ** ** Two arguments, v1 and v2, identify the files to be diffed. Show the ** difference between the two artifacts. Show diff side by side unless sbs ** is 0. Generate plaintext if "patch" is present. +** +** Additional parameters: +** +** verbose Show more detail when describing artifacts */ void diff_page(void){ int v1, v2; int isPatch; int sideBySide; char *zV1; char *zV2; const char *zRe; + const char *zW; /* URL param for ignoring whitespace */ ReCompiled *pRe = 0; u64 diffFlags; + u32 objdescFlags = 0; login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } v1 = name_to_rid_www("v1"); v2 = name_to_rid_www("v2"); if( v1==0 || v2==0 ) fossil_redirect_home(); zRe = P("regex"); if( zRe ) re_compile(&pRe, zRe, 0); + if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL; isPatch = P("patch")!=0; if( isPatch ){ Blob c1, c2, *pOut; pOut = cgi_output_blob(); cgi_set_content_type("text/plain"); @@ -1297,39 +1444,49 @@ text_diff(&c1, &c2, pOut, pRe, diffFlags); blob_reset(&c1); blob_reset(&c2); return; } - + sideBySide = !is_false(PD("sbs","1")); zV1 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v1); zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2); diffFlags = construct_diff_flags(1, sideBySide) | DIFF_HTML; style_header("Diff"); + zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; + if( *zW ){ + style_submenu_element("Show Whitespace Changes", "Show Whitespace Changes", + "%s/fdiff?v1=%T&v2=%T&sbs=%d", + g.zTop, P("v1"), P("v2"), sideBySide); + }else{ + style_submenu_element("Ignore Whitespace", "Ignore Whitespace", + "%s/fdiff?v1=%T&v2=%T&sbs=%d&w", + g.zTop, P("v1"), P("v2"), sideBySide); + } style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch", g.zTop, P("v1"), P("v2")); if( !sideBySide ){ - style_submenu_element("Side-by-side Diff", "sbsdiff", - "%s/fdiff?v1=%T&v2=%T&sbs=1", - g.zTop, P("v1"), P("v2")); + style_submenu_element("Side-by-Side Diff", "sbsdiff", + "%s/fdiff?v1=%T&v2=%T&sbs=1%s", + g.zTop, P("v1"), P("v2"), zW); }else{ style_submenu_element("Unified Diff", "udiff", - "%s/fdiff?v1=%T&v2=%T&sbs=0", - g.zTop, P("v1"), P("v2")); + "%s/fdiff?v1=%T&v2=%T&sbs=0%s", + g.zTop, P("v1"), P("v2"), zW); } if( P("smhdr")!=0 ){ @ <h2>Differences From Artifact - @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a> To - @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>.</h2> + @ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To + @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2> }else{ @ <h2>Differences From - @ Artifact %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2> - object_description(v1, 0, 0); - @ <h2>To Artifact %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2> - object_description(v2, 0, 0); + @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2> + object_description(v1, objdescFlags, 0); + @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2> + object_description(v2, objdescFlags, 0); } if( pRe ){ @ <b>Only differences that match regular expression "%h(zRe)" @ are shown.</b> } @@ -1346,17 +1503,23 @@ ** Return the uninterpreted content of an artifact. Used primarily ** to view artifacts that are images. */ void rawartifact_page(void){ int rid; + char *zUuid; const char *zMime; Blob content; rid = name_to_rid_www("name"); login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } if( rid==0 ) fossil_redirect_home(); + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){ + g.isConst = 1; + } + free(zUuid); zMime = P("m"); if( zMime==0 ){ char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename" " WHERE mlink.fid=%d" " AND filename.fnid=mlink.fnid", rid); @@ -1429,24 +1592,29 @@ ** WEBPAGE: hexdump ** URL: /hexdump?name=ARTIFACTID ** ** Show the complete content of a file identified by ARTIFACTID ** as preformatted text. +** +** Other parameters: +** +** verbose Show more detail when describing the object */ void hexdump_page(void){ int rid; Blob content; Blob downloadName; char *zUuid; + u32 objdescFlags = 0; rid = name_to_rid_www("name"); login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } if( rid==0 ) fossil_redirect_home(); if( g.perm.Admin ){ const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); - if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ + if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun", g.zTop, zUuid); }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); @@ -1458,26 +1626,54 @@ @ <h2>Artifact %s(zUuid) (%d(rid)):</h2> }else{ @ <h2>Artifact %s(zUuid):</h2> } blob_zero(&downloadName); - object_description(rid, 0, &downloadName); + if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL; + object_description(rid, objdescFlags, &downloadName); style_submenu_element("Download", "Download", "%s/raw/%T?name=%s", g.zTop, blob_str(&downloadName), zUuid); @ <hr /> content_get(rid, &content); @ <blockquote><pre> hexdump(&content); @ </pre></blockquote> style_footer(); } + +/* +** Attempt to lookup the specified check-in and file name into an rid. +*/ +int artifact_from_ci_and_filename( + const char *zCI, + const char *zFilename +){ + int cirid; + Manifest *pManifest; + ManifestFile *pFile; + + if( zCI==0 ) return 0; + if( zFilename==0 ) return 0; + cirid = name_to_rid(zCI); + pManifest = manifest_get(cirid, CFTYPE_MANIFEST, 0); + if( pManifest==0 ) return 0; + manifest_file_rewind(pManifest); + while( (pFile = manifest_file_next(pManifest,0))!=0 ){ + if( fossil_strcmp(zFilename, pFile->zName)==0 ){ + int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); + manifest_destroy(pManifest); + return rid; + } + } + return 0; +} /* ** Look for "ci" and "filename" query parameters. If found, try to ** use them to extract the record ID of an artifact for the file. */ -int artifact_from_ci_and_filename(void){ +int artifact_from_ci_and_filename_www(void){ const char *zFilename; const char *zCI; int cirid; Manifest *pManifest; ManifestFile *pFile; @@ -1505,35 +1701,62 @@ ** file. This routine appends that text to the HTTP reply with line numbering. ** ** zLn is the ?ln= parameter for the HTTP query. If there is an argument, ** then highlight that line number and scroll to it once the page loads. ** If there are two line numbers, highlight the range of lines. +** Multiple ranges can be highlighed by adding additional line numbers +** separated by a non-digit character (also not one of [-,.]). */ void output_text_with_line_numbers( const char *z, const char *zLn ){ int iStart, iEnd; /* Start and end of region to highlight */ int n = 0; /* Current line number */ - int i; /* Loop index */ + int i = 0; /* Loop index */ int iTop = 0; /* Scroll so that this line is on top of screen. */ + Stmt q; iStart = iEnd = atoi(zLn); + db_multi_exec( + "CREATE TEMP TABLE lnos(iStart INTEGER PRIMARY KEY, iEnd INTEGER)"); if( iStart>0 ){ - for(i=0; fossil_isdigit(zLn[i]); i++){} - if( zLn[i]==',' || zLn[i]=='-' || zLn[i]=='.' ){ - i++; - while( zLn[i]=='.' ){ i++; } - iEnd = atoi(&zLn[i]); - } - if( iEnd<iStart ) iEnd = iStart; + do{ + while( fossil_isdigit(zLn[i]) ) i++; + if( zLn[i]==',' || zLn[i]=='-' || zLn[i]=='.' ){ + i++; + while( zLn[i]=='.' ){ i++; } + iEnd = atoi(&zLn[i]); + while( fossil_isdigit(zLn[i]) ) i++; + } + while( fossil_isdigit(zLn[i]) ) i++; + if( iEnd<iStart ) iEnd = iStart; + db_multi_exec( + "INSERT OR REPLACE INTO lnos VALUES(%d,%d)", iStart, iEnd + ); + iStart = iEnd = atoi(&zLn[i++]); + }while( zLn[i] && iStart && iEnd ); + } + db_prepare(&q, "SELECT min(iStart), iEnd FROM lnos"); + if( db_step(&q)==SQLITE_ROW ){ + iStart = db_column_int(&q, 0); + iEnd = db_column_int(&q, 1); iTop = iStart - 15 + (iEnd-iStart)/4; if( iTop>iStart - 2 ) iTop = iStart-2; } + db_finalize(&q); @ <pre> while( z[0] ){ n++; + db_prepare(&q, + "SELECT min(iStart), max(iEnd) FROM lnos" + " WHERE iStart <= %d AND iEnd >= %d", n, n); + if( db_step(&q)==SQLITE_ROW ){ + iStart = db_column_int(&q, 0); + iEnd = db_column_int(&q, 1); + } + db_finalize(&q); for(i=0; z[i] && z[i]!='\n'; i++){} if( n==iTop ) cgi_append_content("<span id=\"topln\">", -1); if( n==iStart ){ cgi_append_content("<div class=\"selectedText\">",-1); } @@ -1541,37 +1764,43 @@ if( i>0 ){ char *zHtml = htmlize(z, i); cgi_append_content(zHtml, -1); fossil_free(zHtml); } - if( n==iStart-15 ) cgi_append_content("</span>", -1); + if( n==iTop ) cgi_append_content("</span>", -1); if( n==iEnd ) cgi_append_content("</div>", -1); else cgi_append_content("\n", 1); z += i; if( z[0]=='\n' ) z++; } if( n<iEnd ) cgi_printf("</div>"); @ </pre> - if( iStart ){ + if( db_int(0, "SELECT EXISTS(SELECT 1 FROM lnos)") ){ @ <script>gebi('topln').scrollIntoView(true);</script> } } /* +** WEBPAGE: whatis ** WEBPAGE: artifact -** URL: /artifact/ARTIFACTID +** +** URL: /artifact/SHA1HASH ** URL: /artifact?ci=CHECKIN&filename=PATH +** URL: /whatis/SHA1HASH ** ** Additional query parameters: ** ** ln - show line numbers ** ln=N - highlight line number N ** ln=M-N - highlight lines M through N inclusive +** ln=M-N+Y-Z - higllight lines M through N and Y through Z (inclusive) +** verbose - show more detail in the description ** -** Show the complete content of a file identified by ARTIFACTID -** as preformatted text. +** The /artifact page show the complete content of a file +** identified by SHA1HASH as preformatted text. The +** /whatis page shows only a description of the file. */ void artifact_page(void){ int rid = 0; Blob content; const char *zMime; @@ -1579,45 +1808,49 @@ int renderAsWiki = 0; int renderAsHtml = 0; int objType; int asText; const char *zUuid; + u32 objdescFlags = 0; + int descOnly = fossil_strcmp(g.zPath,"whatis")==0; + const char *zLn = P("ln"); if( P("ci") && P("filename") ){ - rid = artifact_from_ci_and_filename(); + rid = artifact_from_ci_and_filename_www(); } if( rid==0 ){ rid = name_to_rid_www("name"); } login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } if( rid==0 ) fossil_redirect_home(); if( g.perm.Admin ){ const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); - if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ + if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun", g.zTop, zUuid); }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); } } - style_header("Artifact Content"); + if( descOnly || P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL; + style_header("%s", descOnly ? "Artifact Description" : "Artifact Content"); zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); if( g.perm.Setup ){ @ <h2>Artifact %s(zUuid) (%d(rid)):</h2> }else{ @ <h2>Artifact %s(zUuid):</h2> } blob_zero(&downloadName); - objType = object_description(rid, 0, &downloadName); + objType = object_description(rid, objdescFlags, &downloadName); style_submenu_element("Download", "Download", "%R/raw/%T?name=%s", blob_str(&downloadName), zUuid); if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ - style_submenu_element("Checkins Using", "Checkins Using", - "%R/timeline?uf=%s&n=200",zUuid); + style_submenu_element("Check-ins Using", "Check-ins Using", + "%R/timeline?n=200&uf=%s",zUuid); } asText = P("txt")!=0; zMime = mimetype_from_name(blob_str(&downloadName)); if( zMime ){ if( fossil_strcmp(zMime, "text/html")==0 ){ @@ -1627,11 +1860,12 @@ }else{ renderAsHtml = 1; style_submenu_element("Text", "Text", "%s/artifact/%s?txt=1", g.zTop, zUuid); } - }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){ + }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 + || fossil_strcmp(zMime, "text/x-markdown")==0 ){ if( asText ){ style_submenu_element("Wiki", "Wiki", "%s/artifact/%s", g.zTop, zUuid); }else{ renderAsWiki = 1; @@ -1641,44 +1875,50 @@ } } if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){ style_submenu_element("Parsed", "Parsed", "%R/info/%s", zUuid); } - @ <hr /> - content_get(rid, &content); - if( renderAsWiki ){ - wiki_convert(&content, 0, 0); - }else if( renderAsHtml ){ - @ <iframe src="%R/raw/%T(blob_str(&downloadName))?name=%s(zUuid)" - @ width="100%%" frameborder="0" marginwidth="0" marginheight="0" - @ sandbox="allow-same-origin" - @ onload="this.height = this.contentDocument.documentElement.scrollHeight;"> - @ </iframe> + if( descOnly ){ + style_submenu_element("Content", "Content", "%R/artifact/%s", zUuid); }else{ - style_submenu_element("Hex","Hex", "%s/hexdump?name=%s", g.zTop, zUuid); - zMime = mimetype_from_content(&content); - @ <blockquote> - if( zMime==0 ){ - const char *zLn = P("ln"); - const char *z; + style_submenu_element("Line Numbers", "Line Numbers", + "%R/info/%s%s",zUuid, + ((zLn&&*zLn) ? "" : "?txt=1&ln=0")); + @ <hr /> + content_get(rid, &content); + if( renderAsWiki ){ + wiki_render_by_mimetype(&content, zMime); + }else if( renderAsHtml ){ + @ <iframe src="%R/raw/%T(blob_str(&downloadName))?name=%s(zUuid)" + @ width="100%%" frameborder="0" marginwidth="0" marginheight="0" + @ sandbox="allow-same-origin" + @ onload="this.height = this.contentDocument.documentElement.scrollHeight;"> + @ </iframe> + }else{ + style_submenu_element("Hex","Hex", "%s/hexdump?name=%s", g.zTop, zUuid); blob_to_utf8_no_bom(&content, 0); - z = blob_str(&content); - if( zLn ){ - output_text_with_line_numbers(z, zLn); - }else{ - @ <pre> - @ %h(z) - @ </pre> - } - }else if( strncmp(zMime, "image/", 6)==0 ){ - @ <img src="%R/raw/%S(zUuid)?m=%s(zMime)" /> - style_submenu_element("Image", "Image", - "%R/raw/%S?m=%s", zUuid, zMime); - }else{ - @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i> - } - @ </blockquote> + zMime = mimetype_from_content(&content); + @ <blockquote> + if( zMime==0 ){ + const char *z; + z = blob_str(&content); + if( zLn ){ + output_text_with_line_numbers(z, zLn); + }else{ + @ <pre> + @ %h(z) + @ </pre> + } + }else if( strncmp(zMime, "image/", 6)==0 ){ + @ <img src="%R/raw/%s(zUuid)?m=%s(zMime)" /> + style_submenu_element("Image", "Image", + "%R/raw/%s?m=%s", zUuid, zMime); + }else{ + @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i> + } + @ </blockquote> + } } style_footer(); } /* @@ -1695,16 +1935,16 @@ Manifest *pTktChng; int modPending; const char *zModAction; char *zTktTitle; login_check_credentials(); - if( !g.perm.RdTkt ){ login_needed(); return; } + if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; } rid = name_to_rid_www("name"); if( rid==0 ){ fossil_redirect_home(); } zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); if( g.perm.Admin ){ - if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ + if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun", g.zTop, zUuid); }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); @@ -1715,46 +1955,56 @@ zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate); memcpy(zTktName, pTktChng->zTicketUuid, UUID_SIZE+1); if( g.perm.ModTkt && (zModAction = P("modaction"))!=0 ){ if( strcmp(zModAction,"delete")==0 ){ moderation_disapprove(rid); - cgi_redirectf("%R/tktview/%s", zTktName); - /*NOTREACHED*/ + /* + ** Next, check if the ticket still exists; if not, we cannot + ** redirect to it. + */ + if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", + zTktName) ){ + cgi_redirectf("%R/tktview/%s", zTktName); + /*NOTREACHED*/ + }else{ + cgi_redirectf("%R/modreq"); + /*NOTREACHED*/ + } } if( strcmp(zModAction,"approve")==0 ){ moderation_approve(rid); } } - zTktTitle = db_table_has_column( "ticket", "title" ) + zTktTitle = db_table_has_column("repository", "ticket", "title" ) ? db_text("(No title)", "SELECT title FROM ticket WHERE tkt_uuid=%Q", zTktName) : 0; style_header("Ticket Change Details"); - style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid); + style_submenu_element("Raw", "Raw", "%R/artifact/%s", zUuid); style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName); style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName); style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName); if( P("plaintext") ){ - style_submenu_element("Formatted", "Formatted", "%R/info/%S", zUuid); + style_submenu_element("Formatted", "Formatted", "%R/info/%s", zUuid); }else{ style_submenu_element("Plaintext", "Plaintext", - "%R/info/%S?plaintext", zUuid); + "%R/info/%s?plaintext", zUuid); } @ <div class="section">Overview</div> @ <p><table class="label-value"> @ <tr><th>Artifact ID:</th> - @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a> + @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a> if( g.perm.Setup ){ @ (%d(rid)) } modPending = moderation_pending(rid); if( modPending ){ @ <span class="modpending">*** Awaiting Moderator Approval ***</span> } @ <tr><th>Ticket:</th> @ <td>%z(href("%R/tktview/%s",zTktName))%s(zTktName)</a> - if(zTktTitle){ + if( zTktTitle ){ @<br>%h(zTktTitle) } @</td></tr> @ <tr><th>Date:</th><td> hyperlink_to_date(zDate, "</td></tr>"); @@ -1797,37 +2047,49 @@ void info_page(void){ const char *zName; Blob uuid; int rid; int rc; + int nLen; zName = P("name"); if( zName==0 ) fossil_redirect_home(); - if( validate16(zName, strlen(zName)) ){ - if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){ - tktview_page(); - return; - } - if( db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'event-%q*'", zName) ){ - event_page(); - return; - } - } + nLen = strlen(zName); blob_set(&uuid, zName); + if( name_collisions(zName) ){ + cgi_set_parameter("src","info"); + ambiguous_page(); + return; + } rc = name_to_uuid(&uuid, -1, "*"); if( rc==1 ){ + if( validate16(zName, nLen) ){ + if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){ + tktview_page(); + return; + } + if( db_exists("SELECT 1 FROM tag" + " WHERE tagname GLOB 'event-%q*'", zName) ){ + event_page(); + return; + } + } style_header("No Such Object"); @ <p>No such object: %h(zName)</p> + if( nLen<4 ){ + @ <p>Object name should be no less than 4 characters. Ten or more + @ characters are recommended.</p> + } style_footer(); return; }else if( rc==2 ){ cgi_set_parameter("src","info"); ambiguous_page(); return; } zName = blob_str(&uuid); - rid = db_int(0, "SELECT rid FROM blob WHERE uuid='%s'", zName); + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName); if( rid==0 ){ style_header("Broken Link"); @ <p>No such object: %h(zName)</p> style_footer(); return; @@ -1968,11 +2230,13 @@ } @ %h(aColor[i].zCName)</label>  @ <input type="text" name="%s(zIdCustom)" @ id="%s(zIdCustom)" class="checkinUserColor" @ value="%h(stdClrFound?"":zDefaultColor)" - @ onfocus="this.form.elements['%s(zId)'][%d(nColor)].checked = true;" /> + @ onfocus="this.form.elements['%s(zId)'][%d(nColor)].checked = true;" + @ onload="this.blur();" + @ onblur="this.parentElement.style.backgroundColor = this.value ? ('#'+this.value.replace('#','')) : '';" /> @ </td> @ </tr> @ </table> } @@ -2031,19 +2295,23 @@ const char *zNewTagFlag; const char *zNewTag; const char *zNewBrFlag; const char *zNewBranch; const char *zCloseFlag; + const char *zHideFlag; int fPropagateColor; /* True if color propagates before edit */ int fNewPropagateColor; /* True if color propagates after edit */ + int fHasHidden = 0; /* True if hidden tag already set */ + int fHasClosed = 0; /* True if closed tag already set */ const char *zChngTime = 0; /* Value of chngtime= query param, if any */ char *zUuid; Blob comment; + char *zBranchName = 0; Stmt q; login_check_credentials(); - if( !g.perm.Write ){ login_needed(); return; } + if( !g.perm.Write ){ login_needed(g.anon.Write); return; } rid = name_to_typed_rid(P("r"), "ci"); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); zComment = db_text(0, "SELECT coalesce(ecomment,comment)" " FROM event WHERE objid=%d", rid); if( zComment==0 ) fossil_redirect_home(); @@ -2073,10 +2341,11 @@ zNewTagFlag = P("newtag") ? " checked" : ""; zNewTag = PDT("tagname",""); zNewBrFlag = P("newbr") ? " checked" : ""; zNewBranch = PDT("brname",""); zCloseFlag = P("close") ? " checked" : ""; + zHideFlag = P("hide") ? " checked" : ""; if( P("apply") ){ Blob ctrl; char *zNow; int nChng = 0; @@ -2123,12 +2392,16 @@ if( P(zLabel) ){ db_multi_exec("REPLACE INTO newtags VALUES(%Q,'-',NULL)", zTag); } } db_finalize(&q); + if( zHideFlag[0] ){ + db_multi_exec("REPLACE INTO newtags VALUES('hidden','*',NULL)"); + } if( zCloseFlag[0] ){ - db_multi_exec("REPLACE INTO newtags VALUES('closed','+',NULL)"); + db_multi_exec("REPLACE INTO newtags VALUES('closed','%s',NULL)", + is_a_leaf(rid)?"+":"*"); } if( zNewTagFlag[0] && zNewTag[0] ){ db_multi_exec("REPLACE INTO newtags VALUES('sym-%q','+',NULL)", zNewTag); } if( zNewBrFlag[0] && zNewBranch[0] ){ @@ -2159,26 +2432,47 @@ } db_finalize(&q); if( nChng>0 ){ int nrid; Blob cksum; - blob_appendf(&ctrl, "U %F\n", g.zLogin); + blob_appendf(&ctrl, "U %F\n", login_name()); md5sum_blob(&ctrl, &cksum); blob_appendf(&ctrl, "Z %b\n", &cksum); db_begin_transaction(); g.markPrivate = content_is_private(rid); nrid = content_put(&ctrl); - manifest_crosslink(nrid, &ctrl); + manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS); assert( blob_is_reset(&ctrl) ); db_end_transaction(0); } cgi_redirectf("ci?name=%s", zUuid); } blob_zero(&comment); blob_append(&comment, zNewComment, -1); zUuid[10] = 0; style_header("Edit Check-in [%s]", zUuid); + /* + ** chgcbn/chgbn: Handle change of (checkbox for) branch name in + ** remaining of form. + */ + @ <script> + @ function chgcbn(checked, branch){ + @ val = gebi('brname').value.trim(); + @ if( !val || !checked ) val = branch; + @ if( checked ) gebi('brname').select(); + @ gebi('hbranch').textContent = val; + @ cidbrid = document.getElementById('cbranch'); + @ if( cidbrid ) cidbrid.textContent = val; + @ } + @ function chgbn(val, branch){ + @ if( !val ) val = branch; + @ gebi('newbr').checked = (val!=branch); + @ gebi('hbranch').textContent = val; + @ cidbrid = document.getElementById('cbranch'); + @ if( cidbrid ) cidbrid.textContent = val; + @ } + @ </script> if( P("preview") ){ Blob suffix; int nTag = 0; @ <b>Preview:</b> @ <blockquote> @@ -2186,11 +2480,11 @@ if( zNewColor && zNewColor[0] ){ @ <tr><td style="background-color: %h(zNewColor);"> }else{ @ <tr><td> } - @ %!w(blob_str(&comment)) + @ %!W(blob_str(&comment)) blob_zero(&suffix); blob_appendf(&suffix, "(user: %h", zNewUser); db_prepare(&q, "SELECT substr(tagname,5) FROM tagxref, tag" " WHERE tagname GLOB 'sym-*' AND tagxref.rid=%d" " AND tagtype>1 AND tag.tagid=tagxref.tagid", @@ -2215,14 +2509,14 @@ @ </blockquote> @ <hr /> blob_reset(&suffix); } @ <p>Make changes to attributes of check-in - @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p> + @ [%z(href("%R/ci/%!S",zUuid))%s(zUuid)</a>]:</p> form_begin(0, "%R/ci_edit"); login_insert_csrf_secret(); - @ <div><input type="hidden" name="r" value="%S(zUuid)" /> + @ <div><input type="hidden" name="r" value="%s(zUuid)" /> @ <table border="0" cellspacing="10"> @ <tr><th align="right" valign="top">User:</th> @ <td valign="top"> @ <input type="text" name="u" size="20" value="%h(zNewUser)" /> @@ -2254,57 +2548,94 @@ @ <td valign="top"> @ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag) /> @ Add the following new tag name to this check-in:</label> @ <input type="text" style="width:15;" name="tagname" value="%h(zNewTag)" @ onkeyup="gebi('newtag').checked=!!this.value" /> + zBranchName = db_text(0, "SELECT value FROM tagxref, tag" + " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid" + " AND tagxref.tagid=%d", rid, TAG_BRANCH); db_prepare(&q, - "SELECT tag.tagid, tagname FROM tagxref, tag" + "SELECT tag.tagid, tagname, tagxref.value FROM tagxref, tag" " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid" " ORDER BY CASE WHEN tagname GLOB 'sym-*' THEN substr(tagname,5)" " ELSE tagname END /*sort*/", rid ); while( db_step(&q)==SQLITE_ROW ){ int tagid = db_column_int(&q, 0); const char *zTagName = db_column_text(&q, 1); + int isSpecialTag = fossil_strncmp(zTagName, "sym-", 4)!=0; char zLabel[30]; + + if( tagid == TAG_CLOSED ){ + fHasClosed = 1; + }else if( (tagid == TAG_COMMENT) || (tagid == TAG_BRANCH) ){ + continue; + }else if( tagid==TAG_HIDDEN ){ + fHasHidden = 1; + }else if( !isSpecialTag && zTagName && + fossil_strcmp(&zTagName[4], zBranchName)==0){ + continue; + } sqlite3_snprintf(sizeof(zLabel), zLabel, "c%d", tagid); @ <br /><label> if( P(zLabel) ){ @ <input type="checkbox" name="c%d(tagid)" checked="checked" /> }else{ @ <input type="checkbox" name="c%d(tagid)" /> } - if( strncmp(zTagName, "sym-", 4)==0 ){ - @ Cancel tag <b>%h(&zTagName[4])</b></label> + if( isSpecialTag ){ + @ Cancel special tag <b>%h(zTagName)</b></label> }else{ - @ Cancel special tag <b>%h(zTagName)</b></label> + @ Cancel tag <b>%h(&zTagName[4])</b></label> } } db_finalize(&q); @ </td></tr> - @ <tr><th align="right" valign="top">Branching:</th> - @ <td valign="top"> - @ <label><input id="newbr" type="checkbox" name="newbr"%s(zNewBrFlag) /> - @ Make this check-in the start of a new branch named:</label> - @ <input type="text" style="width:15;" name="brname" value="%h(zNewBranch)" - @ onkeyup="gebi('newbr').checked=!!this.value" /> - @ </td></tr> - - if( is_a_leaf(rid) - && !db_exists("SELECT 1 FROM tagxref " - " WHERE tagid=%d AND rid=%d AND tagtype>0", - TAG_CLOSED, rid) - ){ - @ <tr><th align="right" valign="top">Leaf Closure:</th> - @ <td valign="top"> - @ <label><input type="checkbox" name="close"%s(zCloseFlag) /> - @ Mark this leaf as "closed" so that it no longer appears on the - @ "leaves" page and is no longer labeled as a "<b>Leaf</b>".</label> - @ </td></tr> - } + if( !zBranchName ){ + zBranchName = db_get("main-branch", "trunk"); + } + if( !zNewBranch || !zNewBranch[0]){ + zNewBranch = zBranchName; + } + @ <tr><th align="right" valign="top">Branching:</th> + @ <td valign="top"> + @ <label><input id="newbr" type="checkbox" name="newbr"%s(zNewBrFlag) + @ onchange="chgcbn(this.checked,'%h(zBranchName)')" /> + @ Make this check-in the start of a new branch named:</label> + @ <input id="brname" type="text" style="width:15;" name="brname" + @ value="%h(zNewBranch)" + @ onkeyup="chgbn(this.value.trim(),'%h(zBranchName)')" /></td></tr> + if( !fHasHidden ){ + @ <tr><th align="right" valign="top">Branch Hiding:</th> + @ <td valign="top"> + @ <label><input type="checkbox" id="hidebr" name="hide"%s(zHideFlag) /> + @ Hide branch + @ <span style="font-weight:bold" id="hbranch">%h(zBranchName)</span> + @ from the timeline starting from this check-in</label> + @ </td></tr> + } + if( !fHasClosed ){ + if( is_a_leaf(rid) ){ + @ <tr><th align="right" valign="top">Leaf Closure:</th> + @ <td valign="top"> + @ <label><input type="checkbox" name="close"%s(zCloseFlag) /> + @ Mark this leaf as "closed" so that it no longer appears on the + @ "leaves" page and is no longer labeled as a "<b>Leaf</b>"</label> + @ </td></tr> + }else if( zBranchName ){ + @ <tr><th align="right" valign="top">Branch Closure:</th> + @ <td valign="top"> + @ <label><input type="checkbox" name="close"%s(zCloseFlag) /> + @ Mark branch + @ <span style="font-weight:bold" id="cbranch">%h(zBranchName)</span> + @ as "closed".</label> + @ </td></tr> + } + } + if( zBranchName ) fossil_free(zBranchName); @ <tr><td colspan="2"> @ <input type="submit" name="preview" value="Preview" /> @ <input type="submit" name="apply" value="Apply Changes" /> Index: src/json.c ================================================================== --- src/json.c +++ src/json.c @@ -18,11 +18,11 @@ ** ** Code for the JSON API. ** ** For notes regarding the public JSON interface, please see: ** -** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit +** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/view ** ** ** Notes for hackers... ** ** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or @@ -30,12 +30,12 @@ ** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f(). ** See the API docs for that typedef (below) for the semantics of the callbacks. ** ** */ -#include "config.h" #include "VERSION.h" +#include "config.h" #include "json.h" #include <assert.h> #include <time.h> #if INTERFACE @@ -57,11 +57,11 @@ /* ** Returns true (non-0) if fossil appears to be running in JSON mode. */ -char fossil_has_json(){ +int fossil_has_json(){ return g.json.isJsonMode && (g.isHTTP || g.json.post.o); } /* ** Placeholder /json/XXX page impl for NYI (Not Yet Implemented) @@ -384,11 +384,11 @@ ** either 0 or 1, as opposed to "0 or non-zero", so that clients can ** pass a different value as dflt. Thus they can use, e.g. -1 to know ** whether or not this function found a match (it will return -1 in ** that case). */ -char json_getenv_bool(char const * pKey, char dflt ){ +int json_getenv_bool(char const * pKey, int dflt ){ cson_value const * v = json_getenv(pKey); const cson_type_id type = v ? cson_value_type_id(v) : CSON_TYPE_UNDEF; switch(type){ case CSON_TYPE_INTEGER: case CSON_TYPE_DOUBLE: @@ -475,15 +475,15 @@ /* ** The boolean equivalent of json_find_option_cstr(). ** If the option is not found, dftl is returned. */ -char json_find_option_bool(char const * zKey, - char const * zCLILong, - char const * zCLIShort, - char dflt ){ - char rc = -1; +int json_find_option_bool(char const * zKey, + char const * zCLILong, + char const * zCLIShort, + char dflt ){ + int rc = -1; if(!g.isHTTP){ if(NULL != find_option(zCLILong ? zCLILong : zKey, zCLIShort, 0)){ rc = 1; } @@ -987,11 +987,11 @@ if(!jfile || !*jfile){ break; } inFile = (0==strcmp("-",jfile)) ? stdin - : fopen(jfile,"rb"); + : fossil_fopen(jfile,"rb"); if(!inFile){ g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED; fossil_fatal("Could not open JSON file [%s].",jfile) /* Does not return. */ ; @@ -1275,15 +1275,15 @@ INT(g, xlinkClusterOnly); INT(g, fTimeFormat); INT(g, markPrivate); INT(g, clockSkewSeen); INT(g, isHTTP); - INT(g, urlIsFile); - INT(g, urlIsHttps); - INT(g, urlIsSsh); - INT(g, urlPort); - INT(g, urlDfltPort); + INT(g.url, isFile); + INT(g.url, isHttps); + INT(g.url, isSsh); + INT(g.url, port); + INT(g.url, dfltPort); INT(g, useLocalauth); INT(g, noPswd); INT(g, userUid); INT(g, rcvid); INT(g, okCsrf); @@ -1299,19 +1299,19 @@ CSTR(g, zExtra); CSTR(g, zBaseURL); CSTR(g, zTop); CSTR(g, zContentType); CSTR(g, zErrMsg); - CSTR(g, urlName); - CSTR(g, urlHostname); - CSTR(g, urlProtocol); - CSTR(g, urlPath); - CSTR(g, urlUser); - CSTR(g, urlPasswd); - CSTR(g, urlCanonical); - CSTR(g, urlProxyAuth); - CSTR(g, urlFossil); + CSTR(g.url, name); + CSTR(g.url, hostname); + CSTR(g.url, protocol); + CSTR(g.url, path); + CSTR(g.url, user); + CSTR(g.url, passwd); + CSTR(g.url, canonical); + CSTR(g.url, proxyAuth); + CSTR(g.url, fossil); CSTR(g, zLogin); CSTR(g, zSSLIdentity); CSTR(g, zIpAddr); CSTR(g, zNonce); CSTR(g, zCsrfToken); @@ -1665,11 +1665,11 @@ cson_value * json_sql_to_array_of_obj(Blob * pSql, cson_array * pTgt, char resetBlob){ Stmt q = empty_Stmt; cson_value * pay = NULL; assert( blob_size(pSql) > 0 ); - db_prepare(&q, "%s", blob_str(pSql)); + db_prepare(&q, "%s", blob_str(pSql) /*safe-for-%s*/); if(resetBlob){ blob_reset(pSql); } pay = json_stmt_to_array_of_obj(&q, pTgt); db_finalize(&q); @@ -1980,19 +1980,19 @@ jv2 = cson_value_new_object(); jo2 = cson_value_get_object(jv2); cson_object_set(jo, "sqlite", jv2); sqlite3_snprintf(BufLen, zBuf, "%.19s [%.10s] (%s)", - SQLITE_SOURCE_ID, &SQLITE_SOURCE_ID[20], SQLITE_VERSION); + sqlite3_sourceid(), &sqlite3_sourceid()[20], sqlite3_libversion()); SETBUF(jo2, "version"); zDb = db_name("repository"); - cson_object_set(jo2, "pageCount", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_count", zDb))); - cson_object_set(jo2, "pageSize", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_size", zDb))); - cson_object_set(jo2, "freeList", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.freelist_count", zDb))); - sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.encoding", zDb)); + cson_object_set(jo2, "pageCount", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA \"%w\".page_count", zDb))); + cson_object_set(jo2, "pageSize", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA \"%w\".page_size", zDb))); + cson_object_set(jo2, "freeList", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA \"%w\".freelist_count", zDb))); + sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA \"%w\".encoding", zDb)); SETBUF(jo2, "encoding"); - sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.journal_mode", zDb)); + sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA \"%w\".journal_mode", zDb)); cson_object_set(jo2, "journalMode", *zBuf ? cson_value_new_string(zBuf, strlen(zBuf)) : cson_value_null()); return jv; #undef SETBUF } @@ -2016,11 +2016,11 @@ for( ; zPages->name; ++zPages, ++i ){ if(filterByMode){ if(g.isHTTP && zPages->runMode < 0) continue; else if(zPages->runMode > 0) continue; } - blob_appendf(pOut, zPages->name, -1); + blob_append(pOut, zPages->name, -1); if((zPages+1)->name){ blob_append(pOut, ", ",2); } } return i; Index: src/json_artifact.c ================================================================== --- src/json_artifact.c +++ src/json_artifact.c @@ -79,11 +79,11 @@ return cson_array_value(pParents); } /* ** Generates an artifact Object for the given rid, -** which must refer to a Checkin. +** which must refer to a Check-in. ** ** Returned value is NULL or an Object owned by the caller. */ cson_value * json_artifact_for_ci( int rid, char showFiles ){ cson_value * v = NULL; @@ -207,15 +207,15 @@ manifest_destroy(pTktChng); return cson_object_value(pay); } /* -** Sub-impl of /json/artifact for checkins. +** Sub-impl of /json/artifact for check-ins. */ static cson_value * json_artifact_ci( cson_object * zParent, int rid ){ if(!g.perm.Read){ - json_set_err( FSL_JSON_E_DENIED, "Viewing checkins requires 'o' privileges." ); + json_set_err( FSL_JSON_E_DENIED, "Viewing check-ins requires 'o' privileges." ); return NULL; }else{ cson_value * artV = json_artifact_for_ci(rid, 1); cson_object * art = cson_value_get_object(artV); if(art){ @@ -245,29 +245,29 @@ ** If the "format" (CLI: -f) flag is set function returns the same as ** json_wiki_get_content_format_flag(), else it returns true (non-0) ** if either the includeContent (HTTP) or -content|-c boolean flags ** (CLI) are set. */ -static char json_artifact_get_content_format_flag(){ +static int json_artifact_get_content_format_flag(){ enum { MagicValue = -9 }; - char contentFormat = json_wiki_get_content_format_flag(MagicValue); + int contentFormat = json_wiki_get_content_format_flag(MagicValue); if(MagicValue == contentFormat){ contentFormat = json_find_option_bool("includeContent","content","c",0) /* deprecated */ ? -1 : 0; } return contentFormat; } -extern char json_wiki_get_content_format_flag( char defaultValue ) /* json_wiki.c */; +extern int json_wiki_get_content_format_flag( int defaultValue ) /* json_wiki.c */; cson_value * json_artifact_wiki(cson_object * zParent, int rid){ if( ! g.perm.RdWiki ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'j' privileges."); return NULL; }else{ enum { MagicValue = -9 }; - char const contentFormat = json_artifact_get_content_format_flag(); + int const contentFormat = json_artifact_get_content_format_flag(); return json_get_wiki_page_by_rid(rid, contentFormat); } } /* @@ -289,11 +289,11 @@ cson_value * json_artifact_file(cson_object * zParent, int rid){ cson_object * pay = NULL; Stmt q = empty_Stmt; cson_array * checkin_arr = NULL; - char contentFormat; + int contentFormat; i64 contentSize = -1; char * parentUuid; if( ! g.perm.Read ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' privileges."); @@ -346,20 +346,20 @@ if(parentUuid){ cson_object_set( zParent, "parent", json_new_string(parentUuid) ); fossil_free(parentUuid); } - /* Find checkins associated with this file... */ + /* Find check-ins associated with this file... */ db_prepare(&q, "SELECT filename.name AS name, " " (mlink.pid==0) AS isNew," " (mlink.fid==0) AS isDel," " cast(strftime('%%s',event.mtime) as int) AS timestamp," " coalesce(event.ecomment,event.comment) as comment," " coalesce(event.euser,event.user) as user," #if 0 - " a.size AS size," /* same for all checkins. */ + " a.size AS size," /* same for all check-ins. */ #endif " b.uuid as checkin, " #if 0 " mlink.mperm as mperm," #endif @@ -373,11 +373,11 @@ " AND b.rid=mlink.mid" " AND mlink.fid=%d" " ORDER BY filename.name, event.mtime", TAG_BRANCH, rid ); - /* TODO: add a "state" flag for the file in each checkin, + /* TODO: add a "state" flag for the file in each check-in, e.g. "modified", "new", "deleted". */ checkin_arr = cson_new_array(); cson_object_set(pay, "checkins", cson_array_value(checkin_arr)); while( (SQLITE_ROW==db_step(&q) ) ){ Index: src/json_branch.c ================================================================== --- src/json_branch.c +++ src/json_branch.c @@ -66,11 +66,11 @@ cson_value * payV; cson_object * pay; cson_value * listV; cson_array * list; char const * range = NULL; - int which = 0; + int branchListFlags = BRL_OPEN_ONLY; char * sawConversionError = NULL; Stmt q; if( !g.perm.Read ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions."); @@ -102,19 +102,19 @@ } /* Normalize range values... */ switch(*range){ case 'c': range = "closed"; - which = -1; + branchListFlags = BRL_CLOSED_ONLY; break; case 'a': range = "all"; - which = 1; + branchListFlags = BRL_BOTH; break; default: range = "open"; - which = 0; + branchListFlags = BRL_OPEN_ONLY; break; }; cson_object_set(pay,"range",json_new_string(range)); if( g.localOpen ){ /* add "current" property (branch name). */ @@ -128,11 +128,11 @@ cson_object_set(pay,"current",json_new_string(zCurrent)); } } - branch_prepare_list_query(&q, which); + branch_prepare_list_query(&q, branchListFlags); cson_object_set(pay,"branches",listV); while((SQLITE_ROW==db_step(&q))){ cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0); if(v){ cson_array_append(list,v); @@ -291,12 +291,12 @@ brid = content_put(&branch); if( brid==0 ){ fossil_fatal("Problem committing manifest: %s", g.zErrMsg); } db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid); - if( manifest_crosslink(brid, &branch)==0 ){ - fossil_fatal("unable to install new manifest"); + if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){ + fossil_fatal("%s\n", g.zErrMsg); } assert( blob_is_reset(&branch) ); content_deltify(rootid, brid, 0); if( zNewRid ){ *zNewRid = brid; Index: src/json_config.c ================================================================== --- src/json_config.c +++ src/json_config.c @@ -53,28 +53,39 @@ */ static const struct JsonConfigProperty { char const * name; int groupMask; } JsonConfigProperties[] = { -{ "css", CONFIGSET_SKIN }, +{ "css", CONFIGSET_CSS }, { "header", CONFIGSET_SKIN }, { "footer", CONFIGSET_SKIN }, -{ "index-page", CONFIGSET_SKIN }, +{ "details", CONFIGSET_SKIN }, +{ "logo-mimetype", CONFIGSET_SKIN }, +{ "logo-image", CONFIGSET_SKIN }, +{ "background-mimetype", CONFIGSET_SKIN }, +{ "background-image", CONFIGSET_SKIN }, { "timeline-block-markup", CONFIGSET_SKIN }, { "timeline-max-comment", CONFIGSET_SKIN }, +{ "timeline-plaintext", CONFIGSET_SKIN }, +{ "adunit", CONFIGSET_SKIN }, +{ "adunit-omit-if-admin", CONFIGSET_SKIN }, +{ "adunit-omit-if-user", CONFIGSET_SKIN }, { "project-name", CONFIGSET_PROJ }, +{ "short-project-name", CONFIGSET_PROJ }, { "project-description", CONFIGSET_PROJ }, +{ "index-page", CONFIGSET_PROJ }, { "manifest", CONFIGSET_PROJ }, { "binary-glob", CONFIGSET_PROJ }, { "clean-glob", CONFIGSET_PROJ }, -{ "encoding-glob", CONFIGSET_PROJ }, { "ignore-glob", CONFIGSET_PROJ }, { "keep-glob", CONFIGSET_PROJ }, { "crnl-glob", CONFIGSET_PROJ }, +{ "encoding-glob", CONFIGSET_PROJ }, { "empty-dirs", CONFIGSET_PROJ }, { "allow-symlinks", CONFIGSET_PROJ }, +{ "dotfiles", CONFIGSET_PROJ }, { "ticket-table", CONFIGSET_TKT }, { "ticket-common", CONFIGSET_TKT }, { "ticket-change", CONFIGSET_TKT }, { "ticket-newpage", CONFIGSET_TKT }, @@ -113,11 +124,11 @@ if(0==(strcmp("all", zName))){ confMask = CONFIGSET_ALL; }else if(0==(strcmp("project", zName))){ confMask |= CONFIGSET_PROJ; }else if(0==(strcmp("skin", zName))){ - confMask |= CONFIGSET_SKIN; + confMask |= (CONFIGSET_CSS|CONFIGSET_SKIN); }else if(0==(strcmp("ticket", zName))){ confMask |= CONFIGSET_TKT; }else if(0==(strcmp("skin-backup", zName))){ optSkinBackups = 1; }else{ @@ -140,11 +151,11 @@ for( i = 0; prop->name; ++prop ){ if(prop->groupMask & confMask){ if( i++ ){ blob_append(&sql,",",1); } - blob_appendf(&sql, "%Q", prop->name); + blob_append_sql(&sql, "%Q", prop->name); } } blob_append(&sql,") ", -1); } @@ -151,11 +162,11 @@ if( optSkinBackups ){ blob_append(&sql, " OR name GLOB 'skin:*'", -1); } blob_append(&sql," ORDER BY name", -1); - db_prepare(&q, blob_str(&sql)); + db_prepare(&q, "%s", blob_sql_text(&sql)); blob_reset(&sql); pay = cson_new_object(); while( (SQLITE_ROW==db_step(&q)) ){ cson_object_set(pay, db_column_text(&q,0), Index: src/json_detail.h ================================================================== --- src/json_detail.h +++ src/json_detail.h @@ -257,11 +257,11 @@ ** ** Whether or not we need to take args from CLI or POST data makes a ** difference in argument/parameter handling in many JSON routines, ** and thus this distinction. */ -char fossil_has_json(); +int fossil_has_json(); enum json_get_changed_files_flags { json_get_changed_files_ELIDE_PARENT = 1 << 0 }; Index: src/json_dir.c ================================================================== --- src/json_dir.c +++ src/json_dir.c @@ -82,11 +82,11 @@ pM = manifest_get_by_name(zCI, &rid); if( pM ){ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); }else{ json_set_err(FSL_JSON_E_UNRESOLVED_UUID, - "Checkin name [%s] is unresolved.", + "Check-in name [%s] is unresolved.", zCI); return NULL; } } Index: src/json_finfo.c ================================================================== --- src/json_finfo.c +++ src/json_finfo.c @@ -62,11 +62,11 @@ zBefore = json_find_option_cstr("before",NULL,"b"); zAfter = json_find_option_cstr("after",NULL,"a"); limit = json_find_option_int("limit",NULL,"n", -1); zCheckin = json_find_option_cstr("checkin",NULL,"ci"); - blob_appendf(&sql, + blob_append_sql(&sql, /*0*/ "SELECT b.uuid," /*1*/ " ci.uuid," /*2*/ " (SELECT uuid FROM blob WHERE rid=mlink.fid)," /* Current file uuid */ /*3*/ " cast(strftime('%%s',event.mtime) AS INTEGER)," /*4*/ " coalesce(event.euser, event.user)," @@ -74,11 +74,11 @@ /*6*/ " (SELECT uuid FROM blob WHERE rid=mlink.pid)," /* Parent file uuid */ /*7*/ " event.bgcolor," /*8*/ " b.size," /*9*/ " (mlink.pid==0) AS isNew," /*10*/ " (mlink.fid==0) AS isDel" - " FROM mlink, blob b, event, blob ci, filename" + " FROM mlink, blob b, event, blob ci, filename" " WHERE filename.name=%Q" " AND mlink.fnid=filename.fnid" " AND b.rid=mlink.fid" " AND event.objid=mlink.mid" " AND event.objid=ci.rid", @@ -89,29 +89,28 @@ char * zU = NULL; int rc = name_to_uuid2( zCheckin, "ci", &zU ); /*printf("zCheckin=[%s], zU=[%s]", zCheckin, zU);*/ if(rc<=0){ json_set_err((rc<0) ? FSL_JSON_E_AMBIGUOUS_UUID : FSL_JSON_E_RESOURCE_NOT_FOUND, - "Checkin UUID %s.", (rc<0) ? "is ambiguous" : "not found"); + "Check-in UUID %s.", (rc<0) ? "is ambiguous" : "not found"); blob_reset(&sql); return NULL; } - blob_appendf(&sql, " AND ci.uuid='%q'", zU); + blob_append_sql(&sql, " AND ci.uuid='%q'", zU); free(zU); }else{ if( zAfter && *zAfter ){ - blob_appendf(&sql, " AND event.mtime>=julianday('%q')", zAfter); + blob_append_sql(&sql, " AND event.mtime>=julianday('%q')", zAfter); sort = 1; }else if( zBefore && *zBefore ){ - blob_appendf(&sql, " AND event.mtime<=julianday('%q')", zBefore); + blob_append_sql(&sql, " AND event.mtime<=julianday('%q')", zBefore); } } - blob_appendf(&sql," ORDER BY event.mtime %s /*sort*/", (sort>0?"ASC":"DESC")); + blob_append_sql(&sql," ORDER BY event.mtime %s /*sort*/", (sort>0?"ASC":"DESC")); /*printf("SQL=\n%s\n",blob_str(&sql));*/ - db_prepare(&q, "%s", blob_str(&sql)/*extra %s to avoid double-expanding - SQL escapes*/); + db_prepare(&q, "%s", blob_sql_text(&sql)); blob_reset(&sql); pay = cson_new_object(); cson_object_set(pay, "name", json_new_string(zFilename)); if( limit > 0 ){ Index: src/json_query.c ================================================================== --- src/json_query.c +++ src/json_query.c @@ -64,11 +64,11 @@ return NULL; } zFmt = json_find_option_cstr2("format",NULL,"f",3); if(!zFmt) zFmt = "o"; - db_prepare(&q,"%s", zSql); + db_prepare(&q,"%s", zSql/*safe-for-%s*/); if( 0 == sqlite3_column_count( q.pStmt ) ){ json_set_err(FSL_JSON_E_USAGE, "Input query has no result columns. " "Only SELECT-like queries are supported."); db_finalize(&q); Index: src/json_report.c ================================================================== --- src/json_report.c +++ src/json_report.c @@ -204,11 +204,11 @@ /* Copy over report's SQL...*/ blob_append(&sql, db_column_text(&q,0), -1); zTitle = mprintf("%s", db_column_text(&q,1)); db_finalize(&q); - db_prepare(&q, "%s", blob_str(&sql)); + db_prepare(&q, "%s", blob_sql_text(&sql)); /** Build the response... */ pay = cson_new_object(); cson_object_set(pay, "report", json_new_int(nReport)); Index: src/json_status.c ================================================================== --- src/json_status.c +++ src/json_status.c @@ -69,11 +69,11 @@ zTmp = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); cson_object_set(tmpO, "uuid", json_new_string(zTmp) ); free(zTmp); cson_object_set( tmpO, "tags", json_tags_for_checkin_rid(vid, 0) ); - + /* FIXME: optimize the datetime/timestamp queries into 1 query. */ zTmp = db_text(0, "SELECT datetime(mtime) || " "' UTC' FROM event WHERE objid=%d", vid); cson_object_set(tmpO, "datetime", json_new_string(zTmp)); @@ -115,11 +115,11 @@ zStatus = "new" /* maintenance reminder: MUST come BEFORE the isChnged checks. */; }else if( isRenamed ){ zStatus = "renamed"; }else if( !file_wd_isfile_or_link(zFullName) ){ - if( file_access(zFullName, 0)==0 ){ + if( file_access(zFullName, F_OK)==0 ){ zStatus = "notAFile"; ++nErr; }else{ zStatus = "missing"; ++nErr; @@ -137,11 +137,11 @@ zStatus = "conflict"; }else{ zStatus = "edited"; } } - + oFile = cson_new_object(); cson_array_append( aFiles, cson_object_value(oFile) ); /* optimization potential: move these keys into cson_strings to take advantage of refcounting. */ cson_object_set( oFile, "name", json_new_string( zPathname ) ); Index: src/json_tag.c ================================================================== --- src/json_tag.c +++ src/json_tag.c @@ -301,11 +301,11 @@ " SELECT rid FROM tagxref" " WHERE tagtype>0 AND tagid=%d" " )" " ORDER BY event.mtime DESC" "%s LIMIT %d", - zSqlBase, zType, tagid, + zSqlBase /*safe-for-%s*/, zType, tagid, (limit>0)?"":"--", limit ); listV = json_stmt_to_array_of_obj(&q, NULL); db_finalize(&q); } @@ -353,11 +353,11 @@ payV = cson_value_new_object(); pay = cson_value_get_object(payV); cson_object_set(pay, "raw", cson_value_new_bool(fRaw) ); if( zCheckin ){ /** - Tags for a specific checkin. Output format: + Tags for a specific check-in. Output format: RAW mode: { "sym-tagname": (value || null), @@ -374,11 +374,11 @@ cson_value * objV = NULL; cson_object * obj = NULL; int const rid = name_to_rid(zCheckin); if(0==rid){ json_set_err(FSL_JSON_E_UNRESOLVED_UUID, - "Could not find artifact for checkin [%s].", + "Could not find artifact for check-in [%s].", zCheckin); goto error; } cson_object_set(pay, "checkin", json_new_string(zCheckin)); db_prepare(&q, @@ -442,11 +442,11 @@ if(!fTicket){ blob_append(&sql, " AND tagname NOT GLOB('tkt-*') ", -1); } blob_append(&sql, " ORDER BY tagname", -1); - db_prepare(&q, blob_buffer(&sql)); + db_prepare(&q, "%s", blob_sql_text(&sql)); blob_reset(&sql); cson_object_set(pay, "includeTickets", cson_value_new_bool(fTicket) ); while( SQLITE_ROW == db_step(&q) ){ const char *zName = db_column_text(&q, 0); if(NULL==arV){ Index: src/json_timeline.c ================================================================== --- src/json_timeline.c +++ src/json_timeline.c @@ -66,11 +66,11 @@ /* ** Create a temporary table suitable for storing timeline data. */ static void json_timeline_temp_table(void){ /* Field order MUST match that from json_timeline_query()!!! */ - static const char zSql[] = + static const char zSql[] = @ CREATE TEMP TABLE IF NOT EXISTS json_timeline( @ sortId INTEGER PRIMARY KEY, @ rid INTEGER, @ uuid TEXT, @ mtime INTEGER, @@ -83,25 +83,26 @@ @ tags TEXT, @ tagId INTEGER, @ brief TEXT @ ) ; - db_multi_exec(zSql); + db_multi_exec("%s", zSql /*safe-for-%s*/); } /* ** Return a pointer to a constant string that forms the basis -** for a timeline query for the JSON interface. +** for a timeline query for the JSON interface. It MUST NOT +** be used in a formatted string argument. */ char const * json_timeline_query(void){ /* Field order MUST match that from json_timeline_temp_table()!!! */ static const char zBaseSql[] = @ SELECT @ NULL, @ blob.rid, @ uuid, - @ CAST(strftime('%%s',event.mtime) AS INTEGER), + @ CAST(strftime('%s',event.mtime) AS INTEGER), @ datetime(event.mtime), @ coalesce(ecomment, comment), @ coalesce(euser, user), @ blob.rid IN leaf, @ bgcolor, @@ -109,11 +110,11 @@ @ (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0) as tags, @ tagid as tagId, @ brief as brief - @ FROM event JOIN blob + @ FROM event JOIN blob @ WHERE blob.rid=event.objid ; return zBaseSql; } @@ -145,10 +146,11 @@ static char json_timeline_add_tag_branch_clause(Blob *pSql, cson_object * pPayload){ char const * zTag = NULL; char const * zBranch = NULL; char const * zMiOnly = NULL; + char const * zUnhide = NULL; int tagid = 0; if(! g.perm.Read ){ return 0; } zTag = json_find_option_cstr("tag",NULL,NULL); @@ -158,10 +160,11 @@ return 0; } zTag = zBranch; zMiOnly = json_find_option_cstr("mionly",NULL,NULL); } + zUnhide = json_find_option_cstr("unhide",NULL,NULL); tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag); if(tagid<=0){ return -1; } @@ -171,21 +174,39 @@ blob_appendf(pSql, " AND (" " EXISTS(SELECT 1 FROM tagxref" " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid); + if(!zUnhide){ + blob_appendf(pSql, + " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=blob.rid" + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", + TAG_HIDDEN); + } if(zBranch){ /* from "r" flag code in page_timeline().*/ blob_appendf(pSql, " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid" " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)", tagid); + if( !zUnhide ){ + blob_appendf(pSql, + " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid" + " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)", + TAG_HIDDEN); + } if( zMiOnly==0 ){ blob_appendf(pSql, " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid" " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)", tagid); + if( !zUnhide ){ + blob_appendf(pSql, + " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid" + " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)", + TAG_HIDDEN); + } } } blob_append(pSql," ) ",3); return 1; } @@ -296,11 +317,11 @@ */ cson_value * json_get_changed_files(int rid, int flags){ cson_value * rowsV = NULL; cson_array * rows = NULL; Stmt q = empty_Stmt; - db_prepare(&q, + db_prepare(&q, "SELECT (pid==0) AS isnew," " (fid==0) AS isdel," " (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name," " blob.uuid as uuid," " (SELECT uuid FROM blob WHERE rid=pid) as parent," @@ -364,21 +385,21 @@ " bgcolor as bgColor" " FROM event JOIN blob" " WHERE blob.rid=event.objid", -1); - blob_appendf(&sql, + blob_append_sql(&sql, " AND event.type='ci'" " AND blob.rid IN (SELECT rid FROM tagxref" " WHERE tagtype>0 AND tagid=%d AND srcid!=0)" " ORDER BY event.mtime DESC", TAG_BRANCH); limit = json_timeline_limit(20); if(limit>0){ - blob_appendf(&sql," LIMIT %d ",limit); + blob_append_sql(&sql," LIMIT %d ",limit); } - db_prepare(&q,"%s", blob_str(&sql)); + db_prepare(&q,"%s", blob_sql_text(&sql)); blob_reset(&sql); pay = json_stmt_to_array_of_obj(&q, NULL); db_finalize(&q); assert(NULL != pay); if(pay){ @@ -436,11 +457,11 @@ Blob sql = empty_blob; if( !g.perm.Hyperlink ){ /* Reminder to self: HTML impl requires 'o' (Read) rights. */ - json_set_err( FSL_JSON_E_DENIED, "Checkin timeline requires 'h' access." ); + json_set_err( FSL_JSON_E_DENIED, "Check-in timeline requires 'h' access." ); return NULL; } verboseFlag = json_find_option_bool("verbose",NULL,"v",0); if( !verboseFlag ){ verboseFlag = json_find_option_bool("files",NULL,"f",0); @@ -462,11 +483,11 @@ #if 0 /* only for testing! */ tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); SET("timelineSql"); #endif - db_multi_exec(blob_buffer(&sql)); + db_multi_exec("%s", blob_buffer(&sql)/*safe-for-%s*/); blob_reset(&sql); db_prepare(&q, "SELECT " " rid AS rid" " FROM json_timeline" " ORDER BY rowid"); @@ -524,14 +545,13 @@ goto error; } #if 0 /* only for testing! */ - tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); - SET("timelineSql"); + cson_object_set(pay, "timelineSql", cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)))); #endif - db_multi_exec(blob_buffer(&sql)); + db_multi_exec("%s", blob_buffer(&sql) /*safe-for-%s*/); blob_reset(&sql); db_prepare(&q, "SELECT" " uuid AS uuid," " mtime AS timestamp," #if 0 @@ -545,12 +565,11 @@ " tags AS tags," /*FIXME: split this into a JSON array*/ " tagId AS tagId," #endif " FROM json_timeline" - " ORDER BY rowid", - -1); + " ORDER BY rowid"); list = cson_new_array(); json_stmt_to_array_of_obj(&q, list); cson_object_set(pay, "timeline", cson_array_value(list)); goto ok; error: @@ -587,11 +606,11 @@ if(check){ json_set_err(check, "Query initialization failed."); goto error; } - db_multi_exec(blob_buffer(&sql)); + db_multi_exec("%s", blob_buffer(&sql) /*safe-for-%s*/); #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ json_set_err((cson_rc.AllocError==check) \ ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN, \ "Object property insertion failed."); \ goto error;\ @@ -618,12 +637,11 @@ " user AS user," " eventType AS eventType," " comment AS comment," " brief AS briefComment" " FROM json_timeline" - " ORDER BY rowid", - -1); + " ORDER BY rowid"); listV = cson_value_new_array(); list = cson_value_get_array(listV); tmp = listV; SET("timeline"); while( (SQLITE_ROW == db_step(&q) )){ Index: src/json_user.c ================================================================== --- src/json_user.c +++ src/json_user.c @@ -286,11 +286,11 @@ /* reminders: 1) does not allocate. 2) we do this because changing a name invalidates any login token because the old name is part of the token hash. */; - blob_appendf(&sql, ", login=%Q", zNameNew); + blob_append_sql(&sql, ", login=%Q", zNameNew); ++gotFields; } } if( zCap && *zCap ){ @@ -298,11 +298,11 @@ /* we "could" arguably silently ignore cap in this case. */ json_set_err(FSL_JSON_E_DENIED, "Changing capabilities requires 'a' or 's' privileges."); goto error; } - blob_appendf(&sql, ", cap=%Q", zCap); + blob_append_sql(&sql, ", cap=%Q", zCap); ++gotFields; } if( zPW && *zPW ){ if(!g.perm.Admin && !g.perm.Setup && !g.perm.Password){ @@ -314,24 +314,24 @@ #define TRY_LOGIN_GROUP 0 /* login group support is not yet implemented. */ #if !TRY_LOGIN_GROUP char * zPWHash = NULL; ++gotFields; zPWHash = sha1_shared_secret(zPW, zNameNew ? zNameNew : zName, NULL); - blob_appendf(&sql, ", pw=%Q", zPWHash); + blob_append_sql(&sql, ", pw=%Q", zPWHash); free(zPWHash); #else ++gotFields; - blob_appendf(&sql, ", pw=coalesce(shared_secret(%Q,%Q," + blob_append_sql(&sql, ", pw=coalesce(shared_secret(%Q,%Q," "(SELECT value FROM config WHERE name='project-code')))", zPW, zNameNew ? zNameNew : zName); /* shared_secret() func is undefined? */ #endif } } if( zInfo ){ - blob_appendf(&sql, ", info=%Q", zInfo); + blob_append_sql(&sql, ", info=%Q", zInfo); ++gotFields; } if((g.perm.Admin || g.perm.Setup) && forceLogout && cson_value_get_bool(forceLogout)){ @@ -344,26 +344,26 @@ "Required user data are missing."); goto error; } assert(uid>0); #if !TRY_LOGIN_GROUP - blob_appendf(&sql, " WHERE uid=%d", uid); + blob_append_sql(&sql, " WHERE uid=%d", uid); #else /* need name for login group support :/ */ - blob_appendf(&sql, " WHERE login=%Q", zName); + blob_append_sql(&sql, " WHERE login=%Q", zName); #endif #if 0 puts(blob_str(&sql)); cson_output_FILE( cson_object_value(pUser), stdout, NULL ); #endif - db_prepare(&q, "%s", blob_str(&sql)); + db_prepare(&q, "%s", blob_sql_text(&sql)); db_exec(&q); db_finalize(&q); #if TRY_LOGIN_GROUP if( zPW || cson_value_get_bool(forceLogout) ){ Blob groupSql = empty_blob; char * zErr = NULL; - blob_appendf(&groupSql, + blob_append_sql(&groupSql, "INSERT INTO user(login)" " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);", zName, zName ); blob_append(&groupSql, blob_str(&sql), blob_size(&sql)); Index: src/json_wiki.c ================================================================== --- src/json_wiki.c +++ src/json_wiki.c @@ -80,11 +80,11 @@ ** the page. ** ** The returned value, if not NULL, is-a JSON Object owned by the ** caller. If it returns NULL then it may set g.json's error state. */ -cson_value * json_get_wiki_page_by_rid(int rid, char contentFormat){ +cson_value * json_get_wiki_page_by_rid(int rid, int contentFormat){ Manifest * pWiki = NULL; if( NULL == (pWiki = manifest_get(rid, CFTYPE_WIKI, 0)) ){ json_set_err( FSL_JSON_E_UNKNOWN, "Error reading wiki page from manifest (rid=%d).", rid ); @@ -145,11 +145,11 @@ /* ** Searches for the latest version of a wiki page with the given ** name. If found it behaves like json_get_wiki_page_by_rid(theRid, ** contentFormat), else it returns NULL. */ -cson_value * json_get_wiki_page_by_name(char const * zPageName, char contentFormat){ +cson_value * json_get_wiki_page_by_name(char const * zPageName, int contentFormat){ int rid; rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x, blob b" " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q' " " AND b.rid=x.rid" @@ -175,12 +175,12 @@ ** [r]aw = -1 ** ** The return value is intended for use with ** json_get_wiki_page_by_rid() and friends. */ -char json_wiki_get_content_format_flag( char defaultValue ){ - char contentFormat = defaultValue; +int json_wiki_get_content_format_flag( int defaultValue ){ + int contentFormat = defaultValue; char const * zFormat = json_find_option_cstr("format",NULL,"f"); if( !zFormat || !*zFormat ){ return contentFormat; } else if('r'==*zFormat){ @@ -203,11 +203,11 @@ ** json_get_wiki_page_by_name() will be returned (owned by the ** caller). On error g.json's error state is set and NULL is returned. */ static cson_value * json_wiki_get_by_name_or_symname(char const * zPageName, char const * zSymname, - char contentFormat ){ + int contentFormat ){ if(!zSymname || !*zSymname){ return json_get_wiki_page_by_name(zPageName, contentFormat); }else{ int rid = symbolic_name_to_rid( zSymname ? zSymname : zPageName, "w" ); if(rid<0){ @@ -229,11 +229,11 @@ ** */ static cson_value * json_wiki_get(){ char const * zPageName; char const * zSymName = NULL; - char contentFormat = -1; + int contentFormat = -1; if( !g.perm.RdWiki && !g.perm.Read ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' or 'j' access."); return NULL; } @@ -308,10 +308,11 @@ char const * zPageName; /* cstr form of page name */ cson_value * contentV; /* passed-in content */ cson_value * emptyContent = NULL; /* placeholder for empty content. */ cson_value * payV = NULL; /* payload/return value */ cson_string const * jstr = NULL; /* temp for cson_value-to-cson_string conversions. */ + char const * zMimeType = 0; unsigned int contentLen = 0; int rid; if( (createMode && !g.perm.NewWiki) || (!createMode && !g.perm.WrWiki)){ json_set_err(FSL_JSON_E_DENIED, @@ -371,11 +372,14 @@ jstr = cson_value_get_string(contentV); contentLen = (int)cson_string_length_bytes(jstr); if(contentLen){ blob_append(&content, cson_string_cstr(jstr),contentLen); } - wiki_cmd_commit(zPageName, 0==rid, &content); + + zMimeType = json_find_option_cstr("mimetype","mimetype","M"); + + wiki_cmd_commit(zPageName, 0==rid, &content, zMimeType, 0); blob_reset(&content); /* Our return value here has a race condition: if this operation is called concurrently for the same wiki page via two requests, payV could reflect the results of the other save operation. @@ -440,22 +444,21 @@ " substr(tagname,6) as name" " FROM tag WHERE tagname GLOB 'wiki-*'", -1); zGlob = json_find_option_cstr("glob",NULL,"g"); if(zGlob && *zGlob){ - blob_appendf(&sql," AND name %s GLOB %Q", - fInvert ? "NOT" : "", zGlob); + blob_append_sql(&sql," AND name %s GLOB %Q", + fInvert ? "NOT" : "", zGlob); }else{ zGlob = json_find_option_cstr("like",NULL,"l"); if(zGlob && *zGlob){ - blob_appendf(&sql," AND name %s LIKE %Q", - fInvert ? "NOT" : "", - zGlob); + blob_append_sql(&sql," AND name %s LIKE %Q", + fInvert ? "NOT" : "", zGlob); } } blob_append(&sql," ORDER BY lower(name)", -1); - db_prepare(&q,"%s", blob_str(&sql)); + db_prepare(&q,"%s", blob_sql_text(&sql)); blob_reset(&sql); listV = cson_value_new_array(); list = cson_value_get_array(listV); while( SQLITE_ROW == db_step(&q) ){ cson_value * v; @@ -542,11 +545,11 @@ blob_init(&w1, pW1->zWiki, -1); blob_zero(&w2); blob_init(&w2, pW2->zWiki, -1); blob_zero(&d); - diffFlags = DIFF_IGNORE_EOLWS | DIFF_INLINE; + diffFlags = DIFF_IGNORE_EOLWS | DIFF_STRIP_EOLCR; text_diff(&w2, &w1, &d, 0, diffFlags); blob_reset(&w1); blob_reset(&w2); pay = cson_new_object(); Index: src/leaf.c ================================================================== --- src/leaf.c +++ src/leaf.c @@ -16,34 +16,35 @@ ******************************************************************************* ** ** This file contains code used to manage the "leaf" table of the ** repository. ** -** The LEAF table contains the rids for all leaves in the checkin DAG. -** A leaf is a checkin that has no children in the same branch. +** The LEAF table contains the rids for all leaves in the check-in DAG. +** A leaf is a check-in that has no children in the same branch. */ #include "config.h" #include "leaf.h" #include <assert.h> /* ** Return true if the check-in with RID=rid is a leaf. ** -** A leaf has no children in the same branch. +** A leaf has no children in the same branch. */ int is_a_leaf(int rid){ int rc; - static const char zSql[] = + static const char zSql[] = @ SELECT 1 FROM plink @ WHERE pid=%d @ AND coalesce((SELECT value FROM tagxref @ WHERE tagid=%d AND rid=plink.pid), 'trunk') @ =coalesce((SELECT value FROM tagxref @ WHERE tagid=%d AND rid=plink.cid), 'trunk') ; - rc = db_int(0, zSql, rid, TAG_BRANCH, TAG_BRANCH); + rc = db_int(0, zSql /*works-like:"%d,%d,%d"*/, + rid, TAG_BRANCH, TAG_BRANCH); return rc==0; } /* ** Count the number of primary non-branch children for the given check-in. @@ -55,19 +56,19 @@ ** A non-branch child is one which is on the same branch as the parent. */ int count_nonbranch_children(int pid){ int nNonBranch = 0; static Stmt q; - static const char zSql[] = + static const char zSql[] = @ SELECT count(*) FROM plink @ WHERE pid=:pid AND isprim @ AND coalesce((SELECT value FROM tagxref @ WHERE tagid=%d AND rid=plink.pid), 'trunk') @ =coalesce((SELECT value FROM tagxref @ WHERE tagid=%d AND rid=plink.cid), 'trunk') ; - db_static_prepare(&q, zSql, TAG_BRANCH, TAG_BRANCH); + db_static_prepare(&q, zSql /*works-like: "%d,%d"*/, TAG_BRANCH, TAG_BRANCH); db_bind_int(&q, ":pid", pid); if( db_step(&q)==SQLITE_ROW ){ nNonBranch = db_column_int(&q, 0); } db_reset(&q); @@ -74,11 +75,11 @@ return nNonBranch; } /* -** Recompute the entire LEAF table. +** Recompute the entire LEAF table. ** ** This can be expensive (5 seconds or so) for a really large repository. ** So it is only done for things like a rebuild. */ void leaf_rebuild(void){ @@ -95,16 +96,16 @@ TAG_BRANCH, TAG_BRANCH ); } /* -** A bag of checkins whose leaf status needs to be checked. +** A bag of check-ins whose leaf status needs to be checked. */ static Bag needToCheck; /* -** Check to see if checkin "rid" is a leaf and either add it to the LEAF +** Check to see if check-in "rid" is a leaf and either add it to the LEAF ** table if it is, or remove it if it is not. */ void leaf_check(int rid){ static Stmt checkIfLeaf; static Stmt addLeaf; @@ -158,11 +159,11 @@ ** Schedule a leaf check for "rid" and its parents. */ void leaf_eventually_check(int rid){ static Stmt parentsOf; - db_static_prepare(&parentsOf, + db_static_prepare(&parentsOf, "SELECT pid FROM plink WHERE cid=:rid AND pid>0" ); db_bind_int(&parentsOf, ":rid", rid); bag_insert(&needToCheck, rid); while( db_step(&parentsOf)==SQLITE_ROW ){ ADDED src/loadctrl.c Index: src/loadctrl.c ================================================================== --- /dev/null +++ src/loadctrl.c @@ -0,0 +1,65 @@ +/* +** Copyright (c) 2014 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code to check the host load-average and abort +** CPU-intensive operations if the load-average is too high. +*/ +#include "config.h" +#include "loadctrl.h" +#include <assert.h> + +/* +** Return the load average for the host processor +*/ +double load_average(void){ +#if !defined(_WIN32) && !defined(FOSSIL_OMIT_LOAD_AVERAGE) + double a[3]; + if( getloadavg(a, 3)>0 ){ + return a[0]>=0.000001 ? a[0] : 0.000001; + } +#endif + return 0.0; +} + +/* +** COMMAND: test-loadavg +** %fossil test-loadavg +** +** Print the load average on the host machine. +*/ +void loadavg_test_cmd(void){ + fossil_print("load-average: %f\n", load_average()); +} + +/* +** Abort the current operation of the load average of the host computer +** is too high. +*/ +void load_control(void){ + double mxLoad = atof(db_get("max-loadavg", "0")); + if( mxLoad<=0.0 || mxLoad>=load_average() ) return; + + style_header("Server Overload"); + @ <h2>The server load is currently too high. + @ Please try again later.</h2> + @ <p>Current load average: %f(load_average()).<br /> + @ Load average limit: %f(mxLoad)</p> + style_footer(); + cgi_set_status(503,"Server Overload"); + cgi_reply(); + exit(0); +} Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -41,11 +41,11 @@ ** logs and downloading diffs of very version of the archive that ** has ever existed, and things like that. */ #include "config.h" #include "login.h" -#if defined(_WIN32) +#if defined(_WIN32) # include <windows.h> /* for Sleep */ # if defined(__MINGW32__) || defined(_MSC_VER) # define sleep Sleep /* windows does not have sleep, but Sleep */ # endif #endif @@ -112,13 +112,13 @@ } } /* ** The IP address of the client is stored as part of login cookies. -** But some clients are behind firewalls that shift the IP address -** with each HTTP request. To allow such (broken) clients to log in, -** extract just a prefix of the IP address. +** But some clients are behind firewalls that shift the IP address +** with each HTTP request. To allow such (broken) clients to log in, +** extract just a prefix of the IP address. */ static char *ipPrefix(const char *zIP){ int i, j; static int ip_prefix_terms = -1; if( ip_prefix_terms<0 ){ @@ -208,12 +208,12 @@ ** On serious (DB-level) error it will probably exit. ** ** zPassword may be either the plain-text form or the encrypted ** form of the user's password. */ -int login_search_uid(char const *zUsername, char const *zPasswd){ - char * zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0); +int login_search_uid(const char *zUsername, const char *zPasswd){ + char *zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0); int const uid = db_int(0, "SELECT uid FROM user" " WHERE login=%Q" " AND length(cap)>0 AND length(pw)>0" @@ -231,12 +231,12 @@ ** The zHash parameter must be a random value which must be ** subsequently stored in user.cookie for later validation. ** ** The returned memory should be free()d after use. */ -char * login_gen_user_cookie_value(char const *zUsername, char const * zHash){ - char * zProjCode = db_get("project-code",NULL); +char *login_gen_user_cookie_value(const char *zUsername, const char *zHash){ + char *zProjCode = db_get("project-code",NULL); char *zCode = abbreviated_project_code(zProjCode); free(zProjCode); assert((zUsername && *zUsername) && "Invalid user data."); return mprintf("%s/%z/%s", zHash, zCode, zUsername); } @@ -252,20 +252,20 @@ ** If zDest is not NULL then the generated cookie is copied to ** *zDdest and ownership is transfered to the caller (who should ** eventually pass it to free()). */ void login_set_user_cookie( - char const * zUsername, /* User's name */ + const char *zUsername, /* User's name */ int uid, /* User's ID */ - char ** zDest /* Optional: store generated cookie value. */ + char **zDest /* Optional: store generated cookie value. */ ){ const char *zCookieName = login_cookie_name(); const char *zExpire = db_get("cookie-expire","8766"); int expires = atoi(zExpire)*3600; char *zHash; char *zCookie; - char const *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */ + const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */ char *zRemoteAddr = ipPrefix(zIpAddr); /* Abbreviated IP address */ assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data."); zHash = db_text(0, "SELECT cookie FROM user" @@ -303,16 +303,16 @@ ** is used. ** ** If zCookieDest is not NULL then the generated cookie is assigned to ** *zCookieDest and the caller must eventually free() it. */ -void login_set_anon_cookie(char const * zIpAddr, char ** zCookieDest ){ - char const *zNow; /* Current time (julian day number) */ +void login_set_anon_cookie(const char *zIpAddr, char **zCookieDest ){ + const char *zNow; /* Current time (julian day number) */ char *zCookie; /* The login cookie */ - char const *zCookieName; /* Name of the login cookie */ + const char *zCookieName; /* Name of the login cookie */ Blob b; /* Blob used during cookie construction */ - char * zRemoteAddr; /* Abbreviated IP address */ + char *zRemoteAddr; /* Abbreviated IP address */ if(!zIpAddr){ zIpAddr = PD("REMOTE_ADDR","nil"); } zRemoteAddr = ipPrefix(zIpAddr); zCookieName = login_cookie_name(); @@ -344,24 +344,20 @@ */ void login_clear_login_data(){ if(!g.userUid){ return; }else{ - char const * cookie = login_cookie_name(); + const char *cookie = login_cookie_name(); /* To logout, change the cookie value to an empty string */ cgi_set_cookie(cookie, "", login_cookie_path(), -86400); db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, " " cexpire=0 WHERE uid=%d" " AND login NOT IN ('anonymous','nobody'," " 'developer','reader')", g.userUid); - cgi_replace_parameter(cookie, NULL) - /* At the time of this writing, cgi_replace_parameter() was - ** "NULL-value-safe", and I'm hoping the NULL doesn't cause any - ** downstream problems here. We could alternately use "" here. - */ - ; + cgi_replace_parameter(cookie, NULL); + cgi_replace_parameter("anon", NULL); } } /* ** Return true if the prefix of zStr matches zPattern. Return false if @@ -395,14 +391,15 @@ /* If a URI appears in the User-Agent, it is probably a bot */ if( strncmp("http", zAgent+i,4)==0 ) return 0; } if( strncmp(zAgent, "Mozilla/", 8)==0 ){ if( atoi(&zAgent[8])<4 ) return 0; /* Many bots advertise as Mozilla/3 */ - if( strglob("*Firefox/[1-9]*", zAgent) ) return 1; - if( strglob("*Chrome/[1-9]*", zAgent) ) return 1; - if( strglob("*(compatible;?MSIE?[1789]*", zAgent) ) return 1; - if( strglob("*AppleWebKit/[1-9]*(KHTML*", zAgent) ) return 1; + if( sqlite3_strglob("*Firefox/[1-9]*", zAgent)==0 ) return 1; + if( sqlite3_strglob("*Chrome/[1-9]*", zAgent)==0 ) return 1; + if( sqlite3_strglob("*(compatible;?MSIE?[1789]*", zAgent)==0 ) return 1; + if( sqlite3_strglob("*Trident/[1-9]*;?rv:[1-9]*", zAgent)==0 ) return 1; /* IE11+ */ + if( sqlite3_strglob("*AppleWebKit/[1-9]*(KHTML*", zAgent)==0 ) return 1; return 0; } if( strncmp(zAgent, "Opera/", 6)==0 ) return 1; if( strncmp(zAgent, "Safari/", 7)==0 ) return 1; if( strncmp(zAgent, "Lynx/", 5)==0 ) return 1; @@ -450,61 +447,96 @@ } sqlite3_result_int(context, rc); } /* -** WEBPAGE: login -** WEBPAGE: logout -** WEBPAGE: my -** -** Generate the login page. -** +** Return true if the current page was reached by a redirect from the /login +** page. +*/ +int referred_from_login(void){ + const char *zReferer = P("HTTP_REFERER"); + char *zPattern; + int rc; + if( zReferer==0 ) return 0; + zPattern = mprintf("%s/login*", g.zBaseURL); + rc = sqlite3_strglob(zPattern, zReferer)==0; + fossil_free(zPattern); + return rc; +} + +/* ** There used to be a page named "my" that was designed to show information ** about a specific user. The "my" page was linked from the "Logged in as USER" ** line on the title bar. The "my" page was never completed so it is now ** removed. Use this page as a placeholder in older installations. +** +** WEBPAGE: login +** WEBPAGE: logout +** WEBPAGE: my +** +** The login/logout page. Parameters: +** +** g=URL Jump back to this URL after login completes +** anon The g=URL is not accessible by "nobody" but is +** accessible by "anonymous" */ void login_page(void){ const char *zUsername, *zPasswd; const char *zNew1, *zNew2; const char *zAnonPw = 0; const char *zGoto = P("g"); - int anonFlag; + int anonFlag; /* Login as "anonymous" would be useful */ char *zErrMsg = ""; int uid; /* User id logged in user */ char *zSha1Pw; const char *zIpAddr; /* IP address of requestor */ + const char *zReferer; login_check_credentials(); + if( login_wants_https_redirect() ){ + const char *zQS = P("QUERY_STRING"); + if( zQS==0 ){ + zQS = ""; + }else if( zQS[0]!=0 ){ + zQS = mprintf("?%s", zQS); + } + cgi_redirectf("%s%s%s", g.zHttpsURL, P("PATH_INFO"), zQS); + return; + } sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, - constant_time_cmp_function, 0, 0); + constant_time_cmp_function, 0, 0); zUsername = P("u"); zPasswd = P("p"); - anonFlag = P("anon")!=0; - if( P("out")!=0 ){ + anonFlag = g.zLogin==0 && PB("anon"); + + /* Handle log-out requests */ + if( P("out") ){ login_clear_login_data(); redirect_to_g(); + return; } + + /* Deal with password-change requests */ if( g.perm.Password && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){ /* The user requests a password change */ zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0); if( db_int(1, "SELECT 0 FROM user" " WHERE uid=%d" " AND (constant_time_cmp(pw,%Q)=0" - " OR constant_time_cmp(pw,%Q)=0)", + " OR constant_time_cmp(pw,%Q)=0)", g.userUid, zSha1Pw, zPasswd) ){ sleep(1); - zErrMsg = + zErrMsg = @ <p><span class="loginError"> @ You entered an incorrect old password while attempting to change @ your password. Your password is unchanged. @ </span></p> ; }else if( fossil_strcmp(zNew1,zNew2)!=0 ){ - zErrMsg = + zErrMsg = @ <p><span class="loginError"> @ The two copies of your new passwords do not match. @ Your password is unchanged. @ </span></p> ; @@ -531,10 +563,11 @@ return; } } } zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */ + zReferer = P("HTTP_REFERER"); uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs")); if( uid>0 ){ login_set_anon_cookie(zIpAddr, NULL); record_login_attempt("anonymous", zIpAddr, 1); redirect_to_g(); @@ -543,11 +576,11 @@ /* Attempting to log in as a user other than anonymous. */ uid = login_search_uid(zUsername, zPasswd); if( uid<=0 ){ sleep(1); - zErrMsg = + zErrMsg = @ <p><span class="loginError"> @ You entered an unknown user or an incorrect password. @ </span></p> ; record_login_attempt(zUsername, zIpAddr, 0); @@ -562,17 +595,41 @@ login_set_user_cookie(zUsername, uid, NULL); redirect_to_g(); } } style_header("Login/Logout"); + style_adunit_config(ADUNIT_OFF); @ %s(zErrMsg) - if( zGoto && P("anon")==0 ){ - @ <p>A login is required for <a href="%h(zGoto)">%h(zGoto)</a>.</p> + if( zGoto ){ + char *zAbbrev = fossil_strdup(zGoto); + int i; + for(i=0; zAbbrev[i] && zAbbrev[i]!='?'; i++){} + zAbbrev[i] = 0; + if( g.zLogin ){ + @ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b> + @ to access <b>%h(zAbbrev)</b>. + }else if( anonFlag ){ + @ <p>Login as <b>anonymous</b> or any named user + @ to access page <b>%h(zAbbrev)</b>. + }else{ + @ <p>Login as a named user to access page <b>%h(zAbbrev)</b>. + } } form_begin(0, "%R/login"); if( zGoto ){ @ <input type="hidden" name="g" value="%h(zGoto)" /> + }else if( zReferer && strncmp(g.zBaseURL, zReferer, strlen(g.zBaseURL))==0 ){ + @ <input type="hidden" name="g" value="%h(zReferer)" /> + } + if( anonFlag ){ + @ <input type="hidden" name="anon" value="1" /> + } + if( g.zLogin ){ + @ <p>Currently logged in as <b>%h(g.zLogin)</b>. + @ <input type="submit" name="out" value="Logout"></p> + @ <hr /> + @ <p>Change user: } @ <table class="login_out"> @ <tr> @ <td class="login_out_label">User ID:</td> if( anonFlag ){ @@ -583,11 +640,11 @@ @ </tr> @ <tr> @ <td class="login_out_label">Password:</td> @ <td><input type="password" id="p" name="p" value="" size="30" /></td> @ </tr> - if( g.zLogin==0 ){ + if( g.zLogin==0 && (anonFlag || zGoto==0) ){ zAnonPw = db_text(0, "SELECT pw FROM user" " WHERE login='anonymous'" " AND cap!=''"); } @ <tr> @@ -594,11 +651,11 @@ @ <td></td> @ <td><input type="submit" name="in" value="Login" @ onClick="chngAction(this.form)" /></td> @ </tr> @ </table> - @ <script type="text/JavaScript"> + @ <script> @ gebi('u').focus() @ function chngAction(form){ if( g.sslNotAvailable==0 && strncmp(g.zBaseURL,"https:",6)!=0 && db_get_boolean("https-login",0) @@ -608,27 +665,18 @@ @ form.action = "%h(zSSL)/login"; @ } } @ } @ </script> - if( g.zLogin==0 ){ - @ <p>Enter - }else{ - @ <p>You are currently logged in as <b>%h(g.zLogin)</b></p> - @ <p>To change your login to a different user, enter - } - @ your user-id and password at the left and press the - @ "Login" button. Your user name will be stored in a browser cookie. - @ You must configure your web browser to accept cookies in order for - @ the login to take.</p> + @ <p>Pressing the Login button grants permission to store a cookie.</p> if( db_get_boolean("self-register", 0) ){ - @ <p>If you do not have an account, you can - @ <a href="%s(g.zTop)/register?g=%T(P("G"))">create one</a>. + @ <p>If you do not have an account, you can + @ <a href="%R/register?g=%T(P("G"))">create one</a>. } if( zAnonPw ){ unsigned int uSeed = captcha_seed(); - char const *zDecoded = captcha_decode(uSeed); + const char *zDecoded = captcha_decode(uSeed); int bAutoCaptcha = db_get_boolean("auto-captcha", 0); char *zCaptcha = captcha_render(zDecoded); @ <p><input type="hidden" name="cs" value="%u(uSeed)" /> @ Visitors may enter <b>anonymous</b> as the user-ID with @@ -641,22 +689,14 @@ @ onclick="gebi('u').value='anonymous'; gebi('p').value='%s(zDecoded)';" /> } @ </div> free(zCaptcha); } - if( g.zLogin ){ - @ <hr /> - @ <p>To log off the system (and delete your login cookie) - @ press the following button:<br /> - @ <input type="submit" name="out" value="Logout" /></p> - } @ </form> if( g.perm.Password ){ @ <hr /> - @ <p>To change your password, enter your old password and your - @ new password twice below then press the "Change Password" - @ button.</p> + @ <p>Change Password for user <b>%h(g.zLogin)</b>:</p> form_begin(0, "%R/login"); @ <table> @ <tr><td class="login_out_label">Old Password:</td> @ <td><input type="password" name="p" size="30" /></td></tr> @ <tr><td class="login_out_label">New Password:</td> @@ -671,11 +711,11 @@ style_footer(); } /* ** Attempt to find login credentials for user zLogin on a peer repository -** with project code zCode. Transfer those credentials to the local +** with project code zCode. Transfer those credentials to the local ** repository. ** ** Return true if a transfer was made and false if not. */ static int login_transfer_credentials( @@ -689,21 +729,25 @@ char *zSQL; /* SQL of the query against other repo */ char *zOtherRepo; /* Filename of the other repository */ int rc; /* Result code from SQLite library functions */ int nXfer = 0; /* Number of credentials transferred */ - zOtherRepo = db_text(0, + zOtherRepo = db_text(0, "SELECT value FROM config WHERE name='peer-repo-%q'", zCode ); if( zOtherRepo==0 ) return 0; /* No such peer repository */ - rc = sqlite3_open(zOtherRepo, &pOther); + rc = sqlite3_open_v2( + zOtherRepo, &pOther, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + g.zVfsName + ); if( rc==SQLITE_OK ){ - sqlite3_create_function(pOther,"now",0,SQLITE_ANY,0,db_now_function,0,0); + sqlite3_create_function(pOther,"now",0,SQLITE_UTF8,0,db_now_function,0,0); sqlite3_create_function(pOther, "constant_time_cmp", 2, SQLITE_UTF8, 0, - constant_time_cmp_function, 0, 0); + constant_time_cmp_function, 0, 0); sqlite3_busy_timeout(pOther, 5000); zSQL = mprintf( "SELECT cexpire FROM user" " WHERE login=%Q" " AND ipaddr=%Q" @@ -728,10 +772,21 @@ } sqlite3_close(pOther); fossil_free(zOtherRepo); return nXfer; } + +/* +** Return TRUE if zLogin is one of the special usernames +*/ +int login_is_special(const char *zLogin){ + if( fossil_strcmp(zLogin, "anonymous")==0 ) return 1; + if( fossil_strcmp(zLogin, "nobody")==0 ) return 1; + if( fossil_strcmp(zLogin, "developer")==0 ) return 1; + if( fossil_strcmp(zLogin, "reader")==0 ) return 1; + return 0; +} /* ** Lookup the uid for a non-built-in user with zLogin and zCookie and ** zRemoteAddr. Return 0 if not found. ** @@ -743,15 +798,12 @@ const char *zLogin, /* User name */ const char *zCookie, /* Login cookie value */ const char *zRemoteAddr /* Abbreviated IP address for valid login */ ){ int uid; - if( fossil_strcmp(zLogin, "anonymous")==0 ) return 0; - if( fossil_strcmp(zLogin, "nobody")==0 ) return 0; - if( fossil_strcmp(zLogin, "developer")==0 ) return 0; - if( fossil_strcmp(zLogin, "reader")==0 ) return 0; - uid = db_int(0, + if( login_is_special(zLogin) ) return 0; + uid = db_int(0, "SELECT uid FROM user" " WHERE login=%Q" " AND ipaddr=%Q" " AND cexpire>julianday('now')" " AND length(cap)>0" @@ -759,19 +811,35 @@ " AND constant_time_cmp(cookie,%Q)=0", zLogin, zRemoteAddr, zCookie ); return uid; } + +/* +** Return true if it is appropriate to redirect login requests to HTTPS. +** +** Redirect to https is appropriate if all of the above are true: +** (1) The redirect-to-https flag is set +** (2) The current connection is http, not https or ssh +** (3) The sslNotAvailable flag is clear +*/ +int login_wants_https_redirect(void){ + if( g.sslNotAvailable ) return 0; + if( db_get_boolean("redirect-to-https",0)==0 ) return 0; + if( P("HTTPS")!=0 ) return 0; + return 1; +} /* ** This routine examines the login cookie to see if it exists and ** is valid. If the login cookie checks out, it then sets global ** variables appropriately. ** ** g.userUid Database USER.UID value. Might be -1 for "nobody" ** g.zLogin Database USER.LOGIN value. NULL for user "nobody" ** g.perm Permissions granted to this user +** g.anon Permissions that would be available to anonymous ** g.isHuman True if the user is human, not a spider or robot ** */ void login_check_credentials(void){ int uid = 0; /* User id */ @@ -778,31 +846,38 @@ const char *zCookie; /* Text of the login cookie */ const char *zIpAddr; /* Raw IP address of the requestor */ char *zRemoteAddr; /* Abbreviated IP address of the requestor */ const char *zCap = 0; /* Capability string */ const char *zPublicPages = 0; /* GLOB patterns of public pages */ + const char *zLogin = 0; /* Login user for credentials */ /* Only run this check once. */ if( g.userUid!=0 ) return; sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, - constant_time_cmp_function, 0, 0); + constant_time_cmp_function, 0, 0); /* If the HTTP connection is coming over 127.0.0.1 and if - ** local login is disabled and if we are using HTTP and not HTTPS, + ** local login is disabled and if we are using HTTP and not HTTPS, ** then there is no need to check user credentials. ** ** This feature allows the "fossil ui" command to give the user ** full access rights without having to log in. */ zRemoteAddr = ipPrefix(zIpAddr = PD("REMOTE_ADDR","nil")); - if( fossil_strcmp(zIpAddr, "127.0.0.1")==0 + if( ( fossil_strcmp(zIpAddr, "127.0.0.1")==0 || + (g.fSshClient & CGI_SSH_CLIENT)!=0 ) && g.useLocalauth && db_get_int("localauth",0)==0 && P("HTTPS")==0 ){ - uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'"); + if( g.localOpen ) zLogin = db_lget("default-user",0); + if( zLogin!=0 ){ + uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin); + }else{ + uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'"); + } g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid); zCap = "sx"; g.noPswd = 1; g.isHuman = 1; sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "localhost"); @@ -835,15 +910,15 @@ ** SECRET is the "captcha-secret" value in the repository. */ double rTime = atof(zArg); Blob b; blob_zero(&b); - blob_appendf(&b, "%s/%s/%s", + blob_appendf(&b, "%s/%s/%s", zArg, zRemoteAddr, db_get("captcha-secret","")); sha1sum_blob(&b, &b); if( fossil_strcmp(zHash, blob_str(&b))==0 ){ - uid = db_int(0, + uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'" " AND length(cap)>0" " AND length(pw)>0" " AND %.17g+0.25>julianday('now')", rTime @@ -952,23 +1027,32 @@ ** Memory of settings */ static int login_anon_once = 1; /* -** Add the default privileges of users "nobody" and "anonymous" as appropriate -** for the user g.zLogin. +** Add to g.perm the default privileges of users "nobody" and/or "anonymous" +** as appropriate for the user g.zLogin. +** +** This routine also sets up g.anon to be either a copy of g.perm for +** all logged in uses, or the privileges that would be available to "anonymous" +** if g.zLogin==0 (meaning that the user is "nobody"). */ void login_set_anon_nobody_capabilities(void){ - if( g.zLogin && login_anon_once ){ + if( login_anon_once ){ const char *zCap; - /* All logged-in users inherit privileges from "nobody" */ + /* All users get privileges from "nobody" */ zCap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'"); login_set_capabilities(zCap, 0); - if( fossil_strcmp(g.zLogin, "nobody")!=0 ){ + zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'"); + if( g.zLogin && fossil_strcmp(g.zLogin, "nobody")!=0 ){ /* All logged-in users inherit privileges from "anonymous" */ - zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'"); login_set_capabilities(zCap, 0); + g.anon = g.perm; + }else{ + /* Record the privileges of anonymous in g.anon */ + g.anon = g.perm; + login_set_capabilities(zCap, LOGIN_ANON); } login_anon_once = 0; } } @@ -975,57 +1059,59 @@ /* ** Flags passed into the 2nd argument of login_set/replace_capabilities(). */ #if INTERFACE #define LOGIN_IGNORE_UV 0x01 /* Ignore "u" and "v" */ +#define LOGIN_ANON 0x02 /* Use g.anon instead of g.perm */ #endif /* -** Adds all capability flags in zCap to g.perm. +** Adds all capability flags in zCap to g.perm or g.anon. */ void login_set_capabilities(const char *zCap, unsigned flags){ int i; + FossilUserPerms *p = (flags & LOGIN_ANON) ? &g.anon : &g.perm; if(NULL==zCap){ return; } for(i=0; zCap[i]; i++){ switch( zCap[i] ){ - case 's': g.perm.Setup = 1; /* Fall thru into Admin */ - case 'a': g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip = - g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki = - g.perm.ApndWiki = g.perm.Hyperlink = g.perm.Clone = - g.perm.NewTkt = g.perm.Password = g.perm.RdAddr = - g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt = - g.perm.ModWiki = g.perm.ModTkt = 1; + case 's': p->Setup = 1; /* Fall thru into Admin */ + case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip = + p->RdWiki = p->WrWiki = p->NewWiki = + p->ApndWiki = p->Hyperlink = p->Clone = + p->NewTkt = p->Password = p->RdAddr = + p->TktFmt = p->Attach = p->ApndTkt = + p->ModWiki = p->ModTkt = 1; /* Fall thru into Read/Write */ - case 'i': g.perm.Read = g.perm.Write = 1; break; - case 'o': g.perm.Read = 1; break; - case 'z': g.perm.Zip = 1; break; - - case 'd': g.perm.Delete = 1; break; - case 'h': g.perm.Hyperlink = 1; break; - case 'g': g.perm.Clone = 1; break; - case 'p': g.perm.Password = 1; break; - - case 'j': g.perm.RdWiki = 1; break; - case 'k': g.perm.WrWiki = g.perm.RdWiki = g.perm.ApndWiki =1; break; - case 'm': g.perm.ApndWiki = 1; break; - case 'f': g.perm.NewWiki = 1; break; - case 'l': g.perm.ModWiki = 1; break; - - case 'e': g.perm.RdAddr = 1; break; - case 'r': g.perm.RdTkt = 1; break; - case 'n': g.perm.NewTkt = 1; break; - case 'w': g.perm.WrTkt = g.perm.RdTkt = g.perm.NewTkt = - g.perm.ApndTkt = 1; break; - case 'c': g.perm.ApndTkt = 1; break; - case 'q': g.perm.ModTkt = 1; break; - case 't': g.perm.TktFmt = 1; break; - case 'b': g.perm.Attach = 1; break; - case 'x': g.perm.Private = 1; break; - - /* The "u" privileges is a little different. It recursively + case 'i': p->Read = p->Write = 1; break; + case 'o': p->Read = 1; break; + case 'z': p->Zip = 1; break; + + case 'd': p->Delete = 1; break; + case 'h': p->Hyperlink = 1; break; + case 'g': p->Clone = 1; break; + case 'p': p->Password = 1; break; + + case 'j': p->RdWiki = 1; break; + case 'k': p->WrWiki = p->RdWiki = p->ApndWiki =1; break; + case 'm': p->ApndWiki = 1; break; + case 'f': p->NewWiki = 1; break; + case 'l': p->ModWiki = 1; break; + + case 'e': p->RdAddr = 1; break; + case 'r': p->RdTkt = 1; break; + case 'n': p->NewTkt = 1; break; + case 'w': p->WrTkt = p->RdTkt = p->NewTkt = + p->ApndTkt = 1; break; + case 'c': p->ApndTkt = 1; break; + case 'q': p->ModTkt = 1; break; + case 't': p->TktFmt = 1; break; + case 'b': p->Attach = 1; break; + case 'x': p->Private = 1; break; + + /* The "u" privileges is a little different. It recursively ** inherits all privileges of the user named "reader" */ case 'u': { if( (flags & LOGIN_IGNORE_UV)==0 ){ const char *zUser; zUser = db_text("", "SELECT cap FROM user WHERE login='reader'"); @@ -1032,11 +1118,11 @@ login_set_capabilities(zUser, flags | LOGIN_IGNORE_UV); } break; } - /* The "v" privileges is a little different. It recursively + /* The "v" privileges is a little different. It recursively ** inherits all privileges of the user named "developer" */ case 'v': { if( (flags & LOGIN_IGNORE_UV)==0 ){ const char *zDev; zDev = db_text("", "SELECT cap FROM user WHERE login='developer'"); @@ -1060,42 +1146,43 @@ /* ** If the current login lacks any of the capabilities listed in ** the input, then return 0. If all capabilities are present, then ** return 1. */ -int login_has_capability(const char *zCap, int nCap){ +int login_has_capability(const char *zCap, int nCap, u32 flgs){ int i; int rc = 1; + FossilUserPerms *p = (flgs & LOGIN_ANON) ? &g.anon : &g.perm; if( nCap<0 ) nCap = strlen(zCap); for(i=0; i<nCap && rc && zCap[i]; i++){ switch( zCap[i] ){ - case 'a': rc = g.perm.Admin; break; - case 'b': rc = g.perm.Attach; break; - case 'c': rc = g.perm.ApndTkt; break; - case 'd': rc = g.perm.Delete; break; - case 'e': rc = g.perm.RdAddr; break; - case 'f': rc = g.perm.NewWiki; break; - case 'g': rc = g.perm.Clone; break; - case 'h': rc = g.perm.Hyperlink; break; - case 'i': rc = g.perm.Write; break; - case 'j': rc = g.perm.RdWiki; break; - case 'k': rc = g.perm.WrWiki; break; - case 'l': rc = g.perm.ModWiki; break; - case 'm': rc = g.perm.ApndWiki; break; - case 'n': rc = g.perm.NewTkt; break; - case 'o': rc = g.perm.Read; break; - case 'p': rc = g.perm.Password; break; - case 'q': rc = g.perm.ModTkt; break; - case 'r': rc = g.perm.RdTkt; break; - case 's': rc = g.perm.Setup; break; - case 't': rc = g.perm.TktFmt; break; + case 'a': rc = p->Admin; break; + case 'b': rc = p->Attach; break; + case 'c': rc = p->ApndTkt; break; + case 'd': rc = p->Delete; break; + case 'e': rc = p->RdAddr; break; + case 'f': rc = p->NewWiki; break; + case 'g': rc = p->Clone; break; + case 'h': rc = p->Hyperlink; break; + case 'i': rc = p->Write; break; + case 'j': rc = p->RdWiki; break; + case 'k': rc = p->WrWiki; break; + case 'l': rc = p->ModWiki; break; + case 'm': rc = p->ApndWiki; break; + case 'n': rc = p->NewTkt; break; + case 'o': rc = p->Read; break; + case 'p': rc = p->Password; break; + case 'q': rc = p->ModTkt; break; + case 'r': rc = p->RdTkt; break; + case 's': rc = p->Setup; break; + case 't': rc = p->TktFmt; break; /* case 'u': READER */ /* case 'v': DEVELOPER */ - case 'w': rc = g.perm.WrTkt; break; - case 'x': rc = g.perm.Private; break; + case 'w': rc = p->WrTkt; break; + case 'x': rc = p->Private; break; /* case 'y': */ - case 'z': rc = g.perm.Zip; break; + case 'z': rc = p->Zip; break; default: rc = 0; break; } } return rc; } @@ -1126,16 +1213,30 @@ /* Set the capabilities */ login_set_capabilities(zCap, 0); login_anon_once = 1; login_set_anon_nobody_capabilities(); } + +/* +** Return true if the user is "nobody" +*/ +int login_is_nobody(void){ + return g.zLogin==0 || g.zLogin[0]==0 || fossil_strcmp(g.zLogin,"nobody")==0; +} + +/* +** Return the login name. If no login name is specified, return "nobody". +*/ +const char *login_name(void){ + return (g.zLogin && g.zLogin[0]) ? g.zLogin : "nobody"; +} /* ** Call this routine when the credential check fails. It causes ** a redirect to the "login" page. */ -void login_needed(void){ +void login_needed(int anonOk){ #ifdef FOSSIL_ENABLE_JSON if(g.json.isJsonMode){ json_err( FSL_JSON_E_DENIED, NULL, 1 ); fossil_exit(0); /* NOTREACHED */ @@ -1142,11 +1243,23 @@ assert(0); }else #endif /* FOSSIL_ENABLE_JSON */ { const char *zUrl = PD("REQUEST_URI", "index"); - cgi_redirect(mprintf("login?g=%T", zUrl)); + const char *zQS = P("QUERY_STRING"); + Blob redir; + blob_init(&redir, 0, 0); + if( login_wants_https_redirect() ){ + blob_appendf(&redir, "%s/login?g=%T", g.zHttpsURL, zUrl); + }else{ + blob_appendf(&redir, "%R/login?g=%T", zUrl); + } + if( anonOk ) blob_append(&redir, "&anon", 5); + if( zQS && zQS[0] ){ + blob_appendf(&redir, "&%s", zQS); + } + cgi_redirect(blob_str(&redir)); /* NOTREACHED */ assert(0); } } @@ -1155,17 +1268,14 @@ ** the anonymous user has Hyperlink permission, then paint a mesage ** to inform the user that much more information is available by ** logging in as anonymous. */ void login_anonymous_available(void){ - if( !g.perm.Hyperlink && - db_exists("SELECT 1 FROM user" - " WHERE login='anonymous'" - " AND cap LIKE '%%h%%'") ){ + if( !g.perm.Hyperlink && g.anon.Hyperlink ){ const char *zUrl = PD("REQUEST_URI", "index"); @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br /> - @ Use <a href="%s(g.zTop)/login?anon=1&g=%T(zUrl)">anonymous login</a> + @ Use <a href="%R/login?anon=1&g=%T(zUrl)">anonymous login</a> @ to enable hyperlinks.</p> } } /* @@ -1198,11 +1308,11 @@ ** */ void register_page(void){ const char *zUsername, *zPasswd, *zConfirm, *zContact, *zCS, *zPw, *zCap; unsigned int uSeed; - char const *zDecoded; + const char *zDecoded; char *zCaptcha; if( !db_get_boolean("self-register", 0) ){ style_header("Registration not possible"); @ <p>This project does not allow user self-registration. Please contact the @ project administrator to obtain an account.</p> @@ -1255,12 +1365,12 @@ @ </span></p> }else{ char *zPw = sha1_shared_secret(blob_str(&passwd), blob_str(&login), 0); int uid; db_multi_exec( - "INSERT INTO user(login,pw,cap,info)" - "VALUES(%B,%Q,%B,%B)", + "INSERT INTO user(login,pw,cap,info,mtime)" + "VALUES(%B,%Q,%B,%B,strftime('%%s','now'))", &login, zPw, &caps, &contact ); free(zPw); /* The user is registered, now just log him in. */ @@ -1347,11 +1457,11 @@ if( zPrefix==0 ) zPrefix = ""; if( zSuffix==0 ) zSuffix = ""; if( pzErrorMsg ) *pzErrorMsg = 0; zSelfCode = abbreviated_project_code(db_get("project-code", "x")); blob_zero(&err); - db_prepare(&q, + db_prepare(&q, "SELECT name, value FROM config" " WHERE name GLOB 'peer-repo-*'" " AND name <> 'peer-repo-%q'" " ORDER BY +value", zSelfCode @@ -1365,21 +1475,25 @@ "DELETE FROM config WHERE name GLOB 'peer-*-%q'", &zLabel[10] ); continue; } - rc = sqlite3_open_v2(zRepoName, &pPeer, SQLITE_OPEN_READWRITE, 0); + rc = sqlite3_open_v2( + zRepoName, &pPeer, + SQLITE_OPEN_READWRITE, + g.zVfsName + ); if( rc!=SQLITE_OK ){ blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, sqlite3_errmsg(pPeer), zSuffix); nErr++; sqlite3_close(pPeer); continue; } sqlite3_create_function(pPeer, "shared_secret", 3, SQLITE_UTF8, 0, sha1_shared_secret_sql_function, 0, 0); - sqlite3_create_function(pPeer, "now", 0,SQLITE_ANY,0,db_now_function,0,0); + sqlite3_create_function(pPeer, "now", 0,SQLITE_UTF8,0,db_now_function,0,0); sqlite3_busy_timeout(pPeer, 5000); zErr = 0; rc = sqlite3_exec(pPeer, zSql, 0, 0, &zErr); if( zErr ){ blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, zErr, zSuffix); @@ -1425,19 +1539,19 @@ const char *zSelf; /* The ATTACH name of our repository */ *pzErrMsg = 0; /* Default to no errors */ zSelf = db_name("repository"); - /* Get the full pathname of the other repository */ + /* Get the full pathname of the other repository */ file_canonical_name(zRepo, &fullName, 0); - zRepo = mprintf(blob_str(&fullName)); + zRepo = fossil_strdup(blob_str(&fullName)); blob_reset(&fullName); /* Get the full pathname for our repository. Also the project code ** and project name for ourself. */ file_canonical_name(g.zRepositoryName, &fullName, 0); - zSelfRepo = mprintf(blob_str(&fullName)); + zSelfRepo = fossil_strdup(blob_str(&fullName)); blob_reset(&fullName); zSelfProjCode = db_get("project-code", "unknown"); zSelfLabel = db_get("project-name", 0); if( zSelfLabel==0 ){ zSelfLabel = zSelfProjCode; @@ -1452,13 +1566,17 @@ /* Make sure the other repository is a valid Fossil database */ if( file_size(zRepo)<0 ){ *pzErrMsg = mprintf("repository file \"%s\" does not exist", zRepo); return; } - rc = sqlite3_open(zRepo, &pOther); + rc = sqlite3_open_v2( + zRepo, &pOther, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + g.zVfsName + ); if( rc!=SQLITE_OK ){ - *pzErrMsg = mprintf(sqlite3_errmsg(pOther)); + *pzErrMsg = fossil_strdup(sqlite3_errmsg(pOther)); }else{ rc = sqlite3_exec(pOther, "SELECT count(*) FROM user", 0, 0, pzErrMsg); } sqlite3_close(pOther); if( rc ) return; @@ -1487,13 +1605,13 @@ */ zSelfProjCode = abbreviated_project_code(zSelfProjCode); zOtherProjCode = abbreviated_project_code(zOtherProjCode); db_begin_transaction(); db_multi_exec( - "DELETE FROM %s.config WHERE name GLOB 'peer-*';" - "INSERT INTO %s.config(name,value) VALUES('peer-repo-%s',%Q);" - "INSERT INTO %s.config(name,value) " + "DELETE FROM \"%w\".config WHERE name GLOB 'peer-*';" + "INSERT INTO \"%w\".config(name,value) VALUES('peer-repo-%q',%Q);" + "INSERT INTO \"%w\".config(name,value) " " SELECT 'peer-name-%q', value FROM other.config" " WHERE name='project-name';", zSelf, zSelf, zOtherProjCode, zRepo, zSelf, zOtherProjCode @@ -1504,11 +1622,11 @@ "INSERT OR IGNORE INTO other.config(name,value)" " VALUES('login-group-code',lower(hex(randomblob(8))));", zNewName ); db_multi_exec( - "REPLACE INTO %s.config(name,value)" + "REPLACE INTO \"%w\".config(name,value)" " SELECT name, value FROM other.config" " WHERE name GLOB 'peer-*' OR name GLOB 'login-group-*'", zSelf ); db_end_transaction(0); Index: src/lookslike.c ================================================================== --- src/lookslike.c +++ src/lookslike.c @@ -132,10 +132,45 @@ flags |= LOOK_LONG; /* Very long line -> binary */ } return flags; } + +/* +** Checks for proper UTF-8. It uses the method described in: +** http://en.wikipedia.org/wiki/UTF-8#Invalid_byte_sequences +** except for the "overlong form" of \u0000 which is not considered invalid +** here: Some languages like Java and Tcl use it. For UTF-8 characters +** > 7f, the variable 'c2' not necessary means the previous character. +** It's number of higher 1-bits indicate the number of continuation bytes +** that are expected to be followed. E.g. when 'c2' has a value in the range +** 0xc0..0xdf it means that 'c' is expected to contain the last continuation +** byte of a UTF-8 character. A value 0xe0..0xef means that after 'c' one +** more continuation byte is expected. +*/ + +int invalid_utf8(const Blob *pContent){ + const unsigned char *z = (unsigned char *) blob_buffer(pContent); + unsigned int n = blob_size(pContent); + unsigned char c, c2; + + if( n==0 ) return 0; /* Empty file -> OK */ + c = *z; + while( --n>0 ){ + c2 = c; + c = *++z; + if( c2>=0x80 ){ + if( ((c2<0xc2) || (c2>=0xf4) || ((c&0xc0)!=0x80)) && + (((c2!=0xf4) || (c>=0x90)) && ((c2!=0xc0) || (c!=0x80))) ){ + return LOOK_INVALID; /* Invalid UTF-8 */ + } + c = (c2 >= 0xe0) ? (c2<<1)+1 : ' '; + } + } + return (c>=0x80) ? LOOK_INVALID : 0; /* Last byte must be ASCII. */ +} + /* ** Define the type needed to represent a Unicode (UTF-16) character. */ #ifndef WCHAR_T @@ -156,11 +191,11 @@ /* ** This macro is used to swap the byte order of a UTF-16 character in the ** looks_like_utf16() function. */ -#define UTF16_SWAP(ch) ((((ch) << 8) & 0xFF00) | (((ch) >> 8) & 0xFF)) +#define UTF16_SWAP(ch) ((((ch) << 8) & 0xff00) | (((ch) >> 8) & 0xff)) #define UTF16_SWAP_IF(expr,ch) ((expr) ? UTF16_SWAP((ch)) : (ch)) /* ** This function attempts to scan each logical line within the blob to ** determine the type of content it appears to contain. The return value @@ -196,15 +231,14 @@ int looks_like_utf16(const Blob *pContent, int bReverse, int stopFlags){ const WCHAR_T *z = (WCHAR_T *)blob_buffer(pContent); unsigned int n = blob_size(pContent); int j, c, flags = LOOK_NONE; /* Assume UTF-16 text, prove otherwise */ - if( n==0 ) return flags; /* Empty file -> text */ if( n%sizeof(WCHAR_T) ){ flags |= LOOK_ODD; /* Odd number of bytes -> binary (UTF-8?) */ - if( n<sizeof(WCHAR_T) ) return flags; /* One byte -> binary (UTF-8?) */ } + if( n<sizeof(WCHAR_T) ) return flags; /* Zero or One byte -> binary (UTF-8?) */ c = *z; if( bReverse ){ c = UTF16_SWAP(c); } if( c==0 ){ @@ -215,15 +249,12 @@ flags |= LOOK_LONE_CR; /* More chars, next char is not LF */ } } j = (c!='\n'); if( !j ) flags |= (LOOK_LF | LOOK_LONE_LF); /* Found LF as first char */ - while( 1 ){ + while( !(flags&stopFlags) && ((n-=sizeof(WCHAR_T))>=sizeof(WCHAR_T)) ){ int c2 = c; - if( flags&stopFlags ) break; - n -= sizeof(WCHAR_T); - if( n<sizeof(WCHAR_T) ) break; c = *++z; if( bReverse ){ c = UTF16_SWAP(c); } ++j; @@ -260,11 +291,11 @@ ** This function returns an array of bytes representing the byte-order-mark ** for UTF-8. */ const unsigned char *get_utf8_bom(int *pnByte){ static const unsigned char bom[] = { - 0xEF, 0xBB, 0xBF, 0x00, 0x00, 0x00 + 0xef, 0xbb, 0xbf, 0x00, 0x00, 0x00 }; if( pnByte ) *pnByte = 3; return bom; } @@ -331,37 +362,49 @@ ** COMMAND: test-looks-like-utf ** ** Usage: %fossil test-looks-like-utf FILENAME ** ** Options: +** -n|--limit <num> Repeat looks-like function <num> times, for +** performance measurement. Default = 1; ** --utf8 Ignoring BOM and file size, force UTF-8 checking ** --utf16 Ignoring BOM and file size, force UTF-16 checking ** ** FILENAME is the name of a file to check for textual content in the UTF-8 ** and/or UTF-16 encodings. */ void looks_like_utf_test_cmd(void){ - Blob blob; /* the contents of the specified file */ - int fUtf8; /* return value of starts_with_utf8_bom() */ - int fUtf16; /* return value of starts_with_utf16_bom() */ - int fUnicode; /* return value of could_be_utf16() */ - int lookFlags; /* output flags from looks_like_utf8/utf16() */ + Blob blob; /* the contents of the specified file */ + int fUtf8 = 0; /* return value of starts_with_utf8_bom() */ + int fUtf16 = 0; /* return value of starts_with_utf16_bom() */ + int fUnicode = 0; /* return value of could_be_utf16() */ + int lookFlags = 0; /* output flags from looks_like_utf8/utf16() */ int bRevUtf16 = 0; /* non-zero -> UTF-16 byte order reversed */ - int bRevUnicode = 0; /* non-zero -> UTF-16 byte order reversed */ int fForceUtf8 = find_option("utf8",0,0)!=0; int fForceUtf16 = find_option("utf16",0,0)!=0; - if( g.argc!=3 ) usage("FILENAME"); - blob_read_from_file(&blob, g.argv[2]); - fUtf8 = starts_with_utf8_bom(&blob, 0); - fUtf16 = starts_with_utf16_bom(&blob, 0, &bRevUtf16); - if( fForceUtf8 ){ - fUnicode = 0; - }else{ - fUnicode = could_be_utf16(&blob, &bRevUnicode) || fForceUtf16; - } - lookFlags = fUnicode ? looks_like_utf16(&blob, bRevUnicode, 0) : - looks_like_utf8(&blob, 0); + const char *zCount = find_option("limit","n",1); + int nRepeat = 1; + + if( g.argc!=3 ) usage("FILENAME"); + if( zCount ){ + nRepeat = atoi(zCount); + } + blob_read_from_file(&blob, g.argv[2]); + while( --nRepeat >= 0 ){ + fUtf8 = starts_with_utf8_bom(&blob, 0); + fUtf16 = starts_with_utf16_bom(&blob, 0, &bRevUtf16); + if( fForceUtf8 ){ + fUnicode = 0; + }else{ + fUnicode = could_be_utf16(&blob, 0) || fForceUtf16; + } + if( fUnicode ){ + lookFlags = looks_like_utf16(&blob, bRevUtf16, 0); + }else{ + lookFlags = looks_like_utf8(&blob, 0)|invalid_utf8(&blob); + } + } fossil_print("File \"%s\" has %d bytes.\n",g.argv[2],blob_size(&blob)); fossil_print("Starts with UTF-8 BOM: %s\n",fUtf8?"yes":"no"); fossil_print("Starts with UTF-16 BOM: %s\n", fUtf16?(bRevUtf16?"reversed":"yes"):"no"); fossil_print("Looks like UTF-%s: %s\n",fUnicode?"16":"8", Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -16,10 +16,11 @@ ******************************************************************************* ** ** This module codes the main() procedure that runs first when the ** program is invoked. */ +#include "VERSION.h" #include "config.h" #include "main.h" #include <string.h> #include <time.h> #include <fcntl.h> @@ -29,13 +30,18 @@ #if defined(_WIN32) # include <windows.h> #else # include <errno.h> /* errno global */ #endif -#include "zlib.h" #ifdef FOSSIL_ENABLE_SSL -# include "openssl/opensslv.h" +# include "openssl/crypto.h" +#endif +#if defined(FOSSIL_ENABLE_MINIZ) +# define MINIZ_HEADER_FILE_ONLY +# include "miniz.c" +#else +# include <zlib.h> #endif #if INTERFACE #ifdef FOSSIL_ENABLE_TCL # include "tcl.h" #endif @@ -66,12 +72,12 @@ char Setup; /* s: use Setup screens on web interface */ char Admin; /* a: administrative permission */ char Delete; /* d: delete wiki or tickets */ char Password; /* p: change password */ char Query; /* q: create new reports */ - char Write; /* i: xfer inbound. checkin */ - char Read; /* o: xfer outbound. checkout */ + char Write; /* i: xfer inbound. check-in */ + char Read; /* o: xfer outbound. check-out */ char Hyperlink; /* h: enable the display of hyperlinks */ char Clone; /* g: clone */ char RdWiki; /* j: view wiki via web */ char NewWiki; /* f: create new wiki via web */ char ApndWiki; /* m: append to wiki via web */ @@ -111,25 +117,26 @@ void *xPostEval; /* Optional, called after Tcl_Eval*(). */ void *pPostContext; /* Optional, provided to xPostEval(). */ }; #endif -/* -** All global variables are in this structure. -*/ struct Global { int argc; char **argv; /* Command-line arguments to the program */ char *nameOfExe; /* Full path of executable. */ const char *zErrlog; /* Log errors to this file, if not NULL */ - int isConst; /* True if the output is unchanging */ + int isConst; /* True if the output is unchanging & cacheable */ + const char *zVfsName; /* The VFS to use for database connections */ sqlite3 *db; /* The connection to the databases */ sqlite3 *dbConfig; /* Separate connection for global_config table */ + char *zAuxSchema; /* Main repository aux-schema */ int useAttach; /* True if global_config is attached to repository */ const char *zConfigDbName;/* Path of the config database. NULL if not open */ sqlite3_int64 now; /* Seconds since 1970 */ int repositoryOpen; /* True if the main repository database is open */ + char *zRepositoryOption; /* Most recent cached repository option value */ char *zRepositoryName; /* Name of the repository database */ + char *zLocalDbName; /* Name of the local database */ const char *zMainDbType;/* "configdb", "localdb", or "repository" */ const char *zConfigDbType; /* "configdb", "localdb", or "repository" */ int localOpen; /* True if the local database is open */ char *zLocalRoot; /* The directory holding the local database */ int minPrefix; /* Number of digits needed for a distinct UUID */ @@ -136,16 +143,22 @@ int fSqlTrace; /* True if --sqltrace flag is present */ int fSqlStats; /* True if --sqltrace or --sqlstats are present */ int fSqlPrint; /* True if -sqlprint flag is present */ int fQuiet; /* True if -quiet flag is present */ int fHttpTrace; /* Trace outbound HTTP requests */ + int fAnyTrace; /* Any kind of tracing */ + char *zHttpAuth; /* HTTP Authorization user:pass information */ int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */ int fSshTrace; /* Trace the SSH setup traffic */ + int fSshClient; /* HTTP client flags for SSH client */ + char *zSshCmd; /* SSH command string */ int fNoSync; /* Do not do an autosync ever. --nosync */ + int fIPv4; /* Use only IPv4, not IPv6. --ipv4 */ char *zPath; /* Name of webpage being served */ char *zExtra; /* Extra path information past the webpage name */ char *zBaseURL; /* Full text of the URL being served */ + char *zHttpsURL; /* zBaseURL translated to https: */ char *zTop; /* Parent directory of zPath */ const char *zContentType; /* The content type of the input HTTP request */ int iErrPriority; /* Priority of current error message */ char *zErrMsg; /* Text of an error message */ int sslNotAvailable; /* SSL is not available. Do not redirect to https: */ @@ -153,54 +166,44 @@ int cgiOutput; /* Write error and status messages to CGI */ int xferPanic; /* Write error messages in XFER protocol */ int fullHttpReply; /* True for full HTTP reply. False for CGI reply */ Th_Interp *interp; /* The TH1 interpreter */ char *th1Setup; /* The TH1 post-creation setup script, if any */ + int th1Flags; /* The TH1 integration state flags */ FILE *httpIn; /* Accept HTTP input from here */ FILE *httpOut; /* Send HTTP output here */ int xlinkClusterOnly; /* Set when cloning. Only process clusters */ int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */ int *aCommitFile; /* Array of files to be committed */ int markPrivate; /* All new artifacts are private if true */ int clockSkewSeen; /* True if clocks on client and server out of sync */ - int wikiFlags; /* Wiki conversion flags applied to %w and %W */ + int wikiFlags; /* Wiki conversion flags applied to %W */ char isHTTP; /* True if server/CGI modes, else assume CLI. */ char javascriptHyperlink; /* If true, set href= using script, not HTML */ Blob httpHeader; /* Complete text of the HTTP request header */ - - int urlIsFile; /* True if a "file:" url */ - int urlIsHttps; /* True if a "https:" url */ - int urlIsSsh; /* True if an "ssh:" url */ - char *urlName; /* Hostname for http: or filename for file: */ - char *urlHostname; /* The HOST: parameter on http headers */ - char *urlProtocol; /* "http" or "https" */ - int urlPort; /* TCP port number for http: or https: */ - int urlDfltPort; /* The default port for the given protocol */ - char *urlPath; /* Pathname for http: */ - char *urlUser; /* User id for http: */ - char *urlPasswd; /* Password for http: */ - char *urlCanonical; /* Canonical representation of the URL */ - char *urlProxyAuth; /* Proxy-Authorizer: string */ - char *urlFossil; /* The fossil query parameter on ssh: */ - char *urlShell; /* The shell query parameter on ssh: */ - unsigned urlFlags; /* Boolean flags controlling URL processing */ - - const char *zLogin; /* Login name. "" if not logged in. */ + UrlData url; /* Information about current URL */ + const char *zLogin; /* Login name. NULL or "" if not logged in. */ const char *zSSLIdentity; /* Value of --ssl-identity option, filename of ** SSL client identity */ int useLocalauth; /* No login required if from 127.0.0.1 */ int noPswd; /* Logged in without password (on 127.0.0.1) */ int userUid; /* Integer user id */ int isHuman; /* True if access by a human, not a spider or bot */ + int comFmtFlags; /* Zero or more "COMMENT_PRINT_*" bit flags */ /* Information used to populate the RCVFROM table */ int rcvid; /* The rcvid. 0 if not yet defined. */ char *zIpAddr; /* The remote IP address */ char *zNonce; /* The nonce used for login */ - /* permissions used by the server */ + /* permissions available to current user */ struct FossilUserPerms perm; + + /* permissions available to current user or to "anonymous". + ** This is the logical union of perm permissions above with + ** the value that perm would take if g.zLogin were "anonymous". */ + struct FossilUserPerms anon; #ifdef FOSSIL_ENABLE_TCL /* all Tcl related context necessary for integration */ struct TclContext tcl; #endif @@ -209,10 +212,13 @@ char zCsrfToken[12]; /* Value of the anti-CSRF token */ int okCsrf; /* Anti-CSRF token is present and valid */ int parseCnt[10]; /* Counts of artifacts parsed */ FILE *fDebug; /* Write debug information here, if the file exists */ +#ifdef FOSSIL_ENABLE_TH1_HOOKS + int fNoThHook; /* Disable all TH1 command/webpage hooks */ +#endif int thTrace; /* True to enable TH1 debugging output */ Blob thLog; /* Text of the TH1 debugging output */ int isHome; /* True if rendering the "home" page */ @@ -238,45 +244,45 @@ */ int resultCode; /* used for passing back specific codes ** from /json callbacks. */ int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */ cson_output_opt outOpt; /* formatting options for JSON mode. */ - cson_value * authToken; /* authentication token */ - char const * jsonp; /* Name of JSONP function wrapper. */ + cson_value *authToken; /* authentication token */ + const char *jsonp; /* Name of JSONP function wrapper. */ unsigned char dispatchDepth /* Tells JSON command dispatching which argument we are currently working on. For this purpose, arg#0 is the "json" path/CLI arg. */; struct { /* "garbage collector" */ - cson_value * v; - cson_array * a; + cson_value *v; + cson_array *a; } gc; struct { /* JSON POST data. */ - cson_value * v; - cson_array * a; + cson_value *v; + cson_array *a; int offset; /* Tells us which PATH_INFO/CLI args part holds the "json" command, so that we can account for sub-repos and path prefixes. This is handled differently for CLI and CGI modes. */ - char const * commandStr /*"command" request param.*/; + const char *commandStr /*"command" request param.*/; } cmd; struct { /* JSON POST data. */ - cson_value * v; - cson_object * o; + cson_value *v; + cson_object *o; } post; struct { /* GET/COOKIE params in JSON mode. */ - cson_value * v; - cson_object * o; + cson_value *v; + cson_object *o; } param; struct { - cson_value * v; - cson_object * o; + cson_value *v; + cson_object *o; } reqPayload; /* request payload object (if any) */ - cson_array * warnings; /* response warnings */ + cson_array *warnings; /* response warnings */ int timerId; /* fetched from fossil_timer_start() */ } json; #endif /* FOSSIL_ENABLE_JSON */ }; @@ -308,15 +314,16 @@ */ static int name_search( const char *zName, /* The name we are looking for */ const NameMap *aMap, /* Search in this array */ int nMap, /* Number of slots in aMap[] */ + int iBegin, /* Lower bound on the array search */ int *pIndex /* OUT: The index in aMap[] of the match */ ){ int upr, lwr, cnt, m, i; int n = strlen(zName); - lwr = 0; + lwr = iBegin; upr = nMap-1; while( lwr<=upr ){ int mid, c; mid = (upr+lwr)/2; c = fossil_strcmp(zName, aMap[mid].zName); @@ -328,11 +335,11 @@ }else{ lwr = mid + 1; } } for(m=cnt=0, i=upr-2; cnt<2 && i<=upr+3 && i<nMap; i++){ - if( i<0 ) continue; + if( i<iBegin ) continue; if( strncmp(zName, aMap[i].zName, n)==0 ){ m = i; cnt++; } } @@ -368,10 +375,20 @@ #endif free(g.zErrMsg); if(g.db){ db_close(0); } + /* + ** FIXME: The next two lines cannot always be enabled; however, they + ** are very useful for tracking down TH1 memory leaks. + */ + if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){ + if( g.interp ){ + Th_DeleteInterp(g.interp); g.interp = 0; + } + assert( Th_GetOutstandingMalloc()==0 ); + } } /* ** Convert all arguments from mbcs (or unicode) to UTF-8. Then ** search g.argv for arguments "--args FILENAME". If found, then @@ -389,11 +406,11 @@ unsigned int nLine; /* Number of lines in the file*/ unsigned int i, j, k; /* Loop counters */ int n; /* Number of bytes in one line */ char *z; /* General use string pointer */ char **newArgv; /* New expanded g.argv under construction */ - char const * zFileName; /* input file name */ + const char *zFileName; /* input file name */ FILE *inFile; /* input FILE */ #if defined(_WIN32) wchar_t buf[MAX_PATH]; #endif @@ -488,40 +505,48 @@ return zNewArgv; } #endif /* -** Return a name for an SQLite error code +** Returns a name for a SQLite return code. */ -static const char *sqlite_error_code_name(int iCode){ +static const char *fossil_sqlite_return_code_name(int rc){ static char zCode[30]; - switch( iCode & 0xff ){ + switch( rc & 0xff ){ case SQLITE_OK: return "SQLITE_OK"; case SQLITE_ERROR: return "SQLITE_ERROR"; + case SQLITE_INTERNAL: return "SQLITE_INTERNAL"; case SQLITE_PERM: return "SQLITE_PERM"; case SQLITE_ABORT: return "SQLITE_ABORT"; case SQLITE_BUSY: return "SQLITE_BUSY"; + case SQLITE_LOCKED: return "SQLITE_LOCKED"; case SQLITE_NOMEM: return "SQLITE_NOMEM"; case SQLITE_READONLY: return "SQLITE_READONLY"; case SQLITE_INTERRUPT: return "SQLITE_INTERRUPT"; case SQLITE_IOERR: return "SQLITE_IOERR"; case SQLITE_CORRUPT: return "SQLITE_CORRUPT"; + case SQLITE_NOTFOUND: return "SQLITE_NOTFOUND"; case SQLITE_FULL: return "SQLITE_FULL"; case SQLITE_CANTOPEN: return "SQLITE_CANTOPEN"; case SQLITE_PROTOCOL: return "SQLITE_PROTOCOL"; case SQLITE_EMPTY: return "SQLITE_EMPTY"; case SQLITE_SCHEMA: return "SQLITE_SCHEMA"; + case SQLITE_TOOBIG: return "SQLITE_TOOBIG"; case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT"; case SQLITE_MISMATCH: return "SQLITE_MISMATCH"; case SQLITE_MISUSE: return "SQLITE_MISUSE"; case SQLITE_NOLFS: return "SQLITE_NOLFS"; + case SQLITE_AUTH: return "SQLITE_AUTH"; case SQLITE_FORMAT: return "SQLITE_FORMAT"; case SQLITE_RANGE: return "SQLITE_RANGE"; case SQLITE_NOTADB: return "SQLITE_NOTADB"; + case SQLITE_NOTICE: return "SQLITE_NOTICE"; case SQLITE_WARNING: return "SQLITE_WARNING"; + case SQLITE_ROW: return "SQLITE_ROW"; + case SQLITE_DONE: return "SQLITE_DONE"; default: { - sqlite3_snprintf(sizeof(zCode),zCode,"error code %d",iCode); + sqlite3_snprintf(sizeof(zCode), zCode, "SQLite return code %d", rc); } } return zCode; } @@ -531,11 +556,26 @@ /* Disable the file alias warning on apple products because Time Machine ** creates lots of aliases and the warning alarms people. */ if( iCode==SQLITE_WARNING ) return; #endif if( iCode==SQLITE_SCHEMA ) return; - fossil_warning("%s: %s", sqlite_error_code_name(iCode), zErrmsg); + fossil_warning("%s: %s", fossil_sqlite_return_code_name(iCode), zErrmsg); +} + +/* +** This function attempts to find command line options known to contain +** bitwise flags and initializes the associated global variables. After +** this function executes, all global variables (i.e. in the "g" struct) +** containing option-settable bitwise flag fields must be initialized. +*/ +static void fossil_init_flags_from_options(void){ + const char *zValue = find_option("comfmtflags", 0, 1); + if( zValue ){ + g.comFmtFlags = atoi(zValue); + }else{ + g.comFmtFlags = COMMENT_PRINT_DEFAULT; + } } /* ** This procedure runs first. */ @@ -550,10 +590,15 @@ #endif { const char *zCmdName = "unknown"; int idx; int rc; + if( sqlite3_libversion_number()<3008003 ){ + fossil_fatal("Unsuitable SQLite version %s, must be at least 3.8.3", + sqlite3_libversion()); + } + sqlite3_config(SQLITE_CONFIG_MULTITHREAD); sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0); memset(&g, 0, sizeof(g)); g.now = time(0); g.httpHeader = empty_blob; #ifdef FOSSIL_ENABLE_JSON @@ -574,10 +619,23 @@ memset(&g.tcl, 0, sizeof(TclContext)); g.tcl.argc = g.argc; g.tcl.argv = copy_args(g.argc, g.argv); /* save full arguments */ #endif g.mainTimerId = fossil_timer_start(); + capture_case_sensitive_option(); + g.zVfsName = find_option("vfs",0,1); + if( g.zVfsName==0 ){ + g.zVfsName = fossil_getenv("FOSSIL_VFS"); + } + if( g.zVfsName ){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(g.zVfsName); + if( pVfs ){ + sqlite3_vfs_register(pVfs, 1); + }else{ + fossil_fatal("no such VFS: \"%s\"", g.zVfsName); + } + } if( fossil_getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){ zCmdName = "cgi"; g.isHTTP = 1; }else if( g.argc<2 ){ fossil_print( @@ -603,16 +661,23 @@ g.fQuiet = find_option("quiet", 0, 0)!=0; g.fSqlTrace = find_option("sqltrace", 0, 0)!=0; g.fSqlStats = find_option("sqlstats", 0, 0)!=0; g.fSystemTrace = find_option("systemtrace", 0, 0)!=0; g.fSshTrace = find_option("sshtrace", 0, 0)!=0; + g.fSshClient = 0; + g.zSshCmd = 0; if( g.fSqlTrace ) g.fSqlStats = 1; - g.fSqlPrint = find_option("sqlprint", 0, 0)!=0; g.fHttpTrace = find_option("httptrace", 0, 0)!=0; +#ifdef FOSSIL_ENABLE_TH1_HOOKS + g.fNoThHook = find_option("no-th-hook", 0, 0)!=0; +#endif + g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|g.fHttpTrace; + g.zHttpAuth = 0; g.zLogin = find_option("user", "U", 1); g.zSSLIdentity = find_option("ssl-identity", 0, 1); g.zErrlog = find_option("errorlog", 0, 1); + fossil_init_flags_from_options(); if( find_option("utc",0,0) ) g.fTimeFormat = 1; if( find_option("localtime",0,0) ) g.fTimeFormat = 2; if( zChdir && file_chdir(zChdir, 0) ){ fossil_fatal("unable to change directories to %s", zChdir); } @@ -629,18 +694,56 @@ g.argv = zNewArgv; } zCmdName = g.argv[1]; } #ifndef _WIN32 - if( !is_valid_fd(2) ) fossil_panic("file descriptor 2 not open"); - /* if( is_valid_fd(3) ) fossil_warning("file descriptor 3 is open"); */ + /* There is a bug in stunnel4 in which it sometimes starts up client + ** processes without first opening file descriptor 2 (standard error). + ** If this happens, and a subsequent open() of a database returns file + ** descriptor 2, and then an assert() fires and writes on fd 2, that + ** can corrupt the data file. To avoid this problem, make sure open() + ** will never return file descriptor 2 or less. */ + if( !is_valid_fd(2) ){ + int nTry = 0; + int fd = 0; + int x = 0; + do{ + fd = open("/dev/null",O_WRONLY); + if( fd>=2 ) break; + if( fd<0 ) x = errno; + }while( nTry++ < 2 ); + if( fd<2 ){ + g.cgiOutput = 1; + g.httpOut = stdout; + g.fullHttpReply = !g.isHTTP; + fossil_fatal("file descriptor 2 is not open. (fd=%d, errno=%d)", + fd, x); + } + } #endif - rc = name_search(zCmdName, aCommand, count(aCommand), &idx); + rc = name_search(zCmdName, aCommand, count(aCommand), FOSSIL_FIRST_CMD, &idx); if( rc==1 ){ - fossil_fatal("%s: unknown command: %s\n" - "%s: use \"help\" for more information\n", - g.argv[0], zCmdName, g.argv[0]); +#ifdef FOSSIL_ENABLE_TH1_HOOKS + if( !g.isHTTP && !g.fNoThHook ){ + rc = Th_CommandHook(zCmdName, 0); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ +#endif + fossil_fatal("%s: unknown command: %s\n" + "%s: use \"help\" for more information\n", + g.argv[0], zCmdName, g.argv[0]); +#ifdef FOSSIL_ENABLE_TH1_HOOKS + } + if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_CommandNotify(zCmdName, 0); + } + } + fossil_exit(0); +#endif }else if( rc==2 ){ int i, n; Blob couldbe; blob_zero(&couldbe); n = strlen(zCmdName); @@ -654,11 +757,44 @@ "%s: use \"help\" for more information\n", g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]); fossil_exit(1); } atexit( fossil_atexit ); - aCommand[idx].xFunc(); +#ifdef FOSSIL_ENABLE_TH1_HOOKS + /* + ** The TH1 return codes from the hook will be handled as follows: + ** + ** TH_OK: The xFunc() and the TH1 notification will both be executed. + ** + ** TH_ERROR: The xFunc() will be executed, the TH1 notification will be + ** skipped. If the xFunc() is being hooked, the error message + ** will be emitted. + ** + ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. + ** + ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be + ** skipped. + ** + ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be + ** executed. + */ + if( !g.isHTTP && !g.fNoThHook ){ + rc = Th_CommandHook(aCommand[idx].zName, aCommand[idx].cmdFlags); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ +#endif + aCommand[idx].xFunc(); +#ifdef FOSSIL_ENABLE_TH1_HOOKS + } + if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_CommandNotify(aCommand[idx].zName, aCommand[idx].cmdFlags); + } + } +#endif fossil_exit(0); /*NOT_REACHED*/ return 0; } @@ -724,10 +860,24 @@ break; } } return zReturn; } + +/* +** Look for a repository command-line option. If present, [re-]cache it in +** the global state and return the new pointer, freeing any previous value. +** If absent and there is no cached value, return NULL. +*/ +const char *find_repository_option(){ + const char *zRepository = find_option("repository", "R", 1); + if( zRepository ){ + if( g.zRepositoryOption ) fossil_free(g.zRepositoryOption); + g.zRepositoryOption = mprintf("%s", zRepository); + } + return g.zRepositoryOption; +} /* ** Verify that there are no unprocessed command-line options. If ** Any remaining command-line argument begins with "-" print ** an error message and quit. @@ -801,10 +951,28 @@ multi_column_list(aCmd, nCmd); } +/* +** This function returns a human readable version string. +*/ +const char *get_version(){ + static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " " + MANIFEST_DATE " UTC"; + return version; +} + +/* +** This function returns the user-agent string for Fossil, for +** use in HTTP(S) requests. +*/ +const char *get_user_agent(){ + static const char version[] = "Fossil/" RELEASE_VERSION " (" MANIFEST_DATE + " " MANIFEST_VERSION ")"; + return version; +} /* ** COMMAND: version ** ** Usage: %fossil version ?-verbose|-v? @@ -813,26 +981,42 @@ ** If the verbose option is specified, additional details will ** be output about what optional features this binary was compiled ** with */ void version_cmd(void){ - fossil_print("This is fossil version " RELEASE_VERSION " " - MANIFEST_VERSION " " MANIFEST_DATE " UTC\n"); - if(!find_option("verbose","v",0)){ + int verboseFlag = 0; + + fossil_print("This is fossil version %s\n", get_version()); + verboseFlag = find_option("verbose","v",0)!=0; + + /* We should be done with options.. */ + verify_all_options(); + + if(!verboseFlag){ return; }else{ #if defined(FOSSIL_ENABLE_TCL) int rc; const char *zRc; #endif fossil_print("Compiled on %s %s using %s (%d-bit)\n", __DATE__, __TIME__, COMPILER_NAME, sizeof(void*)*8); - fossil_print("SQLite %s %.30s\n", SQLITE_VERSION, SQLITE_SOURCE_ID); - fossil_print("Schema version %s\n", AUX_SCHEMA); + fossil_print("SQLite %s %.30s\n", sqlite3_libversion(), sqlite3_sourceid()); + fossil_print("Schema version %s\n", AUX_SCHEMA_MAX); +#if defined(FOSSIL_ENABLE_MINIZ) + fossil_print("miniz %s, loaded %s\n", MZ_VERSION, mz_version()); +#else fossil_print("zlib %s, loaded %s\n", ZLIB_VERSION, zlibVersion()); +#endif #if defined(FOSSIL_ENABLE_SSL) - fossil_print("SSL (%s)\n", OPENSSL_VERSION_TEXT); + fossil_print("SSL (%s)\n", SSLeay_version(SSLEAY_VERSION)); +#endif +#if defined(FOSSIL_ENABLE_TH1_DOCS) + fossil_print("TH1_DOCS\n"); +#endif +#if defined(FOSSIL_ENABLE_TH1_HOOKS) + fossil_print("TH1_HOOKS\n"); #endif #if defined(FOSSIL_ENABLE_TCL) Th_FossilInit(TH_INIT_DEFAULT | TH_INIT_FORCE_TCL); rc = Th_Eval(g.interp, 0, "tclInvoke info patchlevel", -1); zRc = Th_ReturnCodeName(rc, 0); @@ -850,34 +1034,39 @@ fossil_print("TCL_PRIVATE_STUBS\n"); #endif #if defined(FOSSIL_ENABLE_JSON) fossil_print("JSON (API %s)\n", FOSSIL_JSON_API_VERSION); #endif +#if defined(BROKEN_MINGW_CMDLINE) + fossil_print("MBCS_COMMAND_LINE\n"); +#else + fossil_print("UNICODE_COMMAND_LINE\n"); +#endif } } /* ** COMMAND: help ** ** Usage: %fossil help COMMAND -** or: %fossil COMMAND -help +** or: %fossil COMMAND --help ** ** Display information on how to use COMMAND. To display a list of ** available commands one of: ** ** %fossil help Show common commands -** %fossil help --a|-all Show both common and auxiliary commands -** %fossil help --t|-test Show test commands only -** %fossil help --x|-aux Show auxiliary commands only -** %fossil help --w|-www Show list of WWW pages +** %fossil help -a|--all Show both common and auxiliary commands +** %fossil help -t|--test Show test commands only +** %fossil help -x|--aux Show auxiliary commands only +** %fossil help -w|--www Show list of WWW pages */ void help_cmd(void){ int rc, idx, isPage = 0; const char *z; - char const * zCmdOrPage; - char const * zCmdOrPagePlural; + const char *zCmdOrPage; + const char *zCmdOrPagePlural; if( g.argc<3 ){ z = g.argv[0]; fossil_print( "Usage: %s help COMMAND\n" "Common COMMANDs: (use \"%s help -a|--all\" for a complete list)\n", @@ -908,11 +1097,11 @@ zCmdOrPagePlural = "pages"; }else{ zCmdOrPage = "command"; zCmdOrPagePlural = "commands"; } - rc = name_search(g.argv[2], aCommand, count(aCommand), &idx); + rc = name_search(g.argv[2], aCommand, count(aCommand), 0, &idx); if( rc==1 ){ fossil_print("unknown %s: %s\nAvailable %s:\n", zCmdOrPage, g.argv[2], zCmdOrPagePlural); command_list(0, isPage ? CMDFLAG_WEBPAGE : (0xff & ~CMDFLAG_WEBPAGE)); fossil_exit(1); @@ -942,21 +1131,21 @@ /* ** WEBPAGE: help ** URL: /help/CMD */ void help_page(void){ - const char * zCmd = P("cmd"); + const char *zCmd = P("cmd"); if( zCmd==0 ) zCmd = P("name"); style_header("Command-line Help"); if( zCmd ){ int rc, idx; char *z, *s, *d; - char const * zCmdOrPage = ('/'==*zCmd) ? "page" : "command"; + const char *zCmdOrPage = ('/'==*zCmd) ? "page" : "command"; style_submenu_element("Command-List", "Command-List", "%s/help", g.zTop); @ <h1>The "%s(zCmd)" %s(zCmdOrPage):</h1> - rc = name_search(zCmd, aCommand, count(aCommand), &idx); + rc = name_search(zCmd, aCommand, count(aCommand), 0, &idx); if( rc==1 ){ @ unknown command: %s(zCmd) }else if( rc==2 ){ @ ambiguous command prefix: %s(zCmd) }else{ @@ -994,11 +1183,11 @@ const char *z = aCommand[i].zName; if( '/'==*z || strncmp(z,"test",4)==0 ) continue; if( j==0 ){ @ <td valign="top"><ul> } - @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z)</a></li> + @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li> j++; if( j>=n ){ @ </ul></td> j = 0; } @@ -1006,11 +1195,11 @@ if( j>0 ){ @ </ul></td> } @ </tr></table> - @ <h1>Available pages:</h1> + @ <h1>Available web UI pages:</h1> @ (Only pages with help text are linked.) @ <table border="0"><tr> for(i=j=0; i<count(aCommand); i++){ const char *z = aCommand[i].zName; if( '/'!=*z ) continue; @@ -1022,14 +1211,44 @@ if( '/'!=*z ) continue; if( j==0 ){ @ <td valign="top"><ul> } if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){ - @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z+1)</a></li> + @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li> }else{ @ <li>%s(z+1)</li> } + j++; + if( j>=n ){ + @ </ul></td> + j = 0; + } + } + if( j>0 ){ + @ </ul></td> + } + @ </tr></table> + + @ <h1>Unsupported commands:</h1> + @ <table border="0"><tr> + for(i=j=0; i<count(aCommand); i++){ + const char *z = aCommand[i].zName; + if( strncmp(z,"test",4)!=0 ) continue; + j++; + } + n = (j+3)/4; + for(i=j=0; i<count(aCommand); i++){ + const char *z = aCommand[i].zName; + if( strncmp(z,"test",4)!=0 ) continue; + if( j==0 ){ + @ <td valign="top"><ul> + } + if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){ + @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li> + }else{ + @ <li>%s(z)</li> + } j++; if( j>=n ){ @ </ul></td> j = 0; } @@ -1106,13 +1325,15 @@ i = strlen(zCur); while( i>0 && zCur[i-1]=='/' ) i--; if( fossil_stricmp(zMode,"on")==0 ){ g.zBaseURL = mprintf("https://%s%.*s", zHost, i, zCur); g.zTop = &g.zBaseURL[8+strlen(zHost)]; + g.zHttpsURL = g.zBaseURL; }else{ g.zBaseURL = mprintf("http://%s%.*s", zHost, i, zCur); g.zTop = &g.zBaseURL[7+strlen(zHost)]; + g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur); } } if( db_is_writeable("repository") ){ if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){ db_multi_exec("INSERT INTO config(name,value,mtime)" @@ -1141,12 +1362,15 @@ ** zRepo might be a directory itself. In that case chroot into ** the directory zRepo. ** ** Assume the user-id and group-id of the repository, or if zRepo ** is a directory, of that directory. +** +** The noJail flag means that the chroot jail is not entered. But +** privileges are still lowered to that of the the user-id and group-id. */ -static char *enter_chroot_jail(char *zRepo){ +static char *enter_chroot_jail(char *zRepo, int noJail){ #if !defined(_WIN32) if( getuid()==0 ){ int i; struct stat sStat; Blob dir; @@ -1155,39 +1379,82 @@ db_close(1); } file_canonical_name(zRepo, &dir, 0); zDir = blob_str(&dir); - if( file_isdir(zDir)==1 ){ - if( file_chdir(zDir, 1) ){ - fossil_fatal("unable to chroot into %s", zDir); - } - zRepo = "/"; - }else{ - for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){} - if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo); - if( i>0 ){ - zDir[i] = 0; + if( !noJail ){ + if( file_isdir(zDir)==1 ){ if( file_chdir(zDir, 1) ){ fossil_fatal("unable to chroot into %s", zDir); } - zDir[i] = '/'; + zRepo = "/"; + }else{ + for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){} + if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo); + if( i>0 ){ + zDir[i] = 0; + if( file_chdir(zDir, 1) ){ + fossil_fatal("unable to chroot into %s", zDir); + } + zDir[i] = '/'; + } + zRepo = &zDir[i]; } - zRepo = &zDir[i]; } if( stat(zRepo, &sStat)!=0 ){ fossil_fatal("cannot stat() repository: %s", zRepo); } i = setgid(sStat.st_gid); i = i || setuid(sStat.st_uid); if(i){ fossil_fatal("setgid/uid() failed with errno %d", errno); } + if( g.db==0 && file_isfile(zRepo) ){ + db_open_repository(zRepo); + } } #endif return zRepo; } + +/* +** Generate a web-page that lists all repositories located under the +** g.zRepositoryName directory and return non-zero. +** +** Or, if no repositories can be located beneath g.zRepositoryName, +** return 0. +*/ +static int repo_list_page(void){ + Blob base; + int n = 0; + + assert( g.db==0 ); + blob_init(&base, g.zRepositoryName, -1); + sqlite3_open(":memory:", &g.db); + db_multi_exec("CREATE TABLE sfile(x TEXT);"); + db_multi_exec("CREATE TABLE vfile(pathname);"); + vfile_scan(&base, blob_size(&base), 0, 0, 0); + db_multi_exec("DELETE FROM sfile WHERE x NOT GLOB '*.fossil'"); + n = db_int(0, "SELECT count(*) FROM sfile"); + if( n>0 ){ + Stmt q; + @ <h1>Available Repositories:</h1> + @ <ol> + db_prepare(&q, "SELECT x, substr(x,-7,-100000)||'/home'" + " FROM sfile ORDER BY x COLLATE nocase;"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q, 0); + const char *zUrl = db_column_text(&q, 1); + @ <li><a href="%h(zUrl)">%h(zName)</a></li> + } + @ </ol> + cgi_reply(); + } + sqlite3_close(g.db); + g.db = 0; + return n; +} /* ** Preconditions: ** ** * Environment variables are set up according to the CGI standard. @@ -1207,11 +1474,15 @@ ** $prefix can be determined from its suffix, then the file $prefix is ** returned as static text. ** ** If no suitable webpage is found, try to redirect to zNotFound. */ -static void process_one_web_page(const char *zNotFound, Glob *pFileGlob){ +static void process_one_web_page( + const char *zNotFound, /* Redirect here on a 404 if not NULL */ + Glob *pFileGlob, /* Deliver static files matching */ + int allowRepoList /* Send repo list for "/" URL */ +){ const char *zPathInfo; char *zPath = NULL; int idx; int i; @@ -1251,12 +1522,18 @@ break; } if( szFile==0 ){ if( zRepo[0]=='/' && zRepo[1]=='/' ){ zRepo++; j--; } szFile = file_size(zRepo); + /* this should only be set from the --baseurl option, not CGI */ + if( g.zBaseURL && g.zBaseURL[0]!=0 && g.zTop && g.zTop[0]!=0 && + file_isdir(g.zRepositoryName)==1 ){ + g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo); + g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo); + } } - if( szFile<0 ){ + if( szFile<0 && i>0 ){ const char *zMimetype; assert( fossil_strcmp(&zRepo[j], ".fossil")==0 ); zRepo[j] = 0; if( zPathInfo[i]=='/' && file_isdir(zRepo)==1 ){ fossil_free(zToFree); @@ -1264,11 +1541,11 @@ continue; } if( pFileGlob!=0 && file_isfile(zRepo) && glob_match(pFileGlob, zRepo) - && strglob("*.fossil*",zRepo)==0 + && sqlite3_strglob("*.fossil*",zRepo)!=0 && (zMimetype = mimetype_from_name(zRepo))!=0 && strcmp(zMimetype, "application/x-fossil-artifact")!=0 ){ Blob content; blob_read_from_file(&content, zRepo); @@ -1282,10 +1559,14 @@ if( szFile<1024 ){ set_base_url(0); if( zNotFound ){ cgi_redirect(zNotFound); + }else if( strcmp(zPathInfo,"/")==0 + && allowRepoList + && repo_list_page() ){ + /* Will return a list of repositories */ }else{ #ifdef FOSSIL_ENABLE_JSON if(g.json.isJsonMode){ json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); return; @@ -1314,11 +1595,12 @@ } /* Find the page that the user has requested, construct and deliver that ** page. */ - if( g.zContentType && memcmp(g.zContentType, "application/x-fossil", 20)==0 ){ + if( g.zContentType && + strncmp(g.zContentType, "application/x-fossil", 20)==0 ){ zPathInfo = "/xfer"; } set_base_url(0); if( zPathInfo==0 || zPathInfo[0]==0 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){ @@ -1364,11 +1646,11 @@ zAltRepo[jj] = 0; zAltRepo += jj+1; }else{ zUser = "nobody"; } - if( g.zLogin==0 ) zUser = "nobody"; + if( g.zLogin==0 || g.zLogin[0]==0 ) zUser = "nobody"; if( zAltRepo[0]!='/' ){ zAltRepo = mprintf("%s/../%s", g.zRepositoryName, zAltRepo); file_simplify_name(zAltRepo, -1, 0); } db_close(1); @@ -1420,21 +1702,37 @@ } /* Locate the method specified by the path and execute the function ** that implements that method. */ - if( name_search(g.zPath, aWebpage, count(aWebpage), &idx) && - name_search("not_found", aWebpage, count(aWebpage), &idx) ){ + if( name_search(g.zPath, aWebpage, count(aWebpage), 0, &idx) ){ #ifdef FOSSIL_ENABLE_JSON if(g.json.isJsonMode){ json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0); }else #endif { - cgi_set_status(404,"Not Found"); - @ <h1>Not Found</h1> - @ <p>Page not found: %h(g.zPath)</p> +#ifdef FOSSIL_ENABLE_TH1_HOOKS + int rc; + if( !g.fNoThHook ){ + rc = Th_WebpageHook(g.zPath, 0); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ +#endif + cgi_set_status(404,"Not Found"); + @ <h1>Not Found</h1> + @ <p>Page not found: %h(g.zPath)</p> +#ifdef FOSSIL_ENABLE_TH1_HOOKS + } + if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_WebpageNotify(g.zPath, 0); + } + } +#endif } }else if( aWebpage[idx].xFunc!=page_xfer && db_schema_is_outofdate() ){ #ifdef FOSSIL_ENABLE_JSON if(g.json.isJsonMode){ json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0); @@ -1444,11 +1742,45 @@ @ <h1>Server Configuration Error</h1> @ <p>The database schema on the server is out-of-date. Please ask @ the administrator to run <b>fossil rebuild</b>.</p> } }else{ - aWebpage[idx].xFunc(); +#ifdef FOSSIL_ENABLE_TH1_HOOKS + /* + ** The TH1 return codes from the hook will be handled as follows: + ** + ** TH_OK: The xFunc() and the TH1 notification will both be executed. + ** + ** TH_ERROR: The xFunc() will be executed, the TH1 notification will be + ** skipped. If the xFunc() is being hooked, the error message + ** will be emitted. + ** + ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. + ** + ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be + ** skipped. + ** + ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be + ** executed. + */ + int rc; + if( !g.fNoThHook ){ + rc = Th_WebpageHook(aWebpage[idx].zName, aWebpage[idx].cmdFlags); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ +#endif + aWebpage[idx].xFunc(); +#ifdef FOSSIL_ENABLE_TH1_HOOKS + } + if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_WebpageNotify(aWebpage[idx].zName, aWebpage[idx].cmdFlags); + } + } +#endif } /* Return the result. */ cgi_reply(); @@ -1457,13 +1789,13 @@ /* If the CGI program contains one or more lines of the form ** ** redirect: repository-filename http://hostname/path/%s ** ** then control jumps here. Search each repository for an artifact ID -** that matches the "name" CGI parameter and for the first match, -** redirect to the corresponding URL with the "name" CGI parameter -** inserted. Paint an error page if no match is found. +** or ticket ID that matches the "name" CGI parameter and for the +** first match, redirect to the corresponding URL with the "name" CGI +** parameter inserted. Paint an error page if no match is found. ** ** If there is a line of the form: ** ** redirect: * URL ** @@ -1484,19 +1816,20 @@ if( fossil_strcmp(azRedirect[i*2],"*")==0 ){ zNotFound = azRedirect[i*2+1]; continue; } db_open_repository(azRedirect[i*2]); - if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%s*'", zName) ){ - cgi_redirectf(azRedirect[i*2+1], zName); + if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'", zName) || + db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){ + cgi_redirectf(azRedirect[i*2+1] /*works-like:"%s"*/, zName); return; } db_close(1); } } if( zNotFound ){ - cgi_redirectf(zNotFound, zName); + cgi_redirectf(zNotFound /*works-like:"%s"*/, zName); }else{ @ <html> @ <head><title>No Such Object @ @

No such object: %h(zName)

@@ -1529,10 +1862,11 @@ const char *zFile; const char *zNotFound = 0; char **azRedirect = 0; /* List of repositories to redirect to */ int nRedirect = 0; /* Number of entries in azRedirect */ Glob *pFileGlob = 0; /* Pattern for files */ + int allowRepoList = 0; /* Allow lists of repository files */ Blob config, line, key, value, value2; if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){ zFile = g.argv[2]; }else{ zFile = g.argv[1]; @@ -1544,57 +1878,140 @@ g.cgiOutput = 1; blob_read_from_file(&config, zFile); while( blob_line(&config, &line) ){ if( !blob_token(&line, &key) ) continue; if( blob_buffer(&key)[0]=='#' ) continue; - if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){ - g.fDebug = fossil_fopen(blob_str(&value), "ab"); - blob_reset(&value); - continue; - } - if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){ - g.zErrlog = mprintf("%s", blob_str(&value)); - continue; - } - if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){ - cgi_setenv("HOME", blob_str(&value)); - blob_reset(&value); - continue; - } if( blob_eq(&key, "repository:") && blob_tail(&line, &value) ){ + /* repository: FILENAME + ** + ** The name of the Fossil repository to be served via CGI. Most + ** fossil CGI scripts have a single non-comment line that contains + ** this one entry. + */ blob_trim(&value); db_open_repository(blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){ + /* directory: DIRECTORY + ** + ** If repository: is omitted, then terms of the PATH_INFO cgi parameter + ** are appended to DIRECTORY looking for a repository (whose name ends + ** in ".fossil") or a file in "files:". + */ db_close(1); g.zRepositoryName = mprintf("%s", blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){ + /* notfound: URL + ** + ** If using directory: and no suitable repository or file is found, + ** then redirect to URL. + */ zNotFound = mprintf("%s", blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "localauth") ){ + /* localauth + ** + ** Grant "administrator" privileges to users connecting with HTTP + ** from IP address 127.0.0.1. Do not bother checking credentials. + */ g.useLocalauth = 1; continue; + } + if( blob_eq(&key, "repolist") ){ + /* repolist + ** + ** If using "directory:" and the URL is "/" then generate a page + ** showing a list of available repositories. + */ + allowRepoList = 1; + continue; } if( blob_eq(&key, "redirect:") && blob_token(&line, &value) && blob_token(&line, &value2) ){ + /* See the header comment on the redirect_web_page() function + ** above for details. */ nRedirect++; azRedirect = fossil_realloc(azRedirect, 2*nRedirect*sizeof(char*)); azRedirect[nRedirect*2-2] = mprintf("%s", blob_str(&value)); azRedirect[nRedirect*2-1] = mprintf("%s", blob_str(&value2)); blob_reset(&value); blob_reset(&value2); continue; } if( blob_eq(&key, "files:") && blob_token(&line, &value) ){ + /* files: GLOBLIST + ** + ** GLOBLIST is a comma-separated list of filename globs. For + ** example: *.html,*.css,*.js + ** + ** If the repository: line is omitted and then PATH_INFO is searched + ** for files that match any of these GLOBs and if any such file is + ** found it is returned verbatim. This feature allows "fossil server" + ** to function as a primitive web-server delivering arbitrary content. + */ pFileGlob = glob_create(blob_str(&value)); + blob_reset(&value); + continue; + } + if( blob_eq(&key, "setenv:") && blob_token(&line, &value) + && blob_token(&line, &value2) ){ + /* setenv: NAME VALUE + ** + ** Sets environment variable NAME to VALUE + */ + fossil_setenv(blob_str(&value), blob_str(&value2)); + blob_reset(&value); + blob_reset(&value2); + continue; + } + if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){ + /* debug: FILENAME + ** + ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go + ** into FILENAME. + */ + g.fDebug = fossil_fopen(blob_str(&value), "ab"); + blob_reset(&value); + continue; + } + if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){ + /* errorlog: FILENAME + ** + ** Causes messages from warnings, errors, and panics to be appended + ** to FILENAME. + */ + g.zErrlog = mprintf("%s", blob_str(&value)); + blob_reset(&value); + continue; + } + if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){ + /* HOME: VALUE + ** + ** Set CGI parameter "HOME" to VALUE. This is legacy. Use + ** setenv: instead. + */ + cgi_setenv("HOME", blob_str(&value)); + blob_reset(&value); + continue; + } + if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){ + /* skin: LABEL + ** + ** Use one of the built-in skins defined by LABEL. LABEL is the + ** name of the subdirectory under the skins/ directory that holds + ** the elements of the built-in skin. If LABEL does not match, + ** this directive is a silent no-op. + */ + skin_use_alternative(blob_str(&value)); + blob_reset(&value); continue; } } blob_reset(&config); if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){ @@ -1602,57 +2019,74 @@ } cgi_init(); if( nRedirect ){ redirect_web_page(nRedirect, azRedirect); }else{ - process_one_web_page(zNotFound, pFileGlob); + process_one_web_page(zNotFound, pFileGlob, allowRepoList); } } /* -** If g.argv[2] exists then it is either the name of a repository +** If g.argv[arg] exists then it is either the name of a repository ** that will be used by a server, or else it is a directory that -** contains multiple repositories that can be served. If g.argv[2] +** contains multiple repositories that can be served. If g.argv[arg] ** is a directory, the repositories it contains must be named -** "*.fossil". If g.argv[2] does not exists, then we must be within +** "*.fossil". If g.argv[arg] does not exists, then we must be within ** a check-out and the repository to be served is the repository of ** that check-out. ** -** Open the repository to be served if it is known. If g.argv[2] is +** Open the repository to be served if it is known. If g.argv[arg] is ** a directory full of repositories, then set g.zRepositoryName to ** the name of that directory and the specific repository will be ** opened later by process_one_web_page() based on the content of ** the PATH_INFO variable. ** -** If disallowDir is set, then the directory full of repositories method -** is disallowed. +** If the fCreate flag is set, then create the repository if it +** does not already exist. */ -static void find_server_repository(int disallowDir){ - if( g.argc<3 ){ +static void find_server_repository(int arg, int fCreate){ + if( g.argc<=arg ){ db_must_be_within_tree(); - }else if( file_isdir(g.argv[2])==1 ){ - if( disallowDir ){ - fossil_fatal("\"%s\" is a directory, not a repository file", g.argv[2]); - }else{ - g.zRepositoryName = mprintf("%s", g.argv[2]); + }else{ + const char *zRepo = g.argv[arg]; + int isDir = file_isdir(zRepo); + if( isDir==1 ){ + g.zRepositoryName = mprintf("%s", zRepo); file_simplify_name(g.zRepositoryName, -1, 0); + }else{ + if( isDir==0 && fCreate ){ + const char *zPassword; + db_create_repository(zRepo); + db_open_repository(zRepo); + db_begin_transaction(); + db_initial_setup(0, "now", g.zLogin); + db_end_transaction(0); + fossil_print("project-id: %s\n", db_get("project-code", 0)); + fossil_print("server-id: %s\n", db_get("server-code", 0)); + zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); + fossil_print("admin-user: %s (initial password is \"%s\")\n", + g.zLogin, zPassword); + cache_initialize(); + g.zLogin = 0; + g.userUid = 0; + }else{ + db_open_repository(zRepo); + } } - }else{ - db_open_repository(g.argv[2]); } } /* ** undocumented format: ** -** fossil http REPOSITORY INFILE OUTFILE IPADDR +** fossil http INFILE OUTFILE IPADDR ?REPOSITORY? ** ** The argv==6 form is used by the win32 server only. ** ** COMMAND: http* ** -** Usage: %fossil http REPOSITORY ?OPTIONS? +** Usage: %fossil http ?REPOSITORY? ?OPTIONS? ** ** Handle a single HTTP request appearing on stdin. The resulting webpage ** is delivered on stdout. This method is used to launch an HTTP request ** handler from inetd, for example. The argument is the name of the ** repository. @@ -1678,28 +2112,33 @@ ** If the --localauth option is given, then automatic login is performed ** for requests coming from localhost, if the "localauth" setting is not ** enabled. ** ** Options: +** --baseurl URL base URL (useful with reverse proxies) +** --files GLOB comma-separate glob patterns for static file to serve ** --localauth enable automatic login for local connections ** --host NAME specify hostname of the server ** --https signal a request coming in via https +** --nojail drop root privilege but do not enter the chroot jail ** --nossl signal that no SSL connections are available ** --notfound URL use URL as "HTTP 404, object not found" page. -** --files GLOB comma-separate glob patterns for static file to serve -** --baseurl URL base URL (useful with reverse proxies) +** --repolist If REPOSITORY is directory, URL "/" lists all repos ** --scgi Interpret input as SCGI rather than HTTP +** --skin LABEL Use override skin LABEL ** ** See also: cgi, server, winsrv */ void cmd_http(void){ - const char *zIpAddr; + const char *zIpAddr = 0; const char *zNotFound; const char *zHost; const char *zAltBase; const char *zFileGlob; int useSCGI; + int noJail; + int allowRepoList; /* The winhttp module passes the --files option as --files-urlenc with ** the argument being URL encoded, to avoid wildcard expansion in the ** shell. This option is for internal use and is undocumented. */ @@ -1709,61 +2148,101 @@ dehttpize(z); zFileGlob = z; }else{ zFileGlob = find_option("files",0,1); } + skin_override(); zNotFound = find_option("notfound", 0, 1); + noJail = find_option("nojail",0,0)!=0; + allowRepoList = find_option("repolist",0,0)!=0; g.useLocalauth = find_option("localauth", 0, 0)!=0; g.sslNotAvailable = find_option("nossl", 0, 0)!=0; useSCGI = find_option("scgi", 0, 0)!=0; zAltBase = find_option("baseurl", 0, 1); if( zAltBase ) set_base_url(zAltBase); - if( find_option("https",0,0)!=0 ) cgi_replace_parameter("HTTPS","on"); + if( find_option("https",0,0)!=0 ){ + zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */ + cgi_replace_parameter("HTTPS","on"); + } zHost = find_option("host", 0, 1); if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost); g.cgiOutput = 1; - if( g.argc!=2 && g.argc!=3 && g.argc!=6 ){ + + /* We should be done with options.. */ + verify_all_options(); + + if( g.argc!=2 && g.argc!=3 && g.argc!=5 && g.argc!=6 ){ fossil_fatal("no repository specified"); } g.fullHttpReply = 1; - if( g.argc==6 ){ - g.httpIn = fossil_fopen(g.argv[3], "rb"); - g.httpOut = fossil_fopen(g.argv[4], "wb"); - zIpAddr = g.argv[5]; + if( g.argc>=5 ){ + g.httpIn = fossil_fopen(g.argv[2], "rb"); + g.httpOut = fossil_fopen(g.argv[3], "wb"); + zIpAddr = g.argv[4]; + find_server_repository(5, 0); }else{ g.httpIn = stdin; g.httpOut = stdout; - zIpAddr = 0; + find_server_repository(2, 0); + } + if( zIpAddr==0 ){ + zIpAddr = cgi_ssh_remote_addr(0); + if( zIpAddr && zIpAddr[0] ){ + g.fSshClient |= CGI_SSH_CLIENT; + } } - find_server_repository(0); - g.zRepositoryName = enter_chroot_jail(g.zRepositoryName); + g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); if( useSCGI ){ cgi_handle_scgi_request(); + }else if( g.fSshClient & CGI_SSH_CLIENT ){ + ssh_request_loop(zIpAddr, glob_create(zFileGlob)); }else{ cgi_handle_http_request(zIpAddr); } - process_one_web_page(zNotFound, glob_create(zFileGlob)); + process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); +} + +/* +** Process all requests in a single SSH connection if possible. +*/ +void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){ + blob_zero(&g.cgiIn); + do{ + cgi_handle_ssh_http_request(zIpAddr); + process_one_web_page(0, FileGlob, 0); + blob_reset(&g.cgiIn); + } while ( g.fSshClient & CGI_SSH_FOSSIL || + g.fSshClient & CGI_SSH_COMPAT ); } /* ** Note that the following command is used by ssh:// processing. ** ** COMMAND: test-http ** Works like the http command but gives setup permission to all users. +** */ void cmd_test_http(void){ + const char *zIpAddr; /* IP address of remote client */ + Th_InitTraceLog(); login_set_capabilities("sx", 0); g.useLocalauth = 1; - cgi_set_parameter("REMOTE_ADDR", "127.0.0.1"); g.httpIn = stdin; g.httpOut = stdout; - find_server_repository(0); + find_server_repository(2, 0); g.cgiOutput = 1; g.fullHttpReply = 1; - cgi_handle_http_request(0); - process_one_web_page(0, 0); + zIpAddr = cgi_ssh_remote_addr(0); + if( zIpAddr && zIpAddr[0] ){ + g.fSshClient |= CGI_SSH_CLIENT; + ssh_request_loop(zIpAddr, 0); + }else{ + cgi_set_parameter("REMOTE_ADDR", "127.0.0.1"); + cgi_handle_http_request(0); + process_one_web_page(0, 0, 0); + } } #if !defined(_WIN32) #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__) /* @@ -1805,12 +2284,12 @@ ** The "ui" command automatically starts a web browser after initializing ** the web server. The "ui" command also binds to 127.0.0.1 and so will ** only process HTTP traffic from the local machine. ** ** The REPOSITORY can be a directory (aka folder) that contains one or -** more repositories with names ending in ".fossil". In this case, the -** a prefix of the URL pathname is used to search the directory for an +** more repositories with names ending in ".fossil". In this case, a +** prefix of the URL pathname is used to search the directory for an ** appropriate repository. To thwart mischief, the pathname in the URL must ** contain only alphanumerics, "_", "/", "-", and ".", and no "-" may ** occur after "/", and every "." must be surrounded on both sides by ** alphanumerics. Any pathname that does not satisfy these constraints ** results in a 404 error. Files in REPOSITORY that match the comma-separated @@ -1819,26 +2298,30 @@ ** "*.fossil*" will be served as static content. With the "ui" command, ** the REPOSITORY can only be a directory if the --notfound option is ** also present. ** ** By default, the "ui" command provides full administrative access without -** having to log in. This can be disabled by setting turning off the -** "localauth" setting. Automatic login for the "server" command is available -** if the --localauth option is present and the "localauth" setting is off -** and the connection is from localhost. The optional REPOSITORY argument -** to "ui" may be a directory and will function as "server" if and only if -** the --notfound option is used. +** having to log in. This can be disabled by turning off the "localauth" +** setting. Automatic login for the "server" command is available if the +** --localauth option is present and the "localauth" setting is off and the +** connection is from localhost. The "ui" command also enables --repolist +** by default. ** ** Options: +** --baseurl URL Use URL as the base (useful for reverse proxies) +** --create Create a new REPOSITORY if it does not already exist +** --files GLOBLIST Comma-separated list of glob patterns for static files ** --localauth enable automatic login for requests from localhost ** --localhost listen on 127.0.0.1 only (always true for "ui") +** --nojail Drop root privileges but do not enter the chroot jail +** --notfound URL Redirect ** -P|--port TCPPORT listen to request on port TCPPORT ** --th-trace trace TH1 execution (for debugging purposes) -** --baseurl URL Use URL as the base (useful for reverse proxies) -** --notfound URL Redirect -** --files GLOBLIST Comma-separated list of glob patterns for static files +** --repolist If REPOSITORY is dir, URL "/" lists repos. ** --scgi Accept SCGI rather than HTTP +** --skin LABEL Use override skin LABEL + ** ** See also: cgi, http, winsrv */ void cmd_webserver(void){ int iPort, mxPort; /* Range of TCP ports allowed */ @@ -1846,39 +2329,62 @@ const char *zBrowser; /* Name of web browser program */ char *zBrowserCmd = 0; /* Command to launch the web browser */ int isUiCmd; /* True if command is "ui", not "server' */ const char *zNotFound; /* The --notfound option or NULL */ int flags = 0; /* Server flags */ +#if !defined(_WIN32) + int noJail; /* Do not enter the chroot jail */ +#endif + int allowRepoList; /* List repositories on URL "/" */ const char *zAltBase; /* Argument to the --baseurl option */ const char *zFileGlob; /* Static content must match this */ char *zIpAddr = 0; /* Bind to this IP address */ + int fCreate = 0; #if defined(_WIN32) const char *zStopperFile; /* Name of file used to terminate server */ zStopperFile = find_option("stopper", 0, 1); #endif - zFileGlob = find_option("files", 0, 1); + zFileGlob = find_option("files-urlenc",0,1); + if( zFileGlob ){ + char *z = mprintf("%s", zFileGlob); + dehttpize(z); + zFileGlob = z; + }else{ + zFileGlob = find_option("files",0,1); + } + skin_override(); +#if !defined(_WIN32) + noJail = find_option("nojail",0,0)!=0; +#endif g.useLocalauth = find_option("localauth", 0, 0)!=0; Th_InitTraceLog(); zPort = find_option("port", "P", 1); zNotFound = find_option("notfound", 0, 1); + allowRepoList = find_option("repolist",0,0)!=0; zAltBase = find_option("baseurl", 0, 1); + fCreate = find_option("create",0,0)!=0; if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI; if( zAltBase ){ set_base_url(zAltBase); } - if ( find_option("localhost", 0, 0)!=0 ){ + if( find_option("localhost", 0, 0)!=0 ){ flags |= HTTP_SERVER_LOCALHOST; } + + /* We should be done with options.. */ + verify_all_options(); + if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); isUiCmd = g.argv[1][0]=='u'; if( isUiCmd ){ - flags |= HTTP_SERVER_LOCALHOST; + flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST; g.useLocalauth = 1; + allowRepoList = 1; } - find_server_repository(isUiCmd && zNotFound==0); + find_server_repository(2, fCreate); if( zPort ){ int i; for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){} if( i>0 ){ zIpAddr = mprintf("%.*s", i, zPort); @@ -1912,10 +2418,12 @@ if( zIpAddr ){ zBrowserCmd = mprintf("%s http://%s:%%d/ &", zBrowser, zIpAddr); }else{ zBrowserCmd = mprintf("%s http://localhost:%%d/ &", zBrowser); } + if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY; + if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT; } db_close(1); if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){ fossil_fatal("unable to listen on TCP socket %d", iPort); } @@ -1924,29 +2432,34 @@ g.httpOut = stdout; if( g.fHttpTrace || g.fSqlTrace ){ fprintf(stderr, "====== SERVER pid %d =======\n", getpid()); } g.cgiOutput = 1; - find_server_repository(isUiCmd && zNotFound==0); - g.zRepositoryName = enter_chroot_jail(g.zRepositoryName); + find_server_repository(2, 0); + g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); if( flags & HTTP_SERVER_SCGI ){ cgi_handle_scgi_request(); }else{ cgi_handle_http_request(0); } - process_one_web_page(zNotFound, glob_create(zFileGlob)); + process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); #else /* Win32 implementation */ if( isUiCmd ){ zBrowser = db_get("web-browser", "start"); if( zIpAddr ){ zBrowserCmd = mprintf("%s http://%s:%%d/ &", zBrowser, zIpAddr); }else{ zBrowserCmd = mprintf("%s http://localhost:%%d/ &", zBrowser); } + if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY; + if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT; } db_close(1); + if( allowRepoList ){ + flags |= HTTP_SERVER_REPOLIST; + } if( win32_http_service(iPort, zNotFound, zFileGlob, flags) ){ win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile, zNotFound, zFileGlob, zIpAddr, flags); } #endif Index: src/main.mk ================================================================== --- src/main.mk +++ src/main.mk @@ -8,11 +8,11 @@ # to regenerate this file. # # This file is included by primary Makefile. # -XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) -I$(OBJDIR) +XTCC = $(TCC) -I. -I$(SRCDIR) -I$(OBJDIR) $(TCCFLAGS) $(CFLAGS) SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/allrepo.c \ @@ -20,10 +20,13 @@ $(SRCDIR)/bag.c \ $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ + $(SRCDIR)/builtin.c \ + $(SRCDIR)/bundle.c \ + $(SRCDIR)/cache.c \ $(SRCDIR)/captcha.c \ $(SRCDIR)/cgi.c \ $(SRCDIR)/checkin.c \ $(SRCDIR)/checkout.c \ $(SRCDIR)/clearsign.c \ @@ -41,10 +44,12 @@ $(SRCDIR)/encode.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/file.c \ $(SRCDIR)/finfo.c \ + $(SRCDIR)/foci.c \ + $(SRCDIR)/fusefs.c \ $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/gzip.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ @@ -66,10 +71,11 @@ $(SRCDIR)/json_tag.c \ $(SRCDIR)/json_timeline.c \ $(SRCDIR)/json_user.c \ $(SRCDIR)/json_wiki.c \ $(SRCDIR)/leaf.c \ + $(SRCDIR)/loadctrl.c \ $(SRCDIR)/login.c \ $(SRCDIR)/lookslike.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/markdown.c \ @@ -82,23 +88,27 @@ $(SRCDIR)/path.c \ $(SRCDIR)/pivot.c \ $(SRCDIR)/popen.c \ $(SRCDIR)/pqueue.c \ $(SRCDIR)/printf.c \ + $(SRCDIR)/publish.c \ + $(SRCDIR)/purge.c \ $(SRCDIR)/rebuild.c \ $(SRCDIR)/regexp.c \ $(SRCDIR)/report.c \ $(SRCDIR)/rss.c \ $(SRCDIR)/schema.c \ $(SRCDIR)/search.c \ $(SRCDIR)/setup.c \ $(SRCDIR)/sha1.c \ $(SRCDIR)/shun.c \ + $(SRCDIR)/sitemap.c \ $(SRCDIR)/skins.c \ $(SRCDIR)/sqlcmd.c \ $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ + $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ $(SRCDIR)/th_main.c \ @@ -114,25 +124,80 @@ $(SRCDIR)/util.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ + $(SRCDIR)/winfile.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/wysiwyg.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/xfersetup.c \ $(SRCDIR)/zip.c +EXTRA_FILES = \ + $(SRCDIR)/../skins/aht/details.txt \ + $(SRCDIR)/../skins/black_and_white/css.txt \ + $(SRCDIR)/../skins/black_and_white/details.txt \ + $(SRCDIR)/../skins/black_and_white/footer.txt \ + $(SRCDIR)/../skins/black_and_white/header.txt \ + $(SRCDIR)/../skins/blitz/css.txt \ + $(SRCDIR)/../skins/blitz/details.txt \ + $(SRCDIR)/../skins/blitz/footer.txt \ + $(SRCDIR)/../skins/blitz/header.txt \ + $(SRCDIR)/../skins/blitz/ticket.txt \ + $(SRCDIR)/../skins/blitz_no_logo/css.txt \ + $(SRCDIR)/../skins/blitz_no_logo/details.txt \ + $(SRCDIR)/../skins/blitz_no_logo/footer.txt \ + $(SRCDIR)/../skins/blitz_no_logo/header.txt \ + $(SRCDIR)/../skins/blitz_no_logo/ticket.txt \ + $(SRCDIR)/../skins/default/css.txt \ + $(SRCDIR)/../skins/default/details.txt \ + $(SRCDIR)/../skins/default/footer.txt \ + $(SRCDIR)/../skins/default/header.txt \ + $(SRCDIR)/../skins/eagle/css.txt \ + $(SRCDIR)/../skins/eagle/details.txt \ + $(SRCDIR)/../skins/eagle/footer.txt \ + $(SRCDIR)/../skins/eagle/header.txt \ + $(SRCDIR)/../skins/enhanced1/css.txt \ + $(SRCDIR)/../skins/enhanced1/details.txt \ + $(SRCDIR)/../skins/enhanced1/footer.txt \ + $(SRCDIR)/../skins/enhanced1/header.txt \ + $(SRCDIR)/../skins/khaki/css.txt \ + $(SRCDIR)/../skins/khaki/details.txt \ + $(SRCDIR)/../skins/khaki/footer.txt \ + $(SRCDIR)/../skins/khaki/header.txt \ + $(SRCDIR)/../skins/original/css.txt \ + $(SRCDIR)/../skins/original/details.txt \ + $(SRCDIR)/../skins/original/footer.txt \ + $(SRCDIR)/../skins/original/header.txt \ + $(SRCDIR)/../skins/plain_gray/css.txt \ + $(SRCDIR)/../skins/plain_gray/details.txt \ + $(SRCDIR)/../skins/plain_gray/footer.txt \ + $(SRCDIR)/../skins/plain_gray/header.txt \ + $(SRCDIR)/../skins/rounded1/css.txt \ + $(SRCDIR)/../skins/rounded1/details.txt \ + $(SRCDIR)/../skins/rounded1/footer.txt \ + $(SRCDIR)/../skins/rounded1/header.txt \ + $(SRCDIR)/../skins/xekri/css.txt \ + $(SRCDIR)/../skins/xekri/details.txt \ + $(SRCDIR)/../skins/xekri/footer.txt \ + $(SRCDIR)/../skins/xekri/header.txt \ + $(SRCDIR)/diff.tcl \ + $(SRCDIR)/markdown.md + TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/bag_.c \ $(OBJDIR)/bisect_.c \ $(OBJDIR)/blob_.c \ $(OBJDIR)/branch_.c \ $(OBJDIR)/browse_.c \ + $(OBJDIR)/builtin_.c \ + $(OBJDIR)/bundle_.c \ + $(OBJDIR)/cache_.c \ $(OBJDIR)/captcha_.c \ $(OBJDIR)/cgi_.c \ $(OBJDIR)/checkin_.c \ $(OBJDIR)/checkout_.c \ $(OBJDIR)/clearsign_.c \ @@ -150,10 +215,12 @@ $(OBJDIR)/encode_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/finfo_.c \ + $(OBJDIR)/foci_.c \ + $(OBJDIR)/fusefs_.c \ $(OBJDIR)/glob_.c \ $(OBJDIR)/graph_.c \ $(OBJDIR)/gzip_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ @@ -175,10 +242,11 @@ $(OBJDIR)/json_tag_.c \ $(OBJDIR)/json_timeline_.c \ $(OBJDIR)/json_user_.c \ $(OBJDIR)/json_wiki_.c \ $(OBJDIR)/leaf_.c \ + $(OBJDIR)/loadctrl_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/lookslike_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/markdown_.c \ @@ -191,23 +259,27 @@ $(OBJDIR)/path_.c \ $(OBJDIR)/pivot_.c \ $(OBJDIR)/popen_.c \ $(OBJDIR)/pqueue_.c \ $(OBJDIR)/printf_.c \ + $(OBJDIR)/publish_.c \ + $(OBJDIR)/purge_.c \ $(OBJDIR)/rebuild_.c \ $(OBJDIR)/regexp_.c \ $(OBJDIR)/report_.c \ $(OBJDIR)/rss_.c \ $(OBJDIR)/schema_.c \ $(OBJDIR)/search_.c \ $(OBJDIR)/setup_.c \ $(OBJDIR)/sha1_.c \ $(OBJDIR)/shun_.c \ + $(OBJDIR)/sitemap_.c \ $(OBJDIR)/skins_.c \ $(OBJDIR)/sqlcmd_.c \ $(OBJDIR)/stash_.c \ $(OBJDIR)/stat_.c \ + $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ $(OBJDIR)/th_main_.c \ @@ -223,10 +295,11 @@ $(OBJDIR)/util_.c \ $(OBJDIR)/verify_.c \ $(OBJDIR)/vfile_.c \ $(OBJDIR)/wiki_.c \ $(OBJDIR)/wikiformat_.c \ + $(OBJDIR)/winfile_.c \ $(OBJDIR)/winhttp_.c \ $(OBJDIR)/wysiwyg_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/zip_.c @@ -238,10 +311,13 @@ $(OBJDIR)/bag.o \ $(OBJDIR)/bisect.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ + $(OBJDIR)/builtin.o \ + $(OBJDIR)/bundle.o \ + $(OBJDIR)/cache.o \ $(OBJDIR)/captcha.o \ $(OBJDIR)/cgi.o \ $(OBJDIR)/checkin.o \ $(OBJDIR)/checkout.o \ $(OBJDIR)/clearsign.o \ @@ -259,10 +335,12 @@ $(OBJDIR)/encode.o \ $(OBJDIR)/event.o \ $(OBJDIR)/export.o \ $(OBJDIR)/file.o \ $(OBJDIR)/finfo.o \ + $(OBJDIR)/foci.o \ + $(OBJDIR)/fusefs.o \ $(OBJDIR)/glob.o \ $(OBJDIR)/graph.o \ $(OBJDIR)/gzip.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ @@ -284,10 +362,11 @@ $(OBJDIR)/json_tag.o \ $(OBJDIR)/json_timeline.o \ $(OBJDIR)/json_user.o \ $(OBJDIR)/json_wiki.o \ $(OBJDIR)/leaf.o \ + $(OBJDIR)/loadctrl.o \ $(OBJDIR)/login.o \ $(OBJDIR)/lookslike.o \ $(OBJDIR)/main.o \ $(OBJDIR)/manifest.o \ $(OBJDIR)/markdown.o \ @@ -300,23 +379,27 @@ $(OBJDIR)/path.o \ $(OBJDIR)/pivot.o \ $(OBJDIR)/popen.o \ $(OBJDIR)/pqueue.o \ $(OBJDIR)/printf.o \ + $(OBJDIR)/publish.o \ + $(OBJDIR)/purge.o \ $(OBJDIR)/rebuild.o \ $(OBJDIR)/regexp.o \ $(OBJDIR)/report.o \ $(OBJDIR)/rss.o \ $(OBJDIR)/schema.o \ $(OBJDIR)/search.o \ $(OBJDIR)/setup.o \ $(OBJDIR)/sha1.o \ $(OBJDIR)/shun.o \ + $(OBJDIR)/sitemap.o \ $(OBJDIR)/skins.o \ $(OBJDIR)/sqlcmd.o \ $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ + $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ $(OBJDIR)/th_main.o \ @@ -332,10 +415,11 @@ $(OBJDIR)/util.o \ $(OBJDIR)/verify.o \ $(OBJDIR)/vfile.o \ $(OBJDIR)/wiki.o \ $(OBJDIR)/wikiformat.o \ + $(OBJDIR)/winfile.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/wysiwyg.o \ $(OBJDIR)/xfer.o \ $(OBJDIR)/xfersetup.o \ $(OBJDIR)/zip.o @@ -348,10 +432,13 @@ install: $(APPNAME) mkdir -p $(INSTALLDIR) mv $(APPNAME) $(INSTALLDIR) +codecheck: $(TRANS_SRC) $(OBJDIR)/codecheck1 + $(OBJDIR)/codecheck1 $(TRANS_SRC) + $(OBJDIR): -mkdir $(OBJDIR) $(OBJDIR)/translate: $(SRCDIR)/translate.c $(BCC) -o $(OBJDIR)/translate $(SRCDIR)/translate.c @@ -360,830 +447,1190 @@ $(BCC) -o $(OBJDIR)/makeheaders $(SRCDIR)/makeheaders.c $(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c +$(OBJDIR)/mkbuiltin: $(SRCDIR)/mkbuiltin.c + $(BCC) -o $(OBJDIR)/mkbuiltin $(SRCDIR)/mkbuiltin.c + $(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c $(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c +$(OBJDIR)/codecheck1: $(SRCDIR)/codecheck1.c + $(BCC) -o $(OBJDIR)/codecheck1 $(SRCDIR)/codecheck1.c + # WARNING. DANGER. Running the test suite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h + +# Setup the options used to compile the included SQLite library. +SQLITE_OPTIONS = -DNDEBUG=1 \ + -DSQLITE_OMIT_LOAD_EXTENSION=1 \ + -DSQLITE_ENABLE_LOCKING_STYLE=0 \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_DEFAULT_FILE_FORMAT=4 \ + -DSQLITE_OMIT_DEPRECATED \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -DSQLITE_ENABLE_FTS4 \ + -DSQLITE_ENABLE_FTS3_PARENTHESIS + +# Setup the options used to compile the included SQLite shell. +SHELL_OPTIONS = -Dmain=sqlite3_shell \ + -DSQLITE_OMIT_LOAD_EXTENSION=1 \ + -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \ + -DSQLITE_SHELL_DBNAME_PROC=fossil_open + +# Setup the options used to compile the included miniz library. +MINIZ_OPTIONS = -DMINIZ_NO_STDIO \ + -DMINIZ_NO_TIME \ + -DMINIZ_NO_ARCHIVE_APIS # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set # to 1. If it is set to 1, then there is no need to build or link -# the sqlite3.o object. Instead, the system sqlite will be linked +# the sqlite3.o object. Instead, the system SQLite will be linked # using -lsqlite3. -SQLITE3_OBJ.1 = +SQLITE3_OBJ.1 = SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o SQLITE3_OBJ. = $(SQLITE3_OBJ.0) -# The FOSSIL_ENABLE_TCL variable may be undefined, set to 0, or set to 1. -# If it is set to 1, then we need to build the Tcl integration code and -# link to the Tcl library. -TCL_OBJ.0 = -TCL_OBJ.1 = $(OBJDIR)/th_tcl.o -TCL_OBJ. = $(TCL_OBJ.0) - -EXTRAOBJ = $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(TCL_OBJ.$(FOSSIL_ENABLE_TCL)) $(OBJDIR)/cson_amalgamation.o - -$(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) +# The FOSSIL_ENABLE_MINIZ variable may be undefined, set to 0, or +# set to 1. If it is set to 1, the miniz library included in the +# source tree should be used; otherwise, it should not. +MINIZ_OBJ.0 = +MINIZ_OBJ.1 = $(OBJDIR)/miniz.o +MINIZ_OBJ. = $(MINIZ_OBJ.0) + + +EXTRAOBJ = \ + $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \ + $(MINIZ_OBJ.$(FOSSIL_ENABLE_MINIZ)) \ + $(OBJDIR)/shell.o \ + $(OBJDIR)/th.o \ + $(OBJDIR)/th_lang.o \ + $(OBJDIR)/th_tcl.o \ + $(OBJDIR)/cson_amalgamation.o + + +$(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ) + $(OBJDIR)/codecheck1 $(TRANS_SRC) $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) # This rule prevents make from using its default rules to try build # an executable named "manifest" out of the file named "manifest.c" # -$(SRCDIR)/../manifest: +$(SRCDIR)/../manifest: # noop -clean: +clean: rm -rf $(OBJDIR)/* $(APPNAME) $(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex $(OBJDIR)/mkindex $(TRANS_SRC) >$@ -$(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h - $(OBJDIR)/makeheaders $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/json_.c:$(OBJDIR)/json.h $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h $(OBJDIR)/json_dir_.c:$(OBJDIR)/json_dir.h $(OBJDIR)/json_finfo_.c:$(OBJDIR)/json_finfo.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_query_.c:$(OBJDIR)/json_query.h $(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h $(OBJDIR)/json_status_.c:$(OBJDIR)/json_status.h $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/utf8_.c:$(OBJDIR)/utf8.h $(OBJDIR)/util_.c:$(OBJDIR)/util.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/wysiwyg_.c:$(OBJDIR)/wysiwyg.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h + +$(OBJDIR)/builtin_data.h: $(OBJDIR)/mkbuiltin $(EXTRA_FILES) + $(OBJDIR)/mkbuiltin --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ + +$(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h + $(OBJDIR)/makeheaders $(OBJDIR)/add_.c:$(OBJDIR)/add.h \ + $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \ + $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \ + $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \ + $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \ + $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \ + $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \ + $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \ + $(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \ + $(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \ + $(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \ + $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \ + $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \ + $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \ + $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \ + $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \ + $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h \ + $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h \ + $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h \ + $(OBJDIR)/content_.c:$(OBJDIR)/content.h \ + $(OBJDIR)/db_.c:$(OBJDIR)/db.h \ + $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h \ + $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h \ + $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h \ + $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \ + $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \ + $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \ + $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ + $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ + $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ + $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ + $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ + $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ + $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ + $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ + $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ + $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ + $(OBJDIR)/http_.c:$(OBJDIR)/http.h \ + $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ + $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ + $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \ + $(OBJDIR)/import_.c:$(OBJDIR)/import.h \ + $(OBJDIR)/info_.c:$(OBJDIR)/info.h \ + $(OBJDIR)/json_.c:$(OBJDIR)/json.h \ + $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \ + $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \ + $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \ + $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \ + $(OBJDIR)/json_dir_.c:$(OBJDIR)/json_dir.h \ + $(OBJDIR)/json_finfo_.c:$(OBJDIR)/json_finfo.h \ + $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h \ + $(OBJDIR)/json_query_.c:$(OBJDIR)/json_query.h \ + $(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h \ + $(OBJDIR)/json_status_.c:$(OBJDIR)/json_status.h \ + $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h \ + $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h \ + $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h \ + $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h \ + $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h \ + $(OBJDIR)/loadctrl_.c:$(OBJDIR)/loadctrl.h \ + $(OBJDIR)/login_.c:$(OBJDIR)/login.h \ + $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \ + $(OBJDIR)/main_.c:$(OBJDIR)/main.h \ + $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \ + $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \ + $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \ + $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \ + $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \ + $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \ + $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \ + $(OBJDIR)/name_.c:$(OBJDIR)/name.h \ + $(OBJDIR)/path_.c:$(OBJDIR)/path.h \ + $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h \ + $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h \ + $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h \ + $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h \ + $(OBJDIR)/publish_.c:$(OBJDIR)/publish.h \ + $(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \ + $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \ + $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \ + $(OBJDIR)/report_.c:$(OBJDIR)/report.h \ + $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \ + $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \ + $(OBJDIR)/search_.c:$(OBJDIR)/search.h \ + $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \ + $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \ + $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \ + $(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \ + $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \ + $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h \ + $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \ + $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \ + $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ + $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ + $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ + $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ + $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ + $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ + $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ + $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ + $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ + $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ + $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h \ + $(OBJDIR)/update_.c:$(OBJDIR)/update.h \ + $(OBJDIR)/url_.c:$(OBJDIR)/url.h \ + $(OBJDIR)/user_.c:$(OBJDIR)/user.h \ + $(OBJDIR)/utf8_.c:$(OBJDIR)/utf8.h \ + $(OBJDIR)/util_.c:$(OBJDIR)/util.h \ + $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h \ + $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h \ + $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h \ + $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h \ + $(OBJDIR)/winfile_.c:$(OBJDIR)/winfile.h \ + $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h \ + $(OBJDIR)/wysiwyg_.c:$(OBJDIR)/wysiwyg.h \ + $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h \ + $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h \ + $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h \ + $(SRCDIR)/sqlite3.h \ + $(SRCDIR)/th.h \ + $(OBJDIR)/VERSION.h touch $(OBJDIR)/headers $(OBJDIR)/headers: Makefile $(OBJDIR)/json.o $(OBJDIR)/json_artifact.o $(OBJDIR)/json_branch.o $(OBJDIR)/json_config.o $(OBJDIR)/json_diff.o $(OBJDIR)/json_dir.o $(OBJDIR)/json_finfo.o $(OBJDIR)/json_login.o $(OBJDIR)/json_query.o $(OBJDIR)/json_report.o $(OBJDIR)/json_status.o $(OBJDIR)/json_tag.o $(OBJDIR)/json_timeline.o $(OBJDIR)/json_user.o $(OBJDIR)/json_wiki.o : $(SRCDIR)/json_detail.h Makefile: $(OBJDIR)/add_.c: $(SRCDIR)/add.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/add.c >$(OBJDIR)/add_.c + $(OBJDIR)/translate $(SRCDIR)/add.c >$@ -$(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h +$(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c $(OBJDIR)/add.h: $(OBJDIR)/headers + $(OBJDIR)/allrepo_.c: $(SRCDIR)/allrepo.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/allrepo.c >$(OBJDIR)/allrepo_.c + $(OBJDIR)/translate $(SRCDIR)/allrepo.c >$@ -$(OBJDIR)/allrepo.o: $(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h +$(OBJDIR)/allrepo.o: $(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/allrepo.o -c $(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h: $(OBJDIR)/headers + $(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/attach.c >$(OBJDIR)/attach_.c + $(OBJDIR)/translate $(SRCDIR)/attach.c >$@ -$(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h +$(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c $(OBJDIR)/attach.h: $(OBJDIR)/headers + $(OBJDIR)/bag_.c: $(SRCDIR)/bag.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/bag.c >$(OBJDIR)/bag_.c + $(OBJDIR)/translate $(SRCDIR)/bag.c >$@ -$(OBJDIR)/bag.o: $(OBJDIR)/bag_.c $(OBJDIR)/bag.h $(SRCDIR)/config.h +$(OBJDIR)/bag.o: $(OBJDIR)/bag_.c $(OBJDIR)/bag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/bag.o -c $(OBJDIR)/bag_.c $(OBJDIR)/bag.h: $(OBJDIR)/headers + $(OBJDIR)/bisect_.c: $(SRCDIR)/bisect.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/bisect.c >$(OBJDIR)/bisect_.c + $(OBJDIR)/translate $(SRCDIR)/bisect.c >$@ -$(OBJDIR)/bisect.o: $(OBJDIR)/bisect_.c $(OBJDIR)/bisect.h $(SRCDIR)/config.h +$(OBJDIR)/bisect.o: $(OBJDIR)/bisect_.c $(OBJDIR)/bisect.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/bisect.o -c $(OBJDIR)/bisect_.c $(OBJDIR)/bisect.h: $(OBJDIR)/headers + $(OBJDIR)/blob_.c: $(SRCDIR)/blob.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/blob.c >$(OBJDIR)/blob_.c + $(OBJDIR)/translate $(SRCDIR)/blob.c >$@ -$(OBJDIR)/blob.o: $(OBJDIR)/blob_.c $(OBJDIR)/blob.h $(SRCDIR)/config.h +$(OBJDIR)/blob.o: $(OBJDIR)/blob_.c $(OBJDIR)/blob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/blob.o -c $(OBJDIR)/blob_.c $(OBJDIR)/blob.h: $(OBJDIR)/headers + $(OBJDIR)/branch_.c: $(SRCDIR)/branch.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/branch.c >$(OBJDIR)/branch_.c + $(OBJDIR)/translate $(SRCDIR)/branch.c >$@ -$(OBJDIR)/branch.o: $(OBJDIR)/branch_.c $(OBJDIR)/branch.h $(SRCDIR)/config.h +$(OBJDIR)/branch.o: $(OBJDIR)/branch_.c $(OBJDIR)/branch.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/branch.o -c $(OBJDIR)/branch_.c $(OBJDIR)/branch.h: $(OBJDIR)/headers + $(OBJDIR)/browse_.c: $(SRCDIR)/browse.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/browse.c >$(OBJDIR)/browse_.c + $(OBJDIR)/translate $(SRCDIR)/browse.c >$@ -$(OBJDIR)/browse.o: $(OBJDIR)/browse_.c $(OBJDIR)/browse.h $(SRCDIR)/config.h +$(OBJDIR)/browse.o: $(OBJDIR)/browse_.c $(OBJDIR)/browse.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/browse.o -c $(OBJDIR)/browse_.c $(OBJDIR)/browse.h: $(OBJDIR)/headers + +$(OBJDIR)/builtin_.c: $(SRCDIR)/builtin.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/builtin.c >$@ + +$(OBJDIR)/builtin.o: $(OBJDIR)/builtin_.c $(OBJDIR)/builtin.h $(OBJDIR)/builtin_data.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/builtin.o -c $(OBJDIR)/builtin_.c + +$(OBJDIR)/builtin.h: $(OBJDIR)/headers + +$(OBJDIR)/bundle_.c: $(SRCDIR)/bundle.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/bundle.c >$@ + +$(OBJDIR)/bundle.o: $(OBJDIR)/bundle_.c $(OBJDIR)/bundle.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/bundle.o -c $(OBJDIR)/bundle_.c + +$(OBJDIR)/bundle.h: $(OBJDIR)/headers + +$(OBJDIR)/cache_.c: $(SRCDIR)/cache.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/cache.c >$@ + +$(OBJDIR)/cache.o: $(OBJDIR)/cache_.c $(OBJDIR)/cache.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/cache.o -c $(OBJDIR)/cache_.c + +$(OBJDIR)/cache.h: $(OBJDIR)/headers + $(OBJDIR)/captcha_.c: $(SRCDIR)/captcha.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/captcha.c >$(OBJDIR)/captcha_.c + $(OBJDIR)/translate $(SRCDIR)/captcha.c >$@ -$(OBJDIR)/captcha.o: $(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h +$(OBJDIR)/captcha.o: $(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/captcha.o -c $(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h: $(OBJDIR)/headers + $(OBJDIR)/cgi_.c: $(SRCDIR)/cgi.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/cgi.c >$(OBJDIR)/cgi_.c + $(OBJDIR)/translate $(SRCDIR)/cgi.c >$@ -$(OBJDIR)/cgi.o: $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h $(SRCDIR)/config.h +$(OBJDIR)/cgi.o: $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/cgi.o -c $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h: $(OBJDIR)/headers + $(OBJDIR)/checkin_.c: $(SRCDIR)/checkin.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/checkin.c >$(OBJDIR)/checkin_.c + $(OBJDIR)/translate $(SRCDIR)/checkin.c >$@ -$(OBJDIR)/checkin.o: $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h $(SRCDIR)/config.h +$(OBJDIR)/checkin.o: $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/checkin.o -c $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h: $(OBJDIR)/headers + $(OBJDIR)/checkout_.c: $(SRCDIR)/checkout.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/checkout.c >$(OBJDIR)/checkout_.c + $(OBJDIR)/translate $(SRCDIR)/checkout.c >$@ -$(OBJDIR)/checkout.o: $(OBJDIR)/checkout_.c $(OBJDIR)/checkout.h $(SRCDIR)/config.h +$(OBJDIR)/checkout.o: $(OBJDIR)/checkout_.c $(OBJDIR)/checkout.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/checkout.o -c $(OBJDIR)/checkout_.c $(OBJDIR)/checkout.h: $(OBJDIR)/headers + $(OBJDIR)/clearsign_.c: $(SRCDIR)/clearsign.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/clearsign.c >$(OBJDIR)/clearsign_.c + $(OBJDIR)/translate $(SRCDIR)/clearsign.c >$@ -$(OBJDIR)/clearsign.o: $(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.h $(SRCDIR)/config.h +$(OBJDIR)/clearsign.o: $(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/clearsign.o -c $(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.h: $(OBJDIR)/headers + $(OBJDIR)/clone_.c: $(SRCDIR)/clone.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/clone.c >$(OBJDIR)/clone_.c + $(OBJDIR)/translate $(SRCDIR)/clone.c >$@ -$(OBJDIR)/clone.o: $(OBJDIR)/clone_.c $(OBJDIR)/clone.h $(SRCDIR)/config.h +$(OBJDIR)/clone.o: $(OBJDIR)/clone_.c $(OBJDIR)/clone.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/clone.o -c $(OBJDIR)/clone_.c $(OBJDIR)/clone.h: $(OBJDIR)/headers + $(OBJDIR)/comformat_.c: $(SRCDIR)/comformat.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/comformat.c >$(OBJDIR)/comformat_.c + $(OBJDIR)/translate $(SRCDIR)/comformat.c >$@ -$(OBJDIR)/comformat.o: $(OBJDIR)/comformat_.c $(OBJDIR)/comformat.h $(SRCDIR)/config.h +$(OBJDIR)/comformat.o: $(OBJDIR)/comformat_.c $(OBJDIR)/comformat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/comformat.o -c $(OBJDIR)/comformat_.c $(OBJDIR)/comformat.h: $(OBJDIR)/headers + $(OBJDIR)/configure_.c: $(SRCDIR)/configure.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/configure.c >$(OBJDIR)/configure_.c + $(OBJDIR)/translate $(SRCDIR)/configure.c >$@ -$(OBJDIR)/configure.o: $(OBJDIR)/configure_.c $(OBJDIR)/configure.h $(SRCDIR)/config.h +$(OBJDIR)/configure.o: $(OBJDIR)/configure_.c $(OBJDIR)/configure.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/configure.o -c $(OBJDIR)/configure_.c $(OBJDIR)/configure.h: $(OBJDIR)/headers + $(OBJDIR)/content_.c: $(SRCDIR)/content.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/content.c >$(OBJDIR)/content_.c + $(OBJDIR)/translate $(SRCDIR)/content.c >$@ -$(OBJDIR)/content.o: $(OBJDIR)/content_.c $(OBJDIR)/content.h $(SRCDIR)/config.h +$(OBJDIR)/content.o: $(OBJDIR)/content_.c $(OBJDIR)/content.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/content.o -c $(OBJDIR)/content_.c $(OBJDIR)/content.h: $(OBJDIR)/headers + $(OBJDIR)/db_.c: $(SRCDIR)/db.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/db.c >$(OBJDIR)/db_.c + $(OBJDIR)/translate $(SRCDIR)/db.c >$@ -$(OBJDIR)/db.o: $(OBJDIR)/db_.c $(OBJDIR)/db.h $(SRCDIR)/config.h +$(OBJDIR)/db.o: $(OBJDIR)/db_.c $(OBJDIR)/db.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/db.o -c $(OBJDIR)/db_.c $(OBJDIR)/db.h: $(OBJDIR)/headers + $(OBJDIR)/delta_.c: $(SRCDIR)/delta.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/delta.c >$(OBJDIR)/delta_.c + $(OBJDIR)/translate $(SRCDIR)/delta.c >$@ -$(OBJDIR)/delta.o: $(OBJDIR)/delta_.c $(OBJDIR)/delta.h $(SRCDIR)/config.h +$(OBJDIR)/delta.o: $(OBJDIR)/delta_.c $(OBJDIR)/delta.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/delta.o -c $(OBJDIR)/delta_.c $(OBJDIR)/delta.h: $(OBJDIR)/headers + $(OBJDIR)/deltacmd_.c: $(SRCDIR)/deltacmd.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/deltacmd.c >$(OBJDIR)/deltacmd_.c + $(OBJDIR)/translate $(SRCDIR)/deltacmd.c >$@ -$(OBJDIR)/deltacmd.o: $(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.h $(SRCDIR)/config.h +$(OBJDIR)/deltacmd.o: $(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/deltacmd.o -c $(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.h: $(OBJDIR)/headers + $(OBJDIR)/descendants_.c: $(SRCDIR)/descendants.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/descendants.c >$(OBJDIR)/descendants_.c + $(OBJDIR)/translate $(SRCDIR)/descendants.c >$@ -$(OBJDIR)/descendants.o: $(OBJDIR)/descendants_.c $(OBJDIR)/descendants.h $(SRCDIR)/config.h +$(OBJDIR)/descendants.o: $(OBJDIR)/descendants_.c $(OBJDIR)/descendants.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/descendants.o -c $(OBJDIR)/descendants_.c $(OBJDIR)/descendants.h: $(OBJDIR)/headers + $(OBJDIR)/diff_.c: $(SRCDIR)/diff.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/diff.c >$(OBJDIR)/diff_.c + $(OBJDIR)/translate $(SRCDIR)/diff.c >$@ -$(OBJDIR)/diff.o: $(OBJDIR)/diff_.c $(OBJDIR)/diff.h $(SRCDIR)/config.h +$(OBJDIR)/diff.o: $(OBJDIR)/diff_.c $(OBJDIR)/diff.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/diff.o -c $(OBJDIR)/diff_.c $(OBJDIR)/diff.h: $(OBJDIR)/headers + $(OBJDIR)/diffcmd_.c: $(SRCDIR)/diffcmd.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/diffcmd.c >$(OBJDIR)/diffcmd_.c + $(OBJDIR)/translate $(SRCDIR)/diffcmd.c >$@ -$(OBJDIR)/diffcmd.o: $(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.h $(SRCDIR)/config.h +$(OBJDIR)/diffcmd.o: $(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/diffcmd.o -c $(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.h: $(OBJDIR)/headers + $(OBJDIR)/doc_.c: $(SRCDIR)/doc.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/doc.c >$(OBJDIR)/doc_.c + $(OBJDIR)/translate $(SRCDIR)/doc.c >$@ -$(OBJDIR)/doc.o: $(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h +$(OBJDIR)/doc.o: $(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c $(OBJDIR)/doc.h: $(OBJDIR)/headers + $(OBJDIR)/encode_.c: $(SRCDIR)/encode.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/encode.c >$(OBJDIR)/encode_.c + $(OBJDIR)/translate $(SRCDIR)/encode.c >$@ -$(OBJDIR)/encode.o: $(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h +$(OBJDIR)/encode.o: $(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c $(OBJDIR)/encode.h: $(OBJDIR)/headers + $(OBJDIR)/event_.c: $(SRCDIR)/event.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/event.c >$(OBJDIR)/event_.c + $(OBJDIR)/translate $(SRCDIR)/event.c >$@ -$(OBJDIR)/event.o: $(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h +$(OBJDIR)/event.o: $(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/event.o -c $(OBJDIR)/event_.c $(OBJDIR)/event.h: $(OBJDIR)/headers + $(OBJDIR)/export_.c: $(SRCDIR)/export.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/export.c >$(OBJDIR)/export_.c + $(OBJDIR)/translate $(SRCDIR)/export.c >$@ -$(OBJDIR)/export.o: $(OBJDIR)/export_.c $(OBJDIR)/export.h $(SRCDIR)/config.h +$(OBJDIR)/export.o: $(OBJDIR)/export_.c $(OBJDIR)/export.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/export.o -c $(OBJDIR)/export_.c $(OBJDIR)/export.h: $(OBJDIR)/headers + $(OBJDIR)/file_.c: $(SRCDIR)/file.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/file.c >$(OBJDIR)/file_.c + $(OBJDIR)/translate $(SRCDIR)/file.c >$@ -$(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h +$(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c $(OBJDIR)/file.h: $(OBJDIR)/headers + $(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/finfo.c >$(OBJDIR)/finfo_.c + $(OBJDIR)/translate $(SRCDIR)/finfo.c >$@ -$(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h +$(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/finfo.o -c $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h: $(OBJDIR)/headers + +$(OBJDIR)/foci_.c: $(SRCDIR)/foci.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/foci.c >$@ + +$(OBJDIR)/foci.o: $(OBJDIR)/foci_.c $(OBJDIR)/foci.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/foci.o -c $(OBJDIR)/foci_.c + +$(OBJDIR)/foci.h: $(OBJDIR)/headers + +$(OBJDIR)/fusefs_.c: $(SRCDIR)/fusefs.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/fusefs.c >$@ + +$(OBJDIR)/fusefs.o: $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/fusefs.o -c $(OBJDIR)/fusefs_.c + +$(OBJDIR)/fusefs.h: $(OBJDIR)/headers + $(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/glob.c >$(OBJDIR)/glob_.c + $(OBJDIR)/translate $(SRCDIR)/glob.c >$@ -$(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h +$(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/glob.o -c $(OBJDIR)/glob_.c $(OBJDIR)/glob.h: $(OBJDIR)/headers + $(OBJDIR)/graph_.c: $(SRCDIR)/graph.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/graph.c >$(OBJDIR)/graph_.c + $(OBJDIR)/translate $(SRCDIR)/graph.c >$@ -$(OBJDIR)/graph.o: $(OBJDIR)/graph_.c $(OBJDIR)/graph.h $(SRCDIR)/config.h +$(OBJDIR)/graph.o: $(OBJDIR)/graph_.c $(OBJDIR)/graph.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/graph.o -c $(OBJDIR)/graph_.c $(OBJDIR)/graph.h: $(OBJDIR)/headers + $(OBJDIR)/gzip_.c: $(SRCDIR)/gzip.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/gzip.c >$(OBJDIR)/gzip_.c + $(OBJDIR)/translate $(SRCDIR)/gzip.c >$@ -$(OBJDIR)/gzip.o: $(OBJDIR)/gzip_.c $(OBJDIR)/gzip.h $(SRCDIR)/config.h +$(OBJDIR)/gzip.o: $(OBJDIR)/gzip_.c $(OBJDIR)/gzip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/gzip.o -c $(OBJDIR)/gzip_.c $(OBJDIR)/gzip.h: $(OBJDIR)/headers + $(OBJDIR)/http_.c: $(SRCDIR)/http.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/http.c >$(OBJDIR)/http_.c + $(OBJDIR)/translate $(SRCDIR)/http.c >$@ -$(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h +$(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http.o -c $(OBJDIR)/http_.c $(OBJDIR)/http.h: $(OBJDIR)/headers + $(OBJDIR)/http_socket_.c: $(SRCDIR)/http_socket.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/http_socket.c >$(OBJDIR)/http_socket_.c + $(OBJDIR)/translate $(SRCDIR)/http_socket.c >$@ -$(OBJDIR)/http_socket.o: $(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.h $(SRCDIR)/config.h +$(OBJDIR)/http_socket.o: $(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_socket.o -c $(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.h: $(OBJDIR)/headers + $(OBJDIR)/http_ssl_.c: $(SRCDIR)/http_ssl.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/http_ssl.c >$(OBJDIR)/http_ssl_.c + $(OBJDIR)/translate $(SRCDIR)/http_ssl.c >$@ -$(OBJDIR)/http_ssl.o: $(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.h $(SRCDIR)/config.h +$(OBJDIR)/http_ssl.o: $(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_ssl.o -c $(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.h: $(OBJDIR)/headers + $(OBJDIR)/http_transport_.c: $(SRCDIR)/http_transport.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/http_transport.c >$(OBJDIR)/http_transport_.c + $(OBJDIR)/translate $(SRCDIR)/http_transport.c >$@ -$(OBJDIR)/http_transport.o: $(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.h $(SRCDIR)/config.h +$(OBJDIR)/http_transport.o: $(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_transport.o -c $(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.h: $(OBJDIR)/headers + $(OBJDIR)/import_.c: $(SRCDIR)/import.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/import.c >$(OBJDIR)/import_.c + $(OBJDIR)/translate $(SRCDIR)/import.c >$@ -$(OBJDIR)/import.o: $(OBJDIR)/import_.c $(OBJDIR)/import.h $(SRCDIR)/config.h +$(OBJDIR)/import.o: $(OBJDIR)/import_.c $(OBJDIR)/import.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/import.o -c $(OBJDIR)/import_.c $(OBJDIR)/import.h: $(OBJDIR)/headers + $(OBJDIR)/info_.c: $(SRCDIR)/info.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/info.c >$(OBJDIR)/info_.c + $(OBJDIR)/translate $(SRCDIR)/info.c >$@ -$(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h +$(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c $(OBJDIR)/info.h: $(OBJDIR)/headers + $(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json.c >$(OBJDIR)/json_.c + $(OBJDIR)/translate $(SRCDIR)/json.c >$@ -$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h +$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json.o -c $(OBJDIR)/json_.c $(OBJDIR)/json.h: $(OBJDIR)/headers + $(OBJDIR)/json_artifact_.c: $(SRCDIR)/json_artifact.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_artifact.c >$(OBJDIR)/json_artifact_.c + $(OBJDIR)/translate $(SRCDIR)/json_artifact.c >$@ -$(OBJDIR)/json_artifact.o: $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h $(SRCDIR)/config.h +$(OBJDIR)/json_artifact.o: $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_artifact.o -c $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h: $(OBJDIR)/headers + $(OBJDIR)/json_branch_.c: $(SRCDIR)/json_branch.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_branch.c >$(OBJDIR)/json_branch_.c + $(OBJDIR)/translate $(SRCDIR)/json_branch.c >$@ -$(OBJDIR)/json_branch.o: $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h $(SRCDIR)/config.h +$(OBJDIR)/json_branch.o: $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_branch.o -c $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h: $(OBJDIR)/headers + $(OBJDIR)/json_config_.c: $(SRCDIR)/json_config.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_config.c >$(OBJDIR)/json_config_.c + $(OBJDIR)/translate $(SRCDIR)/json_config.c >$@ -$(OBJDIR)/json_config.o: $(OBJDIR)/json_config_.c $(OBJDIR)/json_config.h $(SRCDIR)/config.h +$(OBJDIR)/json_config.o: $(OBJDIR)/json_config_.c $(OBJDIR)/json_config.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_config.o -c $(OBJDIR)/json_config_.c $(OBJDIR)/json_config.h: $(OBJDIR)/headers + $(OBJDIR)/json_diff_.c: $(SRCDIR)/json_diff.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_diff.c >$(OBJDIR)/json_diff_.c + $(OBJDIR)/translate $(SRCDIR)/json_diff.c >$@ -$(OBJDIR)/json_diff.o: $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h $(SRCDIR)/config.h +$(OBJDIR)/json_diff.o: $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_diff.o -c $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h: $(OBJDIR)/headers + $(OBJDIR)/json_dir_.c: $(SRCDIR)/json_dir.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_dir.c >$(OBJDIR)/json_dir_.c + $(OBJDIR)/translate $(SRCDIR)/json_dir.c >$@ -$(OBJDIR)/json_dir.o: $(OBJDIR)/json_dir_.c $(OBJDIR)/json_dir.h $(SRCDIR)/config.h +$(OBJDIR)/json_dir.o: $(OBJDIR)/json_dir_.c $(OBJDIR)/json_dir.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_dir.o -c $(OBJDIR)/json_dir_.c $(OBJDIR)/json_dir.h: $(OBJDIR)/headers + $(OBJDIR)/json_finfo_.c: $(SRCDIR)/json_finfo.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_finfo.c >$(OBJDIR)/json_finfo_.c + $(OBJDIR)/translate $(SRCDIR)/json_finfo.c >$@ -$(OBJDIR)/json_finfo.o: $(OBJDIR)/json_finfo_.c $(OBJDIR)/json_finfo.h $(SRCDIR)/config.h +$(OBJDIR)/json_finfo.o: $(OBJDIR)/json_finfo_.c $(OBJDIR)/json_finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_finfo.o -c $(OBJDIR)/json_finfo_.c $(OBJDIR)/json_finfo.h: $(OBJDIR)/headers + $(OBJDIR)/json_login_.c: $(SRCDIR)/json_login.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_login.c >$(OBJDIR)/json_login_.c + $(OBJDIR)/translate $(SRCDIR)/json_login.c >$@ -$(OBJDIR)/json_login.o: $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h $(SRCDIR)/config.h +$(OBJDIR)/json_login.o: $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_login.o -c $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h: $(OBJDIR)/headers + $(OBJDIR)/json_query_.c: $(SRCDIR)/json_query.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_query.c >$(OBJDIR)/json_query_.c + $(OBJDIR)/translate $(SRCDIR)/json_query.c >$@ -$(OBJDIR)/json_query.o: $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h $(SRCDIR)/config.h +$(OBJDIR)/json_query.o: $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_query.o -c $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h: $(OBJDIR)/headers + $(OBJDIR)/json_report_.c: $(SRCDIR)/json_report.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_report.c >$(OBJDIR)/json_report_.c + $(OBJDIR)/translate $(SRCDIR)/json_report.c >$@ -$(OBJDIR)/json_report.o: $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h $(SRCDIR)/config.h +$(OBJDIR)/json_report.o: $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_report.o -c $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h: $(OBJDIR)/headers + $(OBJDIR)/json_status_.c: $(SRCDIR)/json_status.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_status.c >$(OBJDIR)/json_status_.c + $(OBJDIR)/translate $(SRCDIR)/json_status.c >$@ -$(OBJDIR)/json_status.o: $(OBJDIR)/json_status_.c $(OBJDIR)/json_status.h $(SRCDIR)/config.h +$(OBJDIR)/json_status.o: $(OBJDIR)/json_status_.c $(OBJDIR)/json_status.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_status.o -c $(OBJDIR)/json_status_.c $(OBJDIR)/json_status.h: $(OBJDIR)/headers + $(OBJDIR)/json_tag_.c: $(SRCDIR)/json_tag.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_tag.c >$(OBJDIR)/json_tag_.c + $(OBJDIR)/translate $(SRCDIR)/json_tag.c >$@ -$(OBJDIR)/json_tag.o: $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h $(SRCDIR)/config.h +$(OBJDIR)/json_tag.o: $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_tag.o -c $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h: $(OBJDIR)/headers + $(OBJDIR)/json_timeline_.c: $(SRCDIR)/json_timeline.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_timeline.c >$(OBJDIR)/json_timeline_.c + $(OBJDIR)/translate $(SRCDIR)/json_timeline.c >$@ -$(OBJDIR)/json_timeline.o: $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h $(SRCDIR)/config.h +$(OBJDIR)/json_timeline.o: $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_timeline.o -c $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h: $(OBJDIR)/headers + $(OBJDIR)/json_user_.c: $(SRCDIR)/json_user.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_user.c >$(OBJDIR)/json_user_.c + $(OBJDIR)/translate $(SRCDIR)/json_user.c >$@ -$(OBJDIR)/json_user.o: $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h $(SRCDIR)/config.h +$(OBJDIR)/json_user.o: $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_user.o -c $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h: $(OBJDIR)/headers + $(OBJDIR)/json_wiki_.c: $(SRCDIR)/json_wiki.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_wiki.c >$(OBJDIR)/json_wiki_.c + $(OBJDIR)/translate $(SRCDIR)/json_wiki.c >$@ -$(OBJDIR)/json_wiki.o: $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h $(SRCDIR)/config.h +$(OBJDIR)/json_wiki.o: $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_wiki.o -c $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h: $(OBJDIR)/headers + $(OBJDIR)/leaf_.c: $(SRCDIR)/leaf.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c + $(OBJDIR)/translate $(SRCDIR)/leaf.c >$@ -$(OBJDIR)/leaf.o: $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h $(SRCDIR)/config.h +$(OBJDIR)/leaf.o: $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h: $(OBJDIR)/headers + +$(OBJDIR)/loadctrl_.c: $(SRCDIR)/loadctrl.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/loadctrl.c >$@ + +$(OBJDIR)/loadctrl.o: $(OBJDIR)/loadctrl_.c $(OBJDIR)/loadctrl.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/loadctrl.o -c $(OBJDIR)/loadctrl_.c + +$(OBJDIR)/loadctrl.h: $(OBJDIR)/headers + $(OBJDIR)/login_.c: $(SRCDIR)/login.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/login.c >$(OBJDIR)/login_.c + $(OBJDIR)/translate $(SRCDIR)/login.c >$@ -$(OBJDIR)/login.o: $(OBJDIR)/login_.c $(OBJDIR)/login.h $(SRCDIR)/config.h +$(OBJDIR)/login.o: $(OBJDIR)/login_.c $(OBJDIR)/login.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/login.o -c $(OBJDIR)/login_.c $(OBJDIR)/login.h: $(OBJDIR)/headers + $(OBJDIR)/lookslike_.c: $(SRCDIR)/lookslike.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/lookslike.c >$(OBJDIR)/lookslike_.c + $(OBJDIR)/translate $(SRCDIR)/lookslike.c >$@ -$(OBJDIR)/lookslike.o: $(OBJDIR)/lookslike_.c $(OBJDIR)/lookslike.h $(SRCDIR)/config.h +$(OBJDIR)/lookslike.o: $(OBJDIR)/lookslike_.c $(OBJDIR)/lookslike.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/lookslike.o -c $(OBJDIR)/lookslike_.c $(OBJDIR)/lookslike.h: $(OBJDIR)/headers + $(OBJDIR)/main_.c: $(SRCDIR)/main.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/main.c >$(OBJDIR)/main_.c + $(OBJDIR)/translate $(SRCDIR)/main.c >$@ $(OBJDIR)/main.o: $(OBJDIR)/main_.c $(OBJDIR)/main.h $(OBJDIR)/page_index.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/main.o -c $(OBJDIR)/main_.c $(OBJDIR)/main.h: $(OBJDIR)/headers + $(OBJDIR)/manifest_.c: $(SRCDIR)/manifest.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/manifest.c >$(OBJDIR)/manifest_.c + $(OBJDIR)/translate $(SRCDIR)/manifest.c >$@ -$(OBJDIR)/manifest.o: $(OBJDIR)/manifest_.c $(OBJDIR)/manifest.h $(SRCDIR)/config.h +$(OBJDIR)/manifest.o: $(OBJDIR)/manifest_.c $(OBJDIR)/manifest.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/manifest.o -c $(OBJDIR)/manifest_.c $(OBJDIR)/manifest.h: $(OBJDIR)/headers + $(OBJDIR)/markdown_.c: $(SRCDIR)/markdown.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/markdown.c >$(OBJDIR)/markdown_.c + $(OBJDIR)/translate $(SRCDIR)/markdown.c >$@ -$(OBJDIR)/markdown.o: $(OBJDIR)/markdown_.c $(OBJDIR)/markdown.h $(SRCDIR)/config.h +$(OBJDIR)/markdown.o: $(OBJDIR)/markdown_.c $(OBJDIR)/markdown.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/markdown.o -c $(OBJDIR)/markdown_.c $(OBJDIR)/markdown.h: $(OBJDIR)/headers + $(OBJDIR)/markdown_html_.c: $(SRCDIR)/markdown_html.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/markdown_html.c >$(OBJDIR)/markdown_html_.c + $(OBJDIR)/translate $(SRCDIR)/markdown_html.c >$@ -$(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h +$(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers + $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/md5.c >$(OBJDIR)/md5_.c + $(OBJDIR)/translate $(SRCDIR)/md5.c >$@ -$(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h +$(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/md5.o -c $(OBJDIR)/md5_.c $(OBJDIR)/md5.h: $(OBJDIR)/headers + $(OBJDIR)/merge_.c: $(SRCDIR)/merge.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/merge.c >$(OBJDIR)/merge_.c + $(OBJDIR)/translate $(SRCDIR)/merge.c >$@ -$(OBJDIR)/merge.o: $(OBJDIR)/merge_.c $(OBJDIR)/merge.h $(SRCDIR)/config.h +$(OBJDIR)/merge.o: $(OBJDIR)/merge_.c $(OBJDIR)/merge.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/merge.o -c $(OBJDIR)/merge_.c $(OBJDIR)/merge.h: $(OBJDIR)/headers + $(OBJDIR)/merge3_.c: $(SRCDIR)/merge3.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/merge3.c >$(OBJDIR)/merge3_.c + $(OBJDIR)/translate $(SRCDIR)/merge3.c >$@ -$(OBJDIR)/merge3.o: $(OBJDIR)/merge3_.c $(OBJDIR)/merge3.h $(SRCDIR)/config.h +$(OBJDIR)/merge3.o: $(OBJDIR)/merge3_.c $(OBJDIR)/merge3.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/merge3.o -c $(OBJDIR)/merge3_.c $(OBJDIR)/merge3.h: $(OBJDIR)/headers + $(OBJDIR)/moderate_.c: $(SRCDIR)/moderate.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/moderate.c >$(OBJDIR)/moderate_.c + $(OBJDIR)/translate $(SRCDIR)/moderate.c >$@ -$(OBJDIR)/moderate.o: $(OBJDIR)/moderate_.c $(OBJDIR)/moderate.h $(SRCDIR)/config.h +$(OBJDIR)/moderate.o: $(OBJDIR)/moderate_.c $(OBJDIR)/moderate.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/moderate.o -c $(OBJDIR)/moderate_.c $(OBJDIR)/moderate.h: $(OBJDIR)/headers + $(OBJDIR)/name_.c: $(SRCDIR)/name.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/name.c >$(OBJDIR)/name_.c + $(OBJDIR)/translate $(SRCDIR)/name.c >$@ -$(OBJDIR)/name.o: $(OBJDIR)/name_.c $(OBJDIR)/name.h $(SRCDIR)/config.h +$(OBJDIR)/name.o: $(OBJDIR)/name_.c $(OBJDIR)/name.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/name.o -c $(OBJDIR)/name_.c $(OBJDIR)/name.h: $(OBJDIR)/headers + $(OBJDIR)/path_.c: $(SRCDIR)/path.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/path.c >$(OBJDIR)/path_.c + $(OBJDIR)/translate $(SRCDIR)/path.c >$@ -$(OBJDIR)/path.o: $(OBJDIR)/path_.c $(OBJDIR)/path.h $(SRCDIR)/config.h +$(OBJDIR)/path.o: $(OBJDIR)/path_.c $(OBJDIR)/path.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/path.o -c $(OBJDIR)/path_.c $(OBJDIR)/path.h: $(OBJDIR)/headers + $(OBJDIR)/pivot_.c: $(SRCDIR)/pivot.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/pivot.c >$(OBJDIR)/pivot_.c + $(OBJDIR)/translate $(SRCDIR)/pivot.c >$@ -$(OBJDIR)/pivot.o: $(OBJDIR)/pivot_.c $(OBJDIR)/pivot.h $(SRCDIR)/config.h +$(OBJDIR)/pivot.o: $(OBJDIR)/pivot_.c $(OBJDIR)/pivot.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/pivot.o -c $(OBJDIR)/pivot_.c $(OBJDIR)/pivot.h: $(OBJDIR)/headers + $(OBJDIR)/popen_.c: $(SRCDIR)/popen.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/popen.c >$(OBJDIR)/popen_.c + $(OBJDIR)/translate $(SRCDIR)/popen.c >$@ -$(OBJDIR)/popen.o: $(OBJDIR)/popen_.c $(OBJDIR)/popen.h $(SRCDIR)/config.h +$(OBJDIR)/popen.o: $(OBJDIR)/popen_.c $(OBJDIR)/popen.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/popen.o -c $(OBJDIR)/popen_.c $(OBJDIR)/popen.h: $(OBJDIR)/headers + $(OBJDIR)/pqueue_.c: $(SRCDIR)/pqueue.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/pqueue.c >$(OBJDIR)/pqueue_.c + $(OBJDIR)/translate $(SRCDIR)/pqueue.c >$@ -$(OBJDIR)/pqueue.o: $(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.h $(SRCDIR)/config.h +$(OBJDIR)/pqueue.o: $(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/pqueue.o -c $(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.h: $(OBJDIR)/headers + $(OBJDIR)/printf_.c: $(SRCDIR)/printf.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/printf.c >$(OBJDIR)/printf_.c + $(OBJDIR)/translate $(SRCDIR)/printf.c >$@ -$(OBJDIR)/printf.o: $(OBJDIR)/printf_.c $(OBJDIR)/printf.h $(SRCDIR)/config.h +$(OBJDIR)/printf.o: $(OBJDIR)/printf_.c $(OBJDIR)/printf.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/printf.o -c $(OBJDIR)/printf_.c $(OBJDIR)/printf.h: $(OBJDIR)/headers + +$(OBJDIR)/publish_.c: $(SRCDIR)/publish.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/publish.c >$@ + +$(OBJDIR)/publish.o: $(OBJDIR)/publish_.c $(OBJDIR)/publish.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/publish.o -c $(OBJDIR)/publish_.c + +$(OBJDIR)/publish.h: $(OBJDIR)/headers + +$(OBJDIR)/purge_.c: $(SRCDIR)/purge.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/purge.c >$@ + +$(OBJDIR)/purge.o: $(OBJDIR)/purge_.c $(OBJDIR)/purge.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/purge.o -c $(OBJDIR)/purge_.c + +$(OBJDIR)/purge.h: $(OBJDIR)/headers + $(OBJDIR)/rebuild_.c: $(SRCDIR)/rebuild.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/rebuild.c >$(OBJDIR)/rebuild_.c + $(OBJDIR)/translate $(SRCDIR)/rebuild.c >$@ -$(OBJDIR)/rebuild.o: $(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.h $(SRCDIR)/config.h +$(OBJDIR)/rebuild.o: $(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/rebuild.o -c $(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.h: $(OBJDIR)/headers + $(OBJDIR)/regexp_.c: $(SRCDIR)/regexp.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/regexp.c >$(OBJDIR)/regexp_.c + $(OBJDIR)/translate $(SRCDIR)/regexp.c >$@ -$(OBJDIR)/regexp.o: $(OBJDIR)/regexp_.c $(OBJDIR)/regexp.h $(SRCDIR)/config.h +$(OBJDIR)/regexp.o: $(OBJDIR)/regexp_.c $(OBJDIR)/regexp.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/regexp.o -c $(OBJDIR)/regexp_.c $(OBJDIR)/regexp.h: $(OBJDIR)/headers + $(OBJDIR)/report_.c: $(SRCDIR)/report.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/report.c >$(OBJDIR)/report_.c + $(OBJDIR)/translate $(SRCDIR)/report.c >$@ -$(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h +$(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c $(OBJDIR)/report.h: $(OBJDIR)/headers + $(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/rss.c >$(OBJDIR)/rss_.c + $(OBJDIR)/translate $(SRCDIR)/rss.c >$@ -$(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h +$(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/rss.o -c $(OBJDIR)/rss_.c $(OBJDIR)/rss.h: $(OBJDIR)/headers + $(OBJDIR)/schema_.c: $(SRCDIR)/schema.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/schema.c >$(OBJDIR)/schema_.c + $(OBJDIR)/translate $(SRCDIR)/schema.c >$@ -$(OBJDIR)/schema.o: $(OBJDIR)/schema_.c $(OBJDIR)/schema.h $(SRCDIR)/config.h +$(OBJDIR)/schema.o: $(OBJDIR)/schema_.c $(OBJDIR)/schema.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/schema.o -c $(OBJDIR)/schema_.c $(OBJDIR)/schema.h: $(OBJDIR)/headers + $(OBJDIR)/search_.c: $(SRCDIR)/search.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/search.c >$(OBJDIR)/search_.c + $(OBJDIR)/translate $(SRCDIR)/search.c >$@ -$(OBJDIR)/search.o: $(OBJDIR)/search_.c $(OBJDIR)/search.h $(SRCDIR)/config.h +$(OBJDIR)/search.o: $(OBJDIR)/search_.c $(OBJDIR)/search.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/search.o -c $(OBJDIR)/search_.c $(OBJDIR)/search.h: $(OBJDIR)/headers + $(OBJDIR)/setup_.c: $(SRCDIR)/setup.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/setup.c >$(OBJDIR)/setup_.c + $(OBJDIR)/translate $(SRCDIR)/setup.c >$@ -$(OBJDIR)/setup.o: $(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h +$(OBJDIR)/setup.o: $(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/setup.o -c $(OBJDIR)/setup_.c $(OBJDIR)/setup.h: $(OBJDIR)/headers + $(OBJDIR)/sha1_.c: $(SRCDIR)/sha1.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/sha1.c >$(OBJDIR)/sha1_.c + $(OBJDIR)/translate $(SRCDIR)/sha1.c >$@ -$(OBJDIR)/sha1.o: $(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h +$(OBJDIR)/sha1.o: $(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sha1.o -c $(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h: $(OBJDIR)/headers + $(OBJDIR)/shun_.c: $(SRCDIR)/shun.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/shun.c >$(OBJDIR)/shun_.c + $(OBJDIR)/translate $(SRCDIR)/shun.c >$@ -$(OBJDIR)/shun.o: $(OBJDIR)/shun_.c $(OBJDIR)/shun.h $(SRCDIR)/config.h +$(OBJDIR)/shun.o: $(OBJDIR)/shun_.c $(OBJDIR)/shun.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/shun.o -c $(OBJDIR)/shun_.c $(OBJDIR)/shun.h: $(OBJDIR)/headers + +$(OBJDIR)/sitemap_.c: $(SRCDIR)/sitemap.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/sitemap.c >$@ + +$(OBJDIR)/sitemap.o: $(OBJDIR)/sitemap_.c $(OBJDIR)/sitemap.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/sitemap.o -c $(OBJDIR)/sitemap_.c + +$(OBJDIR)/sitemap.h: $(OBJDIR)/headers + $(OBJDIR)/skins_.c: $(SRCDIR)/skins.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/skins.c >$(OBJDIR)/skins_.c + $(OBJDIR)/translate $(SRCDIR)/skins.c >$@ -$(OBJDIR)/skins.o: $(OBJDIR)/skins_.c $(OBJDIR)/skins.h $(SRCDIR)/config.h +$(OBJDIR)/skins.o: $(OBJDIR)/skins_.c $(OBJDIR)/skins.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/skins.o -c $(OBJDIR)/skins_.c $(OBJDIR)/skins.h: $(OBJDIR)/headers + $(OBJDIR)/sqlcmd_.c: $(SRCDIR)/sqlcmd.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/sqlcmd.c >$(OBJDIR)/sqlcmd_.c + $(OBJDIR)/translate $(SRCDIR)/sqlcmd.c >$@ -$(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h +$(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sqlcmd.o -c $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h: $(OBJDIR)/headers + $(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/stash.c >$(OBJDIR)/stash_.c + $(OBJDIR)/translate $(SRCDIR)/stash.c >$@ -$(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h +$(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/stash.o -c $(OBJDIR)/stash_.c $(OBJDIR)/stash.h: $(OBJDIR)/headers + $(OBJDIR)/stat_.c: $(SRCDIR)/stat.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/stat.c >$(OBJDIR)/stat_.c + $(OBJDIR)/translate $(SRCDIR)/stat.c >$@ -$(OBJDIR)/stat.o: $(OBJDIR)/stat_.c $(OBJDIR)/stat.h $(SRCDIR)/config.h +$(OBJDIR)/stat.o: $(OBJDIR)/stat_.c $(OBJDIR)/stat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/stat.o -c $(OBJDIR)/stat_.c $(OBJDIR)/stat.h: $(OBJDIR)/headers + +$(OBJDIR)/statrep_.c: $(SRCDIR)/statrep.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/statrep.c >$@ + +$(OBJDIR)/statrep.o: $(OBJDIR)/statrep_.c $(OBJDIR)/statrep.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/statrep.o -c $(OBJDIR)/statrep_.c + +$(OBJDIR)/statrep.h: $(OBJDIR)/headers + $(OBJDIR)/style_.c: $(SRCDIR)/style.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/style.c >$(OBJDIR)/style_.c + $(OBJDIR)/translate $(SRCDIR)/style.c >$@ -$(OBJDIR)/style.o: $(OBJDIR)/style_.c $(OBJDIR)/style.h $(SRCDIR)/config.h +$(OBJDIR)/style.o: $(OBJDIR)/style_.c $(OBJDIR)/style.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/style.o -c $(OBJDIR)/style_.c $(OBJDIR)/style.h: $(OBJDIR)/headers + $(OBJDIR)/sync_.c: $(SRCDIR)/sync.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/sync.c >$(OBJDIR)/sync_.c + $(OBJDIR)/translate $(SRCDIR)/sync.c >$@ -$(OBJDIR)/sync.o: $(OBJDIR)/sync_.c $(OBJDIR)/sync.h $(SRCDIR)/config.h +$(OBJDIR)/sync.o: $(OBJDIR)/sync_.c $(OBJDIR)/sync.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sync.o -c $(OBJDIR)/sync_.c $(OBJDIR)/sync.h: $(OBJDIR)/headers + $(OBJDIR)/tag_.c: $(SRCDIR)/tag.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/tag.c >$(OBJDIR)/tag_.c + $(OBJDIR)/translate $(SRCDIR)/tag.c >$@ -$(OBJDIR)/tag.o: $(OBJDIR)/tag_.c $(OBJDIR)/tag.h $(SRCDIR)/config.h +$(OBJDIR)/tag.o: $(OBJDIR)/tag_.c $(OBJDIR)/tag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tag.o -c $(OBJDIR)/tag_.c $(OBJDIR)/tag.h: $(OBJDIR)/headers + $(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/tar.c >$(OBJDIR)/tar_.c + $(OBJDIR)/translate $(SRCDIR)/tar.c >$@ -$(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h +$(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c $(OBJDIR)/tar.h: $(OBJDIR)/headers + $(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/th_main.c >$(OBJDIR)/th_main_.c + $(OBJDIR)/translate $(SRCDIR)/th_main.c >$@ -$(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h +$(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/th_main.o -c $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h: $(OBJDIR)/headers + $(OBJDIR)/timeline_.c: $(SRCDIR)/timeline.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/timeline.c >$(OBJDIR)/timeline_.c + $(OBJDIR)/translate $(SRCDIR)/timeline.c >$@ -$(OBJDIR)/timeline.o: $(OBJDIR)/timeline_.c $(OBJDIR)/timeline.h $(SRCDIR)/config.h +$(OBJDIR)/timeline.o: $(OBJDIR)/timeline_.c $(OBJDIR)/timeline.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/timeline.o -c $(OBJDIR)/timeline_.c $(OBJDIR)/timeline.h: $(OBJDIR)/headers + $(OBJDIR)/tkt_.c: $(SRCDIR)/tkt.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/tkt.c >$(OBJDIR)/tkt_.c + $(OBJDIR)/translate $(SRCDIR)/tkt.c >$@ -$(OBJDIR)/tkt.o: $(OBJDIR)/tkt_.c $(OBJDIR)/tkt.h $(SRCDIR)/config.h +$(OBJDIR)/tkt.o: $(OBJDIR)/tkt_.c $(OBJDIR)/tkt.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tkt.o -c $(OBJDIR)/tkt_.c $(OBJDIR)/tkt.h: $(OBJDIR)/headers + $(OBJDIR)/tktsetup_.c: $(SRCDIR)/tktsetup.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/tktsetup.c >$(OBJDIR)/tktsetup_.c + $(OBJDIR)/translate $(SRCDIR)/tktsetup.c >$@ -$(OBJDIR)/tktsetup.o: $(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.h $(SRCDIR)/config.h +$(OBJDIR)/tktsetup.o: $(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tktsetup.o -c $(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.h: $(OBJDIR)/headers + $(OBJDIR)/undo_.c: $(SRCDIR)/undo.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/undo.c >$(OBJDIR)/undo_.c + $(OBJDIR)/translate $(SRCDIR)/undo.c >$@ -$(OBJDIR)/undo.o: $(OBJDIR)/undo_.c $(OBJDIR)/undo.h $(SRCDIR)/config.h +$(OBJDIR)/undo.o: $(OBJDIR)/undo_.c $(OBJDIR)/undo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/undo.o -c $(OBJDIR)/undo_.c $(OBJDIR)/undo.h: $(OBJDIR)/headers + $(OBJDIR)/unicode_.c: $(SRCDIR)/unicode.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/unicode.c >$(OBJDIR)/unicode_.c + $(OBJDIR)/translate $(SRCDIR)/unicode.c >$@ -$(OBJDIR)/unicode.o: $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h $(SRCDIR)/config.h +$(OBJDIR)/unicode.o: $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/unicode.o -c $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h: $(OBJDIR)/headers + $(OBJDIR)/update_.c: $(SRCDIR)/update.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/update.c >$(OBJDIR)/update_.c + $(OBJDIR)/translate $(SRCDIR)/update.c >$@ -$(OBJDIR)/update.o: $(OBJDIR)/update_.c $(OBJDIR)/update.h $(SRCDIR)/config.h +$(OBJDIR)/update.o: $(OBJDIR)/update_.c $(OBJDIR)/update.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/update.o -c $(OBJDIR)/update_.c $(OBJDIR)/update.h: $(OBJDIR)/headers + $(OBJDIR)/url_.c: $(SRCDIR)/url.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/url.c >$(OBJDIR)/url_.c + $(OBJDIR)/translate $(SRCDIR)/url.c >$@ -$(OBJDIR)/url.o: $(OBJDIR)/url_.c $(OBJDIR)/url.h $(SRCDIR)/config.h +$(OBJDIR)/url.o: $(OBJDIR)/url_.c $(OBJDIR)/url.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/url.o -c $(OBJDIR)/url_.c $(OBJDIR)/url.h: $(OBJDIR)/headers + $(OBJDIR)/user_.c: $(SRCDIR)/user.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/user.c >$(OBJDIR)/user_.c + $(OBJDIR)/translate $(SRCDIR)/user.c >$@ -$(OBJDIR)/user.o: $(OBJDIR)/user_.c $(OBJDIR)/user.h $(SRCDIR)/config.h +$(OBJDIR)/user.o: $(OBJDIR)/user_.c $(OBJDIR)/user.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/user.o -c $(OBJDIR)/user_.c $(OBJDIR)/user.h: $(OBJDIR)/headers + $(OBJDIR)/utf8_.c: $(SRCDIR)/utf8.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/utf8.c >$(OBJDIR)/utf8_.c + $(OBJDIR)/translate $(SRCDIR)/utf8.c >$@ -$(OBJDIR)/utf8.o: $(OBJDIR)/utf8_.c $(OBJDIR)/utf8.h $(SRCDIR)/config.h +$(OBJDIR)/utf8.o: $(OBJDIR)/utf8_.c $(OBJDIR)/utf8.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/utf8.o -c $(OBJDIR)/utf8_.c $(OBJDIR)/utf8.h: $(OBJDIR)/headers + $(OBJDIR)/util_.c: $(SRCDIR)/util.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/util.c >$(OBJDIR)/util_.c + $(OBJDIR)/translate $(SRCDIR)/util.c >$@ -$(OBJDIR)/util.o: $(OBJDIR)/util_.c $(OBJDIR)/util.h $(SRCDIR)/config.h +$(OBJDIR)/util.o: $(OBJDIR)/util_.c $(OBJDIR)/util.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/util.o -c $(OBJDIR)/util_.c $(OBJDIR)/util.h: $(OBJDIR)/headers + $(OBJDIR)/verify_.c: $(SRCDIR)/verify.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/verify.c >$(OBJDIR)/verify_.c + $(OBJDIR)/translate $(SRCDIR)/verify.c >$@ -$(OBJDIR)/verify.o: $(OBJDIR)/verify_.c $(OBJDIR)/verify.h $(SRCDIR)/config.h +$(OBJDIR)/verify.o: $(OBJDIR)/verify_.c $(OBJDIR)/verify.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/verify.o -c $(OBJDIR)/verify_.c $(OBJDIR)/verify.h: $(OBJDIR)/headers + $(OBJDIR)/vfile_.c: $(SRCDIR)/vfile.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/vfile.c >$(OBJDIR)/vfile_.c + $(OBJDIR)/translate $(SRCDIR)/vfile.c >$@ -$(OBJDIR)/vfile.o: $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h $(SRCDIR)/config.h +$(OBJDIR)/vfile.o: $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/vfile.o -c $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h: $(OBJDIR)/headers + $(OBJDIR)/wiki_.c: $(SRCDIR)/wiki.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/wiki.c >$(OBJDIR)/wiki_.c + $(OBJDIR)/translate $(SRCDIR)/wiki.c >$@ -$(OBJDIR)/wiki.o: $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h $(SRCDIR)/config.h +$(OBJDIR)/wiki.o: $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/wiki.o -c $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h: $(OBJDIR)/headers + $(OBJDIR)/wikiformat_.c: $(SRCDIR)/wikiformat.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/wikiformat.c >$(OBJDIR)/wikiformat_.c + $(OBJDIR)/translate $(SRCDIR)/wikiformat.c >$@ -$(OBJDIR)/wikiformat.o: $(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.h $(SRCDIR)/config.h +$(OBJDIR)/wikiformat.o: $(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/wikiformat.o -c $(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.h: $(OBJDIR)/headers + +$(OBJDIR)/winfile_.c: $(SRCDIR)/winfile.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/winfile.c >$@ + +$(OBJDIR)/winfile.o: $(OBJDIR)/winfile_.c $(OBJDIR)/winfile.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/winfile.o -c $(OBJDIR)/winfile_.c + +$(OBJDIR)/winfile.h: $(OBJDIR)/headers + $(OBJDIR)/winhttp_.c: $(SRCDIR)/winhttp.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/winhttp.c >$(OBJDIR)/winhttp_.c + $(OBJDIR)/translate $(SRCDIR)/winhttp.c >$@ -$(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h +$(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/winhttp.o -c $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h: $(OBJDIR)/headers + $(OBJDIR)/wysiwyg_.c: $(SRCDIR)/wysiwyg.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/wysiwyg.c >$(OBJDIR)/wysiwyg_.c + $(OBJDIR)/translate $(SRCDIR)/wysiwyg.c >$@ -$(OBJDIR)/wysiwyg.o: $(OBJDIR)/wysiwyg_.c $(OBJDIR)/wysiwyg.h $(SRCDIR)/config.h +$(OBJDIR)/wysiwyg.o: $(OBJDIR)/wysiwyg_.c $(OBJDIR)/wysiwyg.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/wysiwyg.o -c $(OBJDIR)/wysiwyg_.c $(OBJDIR)/wysiwyg.h: $(OBJDIR)/headers + $(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/xfer.c >$(OBJDIR)/xfer_.c + $(OBJDIR)/translate $(SRCDIR)/xfer.c >$@ -$(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h +$(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h: $(OBJDIR)/headers + $(OBJDIR)/xfersetup_.c: $(SRCDIR)/xfersetup.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/xfersetup.c >$(OBJDIR)/xfersetup_.c + $(OBJDIR)/translate $(SRCDIR)/xfersetup.c >$@ -$(OBJDIR)/xfersetup.o: $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h +$(OBJDIR)/xfersetup.o: $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfersetup.o -c $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h: $(OBJDIR)/headers + $(OBJDIR)/zip_.c: $(SRCDIR)/zip.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/zip.c >$(OBJDIR)/zip_.c + $(OBJDIR)/translate $(SRCDIR)/zip.c >$@ -$(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h +$(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c $(OBJDIR)/zip.h: $(OBJDIR)/headers + $(OBJDIR)/sqlite3.o: $(SRCDIR)/sqlite3.c - $(XTCC) -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_STAT3 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_WIN32_NO_ANSI -c $(SRCDIR)/sqlite3.c -o $(OBJDIR)/sqlite3.o + $(XTCC) $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) -c $(SRCDIR)/sqlite3.c -o $@ $(OBJDIR)/shell.o: $(SRCDIR)/shell.c $(SRCDIR)/sqlite3.h - $(XTCC) -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -Dsqlite3_strglob=strglob -c $(SRCDIR)/shell.c -o $(OBJDIR)/shell.o + $(XTCC) $(SHELL_OPTIONS) $(SHELL_CFLAGS) -c $(SRCDIR)/shell.c -o $@ $(OBJDIR)/th.o: $(SRCDIR)/th.c - $(XTCC) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o + $(XTCC) -c $(SRCDIR)/th.c -o $@ $(OBJDIR)/th_lang.o: $(SRCDIR)/th_lang.c - $(XTCC) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o + $(XTCC) -c $(SRCDIR)/th_lang.c -o $@ $(OBJDIR)/th_tcl.o: $(SRCDIR)/th_tcl.c - $(XTCC) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o + $(XTCC) -c $(SRCDIR)/th_tcl.c -o $@ + +$(OBJDIR)/miniz.o: $(SRCDIR)/miniz.c + $(XTCC) $(MINIZ_OPTIONS) -c $(SRCDIR)/miniz.c -o $@ $(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c - $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o + $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $@ # # The list of all the targets that do not correspond to real files. This stops # 'make' from getting confused when someone makes an error in a rule. # .PHONY: all install test clean Index: src/makeheaders.c ================================================================== --- src/makeheaders.c +++ src/makeheaders.c @@ -37,15 +37,16 @@ #include #include #include #include #include +#include + #if defined( __MINGW32__) || defined(__DMC__) || defined(_MSC_VER) || defined(__POCC__) # ifndef WIN32 # define WIN32 # endif -# include #else # include #endif /* @@ -111,19 +112,19 @@ ** ** struct Xyzzy; ** ** Not every object has a forward declaration. If it does, thought, the ** forward declaration will be contained in the zFwd field for C and -** the zFwdCpp for C++. The zDecl field contains the complete -** declaration text. +** the zFwdCpp for C++. The zDecl field contains the complete +** declaration text. */ typedef struct Decl Decl; struct Decl { char *zName; /* Name of the object being declared. The appearance ** of this name is a source file triggers the declaration ** to be added to the header for that file. */ - char *zFile; /* File from which extracted. */ + const char *zFile; /* File from which extracted. */ char *zIf; /* Surround the declaration with this #if */ char *zFwd; /* A forward declaration. NULL if there is none. */ char *zFwdCpp; /* Use this forward declaration for C++. */ char *zDecl; /* A full declaration of this object */ char *zExtra; /* Extra declaration text inserted into class objects */ @@ -162,11 +163,11 @@ ** in the output when using the -H option.) ** ** EXPORT scope The object is visible and usable everywhere. ** ** The DP_Flag is a temporary use flag that is used during processing to -** prevent an infinite loop. It's use is localized. +** prevent an infinite loop. It's use is localized. ** ** The DP_Cplusplus, DP_ExternCReqd and DP_ExternReqd flags are permanent ** and are used to specify what type of declaration the object requires. */ #define DP_Forward 0x001 /* Has a forward declaration in this file */ @@ -200,11 +201,11 @@ ** Be careful not to confuse PS_Export with DP_Export or ** PS_Local with DP_Local. Their names are similar, but the meanings ** of these flags are very different. */ #define PS_Extern 0x000800 /* "extern" has been seen */ -#define PS_Export 0x001000 /* If between "#if EXPORT_INTERFACE" +#define PS_Export 0x001000 /* If between "#if EXPORT_INTERFACE" ** and "#endif" */ #define PS_Export2 0x002000 /* If "EXPORT" seen */ #define PS_Typedef 0x004000 /* If "typedef" has been seen */ #define PS_Static 0x008000 /* If "static" has been seen */ #define PS_Interface 0x010000 /* If within #if INTERFACE..#endif */ @@ -230,11 +231,11 @@ #define TY_Union 0x04000000 #define TY_Enumeration 0x08000000 #define TY_Defunct 0x10000000 /* Used to erase a declaration */ /* -** Each nested #if (or #ifdef or #ifndef) is stored in a stack of +** Each nested #if (or #ifdef or #ifndef) is stored in a stack of ** instances of the following structure. */ typedef struct Ifmacro Ifmacro; struct Ifmacro { int nLine; /* Line number where this macro occurs */ @@ -292,11 +293,11 @@ int flags; /* One or more DP_, PS_ and/or TY_ flags */ InFile *pNext; /* Next input file in the list of them all */ IdentTable idTable; /* All identifiers in this input file */ }; -/* +/* ** An unbounded string is able to grow without limit. We use these ** to construct large in-memory strings from lots of smaller components. */ typedef struct String String; struct String { @@ -331,19 +332,19 @@ ** never to read a file that it generated itself. ** ** The "#undef INTERFACE" part is a hack to work around a name collision ** in MSVC 2008. */ -const char zTopLine[] = +const char zTopLine[] = "/* \aThis file was automatically generated. Do not edit! */\n" "#undef INTERFACE\n"; #define nTopLine (sizeof(zTopLine)-1) /* ** The name of the file currently being parsed. */ -static char *zFilename; +static const char *zFilename; /* ** The stack of #if macros for the file currently being parsed. */ static Ifmacro *ifStack = 0; @@ -701,11 +702,11 @@ struct stat sStat; FILE *pIn; char *zBuf; int n; - if( stat(zFilename,&sStat)!=0 + if( stat(zFilename,&sStat)!=0 #ifndef WIN32 || !S_ISREG(sStat.st_mode) #endif ){ return 0; @@ -888,12 +889,12 @@ } } } i++; } - if( z[i] ){ - i += 2; + if( z[i] ){ + i += 2; }else{ isBlockComment = 0; fprintf(stderr,"%s:%d: Unterminated comment\n", zFilename, startLine); nErr++; @@ -905,11 +906,11 @@ pToken->eType = TT_Other; pToken->nText = 1 + (z[i+1]=='+'); } break; - case '0': + case '0': if( z[i+1]=='x' || z[i+1]=='X' ){ /* A hex constant */ i += 2; while( isxdigit(z[i]) ){ i++; } }else{ @@ -962,11 +963,11 @@ while( isalnum(z[i]) || z[i]=='_' ){ i++; }; pToken->eType = TT_Id; pToken->nText = i - pIn->i; break; - case ':': + case ':': pToken->eType = TT_Other; pToken->nText = 1 + (z[i+1]==':'); break; case '=': @@ -976,11 +977,11 @@ case '-': case '*': case '%': case '^': case '&': - case '|': + case '|': pToken->eType = TT_Other; pToken->nText = 1 + (z[i+1]=='='); break; default: @@ -1063,11 +1064,11 @@ } } /* NOT REACHED */ } -/* +/* ** This routine looks for identifiers (strings of contiguous alphanumeric ** characters) within a preprocessor directive and adds every such string ** found to the given identifier table */ static void FindIdentifiersInMacro(Token *pToken, IdentTable *pTable){ @@ -1156,11 +1157,11 @@ case TT_Id: if( pTable ){ IdentTableInsert(pTable,pToken->zText,pToken->nText); } break; - + case TT_Preprocessor: if( pTable!=0 ){ FindIdentifiersInMacro(pToken,pTable); } break; @@ -1262,11 +1263,11 @@ exit(1); } pList = TokenizeFile(zFile,&sTable); for(p=pList; p; p=p->pNext){ int j; - switch( p->eType ){ + switch( p->eType ){ case TT_Space: printf("%4d: Space\n",p->nLine); break; case TT_Id: printf("%4d: Id %.*s\n",p->nLine,p->nText,p->zText); @@ -1329,11 +1330,11 @@ needSpace = 1; break; default: c = pFirst->zText[0]; - printf("%s%.*s", + printf("%s%.*s", (needSpace && (c=='*' || c=='{')) ? " " : "", pFirst->nText, pFirst->zText); needSpace = pFirst->zText[0]==','; break; } @@ -1370,13 +1371,13 @@ StringInit(&str); pLast = pLast->pNext; while( pFirst!=pLast ){ if( pFirst==pSkip ){ iSkip = nSkip; } - if( iSkip>0 ){ + if( iSkip>0 ){ iSkip--; - pFirst=pFirst->pNext; + pFirst=pFirst->pNext; continue; } switch( pFirst->eType ){ case TT_Preprocessor: StringAppend(&str,"\n",1); @@ -1383,13 +1384,13 @@ StringAppend(&str,pFirst->zText,pFirst->nText); StringAppend(&str,"\n",1); needSpace = 0; break; - case TT_Id: + case TT_Id: switch( pFirst->zText[0] ){ - case 'E': + case 'E': if( pFirst->nText==6 && strncmp(pFirst->zText,"EXPORT",6)==0 ){ skipOne = 1; } break; case 'P': @@ -1644,17 +1645,17 @@ pLast = pLast->pNext; for(p=pFirst; p && p!=pLast; p=p->pNext){ if( p->eType==TT_Id ){ static IdentTable sReserved; static int isInit = 0; - static char *aWords[] = { "char", "class", - "const", "double", "enum", "extern", "EXPORT", "ET_PROC", + static const char *aWords[] = { "char", "class", + "const", "double", "enum", "extern", "EXPORT", "ET_PROC", "float", "int", "long", "PRIVATE", "PROTECTED", "PUBLIC", - "register", "static", "struct", "sizeof", "signed", "typedef", + "register", "static", "struct", "sizeof", "signed", "typedef", "union", "volatile", "virtual", "void", }; - + if( !isInit ){ int i; for(i=0; izText[0]!=')' ){ pLast = pLast->pPrev; } if( pLast==0 || pLast==pFirst || pFirst->pNext==pLast ){ - fprintf(stderr,"%s:%d: Unrecognized syntax.\n", + fprintf(stderr,"%s:%d: Unrecognized syntax.\n", zFilename, pFirst->nLine); return 1; } if( flags & (PS_Interface|PS_Export|PS_Local) ){ fprintf(stderr,"%s:%d: Missing \"inline\" on function or procedure.\n", @@ -1848,11 +1849,11 @@ return 1; } #ifdef DEBUG if( debugMask & PARSER ){ - printf("**** Found inline routine: %.*s on line %d...\n", + printf("**** Found inline routine: %.*s on line %d...\n", pName->nText, pName->zText, pFirst->nLine); PrintTokens(pFirst,pEnd); printf("\n"); } #endif @@ -1887,11 +1888,11 @@ ** to search for an occurrence of an ID followed immediately by '('. ** If found, we have a prototype. Otherwise we are dealing with a ** variable definition. */ static int isVariableDef(Token *pFirst, Token *pEnd){ - if( pEnd && pEnd->zText[0]=='=' && + if( pEnd && pEnd->zText[0]=='=' && (pEnd->pPrev->nText!=8 || strncmp(pEnd->pPrev->zText,"operator",8)!=0) ){ return 1; } while( pFirst && pFirst!=pEnd && pFirst->pNext && pFirst->pNext!=pEnd ){ @@ -1948,11 +1949,11 @@ } while( pFirst!=0 && pFirst->pNext!=pEnd && ((pFirst->nText==6 && strncmp(pFirst->zText,"static",6)==0) || (pFirst->nText==5 && strncmp(pFirst->zText,"LOCAL",6)==0)) ){ - /* Lose the initial "static" or local from local variables. + /* Lose the initial "static" or local from local variables. ** We'll prepend "extern" later. */ pFirst = pFirst->pNext; isLocal = 1; } if( pFirst==0 || !isLocal ){ @@ -1961,11 +1962,11 @@ }else if( flags & PS_Method ){ /* Methods are declared by their class. Don't declare separately. */ return nErr; } isVar = (flags & (PS_Typedef|PS_Method))==0 && isVariableDef(pFirst,pEnd); - if( isVar && (flags & (PS_Interface|PS_Export|PS_Local))!=0 + if( isVar && (flags & (PS_Interface|PS_Export|PS_Local))!=0 && (flags & PS_Extern)==0 ){ fprintf(stderr,"%s:%d: Can't define a variable in this context\n", zFilename, pFirst->nLine); nErr++; } @@ -2094,11 +2095,11 @@ nCmd++; } if( nCmd==5 && strncmp(zCmd,"endif",5)==0 ){ /* - ** Pop the if stack + ** Pop the if stack */ pIf = ifStack; if( pIf==0 ){ fprintf(stderr,"%s:%d: extra '#endif'.\n",zFilename,pToken->nLine); return 1; @@ -2105,11 +2106,11 @@ } ifStack = pIf->pNext; SafeFree(pIf); }else if( nCmd==6 && strncmp(zCmd,"define",6)==0 ){ /* - ** Record a #define if we are in PS_Interface or PS_Export + ** Record a #define if we are in PS_Interface or PS_Export */ Decl *pDecl; if( !(flags & (PS_Local|PS_Interface|PS_Export)) ){ return 0; } zArg = &zCmd[6]; while( *zArg && isspace(*zArg) && *zArg!='\n' ){ @@ -2128,11 +2129,11 @@ }else if( flags & PS_Local ){ DeclSetProperty(pDecl,DP_Local); } }else if( nCmd==7 && strncmp(zCmd,"include",7)==0 ){ /* - ** Record an #include if we are in PS_Interface or PS_Export + ** Record an #include if we are in PS_Interface or PS_Export */ Include *pInclude; char *zIf; if( !(flags & (PS_Interface|PS_Export)) ){ return 0; } @@ -2183,11 +2184,11 @@ PushIfMacro(0,0,0,pToken->nLine,PS_Local); }else{ PushIfMacro(0,zArg,nArg,pToken->nLine,0); } }else if( nCmd==5 && strncmp(zCmd,"ifdef",5)==0 ){ - /* + /* ** Push an #ifdef. */ zArg = &zCmd[5]; while( *zArg && isspace(*zArg) && *zArg!='\n' ){ zArg++; @@ -2206,11 +2207,11 @@ if( *zArg==0 || *zArg=='\n' ){ return 0; } nArg = pToken->nText + (int)(pToken->zText - zArg); PushIfMacro("!defined",zArg,nArg,pToken->nLine,0); }else if( nCmd==4 && strncmp(zCmd,"else",4)==0 ){ /* - ** Invert the #if on the top of the stack + ** Invert the #if on the top of the stack */ if( ifStack==0 ){ fprintf(stderr,"%s:%d: '#else' without an '#if'\n",zFilename, pToken->nLine); return 1; @@ -2223,33 +2224,33 @@ }else{ pIf->flags = 0; } }else{ /* - ** This directive can be safely ignored + ** This directive can be safely ignored */ return 0; } - /* - ** Recompute the preset flags + /* + ** Recompute the preset flags */ *pPresetFlags = 0; for(pIf = ifStack; pIf; pIf=pIf->pNext){ *pPresetFlags |= pIf->flags; } - + return nErr; } /* ** Parse an entire file. Return the number of errors. ** ** pList is a list of tokens in the file. Whitespace tokens have been ** eliminated, and text with {...} has been collapsed into a ** single TT_Brace token. -** +** ** initFlags are a set of parse flags that should always be set for this ** file. For .c files this is normally 0. For .h files it is PS_Interface. */ static int ParseFile(Token *pList, int initFlags){ int nErr = 0; @@ -2278,11 +2279,11 @@ pStart = 0; flags = presetFlags; break; case '=': - if( pList->pPrev->nText==8 + if( pList->pPrev->nText==8 && strncmp(pList->pPrev->zText,"operator",8)==0 ){ break; } nErr += ProcessDecl(pStart,pList,flags); pStart = 0; @@ -2470,11 +2471,11 @@ pDecl->zExtra = 0; } /* ** Reset the DP_Forward and DP_Declared flags on all Decl structures. -** Set both flags for anything that is tagged as local and isn't +** Set both flags for anything that is tagged as local and isn't ** in the file zFilename so that it won't be printing in other files. */ static void ResetDeclFlags(char *zFilename){ Decl *pDecl; @@ -2573,11 +2574,11 @@ int flag; int isCpp; /* True if generating C++ */ int doneTypedef = 0; /* True if a typedef has been done for this object */ /* printf("BEGIN %s of %s\n",needFullDecl?"FULL":"PROTOTYPE",pDecl->zName);*/ - /* + /* ** For any object that has a forward declaration, go ahead and do the ** forward declaration first. */ isCpp = (pState->flags & DP_Cplusplus) != 0; for(p=pDecl; p; p=p->pSameName){ @@ -2625,12 +2626,12 @@ ** function on a recursive call with the same pDecl. Hence, recursive ** calls to this function (through ScanText()) can never change the ** value of DP_Flag out from under us. */ for(p=pDecl; p; p=p->pSameName){ - if( !DeclHasProperty(p,DP_Declared) - && (p->zFwd==0 || needFullDecl) + if( !DeclHasProperty(p,DP_Declared) + && (p->zFwd==0 || needFullDecl) && p->zDecl!=0 ){ DeclSetProperty(p,DP_Forward|DP_Declared|DP_Flag); }else{ DeclClearProperty(p,DP_Flag); @@ -2734,12 +2735,12 @@ ** by sToken. */ pDecl = FindDecl(sToken.zText,sToken.nText); if( pDecl==0 ) continue; - /* - ** If we get this far, we've found an identifier that has a + /* + ** If we get this far, we've found an identifier that has a ** declaration in the database. Now see if we the full declaration ** or just a forward declaration. */ GetNonspaceToken(&sIn,&sNext); if( sNext.zText[0]=='*' ){ @@ -2769,12 +2770,12 @@ int progress; do{ progress = 0; for(pDecl=pDeclFirst; pDecl; pDecl=pDecl->pNext){ - if( DeclHasProperty(pDecl,DP_Forward) - && !DeclHasProperty(pDecl,DP_Declared) + if( DeclHasProperty(pDecl,DP_Forward) + && !DeclHasProperty(pDecl,DP_Declared) ){ DeclareObject(pDecl,pState,1); progress = 1; assert( DeclHasProperty(pDecl,DP_Declared) ); } @@ -2841,11 +2842,11 @@ nErr++; } }else if( report ){ fprintf(report,"unchanged\n"); } - SafeFree(zOldVersion); + SafeFree(zOldVersion); IdentTableReset(&includeTable); StringReset(&outStr); return nErr; } @@ -2877,11 +2878,11 @@ } ChangeIfContext(0,&sState); printf("%s",StringGet(&outStr)); IdentTableReset(&includeTable); StringReset(&outStr); - return 0; + return 0; } #ifdef DEBUG /* ** Return the number of characters in the given string prior to the @@ -3039,11 +3040,11 @@ int nSrc; char *zSrc; InFile *pFile; int i; - /* + /* ** Get the name of the input file to be scanned. The input file is ** everything before the first ':' or the whole file if no ':' is seen. ** ** Except, on windows, ignore any ':' that occurs as the second character ** since it might be part of the drive specifier. So really, the ":' has @@ -3098,11 +3099,11 @@ } } /* ** If pFile->zSrc contains no 'c' or 'C' in its extension, it - ** must be a header file. In that case, we need to set the + ** must be a header file. In that case, we need to set the ** PS_Interface flag. */ pFile->flags |= PS_Interface; for(i=nSrc-1; i>0 && zSrc[i]!='.'; i--){ if( zSrc[i]=='c' || zSrc[i]=='C' ){ @@ -3109,11 +3110,11 @@ pFile->flags &= ~PS_Interface; break; } } - /* Done! + /* Done! */ return pFile; } /* MS-Windows and MS-DOS both have the following serious OS bug: the @@ -3161,11 +3162,11 @@ while( c!=EOF ){ while( c!=EOF && isspace(c) ){ if( c=='\n' ){ startOfLine = 1; } - c = getc(in); + c = getc(in); if( startOfLine && c=='#' ){ while( c!=EOF && c!='\n' ){ c = getc(in); } } @@ -3183,11 +3184,11 @@ if( nAlloc==0 ){ nAlloc = 100 + argc; zNew = malloc( sizeof(char*) * nAlloc ); }else{ nAlloc *= 2; - zNew = realloc( zNew, sizeof(char*) * nAlloc ); + zNew = realloc( zNew, sizeof(char*) * nAlloc ); } } if( zNew ){ int j = nNew + index; zNew[j] = malloc( n + 1 ); @@ -3253,11 +3254,11 @@ /* ** The following text contains a few simple #defines that we want ** to be available to every file. */ -static char zInit[] = +static const char zInit[] = "#define INTERFACE 0\n" "#define EXPORT_INTERFACE 0\n" "#define LOCAL_INTERFACE 0\n" "#define EXPORT\n" "#define LOCAL static\n" Index: src/makemake.tcl ================================================================== --- src/makemake.tcl +++ src/makemake.tcl @@ -1,8 +1,8 @@ #!/usr/bin/tclsh # -# Run this TCL script to generate the various makefiles for a variety +# Run this Tcl script to generate the various makefiles for a variety # of platforms. Files generated include: # # src/main.mk # makefile for all unix systems # win/Makefile.mingw # makefile for mingw on windows # win/Makefile.* # makefiles for other windows compilers @@ -12,12 +12,15 @@ # tclsh makemake.tcl # ############################################################################# # Basenames of all source files that get preprocessed using -# "translate" and "makeheaders". To add new source files to the +# "translate" and "makeheaders". To add new C-language source files to the # project, simply add the basename to this list and rerun this script. +# +# Set the separate extra_files variable further down for how to add non-C +# files, such as string and BLOB resources. # set src { add allrepo attach @@ -24,10 +27,13 @@ bag bisect blob branch browse + builtin + bundle + cache captcha cgi checkin checkout clearsign @@ -45,10 +51,12 @@ encode event export file finfo + foci + fusefs glob graph gzip http http_socket @@ -69,10 +77,11 @@ json_tag json_timeline json_user json_wiki leaf + loadctrl login lookslike main manifest markdown @@ -85,23 +94,27 @@ path pivot popen pqueue printf + publish + purge rebuild regexp report rss schema search setup sha1 shun + sitemap skins sqlcmd stash stat + statrep style sync tag tar th_main @@ -117,17 +130,69 @@ util verify vfile wiki wikiformat + winfile winhttp wysiwyg xfer xfersetup zip http_ssl } + +# Additional resource files that get built into the executable. +# +set extra_files { + diff.tcl + markdown.md + ../skins/*/*.txt +} + +# Options used to compile the included SQLite library. +# +set SQLITE_OPTIONS { + -DNDEBUG=1 + -DSQLITE_OMIT_LOAD_EXTENSION=1 + -DSQLITE_ENABLE_LOCKING_STYLE=0 + -DSQLITE_THREADSAFE=0 + -DSQLITE_DEFAULT_FILE_FORMAT=4 + -DSQLITE_OMIT_DEPRECATED + -DSQLITE_ENABLE_EXPLAIN_COMMENTS + -DSQLITE_ENABLE_FTS4 + -DSQLITE_ENABLE_FTS3_PARENTHESIS +} +#lappend SQLITE_OPTIONS -DSQLITE_ENABLE_FTS3=1 +#lappend SQLITE_OPTIONS -DSQLITE_ENABLE_STAT4 +#lappend SQLITE_OPTIONS -DSQLITE_WIN32_NO_ANSI +#lappend SQLITE_OPTIONS -DSQLITE_WINNT_MAX_PATH_CHARS=4096 + +# Options used to compile the included SQLite shell. +# +set SHELL_OPTIONS { + -Dmain=sqlite3_shell + -DSQLITE_OMIT_LOAD_EXTENSION=1 + -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) + -DSQLITE_SHELL_DBNAME_PROC=fossil_open +} + +# miniz (libz drop-in alternative) precompiler flags. +# +set MINIZ_OPTIONS { + -DMINIZ_NO_STDIO + -DMINIZ_NO_TIME + -DMINIZ_NO_ARCHIVE_APIS +} + +# Options used to compile the included SQLite shell on Windows. +# +set SHELL_WIN32_OPTIONS $SHELL_OPTIONS +lappend SHELL_WIN32_OPTIONS -Daccess=file_access +lappend SHELL_WIN32_OPTIONS -Dsystem=fossil_system +lappend SHELL_WIN32_OPTIONS -Dgetenv=fossil_getenv +lappend SHELL_WIN32_OPTIONS -Dfopen=fossil_fopen # Name of the final application # set name fossil @@ -143,10 +208,19 @@ } # STOP HERE. # Unless the build procedures changes, you should not have to edit anything # below this line. + +# Expand any wildcards in "extra_files" +set new_extra_files {} +foreach file $extra_files { + foreach x [glob -nocomplain $file] { + lappend new_extra_files $x + } +} +set extra_files $new_extra_files ############################################################################## ############################################################################## ############################################################################## # Start by generating the "main.mk" makefile used for all unix systems. @@ -165,17 +239,22 @@ # to regenerate this file. # # This file is included by primary Makefile. # -XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) -I$(OBJDIR) +XTCC = $(TCC) -I. -I$(SRCDIR) -I$(OBJDIR) $(TCCFLAGS) $(CFLAGS) } writeln -nonewline "SRC =" foreach s [lsort $src] { writeln -nonewline " \\\n \$(SRCDIR)/$s.c" } +writeln "\n" +writeln -nonewline "EXTRA_FILES =" +foreach s [lsort $extra_files] { + writeln -nonewline " \\\n \$(SRCDIR)/$s" +} writeln "\n" writeln -nonewline "TRANS_SRC =" foreach s [lsort $src] { writeln -nonewline " \\\n \$(OBJDIR)/${s}_.c" } @@ -186,16 +265,22 @@ } writeln "\n" writeln "APPNAME = $name\$(E)" writeln "\n" -writeln { +writeln [string map [list \ + <<>> [join $SQLITE_OPTIONS " \\\n "] \ + <<>> [join $SHELL_OPTIONS " \\\n "] \ + <<>> [join $MINIZ_OPTIONS " \\\n "]] { all: $(OBJDIR) $(APPNAME) install: $(APPNAME) mkdir -p $(INSTALLDIR) mv $(APPNAME) $(INSTALLDIR) + +codecheck: $(TRANS_SRC) $(OBJDIR)/codecheck1 + $(OBJDIR)/codecheck1 $(TRANS_SRC) $(OBJDIR): -mkdir $(OBJDIR) $(OBJDIR)/translate: $(SRCDIR)/translate.c @@ -205,13 +290,19 @@ $(BCC) -o $(OBJDIR)/makeheaders $(SRCDIR)/makeheaders.c $(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c +$(OBJDIR)/mkbuiltin: $(SRCDIR)/mkbuiltin.c + $(BCC) -o $(OBJDIR)/mkbuiltin $(SRCDIR)/mkbuiltin.c + $(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c $(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c +$(OBJDIR)/codecheck1: $(SRCDIR)/codecheck1.c + $(BCC) -o $(OBJDIR)/codecheck1 $(SRCDIR)/codecheck1.c + # WARNING. DANGER. Running the test suite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) @@ -218,106 +309,117 @@ $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid \ $(SRCDIR)/../manifest \ $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h + +# Setup the options used to compile the included SQLite library. +SQLITE_OPTIONS = <<>> + +# Setup the options used to compile the included SQLite shell. +SHELL_OPTIONS = <<>> + +# Setup the options used to compile the included miniz library. +MINIZ_OPTIONS = <<>> # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set # to 1. If it is set to 1, then there is no need to build or link -# the sqlite3.o object. Instead, the system sqlite will be linked +# the sqlite3.o object. Instead, the system SQLite will be linked # using -lsqlite3. -SQLITE3_OBJ.1 = +SQLITE3_OBJ.1 = SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o SQLITE3_OBJ. = $(SQLITE3_OBJ.0) -# The FOSSIL_ENABLE_TCL variable may be undefined, set to 0, or set to 1. -# If it is set to 1, then we need to build the Tcl integration code and -# link to the Tcl library. -TCL_OBJ.0 = -TCL_OBJ.1 = $(OBJDIR)/th_tcl.o -TCL_OBJ. = $(TCL_OBJ.0) - -EXTRAOBJ = \ - $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \ - $(OBJDIR)/shell.o \ - $(OBJDIR)/th.o \ - $(OBJDIR)/th_lang.o \ - $(TCL_OBJ.$(FOSSIL_ENABLE_TCL)) \ - $(OBJDIR)/cson_amalgamation.o - -$(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) +# The FOSSIL_ENABLE_MINIZ variable may be undefined, set to 0, or +# set to 1. If it is set to 1, the miniz library included in the +# source tree should be used; otherwise, it should not. +MINIZ_OBJ.0 = +MINIZ_OBJ.1 = $(OBJDIR)/miniz.o +MINIZ_OBJ. = $(MINIZ_OBJ.0) +}] + +writeln [string map [list <<>> \\] { +EXTRAOBJ = <<>> + $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) <<>> + $(MINIZ_OBJ.$(FOSSIL_ENABLE_MINIZ)) <<>> + $(OBJDIR)/shell.o <<>> + $(OBJDIR)/th.o <<>> + $(OBJDIR)/th_lang.o <<>> + $(OBJDIR)/th_tcl.o <<>> + $(OBJDIR)/cson_amalgamation.o +}] + +writeln { +$(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ) + $(OBJDIR)/codecheck1 $(TRANS_SRC) $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) # This rule prevents make from using its default rules to try build # an executable named "manifest" out of the file named "manifest.c" # -$(SRCDIR)/../manifest: +$(SRCDIR)/../manifest: # noop -clean: +clean: rm -rf $(OBJDIR)/* $(APPNAME) } set mhargs {} foreach s [lsort $src] { - append mhargs " \$(OBJDIR)/${s}_.c:\$(OBJDIR)/$s.h" - set extra_h($s) {} + append mhargs "\$(OBJDIR)/${s}_.c:\$(OBJDIR)/$s.h <<>>" + set extra_h($s) { } } -append mhargs " \$(SRCDIR)/sqlite3.h" -append mhargs " \$(SRCDIR)/th.h" -#append mhargs " \$(SRCDIR)/cson_amalgamation.h" -append mhargs " \$(OBJDIR)/VERSION.h" +append mhargs "\$(SRCDIR)/sqlite3.h <<>>" +append mhargs "\$(SRCDIR)/th.h <<>>" +#append mhargs "\$(SRCDIR)/cson_amalgamation.h <<>>" +append mhargs "\$(OBJDIR)/VERSION.h" +set mhargs [string map [list <<>> \\\n\t] $mhargs] writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(OBJDIR)/mkindex" -writeln "\t\$(OBJDIR)/mkindex \$(TRANS_SRC) >$@" -writeln "\$(OBJDIR)/headers:\t\$(OBJDIR)/page_index.h \$(OBJDIR)/makeheaders \$(OBJDIR)/VERSION.h" +writeln "\t\$(OBJDIR)/mkindex \$(TRANS_SRC) >\$@\n" + +writeln "\$(OBJDIR)/builtin_data.h: \$(OBJDIR)/mkbuiltin \$(EXTRA_FILES)" +writeln "\t\$(OBJDIR)/mkbuiltin --prefix \$(SRCDIR)/ \$(EXTRA_FILES) >\$@\n" + +writeln "\$(OBJDIR)/headers:\t\$(OBJDIR)/page_index.h \$(OBJDIR)/builtin_data.h \$(OBJDIR)/makeheaders \$(OBJDIR)/VERSION.h" writeln "\t\$(OBJDIR)/makeheaders $mhargs" writeln "\ttouch \$(OBJDIR)/headers" writeln "\$(OBJDIR)/headers: Makefile" writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_artifact.o \$(OBJDIR)/json_branch.o \$(OBJDIR)/json_config.o \$(OBJDIR)/json_diff.o \$(OBJDIR)/json_dir.o \$(OBJDIR)/json_finfo.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_query.o \$(OBJDIR)/json_report.o \$(OBJDIR)/json_status.o \$(OBJDIR)/json_tag.o \$(OBJDIR)/json_timeline.o \$(OBJDIR)/json_user.o \$(OBJDIR)/json_wiki.o : \$(SRCDIR)/json_detail.h" writeln "Makefile:" -set extra_h(main) \$(OBJDIR)/page_index.h +set extra_h(main) " \$(OBJDIR)/page_index.h " +set extra_h(builtin) " \$(OBJDIR)/builtin_data.h " foreach s [lsort $src] { writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(OBJDIR)/translate" - writeln "\t\$(OBJDIR)/translate \$(SRCDIR)/$s.c >\$(OBJDIR)/${s}_.c\n" - writeln "\$(OBJDIR)/$s.o:\t\$(OBJDIR)/${s}_.c \$(OBJDIR)/$s.h $extra_h($s) \$(SRCDIR)/config.h" + writeln "\t\$(OBJDIR)/translate \$(SRCDIR)/$s.c >\$@\n" + writeln "\$(OBJDIR)/$s.o:\t\$(OBJDIR)/${s}_.c \$(OBJDIR)/$s.h$extra_h($s)\$(SRCDIR)/config.h" writeln "\t\$(XTCC) -o \$(OBJDIR)/$s.o -c \$(OBJDIR)/${s}_.c\n" - writeln "\$(OBJDIR)/$s.h:\t\$(OBJDIR)/headers" + writeln "\$(OBJDIR)/$s.h:\t\$(OBJDIR)/headers\n" } - writeln "\$(OBJDIR)/sqlite3.o:\t\$(SRCDIR)/sqlite3.c" -set opt {-DSQLITE_OMIT_LOAD_EXTENSION=1} -append opt " -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4" -#append opt " -DSQLITE_ENABLE_FTS3=1" -append opt " -DSQLITE_ENABLE_STAT3" -append opt " -Dlocaltime=fossil_localtime" -append opt " -DSQLITE_ENABLE_LOCKING_STYLE=0" -append opt " -DSQLITE_WIN32_NO_ANSI" -set SQLITE_OPTIONS $opt -writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/sqlite3.c -o \$(OBJDIR)/sqlite3.o\n" +writeln "\t\$(XTCC) \$(SQLITE_OPTIONS) \$(SQLITE_CFLAGS) -c \$(SRCDIR)/sqlite3.c -o \$@\n" writeln "\$(OBJDIR)/shell.o:\t\$(SRCDIR)/shell.c \$(SRCDIR)/sqlite3.h" -set opt {-Dmain=sqlite3_shell} -append opt " -DSQLITE_OMIT_LOAD_EXTENSION=1" -append opt " -Dsqlite3_strglob=strglob" -writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/shell.c -o \$(OBJDIR)/shell.o\n" +writeln "\t\$(XTCC) \$(SHELL_OPTIONS) \$(SHELL_CFLAGS) -c \$(SRCDIR)/shell.c -o \$@\n" writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c" -writeln "\t\$(XTCC) -c \$(SRCDIR)/th.c -o \$(OBJDIR)/th.o\n" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th.c -o \$@\n" writeln "\$(OBJDIR)/th_lang.o:\t\$(SRCDIR)/th_lang.c" -writeln "\t\$(XTCC) -c \$(SRCDIR)/th_lang.c -o \$(OBJDIR)/th_lang.o\n" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th_lang.c -o \$@\n" writeln "\$(OBJDIR)/th_tcl.o:\t\$(SRCDIR)/th_tcl.c" -writeln "\t\$(XTCC) -c \$(SRCDIR)/th_tcl.c -o \$(OBJDIR)/th_tcl.o\n" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th_tcl.c -o \$@\n" -set opt {} writeln { +$(OBJDIR)/miniz.o: $(SRCDIR)/miniz.c + $(XTCC) $(MINIZ_OPTIONS) -c $(SRCDIR)/miniz.c -o $@ + $(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c - $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o + $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $@ # # The list of all the targets that do not correspond to real files. This stops # 'make' from getting confused when someone makes an error in a rule. # @@ -386,10 +488,23 @@ # FOSSIL_ENABLE_JSON = 1 #### Enable HTTPS support via OpenSSL (links to libssl and libcrypto) # # FOSSIL_ENABLE_SSL = 1 + +#### Automatically build OpenSSL when building Fossil (causes rebuild +# issues when building incrementally). +# +# FOSSIL_BUILD_SSL = 1 + +#### Enable TH1 scripts in embedded documentation files +# +# FOSSIL_ENABLE_TH1_DOCS = 1 + +#### Enable hooks for commands and web pages via TH1 +# +# FOSSIL_ENABLE_TH1_HOOKS = 1 #### Enable scripting support via Tcl/Tk # # FOSSIL_ENABLE_TCL = 1 @@ -399,17 +514,26 @@ #### Load Tcl using the private stubs mechanism # # FOSSIL_ENABLE_TCL_PRIVATE_STUBS = 1 +#### Use 'system' SQLite +# +# USE_SYSTEM_SQLITE = 1 + +#### Use the miniz compression library +# +# FOSSIL_ENABLE_MINIZ = 1 + #### Use the Tcl source directory instead of the install directory? # This is useful when Tcl has been compiled statically with MinGW. # FOSSIL_TCL_SOURCE = 1 #### Check if the workaround for the MinGW command line handling needs to -# be enabled by default. +# be enabled by default. This check may be somewhat fragile due to the +# use of "findstring". # ifndef MINGW_IS_32BIT_ONLY ifeq (,$(findstring w64-mingw32,$(PREFIX))) MINGW_IS_32BIT_ONLY = 1 endif @@ -417,18 +541,59 @@ #### The directories where the zlib include and library files are located. # ZINCDIR = $(SRCDIR)/../compat/zlib ZLIBDIR = $(SRCDIR)/../compat/zlib + +#### Make an attempt to detect if Fossil is being built for the x64 processor +# architecture. This check may be somewhat fragile due to "findstring". +# +ifndef X64 +ifneq (,$(findstring x86_64-w64-mingw32,$(PREFIX))) +X64 = 1 +endif +endif + +#### Determine if the optimized assembly routines provided with zlib should be +# used, taking into account whether zlib is actually enabled and the target +# processor architecture. +# +ifndef X64 +SSLCONFIG = mingw +ifndef FOSSIL_ENABLE_MINIZ +ZLIBCONFIG = LOC="-DASMV -DASMINF" OBJA="inffas86.o match.o" +LIBTARGETS = $(ZLIBDIR)/inffas86.o $(ZLIBDIR)/match.o +else +ZLIBCONFIG = +LIBTARGETS = +endif +else +SSLCONFIG = mingw64 +ZLIBCONFIG = +LIBTARGETS = +endif + +#### Disable creation of the OpenSSL shared libraries. Also, disable support +# for both SSLv2 and SSLv3 (i.e. thereby forcing the use of TLS). +# +SSLCONFIG += no-ssl2 no-ssl3 no-shared + +#### When using zlib, make sure that OpenSSL is configured to use the zlib +# that Fossil knows about (i.e. the one within the source tree). +# +ifndef FOSSIL_ENABLE_MINIZ +SSLCONFIG += --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib +endif #### The directories where the OpenSSL include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "openssl-1.x" sub-directory of the # Fossil source code directory and the target OpenSSL source directory. # -OPENSSLINCDIR = $(SRCDIR)/../compat/openssl-1.0.1e/include -OPENSSLLIBDIR = $(SRCDIR)/../compat/openssl-1.0.1e +OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.0.2a +OPENSSLINCDIR = $(OPENSSLDIR)/include +OPENSSLLIBDIR = $(OPENSSLDIR) #### Either the directory where the Tcl library is installed or the Tcl # source code directory resides (depending on the value of the macro # FOSSIL_TCL_SOURCE). If this points to the Tcl install directory, # this directory must have "include" and "lib" sub-directories. If @@ -469,11 +634,17 @@ # will run on the target platform. This is usually the same # as BCC, unless you are cross-compiling. This C compiler builds # the finished binary for fossil. The BCC compiler above is used # for building intermediate code-generator tools. # -TCC = $(PREFIX)gcc -Os -Wall -L$(ZLIBDIR) -I$(ZINCDIR) +TCC = $(PREFIX)gcc -Os -Wall + +#### When not using the miniz compression library, zlib is required. +# +ifndef FOSSIL_ENABLE_MINIZ +TCC += -L$(ZLIBDIR) -I$(ZINCDIR) +endif #### Add the necessary command line options to build with debugging # symbols, if enabled. # ifdef FOSSIL_ENABLE_SYMBOLS @@ -481,11 +652,15 @@ endif #### Compile resources for use in building executables that will run # on the target platform. # -RCC = $(PREFIX)windres -I$(SRCDIR) -I$(ZINCDIR) +RCC = $(PREFIX)windres -I$(SRCDIR) + +ifndef FOSSIL_ENABLE_MINIZ +RCC += -I$(ZINCDIR) +endif # With HTTPS support ifdef FOSSIL_ENABLE_SSL TCC += -L$(OPENSSLLIBDIR) -I$(OPENSSLINCDIR) RCC += -I$(OPENSSLINCDIR) @@ -499,22 +674,40 @@ else TCC += -L$(TCLLIBDIR) -I$(TCLINCDIR) RCC += -I$(TCLINCDIR) endif endif + +# With miniz (i.e. instead of zlib) +ifdef FOSSIL_ENABLE_MINIZ +TCC += -DFOSSIL_ENABLE_MINIZ=1 +RCC += -DFOSSIL_ENABLE_MINIZ=1 +endif # With MinGW command line handling workaround ifdef MINGW_IS_32BIT_ONLY -TCC += -DBROKEN_MINGW_CMDLINE=1 -D_USE_32BIT_TIME_T -RCC += -DBROKEN_MINGW_CMDLINE=1 -D_USE_32BIT_TIME_T +TCC += -DBROKEN_MINGW_CMDLINE=1 +RCC += -DBROKEN_MINGW_CMDLINE=1 endif # With HTTPS support ifdef FOSSIL_ENABLE_SSL TCC += -DFOSSIL_ENABLE_SSL=1 RCC += -DFOSSIL_ENABLE_SSL=1 endif + +# With TH1 embedded docs support +ifdef FOSSIL_ENABLE_TH1_DOCS +TCC += -DFOSSIL_ENABLE_TH1_DOCS=1 +RCC += -DFOSSIL_ENABLE_TH1_DOCS=1 +endif + +# With TH1 hook support +ifdef FOSSIL_ENABLE_TH1_HOOKS +TCC += -DFOSSIL_ENABLE_TH1_HOOKS=1 +RCC += -DFOSSIL_ENABLE_TH1_HOOKS=1 +endif # With Tcl support ifdef FOSSIL_ENABLE_TCL TCC += -DFOSSIL_ENABLE_TCL=1 RCC += -DFOSSIL_ENABLE_TCL=1 @@ -541,30 +734,45 @@ #### We add the -static option here so that we can build a static # executable that will run in a chroot jail. # LIB = -static -# MinGW: If available, use the Unicode capable runtime startup code. +#### MinGW: If available, use the Unicode capable runtime startup code. +# ifndef MINGW_IS_32BIT_ONLY LIB += -municode endif -# OpenSSL: Add the necessary libraries required, if enabled. +#### SQLite: If enabled, use the system SQLite library. +# +ifdef USE_SYSTEM_SQLITE +LIB += -lsqlite3 +endif + +#### OpenSSL: Add the necessary libraries required, if enabled. +# ifdef FOSSIL_ENABLE_SSL LIB += -lssl -lcrypto -lgdi32 endif -# Tcl: Add the necessary libraries required, if enabled. +#### Tcl: Add the necessary libraries required, if enabled. +# ifdef FOSSIL_ENABLE_TCL LIB += $(LIBTCL) endif #### Extra arguments for linking the finished binary. Fossil needs # to link against the Z-Lib compression library. There are no # other mandatory dependencies. # -LIB += -lmingwex -lz +LIB += -lmingwex + +#### When not using the miniz compression library, zlib is required. +# +ifndef FOSSIL_ENABLE_MINIZ +LIB += -lz +endif #### These libraries MUST appear in the same order as they do for Tcl # or linking with it will not work (exact reason unknown). # ifdef FOSSIL_ENABLE_TCL @@ -582,11 +790,15 @@ # TCLSH = tclsh #### Nullsoft installer MakeNSIS location # -MAKENSIS = "$(ProgramFiles)\NSIS\MakeNSIS.exe" +MAKENSIS = "$(PROGRAMFILES)\NSIS\MakeNSIS.exe" + +#### Inno Setup executable location +# +INNOSETUP = "$(PROGRAMFILES)\Inno Setup 5\ISCC.exe" #### Include a configuration file that can override any one of these settings. # -include config.w32 @@ -597,10 +809,15 @@ } writeln -nonewline "SRC =" foreach s [lsort $src] { writeln -nonewline " \\\n \$(SRCDIR)/$s.c" } +writeln "\n" +writeln -nonewline "EXTRA_FILES =" +foreach s [lsort $extra_files] { + writeln -nonewline " \\\n \$(SRCDIR)/$s" +} writeln "\n" writeln -nonewline "TRANS_SRC =" foreach s [lsort $src] { writeln -nonewline " \\\n \$(OBJDIR)/${s}_.c" } @@ -608,35 +825,44 @@ writeln -nonewline "OBJ =" foreach s [lsort $src] { writeln -nonewline " \\\n \$(OBJDIR)/$s.o" } writeln "\n" -writeln "APPNAME = ${name}.exe" +writeln "APPNAME = ${name}.exe" +writeln "APPTARGETS =" writeln { #### If the USE_WINDOWS variable exists, it is assumed that we are building # inside of a Windows-style shell; otherwise, it is assumed that we are # building inside of a Unix-style shell. Note that the "move" command is # broken when attempting to use it from the Windows shell via MinGW make # because the SHELL variable is only used for certain commands that are # recognized internally by make. # ifdef USE_WINDOWS -TRANSLATE = $(subst /,\,$(OBJDIR)/translate) -MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders) -MKINDEX = $(subst /,\,$(OBJDIR)/mkindex) -VERSION = $(subst /,\,$(OBJDIR)/version) +TRANSLATE = $(subst /,\,$(OBJDIR)/translate.exe) +MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders.exe) +MKINDEX = $(subst /,\,$(OBJDIR)/mkindex.exe) +MKBUILTIN = $(subst /,\,$(OBJDIR)/mkbuiltin.exe) +MKVERSION = $(subst /,\,$(OBJDIR)/mkversion.exe) +CODECHECK1 = $(subst /,\,$(OBJDIR)/codecheck1.exe) +CAT = type CP = copy +GREP = find MV = copy RM = del /Q MKDIR = -mkdir RMDIR = rmdir /S /Q else -TRANSLATE = $(OBJDIR)/translate -MAKEHEADERS = $(OBJDIR)/makeheaders -MKINDEX = $(OBJDIR)/mkindex -VERSION = $(OBJDIR)/version +TRANSLATE = $(OBJDIR)/translate.exe +MAKEHEADERS = $(OBJDIR)/makeheaders.exe +MKINDEX = $(OBJDIR)/mkindex.exe +MKBUILTIN = $(OBJDIR)/mkbuiltin.exe +MKVERSION = $(OBJDIR)/mkversion.exe +CODECHECK1 = $(OBJDIR)/codecheck1.exe +CAT = cat CP = cp +GREP = grep MV = mv RM = rm -f MKDIR = -mkdir -p RMDIR = rm -rf endif} @@ -644,15 +870,19 @@ writeln { all: $(OBJDIR) $(APPNAME) $(OBJDIR)/fossil.o: $(SRCDIR)/../win/fossil.rc $(OBJDIR)/VERSION.h ifdef USE_WINDOWS + $(CAT) $(subst /,\,$(SRCDIR)\miniz.c) | $(GREP) "define MZ_VERSION" > $(subst /,\,$(OBJDIR)\minizver.h) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.rc) $(subst /,\,$(OBJDIR)) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.ico) $(subst /,\,$(OBJDIR)) + $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.exe.manifest) $(subst /,\,$(OBJDIR)) else + $(CAT) $(SRCDIR)/miniz.c | $(GREP) "define MZ_VERSION" > $(OBJDIR)/minizver.h $(CP) $(SRCDIR)/../win/fossil.rc $(OBJDIR) $(CP) $(SRCDIR)/../win/fossil.ico $(OBJDIR) + $(CP) $(SRCDIR)/../win/fossil.exe.manifest $(OBJDIR) endif $(RCC) $(OBJDIR)/fossil.rc -o $(OBJDIR)/fossil.o install: $(OBJDIR) $(APPNAME) ifdef USE_WINDOWS @@ -668,50 +898,84 @@ $(MKDIR) $(subst /,\,$(OBJDIR)) else $(MKDIR) $(OBJDIR) endif -$(OBJDIR)/translate: $(SRCDIR)/translate.c - $(BCC) -o $(OBJDIR)/translate $(SRCDIR)/translate.c - -$(OBJDIR)/makeheaders: $(SRCDIR)/makeheaders.c - $(BCC) -o $(OBJDIR)/makeheaders $(SRCDIR)/makeheaders.c - -$(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c - $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c - -$(VERSION): $(SRCDIR)/mkversion.c - $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c +$(TRANSLATE): $(SRCDIR)/translate.c + $(BCC) -o $@ $(SRCDIR)/translate.c + +$(MAKEHEADERS): $(SRCDIR)/makeheaders.c + $(BCC) -o $@ $(SRCDIR)/makeheaders.c + +$(MKINDEX): $(SRCDIR)/mkindex.c + $(BCC) -o $@ $(SRCDIR)/mkindex.c + +$(MKBUILTIN): $(SRCDIR)/mkbuiltin.c + $(BCC) -o $@ $(SRCDIR)/mkbuiltin.c + +$(MKVERSION): $(SRCDIR)/mkversion.c + $(BCC) -o $@ $(SRCDIR)/mkversion.c + +$(CODECHECK1): $(SRCDIR)/codecheck1.c + $(BCC) -o $@ $(SRCDIR)/codecheck1.c # WARNING. DANGER. Running the test suite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) -$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION) - $(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h - -EXTRAOBJ = \ - $(OBJDIR)/sqlite3.o \ - $(OBJDIR)/shell.o \ - $(OBJDIR)/th.o \ - $(OBJDIR)/th_lang.o \ - $(OBJDIR)/cson_amalgamation.o - -ifdef FOSSIL_ENABLE_TCL -EXTRAOBJ += $(OBJDIR)/th_tcl.o -endif - +$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION) + $(MKVERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$@ + +# The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set +# to 1. If it is set to 1, then there is no need to build or link +# the sqlite3.o object. Instead, the system SQLite will be linked +# using -lsqlite3. +SQLITE3_OBJ.1 = +SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o +SQLITE3_OBJ. = $(SQLITE3_OBJ.0) + +# The FOSSIL_ENABLE_MINIZ variable may be undefined, set to 0, or +# set to 1. If it is set to 1, the miniz library included in the +# source tree should be used; otherwise, it should not. +MINIZ_OBJ.0 = +MINIZ_OBJ.1 = $(OBJDIR)/miniz.o +MINIZ_OBJ. = $(MINIZ_OBJ.0) +} + +writeln [string map [list <<>> \\] { +EXTRAOBJ = <<>> + $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) <<>> + $(MINIZ_OBJ.$(FOSSIL_ENABLE_MINIZ)) <<>> + $(OBJDIR)/shell.o <<>> + $(OBJDIR)/th.o <<>> + $(OBJDIR)/th_lang.o <<>> + $(OBJDIR)/th_tcl.o <<>> + $(OBJDIR)/cson_amalgamation.o +}] + +writeln { zlib: - $(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc libz.a + $(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) $(ZLIBCONFIG) -f win32/Makefile.gcc libz.a clean-zlib: $(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc clean -openssl: zlib - cd $(OPENSSLLIBDIR);./Configure --cross-compile-prefix=$(PREFIX) --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib mingw +$(ZLIBDIR)/inffas86.o: + $(TCC) -c -o $@ -DASMINF -I$(ZLIBDIR) -O3 $(ZLIBDIR)/contrib/inflate86/inffas86.c + +$(ZLIBDIR)/match.o: + $(TCC) -c -o $@ -DASMV $(ZLIBDIR)/contrib/asm686/match.S + + +ifndef FOSSIL_ENABLE_MINIZ +LIBTARGETS += zlib +endif + +openssl: $(LIBTARGETS) + cd $(OPENSSLLIBDIR);./Configure --cross-compile-prefix=$(PREFIX) $(SSLCONFIG) $(MAKE) -C $(OPENSSLLIBDIR) build_libs clean-openssl: $(MAKE) -C $(OPENSSLLIBDIR) clean @@ -720,12 +984,19 @@ $(MAKE) -C $(TCLSRCDIR)/win $(TCLTARGET) clean-tcl: $(MAKE) -C $(TCLSRCDIR)/win distclean -$(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(OBJDIR)/fossil.o zlib - $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/fossil.o +APPTARGETS += $(LIBTARGETS) + +ifdef FOSSIL_BUILD_SSL +APPTARGETS += openssl +endif + +$(APPNAME): $(APPTARGETS) $(OBJDIR)/headers $(CODECHECK1) $(OBJ) $(EXTRAOBJ) $(OBJDIR)/fossil.o + $(CODECHECK1) $(TRANS_SRC) + $(TCC) -o $@ $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/fossil.o # This rule prevents make from using its default rules to try build # an executable named "manifest" out of the file named "manifest.c" # $(SRCDIR)/../manifest: @@ -739,64 +1010,85 @@ $(RM) $(APPNAME) $(RMDIR) $(OBJDIR) endif setup: $(OBJDIR) $(APPNAME) - $(MAKENSIS) ./fossil.nsi + $(MAKENSIS) ./setup/fossil.nsi + +innosetup: $(OBJDIR) $(APPNAME) + $(INNOSETUP) ./setup/fossil.iss -DAppVersion=$(shell $(CAT) ./VERSION) } set mhargs {} foreach s [lsort $src] { if {[string length $mhargs] > 0} {append mhargs " \\\n\t\t"} append mhargs "\$(OBJDIR)/${s}_.c:\$(OBJDIR)/$s.h" - set extra_h($s) {} + set extra_h($s) { } } append mhargs " \\\n\t\t\$(SRCDIR)/sqlite3.h" append mhargs " \\\n\t\t\$(SRCDIR)/th.h" append mhargs " \\\n\t\t\$(OBJDIR)/VERSION.h" -writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(OBJDIR)/mkindex" -writeln "\t\$(MKINDEX) \$(TRANS_SRC) >$@\n" -writeln "\$(OBJDIR)/headers:\t\$(OBJDIR)/page_index.h \$(OBJDIR)/makeheaders \$(OBJDIR)/VERSION.h" +writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(MKINDEX)" +writeln "\t\$(MKINDEX) \$(TRANS_SRC) >\$@\n" + +writeln "\$(OBJDIR)/builtin_data.h:\t\$(MKBUILTIN) \$(EXTRA_FILES)" +writeln "\t\$(MKBUILTIN) --prefix \$(SRCDIR)/ \$(EXTRA_FILES) >\$@\n" + +writeln "\$(OBJDIR)/headers:\t\$(OBJDIR)/page_index.h \$(OBJDIR)/builtin_data.h \$(MAKEHEADERS) \$(OBJDIR)/VERSION.h" writeln "\t\$(MAKEHEADERS) $mhargs" writeln "\techo Done >\$(OBJDIR)/headers\n" writeln "\$(OBJDIR)/headers: Makefile\n" writeln "Makefile:\n" -set extra_h(main) \$(OBJDIR)/page_index.h +set extra_h(main) " \$(OBJDIR)/page_index.h " +set extra_h(builtin) " \$(OBJDIR)/builtin_data.h " foreach s [lsort $src] { - writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(OBJDIR)/translate" - writeln "\t\$(TRANSLATE) \$(SRCDIR)/$s.c >\$(OBJDIR)/${s}_.c\n" - writeln "\$(OBJDIR)/$s.o:\t\$(OBJDIR)/${s}_.c \$(OBJDIR)/$s.h $extra_h($s) \$(SRCDIR)/config.h" + writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(TRANSLATE)" + writeln "\t\$(TRANSLATE) \$(SRCDIR)/$s.c >\$@\n" + writeln "\$(OBJDIR)/$s.o:\t\$(OBJDIR)/${s}_.c \$(OBJDIR)/$s.h$extra_h($s)\$(SRCDIR)/config.h" writeln "\t\$(XTCC) -o \$(OBJDIR)/$s.o -c \$(OBJDIR)/${s}_.c\n" writeln "\$(OBJDIR)/${s}.h:\t\$(OBJDIR)/headers\n" } +set SQLITE_WIN32_OPTIONS $SQLITE_OPTIONS +lappend SQLITE_WIN32_OPTIONS -DSQLITE_WIN32_NO_ANSI -writeln "\$(OBJDIR)/sqlite3.o:\t\$(SRCDIR)/sqlite3.c" -set opt $SQLITE_OPTIONS -writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/sqlite3.c -o \$(OBJDIR)/sqlite3.o\n" +set MINGW_SQLITE_OPTIONS $SQLITE_WIN32_OPTIONS +lappend MINGW_SQLITE_OPTIONS -D_HAVE__MINGW_H +lappend MINGW_SQLITE_OPTIONS -DSQLITE_USE_MALLOC_H +lappend MINGW_SQLITE_OPTIONS -DSQLITE_USE_MSIZE -set opt {} +set MINIZ_WIN32_OPTIONS $MINIZ_OPTIONS + +set j " \\\n " +writeln "SQLITE_OPTIONS = [join $MINGW_SQLITE_OPTIONS $j]\n" +set j " \\\n " +writeln "SHELL_OPTIONS = [join $SHELL_WIN32_OPTIONS $j]\n" +set j " \\\n " +writeln "MINIZ_OPTIONS = [join $MINIZ_WIN32_OPTIONS $j]\n" + +writeln "\$(OBJDIR)/sqlite3.o:\t\$(SRCDIR)/sqlite3.c \$(SRCDIR)/../win/Makefile.mingw" +writeln "\t\$(XTCC) \$(SQLITE_OPTIONS) \$(SQLITE_CFLAGS) -c \$(SRCDIR)/sqlite3.c -o \$@\n" + writeln "\$(OBJDIR)/cson_amalgamation.o:\t\$(SRCDIR)/cson_amalgamation.c" -writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/cson_amalgamation.c -o \$(OBJDIR)/cson_amalgamation.o\n" +writeln "\t\$(XTCC) -c \$(SRCDIR)/cson_amalgamation.c -o \$@\n" writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_artifact.o \$(OBJDIR)/json_branch.o \$(OBJDIR)/json_config.o \$(OBJDIR)/json_diff.o \$(OBJDIR)/json_dir.o \$(OBJDIR)/jsos_finfo.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_query.o \$(OBJDIR)/json_report.o \$(OBJDIR)/json_status.o \$(OBJDIR)/json_tag.o \$(OBJDIR)/json_timeline.o \$(OBJDIR)/json_user.o \$(OBJDIR)/json_wiki.o : \$(SRCDIR)/json_detail.h\n" -writeln "\$(OBJDIR)/shell.o:\t\$(SRCDIR)/shell.c \$(SRCDIR)/sqlite3.h" -set opt {-Dmain=sqlite3_shell} -append opt " -DSQLITE_OMIT_LOAD_EXTENSION=1" -writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/shell.c -o \$(OBJDIR)/shell.o\n" +writeln "\$(OBJDIR)/shell.o:\t\$(SRCDIR)/shell.c \$(SRCDIR)/sqlite3.h \$(SRCDIR)/../win/Makefile.mingw" +writeln "\t\$(XTCC) \$(SHELL_OPTIONS) \$(SHELL_CFLAGS) -c \$(SRCDIR)/shell.c -o \$@\n" writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c" -writeln "\t\$(XTCC) -c \$(SRCDIR)/th.c -o \$(OBJDIR)/th.o\n" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th.c -o \$@\n" writeln "\$(OBJDIR)/th_lang.o:\t\$(SRCDIR)/th_lang.c" -writeln "\t\$(XTCC) -c \$(SRCDIR)/th_lang.c -o \$(OBJDIR)/th_lang.o\n" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th_lang.c -o \$@\n" -writeln {ifdef FOSSIL_ENABLE_TCL -$(OBJDIR)/th_tcl.o: $(SRCDIR)/th_tcl.c - $(XTCC) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o -endif} +writeln "\$(OBJDIR)/th_tcl.o:\t\$(SRCDIR)/th_tcl.c" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th_tcl.c -o \$@\n" + +writeln "\$(OBJDIR)/miniz.o:\t\$(SRCDIR)/miniz.c" +writeln "\t\$(XTCC) \$(MINIZ_OPTIONS) -c \$(SRCDIR)/miniz.c -o \$@\n" close $output_file # # End of the win/Makefile.mingw output ############################################################################## @@ -834,11 +1126,12 @@ CFLAGS = -o BCC = $(DMDIR)\bin\dmc $(CFLAGS) TCC = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL) LIBS = $(DMDIR)\extra\lib\ zlib wsock32 advapi32 } -writeln "SQLITE_OPTIONS = $SQLITE_OPTIONS\n" +writeln "SQLITE_OPTIONS = [join $SQLITE_OPTIONS { }]\n" +writeln "SHELL_OPTIONS = [join $SHELL_WIN32_OPTIONS { }]\n" writeln -nonewline "SRC = " foreach s [lsort $src] { writeln -nonewline "${s}_.c " } writeln "\n" @@ -854,12 +1147,13 @@ APPNAME = $(OBJDIR)\fossil$(E) all: $(APPNAME) -$(APPNAME) : translate$E mkindex$E headers $(OBJ) $(OBJDIR)\link - cd $(OBJDIR) +$(APPNAME) : translate$E mkindex$E codecheck1$E headers $(OBJ) $(OBJDIR)\link + cd $(OBJDIR) + codecheck1$E $(SRC) $(DMDIR)\bin\link @link $(OBJDIR)\fossil.res: $B\win\fossil.rc $(RC) $(RCFLAGS) -o$@ $** @@ -883,18 +1177,24 @@ $(BCC) -o$@ $** mkindex$E: $(SRCDIR)\mkindex.c $(BCC) -o$@ $** -version$E: $B\src\mkversion.c +mkbuiltin$E: $(SRCDIR)\mkbuiltin.c + $(BCC) -o$@ $** + +mkversion$E: $(SRCDIR)\mkversion.c + $(BCC) -o$@ $** + +codecheck1$E: $(SRCDIR)\codecheck1.c $(BCC) -o$@ $** $(OBJDIR)\shell$O : $(SRCDIR)\shell.c - $(TCC) -o$@ -c -Dmain=sqlite3_shell $(SQLITE_OPTIONS) $** + $(TCC) -o$@ -c $(SHELL_OPTIONS) $(SQLITE_OPTIONS) $(SHELL_CFLAGS) $** $(OBJDIR)\sqlite3$O : $(SRCDIR)\sqlite3.c - $(TCC) -o$@ -c $(SQLITE_OPTIONS) $** + $(TCC) -o$@ -c $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) $** $(OBJDIR)\th$O : $(SRCDIR)\th.c $(TCC) -o$@ -c $** $(OBJDIR)\th_lang$O : $(SRCDIR)\th_lang.c @@ -901,22 +1201,25 @@ $(TCC) -o$@ -c $** $(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h cp $@ $@ -VERSION.h : version$E $B\manifest.uuid $B\manifest $B\VERSION +VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION + +$** > $@ + +page_index.h: mkindex$E $(SRC) +$** > $@ -page_index.h: mkindex$E $(SRC) - +$** > $@ +builtin_data.h: mkbuiltin$E $(EXTRA_FILES) + mkbuiltin$E --prefix $(SRCDIR)/ $(EXTRA_FILES) > $@ clean: -del $(OBJDIR)\*.obj -del *.obj *_.c *.h *.map realclean: - -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E + -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E codecheck1$E mkbuiltin$E $(OBJDIR)\json$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_config$O : $(SRCDIR)\json_detail.h @@ -939,11 +1242,11 @@ writeln "\t\$(TCC) -o\$@ -c ${s}_.c\n" writeln "${s}_.c : \$(SRCDIR)\\$s.c" writeln "\t+translate\$E \$** > \$@\n" } -writeln -nonewline "headers: makeheaders\$E page_index.h VERSION.h\n\t +makeheaders\$E " +writeln -nonewline "headers: makeheaders\$E page_index.h builtin_data.h VERSION.h\n\t +makeheaders\$E " foreach s [lsort $src] { writeln -nonewline "${s}_.c:$s.h " } writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h \$(SRCDIR)\\cson_amalgamation.h" writeln "\t@copy /Y nul: headers" @@ -962,67 +1265,204 @@ writeln {# ############################################################################## # WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") ############################################################################## +# +# This Makefile will only function correctly if used from a sub-directory +# that is a direct child of the top-level directory for this project. +# +!if !exist("..\.fossil-settings") +!error "Please change the current directory to the one containing this file." +!endif + # # This file is automatically generated. Instead of editing this # file, edit "makemake.tcl" then run "tclsh makemake.tcl" # to regenerate this file. # -B = .. -SRCDIR = $B\src -OBJDIR = . -OX = . -O = .obj -E = .exe +B = .. +SRCDIR = $B\src +OBJDIR = . +OX = . +O = .obj +E = .exe +P = .pdb + +# Perl is only necessary if OpenSSL support is enabled and it must +# be built from source code. The PERLDIR variable should point to +# the directory containing the main Perl binary (i.e. "perl.exe"). +PERLDIR = C:\Perl\bin +PERL = perl.exe + +# Uncomment to enable debug symbols +# DEBUG = 1 + +# Uncomment to support Windows XP with Visual Studio 201x +# FOSSIL_ENABLE_WINXP = 1 # Uncomment to enable JSON API # FOSSIL_ENABLE_JSON = 1 + +# Uncomment to enable miniz usage +# FOSSIL_ENABLE_MINIZ = 1 # Uncomment to enable SSL support # FOSSIL_ENABLE_SSL = 1 + +# Uncomment to build SSL libraries +# FOSSIL_BUILD_SSL = 1 + +# Uncomment to enable TH1 scripts in embedded documentation files +# FOSSIL_ENABLE_TH1_DOCS = 1 + +# Uncomment to enable TH1 hooks +# FOSSIL_ENABLE_TH1_HOOKS = 1 + +# Uncomment to enable Tcl support +# FOSSIL_ENABLE_TCL = 1 !ifdef FOSSIL_ENABLE_SSL -SSLINCDIR = $(B)\compat\openssl-1.0.1e\include -SSLLIBDIR = $(B)\compat\openssl-1.0.1e\out32 +SSLDIR = $(B)\compat\openssl-1.0.2a +SSLINCDIR = $(SSLDIR)\inc32 +SSLLIBDIR = $(SSLDIR)\out32 +SSLLFLAGS = /nologo /opt:ref /debug SSLLIB = ssleay32.lib libeay32.lib user32.lib gdi32.lib +!if "$(PLATFORM)"=="amd64" || "$(PLATFORM)"=="x64" +!message Using 'x64' platform for OpenSSL... +# BUGBUG (OpenSSL): Apparently, using "no-ssl*" here breaks the build. +# SSLCONFIG = VC-WIN64A no-asm no-ssl2 no-ssl3 no-shared +SSLCONFIG = VC-WIN64A no-asm no-shared +SSLSETUP = ms\do_win64a.bat +SSLNMAKE = ms\nt.mak all +SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 +!elseif "$(PLATFORM)"=="ia64" +!message Using 'ia64' platform for OpenSSL... +# BUGBUG (OpenSSL): Apparently, using "no-ssl*" here breaks the build. +# SSLCONFIG = VC-WIN64I no-asm no-ssl2 no-ssl3 no-shared +SSLCONFIG = VC-WIN64I no-asm no-shared +SSLSETUP = ms\do_win64i.bat +SSLNMAKE = ms\nt.mak all +SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 +!else +!message Assuming 'x86' platform for OpenSSL... +# BUGBUG (OpenSSL): Apparently, using "no-ssl*" here breaks the build. +# SSLCONFIG = VC-WIN32 no-asm no-ssl2 no-ssl3 no-shared +SSLCONFIG = VC-WIN32 no-asm no-shared +SSLSETUP = ms\do_ms.bat +SSLNMAKE = ms\nt.mak all +SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 +!endif +!endif + +!ifdef FOSSIL_ENABLE_TCL +TCLDIR = $(B)\compat\tcl-8.6 +TCLSRCDIR = $(TCLDIR) +TCLINCDIR = $(TCLSRCDIR)\generic !endif # zlib options -ZINCDIR = $(B)\compat\zlib -ZLIBDIR = $(B)\compat\zlib -ZLIB = zlib.lib - -INCL = -I. -I$(SRCDIR) -I$B\win\include -I$(ZINCDIR) - -!ifdef FOSSIL_ENABLE_SSL -INCL = $(INCL) -I$(SSLINCDIR) -!endif - -CFLAGS = -nologo -MT -O2 -BCC = $(CC) $(CFLAGS) -TCC = $(CC) -c $(CFLAGS) $(MSCDEF) $(INCL) -RCC = rc -D_WIN32 -D_MSC_VER $(MSCDEF) $(INCL) -LIBS = $(ZLIB) ws2_32.lib advapi32.lib -LIBDIR = -LIBPATH:$(ZLIBDIR) - -!ifdef FOSSIL_ENABLE_JSON -TCC = $(TCC) -DFOSSIL_ENABLE_JSON=1 -RCC = $(RCC) -DFOSSIL_ENABLE_JSON=1 -!endif - -!ifdef FOSSIL_ENABLE_SSL -TCC = $(TCC) -DFOSSIL_ENABLE_SSL=1 -RCC = $(RCC) -DFOSSIL_ENABLE_SSL=1 -LIBS = $(LIBS) $(SSLLIB) -LIBDIR = $(LIBDIR) -LIBPATH:$(SSLLIBDIR) -!endif -} -regsub -all {[-]D} $SQLITE_OPTIONS {/D} MSC_SQLITE_OPTIONS -set j " \\\n " -writeln "SQLITE_OPTIONS = [join $MSC_SQLITE_OPTIONS $j]\n" +ZINCDIR = $(B)\compat\zlib +ZLIBDIR = $(B)\compat\zlib +ZLIB = zlib.lib + +INCL = /I. /I$(SRCDIR) /I$B\win\include + +!ifndef FOSSIL_ENABLE_MINIZ +INCL = $(INCL) /I$(ZINCDIR) +!endif + +!ifdef FOSSIL_ENABLE_SSL +INCL = $(INCL) /I$(SSLINCDIR) +!endif + +!ifdef FOSSIL_ENABLE_TCL +INCL = $(INCL) /I$(TCLINCDIR) +!endif + +CFLAGS = /nologo +LDFLAGS = /NODEFAULTLIB:msvcrt /MANIFEST:NO + +!ifdef FOSSIL_ENABLE_WINXP +XPCFLAGS = $(XPCFLAGS) /D_USING_V110_SDK71_=1 +CFLAGS = $(CFLAGS) $(XPCFLAGS) +!if "$(PLATFORM)"=="amd64" || "$(PLATFORM)"=="x64" +XPLDFLAGS = $(XPLDFLAGS) /SUBSYSTEM:CONSOLE,5.02 +!else +XPLDFLAGS = $(XPLDFLAGS) /SUBSYSTEM:CONSOLE,5.01 +!endif +LDFLAGS = $(LDFLAGS) $(XPLDFLAGS) +!endif + +!ifdef DEBUG +CFLAGS = $(CFLAGS) /Zi /MTd /Od +LDFLAGS = $(LDFLAGS) /DEBUG +!else +CFLAGS = $(CFLAGS) /MT /O2 +!endif + +BCC = $(CC) $(CFLAGS) +TCC = $(CC) /c $(CFLAGS) $(MSCDEF) $(INCL) +RCC = rc /D_WIN32 /D_MSC_VER $(MSCDEF) $(INCL) +LIBS = ws2_32.lib advapi32.lib +LIBDIR = + +!ifndef FOSSIL_ENABLE_MINIZ +LIBS = $(LIBS) $(ZLIB) +LIBDIR = $(LIBDIR) /LIBPATH:$(ZLIBDIR) +!endif + +!ifdef FOSSIL_ENABLE_MINIZ +TCC = $(TCC) /DFOSSIL_ENABLE_MINIZ=1 +RCC = $(RCC) /DFOSSIL_ENABLE_MINIZ=1 +!endif + +!ifdef FOSSIL_ENABLE_JSON +TCC = $(TCC) /DFOSSIL_ENABLE_JSON=1 +RCC = $(RCC) /DFOSSIL_ENABLE_JSON=1 +!endif + +!ifdef FOSSIL_ENABLE_SSL +TCC = $(TCC) /DFOSSIL_ENABLE_SSL=1 +RCC = $(RCC) /DFOSSIL_ENABLE_SSL=1 +LIBS = $(LIBS) $(SSLLIB) +LIBDIR = $(LIBDIR) /LIBPATH:$(SSLLIBDIR) +!endif + +!ifdef FOSSIL_ENABLE_TH1_DOCS +TCC = $(TCC) /DFOSSIL_ENABLE_TH1_DOCS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TH1_DOCS=1 +!endif + +!ifdef FOSSIL_ENABLE_TH1_HOOKS +TCC = $(TCC) /DFOSSIL_ENABLE_TH1_HOOKS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TH1_HOOKS=1 +!endif + +!ifdef FOSSIL_ENABLE_TCL +TCC = $(TCC) /DFOSSIL_ENABLE_TCL=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TCL=1 +TCC = $(TCC) /DFOSSIL_ENABLE_TCL_STUBS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TCL_STUBS=1 +TCC = $(TCC) /DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1 +TCC = $(TCC) /DUSE_TCL_STUBS=1 +RCC = $(RCC) /DUSE_TCL_STUBS=1 +!endif +} +regsub -all {[-]D} [join $SQLITE_WIN32_OPTIONS { }] {/D} MSC_SQLITE_OPTIONS +set j " \\\n " +writeln "SQLITE_OPTIONS = [join $MSC_SQLITE_OPTIONS $j]\n" + +regsub -all {[-]D} [join $SHELL_WIN32_OPTIONS { }] {/D} MSC_SHELL_OPTIONS +set j " \\\n " +writeln "SHELL_OPTIONS = [join $MSC_SHELL_OPTIONS $j]\n" + +regsub -all {[-]D} [join $MINIZ_WIN32_OPTIONS { }] {/D} MSC_MINIZ_OPTIONS +set j " \\\n " +writeln "MINIZ_OPTIONS = [join $MSC_MINIZ_OPTIONS $j]\n" + writeln -nonewline "SRC = " set i 0 foreach s [lsort $src] { if {$i > 0} { writeln " \\" @@ -1029,45 +1469,95 @@ writeln -nonewline " " } writeln -nonewline "${s}_.c"; incr i } writeln "\n" -set AdditionalObj [list shell sqlite3 th th_lang cson_amalgamation] +writeln -nonewline "EXTRA_FILES = " +set i 0 +foreach s [lsort $extra_files] { + if {$i > 0} { + writeln " \\" + writeln -nonewline " " + } + writeln -nonewline "\$(SRCDIR)\\${s}"; incr i +} +writeln "\n" +set AdditionalObj [list shell sqlite3 th th_lang th_tcl cson_amalgamation] writeln -nonewline "OBJ = " set i 0 foreach s [lsort [concat $src $AdditionalObj]] { if {$i > 0} { writeln " \\" writeln -nonewline " " } writeln -nonewline "\$(OX)\\$s\$O"; incr i } -writeln " \\" -writeln -nonewline " \$(OX)\\fossil.res\n" +if {$i > 0} { + writeln " \\" +} +writeln "!ifdef FOSSIL_ENABLE_MINIZ" +writeln -nonewline " " +writeln "\$(OX)\\miniz\$O \\"; incr i +writeln "!endif" +writeln -nonewline " \$(OX)\\fossil.res\n\n" writeln { -APPNAME = $(OX)\fossil$(E) +APPNAME = $(OX)\fossil$(E) +PDBNAME = $(OX)\fossil$(P) +APPTARGETS = all: $(OX) $(APPNAME) zlib: @echo Building zlib from "$(ZLIBDIR)"... - @pushd "$(ZLIBDIR)" && nmake /f win32\Makefile.msc $(ZLIB) && popd +!ifdef FOSSIL_ENABLE_WINXP + @pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc $(ZLIB) "CC=cl $(XPCFLAGS)" "LD=link $(XPLDFLAGS)" && popd +!else + @pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc $(ZLIB) && popd +!endif + +!ifdef FOSSIL_ENABLE_SSL +openssl: + @echo Building OpenSSL from "$(SSLDIR)"... +!if "$(PERLDIR)" != "" + @set PATH=$(PERLDIR);$(PATH) +!endif + @pushd "$(SSLDIR)" && $(PERL) Configure $(SSLCONFIG) && popd + @pushd "$(SSLDIR)" && call $(SSLSETUP) && popd +!ifdef FOSSIL_ENABLE_WINXP + @pushd "$(SSLDIR)" && $(MAKE) /f $(SSLNMAKE) "CC=cl $(SSLCFLAGS) $(XPCFLAGS)" "LFLAGS=$(SSLLFLAGS) $(XPLDFLAGS)" && popd +!else + @pushd "$(SSLDIR)" && $(MAKE) /f $(SSLNMAKE) "CC=cl $(SSLCFLAGS)" && popd +!endif +!endif + +!ifndef FOSSIL_ENABLE_MINIZ +APPTARGETS = $(APPTARGETS) zlib +!endif + +!ifdef FOSSIL_ENABLE_SSL +!ifdef FOSSIL_BUILD_SSL +APPTARGETS = $(APPTARGETS) openssl +!endif +!endif -$(APPNAME) : translate$E mkindex$E headers $(OBJ) $(OX)\linkopts zlib - cd $(OX) - link /NODEFAULTLIB:msvcrt -OUT:$@ $(LIBDIR) Wsetargv.obj fossil.res @linkopts +$(APPNAME) : $(APPTARGETS) translate$E mkindex$E codecheck1$E headers $(OBJ) $(OX)\linkopts + cd $(OX) + codecheck1$E $(SRC) + link $(LDFLAGS) /OUT:$@ $(LIBDIR) Wsetargv.obj fossil.res @linkopts $(OX)\linkopts: $B\win\Makefile.msc} set redir {>} foreach s [lsort [concat $src $AdditionalObj]] { writeln "\techo \$(OX)\\$s.obj $redir \$@" set redir {>>} } -writeln "\techo \$(LIBS) >> \$@\n\n" - +set redir {>>} +writeln "!ifdef FOSSIL_ENABLE_MINIZ" +writeln "\techo \$(OX)\\miniz.obj $redir \$@" +writeln "!endif" +writeln "\techo \$(LIBS) $redir \$@" writeln { - $(OX): @-mkdir $@ translate$E: $(SRCDIR)\translate.c $(BCC) $** @@ -1076,50 +1566,75 @@ $(BCC) $** mkindex$E: $(SRCDIR)\mkindex.c $(BCC) $** -mkversion$E: $B\src\mkversion.c +mkbuiltin$E: $(SRCDIR)\mkbuiltin.c + $(BCC) $** + +mkversion$E: $(SRCDIR)\mkversion.c + $(BCC) $** + +codecheck1$E: $(SRCDIR)\codecheck1.c $(BCC) $** -$(OX)\shell$O : $(SRCDIR)\shell.c - $(TCC) /Fo$@ /Dmain=sqlite3_shell $(SQLITE_OPTIONS) -c $(SRCDIR)\shell.c +$(OX)\shell$O : $(SRCDIR)\shell.c $B\win\Makefile.msc + $(TCC) /Fo$@ $(SHELL_OPTIONS) $(SQLITE_OPTIONS) $(SHELL_CFLAGS) -c $(SRCDIR)\shell.c -$(OX)\sqlite3$O : $(SRCDIR)\sqlite3.c - $(TCC) /Fo$@ -c $(SQLITE_OPTIONS) $** +$(OX)\sqlite3$O : $(SRCDIR)\sqlite3.c $B\win\Makefile.msc + $(TCC) /Fo$@ -c $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) $(SRCDIR)\sqlite3.c $(OX)\th$O : $(SRCDIR)\th.c $(TCC) /Fo$@ -c $** $(OX)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) /Fo$@ -c $** +$(OX)\th_tcl$O : $(SRCDIR)\th_tcl.c + $(TCC) /Fo$@ -c $** + +$(OX)\miniz$O : $(SRCDIR)\miniz.c + $(TCC) /Fo$@ -c $(MINIZ_OPTIONS) $(SRCDIR)\miniz.c + VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION $** > $@ $(OX)\cson_amalgamation$O : $(SRCDIR)\cson_amalgamation.c - $(TCC) /Fo$@ -c $** + $(TCC) /Fo$@ /c $** -page_index.h: mkindex$E $(SRC) +page_index.h: mkindex$E $(SRC) $** > $@ +builtin_data.h: mkbuiltin$E $(EXTRA_FILES) + mkbuiltin$E --prefix $(SRCDIR)/ $(EXTRA_FILES) > $@ + clean: - -del $(OX)\*.obj - -del *.obj - -del *_.c - -del *.h - -del *.map - -del *.manifest - -del headers - -del linkopts - -del *.res + del $(OX)\*.obj 2>NUL + del *.obj 2>NUL + del *_.c 2>NUL + del *.h 2>NUL + del *.ilk 2>NUL + del *.map 2>NUL + del *.res 2>NUL + del headers 2>NUL + del linkopts 2>NUL + del vc*.pdb 2>NUL realclean: clean - -del $(APPNAME) - -del translate$E - -del mkindex$E - -del makeheaders$E - -del mkversion$E + del $(APPNAME) 2>NUL + del $(PDBNAME) 2>NUL + del translate$E 2>NUL + del translate$P 2>NUL + del mkindex$E 2>NUL + del mkindex$P 2>NUL + del makeheaders$E 2>NUL + del makeheaders$P 2>NUL + del mkversion$E 2>NUL + del mkversion$P 2>NUL + del codecheck1$E 2>NUL + del codecheck1$P 2>NUL + del mkbuiltin$E 2>NUL + del mkbuiltin$P 2>NUL $(OBJDIR)\json$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_config$O : $(SRCDIR)\json_detail.h @@ -1132,23 +1647,22 @@ $(OBJDIR)\json_status$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h - } foreach s [lsort $src] { writeln "\$(OX)\\$s\$O : ${s}_.c ${s}.h" writeln "\t\$(TCC) /Fo\$@ -c ${s}_.c\n" writeln "${s}_.c : \$(SRCDIR)\\$s.c" writeln "\ttranslate\$E \$** > \$@\n" } writeln "fossil.res : \$B\\win\\fossil.rc" -writeln "\t\$(RCC) -fo \$@ \$**" +writeln "\t\$(RCC) /fo \$@ \$**\n" -writeln "headers: makeheaders\$E page_index.h VERSION.h" +writeln "headers: makeheaders\$E page_index.h builtin_data.h VERSION.h" writeln -nonewline "\tmakeheaders\$E " set i 0 foreach s [lsort $src] { if {$i > 0} { writeln " \\" @@ -1173,11 +1687,13 @@ # puts "building ../win/Makefile.PellesCGMake" set output_file [open ../win/Makefile.PellesCGMake w] fconfigure $output_file -translation binary -writeln {# +writeln [string map [list \ + <<>> [join $SQLITE_WIN32_OPTIONS { }] \ + <<>> [join $SHELL_WIN32_OPTIONS { }]] {# ############################################################################## # WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") ############################################################################## # # This file is automatically generated. Instead of editing this @@ -1207,13 +1723,13 @@ # and # PellesC 6.00.4 # gmake 3.80 # zlib sources 1.2.5 # Windows 7 Home Premium -# +# -# +# PellesCDir=c:\Programme\PellesC # Select between 32/64 bit code, default is 32 bit #TARGETVERSION=64 @@ -1252,25 +1768,25 @@ RC=$(PellesCDir)\bin\porc.exe RCFLAGS=$(INCLUDE) -D__POCC__=1 -D_M_X$(TARGETVERSION) # define the special utilities files, needed to generate # the automatically generated source files -UTILS=translate.exe mkindex.exe makeheaders.exe +UTILS=translate.exe mkindex.exe makeheaders.exe mkbuiltin.exe UTILS_OBJ=$(UTILS:.exe=.obj) UTILS_SRC=$(foreach uf,$(UTILS),$(SRCDIR)$(uf:.exe=.c)) -# define the sqlite files, which need special flags on compile +# define the SQLite files, which need special flags on compile SQLITESRC=sqlite3.c ORIGSQLITESRC=$(foreach sf,$(SQLITESRC),$(SRCDIR)$(sf)) SQLITEOBJ=$(foreach sf,$(SQLITESRC),$(sf:.c=.obj)) -SQLITEDEFINES=-DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_WIN32_NO_ANSI +SQLITEDEFINES=<<>> -# define the sqlite shell files, which need special flags on compile +# define the SQLite shell files, which need special flags on compile SQLITESHELLSRC=shell.c ORIGSQLITESHELLSRC=$(foreach sf,$(SQLITESHELLSRC),$(SRCDIR)$(sf)) SQLITESHELLOBJ=$(foreach sf,$(SQLITESHELLSRC),$(sf:.c=.obj)) -SQLITESHELLDEFINES=-Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -Dsqlite3_strglob=strglob +SQLITESHELLDEFINES=<<>> # define the th scripting files, which need special flags on compile THSRC=th.c th_lang.c ORIGTHSRC=$(foreach sf,$(THSRC),$(SRCDIR)$(sf)) THOBJ=$(foreach sf,$(THSRC),$(sf:.c=.obj)) @@ -1291,11 +1807,11 @@ # main target file is the application APPLICATION=fossil.exe # define the standard make target .PHONY: default -default: page_index.h headers $(APPLICATION) +default: page_index.h builtin_data.h headers $(APPLICATION) # symbolic target to generate the source generate utils .PHONY: utils utils: $(UTILS) @@ -1317,16 +1833,19 @@ # generate the index source, containing all web references,.. page_index.h: $(TRANSLATEDSRC) mkindex.exe mkindex.exe $(TRANSLATEDSRC) >$@ +builtin_data.h: $(EXTRA_FILES) mkbuiltin.exe + mkbuiltin.exe --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ + # extracting version info from manifest VERSION.h: version.exe ..\manifest.uuid ..\manifest ..\VERSION - version.exe ..\manifest.uuid ..\manifest ..\VERSION > $@ + version.exe ..\manifest.uuid ..\manifest ..\VERSION >$@ # generate the simplified headers -headers: makeheaders.exe page_index.h VERSION.h ../src/sqlite3.h ../src/th.h VERSION.h +headers: makeheaders.exe page_index.h builtin_data.h VERSION.h ../src/sqlite3.h ../src/th.h VERSION.h makeheaders.exe $(foreach ts,$(TRANSLATEDSRC),$(ts):$(ts:_.c=.h)) ../src/sqlite3.h ../src/th.h VERSION.h echo Done >$@ # compile C sources with relevant options @@ -1363,6 +1882,6 @@ del /F $(RESOURCE) .PHONY: clobber clobber: clean del /F *.exe -} +}] Index: src/manifest.c ================================================================== --- src/manifest.c +++ src/manifest.c @@ -42,14 +42,21 @@ */ #define PERM_REG 0 /* regular file */ #define PERM_EXE 1 /* executable */ #define PERM_LNK 2 /* symlink */ +/* +** Flags for use with manifest_crosslink(). +*/ +#define MC_NONE 0 /* default handling */ +#define MC_PERMIT_HOOKS 1 /* permit hooks to execute */ +#define MC_NO_ERRORS 2 /* do not issue errors for a bad parse */ + /* ** A single F-card within a manifest */ -struct ManifestFile { +struct ManifestFile { char *zName; /* Name of a file */ char *zUuid; /* UUID of the file */ char *zPerm; /* File permissions */ char *zPrior; /* Prior name if the name was changed */ }; @@ -83,11 +90,11 @@ ManifestFile *aFile; /* One entry for each F-card */ int nParent; /* Number of parents. */ int nParentAlloc; /* Slots allocated in azParent[] */ char **azParent; /* UUIDs of parents. One for each P card argument */ int nCherrypick; /* Number of entries in aCherrypick[] */ - struct { + struct { char *zCPTarget; /* UUID of cherry-picked version w/ +|- prefix */ char *zCPBase; /* UUID of cherry-pick baseline. NULL for singletons */ } *aCherrypick; int nCChild; /* Number of cluster children */ int nCChildAlloc; /* Number of closts allocated in azCChild[] */ @@ -99,11 +106,11 @@ char *zUuid; /* UUID that the tag is applied to */ char *zValue; /* Value if the tag is really a property */ } *aTag; /* One for each T card */ int nField; /* Number of J cards */ int nFieldAlloc; /* Slots allocated in aField[] */ - struct { + struct { char *zName; /* Key or field name */ char *zValue; /* Value of the field */ } *aField; /* One for each J card */ }; #endif @@ -129,16 +136,16 @@ ** Clear the memory allocated in a manifest object */ void manifest_destroy(Manifest *p){ if( p ){ blob_reset(&p->content); - free(p->aFile); - free(p->azParent); - free(p->azCChild); - free(p->aTag); - free(p->aField); - free(p->aCherrypick); + fossil_free(p->aFile); + fossil_free(p->azParent); + fossil_free(p->azCChild); + fossil_free(p->aTag); + fossil_free(p->aField); + fossil_free(p->aCherrypick); if( p->pBaseline ) manifest_destroy(p->pBaseline); memset(p, 0, sizeof(*p)); fossil_free(p); } } @@ -223,18 +230,18 @@ */ static void remove_pgp_signature(char **pz, int *pn){ char *z = *pz; int n = *pn; int i; - if( memcmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)!=0 ) return; + if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)!=0 ) return; for(i=34; i=n ) return; z += i; n -= i; *pz = z; for(i=n-1; i>=0; i--){ - if( z[i]=='\n' && memcmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){ + if( z[i]=='\n' && strncmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){ n = i+1; break; } } *pn = n; @@ -245,11 +252,11 @@ ** Verify the Z-card checksum on the artifact, if there is such a ** checksum. Return 0 if there is no Z-card. Return 1 if the Z-card ** exists and is correct. Return 2 if the Z-card exists and has the wrong ** value. ** -** 0123456789 123456789 123456789 123456789 +** 0123456789 123456789 123456789 123456789 ** Z aea84f4f863865a8d59d0384e4d2a41c */ static int verify_z_card(const char *z, int n){ if( n<35 ) return 0; if( z[n-35]!='Z' || z[n-34]!=' ' ) return 0; @@ -354,10 +361,11 @@ char *z; int n; char *zUuid; int sz = 0; int isRepeat, hasSelfRefTag = 0; + Blob bUuid = BLOB_INITIALIZER; static Bag seen; const char *zErr = 0; if( rid==0 ){ isRepeat = 1; @@ -372,13 +380,13 @@ ** if that is not the case for this artifact. */ if( !isRepeat ) g.parseCnt[0]++; z = blob_materialize(pContent); n = blob_size(pContent); - if( n<=0 || z[n-1]!='\n' ){ + if( pErr && (n<=0 || z[n-1]!='\n') ){ blob_reset(pContent); - blob_appendf(pErr, n ? "not terminated with \\n" : "zero-length"); + blob_append(pErr, n ? "not terminated with \\n" : "zero-length", -1); return 0; } /* Strip off the PGP signature if there is one. */ @@ -397,10 +405,15 @@ if( verify_z_card(z, n)==2 ){ blob_reset(pContent); blob_appendf(pErr, "incorrect Z-card cksum"); return 0; } + + /* Store the UUID (before modifying the blob) only for error + ** reporting purposes. + */ + sha1sum_blob(pContent, &bUuid); /* Allocate a Manifest object to hold the parsed control artifact. */ p = fossil_malloc( sizeof(*p) ); memset(p, 0, sizeof(*p)); @@ -429,11 +442,11 @@ char *zName, *zTarget, *zSrc; int nTarget = 0, nSrc = 0; zName = next_token(&x, 0); zTarget = next_token(&x, &nTarget); zSrc = next_token(&x, &nSrc); - if( zName==0 || zTarget==0 ) goto manifest_syntax_error; + if( zName==0 || zTarget==0 ) goto manifest_syntax_error; if( p->zAttachName!=0 ) goto manifest_syntax_error; defossilize(zName); if( !file_is_simple_pathname(zName, 0) ){ SYNTAX("invalid filename on A-card"); } @@ -498,11 +511,11 @@ } /* ** E ** - ** An "event" card that contains the timestamp of the event in the + ** An "event" card that contains the timestamp of the event in the ** format YYYY-MM-DDtHH:MM:SS and a unique identifier for the event. ** The event timestamp is distinct from the D timestamp. The D ** timestamp is when the artifact was created whereas the E timestamp ** is when the specific event is said to occur. */ @@ -545,11 +558,11 @@ SYNTAX("F-card old filename is not a simple path"); } } if( p->nFile>=p->nFileAlloc ){ p->nFileAlloc = p->nFileAlloc*2 + 10; - p->aFile = fossil_realloc(p->aFile, + p->aFile = fossil_realloc(p->aFile, p->nFileAlloc*sizeof(p->aFile[0]) ); } i = p->nFile++; p->aFile[i].zName = zName; p->aFile[i].zUuid = zUuid; @@ -665,11 +678,11 @@ ** P ... ** ** Specify one or more other artifacts which are the parents of ** this artifact. The first parent is the primary parent. All ** others are parents by merge. Note that the initial empty - ** checkin historically has an empty P-card, so empty P-cards + ** check-in historically has an empty P-card, so empty P-cards ** must be accepted. */ case 'P': { while( (zUuid = next_token(&x, &sz))!=0 ){ if( sz!=UUID_SIZE ) SYNTAX("wrong size UUID on P-card"); @@ -686,12 +699,12 @@ } /* ** Q (+|-) ?? ** - ** Specify one or a range of checkins that are cherrypicked into - ** this checkin ("+") or backed out of this checkin ("-"). + ** Specify one or a range of check-ins that are cherrypicked into + ** this check-in ("+") or backed out of this check-in ("-"). */ case 'Q': { if( (zUuid=next_token(&x, &sz))==0 ) SYNTAX("missing UUID on Q-card"); if( sz!=UUID_SIZE+1 ) SYNTAX("wrong size UUID on Q-card"); if( zUuid[0]!='+' && zUuid[0]!='-' ){ @@ -737,11 +750,11 @@ ** singleton tag, "*" to create a propagating tag, or "-" to create ** anti-tag that undoes a prior "+" or blocks propagation of of ** a "*". ** ** The tag is applied to . If is "*" then the tag is - ** applied to the current manifest. If is provided then + ** applied to the current manifest. If is provided then ** the tag is really a property with the given value. ** ** Tags are not allowed in clusters. Multiple T lines are allowed. */ case 'T': { @@ -884,67 +897,72 @@ SYNTAX("cluster contains a card other than M- or Z-"); } if( !seenZ ) SYNTAX("missing Z-card on cluster"); p->type = CFTYPE_CLUSTER; }else if( p->zEventId ){ + if( p->zAttachName ) SYNTAX("A-card in event"); + if( p->zBaseline ) SYNTAX("B-card in event"); if( p->rDate<=0.0 ) SYNTAX("missing date on event"); if( p->nFile>0 ) SYNTAX("F-card in event"); - if( p->zRepoCksum ) SYNTAX("R-card in event"); - if( p->zBaseline ) SYNTAX("B-card in event"); if( p->nField>0 ) SYNTAX("J-card in event"); if( p->zTicketUuid ) SYNTAX("K-card in event"); if( p->zWikiTitle!=0 ) SYNTAX("L-card in event"); + if( p->zRepoCksum ) SYNTAX("R-card in event"); if( p->zWiki==0 ) SYNTAX("missing W-card on event"); - if( p->zAttachName ) SYNTAX("A-card in event"); if( !seenZ ) SYNTAX("missing Z-card on event"); p->type = CFTYPE_EVENT; - }else if( hasSelfRefTag || p->nFile>0 || p->zRepoCksum!=0 || p->zBaseline ){ + }else if( p->zWiki!=0 || p->zWikiTitle!=0 ){ + if( p->zAttachName ) SYNTAX("A-card in wiki"); + if( p->zBaseline ) SYNTAX("B-card in wiki"); + if( p->rDate<=0.0 ) SYNTAX("missing date on wiki"); + if( p->nFile>0 ) SYNTAX("F-card in wiki"); + if( p->nField>0 ) SYNTAX("J-card in wiki"); + if( p->zTicketUuid ) SYNTAX("K-card in wiki"); + if( p->zWikiTitle==0 ) SYNTAX("missing L-card on wiki"); + if( p->zRepoCksum ) SYNTAX("R-card in wiki"); + if( p->nTag>0 ) SYNTAX("T-card in wiki"); + if( p->zWiki==0 ) SYNTAX("missing W-card on wiki"); + if( !seenZ ) SYNTAX("missing Z-card on wiki"); + p->type = CFTYPE_WIKI; + }else if( hasSelfRefTag || p->nFile>0 || p->zRepoCksum!=0 || p->zBaseline + || p->nParent>0 ){ + if( p->zAttachName ) SYNTAX("A-card in manifest"); if( p->rDate<=0.0 ) SYNTAX("missing date on manifest"); if( p->nField>0 ) SYNTAX("J-card in manifest"); if( p->zTicketUuid ) SYNTAX("K-card in manifest"); - if( p->zWiki ) SYNTAX("W-card in manifest"); - if( p->zWikiTitle ) SYNTAX("L-card in manifest"); - if( p->zTicketUuid ) SYNTAX("K-card in manifest"); - if( p->zAttachName ) SYNTAX("A-card in manifest"); p->type = CFTYPE_MANIFEST; }else if( p->nField>0 || p->zTicketUuid!=0 ){ + if( p->zAttachName ) SYNTAX("A-card in ticket"); if( p->rDate<=0.0 ) SYNTAX("missing date on ticket"); - if( p->zWiki ) SYNTAX("W-card in ticket"); - if( p->zWikiTitle ) SYNTAX("L-card in ticket"); if( p->nField==0 ) SYNTAX("missing J-card on ticket"); + if( p->zTicketUuid==0 ) SYNTAX("missing K-card on ticket"); + if( p->zMimetype) SYNTAX("N-card in ticket"); if( p->nTag>0 ) SYNTAX("T-card in ticket"); - if( p->zTicketUuid==0 ) SYNTAX("missing K-card on ticket"); if( p->zUser==0 ) SYNTAX("missing U-card on ticket"); - if( p->zAttachName ) SYNTAX("A-card in ticket"); - if( p->zMimetype) SYNTAX("N-card in ticket"); if( !seenZ ) SYNTAX("missing Z-card on ticket"); p->type = CFTYPE_TICKET; - }else if( p->zWiki!=0 || p->zWikiTitle!=0 ){ - if( p->rDate<=0.0 ) SYNTAX("missing date on wiki"); - if( p->nTag>0 ) SYNTAX("T-card in wiki"); - if( p->zWiki==0 ) SYNTAX("missing W-card on wiki"); - if( p->zWikiTitle==0 ) SYNTAX("missing L-card on wiki"); - if( p->zAttachName ) SYNTAX("A-card in wiki"); - if( !seenZ ) SYNTAX("missing Z-card on wiki"); - p->type = CFTYPE_WIKI; }else if( p->zAttachName ){ if( p->rDate<=0.0 ) SYNTAX("missing date on attachment"); if( p->nTag>0 ) SYNTAX("T-card in attachment"); if( !seenZ ) SYNTAX("missing Z-card on attachment"); p->type = CFTYPE_ATTACHMENT; }else{ if( p->rDate<=0.0 ) SYNTAX("missing date on control"); - if( p->nParent>0 ) SYNTAX("P-card in control"); if( p->zMimetype ) SYNTAX("N-card in control"); if( !seenZ ) SYNTAX("missing Z-card on control"); p->type = CFTYPE_CONTROL; } md5sum_init(); if( !isRepeat ) g.parseCnt[p->type]++; + blob_reset(&bUuid); return p; manifest_syntax_error: + if(bUuid.nUsed){ + blob_appendf(pErr, "manifest [%.40s] ", blob_str(&bUuid)); + blob_reset(&bUuid); + } if( zErr ){ blob_appendf(pErr, "line %d: %s", lineNo, zErr); }else{ blob_appendf(pErr, "unknown error on line %d", lineNo); } @@ -977,25 +995,25 @@ } return p; } /* -** Given a checkin name, load and parse the manifest for that checkin. +** Given a check-in name, load and parse the manifest for that check-in. ** Throw a fatal error if anything goes wrong. */ Manifest *manifest_get_by_name(const char *zName, int *pRid){ int rid; Manifest *p; rid = name_to_typed_rid(zName, "ci"); if( !is_a_version(rid) ){ - fossil_fatal("no such checkin: %s", zName); + fossil_fatal("no such check-in: %s", zName); } if( pRid ) *pRid = rid; p = manifest_get(rid, CFTYPE_MANIFEST, 0); if( p==0 ){ - fossil_fatal("cannot parse manifest for checkin: %s", zName); + fossil_fatal("cannot parse manifest for check-in: %s", zName); } return p; } /* @@ -1044,11 +1062,11 @@ db_multi_exec( "INSERT OR IGNORE INTO orphan(rid, baseline) VALUES(%d,%d)", p->rid, rid ); return 1; - } + } fossil_fatal("cannot access baseline manifest %S", p->zBaseline); } } return 0; } @@ -1069,11 +1087,11 @@ ** ** Return NULL for end-of-records or if there is an error. If an error ** occurs and pErr!=0 then store 1 in *pErr. */ ManifestFile *manifest_file_next( - Manifest *p, + Manifest *p, int *pErr ){ ManifestFile *pOut = 0; if( pErr ) *pErr = 0; if( p->pBaseline==0 ){ @@ -1155,29 +1173,32 @@ ** of a file. */ int manifest_file_mperm(ManifestFile *pFile){ int mperm = PERM_REG; if( pFile && pFile->zPerm){ - if( strstr(pFile->zPerm,"x")!=0 ) + if( strstr(pFile->zPerm,"x")!=0 ){ mperm = PERM_EXE; - else if( strstr(pFile->zPerm,"l")!=0 ) + }else if( strstr(pFile->zPerm,"l")!=0 ){ mperm = PERM_LNK; + } } return mperm; } /* ** Add a single entry to the mlink table. Also add the filename to ** the filename table if it is not there already. */ static void add_one_mlink( + int pmid, /* The parent manifest */ + const char *zFromUuid, /* UUID for content in parent */ int mid, /* The record ID of the manifest */ - const char *zFromUuid, /* UUID for the mlink.pid. "" to add file */ - const char *zToUuid, /* UUID for the mlink.fid. "" to delete */ + const char *zToUuid, /* UUID for content in child */ const char *zFilename, /* Filename */ const char *zPrior, /* Previous filename. NULL if unchanged */ int isPublic, /* True if mid is not a private manifest */ + int isPrimary, /* pmid is the primary parent of mid */ int mperm /* 1: exec, 2: symlink */ ){ int fnid, pfnid, pid, fid; static Stmt s1; @@ -1197,35 +1218,41 @@ }else{ fid = uuid_to_rid(zToUuid, 1); if( isPublic ) content_make_public(fid); } db_static_prepare(&s1, - "INSERT INTO mlink(mid,pid,fid,fnid,pfnid,mperm)" - "VALUES(:m,:p,:f,:n,:pfn,:mp)" + "INSERT INTO mlink(mid,fid,pmid,pid,fnid,pfnid,mperm,isaux)" + "VALUES(:m,:f,:pm,:p,:n,:pfn,:mp,:isaux)" ); db_bind_int(&s1, ":m", mid); - db_bind_int(&s1, ":p", pid); db_bind_int(&s1, ":f", fid); + db_bind_int(&s1, ":pm", pmid); + db_bind_int(&s1, ":p", pid); db_bind_int(&s1, ":n", fnid); db_bind_int(&s1, ":pfn", pfnid); db_bind_int(&s1, ":mp", mperm); + db_bind_int(&s1, ":isaux", isPrimary==0); db_exec(&s1); if( pid && fid ){ content_deltify(pid, fid, 0); } } /* -** Do a binary search to find a file in the p->aFile[] array. +** Do a binary search to find a file in the p->aFile[] array. ** ** As an optimization, guess that the file we seek is at index p->iFile. ** That will usually be the case. If it is not found there, then do the ** actual binary search. ** ** Update p->iFile to be the index of the file that is found. */ -static ManifestFile *manifest_file_seek_base(Manifest *p, const char *zName){ +static ManifestFile *manifest_file_seek_base( + Manifest *p, /* Manifest to search */ + const char *zName, /* Name of the file we are looking for */ + int bBest /* 0: exact match only. 1: closest match */ +){ int lwr, upr; int c; int i; lwr = 0; upr = p->nFile - 1; @@ -1249,31 +1276,36 @@ }else{ p->iFile = i; return &p->aFile[i]; } } + if( bBest ){ + if( lwr>=p->nFile ) lwr = p->nFile-1; + i = (int)strlen(zName); + if( strncmp(zName, p->aFile[lwr].zName, i)==0 ) return &p->aFile[lwr]; + } return 0; } /* ** Locate a file named zName in the aFile[] array of the given manifest. ** Return a pointer to the appropriate ManifestFile object. Return NULL ** if not found. ** -** This routine works even if p is a delta-manifest. The pointer +** This routine works even if p is a delta-manifest. The pointer ** returned might be to the baseline. ** ** We assume that filenames are in sorted order and use a binary search. */ -ManifestFile *manifest_file_seek(Manifest *p, const char *zName){ +ManifestFile *manifest_file_seek(Manifest *p, const char *zName, int bBest){ ManifestFile *pFile; - - pFile = manifest_file_seek_base(p, zName); + + pFile = manifest_file_seek_base(p, zName, p->zBaseline ? 0 : bBest); if( pFile && pFile->zUuid==0 ) return 0; if( pFile==0 && p->zBaseline ){ fetch_baseline(p, 1); - pFile = manifest_file_seek_base(p->pBaseline, zName); + pFile = manifest_file_seek_base(p->pBaseline, zName,bBest); } return pFile; } /* @@ -1283,11 +1315,11 @@ */ ManifestFile *manifest_file_find(Manifest *p, const char *zName){ int i; Manifest *pBase; if( filenames_are_case_sensitive() ){ - return manifest_file_seek(p, zName); + return manifest_file_seek(p, zName, 0); } for(i=0; inFile; i++){ if( fossil_stricmp(zName, p->aFile[i].zName)==0 ){ return &p->aFile[i]; } @@ -1314,38 +1346,43 @@ ** ** Deleted files have mlink.fid=0. ** Added files have mlink.pid=0. ** Edited files have both mlink.pid!=0 and mlink.fid!=0 */ -static void add_mlink(int pid, Manifest *pParent, int cid, Manifest *pChild){ +static void add_mlink( + int pmid, Manifest *pParent, /* Parent check-in */ + int mid, Manifest *pChild, /* The child check-in */ + int isPrim /* TRUE if pmid is the primary parent of mid */ +){ Blob otherContent; int otherRid; int i, rc; ManifestFile *pChildFile, *pParentFile; Manifest **ppOther; static Stmt eq; int isPublic; /* True if pChild is non-private */ - /* If mlink table entires are already set for cid, then abort early - ** doing no work. + /* If mlink table entires are already exist for the pmid-to-mid transition, + ** then abort early doing no work. */ - db_static_prepare(&eq, "SELECT 1 FROM mlink WHERE mid=:mid"); - db_bind_int(&eq, ":mid", cid); + db_static_prepare(&eq, "SELECT 1 FROM mlink WHERE mid=:mid AND pmid=:pmid"); + db_bind_int(&eq, ":mid", mid); + db_bind_int(&eq, ":pmid", pmid); rc = db_step(&eq); db_reset(&eq); if( rc==SQLITE_ROW ) return; /* Compute the value of the missing pParent or pChild parameter. - ** Fetch the baseline checkins for both. + ** Fetch the baseline check-ins for both. */ assert( pParent==0 || pChild==0 ); if( pParent==0 ){ ppOther = &pParent; - otherRid = pid; + otherRid = pmid; }else{ ppOther = &pChild; - otherRid = cid; + otherRid = mid; } if( (*ppOther = manifest_cache_find(otherRid))==0 ){ content_get(otherRid, &otherContent); if( blob_size(&otherContent)==0 ) return; *ppOther = manifest_parse(&otherContent, otherRid, 0); @@ -1353,20 +1390,20 @@ } if( fetch_baseline(pParent, 0) || fetch_baseline(pChild, 0) ){ manifest_destroy(*ppOther); return; } - isPublic = !content_is_private(cid); + isPublic = !content_is_private(mid); /* Try to make the parent manifest a delta from the child, if that - ** is an appropriate thing to do. For a new baseline, make the + ** is an appropriate thing to do. For a new baseline, make the ** previous baseline a delta from the current baseline. */ if( (pParent->zBaseline==0)==(pChild->zBaseline==0) ){ - content_deltify(pid, cid, 0); + content_deltify(pmid, mid, 0); }else if( pChild->zBaseline==0 && pParent->zBaseline!=0 ){ - content_deltify(pParent->pBaseline->rid, cid, 0); + content_deltify(pParent->pBaseline->rid, mid, 0); } /* Remember all children less than a few seconds younger than their parent, ** as we might want to fudge the times for those children. */ @@ -1379,38 +1416,39 @@ ); } /* First look at all files in pChild, ignoring its baseline. This ** is where most of the changes will be found. - */ + */ for(i=0, pChildFile=pChild->aFile; inFile; i++, pChildFile++){ int mperm = manifest_file_mperm(pChildFile); if( pChildFile->zPrior ){ - pParentFile = manifest_file_seek(pParent, pChildFile->zPrior); + pParentFile = manifest_file_seek(pParent, pChildFile->zPrior, 0); if( pParentFile ){ /* File with name change */ - add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid, - pChildFile->zName, pChildFile->zPrior, isPublic, mperm); + add_one_mlink(pmid, pParentFile->zUuid, mid, pChildFile->zUuid, + pChildFile->zName, pChildFile->zPrior, + isPublic, isPrim, mperm); }else{ /* File name changed, but the old name is not found in the parent! ** Treat this like a new file. */ - add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0, - isPublic, mperm); + add_one_mlink(pmid, 0, mid, pChildFile->zUuid, pChildFile->zName, 0, + isPublic, isPrim, mperm); } }else{ - pParentFile = manifest_file_seek(pParent, pChildFile->zName); + pParentFile = manifest_file_seek(pParent, pChildFile->zName, 0); if( pParentFile==0 ){ if( pChildFile->zUuid ){ /* A new file */ - add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0, - isPublic, mperm); + add_one_mlink(pmid, 0, mid, pChildFile->zUuid, pChildFile->zName, 0, + isPublic, isPrim, mperm); } }else if( fossil_strcmp(pChildFile->zUuid, pParentFile->zUuid)!=0 || manifest_file_mperm(pParentFile)!=mperm ){ /* Changes in file content or permissions */ - add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid, - pChildFile->zName, 0, isPublic, mperm); + add_one_mlink(pmid, pParentFile->zUuid, mid, pChildFile->zUuid, + pChildFile->zName, 0, isPublic, isPrim, mperm); } } } if( pParent->zBaseline && pChild->zBaseline ){ /* Both parent and child are delta manifests. Look for files that @@ -1417,39 +1455,39 @@ ** are deleted or modified in the parent but which reappear or revert ** to baseline in the child and show such files as being added or changed ** in the child. */ for(i=0, pParentFile=pParent->aFile; inFile; i++, pParentFile++){ if( pParentFile->zUuid ){ - pChildFile = manifest_file_seek_base(pChild, pParentFile->zName); + pChildFile = manifest_file_seek_base(pChild, pParentFile->zName, 0); if( pChildFile==0 ){ /* The child file reverts to baseline. Show this as a change */ - pChildFile = manifest_file_seek(pChild, pParentFile->zName); + pChildFile = manifest_file_seek(pChild, pParentFile->zName, 0); if( pChildFile ){ - add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid, - pChildFile->zName, 0, isPublic, + add_one_mlink(pmid, pParentFile->zUuid, mid, pChildFile->zUuid, + pChildFile->zName, 0, isPublic, isPrim, manifest_file_mperm(pChildFile)); } } }else{ - pChildFile = manifest_file_seek(pChild, pParentFile->zName); + pChildFile = manifest_file_seek(pChild, pParentFile->zName, 0); if( pChildFile ){ /* File resurrected in the child after having been deleted in ** the parent. Show this as an added file. */ - add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0, - isPublic, manifest_file_mperm(pChildFile)); + add_one_mlink(pmid, 0, mid, pChildFile->zUuid, pChildFile->zName, 0, + isPublic, isPrim, manifest_file_mperm(pChildFile)); } } } }else if( pChild->zBaseline==0 ){ /* pChild is a baseline. Look for files that are present in pParent ** but are missing from pChild and mark them as having been deleted. */ manifest_file_rewind(pParent); while( (pParentFile = manifest_file_next(pParent,0))!=0 ){ - pChildFile = manifest_file_seek(pChild, pParentFile->zName); + pChildFile = manifest_file_seek(pChild, pParentFile->zName, 0); if( pChildFile==0 && pParentFile->zUuid!=0 ){ - add_one_mlink(cid, pParentFile->zUuid, 0, pParentFile->zName, 0, - isPublic, 0); + add_one_mlink(pmid, pParentFile->zUuid, mid, 0, pParentFile->zName, 0, + isPublic, isPrim, 0); } } } manifest_cache_insert(*ppOther); } @@ -1472,11 +1510,11 @@ ");" ); } #if INTERFACE -/* Timestamps might be adjusted slightly to ensure that checkins appear +/* Timestamps might be adjusted slightly to ensure that check-ins appear ** on the timeline in chronological order. This is the maximum amount ** of the adjustment window, in days. */ #define AGE_FUDGE_WINDOW (2.0/86400.0) /* 2 seconds */ @@ -1488,18 +1526,30 @@ #endif /* LOCAL_INTERFACE */ /* ** Finish up a sequence of manifest_crosslink calls. */ -void manifest_crosslink_end(void){ +int manifest_crosslink_end(int flags){ Stmt q, u; int i; + int rc = TH_OK; + int permitHooks = (flags & MC_PERMIT_HOOKS); + const char *zScript = 0; assert( manifest_crosslink_busy==1 ); + if( permitHooks ){ + rc = xfer_run_common_script(); + if( rc==TH_OK ){ + zScript = xfer_ticket_code(); + } + } db_prepare(&q, "SELECT uuid FROM pending_tkt"); while( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); ticket_rebuild_entry(zUuid); + if( permitHooks && rc==TH_OK ){ + rc = xfer_run_script(zScript, zUuid, 0); + } } db_finalize(&q); db_multi_exec("DROP TABLE pending_tkt"); /* If multiple check-ins happen close together in time, adjust their @@ -1522,18 +1572,21 @@ db_step(&u); db_reset(&u); } db_finalize(&q); db_finalize(&u); - db_multi_exec( - "UPDATE event SET mtime=(SELECT m1 FROM time_fudge WHERE mid=objid)" - " WHERE objid IN (SELECT mid FROM time_fudge);" - "DROP TABLE time_fudge;" - ); + if( db_exists("SELECT 1 FROM time_fudge") ){ + db_multi_exec( + "UPDATE event SET mtime=(SELECT m1 FROM time_fudge WHERE mid=objid)" + " WHERE objid IN (SELECT mid FROM time_fudge);" + ); + } + db_multi_exec("DROP TABLE time_fudge;"); db_end_transaction(0); manifest_crosslink_busy = 0; + return ( rc!=TH_ERROR ); } /* ** Make an entry in the event table for a ticket change artifact. */ @@ -1557,62 +1610,101 @@ if( once ){ once = 0; zTitleExpr = db_get("ticket-title-expr", "title"); zStatusColumn = db_get("ticket-status-column", "status"); } - zTitle = db_text("unknown", - "SELECT %s FROM ticket WHERE tkt_uuid='%s'", + zTitle = db_text("unknown", + "SELECT \"%w\" FROM ticket WHERE tkt_uuid=%Q", zTitleExpr, pManifest->zTicketUuid ); if( !isNew ){ for(i=0; inField; i++){ if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){ zNewStatus = pManifest->aField[i].zValue; } } if( zNewStatus ){ - blob_appendf(&comment, "%h ticket [%.10s]: %h", - zNewStatus, pManifest->zTicketUuid, zTitle + blob_appendf(&comment, "%h ticket [%!S|%S]: %h", + zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle ); if( pManifest->nField>1 ){ blob_appendf(&comment, " plus %d other change%s", pManifest->nField-1, pManifest->nField==2 ? "" : "s"); } - blob_appendf(&brief, "%h ticket [%.10s].", - zNewStatus, pManifest->zTicketUuid); + blob_appendf(&brief, "%h ticket [%!S|%S].", + zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid); }else{ - zNewStatus = db_text("unknown", - "SELECT %s FROM ticket WHERE tkt_uuid='%s'", + zNewStatus = db_text("unknown", + "SELECT \"%w\" FROM ticket WHERE tkt_uuid=%Q", zStatusColumn, pManifest->zTicketUuid ); - blob_appendf(&comment, "Ticket [%.10s] %h status still %h with " + blob_appendf(&comment, "Ticket [%!S|%S] %h status still %h with " "%d other change%s", - pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField, - pManifest->nField==1 ? "" : "s" + pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle, zNewStatus, + pManifest->nField, pManifest->nField==1 ? "" : "s" ); - free(zNewStatus); - blob_appendf(&brief, "Ticket [%.10s]: %d change%s", - pManifest->zTicketUuid, pManifest->nField, + fossil_free(zNewStatus); + blob_appendf(&brief, "Ticket [%!S|%S]: %d change%s", + pManifest->zTicketUuid, pManifest->zTicketUuid, pManifest->nField, pManifest->nField==1 ? "" : "s" ); } }else{ - blob_appendf(&comment, "New ticket [%.10s] %h.", - pManifest->zTicketUuid, zTitle + blob_appendf(&comment, "New ticket [%!S|%S] %h.", + pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle ); - blob_appendf(&brief, "New ticket [%.10s].", pManifest->zTicketUuid); + blob_appendf(&brief, "New ticket [%!S|%S].", pManifest->zTicketUuid, + pManifest->zTicketUuid); } - free(zTitle); + fossil_free(zTitle); db_multi_exec( "REPLACE INTO event(type,tagid,mtime,objid,user,comment,brief)" "VALUES('t',%d,%.17g,%d,%Q,%Q,%Q)", tktTagId, pManifest->rDate, rid, pManifest->zUser, blob_str(&comment), blob_str(&brief) ); blob_reset(&comment); blob_reset(&brief); } + +/* +** Add an extra line of text to the end of a manifest to prevent it being +** recognized as a valid manifest. +** +** This routine is called prior to writing out the text of a manifest as +** the "manifest" file in the root of a repository when +** "fossil setting manifest on" is enabled. That way, if the files of +** the project are imported into a different Fossil project, the manifest +** file will not be interpreted as a control artifact in that other project. +** +** Normally it is sufficient to simply append the extra line of text. +** However, if the manifest is PGP signed then the extra line has to be +** inserted before the PGP signature (thus invalidating the signature). +*/ +void sterilize_manifest(Blob *p){ + char *z, *zOrig; + int n, nOrig; + static const char zExtraLine[] = + "# Remove this line to create a well-formed manifest.\n"; + + z = zOrig = blob_materialize(p); + n = nOrig = blob_size(p); + remove_pgp_signature(&z, &n); + if( z==zOrig ){ + blob_append(p, zExtraLine, -1); + }else{ + int iEnd; + Blob copy; + memcpy(©, p, sizeof(copy)); + blob_init(p, 0, 0); + iEnd = (int)(&z[n] - zOrig); + blob_append(p, zOrig, iEnd); + blob_append(p, zExtraLine, -1); + blob_append(p, &zOrig[iEnd], -1); + blob_zero(©); + } +} /* ** This is the comparison function used to sort the tag array. */ static int tag_compare(const void *a, const void *b){ @@ -1640,70 +1732,108 @@ ** ** If the input is a control artifact, then make appropriate entries ** in the auxiliary tables of the database in order to crosslink the ** artifact. ** -** If global variable g.xlinkClusterOnly is true, then ignore all +** If global variable g.xlinkClusterOnly is true, then ignore all ** control artifacts other than clusters. ** ** This routine always resets the pContent blob before returning. ** ** Historical note: This routine original processed manifests only. ** Processing for other control artifacts was added later. The name ** of the routine, "manifest_crosslink", and the name of this source ** file, is a legacy of its original use. */ -int manifest_crosslink(int rid, Blob *pContent){ - int i; +int manifest_crosslink(int rid, Blob *pContent, int flags){ + int i, rc = TH_OK; Manifest *p; Stmt q; int parentid = 0; + int permitHooks = (flags & MC_PERMIT_HOOKS); + const char *zScript = 0; + const char *zUuid = 0; if( (p = manifest_cache_find(rid))!=0 ){ blob_reset(pContent); }else if( (p = manifest_parse(pContent, rid, 0))==0 ){ assert( blob_is_reset(pContent) || pContent==0 ); + if( (flags & MC_NO_ERRORS)==0 ){ + fossil_error(1, "syntax error in manifest [%S]", + db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid)); + } return 0; } if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){ manifest_destroy(p); assert( blob_is_reset(pContent) ); + if( (flags & MC_NO_ERRORS)==0 ) fossil_error(1, "no manifest"); return 0; } if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){ manifest_destroy(p); assert( blob_is_reset(pContent) ); + if( (flags & MC_NO_ERRORS)==0 ){ + fossil_error(1, "cannot fetch baseline for manifest [%S]", + db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid)); + } return 0; } db_begin_transaction(); if( p->type==CFTYPE_MANIFEST ){ + if( permitHooks ){ + zScript = xfer_commit_code(); + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + } if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){ char *zCom; + char zBaseId[30]; + if( p->zBaseline ){ + sqlite3_snprintf(sizeof(zBaseId), zBaseId, "%d", + uuid_to_rid(p->zBaseline,1)); + }else{ + sqlite3_snprintf(sizeof(zBaseId), zBaseId, "NULL"); + } for(i=0; inParent; i++){ int pid = uuid_to_rid(p->azParent[i], 1); - db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)" - "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, p->rDate); - if( i==0 ){ - add_mlink(pid, 0, rid, p); - parentid = pid; - } - } - db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid); + db_multi_exec( + "INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime, baseid)" + "VALUES(%d, %d, %d, %.17g, %s)", + pid, rid, i==0, p->rDate, zBaseId/*safe-for-%s*/); + add_mlink(pid, 0, rid, p, i==0); + if( i==0 ) parentid = pid; + } + if( p->nParent>1 ){ + /* Remove incorrect MLINK create-file entries that arise when a + ** file is added by merge. */ + db_multi_exec( + "DELETE FROM mlink" + " WHERE mid=%d" + " AND pid=0" + " AND fnid IN " + " (SELECT fnid FROM mlink WHERE mid=%d GROUP BY fnid" + " HAVING count(*)<%d)", + rid, rid, p->nParent + ); + } + db_prepare(&q, "SELECT cid, isprim FROM plink WHERE pid=%d", rid); while( db_step(&q)==SQLITE_ROW ){ int cid = db_column_int(&q, 0); - add_mlink(rid, p, cid, 0); + int isprim = db_column_int(&q, 1); + add_mlink(rid, p, cid, 0, isprim); } db_finalize(&q); if( p->nParent==0 ){ /* For root files (files without parents) add mlink entries ** showing all content as new. */ int isPublic = !content_is_private(rid); for(i=0; inFile; i++){ - add_one_mlink(rid, 0, p->aFile[i].zUuid, p->aFile[i].zName, 0, - isPublic, manifest_file_mperm(&p->aFile[i])); + add_one_mlink(0, 0, rid, p->aFile[i].zUuid, p->aFile[i].zName, 0, + isPublic, 1, manifest_file_mperm(&p->aFile[i])); } } + search_doc_touch('c', rid, 0); db_multi_exec( "REPLACE INTO event(type,mtime,objid,user,comment," "bgcolor,euser,ecomment,omtime)" "VALUES('ci'," " coalesce(" @@ -1713,19 +1843,19 @@ " %d,%Q,%Q," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>0)," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),%.17g);", TAG_DATE, rid, p->rDate, - rid, p->zUser, p->zComment, + rid, p->zUser, p->zComment, TAG_BGCOLOR, rid, TAG_USER, rid, TAG_COMMENT, rid, p->rDate ); zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event" " WHERE rowid=last_insert_rowid()"); wiki_extract_links(zCom, rid, 0, p->rDate, 1, WIKI_INLINE); - free(zCom); + fossil_free(zCom); /* If this is a delta-manifest, record the fact that this repository ** contains delta manifests, to free the "commit" logic to generate ** new delta manifests. */ @@ -1768,14 +1898,14 @@ switch( p->aTag[i].zName[0] ){ case '-': type = 0; break; /* Cancel prior occurrences */ case '+': type = 1; break; /* Apply to target only */ case '*': type = 2; break; /* Propagate to descendants */ default: - fossil_fatal("unknown tag type in manifest: %s", p->aTag); + fossil_error(1, "unknown tag type in manifest: %s", p->aTag); return 0; } - tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue, + tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue, rid, p->rDate, tid); } } if( parentid ){ tag_propagate_all(parentid); @@ -1790,11 +1920,11 @@ char zLength[40]; while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; nWiki = strlen(p->zWiki); sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); tag_insert(zTag, 1, zLength, rid, p->rDate, rid); - free(zTag); + fossil_free(zTag); prior = db_int(0, "SELECT rid FROM tagxref" " WHERE tagid=%d AND mtime<%.17g" " ORDER BY mtime DESC", tagid, p->rDate @@ -1805,24 +1935,24 @@ if( nWiki>0 ){ zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle); }else{ zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle); } + search_doc_touch('w',rid,p->zWikiTitle); db_multi_exec( "REPLACE INTO event(type,mtime,objid,user,comment," " bgcolor,euser,ecomment)" "VALUES('w',%.17g,%d,%Q,%Q," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", - p->rDate, rid, p->zUser, zComment, - TAG_BGCOLOR, rid, + p->rDate, rid, p->zUser, zComment, TAG_BGCOLOR, rid, TAG_USER, rid, TAG_COMMENT, rid ); - free(zComment); + fossil_free(zComment); } if( p->type==CFTYPE_EVENT ){ char *zTag = mprintf("event-%s", p->zEventId); int tagid = tag_findid(zTag, 1); int prior, subsequent; @@ -1830,11 +1960,11 @@ char zLength[40]; while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; nWiki = strlen(p->zWiki); sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); tag_insert(zTag, 1, zLength, rid, p->rDate, rid); - free(zTag); + fossil_free(zTag); prior = db_int(0, "SELECT rid FROM tagxref" " WHERE tagid=%d AND mtime<%.17g AND rid!=%d" " ORDER BY mtime DESC", tagid, p->rDate, rid @@ -1858,33 +1988,38 @@ } } if( subsequent ){ content_deltify(rid, subsequent, 0); }else{ + search_doc_touch('e',rid,0); db_multi_exec( "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)" "VALUES('e',%.17g,%d,%d,%Q,%Q," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", - p->rEventDate, rid, tagid, p->zUser, p->zComment, + p->rEventDate, rid, tagid, p->zUser, p->zComment, TAG_BGCOLOR, rid ); } } if( p->type==CFTYPE_TICKET ){ char *zTag; - assert( manifest_crosslink_busy==1 ); zTag = mprintf("tkt-%s", p->zTicketUuid); tag_insert(zTag, 1, 0, rid, p->rDate, rid); - free(zTag); + fossil_free(zTag); db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)", p->zTicketUuid); } if( p->type==CFTYPE_ATTACHMENT ){ + char *zComment = 0; + const char isAdd = (p->zAttachSrc && p->zAttachSrc[0]) ? 1 : 0; + const char attachToType = fossil_is_uuid(p->zAttachTarget) + ? 't' /* attach to ticket */ + : 'w' /* attach to wiki page */; db_multi_exec( "INSERT INTO attachment(attachid, mtime, src, target," - "filename, comment, user)" + "filename, comment, user)" "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);", rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName, (p->zComment ? p->zComment : ""), p->zUser ); db_multi_exec( @@ -1893,74 +2028,70 @@ " WHERE target=%Q AND filename=%Q))" " WHERE target=%Q AND filename=%Q", p->zAttachTarget, p->zAttachName, p->zAttachTarget, p->zAttachName ); - if( strlen(p->zAttachTarget)!=UUID_SIZE - || !validate16(p->zAttachTarget, UUID_SIZE) - ){ - char *zComment; - if( p->zAttachSrc && p->zAttachSrc[0] ){ + if( 'w' == attachToType ){ + if( isAdd ){ zComment = mprintf( - "Add attachment [%R/artifact/%S|%h] to wiki page [%h]", + "Add attachment [/artifact/%!S|%h] to wiki page [%h]", p->zAttachSrc, p->zAttachName, p->zAttachTarget); }else{ zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", p->zAttachName, p->zAttachTarget); } - db_multi_exec( - "REPLACE INTO event(type,mtime,objid,user,comment)" - "VALUES('w',%.17g,%d,%Q,%Q)", - p->rDate, rid, p->zUser, zComment - ); - free(zComment); - }else{ - char *zComment; - if( p->zAttachSrc && p->zAttachSrc[0] ){ - zComment = mprintf( - "Add attachment [%R/artifact/%S|%h] to ticket [%S]", - p->zAttachSrc, p->zAttachName, p->zAttachTarget); - }else{ - zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]", - p->zAttachName, p->zAttachTarget); - } - db_multi_exec( - "REPLACE INTO event(type,mtime,objid,user,comment)" - "VALUES('t',%.17g,%d,%Q,%Q)", - p->rDate, rid, p->zUser, zComment - ); - free(zComment); - } + }else{ + if( isAdd ){ + zComment = mprintf( + "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]", + p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget); + }else{ + zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]", + p->zAttachName, p->zAttachTarget, p->zAttachTarget); + } + } + db_multi_exec( + "REPLACE INTO event(type,mtime,objid,user,comment)" + "VALUES('%c',%.17g,%d,%Q,%Q)", + attachToType, p->rDate, rid, p->zUser, zComment + ); + fossil_free(zComment); } if( p->type==CFTYPE_CONTROL ){ Blob comment; int i; const char *zName; const char *zValue; - const char *zUuid; + const char *zTagUuid; int branchMove = 0; blob_zero(&comment); if( p->zComment ){ blob_appendf(&comment, " %s.", p->zComment); } /* Next loop expects tags to be sorted on UUID, so sort it. */ qsort(p->aTag, p->nTag, sizeof(p->aTag[0]), tag_compare); for(i=0; inTag; i++){ - zUuid = p->aTag[i].zUuid; - if( !zUuid ) continue; - if( i==0 || fossil_strcmp(zUuid, p->aTag[i-1].zUuid)!=0 ){ + zTagUuid = p->aTag[i].zUuid; + if( !zTagUuid ) continue; + if( i==0 || fossil_strcmp(zTagUuid, p->aTag[i-1].zUuid)!=0 ){ blob_appendf(&comment, - " Edit [%S]:", - zUuid); + " Edit [%!S|%S]:", + zTagUuid, zTagUuid); branchMove = 0; + if( permitHooks && db_exists("SELECT 1 FROM event, blob" + " WHERE event.type='ci' AND event.objid=blob.rid" + " AND blob.uuid=%Q", zTagUuid) ){ + zScript = xfer_commit_code(); + zUuid = zTagUuid; + } } zName = p->aTag[i].zName; zValue = p->aTag[i].zValue; if( strcmp(zName, "*branch")==0 ){ blob_appendf(&comment, - " Move to branch [/timeline?r=%h&nd&dp=%S | %h].", - zValue, zUuid, zValue); + " Move to branch [/timeline?r=%h&nd&dp=%!S&unhide | %h].", + zValue, zTagUuid, zValue); branchMove = 1; continue; }else if( strcmp(zName, "*bgcolor")==0 ){ blob_appendf(&comment, " Change branch background color to \"%h\".", zValue); @@ -2021,17 +2152,23 @@ p->rDate, rid, p->zUser, blob_str(&comment)+1 ); blob_reset(&comment); } db_end_transaction(0); + if( permitHooks ){ + rc = xfer_run_common_script(); + if( rc==TH_OK ){ + rc = xfer_run_script(zScript, zUuid, 0); + } + } if( p->type==CFTYPE_MANIFEST ){ manifest_cache_insert(p); }else{ manifest_destroy(p); } assert( blob_is_reset(pContent) ); - return 1; + return ( rc!=TH_ERROR ); } /* ** COMMAND: test-crosslink ** @@ -2045,7 +2182,7 @@ Blob content; db_find_and_open_repository(0, 0); if( g.argc!=3 ) usage("RECORDID"); rid = name_to_rid(g.argv[2]); content_get(rid, &content); - manifest_crosslink(rid, &content); + manifest_crosslink(rid, &content, MC_NONE); } Index: src/markdown.c ================================================================== --- src/markdown.c +++ src/markdown.c @@ -161,11 +161,11 @@ }; /* html_tag -- structure for quick HTML tag search (inspired from discount) */ struct html_tag { - char *text; + const char *text; int size; }; @@ -172,11 +172,11 @@ /******************** * GLOBAL VARIABLES * ********************/ /* block_tags -- recognised block tags, sorted by cmp_html_tag */ -static struct html_tag block_tags[] = { +static const struct html_tag block_tags[] = { { "p", 1 }, { "dl", 2 }, { "h1", 2 }, { "h2", 2 }, { "h3", 2 }, @@ -276,11 +276,11 @@ return fossil_strnicmp(hta->text, htb->text, hta->size); } /* find_block_tag -- returns the current block tag */ -static struct html_tag *find_block_tag(char *data, size_t size){ +static const struct html_tag *find_block_tag(const char *data, size_t size){ size_t i = 0; struct html_tag key; /* looking for the word end */ while( i=size ) return 0; if( i>j && data[i]=='>' ) return i+1; @@ -842,11 +842,13 @@ return end; } } -/* get_link_inline -- extract inline-style link and title from parenthesed data*/ +/* get_link_inline -- extract inline-style link and title from +** parenthesed data +*/ static int get_link_inline( struct Blob *link, struct Blob *title, char *data, size_t size @@ -1070,11 +1072,11 @@ /********************************* * BLOCK-LEVEL PARSING FUNCTIONS * *********************************/ /* is_empty -- returns the line length when it is empty, 0 otherwise */ -static size_t is_empty(char *data, size_t size){ +static size_t is_empty(const char *data, size_t size){ size_t i; for(i=0; imake.listitem ){ rndr->make.listitem(ob, work, *flags, rndr->make.opaque); } if( work!=&fallback ) release_work_buffer(rndr, work); - blob_zero(&fallback); + blob_reset(&fallback); return beg; } /* render of li contents */ if( has_inside_empty ) *flags |= MKD_LI_BLOCK; @@ -1558,11 +1560,11 @@ if( rndr->make.listitem ){ rndr->make.listitem(ob, inter, *flags, rndr->make.opaque); } release_work_buffer(rndr, inter); if( work!=&fallback ) release_work_buffer(rndr, work); - blob_zero(&fallback); + blob_reset(&fallback); return beg; } /* parse_list -- parsing ordered or unordered list block */ @@ -1584,11 +1586,11 @@ if( !j || (flags & MKD_LI_END) ) break; } if( rndr->make.list ) rndr->make.list(ob, work, flags, rndr->make.opaque); if( work!=&fallback ) release_work_buffer(rndr, work); - blob_zero(&fallback); + blob_reset(&fallback); return i; } /* parse_atxheader -- parsing of atx-style headers */ @@ -1631,11 +1633,15 @@ } /* htmlblock_end -- checking end of HTML block : [ \t]*\n[ \t*]\n */ /* returns the length on match, 0 otherwise */ -static size_t htmlblock_end(struct html_tag *tag, char *data, size_t size){ +static size_t htmlblock_end( + const struct html_tag *tag, + const char *data, + size_t size +){ size_t i, w; /* assuming data[0]=='<' && data[1]=='/' already tested */ /* checking tag is a match */ @@ -1668,11 +1674,11 @@ struct render *rndr, char *data, size_t size ){ size_t i, j = 0; - struct html_tag *curtag; + const struct html_tag *curtag; int found; size_t work_size = 0; struct Blob work = BLOB_INITIALIZER; /* identification of the opening tag */ @@ -2155,11 +2161,11 @@ struct Blob *ib, /* input blob in markdown */ const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */ ){ struct link_ref *lr; struct Blob text = BLOB_INITIALIZER; - size_t i, beg, end; + size_t i, beg, end = 0; struct render rndr; char *ib_data; /* filling the render structure */ if( !rndrer ) return; @@ -2224,17 +2230,17 @@ if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque); parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text)); if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque); /* clean-up */ - blob_zero(&text); + blob_reset(&text); lr = (struct link_ref *)blob_buffer(&rndr.refs); end = blob_size(&rndr.refs)/sizeof(struct link_ref); for(i=0; i'. + + * **Ordered list** items are prefixed by a number and a period. **Unordered list** items + are prefixed by a hyphen, asterisk or plus sign. Prefix and item text are separated by + whitespace. + + * **Code blocks** are formed by lines of text (possibly including empty lines) prefixed by + at least 4 spaces or a tab. + + * A **horizontal rule** is a line consisting of 3 or more asterisks, hyphens or underscores, + with optional whitespace between them. + + - Span elements + + * 3 types of **links** exist: + + - **automatic links** are URLs or email addresses enclosed in angle brackets + ('<' and '>'), and are displayed as such. + + - **inline links** consist of the displayed link text in square brackets ('[' and ']'), + followed by the link target in parentheses. + + - **reference links** separate _link instance_ from _link definition_. A link instance + consists of the displayed link text in square brackets, followed by a link definition name + in square brackets. + The corresponding link definition can occur anywhere on the page, and consists + of the link definition name in square brackets followed by a colon, whitespace and the + link target. + + * **Emphasis** can be given by wrapping text in one or two asterisks or underscores - use + one for HTML ``, and two for `` emphasis. + + * A **code span** is text wrapped in backticks ('`'). + + * **Images** use a syntax much like inline or reference links, but with alt attribute text + ('img alt=...') instead of link text, and the first pair of square + brackets in an image instance prefixed by an exclamation mark. + + - **Inline HTML** is mostly interpreted automatically. + + - **Escaping** Markdown punctuation characters is done by prefixing them by a backslash ('\\'). + +## Details + +### Paragraphs + +To cause an explicit line break within a paragraph, use 2 or more spaces at the end of a line. + +Any line containing only whitespace (space or tab characters) is considered a blank line. + +### Headers + +#### 'Setext' style headers (underlined) + +The number of underlining equal signs or hyphens used has no impact on the resulting header. + +Underlining using equal sign(s) creates a top level header (corresponding to HTML `

`), +while hyphen(s) create a second level header (HTML `

`). Thus, only 2 levels of headers +can be made this way. + +#### 'Atx' style headers (hash prefixed) + +1 to 6 hash characters can be used to indicate header levels 1 (HTML `

`) to 6 (`

`). + +Headers may optionally be 'closed' for cosmetic reasons, by appending a whitespace and hash +characters to the header. The number of trailing hash characters has no impact on the header +level. + +### Block quotes + +Not every line in a paragraph needs to be prefixed by '>' in order to make it a block quote, +only the first line. + +Block quoted paragraphs can be nested by using multiple '>' characters as prefix. + +Within a block quote, Markdown formatting (e.g. lists, emphasis) still works as normal. + +### Lists + +A list item prefix need not occur first on its line; up to 3 leading spaces are allowed +(4 spaces would make a code block out of the following text). + +For unordered lists, asterisks, hyphens and plus signs can be used interchangeably. + +For ordered lists, arbitrary numbers can be used as part of an item prefix; the items will be +renumbered during rendering. However, future implementations may demand that the number used +for the first item in a list indicates an offset to be used for subsequent items. + +For list items spanning multiple lines, subsequent lines can be indented using an arbitrary amount +of whitespace. + +List items will be wrapped in HTML `

` tags if they are separated by blank lines. + +A list item may span multiple paragraphs. At least the first line of each such paragraph must +be indented using at least 4 spaces or a tab character. + +Block quotes within list items must have their '>' delimiters indented using 4 up to 7 spaces. + +Code blocks within list items need to be indented _twice_, that is, using 8 spaces or 2 tab +characters. + +### Code blocks + +Lines within a code block are rendered verbatim using HTML `

` and `` tags, except that
+HTML punctuation characters like '<' and '&' are automatically converted to HTML entities. Thus,
+there is no need to explicitly escape HTML syntax within a code block.
+
+A code block runs until the first non blank line with indent less than 4 spaces or 1 tab character.
+
+
+Regular Markdown syntax is not processed within code blocks.
+
+### Links
+
+#### Automatic links
+
+When rendering automatic links to email addresses, HTML encoding obfuscation is used to 
+prevent some spambots from harvesting.
+
+#### Inline links
+
+Links to resources on the same server can use relative paths (i.e. can start with a '/').
+
+An optional title for the link (e.g. to have mouseover text in the browser) may be given behind 
+the link target but within the parentheses, in single and double quotes, and separated from the 
+link target by whitespace. 
+
+#### Reference links
+
+> Each reference link consists of 
+>
+>   - one or more _link instances_ at appropriate locations in the page text
+>   - a single _link definition_ at an arbitrary location on the page
+> 
+> During rendering, each link instance is resolved, and the corresponding definition is
+> filled in. No separate link definition clauses occur in the rendered output.
+> 
+> There are 3 fields involved in link instances and definitions:
+>
+>   - link text (i.e. the text that is displayed at the resulting link)
+>   - link definition name (i.e. an unique ID binding link instances to link definition)
+>   - link target (a target URL for the link)
+
+Multiple link instances may reference the same link definition using its link definition
+name.
+
+Link definition names are case insensitive, and may contain letters, numbers, spaces and
+punctuation.
+
+##### Link instance
+
+A space may be inserted between the bracket pairs for link text and link definition name.
+
+A link instance can use an _implicit link definition name_ shortcut, in which case the link
+text is used as the link definition name. The second set of brackets then remains empty, e.g.
+'[Google][]' ('Google' being used as both link text and link definition name).
+
+##### Link definition
+
+The first bracket pair containing the link definition name may be indented using up to 3 spaces.
+
+The link target may optionally be surrounded by angle brackets ('<' and '>').
+
+A link target may be followed by an optional title (e.g. to have mouseover text in the browser).
+This title may be enclosed in parentheses, single or double quotes.
+
+Link definitions may be split into 2 lines, with the title on the second line, arbitrarily
+indented. This may be more visually pleasing when using long link targets.
+
+### Emphasis
+
+The same character(s) used for starting the emphasis must be used to end it; don't mix
+asterisks and underscores.
+
+Emphasis can be used in the middle of a word. That is, there need not be whitespace on either
+side of emphasis start or end punctuation characters.
+
+### Code spans
+
+To include a literal backtick character in a code span, use multiple backticks as opening and
+closing delimiters.
+
+Whitespace may exist immediately after the opening delimiter and before the closing delimiter 
+of a code span, to allow for code fragments starting or ending with a backtick.
+
+Within a code span - like within a code block - angle brackets and ampersands are automatically encoded to make including
+HTML fragments easier.
+
+### Images
+
+If necessary, HTML must be used to specify image dimensions. Markdown has no provision for this.
+
+### Inline HTML
+
+Start and end tags of 
+a HTML block level construct (`
`, `` etc) must be separated from surrounding +context using blank lines, and must both occur at the start of a line. + +No extra unwanted `

` HTML tags are added around HTML block level tags. + +Markdown formatting within HTML block level tags is not processed; however, formatting within +span level tags (e.g. ``) is processed normally. + +### Escaping Markdown punctuation + +The following punctuation characters can be escaped using backslash: + + - \\ backslash + - ` backtick + - * asterisk + - _ underscore + - {} curly braces + - [] square brackets + - () parentheses + - # hash mark + - + plus sign + - - minus sign (hyphen) + - . dot + - ! exclamation mark + +To render a literal backslash, use 2 backslashes ('\\\\'). + Index: src/markdown_html.c ================================================================== --- src/markdown_html.c +++ src/markdown_html.c @@ -82,10 +82,24 @@ } } /* HTML block tags */ + +/* Size of the prolog: "

\n" */ +#define PROLOG_SIZE 23 + +static void html_prolog(struct Blob *ob, void *opaque){ + INTER_BLOCK(ob); + BLOB_APPEND_LITTERAL(ob, "
\n"); + assert( blob_size(ob)==PROLOG_SIZE ); +} + +static void html_epilog(struct Blob *ob, void *opaque){ + INTER_BLOCK(ob); + BLOB_APPEND_LITTERAL(ob, "
\n"); +} static void html_raw_block(struct Blob *ob, struct Blob *text, void *opaque){ char *data = blob_buffer(text); size_t first = 0, size = blob_size(text); INTER_BLOCK(ob); @@ -116,13 +130,12 @@ void *opaque ){ struct Blob *title = opaque; /* The first header at the beginning of a text is considered as * a title and not output. */ - if( blob_size(ob)==0 && blob_size(title)==0 ){ + if( blob_size(ob)<=PROLOG_SIZE && blob_size(title)==0 ){ BLOB_APPEND_BLOB(title, text); - return; } INTER_BLOCK(ob); blob_appendf(ob, "", level); BLOB_APPEND_BLOB(ob, text); blob_appendf(ob, "", level); @@ -364,11 +377,13 @@ struct Blob *input_markdown, struct Blob *output_title, struct Blob *output_body ){ struct mkd_renderer html_renderer = { - 0, 0, /* no prolog or epilog */ + /* prolog and epilog */ + html_prolog, + html_epilog, /* block level elements */ html_blockcode, html_blockquote, html_raw_block, @@ -397,11 +412,12 @@ html_normal_text, /* misc. parameters */ 64, /* maximum stack */ "*_", /* emphasis characters */ - output_title /* opaque data */ + 0 /* opaque data */ }; + html_renderer.opaque = output_title; blob_reset(output_title); blob_reset(output_body); markdown(output_body, input_markdown, &html_renderer); } Index: src/md5.c ================================================================== --- src/md5.c +++ src/md5.c @@ -16,10 +16,11 @@ * To compute the message digest of a chunk of bytes, declare an * MD5Context structure, pass it to MD5Init, call MD5Update as * needed on buffers full of bytes, and then call MD5Final, which * will fill a supplied 16-byte array with the digest. */ +#include "config.h" #include #include #include #include "md5.h" @@ -163,11 +164,11 @@ /* * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious * initialization constants. */ static void MD5Init(MD5Context *ctx){ - ctx->isInit = 1; + ctx->isInit = 1; ctx->buf[0] = 0x67452301; ctx->buf[1] = 0xefcdab89; ctx->buf[2] = 0x98badcfe; ctx->buf[3] = 0x10325476; ctx->bits[0] = 0; @@ -176,11 +177,11 @@ /* * Update context to reflect the concatenation of another buffer full * of bytes. */ -static +static void MD5Update(MD5Context *pCtx, const unsigned char *buf, unsigned int len){ struct Context *ctx = (struct Context *)pCtx; uint32 t; /* Update bitcount */ @@ -223,11 +224,11 @@ memcpy(ctx->in, buf, len); } /* - * Final wrapup - pad to 64-byte boundary with the bit pattern + * Final wrapup - pad to 64-byte boundary with the bit pattern * 1 0* (64-bit count of bits processed, MSB-first) */ static void MD5Final(unsigned char digest[16], MD5Context *pCtx){ struct Context *ctx = (struct Context *)pCtx; unsigned count; @@ -273,11 +274,11 @@ ** "unsigned char digest[16]" in the calling function. The MD5 ** digest is stored in the first 16 bytes. zBuf should ** be "char zBuf[33]". */ static void DigestToBase16(unsigned char *digest, char *zBuf){ - static char const zEncode[] = "0123456789abcdef"; + static const char zEncode[] = "0123456789abcdef"; int i, j; for(j=i=0; i<16; i++){ int a = digest[i]; zBuf[j++] = zEncode[(a>>4)&0xf]; @@ -342,11 +343,11 @@ return zResult; } /* ** Finish the incremental MD5 checksum. Store the result in blob pOut -** if pOut!=0. Also return a pointer to the result. +** if pOut!=0. Also return a pointer to the result. ** ** This resets the incremental checksum preparing for the next round ** of computation. The return pointer points to a static buffer that ** is overwritten by subsequent calls to this function. */ @@ -430,11 +431,11 @@ */ void md5sum_test(void){ int i; Blob in; Blob cksum; - + for(i=2; i0)" - " FROM event WHERE objid=%d", rid, rid, rid); + " FROM event WHERE objid=%d", timeline_utc(), rid, rid, rid); if( db_step(&q)==SQLITE_ROW ){ const char *zTagList = db_column_text(&q, 4); char *zCom; if( zTagList && zTagList[0] ){ zCom = mprintf("%s (%s)", db_column_text(&q, 2), zTagList); }else{ zCom = mprintf("%s", db_column_text(&q,2)); } - fossil_print("%-*s [%S] by %s on %s\n%*s", + fossil_print("%-*s [%S] by %s on %s\n%*s", indent-1, zLabel, db_column_text(&q, 3), db_column_text(&q, 1), db_column_text(&q, 0), indent, ""); - comment_print(zCom, indent, 78); + comment_print(zCom, db_column_text(&q,2), indent, -1, g.comFmtFlags); fossil_free(zCom); } db_finalize(&q); } @@ -91,10 +91,12 @@ ** files whose names differ only in case are taken ** to be the same file. ** ** -f|--force Force the merge even if it would be a no-op. ** +** --force-missing Force the merge even if there is missing content. +** ** --integrate Merged branch will be closed when committing. ** ** -n|--dry-run If given, display instead of run actions ** ** -v|--verbose Show additional details of the merge @@ -107,10 +109,11 @@ int integrateFlag; /* True if the --integrate option is present */ int pickFlag; /* True if the --cherrypick option is present */ int backoutFlag; /* True if the --backout option is present */ int dryRunFlag; /* True if the --dry-run or -n option is present */ int forceFlag; /* True if the --force or -f option is present */ + int forceMissingFlag; /* True if the --force-missing option is present */ const char *zBinGlob; /* The value of --binary */ const char *zPivot; /* The value of --baseline */ int debugFlag; /* True if --debug is present */ int nChng; /* Number of file name changes */ int *aChng; /* An array of file name changes */ @@ -127,10 +130,11 @@ ** P The "pivot" - the most recent common ancestor of V and M. */ undo_capture_command_line(); verboseFlag = find_option("verbose","v",0)!=0; + forceMissingFlag = find_option("force-missing",0,0)!=0; if( !verboseFlag ){ verboseFlag = find_option("detail",0,0)!=0; /* deprecated */ } pickFlag = find_option("cherrypick",0,0)!=0; integrateFlag = find_option("integrate",0,0)!=0; @@ -141,11 +145,10 @@ if( !dryRunFlag ){ dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */ } forceFlag = find_option("force","f",0)!=0; zPivot = find_option("baseline",0,1); - capture_case_sensitive_option(); verify_all_options(); db_must_be_within_tree(); if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); vid = db_lget_int("checkout", 0); if( vid==0 ){ @@ -163,11 +166,11 @@ }else if( g.argc==2 ){ /* No version specified on the command-line so pick the most recent ** leaf that is (1) not the version currently checked out and (2) ** has not already been merged into the current checkout and (3) ** the leaf is not closed and (4) the leaf is in the same branch - ** as the current checkout. + ** as the current checkout. */ Stmt q; if( pickFlag || backoutFlag || integrateFlag){ fossil_fatal("cannot use --backout, --cherrypick or --integrate with a fork merge"); } @@ -195,22 +198,22 @@ TAG_BRANCH, vid) ); } db_prepare(&q, "SELECT blob.uuid," - " datetime(event.mtime,'localtime')," + " datetime(event.mtime%s)," " coalesce(ecomment, comment)," " coalesce(euser, user)" " FROM event, blob" " WHERE event.objid=%d AND blob.rid=%d", - mid, mid + timeline_utc(), mid, mid ); if( db_step(&q)==SQLITE_ROW ){ char *zCom = mprintf("Merging fork [%S] at %s by %s: \"%s\"", db_column_text(&q, 0), db_column_text(&q, 1), db_column_text(&q, 3), db_column_text(&q, 2)); - comment_print(zCom, 0, 79); + comment_print(zCom, db_column_text(&q,2), 0, -1, g.comFmtFlags); fossil_free(zCom); } db_finalize(&q); }else{ usage("?OPTIONS? ?VERSION?"); @@ -258,31 +261,27 @@ if( !forceFlag && mid==pid ){ fossil_print("Merge skipped because it is a no-op. " " Use --force to override.\n"); return; } - if( integrateFlag ){ - if( db_exists("SELECT 1 FROM vmerge WHERE id=-4")) { - /* Fossil earlier than [55cacfcace] cannot handle this, - * therefore disallow it. */ - fossil_fatal("Integration of another branch already in progress." - " Commit or Undo needed first", g.argv[2]); - } - if( !is_a_leaf(mid) ){ - fossil_warning("ignoring --integrate: %s is not a leaf", g.argv[2]); - integrateFlag = 0; - } + if( integrateFlag && !is_a_leaf(mid)){ + fossil_warning("ignoring --integrate: %s is not a leaf", g.argv[2]); + integrateFlag = 0; } if( verboseFlag ){ print_checkin_description(mid, 12, integrateFlag?"integrate:":"merge-from:"); print_checkin_description(pid, 12, "baseline:"); } vfile_check_signature(vid, CKSIG_ENOTFILE); db_begin_transaction(); if( !dryRunFlag ) undo_begin(); - load_vfile_from_rid(mid); - load_vfile_from_rid(pid); + if( load_vfile_from_rid(mid) && !forceMissingFlag ){ + fossil_fatal("missing content, unable to merge"); + } + if( load_vfile_from_rid(pid) && !forceMissingFlag ){ + fossil_fatal("missing content, unable to merge"); + } if( debugFlag ){ char *z; z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pid); fossil_print("P=%d %z\n", pid, z); z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); @@ -473,13 +472,13 @@ undo_save(zName); vfile_to_disk(0, idm, 0, 0); } } db_finalize(&q); - + /* - ** Find files that have changed from P->M but not P->V. + ** Find files that have changed from P->M but not P->V. ** Copy the M content over into V. */ db_prepare(&q, "SELECT idv, ridm, fn, islinkm FROM fv" " WHERE idp>0 AND idv>0 AND idm>0" @@ -525,18 +524,18 @@ int rc; char *zFullPath; Blob m, p, r; /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ if( verboseFlag ){ - fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n", + fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv); }else{ fossil_print("MERGE %s\n", zName); } if( islinkv || islinkm /* || file_wd_islink(zFullPath) */ ){ fossil_print("***** Cannot merge symlink %s\n", zName); - nConflict++; + nConflict++; }else{ undo_save(zName); zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); content_get(ridp, &p); content_get(ridm, &m); @@ -644,11 +643,11 @@ fossil_warning("WARNING: %d unmanaged files were overwritten", nOverwrite); } if( dryRunFlag ){ fossil_warning("REMINDER: this was a dry run -" - " no file were actually changed."); + " no files were actually changed."); } /* ** Clean up the mid and pid VFILE entries. Then commit the changes. */ Index: src/merge3.c ================================================================== --- src/merge3.c +++ src/merge3.c @@ -136,11 +136,11 @@ } /* ** Text of boundary markers for merge conflicts. */ -static char const * const mergeMarker[] = { +static const char *const mergeMarker[] = { /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/ "<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<<<<\n", "======= COMMON ANCESTOR content follows ============================\n", "======= MERGED IN content follows ==================================\n", ">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" @@ -268,17 +268,17 @@ nConflict++; while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){ sz++; } DEBUG( printf("CONFLICT %d\n", sz); ) - blob_appendf(pOut, mergeMarker[0]); + blob_append(pOut, mergeMarker[0], -1); i1 = output_one_side(pOut, pV1, aC1, i1, sz); - blob_appendf(pOut, mergeMarker[1]); + blob_append(pOut, mergeMarker[1], -1); blob_copy_lines(pOut, pPivot, sz); - blob_appendf(pOut, mergeMarker[2]); + blob_append(pOut, mergeMarker[2], -1); i2 = output_one_side(pOut, pV2, aC2, i2, sz); - blob_appendf(pOut, mergeMarker[3]); + blob_append(pOut, mergeMarker[3], -1); } /* If we are finished with an edit triple, advance to the next ** triple. */ @@ -364,14 +364,18 @@ ** ** fossil 3-way-merge Xbase.c Xlocal.c Xup.c Xlocal.c ** cp Xup.c Xbase.c ** # Verify that everything still works ** fossil commit -** +** */ void delta_3waymerge_cmd(void){ Blob pivot, v1, v2, merged; + + /* We should be done with options.. */ + verify_all_options(); + if( g.argc!=6 ){ usage("PIVOT V1 V2 MERGED"); } if( blob_read_from_file(&pivot, g.argv[2])<0 ){ fossil_fatal("cannot read %s\n", g.argv[2]); ADDED src/miniz.c Index: src/miniz.c ================================================================== --- /dev/null +++ src/miniz.c @@ -0,0 +1,4916 @@ +/* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing + See "unlicense" statement at the end of this file. + Rich Geldreich , last updated Oct. 13, 2013 + Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define + MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Change History + 10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major release with Zip64 support (almost there!): + - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug + would only have occured in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place() + (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag). + - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size + - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries. + Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice). + - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes + - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed + - Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6. + - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti + - Merged MZ_FORCEINLINE fix from hdeanclark + - Fix include before config #ifdef, thanks emil.brink + - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can + set it to 1 for real-time compression). + - Merged in some compiler fixes from paulharris's github repro. + - Retested this build under Windows (VS 2010, including static analysis), tcc 0.9.26, gcc v4.6 and clang v3.3. + - Added example6.c, which dumps an image of the mandelbrot set to a PNG file. + - Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. + - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled + - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch + 5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). + 5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. + - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. + - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. + - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly + "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). + - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. + - Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. + - Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. + - Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) + - Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). + 4/12/12 v1.12 - More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's. + level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson for the feedback/bug report. + 5/28/11 v1.11 - Added statement from unlicense.org + 5/27/11 v1.10 - Substantial compressor optimizations: + - Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a + - Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86). + - Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types. + - Refactored the compression code for better readability and maintainability. + - Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large + drop in throughput on some files). + 5/15/11 v1.09 - Initial stable release. + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or + greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses + approximately as well as zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function + coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory + block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in + zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. + Supports raw deflate streams or standard zlib streams with adler-32 checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. + I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but + there are no guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by + Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to + get the job done with minimal fuss. There are simple API's to retrieve file information, read files from + existing archives, create new archives, append new files to existing archives, or clone archive data from + one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), + or you can specify custom file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central + directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one example) can be used to identify + multiple versions of the same file in an archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and + retrieve detailed info on each file by calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data + to disk and builds an exact image of the central directory in memory. The central directory image is written + all at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file data to any power of 2 alignment, + which can be useful when the archive will be read from optical media. Also, the writer supports placing + arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still + readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, + const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be appended to. + Note the appending is done in-place and is not an atomic operation, so if something goes wrong + during the operation it's possible the archive could be left without a central directory (although the local + file headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to + preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and + you're done. This is safe but requires a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), + append new files as needed, then finalize the archive which will write an updated central directory to the + original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a + possibility that the archive's central directory could be lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the + below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your target platform: + #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 + #define MINIZ_LITTLE_ENDIAN 1 + #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz + uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files + (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ + +#ifndef MINIZ_HEADER_INCLUDED +#define MINIZ_HEADER_INCLUDED + +#include + +// Defines to completely disable specific portions of miniz.c: +// If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. + +// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. +//#define MINIZ_NO_STDIO + +// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or +// get/set file times, and the C run-time funcs that get/set times won't be called. +// The current downside is the times written to your archives will be from 1979. +//#define MINIZ_NO_TIME + +// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_APIS + +// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_WRITING_APIS + +// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. +//#define MINIZ_NO_ZLIB_APIS + +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. +//#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc +// callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user +// functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. +//#define MINIZ_NO_MALLOC + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) + // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux + #define MINIZ_NO_TIME +#endif + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) + #include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +// MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define MINIZ_X86_OR_X64_CPU 1 +#endif + +#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. +#define MINIZ_LITTLE_ENDIAN 1 +#endif + +#if MINIZ_X86_OR_X64_CPU +// Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). +#define MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API Definitions. + +// For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! +typedef unsigned long mz_ulong; + +// mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +// mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +// mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +// Compression strategies. +enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; + +// Method +#define MZ_DEFLATED 8 + +#ifndef MINIZ_NO_ZLIB_APIS + +// Heap allocation callbacks. +// Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +#define MZ_VERSION "9.1.15" +#define MZ_VERNUM 0x91F0 +#define MZ_VER_MAJOR 9 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 15 +#define MZ_VER_SUBREVISION 0 + +// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). +enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; + +// Return status codes. MZ_PARAM_ERROR is non-standard. +enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; + +// Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. +enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; + +// Window bits +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +// Compression/decompression stream struct. +typedef struct mz_stream_s +{ + const unsigned char *next_in; // pointer to next byte to read + unsigned int avail_in; // number of bytes available at next_in + mz_ulong total_in; // total number of bytes consumed so far + + unsigned char *next_out; // pointer to next byte to write + unsigned int avail_out; // number of bytes that can be written to next_out + mz_ulong total_out; // total number of bytes produced so far + + char *msg; // error msg (unused) + struct mz_internal_state *state; // internal state, allocated by zalloc/zfree + + mz_alloc_func zalloc; // optional heap allocation function (defaults to malloc) + mz_free_func zfree; // optional heap free function (defaults to free) + void *opaque; // heap alloc function user pointer + + int data_type; // data_type (unused) + mz_ulong adler; // adler32 of the source or uncompressed data + mz_ulong reserved; // not used +} mz_stream; + +typedef mz_stream *mz_streamp; + +// Returns the version string of miniz.c. +const char *mz_version(void); + +// mz_deflateInit() initializes a compressor with default options: +// Parameters: +// pStream must point to an initialized mz_stream struct. +// level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. +// level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. +// (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if the input parameters are bogus. +// MZ_MEM_ERROR on out of memory. +int mz_deflateInit(mz_streamp pStream, int level); + +// mz_deflateInit2() is like mz_deflate(), except with more control: +// Additional parameters: +// method must be MZ_DEFLATED +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) +// mem_level must be between [1, 9] (it's checked but ignored by miniz.c) +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +// Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). +int mz_deflateReset(mz_streamp pStream); + +// mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. +// Return values: +// MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). +// MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) +int mz_deflate(mz_streamp pStream, int flush); + +// mz_deflateEnd() deinitializes a compressor: +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +int mz_deflateEnd(mz_streamp pStream); + +// mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +// Single-call compression functions mz_compress() and mz_compress2(): +// Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +// mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). +mz_ulong mz_compressBound(mz_ulong source_len); + +// Initializes a decompressor. +int mz_inflateInit(mz_streamp pStream); + +// mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +// Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. +// On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). +// MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. +// Return values: +// MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. +// MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_DATA_ERROR if the deflate stream is invalid. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again +// with more input data, or with more room in the output buffer (except when using single call decompression, described above). +int mz_inflate(mz_streamp pStream, int flush); + +// Deinitializes a decompressor. +int mz_inflateEnd(mz_streamp pStream); + +// Single-call decompression. +// Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); + +// Returns a string description of the specified error code, or NULL if the error code is invalid. +const char *mz_error(int err); + +// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + typedef unsigned char Byte; + typedef unsigned int uInt; + typedef mz_ulong uLong; + typedef Byte Bytef; + typedef uInt uIntf; + typedef char charf; + typedef int intf; + typedef void *voidpf; + typedef uLong uLongf; + typedef void *voidp; + typedef void *const voidpc; + #define Z_NULL 0 + #define Z_NO_FLUSH MZ_NO_FLUSH + #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH + #define Z_SYNC_FLUSH MZ_SYNC_FLUSH + #define Z_FULL_FLUSH MZ_FULL_FLUSH + #define Z_FINISH MZ_FINISH + #define Z_BLOCK MZ_BLOCK + #define Z_OK MZ_OK + #define Z_STREAM_END MZ_STREAM_END + #define Z_NEED_DICT MZ_NEED_DICT + #define Z_ERRNO MZ_ERRNO + #define Z_STREAM_ERROR MZ_STREAM_ERROR + #define Z_DATA_ERROR MZ_DATA_ERROR + #define Z_MEM_ERROR MZ_MEM_ERROR + #define Z_BUF_ERROR MZ_BUF_ERROR + #define Z_VERSION_ERROR MZ_VERSION_ERROR + #define Z_PARAM_ERROR MZ_PARAM_ERROR + #define Z_NO_COMPRESSION MZ_NO_COMPRESSION + #define Z_BEST_SPEED MZ_BEST_SPEED + #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION + #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION + #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY + #define Z_FILTERED MZ_FILTERED + #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY + #define Z_RLE MZ_RLE + #define Z_FIXED MZ_FIXED + #define Z_DEFLATED MZ_DEFLATED + #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS + #define alloc_func mz_alloc_func + #define free_func mz_free_func + #define internal_state mz_internal_state + #define z_stream mz_stream + #define deflateInit mz_deflateInit + #define deflateInit2 mz_deflateInit2 + #define deflateReset mz_deflateReset + #define deflate mz_deflate + #define deflateEnd mz_deflateEnd + #define deflateBound mz_deflateBound + #define compress mz_compress + #define compress2 mz_compress2 + #define compressBound mz_compressBound + #define inflateInit mz_inflateInit + #define inflateInit2 mz_inflateInit2 + #define inflate mz_inflate + #define inflateEnd mz_inflateEnd + #define uncompress mz_uncompress + #define crc32 mz_crc32 + #define adler32 mz_adler32 + #define MAX_WBITS 15 + #define MAX_MEM_LEVEL 9 + #define zError mz_error + #define ZLIB_VERSION MZ_VERSION + #define ZLIB_VERNUM MZ_VERNUM + #define ZLIB_VER_MAJOR MZ_VER_MAJOR + #define ZLIB_VER_MINOR MZ_VER_MINOR + #define ZLIB_VER_REVISION MZ_VER_REVISION + #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION + #define zlibVersion mz_version + #define zlib_version mz_version() +#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Types and macros + +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef long long mz_int64; +typedef unsigned long long mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +// An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message. +#ifdef _MSC_VER + #define MZ_MACRO_END while (0, 0) +#else + #define MZ_MACRO_END while (0) +#endif + +// ------------------- ZIP archive reading/writing + +#ifndef MINIZ_NO_ARCHIVE_APIS + +enum +{ + MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +}; + +typedef struct +{ + mz_uint32 m_file_index; + mz_uint32 m_central_dir_ofs; + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; +#ifndef MINIZ_NO_TIME + time_t m_time; +#endif + mz_uint32 m_crc32; + mz_uint64 m_comp_size; + mz_uint64 m_uncomp_size; + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + mz_uint64 m_local_header_ofs; + mz_uint32 m_comment_size; + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum +{ + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef struct mz_zip_archive_tag +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + mz_uint m_total_files; + mz_zip_mode m_zip_mode; + + mz_uint m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef enum +{ + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +} mz_zip_flags; + +// ZIP archive reading + +// Inits a ZIP archive reader. +// These functions read and validate the archive's central directory. +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags); +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +#endif + +// Returns the total number of files in the archive. +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +// Returns detailed information about an archive file entry. +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +// Determines if an archive file entry is a directory entry. +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +// Retrieves the filename of an archive file entry. +// Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +// Attempts to locates a file in the archive's central directory. +// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH +// Returns -1 if the file cannot be found. +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + +// Extracts a archive file to a memory buffer using no memory allocation. +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +// Extracts a archive file to a memory buffer. +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +// Extracts a archive file to a dynamically allocated heap buffer. +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +// Extracts a archive file using a callback function to output the file's data. +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +// Extracts a archive file to a disk file and sets its last accessed and modified times. +// This function only extracts files, not archive directory records. +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); +#endif + +// Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +// ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +// Inits a ZIP archive writer. +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +#endif + +// Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. +// For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. +// For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). +// Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. +// Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before +// the archive is finalized the file's central directory will be hosed. +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); + +// Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. +// To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +#ifndef MINIZ_NO_STDIO +// Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +#endif + +// Adds a file to an archive by fully cloning the data from another archive. +// This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields. +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index); + +// Finalizes the archive by writing the central directory records followed by the end of central directory record. +// After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). +// An archive must be manually finalized by calling this function for it to be valid. +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize); + +// Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. +// Note for the archive to be valid, it must have been finalized before ending. +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +// Misc. high-level helper functions: + +// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +// Reads a single file from an archive into a heap block. +// Returns NULL on failure. +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +// ------------------- Low-level Decompression API Definitions + +// Decompression flags used by tinfl_decompress(). +// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. +// TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. +// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). +// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +// High level decompression functions: +// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. +// On return: +// Function returns a pointer to the decompressed data, or NULL on failure. +// *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must call mz_free() on the returned block when it's no longer needed. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. +// Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. +// Returns 1 on success or 0 on failure. +typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; + +// Max size of LZ dictionary. +#define TINFL_LZ_DICT_SIZE 32768 + +// Return status. +typedef enum +{ + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +// Initializes the decompressor to its initial state. +#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +// Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. +// This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +// Internal/private bits follow. +enum +{ + TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS + #define TINFL_USE_64BIT_BITBUF 1 +#endif + +#if TINFL_USE_64BIT_BITBUF + typedef mz_uint64 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (64) +#else + typedef mz_uint32 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +// ------------------- Low-level Compression API Definitions + +// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). +#define TDEFL_LESS_MEMORY 0 + +// tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): +// TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). +enum +{ + TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. +// TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). +// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. +// TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). +// TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) +// TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. +// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. +// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. +// The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). +enum +{ + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +// High level compression functions: +// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of source block to compress. +// flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must free() the returned block when it's no longer needed. +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. +// Returns 0 on failure. +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// Compresses an image to a compressed PNG file in memory. +// On entry: +// pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. +// The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. +// level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL +// If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pLen_out will be set to the size of the PNG image file. +// The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +// Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); + +// tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; + +// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). +#if TDEFL_LESS_MEMORY +enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#else +enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#endif + +// The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. +typedef enum +{ + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1, +} tdefl_status; + +// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums +typedef enum +{ + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +// tdefl's compression state structure. +typedef struct +{ + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +// Initializes the compressor. +// There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. +// pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. +// If pBut_buf_func is NULL the user should always call the tdefl_compress() API. +// flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +// Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. +// tdefl_compress_buffer() always consumes the entire input buffer. +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros. +#ifndef MINIZ_NO_ZLIB_APIS +// Create tdefl_compress() flags given zlib-style compression parameters. +// level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) +// window_bits may be -15 (raw deflate) or 15 (zlib) +// strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); +#endif // #ifndef MINIZ_NO_ZLIB_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_INCLUDED + +// ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.) + +#ifndef MINIZ_HEADER_FILE_ONLY + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16)==2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32)==4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64)==8 ? 1 : -1]; + +#include +#include + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC + #define MZ_MALLOC(x) NULL + #define MZ_FREE(x) (void)x, ((void)0) + #define MZ_REALLOC(p, x) NULL +#else + #define MZ_MALLOC(x) malloc(x) + #define MZ_FREE(x) free(x) + #define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a,b) (((a)>(b))?(a):(b)) +#define MZ_MIN(a,b) (((a)<(b))?(a):(b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) + #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else + #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) + #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#ifdef _MSC_VER + #define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) + #define MZ_FORCEINLINE inline __attribute__((__always_inline__)) +#else + #define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +// ------------------- zlib-style API's + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; + if (!ptr) return MZ_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + return (s2 << 16) + s1; +} + +// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) return MZ_CRC32_INIT; + crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } + return ~crcu32; +} + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +#ifndef MINIZ_NO_ZLIB_APIS + +static void *def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } +static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } +static void *def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, ((tdefl_compressor*)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; + if (!pStream->avail_out) return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; + for ( ; ; ) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; // Can't make forward progress without some input. + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state* pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + + pState = (inflate_state*)pStream->state; + if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; pState->m_first_call = 0; + if (pState->m_last_status < 0) return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + // flush != MZ_FINISH then we must assume there's more input. + if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for ( ; ; ) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. + else if (flush == MZ_FINISH) + { + // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) +{ + static struct { int m_err; const char *m_pDesc; } s_error_descs[] = + { + { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, + { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } + }; + mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif //MINIZ_NO_ZLIB_APIS + +// ------------------- Low-level Decompression (completely independent from all compression API's) + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN switch(r->m_state) { case 0: +#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END +#define TINFL_CR_FINISH } + +// TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never +// reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. +#define TINFL_GET_BYTE(state_index, c) do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for ( ; ; ) { \ + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ + TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else c = *pIn_buf_cur++; } MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END + +// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. +// It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a +// Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the +// bit buffer contains >=15 bits (deflate's max. Huffman code size). +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ + } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ + } while (num_bits < 15); + +// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read +// beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully +// decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. +// The slow path is only executed at the very end of the input buffer. +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ + int temp; mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ + } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; + static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } + + num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } + while (pIn_buf_cur >= pIn_buf_end) + { + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) + { + TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); + } + else + { + TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); + } + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; + r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } + r->m_table_sizes[2] = 19; + } + for ( ; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; + cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) + { + mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for ( ; ; ) + { + mz_uint8 *pSrc; + for ( ; ; ) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } +#else + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + counter = sym2; bit_buf >>= code_len; num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + bit_buf >>= code_len; num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) break; + + num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + TINFL_CR_FINISH + +common_exit: + r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +// Higher level helper functions. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for ( ; ; ) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) break; + new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for ( ; ; ) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +// ------------------- Low-level Compression (independent from all decompression API's) + +// Purposely making these tables static for faster init and thread safety. +static const mz_uint16 s_tdefl_len_sym[256] = { + 257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272, + 273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276, + 277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, + 279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280, + 281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281, + 282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282, + 283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283, + 284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 }; + +static const mz_uint8 s_tdefl_len_extra[256] = { + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 }; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = { + 0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 }; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = { + 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7 }; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = { + 0,0,18,19,20,20,21,21,22,22,22,22,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,26,26,26,26,26,26,26,26,26,26,26,26, + 26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28, + 28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29 }; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = { + 0,0,8,8,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 }; + +// Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. +typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; +static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, tdefl_sym_freq* pSyms1) +{ + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq* pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const mz_uint32* pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } + for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { tdefl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } + } + return pCur_syms; +} + +// tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) +{ + int root, leaf, next, avbl, used, dpth; + if (n==0) return; else if (n==1) { A[0].m_key = 1; return; } + A[0].m_key += A[1].m_key; root = 0; leaf = 2; + for (next=1; next < n-1; next++) + { + if (leaf>=n || A[root].m_key=n || (root=0; next--) A[next].m_key = A[A[next].m_key].m_key+1; + avbl = 1; used = dpth = 0; root = n-2; next = n-1; + while (avbl>0) + { + while (root>=0 && (int)A[root].m_key==dpth) { used++; root--; } + while (avbl>used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } + avbl = 2*used; dpth++; used = 0; + } +} + +// Limits canonical Huffman code table's max code size. +enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) +{ + int i; mz_uint32 total = 0; if (code_list_len <= 1) return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) +{ + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; + code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) do { \ + mz_uint bits = b; mz_uint len = l; MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); d->m_bits_in += len; \ + while (d->m_bits_in >= 8) { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ +} MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \ + if (rle_repeat_count < 3) { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } else { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ +} rle_repeat_count = 0; } } + +#define TDEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \ + if (rle_z_count < 3) { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \ + } else if (rle_z_count <= 10) { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } else { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ +} rle_z_count = 0; } } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) +{ + int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; ) + { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) +{ + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) *p++ = 8; + for ( ; i <= 255; ++i) *p++ = 9; + for ( ; i <= 279; ++i) *p++ = 7; + for ( ; i <= 287; ++i) *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) { bit_buffer |= (((mz_uint64)(b)) << bits_in); bits_in += (l); } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) + { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + // This sequence coaxes MSVC into using cmov's vs. jmp's. + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64*)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) + { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) + { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + if (match_dist < 512) + { + sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } + else + { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) +{ + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. + if ( ((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size) ) + { + mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } + } + else + { + mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16*)(p) +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for ( ; ; ) + { + for ( ; ; ) + { + if (--num_probes_left == 0) return; + #define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; q = (const mz_uint16*)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD(q) != s01) continue; p = s; probe_len = 32; + do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); + if (!probe_len) + { + *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for ( ; ; ) + { + for ( ; ; ) + { + if (--num_probes_left == 0) return; + #define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; + if (probe_len > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; + c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) + { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) + { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + // Simple lazy/greedy parsing state machine. + len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + // Move the lookahead forward by len_to_move bytes. + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); + // Check if it's time to flush the current LZ codes to the internal output buffer. + if ( (d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ( (d->m_total_lz_bytes > 31*1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) ) + { + int n; + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) +{ + if (!d) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if ( ((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf) ) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; + d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; + pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; + do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); + pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; + p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; + } + memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; + *pOut_len = out_buf.m_size; return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) return 0; + out_buf.m_pBuf = (mz_uint8*)pOut_buf; out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; + return out_buf.m_size; +} + +#ifndef MINIZ_NO_ZLIB_APIS +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +// level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} +#endif //MINIZ_NO_ZLIB_APIS + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable:4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) +#endif + +// Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at +// http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. +// This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) +{ + // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; + if (!pComp) return NULL; + MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } + // write dummy header + for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); + // compress image data + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + // write real header + *pLen_out = out_buf.m_size-41; + { + static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; + mz_uint8 pnghdr[41]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52, + 0,0,(mz_uint8)(w>>8),(mz_uint8)w,0,0,(mz_uint8)(h>>8),(mz_uint8)h,8,chans[num_chans],0,0,0,0,0,0,0, + (mz_uint8)(*pLen_out>>24),(mz_uint8)(*pLen_out>>16),(mz_uint8)(*pLen_out>>8),(mz_uint8)*pLen_out,0x49,0x44,0x41,0x54}; + c=(mz_uint32)mz_crc32(MZ_CRC32_INIT,pnghdr+12,17); for (i=0; i<4; ++i, c<<=8) ((mz_uint8*)(pnghdr+29))[i]=(mz_uint8)(c>>24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + // write footer (IDAT CRC-32, followed by IEND chunk) + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+41-4, *pLen_out+4); for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-16)[i] = (mz_uint8)(c >> 24); + // compute final size of file, grab compressed data buffer and return + *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +// ------------------- .ZIP archive reading + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef MINIZ_NO_STDIO + #define MZ_FILE void * +#else + #include + #include + + #if defined(_MSC_VER) || defined(__MINGW64__) + static FILE *mz_fopen(const char *pFilename, const char *pMode) + { + FILE* pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; + } + static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) + { + FILE* pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; + } + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN mz_fopen + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 _ftelli64 + #define MZ_FSEEK64 _fseeki64 + #define MZ_FILE_STAT_STRUCT _stat + #define MZ_FILE_STAT _stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN mz_freopen + #define MZ_DELETE_FILE remove + #elif defined(__MINGW32__) + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello64 + #define MZ_FSEEK64 fseeko64 + #define MZ_FILE_STAT_STRUCT _stat + #define MZ_FILE_STAT _stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #elif defined(__TINYC__) + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftell + #define MZ_FSEEK64 fseek + #define MZ_FILE_STAT_STRUCT stat + #define MZ_FILE_STAT stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #elif defined(__GNUC__) && _LARGEFILE64_SOURCE + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen64(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello64 + #define MZ_FSEEK64 fseeko64 + #define MZ_FILE_STAT_STRUCT stat64 + #define MZ_FILE_STAT stat64 + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) + #define MZ_DELETE_FILE remove + #else + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello + #define MZ_FSEEK64 fseeko + #define MZ_FILE_STAT_STRUCT stat + #define MZ_FILE_STAT stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #endif // #ifdef _MSC_VER +#endif // #ifdef MINIZ_NO_STDIO + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +// Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. +enum +{ + // ZIP archive identifiers and record sizes + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + // Central directory header record offsets + MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + // Local directory header offsets + MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + // End of central directory offsets + MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + MZ_FILE *m_pFile; + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; + if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; + pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; + memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +static void mz_zip_time_to_dos_time(time_t time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif + +#ifndef MINIZ_NO_STDIO +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef MINIZ_NO_TIME + (void)pFilename; *pDOS_date = *pDOS_time = 0; +#else + struct MZ_FILE_STAT_STRUCT file_stat; + // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date); +#endif // #ifdef MINIZ_NO_TIME + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, time_t modified_time) +{ + struct utimbuf t; t.actime = access_time; t.modtime = modified_time; + return !utime(pFilename, &t); +} +#endif // #ifndef MINIZ_NO_TIME +#endif // #ifndef MINIZ_NO_STDIO + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) do { mz_uint32 t = a; a = b; b = t; } MZ_MACRO_END + +// Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + int start = (size - 2) >> 1, end; + while (start >= 0) + { + int child, root = start; + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= size) + break; + child += (((child + 1) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + start--; + } + + end = size - 1; + while (end > 0) + { + int child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= end) + break; + child += (((child + 1) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint32 flags) +{ + mz_uint cdir_size, num_this_disk, cdir_disk_index; + mz_uint64 cdir_ofs; + mz_int64 cur_file_ofs; + const mz_uint8 *p; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + // Find the end of central directory record by scanning the file from the end towards the beginning. + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for ( ; ; ) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + for (i = n - 4; i >= 0; --i) + if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + break; + if (i >= 0) + { + cur_file_ofs += i; + break; + } + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return MZ_FALSE; + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + // Read and verify the end of central directory record. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || + ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) + return MZ_FALSE; + + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return MZ_FALSE; + + if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return MZ_FALSE; + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + + // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices. + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return MZ_FALSE; + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return MZ_FALSE; + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return MZ_FALSE; + + // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported). + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, comp_size, decomp_size, disk_index; + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return MZ_FALSE; + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF)) + return MZ_FALSE; + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index != num_this_disk) && (disk_index != 1)) + return MZ_FALSE; + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return MZ_FALSE; + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return MZ_FALSE; + n -= total_header_size; p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return MZ_FALSE; + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags) +{ + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + pZip->m_pState->m_mem_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + mz_uint64 file_size; + MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return MZ_FALSE; + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + file_size = MZ_FTELL64(pFile); + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & 1); +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, external_attr; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + + // First see if the filename ends with a '/' character. + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. + // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. + // FIXME: Remove this check? Is it necessary - we already check the filename. + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & 0x10) != 0) + return MZ_TRUE; + + return MZ_FALSE; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if ((!p) || (!pStat)) + return MZ_FALSE; + + // Unpack the central directory record. + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + // Copy as much of the filename and comment as possible. + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; + + return MZ_TRUE; +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + int l = 0, h = size - 1; + while (l <= h) + { + int m = (l + h) >> 1, file_index = pIndices[m], comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + return file_index; + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + return -1; +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint file_index; size_t name_len, comment_len; + if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return -1; + if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + return mz_zip_reader_locate_file_binary_search(pZip, pName); + name_len = strlen(pName); if (name_len > 0xFFFF) return -1; + comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1; + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) + return file_index; + } + return -1; +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((buf_size) && (!pBuf)) + return MZ_FALSE; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Ensure supplied output buffer is large enough. + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return MZ_FALSE; + return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); + } + + // Decompress the file either directly from memory or from a file input buffer. + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + // Read directly from the archive in memory. + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + // Use a user provided read buffer. + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + // Temporarily allocate a read buffer. + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#endif + return MZ_FALSE; + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + if (!p) + return NULL; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#endif + return NULL; + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + return NULL; + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + { + if (pSize) *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + // Decompress the file either directly from memory or from a file input buffer. + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pState->m_pMem) + { +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#endif + return MZ_FALSE; + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + status = TINFL_STATUS_FAILED; + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + status = TINFL_STATUS_FAILED; + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + status = TINFL_STATUS_FAILED; + break; + } + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return MZ_FALSE; + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + if (MZ_FCLOSE(pFile) == EOF) + return MZ_FALSE; +#ifndef MINIZ_NO_TIME + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + return status; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} +#endif + +// ------------------- .ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } +static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (pZip->m_file_offset_alignment) + { + // Ensure user specified file offset alignment is a power of 2. + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return MZ_FALSE; + } + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); +#ifdef _MSC_VER + if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#else + if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#endif + return 0; + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + return 0; + pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + MZ_FILE *pFile; + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_pFile = pFile; + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf); + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + cur_ofs += n; size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + // No sense in trying to write to an archive that's already at the support max size + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + pFilename; return MZ_FALSE; +#else + // Archive is being read from stdio - try to reopen as writable. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + if (!pFilename) + return MZ_FALSE; + pZip->m_pWrite = mz_zip_file_write_func; + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. + mz_zip_reader_end(pZip); + return MZ_FALSE; + } +#endif // #ifdef MINIZ_NO_STDIO + } + else if (pState->m_pMem) + { + // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + } + // Archive is being read via a user provided read function - make sure the user has specified a write function too. + else if (!pZip->m_pWrite) + return MZ_FALSE; + + // Start writing new files at the archive's current central directory location. + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_central_directory_file_ofs = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + // No zip64 support yet + if ((local_header_ofs > 0xFFFFFFFF) || (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + comment_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) + return MZ_FALSE; + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + // Try to push the central directory array back into its original state. + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. + if (*pArchive_name == '/') + return MZ_FALSE; + while (*pArchive_name) + { + if ((*pArchive_name == '\\') || (*pArchive_name == ':')) + return MZ_FALSE; + pArchive_name++; + } + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return MZ_FALSE; + cur_file_ofs += s; n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return MZ_FALSE; + // No zip64 support yet + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + +#ifndef MINIZ_NO_TIME + { + time_t cur_time; time(&cur_time); + mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif // #ifndef MINIZ_NO_TIME + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + // Set DOS Subdirectory attribute bit. + ext_attributes |= 0x10; + // Subdirectories cannot contain data. + if ((buf_size) || (uncomp_size)) + return MZ_FALSE; + } + + // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return MZ_FALSE; + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return MZ_FALSE; + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + method = MZ_DEFLATED; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + MZ_FILE *pSrc_file = NULL; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date)) + return MZ_FALSE; + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return MZ_FALSE; + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + if (uncomp_size > 0xFFFFFFFF) + { + // No zip64 support yet + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (uncomp_size) + { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + if (!level) + { + while (uncomp_remaining) + { + mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + for ( ; ; ) + { + size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + + if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) + break; + + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + break; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + MZ_FCLOSE(pSrc_file); pSrc_file = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes; + mz_uint64 comp_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; const mz_uint8 *pSrc_central_header; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) + return MZ_FALSE; + pState = pZip->m_pState; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + cur_dst_file_ofs = pZip->m_archive_size; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + cur_dst_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(sizeof(mz_uint32) * 4, MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining))))) + return MZ_FALSE; + + while (comp_bytes_remaining) + { + n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_dst_file_ofs += n; + + comp_bytes_remaining -= n; + } + + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + // Copy data descriptor + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + // no zip64 support yet + if (cur_dst_file_ofs > 0xFFFFFFFF) + return MZ_FALSE; + + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return MZ_FALSE; + + n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + if (pState->m_central_dir.m_size > 0xFFFFFFFF) + return MZ_FALSE; + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + + pState = pZip->m_pState; + + // no zip64 support yet + if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + // Write central directory + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) + return MZ_FALSE; + pZip->m_archive_size += central_dir_size; + } + + // Write end of central directory record + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr)) + return MZ_FALSE; +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return MZ_FALSE; +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_archive_size += sizeof(hdr); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) + return MZ_FALSE; + if (pZip->m_pWrite != mz_zip_heap_write_func) + return MZ_FALSE; + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *pBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + return MZ_FALSE; + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + MZ_CLEAR_OBJ(zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + // Create a new archive. + if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) + return MZ_FALSE; + created_new_archive = MZ_TRUE; + } + else + { + // Append to an existing archive. + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return MZ_FALSE; + if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) + { + mz_zip_reader_end(&zip_archive); + return MZ_FALSE; + } + } + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) + if (!mz_zip_writer_finalize_archive(&zip_archive)) + status = MZ_FALSE; + if (!mz_zip_writer_end(&zip_archive)) + status = MZ_FALSE; + if ((!status) && (created_new_archive)) + { + // It's a new archive and something went wrong, so just delete it. + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + return status; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + int file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + return NULL; + + MZ_CLEAR_OBJ(zip_archive); + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return NULL; + + if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0) + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + + mz_zip_reader_end(&zip_archive); + return p; +} + +#endif // #ifndef MINIZ_NO_STDIO + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_FILE_ONLY + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ ADDED src/mkbuiltin.c Index: src/mkbuiltin.c ================================================================== --- /dev/null +++ src/mkbuiltin.c @@ -0,0 +1,164 @@ +/* +** Copyright (c) 2014 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This is a stand-alone utility program that is part of the Fossil build +** process. This program reads files named on the command line and converts +** them into ANSI-C static char array variables. Output is written onto +** standard output. +** +** The makefiles use this utility package various resources (large scripts, +** GIF images, etc) that are separate files in the source code as byte +** arrays in the resulting executable. +*/ +#include +#include +#include + + +/* +** Read the entire content of the file named zFilename into memory obtained +** from malloc() and return a pointer to that memory. Write the size of the +** file into *pnByte. +*/ +static unsigned char *read_file(const char *zFilename, int *pnByte){ + FILE *in; + unsigned char *z; + int nByte; + int got; + in = fopen(zFilename, "rb"); + if( in==0 ){ + return 0; + } + fseek(in, 0, SEEK_END); + *pnByte = nByte = ftell(in); + fseek(in, 0, SEEK_SET); + z = malloc( nByte+1 ); + if( z==0 ){ + fprintf(stderr, "failed to allocate %d bytes\n", nByte+1); + exit(1); + } + got = fread(z, 1, nByte, in); + fclose(in); + z[got] = 0; + return z; +} + +/* +** There is an instance of the following for each file translated. +*/ +typedef struct Resource Resource; +struct Resource { + const char *zName; + int nByte; + int idx; +}; + +/* +** Compare two Resource objects for sorting purposes. They sort +** in zName order so that Fossil can search for resources using +** a binary search. +*/ +static int compareResource(const void *a, const void *b){ + Resource *pA = (Resource*)a; + Resource *pB = (Resource*)b; + return strcmp(pA->zName, pB->zName); +} + +int main(int argc, char **argv){ + int i, sz; + int j, n; + Resource *aRes; + int nRes; + unsigned char *pData; + int nErr = 0; + int nSkip; + int nPrefix = 0; + + if( argc>3 && strcmp(argv[1],"--prefix")==0 ){ + nPrefix = (int)strlen(argv[2]); + argc -= 2; + argv += 2; + } + nRes = argc - 1; + aRes = malloc( nRes*sizeof(aRes[0]) ); + if( aRes==0 ){ + fprintf(stderr, "malloc failed\n"); + return 1; + } + for(i=0; i=nPrefix ) z += nPrefix; + while( z[0]=='.' || z[0]=='/' ){ z++; } + aRes[i].zName = z; + } + qsort(aRes, nRes, sizeof(aRes[0]), compareResource); + for(i=0; i #include +#include int main(int argc, char *argv[]){ FILE *m,*u,*v; char *z; int i, x, d; @@ -17,11 +18,14 @@ char b[1000]; char vx[1000]; memset(b,0,sizeof(b)); memset(vx,0,sizeof(vx)); u = fopen(argv[1],"r"); - fgets(b, sizeof(b)-1,u); + if( fgets(b, sizeof(b)-1,u)==0 ){ + fprintf(stderr, "malformed manifest.uuid file: %s\n", argv[1]); + exit(1); + } fclose(u); for(z=b; z[0] && z[0]!='\r' && z[0]!='\n'; z++){} *z = 0; printf("#define MANIFEST_UUID \"%s\"\n",b); printf("#define MANIFEST_VERSION \"[%10.10s]\"\n",b); @@ -32,15 +36,18 @@ printf("#define MANIFEST_YEAR \"%.4s\"\n",b+2); } } fclose(m); v = fopen(argv[3],"r"); - fgets(b, sizeof(b)-1,v); + if( fgets(b, sizeof(b)-1,v)==0 ){ + fprintf(stderr, "malformed VERSION file: %s\n", argv[3]); + exit(1); + } fclose(v); for(z=b; z[0] && z[0]!='\r' && z[0]!='\n'; z++){} *z = 0; - printf("#define RELEASE_VERSION \"%s\"\n", b); + printf("#define RELEASE_VERSION \"%s\"\n", b); x=0; i=0; z=b; while(1){ if( z[0]>='0' && z[0]<='9' ){ @@ -69,7 +76,21 @@ } } printf("#define RELEASE_RESOURCE_VERSION %s", vx); while( d<3 ){ printf(",0"); d++; } printf("\n"); +#if defined(__DMC__) /* e.g. 0x857 */ + d = (__DMC__ & 0xF00) >> 8; /* major */ + x = (__DMC__ & 0x0F0) >> 4; /* minor */ + i = (__DMC__ & 0x00F); /* revision */ + printf("#define COMPILER_VERSION \"%d.%d.%d\"\n", d, x, i); +#elif defined(__POCC__) /* e.g. 700 */ + d = (__POCC__ / 100); /* major */ + x = (__POCC__ % 100); /* minor */ + printf("#define COMPILER_VERSION \"%d.%02d\"\n", d, x); +#elif defined(_MSC_VER) /* e.g. 1800 */ + d = (_MSC_VER / 100); /* major */ + x = (_MSC_VER % 100); /* minor */ + printf("#define COMPILER_VERSION \"%d.%02d\"\n", d, x); +#endif return 0; } Index: src/moderate.c ================================================================== --- src/moderate.c +++ src/moderate.c @@ -26,28 +26,23 @@ ** Create a table to represent pending moderation requests, if the ** table does not already exist. */ void moderation_table_create(void){ db_multi_exec( - "CREATE TABLE IF NOT EXISTS modreq(\n" + "CREATE TABLE IF NOT EXISTS %s.modreq(\n" " objid INTEGER PRIMARY KEY,\n" /* Record pending approval */ " attachRid INT,\n" /* Object attached */ " tktid TEXT\n" /* Associated ticket id */ - ");\n" + ");\n", db_name("repository") ); } /* ** Return TRUE if the modreq table exists */ int moderation_table_exists(void){ - static int modreqExists = -1; - if( modreqExists<0 ){ - modreqExists = db_exists("SELECT 1 FROM %s.sqlite_master" - " WHERE name='modreq'", db_name("repository")); - } - return modreqExists; + return db_table_exists("repository", "modreq"); } /* ** Return TRUE if the object specified is being held for moderation. */ @@ -64,20 +59,20 @@ /* ** Check to see if the object identified by RID is used for anything. */ static int object_used(int rid){ - static const char *aTabField[] = { + static const char *const aTabField[] = { "modreq", "attachRid", "mlink", "mid", "mlink", "fid", "tagxref", "srcid", "tagxref", "rid", }; int i; for(i=0; iAll Pending Moderation Requests if( moderation_table_exists() ){ blob_init(&sql, timeline_query_for_www(), -1); - blob_appendf(&sql, + blob_append_sql(&sql, " AND event.objid IN (SELECT objid FROM modreq)" " ORDER BY event.mtime DESC" ); - db_prepare(&q, blob_str(&sql)); - www_print_timeline(&q, 0, 0, 0, 0); + db_prepare(&q, "%s", blob_sql_text(&sql)); + www_print_timeline(&q, 0, 0, 0, 0, 0); db_finalize(&q); } style_footer(); } Index: src/name.c ================================================================== --- src/name.c +++ src/name.c @@ -17,11 +17,11 @@ ** ** This file contains code used to convert user-supplied object names into ** canonical UUIDs. ** ** A user-supplied object name is any unique prefix of a valid UUID but -** not necessarily in canonical form. +** not necessarily in canonical form. */ #include "config.h" #include "name.h" #include @@ -42,19 +42,53 @@ if( z[7]!='-') return 0; if( !fossil_isdigit(z[8]) ) return 0; if( !fossil_isdigit(z[9]) ) return 0; return 1; } + +/* +** Return the RID that is the "root" of the branch that contains +** check-in "rid" if inBranch==0 or the first check-in in the branch +** if inBranch==1. +*/ +int start_of_branch(int rid, int inBranch){ + Stmt q; + int rc; + char *zBr; + zBr = db_text("trunk","SELECT value FROM tagxref" + " WHERE rid=%d AND tagid=%d" + " AND tagtype>0", + rid, TAG_BRANCH); + db_prepare(&q, + "SELECT pid, EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0" + " AND value=%Q AND rid=plink.pid)" + " FROM plink" + " WHERE cid=:cid AND isprim", + TAG_BRANCH, zBr + ); + fossil_free(zBr); + do{ + db_reset(&q); + db_bind_int(&q, ":cid", rid); + rc = db_step(&q); + if( rc!=SQLITE_ROW ) break; + if( inBranch && db_column_int(&q,1)==0 ) break; + rid = db_column_int(&q, 0); + }while( db_column_int(&q, 1)==1 && rid>0 ); + db_finalize(&q); + return rid; +} /* ** Convert a symbolic name into a RID. Acceptable forms: ** ** * SHA1 hash ** * SHA1 hash prefix of at least 4 characters ** * Symbolic Name ** * "tag:" + symbolic name -** * Date or date-time +** * Date or date-time ** * "date:" + Date or date-time ** * symbolic-name ":" date-time ** * "tip" ** ** The following additional forms are available in local checkouts: @@ -64,22 +98,35 @@ ** * "next" ** ** Return the RID of the matching artifact. Or return 0 if the name does not ** match any known object. Or return -1 if the name is ambiguous. ** -** The zType parameter specifies the type of artifact: ci, t, w, e, g. +** The zType parameter specifies the type of artifact: ci, t, w, e, g. ** If zType is NULL or "" or "*" then any type of artifact will serve. +** If zType is "br" then find the first check-in of the named branch +** rather than the last. ** zType is "ci" in most use cases since we are usually searching for ** a check-in. +** +** Note that the input zTag for types "t" and "e" is the SHA1 hash of +** the ticket-change or event-change artifact, not the randomly generated +** hexadecimal identifier assigned to tickets and events. Those identifiers +** live in a separate namespace. */ int symbolic_name_to_rid(const char *zTag, const char *zType){ int vid; int rid = 0; int nTag; int i; + int startOfBranch = 0; - if( zType==0 || zType[0]==0 ) zType = "*"; + if( zType==0 || zType[0]==0 ){ + zType = "*"; + }else if( zType[0]=='b' ){ + zType = "ci"; + startOfBranch = 1; + } if( zTag==0 || zTag[0]==0 ) return 0; /* special keyword: "tip" */ if( fossil_strcmp(zTag, "tip")==0 && (zType[0]=='*' || zType[0]=='c') ){ rid = db_int(0, @@ -93,11 +140,11 @@ /* special keywords: "prev", "previous", "current", and "next" */ if( g.localOpen && (vid=db_lget_int("checkout",0))!=0 ){ if( fossil_strcmp(zTag, "current")==0 ){ rid = vid; - }else if( fossil_strcmp(zTag, "prev")==0 + }else if( fossil_strcmp(zTag, "prev")==0 || fossil_strcmp(zTag, "previous")==0 ){ rid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", vid); }else if( fossil_strcmp(zTag, "next")==0 ){ rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d" " ORDER BY isprim DESC, mtime DESC", vid); @@ -105,19 +152,19 @@ if( rid ) return rid; } /* Date and times */ if( memcmp(zTag, "date:", 5)==0 ){ - rid = db_int(0, + rid = db_int(0, "SELECT objid FROM event" " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'" " ORDER BY mtime DESC LIMIT 1", &zTag[5], zType); return rid; } if( fossil_isdate(zTag) ){ - rid = db_int(0, + rid = db_int(0, "SELECT objid FROM event" " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'" " ORDER BY mtime DESC LIMIT 1", zTag, zType); if( rid) return rid; @@ -124,19 +171,19 @@ } /* Deprecated date & time formats: "local:" + date-time and ** "utc:" + date-time */ if( memcmp(zTag, "local:", 6)==0 ){ - rid = db_int(0, + rid = db_int(0, "SELECT objid FROM event" " WHERE mtime<=julianday(%Q) AND type GLOB '%q'" " ORDER BY mtime DESC LIMIT 1", &zTag[6], zType); return rid; } if( memcmp(zTag, "utc:", 4)==0 ){ - rid = db_int(0, + rid = db_int(0, "SELECT objid FROM event" " WHERE mtime<=julianday('%qz') AND type GLOB '%q'" " ORDER BY mtime DESC LIMIT 1", &zTag[4], zType); return rid; @@ -151,41 +198,18 @@ " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " " AND event.objid=tagxref.rid " " AND event.type GLOB '%q'", &zTag[4], zType ); + if( startOfBranch ) rid = start_of_branch(rid,1); return rid; } - + /* root:TAG -> The origin of the branch */ if( memcmp(zTag, "root:", 5)==0 ){ - Stmt q; - int rc; - char *zBr; rid = symbolic_name_to_rid(zTag+5, zType); - zBr = db_text("trunk","SELECT value FROM tagxref" - " WHERE rid=%d AND tagid=%d" - " AND tagtype>0", - rid, TAG_BRANCH); - db_prepare(&q, - "SELECT pid, EXISTS(SELECT 1 FROM tagxref" - " WHERE tagid=%d AND tagtype>0" - " AND value=%Q AND rid=plink.pid)" - " FROM plink" - " WHERE cid=:cid AND isprim", - TAG_BRANCH, zBr - ); - fossil_free(zBr); - do{ - db_reset(&q); - db_bind_int(&q, ":cid", rid); - rc = db_step(&q); - if( rc!=SQLITE_ROW ) break; - rid = db_column_int(&q, 0); - }while( db_column_int(&q, 1)==1 && rid>0 ); - db_finalize(&q); - return rid; + return start_of_branch(rid, 0); } /* symbolic-name ":" date-time */ nTag = strlen(zTag); for(i=0; i0 " " AND event.objid=tagxref.rid " " AND event.type GLOB '%q'", zTag, zType ); - if( rid>0 ) return rid; + if( rid>0 ){ + if( startOfBranch ) rid = start_of_branch(rid,1); + return rid; + } /* Undocumented: numeric tags get translated directly into the RID */ if( memcmp(zTag, "rid:", 4)==0 ){ zTag += 4; for(i=0; fossil_isdigit(zTag[i]); i++){} if( zTag[i]==0 ){ if( strcmp(zType,"*")==0 ){ rid = atoi(zTag); }else{ - rid = db_int(0, + rid = db_int(0, "SELECT event.objid" " FROM event" " WHERE event.objid=%s" - " AND event.type GLOB '%q'", zTag, zType); + " AND event.type GLOB '%q'", zTag /*safe-for-%s*/, zType); } } } return rid; } - /* ** This routine takes a user-entered UUID which might be in mixed ** case and might only be a prefix of the full UUID and converts it ** into the full-length UUID in canonical form. @@ -278,12 +304,12 @@ ** the name as a tag. If multiple tags match, pick the latest. ** If the input name matches "tag:*" then always resolve as a tag. ** ** If the input is not a tag, then try to match it as an ISO-8601 date ** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date. -** If the input is of the form "date:*" or "localtime:*" or "utc:*" then -** always resolve the name as a date. +** If the input is of the form "date:*" then always resolve the name as +** a date. The forms "utc:*" and "local:" are deprecated. ** ** Return 0 on success. Return 1 if the name cannot be resolved. ** Return 2 name is ambiguous. */ int name_to_uuid(Blob *pName, int iErrPriority, const char *zType){ @@ -310,19 +336,43 @@ ** zName does not resolve, 0 is returned. If it is ambiguous, a ** negative value is returned. On success the rid is returned and ** pUuid (if it is not NULL) is set to the a newly-allocated string, ** the full UUID, which must eventually be free()d by the caller. */ -int name_to_uuid2(char const *zName, const char *zType, char **pUuid){ +int name_to_uuid2(const char *zName, const char *zType, char **pUuid){ int rid = symbolic_name_to_rid(zName, zType); if((rid>0) && pUuid){ *pUuid = db_text(NULL, "SELECT uuid FROM blob WHERE rid=%d", rid); } return rid; } +/* +** name_collisions searches through events, blobs, and tickets for +** collisions of a given UUID based on its length on UUIDs no shorter +** than 4 characters in length. +*/ +int name_collisions(const char *zName){ + int c = 0; /* count of collisions for zName */ + int nLen; /* length of zName */ + nLen = strlen(zName); + if( nLen>=4 && nLen<=UUID_SIZE && validate16(zName, nLen) ){ + c = db_int(0, + "SELECT" + " (SELECT count(*) FROM ticket" + " WHERE tkt_uuid GLOB '%q*') +" + " (SELECT count(*) FROM tag" + " WHERE tagname GLOB 'event-%q*') +" + " (SELECT count(*) FROM blob" + " WHERE uuid GLOB '%q*');", + zName, zName, zName + ); + if( c<2 ) c = 0; + } + return c; +} /* ** COMMAND: test-name-to-id ** ** Convert a name to a full artifact ID. @@ -363,36 +413,33 @@ int rid; if( zName==0 || zName[0]==0 ) return 0; rid = symbolic_name_to_rid(zName, zType); if( rid<0 ){ - fossil_error(1, "ambiguous name: %s", zName); - return 0; + fossil_fatal("ambiguous name: %s", zName); }else if( rid==0 ){ - fossil_error(1, "not found: %s", zName); - return 0; - }else{ - return rid; + fossil_fatal("not found: %s", zName); } + return rid; } int name_to_rid(const char *zName){ return name_to_typed_rid(zName, "*"); } /* ** WEBPAGE: ambiguous ** URL: /ambiguous?name=UUID&src=WEBPAGE -** +** ** The UUID given by the name parameter is ambiguous. Display a page ** that shows all possible choices and let the user select between them. */ void ambiguous_page(void){ Stmt q; - const char *zName = P("name"); + const char *zName = P("name"); const char *zSrc = P("src"); char *z; - + if( zName==0 || zName[0]==0 || zSrc==0 || zSrc[0]==0 ){ fossil_redirect_home(); } style_header("Ambiguous Artifact ID"); @

The artifact id %h(zName) is ambiguous and might @@ -402,16 +449,56 @@ canonical16(z, strlen(z)); db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z); while( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); int rid = db_column_int(&q, 1); - @

  • - @ %S(zUuid) - + @

  • + @ %s(zUuid) - + object_description(rid, 0, 0); + @

  • + } + db_finalize(&q); + db_prepare(&q, + " SELECT tkt_rid, tkt_uuid, title" + " FROM ticket, ticketchng" + " WHERE ticket.tkt_id = ticketchng.tkt_id" + " AND tkt_uuid GLOB '%q*'" + " GROUP BY tkt_uuid" + " ORDER BY tkt_ctime DESC", z); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + const char *zUuid = db_column_text(&q, 1); + const char *zTitle = db_column_text(&q, 2); + @
  • + @ %s(zUuid) - + @

      + @ Ticket + hyperlink_to_uuid(zUuid); + @ - %s(zTitle). + @
      • + object_description(rid, 0, 0); + @
      + @

    • + } + db_finalize(&q); + db_prepare(&q, + "SELECT rid, uuid FROM" + " (SELECT tagxref.rid AS rid, substr(tagname, 7) AS uuid" + " FROM tagxref, tag WHERE tagxref.tagid = tag.tagid" + " AND tagname GLOB 'event-%q*') GROUP BY uuid", z); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + const char* zUuid = db_column_text(&q, 1); + @
    • + @ %s(zUuid) - + @

      • object_description(rid, 0, 0); + @
      @

    • } @ + db_finalize(&q); style_footer(); } /* ** Convert the name in CGI parameter zParamName into a rid and return that @@ -433,10 +520,152 @@ cgi_redirectf("%s/ambiguous/%T?src=%t", g.zTop, zName, g.zPath); rid = 0; } return rid; } + +/* +** Generate a description of artifact "rid" +*/ +void whatis_rid(int rid, int verboseFlag){ + Stmt q; + int cnt; + + /* Basic information about the object. */ + db_prepare(&q, + "SELECT uuid, size, datetime(mtime%s), ipaddr" + " FROM blob, rcvfrom" + " WHERE rid=%d" + " AND rcvfrom.rcvid=blob.rcvid", + timeline_utc(), rid); + if( db_step(&q)==SQLITE_ROW ){ + if( verboseFlag ){ + fossil_print("artifact: %s (%d)\n", db_column_text(&q,0), rid); + fossil_print("size: %d bytes\n", db_column_int(&q,1)); + fossil_print("received: %s from %s\n", + db_column_text(&q, 2), + db_column_text(&q, 3)); + }else{ + fossil_print("artifact: %s\n", db_column_text(&q,0)); + fossil_print("size: %d bytes\n", db_column_int(&q,1)); + } + } + db_finalize(&q); + + /* Report any symbolic tags on this artifact */ + db_prepare(&q, + "SELECT substr(tagname,5)" + " FROM tag JOIN tagxref ON tag.tagid=tagxref.tagid" + " WHERE tagxref.rid=%d" + " AND tagname GLOB 'sym-*'" + " ORDER BY 1", + rid + ); + cnt = 0; + while( db_step(&q)==SQLITE_ROW ){ + const char *zPrefix = cnt++ ? ", " : "tags: "; + fossil_print("%s%s", zPrefix, db_column_text(&q,0)); + } + if( cnt ) fossil_print("\n"); + db_finalize(&q); + + /* Report any HIDDEN, PRIVATE, CLUSTER, or CLOSED tags on this artifact */ + db_prepare(&q, + "SELECT tagname" + " FROM tag JOIN tagxref ON tag.tagid=tagxref.tagid" + " WHERE tagxref.rid=%d" + " AND tag.tagid IN (5,6,7,9)" + " ORDER BY 1", + rid + ); + cnt = 0; + while( db_step(&q)==SQLITE_ROW ){ + const char *zPrefix = cnt++ ? ", " : "raw-tags: "; + fossil_print("%s%s", zPrefix, db_column_text(&q,0)); + } + if( cnt ) fossil_print("\n"); + db_finalize(&q); + + /* Check for entries on the timeline that reference this object */ + db_prepare(&q, + "SELECT type, datetime(mtime%s)," + " coalesce(euser,user), coalesce(ecomment,comment)" + " FROM event WHERE objid=%d", timeline_utc(), rid); + if( db_step(&q)==SQLITE_ROW ){ + const char *zType; + switch( db_column_text(&q,0)[0] ){ + case 'c': zType = "Check-in"; break; + case 'w': zType = "Wiki-edit"; break; + case 'e': zType = "Event"; break; + case 't': zType = "Ticket-change"; break; + case 'g': zType = "Tag-change"; break; + default: zType = "Unknown"; break; + } + fossil_print("type: %s by %s on %s\n", zType, db_column_text(&q,2), + db_column_text(&q, 1)); + fossil_print("comment: "); + comment_print(db_column_text(&q,3), 0, 12, -1, g.comFmtFlags); + } + db_finalize(&q); + + /* Check to see if this object is used as a file in a check-in */ + db_prepare(&q, + "SELECT filename.name, blob.uuid, datetime(event.mtime%s)," + " coalesce(euser,user), coalesce(ecomment,comment)" + " FROM mlink, filename, blob, event" + " WHERE mlink.fid=%d" + " AND filename.fnid=mlink.fnid" + " AND event.objid=mlink.mid" + " AND blob.rid=mlink.mid" + " ORDER BY event.mtime DESC /*sort*/", + timeline_utc(), rid); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("file: %s\n", db_column_text(&q,0)); + fossil_print(" part of [%S] by %s on %s\n", + db_column_text(&q, 1), + db_column_text(&q, 3), + db_column_text(&q, 2)); + fossil_print(" "); + comment_print(db_column_text(&q,4), 0, 12, -1, g.comFmtFlags); + } + db_finalize(&q); + + /* Check to see if this object is used as an attachment */ + db_prepare(&q, + "SELECT attachment.filename," + " attachment.comment," + " attachment.user," + " datetime(attachment.mtime%s)," + " attachment.target," + " CASE WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('tkt-'||target))" + " THEN 'ticket'" + " WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('wiki-'||target))" + " THEN 'wiki' END," + " attachment.attachid," + " (SELECT uuid FROM blob WHERE rid=attachid)" + " FROM attachment JOIN blob ON attachment.src=blob.uuid" + " WHERE blob.rid=%d", + timeline_utc(), rid + ); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("attachment: %s\n", db_column_text(&q,0)); + fossil_print(" attached to %s %s\n", + db_column_text(&q,5), db_column_text(&q,4)); + if( verboseFlag ){ + fossil_print(" via %s (%d)\n", + db_column_text(&q,7), db_column_int(&q,6)); + }else{ + fossil_print(" via %s\n", + db_column_text(&q,7)); + } + fossil_print(" by user %s on %s\n", + db_column_text(&q,2), db_column_text(&q,3)); + fossil_print(" "); + comment_print(db_column_text(&q,1), 0, 12, -1, g.comFmtFlags); + } + db_finalize(&q); +} /* ** COMMAND: whatis* ** Usage: %fossil whatis NAME ** @@ -446,84 +675,463 @@ */ void whatis_cmd(void){ int rid; const char *zName; int verboseFlag; + int i; + const char *zType = 0; db_find_and_open_repository(0,0); verboseFlag = find_option("verbose","v",0)!=0; - if( g.argc!=3 ) usage("whatis NAME"); - zName = g.argv[2]; - rid = symbolic_name_to_rid(zName, 0); - if( rid<0 ){ - fossil_print("Ambiguous artifact name prefix: %s\n", zName); - }else if( rid==0 ){ - fossil_print("Unknown artifact: %s\n", zName); - }else{ - Stmt q; - db_prepare(&q, - "SELECT uuid, size, datetime(mtime, 'localtime'), ipaddr," - " (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref" - " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" - " AND tagxref.rid=blob.rid AND tagxref.tagtype>0)" - " FROM blob, rcvfrom" - " WHERE rid=%d" - " AND rcvfrom.rcvid=blob.rcvid", - rid); - if( db_step(&q)==SQLITE_ROW ){ - const char *zTagList = db_column_text(&q, 4); - if( verboseFlag ){ - fossil_print("artifact: %s (%d)\n", db_column_text(&q,0), rid); - fossil_print("size: %d bytes\n", db_column_int(&q,1)); - fossil_print("received: %s from %s\n", - db_column_text(&q, 2), - db_column_text(&q, 3)); - }else{ - fossil_print("artifact: %s\n", db_column_text(&q,0)); - fossil_print("size: %d bytes\n", db_column_int(&q,1)); - } - if( zTagList && zTagList[0] ){ - fossil_print("tags: %s\n", zTagList); - } - } - db_finalize(&q); - db_prepare(&q, - "SELECT type, datetime(mtime,'localtime')," - " coalesce(euser,user), coalesce(ecomment,comment)" - " FROM event WHERE objid=%d", rid); - if( db_step(&q)==SQLITE_ROW ){ - const char *zType; - switch( db_column_text(&q,0)[0] ){ - case 'c': zType = "Check-in"; break; - case 'w': zType = "Wiki-edit"; break; - case 'e': zType = "Event"; break; - case 't': zType = "Ticket-change"; break; - case 'g': zType = "Tag-change"; break; - default: zType = "Unknown"; break; - } - fossil_print("type: %s by %s on %s\n", zType, db_column_text(&q,2), - db_column_text(&q, 1)); - fossil_print("comment: "); - comment_print(db_column_text(&q,3), 10, 78); - } - db_finalize(&q); - db_prepare(&q, - "SELECT filename.name, blob.uuid, datetime(event.mtime,'localtime')," - " coalesce(euser,user), coalesce(ecomment,comment)" - " FROM mlink, filename, blob, event" - " WHERE mlink.fid=%d" - " AND filename.fnid=mlink.fnid" - " AND event.objid=mlink.mid" - " AND blob.rid=mlink.mid" - " ORDER BY event.mtime DESC /*sort*/", - rid); - while( db_step(&q)==SQLITE_ROW ){ - fossil_print("file: %s\n", db_column_text(&q,0)); - fossil_print(" part of [%.10s] by %s on %s\n", - db_column_text(&q, 1), - db_column_text(&q, 3), - db_column_text(&q, 2)); - fossil_print(" "); - comment_print(db_column_text(&q,4), 10, 78); - } - db_finalize(&q); - } + zType = find_option("type",0,1); + + /* We should be done with options.. */ + verify_all_options(); + + if( g.argc<3 ) usage("whatis NAME ..."); + for(i=2; i2 ) fossil_print("%.79c\n",'-'); + rid = symbolic_name_to_rid(zName, zType); + if( rid<0 ){ + Stmt q; + int cnt = 0; + fossil_print("name: %s (ambiguous)\n", zName); + db_prepare(&q, + "SELECT rid FROM blob WHERE uuid>=lower(%Q) AND uuid<(lower(%Q)||'z')", + zName, zName + ); + while( db_step(&q)==SQLITE_ROW ){ + if( cnt++ ) fossil_print("%12s---- meaning #%d ----\n", " ", cnt); + whatis_rid(db_column_int(&q, 0), verboseFlag); + } + db_finalize(&q); + }else if( rid==0 ){ + /* 0123456789 12 */ + fossil_print("unknown: %s\n", zName); + }else{ + fossil_print("name: %s\n", zName); + whatis_rid(rid, verboseFlag); + } + } +} + +/* +** COMMAND: test-whatis-all +** Usage: %fossil test-whatis-all +** +** Show "whatis" information about every artifact in the repository +*/ +void test_whatis_all_cmd(void){ + Stmt q; + int cnt = 0; + db_find_and_open_repository(0,0); + db_prepare(&q, "SELECT rid FROM blob ORDER BY rid"); + while( db_step(&q)==SQLITE_ROW ){ + if( cnt++ ) fossil_print("%.79c\n", '-'); + whatis_rid(db_column_int(&q,0), 1); + } + db_finalize(&q); +} + + +/* +** COMMAND: test-ambiguous +** Usage: %fossil test-ambiguous [--minsize N] +** +** Show a list of ambiguous SHA1-hash abbreviations of N characters or +** more where N defaults to 4. Change N to a different value using +** the "--minsize N" command-line option. +*/ +void test_ambiguous_cmd(void){ + Stmt q, ins; + int i; + int minSize = 4; + const char *zMinsize; + char zPrev[100]; + db_find_and_open_repository(0,0); + zMinsize = find_option("minsize",0,1); + if( zMinsize && atoi(zMinsize)>0 ) minSize = atoi(zMinsize); + db_multi_exec("CREATE TEMP TABLE dups(uuid, cnt)"); + db_prepare(&ins,"INSERT INTO dups(uuid) VALUES(substr(:uuid,1,:cnt))"); + db_prepare(&q, + "SELECT uuid FROM blob " + "UNION " + "SELECT substr(tagname,7) FROM tag WHERE tagname GLOB 'event-*' " + "UNION " + "SELECT tkt_uuid FROM ticket " + "ORDER BY 1" + ); + zPrev[0] = 0; + while( db_step(&q)==SQLITE_ROW ){ + const char *zUuid = db_column_text(&q, 0); + for(i=0; zUuid[i]==zPrev[i] && zUuid[i]!=0; i++){} + if( i>=minSize ){ + db_bind_int(&ins, ":cnt", i); + db_bind_text(&ins, ":uuid", zUuid); + db_step(&ins); + db_reset(&ins); + } + sqlite3_snprintf(sizeof(zPrev), zPrev, "%s", zUuid); + } + db_finalize(&ins); + db_finalize(&q); + db_prepare(&q, "SELECT uuid FROM dups ORDER BY length(uuid) DESC, uuid"); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%s\n", db_column_text(&q, 0)); + } + db_finalize(&q); +} + +/* +** Schema for the description table +*/ +static const char zDescTab[] = +@ CREATE TEMP TABLE IF NOT EXISTS description( +@ rid INTEGER PRIMARY KEY, -- RID of the object +@ uuid TEXT, -- SHA1 hash of the object +@ ctime DATETIME, -- Time of creation +@ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts +@ type TEXT, -- file, checkin, wiki, ticket, etc. +@ summary TEXT, -- Summary comment for the object +@ detail TEXT -- File name, check-in comment, etc +@ ); +; + +/* +** Create the description table if it does not already exists. +** Populate fields of this table with descriptions for all artifacts +** whose RID matches the SQL expression in zWhere. +*/ +void describe_artifacts(const char *zWhere){ + db_multi_exec("%s", zDescTab/*safe-for-%s*/); + + /* Describe check-ins */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, event.mtime, 'checkin',\n" + " 'check-in on ' || strftime('%%Y-%%m-%%d %%H:%%M',event.mtime)\n" + " FROM event, blob\n" + " WHERE (event.objid %s) AND event.type='ci'\n" + " AND event.objid=blob.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Describe files */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, event.mtime, 'file', 'file '||filename.name\n" + " FROM mlink, blob, event, filename\n" + " WHERE (mlink.fid %s)\n" + " AND mlink.mid=event.objid\n" + " AND filename.fnid=mlink.fnid\n" + " AND mlink.fid=blob.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Describe tags */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, tagxref.mtime, 'tag',\n" + " 'tag '||substr((SELECT uuid FROM blob WHERE rid=tagxref.rid),1,16)\n" + " FROM tagxref, blob\n" + " WHERE (tagxref.srcid %s) AND tagxref.srcid!=tagxref.rid\n" + " AND tagxref.srcid=blob.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Cluster artifacts */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, tagxref.mtime, 'cluster', 'cluster'\n" + " FROM tagxref, blob\n" + " WHERE (tagxref.rid %s)\n" + " AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='cluster')\n" + " AND blob.rid=tagxref.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Ticket change artifacts */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, tagxref.mtime, 'ticket',\n" + " 'ticket '||substr(tag.tagname,5,21)\n" + " FROM tagxref, tag, blob\n" + " WHERE (tagxref.rid %s)\n" + " AND tag.tagid=tagxref.tagid\n" + " AND tag.tagname GLOB 'tkt-*'" + " AND blob.rid=tagxref.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Wiki edit artifacts */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, tagxref.mtime, 'wiki',\n" + " printf('wiki \"%%s\"',substr(tag.tagname,6))\n" + " FROM tagxref, tag, blob\n" + " WHERE (tagxref.rid %s)\n" + " AND tag.tagid=tagxref.tagid\n" + " AND tag.tagname GLOB 'wiki-*'" + " AND blob.rid=tagxref.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Event edit artifacts */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, tagxref.mtime, 'event',\n" + " 'event '||substr(tag.tagname,7)\n" + " FROM tagxref, tag, blob\n" + " WHERE (tagxref.rid %s)\n" + " AND tag.tagid=tagxref.tagid\n" + " AND tag.tagname GLOB 'event-*'" + " AND blob.rid=tagxref.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Attachments */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, attachment.mtime, 'attach-control',\n" + " 'attachment-control for '||attachment.filename\n" + " FROM attachment, blob\n" + " WHERE (attachment.attachid %s)\n" + " AND blob.rid=attachment.attachid", + zWhere /*safe-for-%s*/ + ); + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, attachment.mtime, 'attachment',\n" + " 'attachment '||attachment.filename\n" + " FROM attachment, blob\n" + " WHERE (blob.rid %s)\n" + " AND blob.rid NOT IN (SELECT rid FROM description)\n" + " AND blob.uuid=attachment.src", + zWhere /*safe-for-%s*/ + ); + + /* Everything else */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,type,summary)\n" + "SELECT blob.rid, blob.uuid," + " CASE WHEN blob.size<0 THEN 'phantom' ELSE '' END,\n" + " 'unknown'\n" + " FROM blob WHERE (blob.rid %s);", + zWhere /*safe-for-%s*/ + ); + + /* Mark private elements */ + db_multi_exec( + "UPDATE description SET isPrivate=1 WHERE rid IN private" + ); +} + +/* +** Print the content of the description table on stdout +*/ +int describe_artifacts_to_stdout(const char *zWhere, const char *zLabel){ + Stmt q; + int cnt = 0; + describe_artifacts(zWhere); + db_prepare(&q, + "SELECT uuid, summary, isPrivate\n" + " FROM description\n" + " ORDER BY ctime, type;" + ); + while( db_step(&q)==SQLITE_ROW ){ + if( zLabel ){ + fossil_print("%s\n", zLabel); + zLabel = 0; + } + fossil_print(" %.16s %s", db_column_text(&q,0), db_column_text(&q,1)); + if( db_column_int(&q,2) ) fossil_print(" (unpublished)"); + fossil_print("\n"); + cnt++; + } + db_finalize(&q); + db_multi_exec("DELETE FROM description;"); + return cnt; +} + +/* +** COMMAND: test-describe-artifacts +** +** Usage: %fossil test-describe-artifacts [--from S] [--count N] +** +** Display a one-line description of every artifact. +*/ +void test_describe_artifacts_cmd(void){ + int iFrom = 0; + int iCnt = 1000000; + const char *z; + char *zRange; + db_find_and_open_repository(0,0); + z = find_option("from",0,1); + if( z ) iFrom = atoi(z); + z = find_option("count",0,1); + if( z ) iCnt = atoi(z); + zRange = mprintf("BETWEEN %d AND %d", iFrom, iFrom+iCnt-1); + describe_artifacts_to_stdout(zRange, 0); +} + +/* +** WEBPAGE: bloblist +** +** Return a page showing all artifacts in the repository. Query parameters: +** +** n=N Show N artifacts +** s=S Start with artifact number S +*/ +void bloblist_page(void){ + Stmt q; + int s = atoi(PD("s","0")); + int n = atoi(PD("n","5000")); + int mx = db_int(0, "SELECT max(rid) FROM blob"); + char *zRange; + + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + style_header("List Of Artifacts"); + if( mx>n && P("s")==0 ){ + int i; + @

      Select a range of artifacts to view:

      + @
        + for(i=1; i<=mx; i+=n){ + @
      • %z(href("%R/bloblist?s=%d&n=%d",i,n)) + @ %d(i)..%d(i+n-1 + } + @
      + style_footer(); + return; + } + if( mx>n ){ + style_submenu_element("Index", "Index", "bloblist"); + } + zRange = mprintf("BETWEEN %d AND %d", s, s+n-1); + describe_artifacts(zRange); + db_prepare(&q, + "SELECT rid, uuid, summary, isPrivate FROM description ORDER BY rid" + ); + @
      + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q,0); + const char *zUuid = db_column_text(&q, 1); + const char *zDesc = db_column_text(&q, 2); + int isPriv = db_column_int(&q,2); + @ + @ + @ + if( isPriv ){ + @ + } + @ + } + @
      %d(rid) %z(href("%R/info/%!S",zUuid))%s(zUuid) %h(zDesc)(unpublished)
      + db_finalize(&q); + style_footer(); +} + +/* +** COMMAND: test-unsent +** +** Usage: %fossil test-unsent +** +** Show all artifacts in the unsent table +*/ +void test_unsent_cmd(void){ + db_find_and_open_repository(0,0); + describe_artifacts_to_stdout("IN unsent", 0); +} + +/* +** COMMAND: test-unclustered +** +** Usage: %fossil test-unclustered +** +** Show all artifacts in the unclustered table +*/ +void test_unclusterd_cmd(void){ + db_find_and_open_repository(0,0); + describe_artifacts_to_stdout("IN unclustered", 0); +} + +/* +** COMMAND: test-phantoms +** +** Usage: %fossil test-phantoms +** +** Show all phantom artifacts +*/ +void test_phatoms_cmd(void){ + db_find_and_open_repository(0,0); + describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0); +} + +/* Maximum number of collision examples to remember */ +#define MAX_COLLIDE 25 + +/* +** WEBPAGE: hash-collisions +** +** Show the number of hash collisions for hash prefixes of various lengths. +*/ +void hash_collisions_webpage(void){ + int i, j, kk; + int nHash = 0; + Stmt q; + char zPrev[UUID_SIZE+1]; + struct { + int cnt; + char *azHit[MAX_COLLIDE]; + char z[UUID_SIZE+1]; + } aCollide[UUID_SIZE+1]; + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + memset(aCollide, 0, sizeof(aCollide)); + memset(zPrev, 0, sizeof(zPrev)); + db_prepare(&q,"SELECT uuid FROM blob ORDER BY 1"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zUuid = db_column_text(&q,0); + int n = db_column_bytes(&q,0); + int i; + nHash++; + for(i=0; zPrev[i] && zPrev[i]==zUuid[i]; i++){} + if( i>0 && i<=UUID_SIZE ){ + if( i>=4 && aCollide[i].cnt + @ LengthInstancesFirst Instance + @ + for(i=1; i<=UUID_SIZE; i++){ + if( aCollide[i].cnt==0 ) continue; + @ %d(i)%d(aCollide[i].cnt)%h(aCollide[i].z) + } + @ + @

      Total number of hashes: %d(nHash)

      + kk = 0; + for(i=UUID_SIZE; i>=4; i--){ + if( aCollide[i].cnt==0 ) continue; + if( aCollide[i].cnt>200 ) break; + kk += aCollide[i].cnt; + if( aCollide[i].cnt<25 ){ + @

      Collisions of length %d(i): + }else{ + @

      First 25 collisions of length %d(i): + } + for(j=0; j + } + } + style_footer(); } Index: src/path.c ================================================================== --- src/path.c +++ src/path.c @@ -13,11 +13,11 @@ ** drh@sqlite.org ** ******************************************************************************* ** ** This file contains code used to trace paths of through the -** directed acyclic graph (DAG) of checkins. +** directed acyclic graph (DAG) of check-ins. */ #include "config.h" #include "path.h" #include @@ -112,11 +112,11 @@ ** Compute the shortest path from iFrom to iTo ** ** If directOnly is true, then use only the "primary" links from parent to ** child. In other words, ignore merges. ** -** Return a pointer to the beginning of the path (the iFrom node). +** Return a pointer to the beginning of the path (the iFrom node). ** Elements of the path can be traversed by following the PathNode.u.pTo ** pointer chain. ** ** Return NULL if no path is found. */ @@ -135,25 +135,25 @@ if( iTo==iFrom ){ path.pEnd = path.pStart; return path.pStart; } if( oneWayOnly && directOnly ){ - db_prepare(&s, + db_prepare(&s, "SELECT cid, 1 FROM plink WHERE pid=:pid AND isprim" ); }else if( oneWayOnly ){ - db_prepare(&s, + db_prepare(&s, "SELECT cid, 1 FROM plink WHERE pid=:pid " ); }else if( directOnly ){ - db_prepare(&s, + db_prepare(&s, "SELECT cid, 1 FROM plink WHERE pid=:pid AND isprim " "UNION ALL " "SELECT pid, 0 FROM plink WHERE cid=:pid AND isprim" ); }else{ - db_prepare(&s, + db_prepare(&s, "SELECT cid, 1 FROM plink WHERE pid=:pid " "UNION ALL " "SELECT pid, 0 FROM plink WHERE cid=:pid" ); } @@ -199,11 +199,11 @@ /* ** COMMAND: test-shortest-path ** ** Usage: %fossil test-shortest-path ?--no-merge? VERSION1 VERSION2 ** -** Report the shortest path between two checkins. If the --no-merge flag +** Report the shortest path between two check-ins. If the --no-merge flag ** is used, follow only direct parent-child paths and omit merge links. */ void shortest_path_test_cmd(void){ int iFrom; int iTo; @@ -230,11 +230,11 @@ " WHERE blob.rid=%d AND event.objid=%d AND event.type='ci'", p->rid, p->rid); fossil_print("%4d: %5d %s", n, p->rid, z); fossil_free(z); if( p->u.pTo ){ - fossil_print(" is a %s of\n", + fossil_print(" is a %s of\n", p->u.pTo->fromIsParent ? "parent" : "child"); }else{ fossil_print("\n"); } } @@ -349,15 +349,15 @@ int newName; /* Name of file in next version */ NameChange *pNext; /* List of all name changes */ }; /* -** Compute all file name changes that occur going from checkin iFrom -** to checkin iTo. +** Compute all file name changes that occur going from check-in iFrom +** to check-in iTo. ** ** The number of name changes is written into *pnChng. For each name -** change, two integers are allocated for *piChng. The first is the +** change, two integers are allocated for *piChng. The first is the ** filename.fnid for the original name as seen in check-in iFrom and ** the second is for new name as it is used in check-in iTo. ** ** Space to hold *piChng is obtained from fossil_malloc() and should ** be released by the caller. @@ -382,10 +382,15 @@ int i; /* Loop counter */ Stmt q1; /* Query of name changes */ *pnChng = 0; *aiChng = 0; + if(0==iFrom){ + fossil_fatal("Invalid 'from' RID: 0"); + }else if(0==iTo){ + fossil_fatal("Invalid 'to' RID: 0"); + } if( iFrom==iTo ) return; path_reset(); p = path_shortest(iFrom, iTo, 1, revOk==0); if( p==0 ) return; path_reverse_path(); @@ -509,5 +514,58 @@ fossil_free(aChng); g.argv += 2; g.argc -= 2; } } + +/* Query to extract all rename operations */ +static const char zRenameQuery[] = +@ SELECT +@ datetime(event.mtime), +@ F.name AS old_name, +@ T.name AS new_name, +@ blob.uuid +@ FROM mlink, filename F, filename T, event, blob +@ WHERE coalesce(mlink.pfnid,0)!=0 AND mlink.pfnid!=mlink.fnid +@ AND F.fnid=mlink.pfnid +@ AND T.fnid=mlink.fnid +@ AND event.objid=mlink.mid +@ AND event.type='ci' +@ AND blob.rid=mlink.mid +@ ORDER BY 1 DESC, 2; +; + +/* +** WEBPAGE: test-rename-list +** +** Print a list of all file rename operations throughout history. +** This page is intended for for testing purposes only and may change +** or be discontinued without notice. +*/ +void test_rename_list_page(void){ + Stmt q; + + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + style_header("List Of File Name Changes"); + @

      NB: Experimental Page

      + @ + @ + @ + @ + @ + db_prepare(&q, "%s", zRenameQuery/*safe-for-%s*/); + while( db_step(&q)==SQLITE_ROW ){ + const char *zDate = db_column_text(&q, 0); + const char *zOld = db_column_text(&q, 1); + const char *zNew = db_column_text(&q, 2); + const char *zUuid = db_column_text(&q, 3); + @ + @ + @ + @ + @ + } + @
      Date & TimeOld NameNew NameCheck-in
      %z(href("%R/timeline?c=%t",zDate))%s(zDate)%z(href("%R/finfo?name=%t",zOld))%h(zOld)%z(href("%R/finfo?name=%t",zNew))%h(zNew)%z(href("%R/info/%!S",zUuid))%S(zUuid)
      + db_finalize(&q); + style_footer(); +} Index: src/popen.c ================================================================== --- src/popen.c +++ src/popen.c @@ -27,10 +27,13 @@ ** Print a fatal error and quit. */ static void win32_fatal_error(const char *zMsg){ fossil_fatal("%s", zMsg); } +#else +#include +#include #endif /* ** The following macros are used to cast pointers to integers and ** integers to pointers. The way you do this varies from one compiler @@ -171,10 +174,11 @@ close(pout[0]); close(pout[1]); *pChildPid = 0; return 1; } + signal(SIGPIPE,SIG_IGN); if( *pChildPid==0 ){ int fd; int nErr = 0; /* This is the child process */ close(0); @@ -200,18 +204,18 @@ #endif } /* ** Close the connection to a child process previously created using -** popen2(). Kill off the child process, then close the pipes. +** popen2(). */ void pclose2(int fdIn, FILE *pOut, int childPid){ #ifdef _WIN32 /* Not implemented, yet */ close(fdIn); fclose(pOut); #else close(fdIn); fclose(pOut); - kill(childPid, SIGINT); + while( waitpid(0, 0, WNOHANG)>0 ) {} #endif } Index: src/printf.c ================================================================== --- src/printf.c +++ src/printf.c @@ -23,10 +23,48 @@ #if defined(_WIN32) # include # include #endif #include + +/* Two custom conversions are used to show a prefix of SHA1 hashes: +** +** %!S Prefix of a length appropriate for URLs +** %S Prefix of a length appropriate for human display +** +** The following macros help determine those lengths. FOSSIL_HASH_DIGITS +** is the default number of digits to display to humans. This value can +** be overridden using the hash-digits setting. FOSSIL_HASH_DIGITS_URL +** is the minimum number of digits to be used in URLs. The number used +** will always be at least 6 more than the number used for human output, +** or 40 if the number of digits in human output is 34 or more. +*/ +#ifndef FOSSIL_HASH_DIGITS +# define FOSSIL_HASH_DIGITS 10 /* For %S (human display) */ +#endif +#ifndef FOSSIL_HASH_DIGITS_URL +# define FOSSIL_HASH_DIGITS_URL 16 /* For %!S (embedded in URLs) */ +#endif + +/* +** Return the number of SHA1 hash digits to display. The number is for +** human output if the bForUrl is false and is destined for a URL if +** bForUrl is false. +*/ +static int hashDigits(int bForUrl){ + static int nDigitHuman = 0; + static int nDigitUrl = 0; + if( nDigitHuman==0 ){ + nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS); + if( nDigitHuman < 6 ) nDigitHuman = 6; + if( nDigitHuman > 40 ) nDigitHuman = 40; + nDigitUrl = nDigitHuman + 6; + if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL; + if( nDigitUrl > 40 ) nDigitUrl = 40; + } + return bForUrl ? nDigitUrl : nDigitHuman; +} /* ** Conversion types fall into various categories as defined by the ** following enumeration. */ @@ -44,19 +82,20 @@ #define etBLOB 11 /* Blob objects. %b */ #define etBLOBSQL 12 /* Blob objects quoted for SQL. %B */ #define etSQLESCAPE 13 /* Strings with '\'' doubled. %q */ #define etSQLESCAPE2 14 /* Strings with '\'' doubled and enclosed in '', NULL pointers replaced by SQL NULL. %Q */ -#define etPOINTER 15 /* The %p conversion */ -#define etHTMLIZE 16 /* Make text safe for HTML */ -#define etHTTPIZE 17 /* Make text safe for HTTP. "/" encoded as %2f */ -#define etURLIZE 18 /* Make text safe for HTTP. "/" not encoded */ -#define etFOSSILIZE 19 /* The fossil header encoding format. */ -#define etPATH 20 /* Path type */ -#define etWIKISTR 21 /* Timeline comment text rendered from a char*: %w */ +#define etSQLESCAPE3 15 /* Double '"' characters within an indentifier. %w */ +#define etPOINTER 16 /* The %p conversion */ +#define etHTMLIZE 17 /* Make text safe for HTML */ +#define etHTTPIZE 18 /* Make text safe for HTTP. "/" encoded as %2f */ +#define etURLIZE 19 /* Make text safe for HTTP. "/" not encoded */ +#define etFOSSILIZE 20 /* The fossil header encoding format. */ +#define etPATH 21 /* Path type */ +#define etWIKISTR 22 /* Timeline comment text rendered from a char*: %W */ #define etSTRINGID 23 /* String with length limit for a UUID prefix: %S */ -#define etROOT 24 /* String value of g.zTop: % */ +#define etROOT 24 /* String value of g.zTop: %R */ /* ** An "etByte" is an 8-bit unsigned value. */ @@ -96,15 +135,16 @@ { 'z', 0, 6, etDYNSTRING, 0, 0 }, { 'q', 0, 4, etSQLESCAPE, 0, 0 }, { 'Q', 0, 4, etSQLESCAPE2, 0, 0 }, { 'b', 0, 2, etBLOB, 0, 0 }, { 'B', 0, 2, etBLOBSQL, 0, 0 }, - { 'w', 0, 2, etWIKISTR, 0, 0 }, + { 'W', 0, 2, etWIKISTR, 0, 0 }, { 'h', 0, 4, etHTMLIZE, 0, 0 }, { 'R', 0, 0, etROOT, 0, 0 }, { 't', 0, 4, etHTTPIZE, 0, 0 }, /* "/" -> "%2F" */ { 'T', 0, 4, etURLIZE, 0, 0 }, /* "/" unchanged */ + { 'w', 0, 4, etSQLESCAPE3, 0, 0 }, { 'F', 0, 4, etFOSSILIZE, 0, 0 }, { 'S', 0, 4, etSTRINGID, 0, 0 }, { 'c', 0, 0, etCHARX, 0, 0 }, { 'o', 8, 0, etRADIX, 0, 2 }, { 'u', 10, 0, etRADIX, 0, 0 }, @@ -166,12 +206,12 @@ /* ** Return an appropriate set of flags for wiki_convert() for displaying ** comments on a timeline. These flag settings are determined by ** configuration parameters. ** -** The altForm2 argument is true for "%!w" (with the "!" alternate-form-2 -** flags) and is false for plain "%w". The ! indicates that the text is +** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2 +** flags) and is false for plain "%W". The ! indicates that the text is ** to be rendered on a form rather than the timeline and that block markup ** is acceptable even if the "timeline-block-markup" setting is false. */ static int wiki_convert_flags(int altForm2){ static int wikiFlags = 0; @@ -608,22 +648,21 @@ case etROOT: { bufpt = g.zTop ? g.zTop : ""; length = (int)strlen(bufpt); break; } - case etSTRINGID: { - precision = 16; - /* Fall through */ - } + case etSTRINGID: case etSTRING: case etDYNSTRING: { int limit = flag_alternateform ? va_arg(ap,int) : -1; bufpt = va_arg(ap,char*); if( bufpt==0 ){ bufpt = ""; }else if( xtype==etDYNSTRING ){ zExtra = bufpt; + }else if( xtype==etSTRINGID ){ + precision = hashDigits(flag_altform2); } length = StrNLen32(bufpt, limit); if( precision>=0 && precisionetBUFSIZE ){ bufpt = zExtra = fossil_malloc( n ); }else{ bufpt = buf; } j = 0; - if( needQuote ) bufpt[j++] = '\''; + if( needQuote ) bufpt[j++] = q; for(i=0; i=0 && precisiontm_year+1900, pNow->tm_mon+1, pNow->tm_mday+1, @@ -934,11 +979,15 @@ va_start(ap, zFormat); vfprintf(out, zFormat, ap); fprintf(out, "\n"); va_end(ap); for(i=0; i + +/* +** COMMAND: unpublished +** +** Usage: %fossil unpublished ?OPTIONS? +** +** Show a list of unpublished or "private" artifacts. Unpublished artifacts +** will never push and hence will not be shared with collaborators. +** +** By default, this command only shows unpublished check-ins. To show +** all unpublished artifacts, use the --all command-line option. +** +** OPTIONS: +** --all Show all artifacts, not just check-ins +*/ +void unpublished_cmd(void){ + int bAll = find_option("all",0,0)!=0; + + db_find_and_open_repository(0,0); + verify_all_options(); + if( bAll ){ + describe_artifacts_to_stdout("IN private", 0); + }else{ + describe_artifacts_to_stdout( + "IN (SELECT rid FROM private CROSS JOIN event" + " WHERE private.rid=event.objid" + " AND event.type='ci')", 0); + } +} + +/* +** COMMAND: publish +** +** Usage: %fossil publish ?--only? TAGS... +** +** Cause artifacts identified by TAGS... to be published (made non-private). +** This can be used (for example) to convert a private branch into a public +** branch, or to publish a bundle that was imported privately. +** +** If any of TAGS names a branch, then all check-ins on the most recent +** instance of that branch are included, not just the most recent check-in. +** +** If any of TAGS name check-ins then all files and tags associated with +** those check-ins are also published automatically. Except if the --only +** option is used, then only the specific artifacts identified by TAGS +** are published. +** +** If a TAG is already public, this command is a harmless no-op. +*/ +void publish_cmd(void){ + int bOnly = find_option("only",0,0)!=0; + int bTest = find_option("test",0,0)!=0; /* Undocumented --test option */ + int bExclusive = find_option("exclusive",0,0)!=0; /* undocumented */ + int i; + + db_find_and_open_repository(0,0); + verify_all_options(); + if( g.argc<3 ) usage("?--only? TAGS..."); + db_begin_transaction(); + db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY);"); + for(i=2; i0 AND value=%Q", + rid,TAG_BRANCH,g.argv[i]) ){ + rid = start_of_branch(rid, 1); + compute_descendants(rid, 1000000000); + }else{ + db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", rid); + } + } + if( !bOnly ){ + find_checkin_associates("ok", bExclusive); + } + if( bTest ){ + /* If the --test option is used, then do not actually publish any + ** artifacts. Instead, just list the artifact information on standard + ** output. The --test option is useful for verifying correct operation + ** of the logic that figures out which artifacts to publish, such as + ** the find_checkin_associates() routine + */ + describe_artifacts_to_stdout("IN ok", 0); + }else{ + /* Standard behavior is simply to remove the published documents from + ** the PRIVATE table */ + db_multi_exec( + "DELETE FROM ok WHERE rid NOT IN private;" + "DELETE FROM private WHERE rid IN ok;" + "INSERT OR IGNORE INTO unsent SELECT rid FROM ok;" + "INSERT OR IGNORE INTO unclustered SELECT rid FROM ok;" + ); + } + db_end_transaction(0); +} ADDED src/purge.c Index: src/purge.c ================================================================== --- /dev/null +++ src/purge.c @@ -0,0 +1,582 @@ +/* +** Copyright (c) 2014 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code used to implement the "purge" command and +** related functionality for removing check-ins from a repository. It also +** manages the graveyard of purged content. +*/ +#include "config.h" +#include "purge.h" +#include + +/* +** SQL code used to initialize the schema of a bundle. +** +** The purgeevent table contains one entry for each purge event. For each +** purge event, multiple artifacts might have been removed. Each removed +** artifact is stored as an entry in the purgeitem table. +** +** The purgeevent and purgeitem tables are not synced, even by the +** "fossil config" command. They exist only as a backup in case of a +** mistaken purge or for content recovery in case there is a bug in the +** purge command. +*/ +static const char zPurgeInit[] = +@ CREATE TABLE IF NOT EXISTS "%w".purgeevent( +@ peid INTEGER PRIMARY KEY, -- Unique ID for the purge event +@ ctime DATETIME, -- When purge occurred. Seconds since 1970. +@ pnotes TEXT -- Human-readable notes about the purge event +@ ); +@ CREATE TABLE IF NOT EXISTS "%w".purgeitem( +@ piid INTEGER PRIMARY KEY, -- ID for the purge item +@ peid INTEGER REFERENCES purgeevent ON DELETE CASCADE, -- Purge event +@ orid INTEGER, -- Original RID before purged +@ uuid TEXT NOT NULL, -- SHA1 hash of the purged artifact +@ srcid INTEGER, -- Basis purgeitem for delta compression +@ isPrivate BOOLEAN, -- True if artifact was originally private +@ sz INT NOT NULL, -- Uncompressed size of the purged artifact +@ desc TEXT, -- Brief description of this artifact +@ data BLOB -- Compressed artifact content +@ ); +; + +/* +** This routine purges multiple artifacts from the repository, transfering +** those artifacts into the PURGEITEM table. +** +** Prior to invoking this routine, the caller must create a (TEMP) table +** named zTab that contains the RID of every artifact to be purged. +** +** This routine does the following: +** +** (1) Create the purgeevent and purgeitem tables, if required +** (2) Create a new purgeevent +** (3) Make sure no DELTA table entries depend on purged artifacts +** (4) Create new purgeitem entries for each purged artifact +** (5) Remove purged artifacts from the BLOB table +** (6) Remove references to purged artifacts in the following tables: +** (a) EVENT +** (b) PRIVATE +** (c) MLINK +** (d) PLINK +** (e) LEAF +** (f) UNCLUSTERED +** (g) UNSENT +** (h) BACKLINK +** (i) ATTACHMENT +** (j) TICKETCHNG +** (7) If any ticket artifacts were removed (6j) then rebuild the +** corresponding ticket entries. Possibly remove entries from +** the ticket table. +** +** Stops 1-4 (saving the purged artifacts into the graveyard) are only +** undertaken if the moveToGraveyard flag is true. +*/ +int purge_artifact_list( + const char *zTab, /* TEMP table containing list of RIDS to be purged */ + const char *zNote, /* Text of the purgeevent.pnotes field */ + int moveToGraveyard /* Move purged artifacts into the graveyard */ +){ + int peid = 0; /* New purgeevent ID */ + Stmt q; /* General-use prepared statement */ + char *z; + + assert( g.repositoryOpen ); /* Main database must already be open */ + db_begin_transaction(); + z = sqlite3_mprintf("IN \"%w\"", zTab); + describe_artifacts(z); + sqlite3_free(z); + + /* Make sure we are not removing a manifest that is the baseline of some + ** manifest that is being left behind. This step is not strictly necessary. + ** is is just a safety check. */ + if( purge_baseline_out_from_under_delta(zTab) ){ + fossil_fatal("attempt to purge a baseline manifest without also purging " + "all of its deltas"); + } + + /* Make sure that no delta that is left behind requires a purged artifact + ** as its basis. If such artifacts exist, go ahead and undelta them now. + */ + db_prepare(&q, "SELECT rid FROM delta WHERE srcid IN \"%w\"" + " AND rid NOT IN \"%w\"", zTab, zTab); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + content_undelta(rid); + verify_before_commit(rid); + } + db_finalize(&q); + + /* Construct the graveyard and copy the artifacts to be purged into the + ** graveyard */ + if( moveToGraveyard ){ + db_multi_exec(zPurgeInit /*works-like:"%w%w"*/, + db_name("repository"), db_name("repository")); + db_multi_exec( + "INSERT INTO purgeevent(ctime,pnotes) VALUES(now(),%Q)", zNote + ); + peid = db_last_insert_rowid(); + db_prepare(&q, "SELECT rid FROM delta WHERE rid IN \"%w\"" + " AND srcid NOT IN \"%w\"", zTab, zTab); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + content_undelta(rid); + } + db_finalize(&q); + db_multi_exec( + "INSERT INTO purgeitem(peid,orid,uuid,sz,isPrivate,desc,data)" + " SELECT %d, rid, uuid, size," + " EXISTS(SELECT 1 FROM private WHERE private.rid=blob.rid)," + " (SELECT summary FROM description WHERE rid=blob.rid)," + " content" + " FROM blob WHERE rid IN \"%w\"", + peid, zTab + ); + db_multi_exec( + "UPDATE purgeitem" + " SET srcid=(SELECT piid FROM purgeitem px, delta" + " WHERE px.orid=delta.srcid" + " AND delta.rid=purgeitem.orid)" + " WHERE peid=%d", + peid + ); + } + + /* Remove the artifacts being purged. Also remove all references to those + ** artifacts from the secondary tables. */ + db_multi_exec("DELETE FROM blob WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM delta WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM delta WHERE srcid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM event WHERE objid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM private WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM mlink WHERE mid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM plink WHERE pid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM plink WHERE cid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM leaf WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM phantom WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM unclustered WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM unsent WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM tagxref" + " WHERE rid IN \"%w\"" + " OR srcid IN \"%w\"" + " OR origid IN \"%w\"", zTab, zTab, zTab); + db_multi_exec("DELETE FROM backlink WHERE srctype=0 AND srcid IN \"%w\"", + zTab); + db_multi_exec( + "CREATE TEMP TABLE \"%w_tickets\" AS" + " SELECT DISTINCT tkt_uuid FROM ticket WHERE tkt_id IN" + " (SELECT tkt_id FROM ticketchng WHERE tkt_rid IN \"%w\")", + zTab, zTab); + db_multi_exec("DELETE FROM ticketchng WHERE tkt_rid IN \"%w\"", zTab); + db_prepare(&q, "SELECT tkt_uuid FROM \"%w_tickets\"", zTab); + while( db_step(&q)==SQLITE_ROW ){ + ticket_rebuild_entry(db_column_text(&q, 0)); + } + db_finalize(&q); + /* db_multi_exec("DROP TABLE \"%w_tickets\"", zTab); */ + + /* Mission accomplished */ + db_end_transaction(0); + return peid; +} + +/* +** The TEMP table named zTab contains RIDs for a set of check-ins. +** +** Check to see if any check-in in zTab is a baseline manifest for some +** delta manifest that is not in zTab. Return true if zTab contains a +** baseline for a delta that is not in zTab. +** +** This is a database integrity preservation check. The check-ins in zTab +** are about to be deleted or otherwise made inaccessible. This routine +** is checking to ensure that purging the check-ins in zTab will not delete +** a baseline manifest out from under a delta. +*/ +int purge_baseline_out_from_under_delta(const char *zTab){ + if( !db_table_has_column("repository","plink","baseid") ){ + /* Skip this check if the current database is an older schema that + ** does not contain the PLINK.BASEID field. */ + return 0; + }else{ + return db_int(0, + "SELECT 1 FROM plink WHERE baseid IN \"%w\" AND cid NOT IN \"%w\"", + zTab, zTab); + } +} + + +/* +** The TEMP table named zTab contains the RIDs for a set of check-in +** artifacts. Expand this set (by adding new entries to zTab) to include +** all other artifacts that are used the set of check-ins in +** the original list. +** +** If the bExclusive flag is true, then the set is only expanded by +** artifacts that are used exclusively by the check-ins in the set. +** When bExclusive is false, then all artifacts used by the check-ins +** are added even if those artifacts are also used by other check-ins +** not in the set. +** +** The "fossil publish" command with the (undocumented) --test and +** --exclusive options can be used for interactiving testing of this +** function. +*/ +void find_checkin_associates(const char *zTab, int bExclusive){ + db_begin_transaction(); + + /* Compute the set of files that need to be added to zTab */ + db_multi_exec("CREATE TEMP TABLE \"%w_files\"(fid INTEGER PRIMARY KEY)",zTab); + db_multi_exec( + "INSERT OR IGNORE INTO \"%w_files\"(fid)" + " SELECT fid FROM mlink WHERE fid!=0 AND mid IN \"%w\"", + zTab, zTab + ); + if( bExclusive ){ + /* But take out all files that are referenced by check-ins not in zTab */ + db_multi_exec( + "DELETE FROM \"%w_files\"" + " WHERE fid IN (SELECT fid FROM mlink" + " WHERE fid IN \"%w_files\"" + " AND mid NOT IN \"%w\")", + zTab, zTab, zTab + ); + } + + /* Compute the set of tags that need to be added to zTag */ + db_multi_exec("CREATE TEMP TABLE \"%w_tags\"(tid INTEGER PRIMARY KEY)",zTab); + db_multi_exec( + "INSERT OR IGNORE INTO \"%w_tags\"(tid)" + " SELECT DISTINCT srcid FROM tagxref WHERE rid in \"%w\" AND srcid!=0", + zTab, zTab + ); + if( bExclusive ){ + /* But take out tags that references some check-ins in zTab and other + ** check-ins not in zTab. The current Fossil implementation never creates + ** such tags, so the following should usually be a no-op. But the file + ** format specification allows such tags, so we should check for them. + */ + db_multi_exec( + "DELETE FROM \"%w_tags\"" + " WHERE tid IN (SELECT srcid FROM tagxref" + " WHERE srcid IN \"%w_tags\"" + " AND rid NOT IN \"%w\")", + zTab, zTab, zTab + ); + } + + /* Transfer the extra artifacts into zTab */ + db_multi_exec( + "INSERT OR IGNORE INTO \"%w\" SELECT fid FROM \"%w_files\";" + "INSERT OR IGNORE INTO \"%w\" SELECT tid FROM \"%w_tags\";" + "DROP TABLE \"%w_files\";" + "DROP TABLE \"%w_tags\";", + zTab, zTab, zTab, zTab, zTab, zTab + ); + + db_end_transaction(0); +} + +/* +** Display the content of a single purge event. +*/ +static void purge_list_event_content(int peid){ + Stmt q; + sqlite3_int64 sz = 0; + db_prepare(&q, "SELECT piid, substr(uuid,1,16), srcid, isPrivate," + " length(data), desc" + " FROM purgeitem WHERE peid=%d", peid); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print(" %5d %s %4s %c %10d %s\n", + db_column_int(&q,0), + db_column_text(&q,1), + db_column_text(&q,2), + db_column_int(&q,3) ? 'P' : ' ', + db_column_int(&q,4), + db_column_text(&q,5)); + sz += db_column_int(&q,4); + } + db_finalize(&q); + fossil_print("%.11c%16s%.8c%10lld\n", ' ', "Total:", ' ', sz); +} + +/* +** Extract the content for purgeitem number piid into a Blob. Return +** the number of errors. +*/ +static int purge_extract_item( + int piid, /* ID of the item to extract */ + Blob *pOut /* Write the content into this blob */ +){ + Stmt q; + int srcid; + Blob h1, h2, x; + static Bag busy; + + db_prepare(&q, "SELECT uuid, srcid, data FROM purgeitem" + " WHERE piid=%d", piid); + if( db_step(&q)!=SQLITE_ROW ){ + db_finalize(&q); + fossil_fatal("missing purge-item %d", piid); + } + if( bag_find(&busy, piid) ) return 1; + srcid = db_column_int(&q, 1); + blob_zero(pOut); + blob_zero(&x); + db_column_blob(&q, 2, &x); + blob_uncompress(&x, pOut); + blob_reset(&x); + if( srcid>0 ){ + Blob baseline, out; + bag_insert(&busy, piid); + purge_extract_item(srcid, &baseline); + blob_zero(&out); + blob_delta_apply(&baseline, pOut, &out); + blob_reset(pOut); + *pOut = out; + blob_reset(&baseline); + } + bag_remove(&busy, piid); + blob_zero(&h1); + db_column_blob(&q, 0, &h1); + sha1sum_blob(pOut, &h2); + if( blob_compare(&h1, &h2)!=0 ){ + fossil_fatal("SHA1 hash mismatch - wanted %s, got %s", + blob_str(&h1), blob_str(&h2)); + } + blob_reset(&h1); + blob_reset(&h2); + db_finalize(&q); + return 0; +} + +/* +** There is a TEMP table ix(piid,srcid) containing a set of purgeitems +** that need to be transferred to the BLOB table. This routine does +** all items that have srcid=iSrc. The pBasis blob holds the content +** of the source document if iSrc>0. +*/ +static void purge_item_resurrect(int iSrc, Blob *pBasis){ + Stmt q; + static Bag busy; + assert( pBasis!=0 || iSrc==0 ); + if( iSrc>0 ){ + if( bag_find(&busy, iSrc) ){ + fossil_fatal("delta loop while uncompressing purged artifacts"); + } + bag_insert(&busy, iSrc); + } + db_prepare(&q, + "SELECT uuid, data, isPrivate, ix.piid" + " FROM ix, purgeitem" + " WHERE ix.srcid=%d" + " AND ix.piid=purgeitem.piid;", + iSrc + ); + while( db_step(&q)==SQLITE_ROW ){ + Blob h1, h2, c1, c2; + int isPriv, rid; + blob_zero(&h1); + db_column_blob(&q, 0, &h1); + blob_zero(&c1); + db_column_blob(&q, 1, &c1); + blob_uncompress(&c1, &c1); + blob_zero(&c2); + if( pBasis ){ + blob_delta_apply(pBasis, &c1, &c2); + blob_reset(&c1); + }else{ + c2 = c1; + } + sha1sum_blob(&c2, &h2); + if( blob_compare(&h1, &h2)!=0 ){ + fossil_fatal("SHA1 hash mismatch - wanted %s, got %s", + blob_str(&h1), blob_str(&h2)); + } + blob_reset(&h2); + isPriv = db_column_int(&q, 2); + rid = content_put_ex(&c2, blob_str(&h1), 0, 0, isPriv); + if( rid==0 ){ + fossil_fatal("%s", g.zErrMsg); + }else{ + if( !isPriv ) content_make_public(rid); + content_get(rid, &c1); + manifest_crosslink(rid, &c1, MC_NO_ERRORS); + } + purge_item_resurrect(db_column_int(&q,3), &c2); + blob_reset(&c2); + } + db_finalize(&q); + if( iSrc>0 ) bag_remove(&busy, iSrc); +} + +/* +** COMMAND: purge +** +** The purge command removes content from a repository and stores that content +** in a "graveyard". The graveyard exists so that content can be recovered +** using the "fossil purge undo" command. +** +** fossil purge cat UUID... +** +** Write the content of one or more artifacts in the graveyard onto +** standard output. +** +** fossil purge ?checkins? TAGS... ?OPTIONS? +** +** Move the check-ins identified by TAGS and all of their descendants +** out of the repository and into the graveyard. The "checkins" +** subcommand keyword is option and can be omitted as long as TAGS +** does not conflict with any other subcommand. +** +** If a TAGS includes a branch name then it means all the check-ins +** on the most recent occurrance of that branch. +** +** --explain Make no changes, but show what would happen. +** --dry-run Make no chances. +** +** fossil purge list|ls ?-l? +** +** Show the graveyard of prior purges. The -l option gives more +** detail in the output. +** +** fossil purge obliterate ID... +** +** Remove one or more purge events from the graveyard. Once a purge +** event is obliterated, it can no longer be undone. +** +** fossil purge undo ID +** +** Restore the content previously removed by purge ID. +** +** SUMMARY: +** fossil purge cat UUID... +** fossil purge [checkins] TAGS... [--explain] +** fossil purge list +** fossil purge obliterate ID... +** fossil purge undo ID +*/ +void purge_cmd(void){ + const char *zSubcmd; + int n; + Stmt q; + if( g.argc<3 ) usage("SUBCOMMAND ?ARGS?"); + zSubcmd = g.argv[2]; + db_find_and_open_repository(0,0); + n = (int)strlen(zSubcmd); + if( strncmp(zSubcmd, "cat", n)==0 ){ + int i, piid; + Blob content; + if( g.argc<4 ) usage("cat UUID..."); + for(i=3; i=g.argc ) usage("[checkin] TAGS... [--explain]"); + db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); + for(; i0 ){ processCnt += incrSize; percent_complete((processCnt*1000)/totalSize); } @@ -433,11 +439,11 @@ /* ** Attempt to convert more full-text blobs into delta-blobs for ** storage efficiency. */ -static void extra_deltification(void){ +void extra_deltification(void){ Stmt q; int topid, previd, rid; int prevfnid, fnid; db_begin_transaction(); db_prepare(&q, @@ -516,21 +522,24 @@ ** Reconstruct the named repository database from the core ** records. Run this command after updating the fossil ** executable in a way that changes the database schema. ** ** Options: +** --analyze Run ANALYZE on the database after rebuilding ** --cluster Compute clusters for unclustered artifacts ** --compress Strive to make the database as small as possible +** --deanalyze Remove ANALYZE tables from the database ** --force Force the rebuild to complete even if errors are seen +** --ifneeded Only do the rebuild if it would change the schema version +** --index Always add in the full-text search index ** --noverify Skip the verification of changes to the BLOB table +** --noindex Always omit the full-text search index ** --pagesize N Set the database pagesize to N. (512..65536 and power of 2) ** --randomize Scan artifacts in a random order +** --stats Show artifact statistics after rebuilding ** --vacuum Run VACUUM on the database after rebuilding -** --deanalyze Remove ANALYZE tables from the database -** --analyze Run ANALYZE on the database after rebuilding ** --wal Set Write-Ahead-Log journalling mode on the database -** --stats Show artifact statistics after rebuilding ** ** See also: deconstruct, reconstruct */ void rebuild_database(void){ int forceFlag; @@ -544,10 +553,14 @@ int runVacuum; int runDeanalyze; int runAnalyze; int runCompress; int showStats; + int runReindex; + int optNoIndex; + int optIndex; + int optIfNeeded; omitVerify = find_option("noverify",0,0)!=0; forceFlag = find_option("force","f",0)!=0; randomizeFlag = find_option("randomize", 0, 0)!=0; doClustering = find_option("cluster", 0, 0)!=0; @@ -555,10 +568,13 @@ runDeanalyze = find_option("deanalyze",0,0)!=0; runAnalyze = find_option("analyze",0,0)!=0; runCompress = find_option("compress",0,0)!=0; zPagesize = find_option("pagesize",0,1); showStats = find_option("stats",0,0)!=0; + optIndex = find_option("index",0,0)!=0; + optNoIndex = find_option("noindex",0,0)!=0; + optIfNeeded = find_option("ifneeded",0,0)!=0; if( zPagesize ){ newPagesize = atoi(zPagesize); if( newPagesize<512 || newPagesize>65536 || (newPagesize&(newPagesize-1))!=0 ){ @@ -574,18 +590,30 @@ usage("?REPOSITORY-FILENAME?"); } db_close(1); db_open_repository(g.zRepositoryName); } + runReindex = search_index_exists(); + if( optIndex ) runReindex = 1; + if( optNoIndex ) runReindex = 0; + if( optIfNeeded && fossil_strcmp(db_get("aux-schema",""),AUX_SCHEMA_MAX)==0 ){ + return; + } + + /* We should be done with options.. */ + verify_all_options(); + db_begin_transaction(); + search_drop_index(); ttyOutput = 1; errCnt = rebuild_db(randomizeFlag, 1, doClustering); reconstruct_private_table(); db_multi_exec( - "REPLACE INTO config(name,value,mtime) VALUES('content-schema','%s',now());" - "REPLACE INTO config(name,value,mtime) VALUES('aux-schema','%s',now());", - CONTENT_SCHEMA, AUX_SCHEMA + "REPLACE INTO config(name,value,mtime) VALUES('content-schema',%Q,now());" + "REPLACE INTO config(name,value,mtime) VALUES('aux-schema',%Q,now());" + "REPLACE INTO config(name,value,mtime) VALUES('rebuilt',%Q,now());", + CONTENT_SCHEMA, AUX_SCHEMA_MAX, get_version() ); if( errCnt && !forceFlag ){ fossil_print( "%d errors. Rolling back changes. Use --force to force a commit.\n", errCnt @@ -606,11 +634,12 @@ db_multi_exec("PRAGMA page_size=%d", newPagesize); runVacuum = 1; } if( runDeanalyze ){ db_multi_exec("DROP TABLE IF EXISTS sqlite_stat1;" - "DROP TABLE IF EXISTS sqlite_stat3;"); + "DROP TABLE IF EXISTS sqlite_stat3;" + "DROP TABLE IF EXISTS sqlite_stat4;"); } if( runAnalyze ){ fossil_print("Analyzing the database... "); fflush(stdout); db_multi_exec("ANALYZE;"); fossil_print("done\n"); @@ -622,12 +651,13 @@ } if( activateWal ){ db_multi_exec("PRAGMA journal_mode=WAL;"); } } + if( runReindex ) search_rebuild_index(); if( showStats ){ - static struct { int idx; const char *zLabel; } aStat[] = { + static const struct { int idx; const char *zLabel; } aStat[] = { { CFTYPE_ANY, "Artifacts:" }, { CFTYPE_MANIFEST, "Manifests:" }, { CFTYPE_CLUSTER, "Clusters:" }, { CFTYPE_CONTROL, "Tags:" }, { CFTYPE_WIKI, "Wikis:" }, @@ -684,11 +714,11 @@ db_close(1); db_open_repository(g.zRepositoryName); } db_begin_transaction(); create_cluster(); - db_end_transaction(0); + db_end_transaction(0); } /* ** COMMAND: test-clusters ** @@ -697,11 +727,11 @@ */ void test_clusters_cmd(void){ Bag pending; Stmt q; int n; - + db_find_and_open_repository(0, 2); bag_init(&pending); db_multi_exec( "CREATE TEMP TABLE xdone(x INTEGER PRIMARY KEY);" "INSERT INTO xdone SELECT rid FROM unclustered;" @@ -719,11 +749,11 @@ db_finalize(&q); while( bag_count(&pending)>0 ){ Manifest *p; int rid = bag_first(&pending); int i; - + bag_remove(&pending, rid); p = manifest_get(rid, CFTYPE_CLUSTER, 0); if( p==0 ){ fossil_fatal("bad cluster: rid=%d", rid); } @@ -787,14 +817,17 @@ int privateOnly = find_option("private",0,0)!=0; int bNeedRebuild = 0; db_find_and_open_repository(OPEN_ANY_SCHEMA, 2); db_close(1); db_open_repository(g.zRepositoryName); + + /* We should be done with options.. */ + verify_all_options(); + if( !bForce ){ Blob ans; char cReply; - blob_zero(&ans); prompt_user( "Scrubbing the repository will permanently delete information.\n" "Changes cannot be undone. Continue (y/N)? ", &ans); cReply = blob_str(&ans)[0]; if( cReply!='y' && cReply!='Y' ){ @@ -815,14 +848,18 @@ "DELETE FROM config WHERE name GLOB 'skin:*';" "DELETE FROM config WHERE name GLOB 'subrepo:*';" ); if( bVerily ){ db_multi_exec( - "DELETE FROM concealed;" - "UPDATE rcvfrom SET ipaddr='unknown';" - "DROP TABLE IF EXISTS accesslog;" - "UPDATE user SET photo=NULL, info='';" + "DELETE FROM concealed;\n" + "UPDATE rcvfrom SET ipaddr='unknown';\n" + "DROP TABLE IF EXISTS accesslog;\n" + "UPDATE user SET photo=NULL, info='';\n" + "DROP TABLE IF EXISTS purgeevent;\n" + "DROP TABLE IF EXISTS purgeitem;\n" + "DROP TABLE IF EXISTS admin_log;\n" + "DROP TABLE IF EXISTS vcache;\n" ); } } if( !bNeedRebuild ){ db_end_transaction(0); @@ -856,25 +893,32 @@ continue; } zUtf8Name = fossil_filename_to_utf8(pEntry->d_name); zSubpath = mprintf("%s/%s", zPath, zUtf8Name); fossil_filename_free(zUtf8Name); - if( file_isdir(zSubpath)==1 ){ +#ifdef _DIRENT_HAVE_D_TYPE + if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK) + ? (file_isdir(zSubpath)==1) : (pEntry->d_type==DT_DIR) ) +#else + if( file_isdir(zSubpath)==1 ) +#endif + { recon_read_dir(zSubpath); - } - blob_init(&path, 0, 0); - blob_appendf(&path, "%s", zSubpath); - if( blob_read_from_file(&aContent, blob_str(&path))==-1 ){ - fossil_fatal("some unknown error occurred while reading \"%s\"", - blob_str(&path)); - } - content_put(&aContent); - blob_reset(&path); - blob_reset(&aContent); - free(zSubpath); - fossil_print("\r%d", ++nFileRead); - fflush(stdout); + }else{ + blob_init(&path, 0, 0); + blob_appendf(&path, "%s", zSubpath); + if( blob_read_from_file(&aContent, blob_str(&path))==-1 ){ + fossil_fatal("some unknown error occurred while reading \"%s\"", + blob_str(&path)); + } + content_put(&aContent); + blob_reset(&path); + blob_reset(&aContent); + fossil_print("\r%d", ++nFileRead); + fflush(stdout); + } + free(zSubpath); } closedir(d); }else { fossil_fatal("encountered error %d while trying to open \"%s\".", errno, g.argv[3]); @@ -903,13 +947,17 @@ fossil_print("\"%s\" is not a directory\n\n", g.argv[3]); usage("FILENAME DIRECTORY"); } db_create_repository(g.argv[2]); db_open_repository(g.argv[2]); + + /* We should be done with options.. */ + verify_all_options(); + db_open_config(0); db_begin_transaction(); - db_initial_setup(0, 0, 0, 1); + db_initial_setup(0, 0, 0); fossil_print("Reading files from directory \"%s\"...\n", g.argv[3]); recon_read_dir(g.argv[3]); fossil_print("\nBuilding the Fossil repository...\n"); @@ -919,11 +967,11 @@ /* Skip the verify_before_commit() step on a reconstruct. Most artifacts ** will have been changed and verification therefore takes a really, really ** long time. */ verify_cancel(); - + db_end_transaction(0); fossil_print("project-id: %s\n", db_get("project-code", 0)); fossil_print("server-id: %s\n", db_get("server-code", 0)); zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); fossil_print("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword); @@ -939,11 +987,11 @@ ** writes all artifacts to the file system. The DESTINATION directory ** will be populated with subdirectories AA and files AA/BBBBBBBBB.., where ** AABBBBBBBBB.. is the 40 character artifact ID, AA the first 2 characters. ** If -L|--prefixlength is given, the length (default 2) of the directory ** prefix can be set to 0,1,..,9 characters. -** +** ** Options: ** -R|--repository REPOSITORY deconstruct given REPOSITORY ** -L|--prefixlength N set the length of the names of the DESTINATION ** subdirectories to N ** --private Include private artifacts. Index: src/regexp.c ================================================================== --- src/regexp.c +++ src/regexp.c @@ -127,11 +127,11 @@ pSet->aState[pSet->nState++] = newState; } /* Extract the next unicode character from *pzIn and return it. Advance ** *pzIn to the first byte past the end of the character returned. To -** be clear: this routine converts utf8 to unicode. This routine is +** be clear: this routine converts utf8 to unicode. This routine is ** optimized for the common case where the next character is a single byte. */ static unsigned re_next_char(ReInput *p){ unsigned c; if( p->i>=p->mx ) return 0; @@ -191,16 +191,16 @@ int rc = 0; ReInput in; in.z = zIn; in.i = 0; - in.mx = nIn>=0 ? nIn : strlen((char const*)zIn); + in.mx = nIn>=0 ? nIn : strlen((const char*)zIn); /* Look for the initial prefix match, if there is one. */ if( pRe->nInit ){ unsigned char x = pRe->zInit[0]; - while( in.i+pRe->nInit<=in.mx + while( in.i+pRe->nInit<=in.mx && (zIn[in.i]!=x || strncmp((const char*)zIn+in.i, (const char*)pRe->zInit, pRe->nInit)!=0) ){ in.i++; } @@ -303,11 +303,11 @@ } } } if( pRe->aOp[x]==RE_OP_CC_EXC ) hit = !hit; if( hit ) re_add_state(pNext, x+n); - break; + break; } } } } for(i=0; inState; i++){ @@ -464,11 +464,11 @@ const char *zErr; while( (c = p->xNextChar(&p->sIn))!=0 ){ iStart = p->nState; switch( c ){ case '|': - case '$': + case '$': case ')': { p->sIn.i--; return 0; } case '(': { @@ -480,11 +480,11 @@ } case '.': { if( rePeek(p)=='*' ){ re_append(p, RE_OP_ANYSTAR, 0); p->sIn.i++; - }else{ + }else{ re_append(p, RE_OP_ANY, 0); } break; } case '*': { @@ -652,11 +652,11 @@ } /* The following is a performance optimization. If the regex begins with ** ".*" (if the input regex lacks an initial "^") and afterwards there are ** one or more matching characters, enter those matching characters into - ** zInit[]. The re_match() routine can then search ahead in the input + ** zInit[]. The re_match() routine can then search ahead in the input ** string looking for the initial match without having to run the whole ** regex engine over the string. Do not worry able trying to match ** unicode characters beyond plane 0 - those are very rare and this is ** just an optimization. */ if( pRe->aOp[0]==RE_OP_ANYSTAR ){ @@ -689,12 +689,12 @@ ** A REGEXP B ** ** is implemented as regexp(B,A). */ static void re_sql_func( - sqlite3_context *context, - int argc, + sqlite3_context *context, + int argc, sqlite3_value **argv ){ ReCompiled *pRe; /* Compiled regular expression */ const char *zPattern; /* The regular expression */ const unsigned char *zStr;/* String being searched */ @@ -768,11 +768,10 @@ */ void re_test_grep(void){ ReCompiled *pRe; const char *zErr; int ignoreCase = find_option("ignore-case","i",0)!=0; - if( g.argc<3 ){ usage("REGEXP [FILE...]"); } zErr = re_compile(&pRe, g.argv[2], ignoreCase); if( zErr ) fossil_fatal("%s", zErr); Index: src/report.c ================================================================== --- src/report.c +++ src/report.c @@ -12,11 +12,11 @@ ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* -** +** ** Code to generate the ticket listings */ #include "config.h" #include #include "report.h" @@ -23,27 +23,37 @@ #include /* Forward references to static routines */ static void report_format_hints(void); +#ifndef SQLITE_RECURSIVE +# define SQLITE_RECURSIVE 33 +#endif + /* ** WEBPAGE: /reportlist +** +** Main menu for Tickets. */ void view_list(void){ const char *zScript; Blob ril; /* Report Item List */ Stmt q; int rn = 0; int cnt = 0; login_check_credentials(); - if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; } + if( !g.perm.RdTkt && !g.perm.NewTkt ){ + login_needed(g.anon.RdTkt || g.anon.NewTkt); + return; + } style_header("Ticket Main Menu"); + ticket_standard_submenu(T_ALL_BUT(T_REPLIST)); if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST
      \n", -1); zScript = ticket_reportlist_code(); if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT
      \n", -1); - + blob_zero(&ril); ticket_init(); db_prepare(&q, "SELECT rn, title, owner FROM reportfmt ORDER BY title"); while( db_step(&q)==SQLITE_ROW ){ @@ -66,14 +76,14 @@ } if( g.perm.TktFmt ){ blob_appendf(&ril, "[%zcopy] ", href("%R/rptedit?rn=%d©=1", rn)); } - if( g.perm.Admin + if( g.perm.Admin || (g.perm.WrTkt && zOwner && fossil_strcmp(g.zLogin,zOwner)==0) ){ - blob_appendf(&ril, "[%zedit]", + blob_appendf(&ril, "[%zedit]", href("%R/rptedit?rn=%d", rn)); } if( g.perm.TktFmt ){ blob_appendf(&ril, "[%zsql]", href("%R/rptsql?rn=%d", rn)); @@ -81,13 +91,13 @@ blob_appendf(&ril, "\n"); } db_finalize(&q); Th_Store("report_items", blob_str(&ril)); - + Th_Render(zScript); - + blob_reset(&ril); if( g.thTrace ) Th_Trace("END_REPORTLIST
      \n", -1); style_footer(); } @@ -167,10 +177,11 @@ /* We've already seen an error. No need to continue. */ return SQLITE_OK; } switch( code ){ case SQLITE_SELECT: + case SQLITE_RECURSIVE: case SQLITE_FUNCTION: { break; } case SQLITE_READ: { static const char *const azAllowed[] = { @@ -183,10 +194,13 @@ "event", "tag", "tagxref", }; int i; + if( fossil_strncmp(zArg1, "fx_", 3)==0 ){ + break; + } for(i=0; i=sizeof(azAllowed)/sizeof(azAllowed[0]) ){ *(char**)pError = mprintf("access to table \"%s\" is restricted",zArg1); @@ -207,11 +221,10 @@ /* ** Activate the query authorizer */ static void report_restrict_sql(char **pzErr){ - (void)fossil_localtime(0); sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)pzErr); } static void report_unrestrict_sql(void){ sqlite3_set_authorizer(g.db, 0, 0); } @@ -229,15 +242,17 @@ const char *zTail; sqlite3_stmt *pStmt; int rc; /* First make sure the SQL is a single query command by verifying that - ** the first token is "SELECT" and that there are no unquoted semicolons. + ** the first token is "SELECT" or "WITH" and that there are no unquoted + ** semicolons. */ for(i=0; fossil_isspace(zSql[i]); i++){} - if( fossil_strnicmp(&zSql[i],"select",6)!=0 ){ - return mprintf("The SQL must be a SELECT statement"); + if( fossil_strnicmp(&zSql[i], "select", 6)!=0 + && fossil_strnicmp(&zSql[i], "with", 4)!=0 ){ + return mprintf("The SQL must be a SELECT or WITH statement"); } for(i=0; zSql[i]; i++){ if( zSql[i]==';' ){ int bad; int c = zSql[i+1]; @@ -251,14 +266,14 @@ return mprintf("Semi-colon detected! " "Only a single SQL statement is allowed"); } } } - + /* Compile the statement and check for illegal accesses or syntax errors. */ report_restrict_sql(&zErr); - rc = sqlite3_prepare(g.db, zSql, -1, &pStmt, &zTail); + rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, &zTail); if( rc!=SQLITE_OK ){ zErr = mprintf("Syntax error: %s", sqlite3_errmsg(g.db)); } if( !sqlite3_stmt_readonly(pStmt) ){ zErr = mprintf("SQL must not modify the database"); @@ -281,20 +296,21 @@ const char *zClrKey; Stmt q; login_check_credentials(); if( !g.perm.TktFmt ){ - login_needed(); + login_needed(g.anon.TktFmt); return; } rn = atoi(PD("rn","0")); db_prepare(&q, "SELECT title, sqlcode, owner, cols " "FROM reportfmt WHERE rn=%d",rn); style_header("SQL For Report Format Number %d", rn); if( db_step(&q)!=SQLITE_ROW ){ @

      Unknown report number: %d(rn)

      style_footer(); + db_finalize(&q); return; } zTitle = db_column_text(&q, 0); zSQL = db_column_text(&q, 1); zOwner = db_column_text(&q, 2); @@ -312,10 +328,11 @@ output_color_key(zClrKey, 0, "border=0 cellspacing=0 cellpadding=3"); @ @ report_format_hints(); style_footer(); + db_finalize(&q); } /* ** WEBPAGE: /rptnew ** WEBPAGE: /rptedit @@ -329,11 +346,11 @@ char *zSQL; char *zErr = 0; login_check_credentials(); if( !g.perm.TktFmt ){ - login_needed(); + login_needed(g.anon.TktFmt); return; } /*view_add_functions(0);*/ rn = atoi(PD("rn","0")); zTitle = P("t"); @@ -372,11 +389,11 @@ } if( zTitle && zSQL ){ if( zSQL[0]==0 ){ zErr = "Please supply an SQL query statement"; }else if( (zTitle = trim_string(zTitle))[0]==0 ){ - zErr = "Please supply a title"; + zErr = "Please supply a title"; }else{ zErr = verify_sql_statement(zSQL); } if( zErr==0 && db_exists("SELECT 1 FROM reportfmt WHERE title=%Q and rn<>%d", @@ -423,11 +440,11 @@ if( zOwner==0 ) zOwner = g.zLogin; style_submenu_element("Cancel", "Cancel", "reportlist"); if( rn>0 ){ style_submenu_element("Delete", "Delete", "rptedit?rn=%d&del1=1", rn); } - style_header(rn>0 ? "Edit Report Format":"Create New Report Format"); + style_header("%s", rn>0 ? "Edit Report Format":"Create New Report Format"); if( zErr ){ @
      %h(zErr)
      } @
      @ @@ -603,23 +620,10 @@ @ description AS '_Description', -- When the column name begins with '_' @ remarks AS '_Remarks' -- content is rendered as wiki @ FROM ticket @
      @ - @

      Or, to see part of the description on the same row, use the - @ wiki() function with some string manipulation. Using the - @ tkt() function on the ticket number will also generate a linked - @ field, but without the extra edit column: - @

      - @
      -  @  SELECT
      -  @    tkt(tn) AS '',
      -  @    title AS 'Title',
      -  @    wiki(substr(description,0,80)) AS 'Description'
      -  @  FROM ticket
      -  @ 
      - @ } /* ** The state of the report generation. */ @@ -639,17 +643,17 @@ ** The callback function for db_query */ static int generate_html( void *pUser, /* Pointer to output state */ int nArg, /* Number of columns in this result row */ - char **azArg, /* Text of data in all columns */ - char **azName /* Names of the columns */ + const char **azArg, /* Text of data in all columns */ + const char **azName /* Names of the columns */ ){ struct GenerateHTML *pState = (struct GenerateHTML*)pUser; int i; const char *zTid; /* Ticket UUID. (value of column named '#') */ - char *zBg = 0; /* Use this background color */ + const char *zBg = 0; /* Use this background color */ /* Do initialization */ if( pState->nCount==0 ){ /* Turn off the authorizer. It is no longer doing anything since the @@ -699,11 +703,11 @@ /* The first time this routine is called, output a table header */ @ zTid = 0; for(i=0; iiBg ) continue; if( pState->iNewRow>=0 && i>=pState->iNewRow ){ if( g.perm.Write && zTid ){ @   zTid = 0; @@ -742,11 +746,11 @@ zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0; if( zBg==0 ) zBg = "white"; @ zTid = 0; for(i=0; iiBg ) continue; zData = azArg[i]; if( zData==0 ) zData = ""; if( pState->iNewRow>=0 && i>=pState->iNewRow ){ if( zTid && g.perm.Write ){ @@ -804,12 +808,12 @@ ** Output a row as a tab-separated line of text. */ static int output_tab_separated( void *pUser, /* Pointer to row-count integer */ int nArg, /* Number of columns in this result row */ - char **azArg, /* Text of data in all columns */ - char **azName /* Names of the columns */ + const char **azArg, /* Text of data in all columns */ + const char **azName /* Names of the columns */ ){ int *pCount = (int*)pUser; int i; if( *pCount==0 ){ @@ -829,18 +833,19 @@ /* ** Generate HTML that describes a color key. */ void output_color_key(const char *zClrKey, int horiz, char *zTabArgs){ int i, j, k; - char *zSafeKey, *zToFree; + const char *zSafeKey; + char *zToFree; while( fossil_isspace(*zClrKey) ) zClrKey++; if( zClrKey[0]==0 ) return; @ if( horiz ){ @ } - zToFree = zSafeKey = mprintf("%h", zClrKey); + zSafeKey = zToFree = mprintf("%h", zClrKey); while( zSafeKey[0] ){ while( fossil_isspace(*zSafeKey) ) zSafeKey++; for(i=0; zSafeKey[i] && !fossil_isspace(zSafeKey[i]); i++){} for(j=i; fossil_isspace(zSafeKey[j]); j++){} for(k=j; zSafeKey[k] && zSafeKey[k]!='\n' && zSafeKey[k]!='\r'; k++){} @@ -862,23 +867,24 @@ /* ** Execute a single read-only SQL statement. Invoke xCallback() on each ** row. */ -int sqlite3_exec_readonly( +static int db_exec_readonly( sqlite3 *db, /* The database on which the SQL executes */ const char *zSql, /* The SQL to be executed */ - sqlite3_callback xCallback, /* Invoke this callback routine */ + int (*xCallback)(void*,int,const char**, const char**), + /* Invoke this callback routine */ void *pArg, /* First argument to xCallback() */ char **pzErrMsg /* Write error messages here */ ){ int rc = SQLITE_OK; /* Return code */ const char *zLeftover; /* Tail of unprocessed SQL */ sqlite3_stmt *pStmt = 0; /* The current SQL statement */ - char **azCols = 0; /* Names of result columns */ + const char **azCols = 0; /* Names of result columns */ int nCol; /* Number of columns of output */ - char **azVals = 0; /* Text of all output columns */ + const char **azVals = 0; /* Text of all output columns */ int i; /* Loop counter */ pStmt = 0; rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); assert( rc==SQLITE_OK || pStmt==0 ); @@ -901,98 +907,159 @@ azVals = fossil_malloc(2*nCol*sizeof(const char*) + 1); while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){ if( azCols==0 ){ azCols = &azVals[nCol]; for(i=0; i - @ function SortableTable(tableEl,columnTypes){ + @ function SortableTable(tableEl,columnTypes,initSort){ @ this.tbody = tableEl.getElementsByTagName('tbody'); + @ this.columnTypes = columnTypes; @ this.sort = function (cell) { @ var column = cell.cellIndex; - @ var sortFn = cell.sortType=="n" ? this.sortNumeric : this.sortText; + @ var sortFn; + @ switch( cell.sortType ){ + @ case "N": case "n": sortFn = this.sortNumeric; break; + @ case "T": case "t": sortFn = this.sortText; break; + @ case "K": case "k": sortFn = this.sortKey; break; + @ default: return; + @ } @ this.sortIndex = column; @ var newRows = new Array(); @ for (j = 0; j < this.tbody[0].rows.length; j++) { @ newRows[j] = this.tbody[0].rows[j]; @ } - @ newRows.sort(sortFn); - @ if (cell.getAttribute("sortdir") == 'down') { - @ newRows.reverse(); - @ cell.setAttribute('sortdir','up'); - @ } else { - @ cell.setAttribute('sortdir','down'); + @ if( this.sortIndex==Math.abs(this.prevColumn)-1 ){ + @ newRows.reverse(); + @ this.prevColumn = -this.prevColumn; + @ }else{ + @ newRows.sort(sortFn); + @ this.prevColumn = this.sortIndex+1; + @ if( cell.sortType>="A" && cell.sortType<="Z" ){ + @ newRows.reverse(); + @ } @ } @ for (i=0;i0)){ @ return; @ } @ if(x && x[0].rows && x[0].rows.length > 0) { - @ var sortRow = x[0].rows[0]; + @ this.hdrRow = x[0].rows[0]; @ } else { @ return; @ } - @ for (var i=0; i } /* @@ -1014,11 +1081,11 @@ Stmt q; char *zErr1 = 0; char *zErr2 = 0; login_check_credentials(); - if( !g.perm.RdTkt ){ login_needed(); return; } + if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; } rn = atoi(PD("rn","0")); if( rn==0 ){ cgi_redirect("reportlist"); return; } @@ -1026,10 +1093,11 @@ /* view_add_functions(tabs); */ db_prepare(&q, "SELECT title, sqlcode, owner, cols FROM reportfmt WHERE rn=%d", rn); if( db_step(&q)!=SQLITE_ROW ){ cgi_redirect("reportlist"); + db_finalize(&q); return; } zTitle = db_column_malloc(&q, 0); zSql = db_column_malloc(&q, 1); zOwner = db_column_malloc(&q, 2); @@ -1054,13 +1122,13 @@ count = 0; if( !tabs ){ struct GenerateHTML sState; db_multi_exec("PRAGMA empty_result_callbacks=ON"); - style_submenu_element("Raw", "Raw", + style_submenu_element("Raw", "Raw", "rptview?tablist=1&%h", PD("QUERY_STRING","")); - if( g.perm.Admin + if( g.perm.Admin || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){ style_submenu_element("Edit", "Edit", "rptedit?rn=%d", rn); } if( g.perm.TktFmt ){ style_submenu_element("SQL", "SQL", "rptsql?rn=%d",rn); @@ -1067,31 +1135,31 @@ } if( g.perm.NewTkt ){ style_submenu_element("New Ticket", "Create a new ticket", "%s/tktnew", g.zTop); } - style_header(zTitle); - output_color_key(zClrKey, 1, + style_header("%s", zTitle); + output_color_key(zClrKey, 1, "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\""); @
      sState.rn = rn; sState.nCount = 0; report_restrict_sql(&zErr1); - sqlite3_exec_readonly(g.db, zSql, generate_html, &sState, &zErr2); + db_exec_readonly(g.db, zSql, generate_html, &sState, &zErr2); report_unrestrict_sql(); @
      if( zErr1 ){ @

      Error: %h(zErr1)

      }else if( zErr2 ){ @

      Error: %h(zErr2)

      } - output_table_sorting_javascript("reportTable",""); + output_table_sorting_javascript("reportTable","",0); style_footer(); }else{ report_restrict_sql(&zErr1); - sqlite3_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2); + db_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2); report_unrestrict_sql(); cgi_set_content_type("text/plain"); } } @@ -1109,21 +1177,19 @@ ** show all reports, which can be used for ticket show. ** Output is written to stdout as tab delimited table */ void rpt_list_reports(void){ Stmt q; - char const aRptOutFrmt[] = "%s\t%s\n"; - fossil_print("Available reports:\n"); - fossil_print(aRptOutFrmt,"report number","report title"); - fossil_print(aRptOutFrmt,zFullTicketRptRn,zFullTicketRptTitle); + fossil_print("%s\t%s\n","report number","report title"); + fossil_print("%s\t%s\n",zFullTicketRptRn,zFullTicketRptTitle); db_prepare(&q,"SELECT rn,title FROM reportfmt ORDER BY rn"); while( db_step(&q)==SQLITE_ROW ){ const char *zRn = db_column_text(&q, 0); const char *zTitle = db_column_text(&q, 1); - fossil_print(aRptOutFrmt,zRn,zTitle); + fossil_print("%s\t%s\n",zRn,zTitle); } db_finalize(&q); } /* @@ -1166,22 +1232,22 @@ if( j>i ){ fossil_print("%*s", j-i, ""); } z += j; } - break; + break; } } /* ** Output a row as a tab-separated line of text. */ int output_separated_file( void *pUser, /* Pointer to row-count integer */ int nArg, /* Number of columns in this result row */ - char **azArg, /* Text of data in all columns */ - char **azName /* Names of the columns */ + const char **azArg, /* Text of data in all columns */ + const char **azName /* Names of the columns */ ){ int *pCount = (int*)pUser; int i; if( *pCount==0 ){ @@ -1201,11 +1267,11 @@ /* ** Generate a report. The rn query parameter is the report number. ** The output is written to stdout as flat file. The zFilter parameter ** is a full WHERE-condition. */ -void rptshow( +void rptshow( const char *zRep, const char *zSepIn, const char *zFilter, tTktShowEncoding enc ){ @@ -1214,11 +1280,11 @@ char *zErr1 = 0; char *zErr2 = 0; int count = 0; int rn; - if (!zRep || !strcmp(zRep,zFullTicketRptRn) || !strcmp(zRep,zFullTicketRptTitle) ){ + if( !zRep || !strcmp(zRep,zFullTicketRptRn) || !strcmp(zRep,zFullTicketRptTitle) ){ zSql = "SELECT * FROM ticket"; }else{ rn = atoi(zRep); if( rn ){ db_prepare(&q, @@ -1240,11 +1306,11 @@ } count = 0; tktEncode = enc; zSep = zSepIn; report_restrict_sql(&zErr1); - sqlite3_exec_readonly(g.db, zSql, output_separated_file, &count, &zErr2); + db_exec_readonly(g.db, zSql, output_separated_file, &count, &zErr2); report_unrestrict_sql(); if( zFilter ){ free(zSql); } } Index: src/rss.c ================================================================== --- src/rss.c +++ src/rss.c @@ -26,12 +26,14 @@ ** WEBPAGE: timeline.rss ** URL: /timeline.rss?y=TYPE&n=LIMIT&tkt=UUID&tag=TAG&wiki=NAME&name=FILENAME ** ** Produce an RSS feed of the timeline. ** -** TYPE may be: all, ci (show checkins only), t (show tickets only), -** w (show wiki only). LIMIT is the number of items to show. +** TYPE may be: all, ci (show check-ins only), t (show tickets only), +** w (show wiki only). +** +** LIMIT is the number of items to show. ** ** tkt=UUID filters for only those events for the specified ticket. tag=TAG ** filters for a tag, and wiki=NAME for a wiki page. Only one may be used. ** ** In addition, name=FILENAME filters for a specific file. This may be @@ -63,11 +65,11 @@ @ FROM event, blob @ WHERE blob.rid=event.objid ; login_check_credentials(); - if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){ + if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){ return; } blob_zero(&bSQL); blob_append( &bSQL, zSQL1, -1 ); @@ -74,18 +76,18 @@ if( zType[0]!='a' ){ if( zType[0]=='c' && !g.perm.Read ) zType = "x"; if( zType[0]=='w' && !g.perm.RdWiki ) zType = "x"; if( zType[0]=='t' && !g.perm.RdTkt ) zType = "x"; - blob_appendf(&bSQL, " AND event.type=%Q", zType); + blob_append_sql(&bSQL, " AND event.type=%Q", zType); }else{ if( !g.perm.Read ){ if( g.perm.RdTkt && g.perm.RdWiki ){ blob_append(&bSQL, " AND event.type!='ci'", -1); }else if( g.perm.RdTkt ){ blob_append(&bSQL, " AND event.type=='t'", -1); - + }else{ blob_append(&bSQL, " AND event.type=='w'", -1); } }else if( !g.perm.RdWiki ){ if( g.perm.RdTkt ){ @@ -120,18 +122,18 @@ }else{ nTagId = 0; } if( nTagId==-1 ){ - blob_appendf(&bSQL, " AND 0"); + blob_append_sql(&bSQL, " AND 0"); }else if( nTagId!=0 ){ - blob_appendf(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref" + blob_append_sql(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref" " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId); } if( zFilename ){ - blob_appendf(&bSQL, + blob_append_sql(&bSQL, " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)", zFilename, filename_collation() ); } @@ -158,13 +160,13 @@ @ %s(g.zBaseURL) @ %h(zProjectDescr) @ %s(zPubDate) @ Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE) free(zPubDate); - db_prepare(&q, blob_str(&bSQL)); + db_prepare(&q, "%s", blob_sql_text(&bSQL)); blob_reset( &bSQL ); - while( db_step(&q)==SQLITE_ROW && nLine<=nLimit ){ + while( db_step(&q)==SQLITE_ROW && nLine @ + + if( zFreeProjectName != 0 ){ + free( zFreeProjectName ); + } +} + +/* +** COMMAND: rss +** +** Usage: %fossil rss ?OPTIONS? +** +** The CLI variant of the /timeline.rss page, this produces an RSS +** feed of the timeline to stdout. Options: +** +** -type|y FLAG +** may be: all (default), ci (show check-ins only), t (show tickets only), +** w (show wiki only). +** +** -limit|n LIMIT +** The maximum number of items to show. +** +** -tkt UUID +** Filters for only those events for the specified ticket. +** +** -tag TAG +** filters for a tag +** +** -wiki NAME +** Filters on a specific wiki page. +** +** Only one of -tkt, -tag, or -wiki may be used. +** +** -name FILENAME +** filters for a specific file. This may be combined with one of the other +** filters (useful for looking at a specific branch). +** +** -url STRING +** Sets the RSS feed's root URL to the given string. The default is +** "URL-PLACEHOLDER" (without quotes). +*/ +void cmd_timeline_rss(void){ + Stmt q; + int nLine=0; + char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0; + Blob bSQL; + const char *zType = find_option("type","y",1); /* Type of events. All if NULL */ + const char *zTicketUuid = find_option("tkt",NULL,1); + const char *zTag = find_option("tag",NULL,1); + const char *zFilename = find_option("name",NULL,1); + const char *zWiki = find_option("wiki",NULL,1); + const char *zLimit = find_option("limit", "n",1); + const char *zBaseURL = find_option("url", NULL, 1); + int nLimit = atoi( (zLimit && *zLimit) ? zLimit : "20" ); + int nTagId; + const char zSQL1[] = + @ SELECT + @ blob.rid, + @ uuid, + @ event.mtime, + @ coalesce(ecomment,comment), + @ coalesce(euser,user), + @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim), + @ (SELECT count(*) FROM plink WHERE cid=blob.rid) + @ FROM event, blob + @ WHERE blob.rid=event.objid + ; + if(!zType || !*zType){ + zType = "all"; + } + if(!zBaseURL || !*zBaseURL){ + zBaseURL = "URL-PLACEHOLDER"; + } + + db_find_and_open_repository(0, 0); + + /* We should be done with options.. */ + verify_all_options(); + + blob_zero(&bSQL); + blob_append( &bSQL, zSQL1, -1 ); + + if( zType[0]!='a' ){ + blob_append_sql(&bSQL, " AND event.type=%Q", zType); + } + + if( zTicketUuid ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'", + zTicketUuid); + if ( nTagId==0 ){ + nTagId = -1; + } + }else if( zTag ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q*'", + zTag); + if ( nTagId==0 ){ + nTagId = -1; + } + }else if( zWiki ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'wiki-%q*'", + zWiki); + if ( nTagId==0 ){ + nTagId = -1; + } + }else{ + nTagId = 0; + } + + if( nTagId==-1 ){ + blob_append_sql(&bSQL, " AND 0"); + }else if( nTagId!=0 ){ + blob_append_sql(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId); + } + + if( zFilename ){ + blob_append_sql(&bSQL, + " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)", + zFilename, filename_collation() + ); + } + + blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 ); + + zProjectName = db_get("project-name", 0); + if( zProjectName==0 ){ + zFreeProjectName = zProjectName = mprintf("Fossil source repository for: %s", + zBaseURL); + } + zProjectDescr = db_get("project-description", 0); + if( zProjectDescr==0 ){ + zProjectDescr = zProjectName; + } + + zPubDate = cgi_rfc822_datestamp(time(NULL)); + + fossil_print(""); + fossil_print(""); + fossil_print("\n"); + fossil_print("%h\n", zProjectName); + fossil_print("%s\n", zBaseURL); + fossil_print("%h\n", zProjectDescr); + fossil_print("%s\n", zPubDate); + fossil_print("Fossil version %s %s\n", + MANIFEST_VERSION, MANIFEST_DATE); + free(zPubDate); + db_prepare(&q, "%s", blob_sql_text(&bSQL)); + blob_reset( &bSQL ); + while( db_step(&q)==SQLITE_ROW && nLine1 && nChild>1 ){ + zPrefix = "*MERGE/FORK* "; + }else if( nParent>1 ){ + zPrefix = "*MERGE* "; + }else if( nChild>1 ){ + zPrefix = "*FORK* "; + } + + fossil_print(""); + fossil_print("%s%h\n", zPrefix, zCom); + fossil_print("%s/info/%s\n", zBaseURL, zId); + fossil_print("%s%h\n", zPrefix, zCom); + fossil_print("%s\n", zDate); + fossil_print("%h\n", zAuthor); + fossil_print("%s/info/%s\n", g.zBaseURL, zId); + fossil_print("\n"); + free(zDate); + nLine++; + } + + db_finalize(&q); + fossil_print("\n"); + fossil_print("\n"); if( zFreeProjectName != 0 ){ free( zFreeProjectName ); } } Index: src/schema.c ================================================================== --- src/schema.c +++ src/schema.c @@ -12,20 +12,20 @@ ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* -** +** ** This file contains string constants that implement the database schema. */ #include "config.h" #include "schema.h" /* ** The database schema for the ~/.fossil configuration database. */ -const char zConfigSchema[] = +const char zConfigSchema[] = @ -- This file contains the schema for the database that is kept in the @ -- ~/.fossil file and that stores information about the users setup. @ -- @ CREATE TABLE global_config( @ name TEXT PRIMARY KEY, @@ -44,25 +44,29 @@ ** a date) which can change frequently. When the content schema changes, ** we have to execute special procedures to update the schema. When ** the aux schema changes, all we need to do is rebuild the database. */ #define CONTENT_SCHEMA "2" -#define AUX_SCHEMA "2011-04-25 19:50" +#define AUX_SCHEMA_MIN "2011-04-25 19:50" +#define AUX_SCHEMA_MAX "2015-01-24" +/* NB: Some features require the latest schema. Warning or error messages +** will appear if an older schema is used. However, the older schemas are +** adequate for many common functions. */ #endif /* INTERFACE */ /* -** The schema for a repository database. +** The schema for a repository database. ** ** Schema1[] contains parts of the schema that are fixed and unchanging ** across versions. Schema2[] contains parts of the schema that can ** change from one version to the next. The information in Schema2[] ** is reconstructed from the information in Schema1[] by the "rebuild" ** operation. */ -const char zRepositorySchema1[] = +const char zRepositorySchema1[] = @ -- The BLOB and DELTA tables contain all records held in the repository. @ -- @ -- The BLOB.CONTENT column is always compressed using zlib. This @ -- column might hold the full text of the record or it might hold @ -- a delta that is able to reconstruct the record from some other @@ -69,11 +73,11 @@ @ -- record. If BLOB.CONTENT holds a delta, then a DELTA table entry @ -- will exist for the record and that entry will point to another @ -- entry that holds the source of the delta. Deltas can be chained. @ -- @ -- The blob and delta tables collectively hold the "global state" of -@ -- a Fossil repository. +@ -- a Fossil repository. @ -- @ CREATE TABLE blob( @ rid INTEGER PRIMARY KEY, -- Record ID @ rcvid INTEGER, -- Origin of this record @ size INTEGER, -- Size of content. -1 for a phantom. @@ -80,20 +84,20 @@ @ uuid TEXT UNIQUE NOT NULL, -- SHA1 hash of the content @ content BLOB, -- Compressed content of this record @ CHECK( length(uuid)==40 AND rid>0 ) @ ); @ CREATE TABLE delta( -@ rid INTEGER PRIMARY KEY, -- Record ID -@ srcid INTEGER NOT NULL REFERENCES blob -- Record holding source document +@ rid INTEGER PRIMARY KEY, -- BLOB that is delta-compressed +@ srcid INTEGER NOT NULL REFERENCES blob -- Baseline for delta-compression @ ); @ CREATE INDEX delta_i1 ON delta(srcid); @ @ ------------------------------------------------------------------------- @ -- The BLOB and DELTA tables above hold the "global state" of a Fossil @ -- project; the stuff that is normally exchanged during "sync". The @ -- "local state" of a repository is contained in the remaining tables of -@ -- the zRepositorySchema1 string. +@ -- the zRepositorySchema1 string. @ ------------------------------------------------------------------------- @ @ -- Whenever new blobs are received into the repository, an entry @ -- in this table records the source of the blob. @ -- @@ -125,11 +129,11 @@ @ info TEXT, -- contact information @ mtime DATE, -- last change. seconds since 1970 @ photo BLOB -- JPEG image of this user @ ); @ -@ -- The VAR table holds miscellanous information about the repository. +@ -- The config table holds miscellanous information about the repository. @ -- in the form of name-value pairs. @ -- @ CREATE TABLE config( @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry @ value CLOB, -- Content of the named parameter @@ -172,11 +176,11 @@ @ ); @ @ -- Some ticket content (such as the originators email address or contact @ -- information) needs to be obscured to protect privacy. This is achieved @ -- by storing an SHA1 hash of the content. For display, the hash is -@ -- mapped back into the original text using this table. +@ -- mapped back into the original text using this table. @ -- @ -- This table contains sensitive information and should not be shared @ -- with unauthorized users. @ -- @ CREATE TABLE concealed( @@ -193,11 +197,11 @@ /* ** The default reportfmt entry for the schema. This is in an extra ** script so that (configure reset) can install the default report. */ const char zRepositorySchemaDefaultReports[] = -@ INSERT INTO reportfmt(title,mtime,cols,sqlcode) +@ INSERT INTO reportfmt(title,mtime,cols,sqlcode) @ VALUES('All Tickets',julianday('1970-01-01'),'#ffffff Key: @ #f2dcdc Active @ #e8e8e8 Review @ #cfe8bd Fixed @ #bde5d6 Tested @@ -224,44 +228,65 @@ @ CREATE TABLE filename( @ fnid INTEGER PRIMARY KEY, -- Filename ID @ name TEXT UNIQUE -- Name of file page @ ); @ -@ -- Linkages between checkins, files created by each checkin, and +@ -- Linkages between check-ins, files created by each check-in, and @ -- the names of those files. @ -- -@ -- pid==0 if the file is added by checkin mid. -@ -- fid==0 if the file is removed by checkin mid. +@ -- Each entry represents a file that changed content from pid to fid +@ -- due to the check-in that goes from pmid to mid. fnid is the name +@ -- of the file in the mid check-in. If the file was renamed as part +@ -- of the mid check-in, then pfnid is the previous filename. +@ +@ -- There can be multiple entries for (mid,fid) if the mid check-in was +@ -- a merge. Entries with isaux==0 are from the primary parent. Merge +@ -- parents have isaux set to true. +@ -- +@ -- Field name mnemonics: +@ -- mid = Manifest ID. (Each check-in is stored as a "Manifest") +@ -- fid = File ID. +@ -- pmid = Parent Manifest ID. +@ -- pid = Parent file ID. +@ -- fnid = File Name ID. +@ -- pfnid = Parent File Name ID. +@ -- isaux = pmid IS AUXiliary parent, not primary parent +@ -- +@ -- pid==0 if the file is added by check-in mid. +@ -- fid==0 if the file is removed by check-in mid. @ -- @ CREATE TABLE mlink( -@ mid INTEGER REFERENCES blob, -- Manifest ID where change occurs -@ pid INTEGER REFERENCES blob, -- File ID in parent manifest -@ fid INTEGER REFERENCES blob, -- Changed file ID in this manifest +@ mid INTEGER REFERENCES plink(cid), -- Check-in that contains fid +@ fid INTEGER REFERENCES blob, -- New file content. 0 if deleted +@ pmid INTEGER REFERENCES plink(cid), -- Check-in that contains pid +@ pid INTEGER REFERENCES blob, -- Prev file content. 0 if new @ fnid INTEGER REFERENCES filename, -- Name of the file @ pfnid INTEGER REFERENCES filename, -- Previous name. 0 if unchanged -@ mperm INTEGER -- File permissions. 1==exec +@ mperm INTEGER, -- File permissions. 1==exec +@ isaux BOOLEAN DEFAULT 0 -- TRUE if pmid is the primary @ ); @ CREATE INDEX mlink_i1 ON mlink(mid); @ CREATE INDEX mlink_i2 ON mlink(fnid); @ CREATE INDEX mlink_i3 ON mlink(fid); @ CREATE INDEX mlink_i4 ON mlink(pid); @ -@ -- Parent/child linkages between checkins +@ -- Parent/child linkages between check-ins @ -- @ CREATE TABLE plink( @ pid INTEGER REFERENCES blob, -- Parent manifest @ cid INTEGER REFERENCES blob, -- Child manifest @ isprim BOOLEAN, -- pid is the primary parent of cid @ mtime DATETIME, -- the date/time stamp on cid. Julian day. +@ baseid INTEGER REFERENCES blob, -- Baseline if cid is a delta manifest. @ UNIQUE(pid, cid) @ ); @ CREATE INDEX plink_i2 ON plink(cid,pid); @ -@ -- A "leaf" checkin is a checkin that has no children in the same +@ -- A "leaf" check-in is a check-in that has no children in the same @ -- branch. The set of all leaves is easily computed with a join, @ -- between the plink and tagxref tables, but it is a slower join for -@ -- very large repositories (repositories with 100,000 or more checkins) +@ -- very large repositories (repositories with 100,000 or more check-ins) @ -- and so it makes sense to precompute the set of leaves. There is @ -- one entry in the following table for each leaf. @ -- @ CREATE TABLE leaf(rid INTEGER PRIMARY KEY); @ @@ -321,13 +346,13 @@ @ rid INTEGER PRIMARY KEY -- Record ID of the phantom @ ); @ @ -- Each baseline or manifest can have one or more tags. A tag @ -- is defined by a row in the next table. -@ -- +@ -- @ -- Wiki pages are tagged with "wiki-NAME" where NAME is the name of -@ -- the wiki page. Tickets changes are tagged with "ticket-UUID" where +@ -- the wiki page. Tickets changes are tagged with "ticket-UUID" where @ -- UUID is the indentifier of the ticket. Tags used to assign symbolic @ -- names to baselines are branches are of the form "sym-NAME" where @ -- NAME is the symbolic name. @ -- @ CREATE TABLE tag( @@ -368,11 +393,11 @@ @ -- facilitate the display of "back links". @ -- @ CREATE TABLE backlink( @ target TEXT, -- Where the hyperlink points to @ srctype INT, -- 0: check-in 1: ticket 2: wiki -@ srcid INT, -- rid for checkin or wiki. tkt_id for ticket. +@ srcid INT, -- rid for check-in or wiki. tkt_id for ticket. @ mtime TIMESTAMP, -- time that the hyperlink was added. Julian day. @ UNIQUE(target, srctype, srcid) @ ); @ CREATE INDEX backlink_src ON backlink(srcid, srctype); @ @@ -435,24 +460,24 @@ #if INTERFACE # define TAG_BGCOLOR 1 /* Set the background color for display */ # define TAG_COMMENT 2 /* The check-in comment */ # define TAG_USER 3 /* User who made a checking */ # define TAG_DATE 4 /* The date of a check-in */ -# define TAG_HIDDEN 5 /* Do not display or sync */ -# define TAG_PRIVATE 6 /* Display but do not sync */ +# define TAG_HIDDEN 5 /* Do not display in timeline */ +# define TAG_PRIVATE 6 /* Do not sync */ # define TAG_CLUSTER 7 /* A cluster */ # define TAG_BRANCH 8 /* Value is name of the current branch */ # define TAG_CLOSED 9 /* Do not display this check-in as a leaf */ -# define TAG_PARENT 10 /* Change to parentage on a checkin */ +# define TAG_PARENT 10 /* Change to parentage on a check-in */ #endif #if EXPORT_INTERFACE # define MAX_INT_TAG 16 /* The largest pre-assigned tag id */ #endif /* -** The schema for the locate FOSSIL database file found at the root -** of very check-out. This database contains the complete state of +** The schema for the local FOSSIL database file found at the root +** of every check-out. This database contains the complete state of ** the checkout. */ const char zLocalSchema[] = @ -- The VVAR table holds miscellanous information about the local database @ -- in the form of name-value pairs. This is similar to the VAR table @@ -479,18 +504,18 @@ @ -- Vfile.chnged is 0 for unmodified files, 1 for files that have @ -- been edited or which have been subjected to a 3-way merge. @ -- Vfile.chnged is 2 if the file has been replaced from a different @ -- version by the merge and 3 if the file has been added by a merge. @ -- Vfile.chnged is 4|5 is the same as 2|3, but the operation has been -@ -- done by an --integrate merge. The difference between vfile.chnged==2|4 -@ -- and a regular add is that with vfile.chnged==2|4 we know that the +@ -- done by an --integrate merge. The difference between vfile.chnged==3|5 +@ -- and a regular add is that with vfile.chnged==3|5 we know that the @ -- current version of the file is already in the repository. @ -- @ CREATE TABLE vfile( @ id INTEGER PRIMARY KEY, -- ID of the checked out file @ vid INTEGER REFERENCES blob, -- The baseline this file is part of. -@ chnged INT DEFAULT 0, -- 0:unchnged 1:edited 2:m-chng 3:m-add 4:i-chng 5:i-add +@ chnged INT DEFAULT 0, -- 0:unchng 1:edit 2:m-chng 3:m-add 4:i-chng 5:i-add @ deleted BOOLEAN DEFAULT 0, -- True if deleted @ isexe BOOLEAN, -- True if file should be executable @ islink BOOLEAN, -- True if file should be symlink @ rid INTEGER, -- Originally from this repository record @ mrid INTEGER, -- Based on this record due to a merge Index: src/search.c ================================================================== --- src/search.c +++ src/search.c @@ -13,62 +13,56 @@ ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** -** This file contains code to implement the "/doc" web page and related -** pages. +** This file contains code to implement a very simple search function +** against timeline comments, check-in content, wiki pages, and/or tickets. +** +** The search is full-text like in that it is looking for words and ignores +** punctuation and capitalization. But it is more akin to "grep" in that +** it scans the entire corpus for the search, and it does not support the +** full functionality of FTS4. */ #include "config.h" #include "search.h" #include #if INTERFACE + +/* Maximum number of search terms */ +#define SEARCH_MAX_TERM 8 + /* -** A compiled search patter +** A compiled search pattern */ struct Search { - int nTerm; - struct srchTerm { - char *z; - int n; - } a[8]; + int nTerm; /* Number of search terms */ + struct srchTerm { /* For each search term */ + char *z; /* Text */ + int n; /* length */ + } a[SEARCH_MAX_TERM]; + /* Snippet controls */ + char *zPattern; /* The search pattern */ + char *zMarkBegin; /* Start of a match */ + char *zMarkEnd; /* End of a match */ + char *zMarkGap; /* A gap between two matches */ + unsigned fSrchFlg; /* Flags */ + int iScore; /* Score of the last match attempt */ + Blob snip; /* Snippet for the most recent match */ }; + +#define SRCHFLG_HTML 0x01 /* Escape snippet text for HTML */ +#define SRCHFLG_STATIC 0x04 /* The static gSearch object */ + #endif /* -** Compile a search pattern -*/ -Search *search_init(const char *zPattern){ - int nPattern = strlen(zPattern); - Search *p; - char *z; - int i; - - p = fossil_malloc( nPattern + sizeof(*p) + 1); - z = (char*)&p[1]; - memcpy(z, zPattern, nPattern+1); - memset(p, 0, sizeof(*p)); - while( *z && p->nTerma)/sizeof(p->a[0]) ){ - while( !fossil_isalnum(*z) && *z ){ z++; } - if( *z==0 ) break; - p->a[p->nTerm].z = z; - for(i=1; fossil_isalnum(z[i]) || z[i]=='_'; i++){} - p->a[p->nTerm].n = i; - z += i; - p->nTerm++; - } - return p; -} - - -/* -** Destroy a search context. -*/ -void search_end(Search *p){ - free(p); -} +** There is a single global Search object: +*/ +static Search gSearch; + /* ** Theses characters constitute a word boundary */ static const char isBoundary[] = { @@ -87,146 +81,1644 @@ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; +#define ISALNUM(x) (!isBoundary[(x)&0xff]) + + +/* +** Destroy a search context. +*/ +void search_end(Search *p){ + if( p ){ + fossil_free(p->zPattern); + fossil_free(p->zMarkBegin); + fossil_free(p->zMarkEnd); + fossil_free(p->zMarkGap); + if( p->iScore ) blob_reset(&p->snip); + memset(p, 0, sizeof(*p)); + if( p!=&gSearch ) fossil_free(p); + } +} + +/* +** Compile a search pattern +*/ +Search *search_init( + const char *zPattern, /* The search pattern */ + const char *zMarkBegin, /* Start of a match */ + const char *zMarkEnd, /* End of a match */ + const char *zMarkGap, /* A gap between two matches */ + unsigned fSrchFlg /* Flags */ +){ + Search *p; + char *z; + int i; + + if( fSrchFlg & SRCHFLG_STATIC ){ + p = &gSearch; + search_end(p); + }else{ + p = fossil_malloc(sizeof(*p)); + memset(p, 0, sizeof(*p)); + } + p->zPattern = z = mprintf("%s", zPattern); + p->zMarkBegin = mprintf("%s", zMarkBegin); + p->zMarkEnd = mprintf("%s", zMarkEnd); + p->zMarkGap = mprintf("%s", zMarkGap); + p->fSrchFlg = fSrchFlg; + blob_init(&p->snip, 0, 0); + while( *z && p->nTerma[p->nTerm].z = z; + for(i=1; ISALNUM(z[i]); i++){} + p->a[p->nTerm].n = i; + z += i; + p->nTerm++; + } + return p; +} + + +/* +** Append n bytes of text to snippet zTxt. Encode the text appropriately. +*/ +static void snippet_text_append( + Search *p, /* The search context */ + Blob *pSnip, /* Append to this snippet */ + const char *zTxt, /* Text to append */ + int n /* How many bytes to append */ +){ + if( n>0 ){ + if( p->fSrchFlg & SRCHFLG_HTML ){ + blob_appendf(pSnip, "%#h", n, zTxt); + }else{ + blob_append(pSnip, zTxt, n); + } + } +} /* -** Compare a search pattern against an input string and return a score. +** Compare a search pattern against one or more input strings which +** collectively comprise a document. Return a match score. Any +** postive value means there was a match. Zero means that one or +** more terms are missing. +** +** The score and a snippet are record for future use. ** ** Scoring: ** * All terms must match at least once or the score is zero -** * 10 bonus points if the first occurrence is an exact match -** * 1 additional point for each subsequent match of the same word -** * Extra points of two consecutive words of the pattern are consecutive +** * One point for each matching term +** * Extra points if consecutive words of the pattern are consecutive ** in the document */ -int search_score(Search *p, const char *zDoc){ - int iPrev = 999; - int score = 10; - int iBonus = 0; - int i, j; - unsigned char seen[8]; - - memset(seen, 0, sizeof(seen)); - for(i=0; zDoc[i]; i++){ - char c = zDoc[i]; - if( isBoundary[c&0xff] ) continue; - for(j=0; jnTerm; j++){ - int n = p->a[j].n; - if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0 ){ - score += 1; - if( !seen[j] ){ - if( isBoundary[zDoc[i+n]&0xff] ) score += 10; - seen[j] = 1; - } - if( j==iPrev+1 ){ - score += iBonus; - } - i += n-1; - iPrev = j; - iBonus = 50; - break; - } - } - iBonus /= 2; - while( !isBoundary[zDoc[i]&0xff] ){ i++; } - } - - /* Every term must be seen or else the score is zero */ - for(j=0; jnTerm; j++){ - if( !seen[j] ) return 0; - } - +static int search_match( + Search *p, /* Search pattern and flags */ + int nDoc, /* Number of strings in this document */ + const char **azDoc /* Text of each string */ +){ + int score; /* Final score */ + int i; /* Offset into current document */ + int ii; /* Loop counter */ + int j; /* Loop over search terms */ + int k; /* Loop over prior terms */ + int iWord = 0; /* Current word number */ + int iDoc; /* Current document number */ + int wantGap = 0; /* True if a zMarkGap is wanted */ + const char *zDoc; /* Current document text */ + const int CTX = 50; /* Amount of snippet context */ + int anMatch[SEARCH_MAX_TERM]; /* Number of terms in best match */ + int aiBestDoc[SEARCH_MAX_TERM]; /* Document containing best match */ + int aiBestOfst[SEARCH_MAX_TERM]; /* Byte offset to start of best match */ + int aiLastDoc[SEARCH_MAX_TERM]; /* Document containing most recent match */ + int aiLastOfst[SEARCH_MAX_TERM]; /* Byte offset to the most recent match */ + int aiWordIdx[SEARCH_MAX_TERM]; /* Word index of most recent match */ + + memset(anMatch, 0, sizeof(anMatch)); + memset(aiWordIdx, 0xff, sizeof(aiWordIdx)); + for(iDoc=0; iDocnTerm; j++){ + int n = p->a[j].n; + if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0 + && (!ISALNUM(zDoc[i+n]) || p->a[j].z[n]=='*') + ){ + aiWordIdx[j] = iWord; + aiLastDoc[j] = iDoc; + aiLastOfst[j] = i; + for(k=1; j-k>=0 && anMatch[j-k] && aiWordIdx[j-k]==iWord-k; k++){} + for(ii=0; iinTerm; j++) score *= anMatch[j]; + blob_reset(&p->snip); + p->iScore = score; + if( score==0 ) return score; + + + /* Prepare a snippet that describes the matching text. + */ + while(1){ + int iOfst; + int iTail; + int iBest; + for(ii=0; iinTerm && anMatch[ii]==0; ii++){} + if( ii>=p->nTerm ) break; /* This is where the loop exits */ + iBest = ii; + iDoc = aiBestDoc[ii]; + iOfst = aiBestOfst[ii]; + for(; iinTerm; ii++){ + if( anMatch[ii]==0 ) continue; + if( aiBestDoc[ii]>iDoc ) continue; + if( aiBestOfst[ii]>iOfst ) continue; + iDoc = aiBestDoc[ii]; + iOfst = aiBestOfst[ii]; + iBest = ii; + } + iTail = iOfst + p->a[iBest].n; + anMatch[iBest] = 0; + for(ii=0; iinTerm; ii++){ + if( anMatch[ii]==0 ) continue; + if( aiBestDoc[ii]!=iDoc ) continue; + if( aiBestOfst[ii]<=iTail+CTX*2 ){ + if( iTaila[ii].n ){ + iTail = aiBestOfst[ii]+p->a[ii].n; + } + anMatch[ii] = 0; + ii = -1; + continue; + } + } + zDoc = azDoc[iDoc]; + iOfst -= CTX; + if( iOfst<0 ) iOfst = 0; + while( iOfst>0 && ISALNUM(zDoc[iOfst-1]) ) iOfst--; + while( zDoc[iOfst] && !ISALNUM(zDoc[iOfst]) ) iOfst++; + for(ii=0; ii0 || wantGap ) blob_append(&p->snip, p->zMarkGap, -1); + wantGap = zDoc[iTail]!=0; + zDoc += iOfst; + iTail -= iOfst; + + /* Add a snippet segment using characters iOfst..iOfst+iTail from zDoc */ + for(i=0; inTerm; j++){ + int n = p->a[j].n; + if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0 + && (!ISALNUM(zDoc[i+n]) || p->a[j].z[n]=='*') + ){ + snippet_text_append(p, &p->snip, zDoc, i); + zDoc += i; + iTail -= i; + blob_append(&p->snip, p->zMarkBegin, -1); + if( p->a[j].z[n]=='*' ){ + while( ISALNUM(zDoc[n]) ) n++; + } + snippet_text_append(p, &p->snip, zDoc, n); + zDoc += n; + iTail -= n; + blob_append(&p->snip, p->zMarkEnd, -1); + i = -1; + break; + } /* end-if */ + } /* end for(j) */ + if( jnTerm ){ + while( ISALNUM(zDoc[i]) && isnip, zDoc, iTail); + } + if( wantGap ) blob_append(&p->snip, p->zMarkGap, -1); return score; } /* -** This is an SQLite function that scores its input using -** a pre-computed pattern. +** COMMAND: test-match +** +** Usage: fossil test-match SEARCHSTRING FILE1 FILE2 ... +*/ +void test_match_cmd(void){ + Search *p; + int i; + Blob x; + int score; + char *zDoc; + int flg = 0; + char *zBegin = (char*)find_option("begin",0,1); + char *zEnd = (char*)find_option("end",0,1); + char *zGap = (char*)find_option("gap",0,1); + if( find_option("html",0,0)!=0 ) flg |= SRCHFLG_HTML; + if( find_option("static",0,0)!=0 ) flg |= SRCHFLG_STATIC; + verify_all_options(); + if( g.argc<4 ) usage("SEARCHSTRING FILE1..."); + if( zBegin==0 ) zBegin = "[["; + if( zEnd==0 ) zEnd = "]]"; + if( zGap==0 ) zGap = " ... "; + p = search_init(g.argv[2], zBegin, zEnd, zGap, flg); + for(i=3; iiScore); + blob_reset(&x); + if( score ){ + fossil_print("%.78c\n%s\n%.78c\n\n", '=', blob_str(&p->snip), '='); + } + } + search_end(p); +} + +/* +** An SQL function to initialize the global search pattern: +** +** search_init(PATTERN,BEGIN,END,GAP,FLAGS) +** +** All arguments are optional. +*/ +static void search_init_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zPattern = 0; + const char *zBegin = ""; + const char *zEnd = ""; + const char *zGap = " ... "; + unsigned int flg = SRCHFLG_HTML; + switch( argc ){ + default: + flg = (unsigned int)sqlite3_value_int(argv[4]); + case 4: + zGap = (const char*)sqlite3_value_text(argv[3]); + case 3: + zEnd = (const char*)sqlite3_value_text(argv[2]); + case 2: + zBegin = (const char*)sqlite3_value_text(argv[1]); + case 1: + zPattern = (const char*)sqlite3_value_text(argv[0]); + } + if( zPattern && zPattern[0] ){ + search_init(zPattern, zBegin, zEnd, zGap, flg | SRCHFLG_STATIC); + }else{ + search_end(&gSearch); + } +} + +/* +** Try to match the input text against the search parameters set up +** by the previous search_init() call. Remember the results globally. +** Return non-zero on a match and zero on a miss. +*/ +static void search_match_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *azDoc[5]; + int nDoc; + int rc; + for(nDoc=0; nDoc0 ){ + sqlite3_result_text(context, blob_str(&gSearch.snip), -1, fossil_free); + blob_init(&gSearch.snip, 0, 0); + } +} + +/* +** This is an SQLite function that computes the searchable text. +** It is a wrapper around the search_stext() routine. See the +** search_stext() routine for further detail. +*/ +static void search_stext_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zType = (const char*)sqlite3_value_text(argv[0]); + int rid = sqlite3_value_int(argv[1]); + const char *zName = (const char*)sqlite3_value_text(argv[2]); + sqlite3_result_text(context, search_stext_cached(zType[0],rid,zName,0), -1, + SQLITE_TRANSIENT); +} +static void search_title_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zType = (const char*)sqlite3_value_text(argv[0]); + int rid = sqlite3_value_int(argv[1]); + const char *zName = (const char*)sqlite3_value_text(argv[2]); + int nHdr; + char *z = search_stext_cached(zType[0], rid, zName, &nHdr); + if( nHdr || zType[0]!='d' ){ + sqlite3_result_text(context, z, nHdr, SQLITE_TRANSIENT); + }else{ + sqlite3_result_value(context, argv[2]); + } +} +static void search_body_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zType = (const char*)sqlite3_value_text(argv[0]); + int rid = sqlite3_value_int(argv[1]); + const char *zName = (const char*)sqlite3_value_text(argv[2]); + int nHdr; + char *z = search_stext_cached(zType[0], rid, zName, &nHdr); + sqlite3_result_text(context, z+nHdr+1, -1, SQLITE_TRANSIENT); +} + +/* +** Encode a string for use as a query parameter in a URL +*/ +static void search_urlencode_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + char *z = mprintf("%T",sqlite3_value_text(argv[0])); + sqlite3_result_text(context, z, -1, fossil_free); } /* ** Register the "score()" SQL function to score its input text ** using the given Search object. Once this function is registered, ** do not delete the Search object. */ -void search_sql_setup(Search *p){ - sqlite3_create_function(g.db, "score", 1, SQLITE_UTF8, p, +void search_sql_setup(sqlite3 *db){ + static int once = 0; + if( once++ ) return; + sqlite3_create_function(db, "search_match", -1, SQLITE_UTF8, 0, + search_match_sqlfunc, 0, 0); + sqlite3_create_function(db, "search_score", 0, SQLITE_UTF8, 0, search_score_sqlfunc, 0, 0); + sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0, + search_snippet_sqlfunc, 0, 0); + sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, + search_init_sqlfunc, 0, 0); + sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, + search_stext_sqlfunc, 0, 0); + sqlite3_create_function(db, "title", 3, SQLITE_UTF8, 0, + search_title_sqlfunc, 0, 0); + sqlite3_create_function(db, "body", 3, SQLITE_UTF8, 0, + search_body_sqlfunc, 0, 0); + sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, + search_urlencode_sqlfunc, 0, 0); } /* ** Testing the search function. ** ** COMMAND: search* -** %fossil search [-all|-a] [-limit|-n #] pattern... +** %fossil search [-all|-a] [-limit|-n #] [-width|-W #] pattern... ** ** Search for timeline entries matching all words ** provided on the command line. Whole-word matches ** scope more highly than partial matches. ** ** Outputs, by default, some top-N fraction of the ** results. The -all option can be used to output ** all matches, regardless of their search score. -** -limit can be used to limit the number of entries -** returned. +** The -limit option can be used to limit the number +** of entries returned. The -width option can be +** used to set the output width used when printing +** matches. */ void search_cmd(void){ - Search *p; Blob pattern; int i; Blob sql = empty_blob; Stmt q; int iBest; char fAll = NULL != find_option("all", "a", 0); /* If set, do not lop off the end of the results. */ - char const * zLimit = find_option("limit","n",1); - int const nLimit = zLimit ? atoi(zLimit) : -1; /* Max number of entries - to list */ + const char *zLimit = find_option("limit","n",1); + const char *zWidth = find_option("width","W",1); + int nLimit = zLimit ? atoi(zLimit) : -1000; /* Max number of matching + lines/entries to list */ + int width; + if( zWidth ){ + width = atoi(zWidth); + if( (width!=0) && (width<=20) ){ + fossil_fatal("-W|--width value must be >20 or 0"); + } + }else{ + width = -1; + } db_must_be_within_tree(); if( g.argc<2 ) return; blob_init(&pattern, g.argv[2], -1); for(i=3; i0;" + " WHERE blob.rid=event.objid" + " AND search_match(coalesce(ecomment,comment));", + timeline_utc() ); iBest = db_int(0, "SELECT max(x) FROM srch"); blob_append(&sql, "SELECT rid, uuid, date, comment, 0, 0 FROM srch " "WHERE 1 ", -1); if(!fAll){ - blob_appendf(&sql,"AND x>%d ", iBest/3); + blob_append_sql(&sql,"AND x>%d ", iBest/3); } blob_append(&sql, "ORDER BY x DESC, date DESC ", -1); - if(nLimit>0){ - blob_appendf(&sql, "LIMIT %d", nLimit); - } - db_prepare(&q, blob_str(&sql)); + db_prepare(&q, "%s", blob_sql_text(&sql)); blob_reset(&sql); - print_timeline(&q, 1000, 0); + print_timeline(&q, nLimit, width, 0); + db_finalize(&q); +} + +#if INTERFACE +/* What to search for */ +#define SRCH_CKIN 0x0001 /* Search over check-in comments */ +#define SRCH_DOC 0x0002 /* Search over embedded documents */ +#define SRCH_TKT 0x0004 /* Search over tickets */ +#define SRCH_WIKI 0x0008 /* Search over wiki */ +#define SRCH_ALL 0x000f /* Search over everything */ +#endif + +/* +** Remove bits from srchFlags which are disallowed by either the +** current server configuration or by user permissions. +*/ +unsigned int search_restrict(unsigned int srchFlags){ + static unsigned int knownGood = 0; + static unsigned int knownBad = 0; + static const struct { unsigned m; const char *zKey; } aSetng[] = { + { SRCH_CKIN, "search-ci" }, + { SRCH_DOC, "search-doc" }, + { SRCH_TKT, "search-tkt" }, + { SRCH_WIKI, "search-wiki" }, + }; + int i; + if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC); + if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT); + if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI); + for(i=0; i", "", " ... ", + SRCHFLG_STATIC|SRCHFLG_HTML); + if( (srchFlags & SRCH_DOC)!=0 ){ + char *zDocGlob = db_get("doc-glob",""); + char *zDocBr = db_get("doc-branch","trunk"); + if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ + db_multi_exec( + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" + ); + db_multi_exec( + "INSERT INTO x(label,url,score,id,date,snip)" + " SELECT printf('Document: %%s',title('d',blob.rid,foci.filename))," + " printf('/doc/%T/%%s',foci.filename)," + " search_score()," + " 'd'||blob.rid," + " (SELECT datetime(event.mtime) FROM event" + " WHERE objid=symbolic_name_to_rid('trunk'))," + " search_snippet()" + " FROM foci CROSS JOIN blob" + " WHERE checkinID=symbolic_name_to_rid('trunk')" + " AND blob.uuid=foci.uuid" + " AND search_match(title('d',blob.rid,foci.filename)," + " body('d',blob.rid,foci.filename))" + " AND %z", + zDocBr, glob_expr("foci.filename", zDocGlob) + ); + } + } + if( (srchFlags & SRCH_WIKI)!=0 ){ + db_multi_exec( + "WITH wiki(name,rid,mtime) AS (" + " SELECT substr(tagname,6), tagxref.rid, max(tagxref.mtime)" + " FROM tag, tagxref" + " WHERE tag.tagname GLOB 'wiki-*'" + " AND tagxref.tagid=tag.tagid" + " GROUP BY 1" + ")" + "INSERT INTO x(label,url,score,id,date,snip)" + " SELECT printf('Wiki: %%s',name)," + " printf('/wiki?name=%%s',urlencode(name))," + " search_score()," + " 'w'||rid," + " datetime(mtime)," + " search_snippet()" + " FROM wiki" + " WHERE search_match(title('w',rid,name),body('w',rid,name));" + ); + } + if( (srchFlags & SRCH_CKIN)!=0 ){ + db_multi_exec( + "WITH ckin(uuid,rid,mtime) AS (" + " SELECT blob.uuid, event.objid, event.mtime" + " FROM event, blob" + " WHERE event.type='ci'" + " AND blob.rid=event.objid" + ")" + "INSERT INTO x(label,url,score,id,date,snip)" + " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," + " printf('/timeline?c=%%s&n=8&y=ci',uuid)," + " search_score()," + " 'c'||rid," + " datetime(mtime)," + " search_snippet()" + " FROM ckin" + " WHERE search_match('',body('c',rid,NULL));" + ); + } + if( (srchFlags & SRCH_TKT)!=0 ){ + db_multi_exec( + "INSERT INTO x(label,url,score,id,date,snip)" + " SELECT printf('Ticket: %%s (%%s)',title('t',tkt_id,NULL)," + "datetime(tkt_mtime))," + " printf('/tktview/%%.20s',tkt_uuid)," + " search_score()," + " 't'||tkt_id," + " datetime(tkt_mtime)," + " search_snippet()" + " FROM ticket" + " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));" + ); + } +} + +/* +** Number of significant bits in a u32 +*/ +static int nbits(u32 x){ + int n = 0; + while( x ){ n++; x >>= 1; } + return n; +} + +/* +** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')). +*/ +static void search_rank_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]); + int nVal = sqlite3_value_bytes(argv[0])/4; + int nCol; /* Number of columns in the index */ + int nTerm; /* Number of search terms in the query */ + int i, j; /* Loop counter */ + double r = 0.0; /* Score */ + const unsigned *aX, *aS; + + if( nVal<2 ) return; + nTerm = aVal[0]; + nCol = aVal[1]; + if( nVal<2+3*nCol*nTerm+nCol ) return; + aS = aVal+2; + aX = aS+nCol; + for(j=0; j0 ){ + x = 0.0; + for(i=0; i','',' ... ',-1,35)" + " FROM ftsidx CROSS JOIN ftsdocs" + " WHERE ftsidx MATCH %Q" + " AND ftsdocs.rowid=ftsidx.docid", + zPattern + ); + if( srchFlags!=SRCH_ALL ){ + const char *zSep = " AND ("; + static const struct { unsigned m; char c; } aMask[] = { + { SRCH_CKIN, 'c' }, + { SRCH_DOC, 'd' }, + { SRCH_TKT, 't' }, + { SRCH_WIKI, 'w' }, + }; + int i; + for(i=0; iTEXT" where TEXT contains +** no white-space or punctuation, then return the length of the mark. +*/ +static int isSnippetMark(const char *z){ + int n; + if( strncmp(z,"",6)!=0 ) return 0; + n = 6; + while( fossil_isalnum(z[n]) ) n++; + if( strncmp(&z[n],"",7)!=0 ) return 0; + return n+7; +} + +/* +** Return a copy of zSnip (in memory obtained from fossil_malloc()) that +** has all "<" characters, other than those on and , +** converted into "<". This is similar to htmlize() except that +** and are preserved. +*/ +static char *cleanSnippet(const char *zSnip){ + int i; + int n = 0; + char *z; + for(i=0; zSnip[i]; i++) if( zSnip[i]=='<' ) n++; + z = fossil_malloc( i+n*4+1 ); + i = 0; + while( zSnip[0] ){ + if( zSnip[0]=='<' ){ + n = isSnippetMark(zSnip); + if( n ){ + memcpy(&z[i], zSnip, n); + zSnip += n; + i += n; + continue; + }else{ + memcpy(&z[i], "<", 4); + i += 4; + zSnip++; + } + }else{ + z[i++] = zSnip[0]; + zSnip++; + } + } + z[i] = 0; + return z; +} + + +/* +** This routine generates web-page output for a search operation. +** Other web-pages can invoke this routine to add search results +** in the middle of the page. +** +** Return the number of rows. +*/ +int search_run_and_output( + const char *zPattern, /* The query pattern */ + unsigned int srchFlags, /* What to search over */ + int fDebug /* Extra debugging output */ +){ + Stmt q; + int nRow = 0; + + srchFlags = search_restrict(srchFlags); + if( srchFlags==0 ) return 0; + search_sql_setup(g.db); + add_content_sql_commands(g.db); + db_multi_exec( + "CREATE TEMP TABLE x(label,url,score,id,date,snip);" + ); + if( !search_index_exists() ){ + search_fullscan(zPattern, srchFlags); + }else{ + search_update_index(srchFlags); + search_indexed(zPattern, srchFlags); + } + db_prepare(&q, "SELECT url, snip, label, score, id" + " FROM x" + " ORDER BY score DESC, date DESC;"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zUrl = db_column_text(&q, 0); + const char *zSnippet = db_column_text(&q, 1); + const char *zLabel = db_column_text(&q, 2); + if( nRow==0 ){ + @
        + } + nRow++; + @
      1. %h(zLabel) + if( fDebug ){ + @ (%e(db_column_double(&q,3)), %s(db_column_text(&q,4))) + } + @
        %z(cleanSnippet(zSnippet))

      2. + } db_finalize(&q); + if( nRow ){ + @
      + } + return nRow; +} + +/* +** Generate some HTML for doing search. At a minimum include the +** Search-Text entry form. If the "s" query parameter is present, also +** show search results. +** +** The srchFlags parameter restricts the set of documents to be searched. +** srchFlags should normally be either a single search category or all +** categories. Any srchFlags with two or more bits set +** is treated like SRCH_ALL for display purposes. +** +** This routine automatically restricts srchFlag according to user +** permissions and the server configuration. The entry box is shown +** disabled if srchFlags is 0 after these restrictions are applied. +** +** If useYparam is true, then this routine also looks at the y= query +** parameter for further search restrictions. +*/ +void search_screen(unsigned srchFlags, int useYparam){ + const char *zType = 0; + const char *zClass = 0; + const char *zDisable1; + const char *zDisable2; + const char *zPattern; + int fDebug = PB("debug"); + srchFlags = search_restrict(srchFlags); + switch( srchFlags ){ + case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; + case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; + case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; + case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break; + } + if( srchFlags==0 ){ + zDisable1 = " disabled"; + zDisable2 = " disabled"; + zPattern = ""; + }else{ + zDisable1 = " autofocus"; + zDisable2 = ""; + zPattern = PD("s",""); + } + @ + if( zClass ){ + @
      + }else{ + @
      + } + @ + if( useYparam && (srchFlags & (srchFlags-1))!=0 && useYparam ){ + static const struct { char *z; char *zNm; unsigned m; } aY[] = { + { "all", "All", SRCH_ALL }, + { "c", "Check-ins", SRCH_CKIN }, + { "d", "Docs", SRCH_DOC }, + { "t", "Tickets", SRCH_TKT }, + { "w", "Wiki", SRCH_WIKI }, + }; + const char *zY = PD("y","all"); + unsigned newFlags = srchFlags; + int i; + @ + srchFlags = newFlags; + } + if( fDebug ){ + @ + } + @ + if( srchFlags==0 ){ + @

      Search is disabled

      + } + @
      + while( fossil_isspace(zPattern[0]) ) zPattern++; + if( zPattern[0] ){ + if( zClass ){ + @
      + }else{ + @
      + } + if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){ + @

      No matches for: %h(zPattern)

      + } + @
      + } +} + +/* +** WEBPAGE: /search +** +** Search for check-in comments, documents, tickets, or wiki that +** match a user-supplied pattern. +*/ +void search_page(void){ + login_check_credentials(); + style_header("Search"); + search_screen(SRCH_ALL, 1); + style_footer(); +} + + +/* +** This is a helper function for search_stext(). Writing into pOut +** the search text obtained from pIn according to zMimetype. +** +** The title of the document is the first line of text. All subsequent +** lines are the body. If the document has no title, the first line +** is blank. +*/ +static void get_stext_by_mimetype( + Blob *pIn, + const char *zMimetype, + Blob *pOut +){ + Blob html, title; + blob_init(&html, 0, 0); + blob_init(&title, 0, 0); + if( zMimetype==0 ) zMimetype = "text/plain"; + if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){ + Blob tail; + blob_init(&tail, 0, 0); + if( wiki_find_title(pIn, &title, &tail) ){ + blob_appendf(pOut, "%s\n", blob_str(&title)); + wiki_convert(&tail, &html, 0); + blob_reset(&tail); + }else{ + blob_append(pOut, "\n", 1); + wiki_convert(pIn, &html, 0); + } + html_to_plaintext(blob_str(&html), pOut); + }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){ + markdown_to_html(pIn, &title, &html); + if( blob_size(&title) ){ + blob_appendf(pOut, "%s\n", blob_str(&title)); + }else{ + blob_append(pOut, "\n", 1); + } + html_to_plaintext(blob_str(&html), pOut); + }else if( fossil_strcmp(zMimetype,"text/html")==0 ){ + if( doc_is_embedded_html(pIn, &title) ){ + blob_appendf(pOut, "%s\n", blob_str(&title)); + } + html_to_plaintext(blob_str(pIn), pOut); + }else{ + blob_append(pOut, "\n", 1); + blob_append(pOut, blob_buffer(pIn), blob_size(pIn)); + } + blob_reset(&html); + blob_reset(&title); +} + +/* +** Query pQuery is pointing at a single row of output. Append a text +** representation of every text-compatible column to pAccum. +*/ +static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery, int iTitle){ + int n = db_column_count(pQuery); + int i; + const char *zMime = 0; + if( iTitle>=0 && iTitlezWiki, -1); + get_stext_by_mimetype(&wiki, wiki_filter_mimetypes(pWiki->zMimetype), + pOut); + blob_reset(&wiki); + manifest_destroy(pWiki); + break; + } + case 'c': { /* Check-in Comments */ + static Stmt q; + static int isPlainText = -1; + db_static_prepare(&q, + "SELECT coalesce(ecomment,comment)" + " ||' (user: '||coalesce(euser,user,'?')" + " ||', tags: '||" + " (SELECT group_concat(substr(tag.tagname,5),',')" + " FROM tag, tagxref" + " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" + " AND tagxref.rid=event.objid AND tagxref.tagtype>0)" + " ||')'" + " FROM event WHERE objid=:x AND type='ci'"); + if( isPlainText<0 ){ + isPlainText = db_get_boolean("timeline-plaintext",0); + } + db_bind_int(&q, ":x", rid); + if( db_step(&q)==SQLITE_ROW ){ + blob_append(pOut, "\n", 1); + if( isPlainText ){ + db_column_blob(&q, 0, pOut); + }else{ + Blob x; + blob_init(&x,0,0); + db_column_blob(&q, 0, &x); + get_stext_by_mimetype(&x, "text/x-fossil-wiki", pOut); + blob_reset(&x); + } + } + db_reset(&q); + break; + } + case 't': { /* Tickets */ + static Stmt q1; + static int iTitle = -1; + db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid"); + db_bind_int(&q1, ":rid", rid); + if( db_step(&q1)==SQLITE_ROW ){ + if( iTitle<0 ){ + int n = db_column_count(&q1); + for(iTitle=0; iTitle0 ){ + blob_reset(&cache.stext); + }else{ + blob_init(&cache.stext,0,0); + } + cache.cType = cType; + cache.rid = rid; + if( cType==0 ) return 0; + search_stext(cType, rid, zName, &cache.stext); + z = blob_str(&cache.stext); + for(i=0; z[i] && z[i]!='\n'; i++){} + cache.nTitle = i; + } + if( pnTitle ) *pnTitle = cache.nTitle; + return blob_str(&cache.stext); +} + +/* +** COMMAND: test-search-stext +** +** Usage: fossil test-search-stext TYPE ARG1 ARG2 +*/ +void test_search_stext(void){ + Blob out; + db_find_and_open_repository(0,0); + if( g.argc!=5 ) usage("TYPE RID NAME"); + search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out); + fossil_print("%s\n",blob_str(&out)); + blob_reset(&out); +} + +/* +** COMMAND: test-convert-stext +** +** Usage: fossil test-convert-stext FILE MIMETYPE +** +** Read the content of FILE and convert it to stext according to MIMETYPE. +** Send the result to standard output. +*/ +void test_convert_stext(void){ + Blob in, out; + db_find_and_open_repository(0,0); + if( g.argc!=4 ) usage("FILENAME MIMETYPE"); + blob_read_from_file(&in, g.argv[2]); + blob_init(&out, 0, 0); + get_stext_by_mimetype(&in, g.argv[3], &out); + fossil_print("%s\n",blob_str(&out)); + blob_reset(&in); + blob_reset(&out); +} + +/* The schema for the full-text index +*/ +static const char zFtsSchema[] = +@ -- One entry for each possible search result +@ CREATE TABLE IF NOT EXISTS "%w".ftsdocs( +@ rowid INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid +@ type CHAR(1), -- Type of document +@ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document +@ name TEXT, -- Additional document description +@ idxed BOOLEAN, -- True if currently in the index +@ label TEXT, -- Label to print on search results +@ url TEXT, -- URL to access this document +@ mtime DATE, -- Date when document created +@ bx TEXT, -- Temporary "body" content cache +@ UNIQUE(type,rid) +@ ); +@ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0; +@ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w'; +@ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS +@ SELECT rowid, type, rid, name, idxed, label, url, mtime, +@ title(type,rid,name) AS 'title', body(type,rid,name) AS 'body' +@ FROM ftsdocs; +@ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx +@ USING fts4(content="ftscontent", title, body%s); +; +static const char zFtsDrop[] = +@ DROP TABLE IF EXISTS "%w".ftsidx; +@ DROP VIEW IF EXISTS "%w".ftscontent; +@ DROP TABLE IF EXISTS "%w".ftsdocs; +; + +/* +** Create or drop the tables associated with a full-text index. +*/ +static int searchIdxExists = -1; +void search_create_index(void){ + const char *zDb = db_name("repository"); + int useStemmer = db_get_boolean("search-stemmer",0); + const char *zExtra = useStemmer ? ",tokenize=porter" : ""; + search_sql_setup(g.db); + db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w%s"*/, + zDb, zDb, zDb, zDb, zDb, zExtra/*safe-for-%s*/); + searchIdxExists = 1; +} +void search_drop_index(void){ + const char *zDb = db_name("repository"); + db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb); + searchIdxExists = 0; +} + +/* +** Return true if the full-text search index exists +*/ +int search_index_exists(void){ + if( searchIdxExists<0 ){ + searchIdxExists = db_table_exists("repository","ftsdocs"); + } + return searchIdxExists; +} + +/* +** Fill the FTSDOCS table with unindexed entries for everything +** in the repository. This uses INSERT OR IGNORE so entries already +** in FTSDOCS are unchanged. +*/ +void search_fill_index(void){ + if( !search_index_exists() ) return; + search_sql_setup(g.db); + db_multi_exec( + "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)" + " SELECT 'c', objid, 0 FROM event WHERE type='ci';" + ); + db_multi_exec( + "WITH latest_wiki(rid,name,mtime) AS (" + " SELECT tagxref.rid, substr(tag.tagname,6), max(tagxref.mtime)" + " FROM tag, tagxref" + " WHERE tag.tagname GLOB 'wiki-*'" + " AND tagxref.tagid=tag.tagid" + " AND tagxref.value>0" + " GROUP BY 2" + ") INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed)" + " SELECT 'w', rid, name, 0 FROM latest_wiki;" + ); + db_multi_exec( + "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)" + " SELECT 't', tkt_id, 0 FROM ticket;" + ); +} + +/* +** The document described by cType,rid,zName is about to be added or +** updated. If the document has already been indexed, then unindex it +** now while we still have access to the old content. Add the document +** to the queue of documents that need to be indexed or reindexed. +*/ +void search_doc_touch(char cType, int rid, const char *zName){ + if( search_index_exists() ){ + char zType[2]; + zType[0] = cType; + zType[1] = 0; + search_sql_setup(g.db); + db_multi_exec( + "DELETE FROM ftsidx WHERE docid IN" + " (SELECT rowid FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)", + zType, rid + ); + db_multi_exec( + "REPLACE INTO ftsdocs(type,rid,name,idxed)" + " VALUES(%Q,%d,%Q,0)", + zType, rid, zName + ); + if( cType=='w' ){ + db_multi_exec( + "DELETE FROM ftsidx WHERE docid IN" + " (SELECT rowid FROM ftsdocs WHERE type='w' AND name=%Q AND idxed)", + zName + ); + db_multi_exec( + "DELETE FROM ftsdocs WHERE type='w' AND name=%Q AND rid!=%d", + zName, rid + ); + } + } +} + +/* +** If the doc-glob and doc-br settings are valid for document search +** and if the latest check-in on doc-br is in the unindexed set of +** check-ins, then update all 'd' entries in FTSDOCS that have +** changed. +*/ +static void search_update_doc_index(void){ + const char *zDocBr = db_get("doc-branch","trunk"); + int ckid = zDocBr ? symbolic_name_to_rid(zDocBr,"ci") : 0; + double rTime; + char *zBrUuid; + if( ckid==0 ) return; + if( !db_exists("SELECT 1 FROM ftsdocs WHERE type='c' AND rid=%d" + " AND NOT idxed", ckid) ) return; + + /* If we get this far, it means that changes to 'd' entries are + ** required. */ + rTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", ckid); + zBrUuid = db_text("","SELECT substr(uuid,1,20) FROM blob WHERE rid=%d",ckid); + db_multi_exec( + "CREATE TEMP TABLE current_docs(rid INTEGER PRIMARY KEY, name);" + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" + "INSERT OR IGNORE INTO current_docs(rid, name)" + " SELECT blob.rid, foci.filename FROM foci, blob" + " WHERE foci.checkinID=%d AND blob.uuid=foci.uuid" + " AND %z", + ckid, glob_expr("foci.filename", db_get("doc-glob","")) + ); + db_multi_exec( + "DELETE FROM ftsidx WHERE docid IN" + " (SELECT rowid FROM ftsdocs WHERE type='d'" + " AND rid NOT IN (SELECT rid FROM current_docs))" + ); + db_multi_exec( + "DELETE FROM ftsdocs WHERE type='d'" + " AND rid NOT IN (SELECT rid FROM current_docs)" + ); + db_multi_exec( + "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,bx,url,mtime)" + " SELECT 'd', rid, name, 0," + " title('d',rid,name)," + " body('d',rid,name)," + " printf('/doc/%q/%%s',urlencode(name))," + " %.17g" + " FROM current_docs", + zBrUuid, rTime + ); + db_multi_exec( + "INSERT INTO ftsidx(docid,title,body)" + " SELECT rowid, label, bx FROM ftsdocs WHERE type='d' AND NOT idxed" + ); + db_multi_exec( + "UPDATE ftsdocs SET" + " idxed=1," + " bx=NULL," + " label='Document: '||label" + " WHERE type='d' AND NOT idxed" + ); +} + +/* +** Deal with all of the unindexed 'c' terms in FTSDOCS +*/ +static void search_update_checkin_index(void){ + db_multi_exec( + "INSERT INTO ftsidx(docid,title,body)" + " SELECT rowid, '', body('c',rid,NULL) FROM ftsdocs" + " WHERE type='c' AND NOT idxed;" + ); + db_multi_exec( + "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" + " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL," + " printf('Check-in [%%.16s] on %%s',blob.uuid,datetime(event.mtime))," + " printf('/timeline?y=ci&c=%%.20s',blob.uuid)," + " event.mtime" + " FROM ftsdocs, event, blob" + " WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed" + " AND event.objid=ftsdocs.rid" + " AND blob.rid=ftsdocs.rid" + ); +} + +/* +** Deal with all of the unindexed 't' terms in FTSDOCS +*/ +static void search_update_ticket_index(void){ + db_multi_exec( + "INSERT INTO ftsidx(docid,title,body)" + " SELECT rowid, title('t',rid,NULL), body('t',rid,NULL) FROM ftsdocs" + " WHERE type='t' AND NOT idxed;" + ); + if( db_changes()==0 ) return; + db_multi_exec( + "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" + " SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL," + " printf('Ticket: %%s (%%s)',title('t',tkt_id,null)," + " datetime(tkt_mtime))," + " printf('/tktview/%%.20s',tkt_uuid)," + " tkt_mtime" + " FROM ftsdocs, ticket" + " WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed" + " AND ticket.tkt_id=ftsdocs.rid" + ); +} + +/* +** Deal with all of the unindexed 'w' terms in FTSDOCS +*/ +static void search_update_wiki_index(void){ + db_multi_exec( + "INSERT INTO ftsidx(docid,title,body)" + " SELECT rowid, title('w',rid,NULL),body('w',rid,NULL) FROM ftsdocs" + " WHERE type='w' AND NOT idxed;" + ); + if( db_changes()==0 ) return; + db_multi_exec( + "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" + " SELECT ftsdocs.rowid, 1, 'w', ftsdocs.rid, ftsdocs.name," + " 'Wiki: '||ftsdocs.name," + " '/wiki?name='||urlencode(ftsdocs.name)," + " tagxref.mtime" + " FROM ftsdocs, tagxref" + " WHERE ftsdocs.type='w' AND NOT ftsdocs.idxed" + " AND tagxref.rid=ftsdocs.rid" + ); +} + +/* +** Deal with all of the unindexed entries in the FTSDOCS table - that +** is to say, all the entries with FTSDOCS.IDXED=0. Add them to the +** index. +*/ +void search_update_index(unsigned int srchFlags){ + if( !search_index_exists() ) return; + if( !db_exists("SELECT 1 FROM ftsdocs WHERE NOT idxed") ) return; + search_sql_setup(g.db); + if( srchFlags & (SRCH_CKIN|SRCH_DOC) ){ + search_update_doc_index(); + search_update_checkin_index(); + } + if( srchFlags & SRCH_TKT ){ + search_update_ticket_index(); + } + if( srchFlags & SRCH_WIKI ){ + search_update_wiki_index(); + } +} + +/* +** Construct, prepopulate, and then update the full-text index. +*/ +void search_rebuild_index(void){ + fossil_print("rebuilding the search index..."); + fflush(stdout); + search_create_index(); + search_fill_index(); + search_update_index(search_restrict(SRCH_ALL)); + fossil_print(" done\n"); +} + +/* +** COMMAND: fts-config* +** +** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT? +** +** The "fossil fts-config" command configures the full-text search capabilities +** of the repository. Subcommands: +** +** reindex Rebuild the search index. This is a no-op if +** index search is disabled +** +** index (on|off) Turn the search index on or off +** +** enable cdtw Enable various kinds of search. c=Check-ins, +** d=Documents, t=Tickets, w=Wiki. +** +** disable cdtw Disable versious kinds of search +** +** stemmer (on|off) Turn the Porter stemmer on or off for indexed +** search. (Unindexed search is never stemmed.) +** +** The current search settings are displayed after any changes are applied. +** Run this command with no arguments to simply see the settings. +*/ +void test_fts_cmd(void){ + static const struct { int iCmd; const char *z; } aCmd[] = { + { 1, "reindex" }, + { 2, "index" }, + { 3, "disable" }, + { 4, "enable" }, + { 5, "stemmer" }, + }; + static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = { + { "search-ckin", "check-in search:", "c" }, + { "search-doc", "document search:", "d" }, + { "search-tkt", "ticket search:", "t" }, + { "search-wiki", "wiki search:", "w" }, + }; + char *zSubCmd = 0; + int i, j, n; + int iCmd = 0; + int iAction = 0; + db_find_and_open_repository(0, 0); + if( g.argc>2 ){ + zSubCmd = g.argv[2]; + n = (int)strlen(zSubCmd); + for(i=0; i=ArraySize(aCmd) ){ + Blob all; + blob_init(&all,0,0); + for(i=0; i=1 ){ + search_drop_index(); + } + if( iAction>=2 ){ + search_rebuild_index(); + } + + /* Always show the status before ending */ + for(i=0; i #include "config.h" +#include #include "setup.h" + +#if INTERFACE +#define ArraySize(x) (sizeof(x)/sizeof(x[0])) +#endif /* ** The table of web pages supported by this application is generated ** automatically by the "mkindex" program and written into a file ** named "page_index.h". We include that file here to get access @@ -46,31 +50,47 @@ }else{ @ %h(zTitle) } @ %h(zDesc) } + + /* ** WEBPAGE: /setup */ void setup_page(void){ login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); } style_header("Server Administration"); /* Make sure the header contains . Issue a warning ** if it does not. */ if( !cgi_header_contains("Configuration Error: Please add - @ <base href="$baseurl/$current_page"> after - @ <head> in the HTML header!

      + @ <base href="$secureurl/$current_page"> after + @ <head> in the HTML header!

      } - @ +#if !defined(_WIN32) + /* Check for /dev/null and /dev/urandom. We want both devices to be present, + ** but they are sometimes omitted (by mistake) from chroot jails. */ + if( access("/dev/null", R_OK|W_OK) ){ + @

      WARNING: Device "/dev/null" is not available + @ for reading and writing.

      + } + if( access("/dev/urandom", R_OK) ){ + @

      WARNING: Device "/dev/urandom" is not available + @ for reading. This means that the pseudo-random number generator used + @ by SQLite will be poorly seeded.

      + } +#endif + + @
      setup_menu_entry("Users", "setup_ulist", "Grant privileges to individual users."); setup_menu_entry("Access", "setup_access", "Control access settings."); setup_menu_entry("Configuration", "setup_config", @@ -82,33 +102,31 @@ setup_menu_entry("Login-Group", "setup_login_group", "Manage single sign-on between this repository and others" " on the same server"); setup_menu_entry("Tickets", "tktsetup", "Configure the trouble-ticketing system for this repository"); + setup_menu_entry("Search","srchsetup", + "Configure the built-in search engine"); setup_menu_entry("Transfers", "xfersetup", "Configure the transfer system for this repository"); setup_menu_entry("Skins", "setup_skin", - "Select from a menu of prepackaged \"skins\" for the web interface"); - setup_menu_entry("CSS", "setup_editcss", - "Edit the Cascading Style Sheet used by all pages of this repository"); - setup_menu_entry("Header", "setup_header", - "Edit HTML text inserted at the top of every page"); - setup_menu_entry("Footer", "setup_footer", - "Edit HTML text inserted at the bottom of every page"); + "Select and/or modify the web interface \"skins\""); setup_menu_entry("Moderation", "setup_modreq", "Enable/Disable requiring moderator approval of Wiki and/or Ticket" " changes and attachments."); setup_menu_entry("Ad-Unit", "setup_adunit", "Edit HTML text for an ad unit inserted after the menu bar"); setup_menu_entry("Logo", "setup_logo", "Change the logo and background images for the server"); setup_menu_entry("Shunned", "shun", "Show artifacts that are shunned by this repository"); - setup_menu_entry("Log", "rcvfromlist", + setup_menu_entry("Artifact Receipts Log", "rcvfromlist", "A record of received artifacts and their sources"); - setup_menu_entry("User-Log", "access_log", + setup_menu_entry("User Log", "access_log", "A record of login attempts"); + setup_menu_entry("Administrative Log", "admin_log", + "View the admin_log entries"); setup_menu_entry("Stats", "stat", "Display repository statistics"); setup_menu_entry("SQL", "admin_sql", "Enter raw SQL commands"); setup_menu_entry("TH1", "admin_th1", @@ -128,11 +146,11 @@ Stmt s; int prevLevel = 0; login_check_credentials(); if( !g.perm.Admin ){ - login_needed(); + login_needed(0); return; } style_submenu_element("Add", "Add User", "setup_uedit"); style_header("User List"); @@ -145,11 +163,11 @@ "SELECT uid, login, cap, info, 1 FROM user" " WHERE login IN ('anonymous','nobody','developer','reader') " " UNION ALL " "SELECT uid, login, cap, info, 2 FROM user" " WHERE login NOT IN ('anonymous','nobody','developer','reader') " - "ORDER BY 5, 2" + "ORDER BY 5, 2 COLLATE nocase" ); while( db_step(&s)==SQLITE_ROW ){ int iLevel = db_column_int(&s, 4); const char *zCap = db_column_text(&s, 2); const char *zLogin = db_column_text(&s, 1); @@ -249,14 +267,11 @@ @ @ @ @ @ - @ + @ @
      wWrite-Tkt: Edit tickets
      xPrivate: Push and/or pull private branches
      zZip download: Download a baseline via the - @ /zip URL even without - @ checkout - @ and history permissions
      Zip download: Download a ZIP archive or tarball
      @ @ @
    • @ Every user, logged in or not, inherits the privileges of @@ -280,10 +295,11 @@ @

    • @ @ @ style_footer(); + db_finalize(&s); } /* ** Return true if zPw is a valid password string. A valid ** password string is: @@ -309,16 +325,16 @@ int uid, i; int higherUser = 0; /* True if user being edited is SETUP and the */ /* user doing the editing is ADMIN. Disallow editing */ char *inherit[128]; int a[128]; - char *oa[128]; + const char *oa[128]; /* Must have ADMIN privileges to access this page */ login_check_credentials(); - if( !g.perm.Admin ){ login_needed(); return; } + if( !g.perm.Admin ){ login_needed(0); return; } /* Check to see if an ADMIN user is trying to edit a SETUP account. ** Don't allow that. */ zId = PD("id", "0"); @@ -328,11 +344,11 @@ zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid); higherUser = zOldCaps && strchr(zOldCaps,'s'); } if( P("can") ){ - cgi_redirect("setup_ulist"); + cgi_redirect("setup_ulist"); /* User pressed the Cancel button */ return; } /* If we have all the necessary information, write the new or ** modified user record. After writing the user record, redirect @@ -365,13 +381,11 @@ zPw = sha1_shared_secret(zPw, zLogin, 0); }else{ zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid); } zOldLogin = db_text(0, "SELECT login FROM user WHERE uid=%d", uid); - if( uid>0 && - db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid) - ){ + if( db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid) ){ style_header("User Creation Error"); @ Login "%h(zLogin)" is already used by @ a different user. @ @

      [Bummer]

      @@ -379,13 +393,15 @@ return; } login_verify_csrf_secret(); db_multi_exec( "REPLACE INTO user(uid,login,info,pw,cap,mtime) " - "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s',now())", - uid, P("login"), P("info"), zPw, zCap + "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())", + uid, zLogin, P("info"), zPw, zCap ); + admin_log( "Updated user [%q] with capabilities [%q].", + zLogin, zCap ); if( atoi(PD("all","0"))>0 ){ Blob sql; char *zErr = 0; blob_zero(&sql); if( zOldLogin==0 ){ @@ -407,12 +423,16 @@ zLogin, P("pw"), zLogin, P("info"), zCap, zOldLogin ); login_group_sql(blob_str(&sql), "
    • ", "
    • \n", &zErr); blob_reset(&sql); + admin_log( "Updated user [%q] in all login groups " + "with capabilities [%q].", + zLogin, zCap ); if( zErr ){ style_header("User Change Error"); + admin_log( "Error updating user '%q': %s'.", zLogin, zErr ); @ %s(zErr) @ @

      [Bummer]

      style_footer(); return; @@ -445,53 +465,95 @@ if( fossil_strcmp(zLogin, "developer") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='developer'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = - ""; + "[D]"; } free(z2); } if( fossil_strcmp(zLogin, "reader") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='reader'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = - ""; + "[R]"; } free(z2); } if( fossil_strcmp(zLogin, "anonymous") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='anonymous'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = - ""; + "[A]"; } free(z2); } if( fossil_strcmp(zLogin, "nobody") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='nobody'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = - ""; + "[N]"; } free(z2); } /* Begin generating the page */ style_submenu_element("Cancel", "Cancel", "setup_ulist"); if( uid ){ - style_header(mprintf("Edit User %h", zLogin)); + style_header("Edit User %h", zLogin); }else{ style_header("Add A New User"); } @
      @
      login_insert_csrf_secret(); + if( login_is_special(zLogin) ){ + @ + @ + @ + } + @ @ @ @ if( uid ){ @ @@ -499,88 +561,126 @@ @ } @ @ @ - @ - @ - @ - @ - @ + if( login_is_special(zLogin) ){ + @ + }else{ + @ + @ + @ + @ + @ + } @ @ @ @ + @
      User ID:%d(uid) (new user)
      Login:
      Contact Info:%h(zLogin)
      Contact Info:
      Capabilities: #define B(x) inherit[x] @
      if( g.perm.Setup ){ - @
      - } - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @
      - @ - @
      + @
      + } + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @
      + @ + @
      @ @ @ - @ Password: - if( zPw[0] ){ - /* Obscure the password for all users */ - @ - }else{ - /* Show an empty password as an empty input field */ - @ - } + @ Selected Cap.: + @ + @ (missing JS?) + @ @ + if( !login_is_special(zLogin) ){ + @ + @ Password: + if( zPw[0] ){ + /* Obscure the password for all users */ + @ + }else{ + /* Show an empty password as an empty input field */ + @ + } + @ + } zGroup = login_group_name(); if( zGroup ){ @ @ Scope: @ @@ -597,14 +697,15 @@ @ } @ @
      @
      + @ @

      Privileges And Capabilities:

      @
        if( higherUser ){ - @
      • + @

      • @ User %h(zLogin) has Setup privileges and you only have Admin privileges @ so you are not permitted to make changes to %h(zLogin). @

      • @ } @@ -615,30 +716,30 @@ @ and reset user passwords. Both automatically get all other privileges @ listed below. Use these two settings with discretion. @

        @ @
      • - @ The "" mark + @ The "N" subscript suffix @ indicates the privileges of nobody that @ are available to all users regardless of whether or not they are logged in. @

      • @ @
      • - @ The "" mark + @ The "A" subscript suffix @ indicates the privileges of anonymous that @ are inherited by all logged-in users. @

      • @ @
      • - @ The "" mark + @ The "D" subscript suffix @ indicates the privileges of developer that @ are inherited by all users with the @ Developer privilege. @

      • @ @
      • - @ The "" mark + @ The "R" subscript suffix @ indicates the privileges of reader that @ are inherited by all users with the Reader @ privilege. @

      • @ @@ -724,17 +825,13 @@ @
          @
        • @ No login is required for user nobody. The @ capabilities of the nobody user are @ inherited by all users, regardless of whether or not they are logged in. - @ To disable universal access to the repository, make sure no user named - @ nobody exists or that the + @ To disable universal access to the repository, make sure that the @ nobody user has no capabilities - @ enabled. The password for nobody is ignore. - @ To avoid problems with spiders overloading the server, it is recommended - @ that the h (Hyperlinks) capability be - @ turned off for the nobody user. + @ enabled. The password for nobody is ignored. @

        • @ @
        • @ Login is required for user anonymous but the @ password is displayed on the login screen beside the password entry box @@ -781,10 +878,12 @@ if( zQ ){ int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ); if( iQ!=iVal ){ login_verify_csrf_secret(); db_set(zVar, iQ ? "1" : "0", 0); + admin_log("Set option [%q] to [%q].", + zVar, iQ ? "on" : "off"); iVal = iQ; } } @ 20 ? "..." : "")); zVal = zQ; } - @ %s(zLabel) } /* ** Generate a text box for an attribute. */ -static void textarea_attribute( +const char *textarea_attribute( const char *zLabel, /* The text label on the textarea */ int rows, /* Rows in the textarea */ int cols, /* Columns in the textarea */ const char *zVar, /* The corresponding row in the VAR table */ const char *zQP, /* The query parameter */ @@ -834,12 +936,15 @@ int disabled /* 1 if the textarea should not be editable */ ){ const char *z = db_get(zVar, (char*)zDflt); const char *zQ = P(zQP); if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){ + const int nZQ = (int)strlen(zQ); login_verify_csrf_secret(); db_set(zVar, zQ, 0); + admin_log("Set textarea_attribute %Q to: %.*s%s", + zVar, 20, zQ, (nZQ>20 ? "..." : "")); z = zQ; } if( rows>0 && cols>0 ){ @ if( zLabel && *zLabel ){ @ %s(zLabel) } } + return z; +} + +/* +** Generate a text box for an attribute. +*/ +static void multiple_choice_attribute( + const char *zLabel, /* The text label on the menu */ + const char *zVar, /* The corresponding row in the VAR table */ + const char *zQP, /* The query parameter */ + const char *zDflt, /* Default value if VAR table entry does not exist */ + int nChoice, /* Number of choices */ + const char *const *azChoice /* Choices. 2 per choice: (VAR value, Display) */ +){ + const char *z = db_get(zVar, (char*)zDflt); + const char *zQ = P(zQP); + int i; + if( zQ && fossil_strcmp(zQ,z)!=0){ + const int nZQ = (int)strlen(zQ); + login_verify_csrf_secret(); + db_set(zVar, zQ, 0); + admin_log("Set multiple_choice_attribute %Q to: %.*s%s", + zVar, 20, zQ, (nZQ>20 ? "..." : "")); + z = zQ; + } + @ %h(zLabel) } /* ** WEBPAGE: setup_access */ void setup_access(void){ login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } style_header("Access Control Settings"); db_begin_transaction(); @

          login_insert_csrf_secret(); @
          + onoff_attribute("Redirect to HTTPS on the Login page", + "redirect-to-https", "redirhttps", 0, 0); + @

          When selected, force the use of HTTPS for the Login page. + @

          Details: When enabled, this option causes the $secureurl TH1 + @ variable is set to an "https:" variant of $baseurl. Otherwise, + @ $secureurl is just an alias for $baseurl. Also when enabled, the + @ Login page redirects to https if accessed via http. + @


          onoff_attribute("Require password for local access", "localauth", "localauth", 0, 0); @

          When enabled, the password sign-in is always required for @ web access. When disabled, unrestricted web access from 127.0.0.1 - @ is allowed for the fossil ui command or - @ from the fossil server, - @ fossil http commands when the + @ is allowed for the fossil ui command or + @ from the fossil server, + @ fossil http commands when the @ "--localauth" command line options is used, or from the - @ fossil cgi if a line containing + @ fossil cgi if a line containing @ the word "localauth" appears in the CGI script. @ @

          A password is always required if any one or more @ of the following are true: @

            @
          1. This button is checked @
          2. The inbound TCP/IP connection is not from 127.0.0.1 @
          3. The server is started using either of the - @ fossil server or - @ fossil http commands + @ fossil server or + @ fossil http commands @ without the "--localauth" option. @
          4. The server is started from CGI without the "localauth" keyword @ in the CGI script. @
          @ @@ -938,10 +1083,21 @@ @

          Fossil tries to spend less than this many seconds gathering @ the out-bound data of sync, clone, and pull packets. @ If the client request takes longer, a partial reply is given similar @ to the download packet limit. 30s is a reasonable default.

          + @
          + entry_attribute("Server Load Average Limit", 11, "max-loadavg", "mxldavg", + "0.0", 0); + @

          Some expensive operations (such as computing tarballs, zip archives, + @ or annotation/blame pages) are prohibited if the load average on the host + @ computer is too large. Set the threshold for disallowing expensive + @ computations here. Set this to 0.0 to disable the load average limit. + @ This limit is only enforced on Unix servers. On Linux systems, + @ access to the /proc virtual filesystem is required, which means this limit + @ might not work inside a chroot() jail.

          + @
          onoff_attribute( "Enable hyperlinks for \"nobody\" based on User-Agent and Javascript", "auto-hyperlink", "autohyperlink", 1, 0); @

          Enable hyperlinks (the equivalent of the "h" permission) for all users @@ -948,21 +1104,26 @@ @ including user "nobody", as long as (1) the User-Agent string in the @ HTTP header indicates that the request is coming from an actual human @ being and not a a robot or spider and (2) the user agent is able to @ run Javascript in order to set the href= attribute of hyperlinks. Bots @ and spiders can forge a User-Agent string that makes them seem to be a - @ normal browser and they can run javascript just like browsers. But most - @ bots do not go to that much trouble so this is normally an effective defense.

          + @ normal browser and they can run javascript just like browsers. But most + @ bots do not go to that much trouble so this is normally an effective + @ defense.

          @ @

          You do not normally want a bot to walk your entire repository because @ if it does, your server will end up computing diffs and annotations for @ every historical version of every file and creating ZIPs and tarballs of @ every historical check-in, which can use a lot of CPU and bandwidth @ even for relatively small projects.

          - @ + @ @

          Additional parameters that control this behavior:

          @
          + onoff_attribute("Enable hyperlinks for humans (as deduced from the UserAgent " + " HTTP header string)", + "auto-hyperlink-ishuman", "ahis", 0, 0); + @
          onoff_attribute("Require mouse movement before enabling hyperlinks", "auto-hyperlink-mouseover", "ahmo", 0, 0); @
          entry_attribute("Delay before enabling hyperlinks (milliseconds)", 5, "auto-hyperlink-delay", "ah-delay", "10", 0); @@ -1038,14 +1199,15 @@ const char *zPw = PD("pw", ""); const char *zNewName = PD("newname", "New Login Group"); login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } file_canonical_name(g.zRepositoryName, &fullName, 0); - zSelfRepo = mprintf(blob_str(&fullName)); + zSelfRepo = fossil_strdup(blob_str(&fullName)); blob_reset(&fullName); if( P("join")!=0 ){ login_group_join(zRepo, zLogin, zPw, zNewName, &zErrMsg); }else if( P("leave") ){ login_group_leave(&zErrMsg); @@ -1114,10 +1276,27 @@ @

          login_insert_csrf_secret(); @ To leave this login group press @ @

          + @

          Implementation Details

          + @

          The following are fields from the CONFIG table related to login-groups, + @ provided here for instructional and debugging purposes:

          + @ + @ + db_prepare(&q, "SELECT name, value, datetime(mtime,'unixepoch') FROM config" + " WHERE name GLOB 'peer-*'" + " OR name GLOB 'project-*'" + " ORDER BY name"); + while( db_step(&q)==SQLITE_ROW ){ + @ + @ + @ + } + db_finalize(&q); + @
          Config.NameConfig.ValueConfig.mtime
          %h(db_column_text(&q,0))%h(db_column_text(&q,1))%h(db_column_text(&q,2))
          + output_table_sorting_javascript("configTab","ttt",1); } style_footer(); } /* @@ -1124,13 +1303,21 @@ ** WEBPAGE: setup_timeline */ void setup_timeline(void){ double tmDiff; char zTmDiff[20]; + static const char *const azTimeFormats[] = { + "0", "HH:MM", + "1", "HH:MM:SS", + "2", "YYYY-MM-DD HH:MM", + "3", "YYMMDD HH:MM", + "4", "(off)" + }; login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } style_header("Timeline Display Preferences"); db_begin_transaction(); @
          @@ -1144,18 +1331,18 @@ @
          onoff_attribute("Plaintext comments on timelines", "timeline-plaintext", "tpt", 0, 0); @

          In timeline displays, check-in comments are displayed literally, - @ without any wiki or HTML interpretation.

          + @ without any wiki or HTML interpretation. (Note: Use CSS to change + @ display formatting features such as fonts and line-wrapping behavior.)

          @
          onoff_attribute("Use Universal Coordinated Time (UTC)", "timeline-utc", "utc", 1, 0); @

          Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or @ Zulu) instead of in local time. On this server, local time is currently - g.fTimeFormat = 2; tmDiff = db_double(0.0, "SELECT julianday('now')"); tmDiff = db_double(0.0, "SELECT (julianday(%.17g,'localtime')-julianday(%.17g))*24.0", tmDiff, tmDiff); sqlite3_snprintf(sizeof(zTmDiff), zTmDiff, "%.1f", tmDiff); @@ -1167,14 +1354,22 @@ @ %s(zTmDiff) hours behind UTC.

          }else{ @ %s(zTmDiff) hours ahead of UTC.

          } + @
          + multiple_choice_attribute("Per-Item Time Format", "timeline-date-format", + "tdf", "0", ArraySize(azTimeFormats)/2, azTimeFormats); + @

          If the "HH:MM" or "HH:MM:SS" format is selected, then the date is shown + @ in a separate box (using CSS class "timelineDate") whenever the date changes. + @ With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats, the complete date + @ and time is shown on every timeline entry (using the CSS class "timelineTime").

          + @
          onoff_attribute("Show version differences by default", "show-version-diffs", "vdiff", 0, 0); - @

          On the version-information pages linked from the timeline can either + @

          The version-information pages linked from the timeline can either @ show complete diffs of all file changes, or can just list the names of @ the files that have changed. Users can get to either page by @ clicking. This setting selects the default.

          @
          @@ -1192,31 +1387,36 @@ /* ** WEBPAGE: setup_settings */ void setup_settings(void){ - struct stControlSettings const *pSet; + Setting const *pSet; login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } (void) aCmdHelp; /* NOTE: Silence compiler warning. */ style_header("Settings"); - db_open_local(0); + if(!g.repositoryOpen){ + /* Provide read-only access to versioned settings, + but only if no repo file was explicitly provided. */ + db_open_local(0); + } db_begin_transaction(); @

          This page provides a simple interface to the "fossil setting" command. @ See the "fossil help setting" output below for further information on @ the meaning of each setting.


          @
          @
          login_insert_csrf_secret(); - for(pSet=ctrlSettings; pSet->name!=0; pSet++){ + for(pSet=aSetting; pSet->name!=0; pSet++){ if( pSet->width==0 ){ int hasVersionableValue = pSet->versionable && - (db_get_do_versionable(pSet->name, NULL)!=0); + (db_get_versioned(pSet->name, NULL)!=0); onoff_attribute(pSet->name, pSet->name, pSet->var!=0 ? pSet->var : pSet->name, is_truth(pSet->def), hasVersionableValue); if( pSet->versionable ){ @ (v)
          @@ -1223,32 +1423,41 @@ } else { @
          } } } + @
          @
          - for(pSet=ctrlSettings; pSet->name!=0; pSet++){ - if( pSet->width!=0 && !pSet->versionable){ + for(pSet=aSetting; pSet->name!=0; pSet++){ + if( pSet->width!=0 && !pSet->versionable && !pSet->forceTextArea ){ entry_attribute(pSet->name, /*pSet->width*/ 25, pSet->name, pSet->var!=0 ? pSet->var : pSet->name, (char*)pSet->def, 0); @
          } + } + for(pSet=aSetting; pSet->name!=0; pSet++){ + if( pSet->width!=0 && !pSet->versionable && pSet->forceTextArea ){ + @%s(pSet->name)
          + textarea_attribute("", /*rows*/ 3, /*cols*/ 50, pSet->name, + pSet->var!=0 ? pSet->var : pSet->name, + (char*)pSet->def, 0); + @
          + } } @
          - for(pSet=ctrlSettings; pSet->name!=0; pSet++){ - int hasVersionableValue = db_get_do_versionable(pSet->name, NULL)!=0; - if( pSet->width!=0 && pSet->versionable){ + for(pSet=aSetting; pSet->name!=0; pSet++){ + if( pSet->width!=0 && pSet->versionable ){ + int hasVersionableValue = db_get_versioned(pSet->name, NULL)!=0; @%s(pSet->name) (v)
          textarea_attribute("", /*rows*/ 3, /*cols*/ 20, pSet->name, pSet->var!=0 ? pSet->var : pSet->name, (char*)pSet->def, hasVersionableValue); @
          } } @
          - @

          @
          @

          Settings marked with (v) are 'versionable' and will be overridden @ by the contents of files named .fossil-settings/PROPERTY. @ If such a file is present, the corresponding field above is not @ editable.


          @@ -1263,26 +1472,35 @@ ** WEBPAGE: setup_config */ void setup_config(void){ login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } style_header("WWW Configuration"); db_begin_transaction(); @

          login_insert_csrf_secret(); @
          entry_attribute("Project Name", 60, "project-name", "pn", "", 0); @

          Give your project a name so visitors know what this site is about. - @ The project name will also be used as the RSS feed title.

          + @ The project name will also be used as the RSS feed title. + @

          @
          textarea_attribute("Project Description", 3, 80, "project-description", "pd", "", 0); @

          Describe your project. This will be used in page headers for search @ engines as well as a short RSS description.

          + @
          + entry_attribute("Tarball and ZIP-archive Prefix", 20, "short-project-name", "spn", "", 0); + @

          This is used as a prefix on the names of generated tarballs and ZIP archive. + @ For best results, keep this prefix brief and avoid special characters such + @ as "/" and "\". + @ If no tarball prefix is specified, then the full Project Name above is used. + @

          @
          onoff_attribute("Enable WYSIWYG Wiki Editing", "wysiwyg-wiki", "wysiwyg-wiki", 0, 0); @

          Enable what-you-see-is-what-you-get (WYSIWYG) editing of wiki pages. @ The WYSIWYG editor generates HTML instead of markup, which makes @@ -1327,157 +1545,18 @@ @

          db_end_transaction(0); style_footer(); } -/* -** WEBPAGE: setup_editcss -*/ -void setup_editcss(void){ - login_check_credentials(); - if( !g.perm.Setup ){ - login_needed(); - } - db_begin_transaction(); - if( P("clear")!=0 ){ - db_multi_exec("DELETE FROM config WHERE name='css'"); - cgi_replace_parameter("css", zDefaultCSS); - db_end_transaction(0); - cgi_redirect("setup_editcss"); - } - if( P("submit")!=0 ){ - textarea_attribute(0, 0, 0, "css", "css", zDefaultCSS, 0); - db_end_transaction(0); - cgi_redirect("setup_editcss"); - } - style_header("Edit CSS"); - @
          - login_insert_csrf_secret(); - @ Edit the CSS below:
          - textarea_attribute("", 35, 80, "css", "css", zDefaultCSS, 0); - @
          - @ - @ - @
          - @

          Note: Press your browser Reload button after - @ modifying the CSS in order to pull in the modified CSS file.

          - @
          - @ The default CSS is shown below for reference. Other examples - @ of CSS files can be seen on the skins page. - @ See also the header and - @ footer editing screens. - @
          -  cgi_append_default_css();
          -  @ 
          - style_footer(); - db_end_transaction(0); -} - -/* -** WEBPAGE: setup_header -*/ -void setup_header(void){ - login_check_credentials(); - if( !g.perm.Setup ){ - login_needed(); - } - db_begin_transaction(); - if( P("clear")!=0 ){ - db_multi_exec("DELETE FROM config WHERE name='header'"); - cgi_replace_parameter("header", zDefaultHeader); - }else if( P("submit")!=0 ){ - textarea_attribute(0, 0, 0, "header", "header", zDefaultHeader, 0); - }else if( P("fixbase")!=0 ){ - const char *z = db_get("header", (char*)zDefaultHeader); - char *zHead = strstr(z, ""); - if( strstr(z, "\n%s", - zHead+6-z, z, zTail); - cgi_replace_parameter("header", zNew); - db_set("header", zNew, 0); - } - } - - style_header("Edit Page Header"); - @
          - - /* Make sure the header contains . Issue a warning - ** if it does not. */ - if( !cgi_header_contains("Please add - @ <base href="$baseurl/$current_page"> after - @ <head> in the header! - @

          - } - - login_insert_csrf_secret(); - @

          Edit HTML text with embedded TH1 (a TCL dialect) that will be used to - @ generate the beginning of every page through start of the main - @ menu.

          - textarea_attribute("", 35, 80, "header", "header", zDefaultHeader, 0); - @
          - @ - @ - @
          - @
          - @ The default header is shown below for reference. Other examples - @ of headers can be seen on the skins page. - @ See also the CSS and - @ footer editing screeens. - @
          -  @ %h(zDefaultHeader)
          -  @ 
          - style_footer(); - db_end_transaction(0); -} - -/* -** WEBPAGE: setup_footer -*/ -void setup_footer(void){ - login_check_credentials(); - if( !g.perm.Setup ){ - login_needed(); - } - db_begin_transaction(); - if( P("clear")!=0 ){ - db_multi_exec("DELETE FROM config WHERE name='footer'"); - cgi_replace_parameter("footer", zDefaultFooter); - } - - style_header("Edit Page Footer"); - @
          - login_insert_csrf_secret(); - @

          Edit HTML text with embedded TH1 (a TCL dialect) that will be used to - @ generate the end of every page.

          - textarea_attribute("", 20, 80, "footer", "footer", zDefaultFooter, 0); - @
          - @ - @ - @
          - @
          - @ The default footer is shown below for reference. Other examples - @ of footers can be seen on the skins page. - @ See also the CSS and - @ header editing screens. - @
          -  @ %h(zDefaultFooter)
          -  @ 
          - style_footer(); - db_end_transaction(0); -} - /* ** WEBPAGE: setup_modreq */ void setup_modreq(void){ login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } style_header("Moderator For Wiki And Tickets"); db_begin_transaction(); @
          @@ -1484,22 +1563,22 @@ login_insert_csrf_secret(); @
          onoff_attribute("Moderate ticket changes", "modreq-tkt", "modreq-tkt", 0, 0); @

          When enabled, any change to tickets is subject to the approval - @ a ticket moderator - a user with the "q" or Mod-Tkt privilege. + @ by a ticket moderator - a user with the "q" or Mod-Tkt privilege. @ Ticket changes enter the system and are shown locally, but are not @ synced until they are approved. The moderator has the option to @ delete the change rather than approve it. Ticket changes made by - @ a user who hwas the Mod-Tkt privilege are never subject to + @ a user who has the Mod-Tkt privilege are never subject to @ moderation. @ @


          onoff_attribute("Moderate wiki changes", "modreq-wiki", "modreq-wiki", 0, 0); @

          When enabled, any change to wiki is subject to the approval - @ a ticket moderator - a user with the "l" or Mod-Wiki privilege. + @ by a wiki moderator - a user with the "l" or Mod-Wiki privilege. @ Wiki changes enter the system and are shown locally, but are not @ synced until they are approved. The moderator has the option to @ delete the change rather than approve it. Wiki changes made by @ a user who has the Mod-Wiki privilege are never subject to @ moderation. @@ -1517,11 +1596,12 @@ ** WEBPAGE: setup_adunit */ void setup_adunit(void){ login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } db_begin_transaction(); if( P("clear")!=0 ){ db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'"); cgi_replace_parameter("adunit",""); @@ -1528,13 +1608,15 @@ } style_header("Edit Ad Unit"); @

          login_insert_csrf_secret(); - @

          Edit HTML text for an ad unit that will be inserted after the - @ menu bar and above the content of every page.

          - textarea_attribute("", 20, 80, "adunit", "adunit", "", 0); + @ Banner Ad-Unit:
          + textarea_attribute("", 6, 80, "adunit", "adunit", "", 0); + @
          + @ Right-Column Ad-Unit:
          + textarea_attribute("", 6, 80, "adunit-right", "adright", "", 0); @
          onoff_attribute("Omit ads to administrator", "adunit-omit-if-admin", "oia", 0, 0); @
          onoff_attribute("Omit ads to logged-in users", @@ -1541,21 +1623,54 @@ "adunit-omit-if-user", "oiu", 0, 0); @
          @ @ @
          + @
          + @ Ad-Unit Notes:
            + @
          • Leave both Ad-Units blank to disable all advertising. + @
          • The "Banner Ad-Unit" is used for wide pages. + @
          • The "Right-Column Ad-Unit" is used on pages with tall, narrow content. + @
          • If the "Right-Column Ad-Unit" is blank, the "Banner Ad-Unit" is used on all pages. + @
          • Suggested CSS changes: + @
            +  @ div.adunit_banner {
            +  @   margin: auto;
            +  @   width: 100%;
            +  @ }
            +  @ div.adunit_right {
            +  @   float: right;
            +  @ }
            +  @ div.adunit_right_container {
            +  @   min-height: height-of-right-column-ad-unit;
            +  @ }
            +  @ 
            + @
          • For a place-holder Ad-Unit for testing, Copy/Paste the following + @ with appropriate adjustments to "width:" and "height:". + @
            +  @ <div style='
            +  @   margin: 0 auto;
            +  @   width: 600px;
            +  @   height: 90px;
            +  @   border: 1px solid #f11;
            +  @   background-color: #fcc;
            +  @ '>Demo Ad</div>
            +  @ 
            + @
          • style_footer(); db_end_transaction(0); } /* ** WEBPAGE: setup_logo */ void setup_logo(void){ + const char *zLogoMtime = db_get_mtime("logo-image", 0, 0); const char *zLogoMime = db_get("logo-mimetype","image/gif"); const char *aLogoImg = P("logoim"); int szLogoImg = atoi(PD("logoim:bytes","0")); + const char *zBgMtime = db_get_mtime("background-image", 0, 0); const char *zBgMime = db_get("background-mimetype","image/gif"); const char *aBgImg = P("bgim"); int szBgImg = atoi(PD("bgim:bytes","0")); if( szLogoImg>0 ){ zLogoMime = PD("logoim:mimetype","image/gif"); @@ -1563,11 +1678,12 @@ if( szBgImg>0 ){ zBgMime = PD("bgim:mimetype","image/gif"); } login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } db_begin_transaction(); if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){ Blob img; Stmt ins; @@ -1619,20 +1735,20 @@ cgi_redirect("setup_logo"); } style_header("Edit Project Logo And Background"); @

            The current project logo has a MIME-Type of %h(zLogoMime) @ and looks like this:

            - @

            logo + @

            logo @

            @ @
            @

            The logo is accessible to all users at this URL: @ %s(g.zBaseURL)/logo. @ The logo may or may not appear on each - @ page depending on the CSS and - @ header setup. + @ page depending on the CSS and + @ header setup. @ To change the logo image, use the following form:

            login_insert_csrf_secret(); @ Logo Image file: @ @

            @@ -1641,20 +1757,20 @@ @

            @
            @ @

            The current background image has a MIME-Type of %h(zBgMime) @ and looks like this:

            - @

            background + @

            background @

            @ @
            @

            The background image is accessible to all users at this URL: @ %s(g.zBaseURL)/background. @ The background image may or may not appear on each - @ page depending on the CSS and - @ header setup. + @ page depending on the CSS and + @ header setup. @ To change the background image, use the following form:

            login_insert_csrf_secret(); @ Background image file: @ @

            @@ -1702,11 +1818,12 @@ void sql_page(void){ const char *zQ = P("q"); int go = P("go")!=0; login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } db_begin_transaction(); style_header("Raw SQL Commands"); @

            Caution: There are no restrictions on the SQL that can be @ run by this page. You can do serious and irrepairable damage to the @@ -1823,11 +1940,12 @@ void th1_page(void){ const char *zQ = P("q"); int go = P("go")!=0; login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } db_begin_transaction(); style_header("Raw TH1 Commands"); @

            Caution: There are no restrictions on the TH1 that can be @ run by this page. If Tcl integration was enabled at compile-time and @@ -1853,5 +1971,159 @@ @

            %h(zR)
            } } style_footer(); } + +static void admin_log_render_limits(){ + int const count = db_int(0,"SELECT COUNT(*) FROM admin_log"); + int i; + int limits[] = { + 10, 20, 50, 100, 250, 500, 0 + }; + for(i = 0; limits[i]; ++i ){ + cgi_printf("%s%d", + i ? " " : "", + limits[i], limits[i]); + if(limits[i]>count) break; + } +} + +/* +** WEBPAGE: admin_log +** +** Shows the contents of the admin_log table, which is only created if +** the admin-log setting is enabled. Requires Admin or Setup ('a' or +** 's') permissions. +*/ +void page_admin_log(){ + Stmt stLog = empty_Stmt; + Blob qLog = empty_blob; + int limit; + int fLogEnabled; + int counter = 0; + login_check_credentials(); + if( !g.perm.Setup && !g.perm.Admin ){ + login_needed(0); + return; + } + style_header("Admin Log"); + create_admin_log_table(); + limit = atoi(PD("n","20")); + fLogEnabled = db_get_boolean("admin-log", 0); + @
            Admin logging is %s(fLogEnabled?"on":"off").
            + + + @
            Limit results to: + admin_log_render_limits(); + @
            + + blob_append_sql(&qLog, + "SELECT datetime(time,'unixepoch'), who, page, what " + "FROM admin_log " + "ORDER BY time DESC "); + if(limit>0){ + @ %d(limit) Most recent entries: + blob_append_sql(&qLog, "LIMIT %d", limit); + } + db_prepare(&stLog, "%s", blob_sql_text(&qLog)); + blob_reset(&qLog); + @ + @ + @ + @ + @ + @ + @ + while( SQLITE_ROW == db_step(&stLog) ){ + const char *zTime = db_column_text(&stLog, 0); + const char *zUser = db_column_text(&stLog, 1); + const char *zPage = db_column_text(&stLog, 2); + const char *zMessage = db_column_text(&stLog, 3); + @ + @ + @ + @ + @ + @ + } + @
            TimeUserPageMessage
            %s(zTime)%s(zUser)%s(zPage)%h(zMessage)
            + if(limit>0 && counter%d(counter) entries shown.
            + } + style_footer(); +} + +/* +** WEBPAGE: srchsetup +** +** Configure the search engine. +*/ +void page_srchsetup(){ + login_check_credentials(); + if( !g.perm.Setup && !g.perm.Admin ){ + login_needed(0); + return; + } + style_header("Search Configuration"); + @
            + login_insert_csrf_secret(); + @
            + @ Server-specific settings that affect the + @ /search webpage. + @
            + @
            + textarea_attribute("Document Glob List", 3, 35, "doc-glob", "dg", "", 0); + @

            The "Document Glob List" is a comma- or newline-separated list + @ of GLOB expressions that identify all documents within the source + @ tree that are to be searched when "Document Search" is enabled. + @ Some examples: + @ + @ + @ + @ + @ + @
            *.wiki,*.html,*.md,*.txt + @ Search all wiki, HTML, Markdown, and Text files
            doc/*.md,*/README.txt,README.txt + @ Search all Markdown files in the doc/ subfolder and all README.txt + @ files.
            *Search all checked-in files
            (blank) + @ Search nothing. (Disables document search).
            + @


            + entry_attribute("Document Branch", 20, "doc-branch", "db", "trunk", 0); + @

            When searching documents, use the versions of the files found at the + @ type of the "Document Branch" branch. Recommended value: "trunk". + @ Document search is disabled if blank. + @


            + onoff_attribute("Search Check-in Comments", "search-ci", "sc", 0, 0); + @
            + onoff_attribute("Search Documents", "search-doc", "sd", 0, 0); + @
            + onoff_attribute("Search Tickets", "search-tkt", "st", 0, 0); + @
            + onoff_attribute("Search Wiki","search-wiki", "sw", 0, 0); + @
            + @

            + @
            + if( P("fts0") ){ + search_drop_index(); + }else if( P("fts1") ){ + search_drop_index(); + search_create_index(); + search_fill_index(); + search_update_index(search_restrict(SRCH_ALL)); + } + if( search_index_exists() ){ + @

            Currently using an SQLite FTS4 search index. This makes search + @ run faster, especially on large repositories, but takes up space.

            + onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0); + @

            + @ + }else{ + @

            The SQLite FTS4 search index is disabled. All searching will be + @ a full-text scan. This usually works fine, but can be slow for + @ larger repositories.

            + onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0); + @

            + } + @

            + style_footer(); +} Index: src/sha1.c ================================================================== --- src/sha1.c +++ src/sha1.c @@ -1,12 +1,21 @@ /* ** This implementation of SHA1. */ -#include #include "config.h" +#include #include "sha1.h" +#ifdef FOSSIL_ENABLE_SSL + +# include +# define SHA1Context SHA_CTX +# define SHA1Init SHA1_Init +# define SHA1Update SHA1_Update +# define SHA1Final(a,b) SHA1_Final(b,a) + +#else /* ** The SHA1 implementation below is adapted from: ** ** $NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $ @@ -55,11 +64,11 @@ ^block[(i+2)&15]^block[i&15],1)) /* * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 * - * Rl0() for little-endian and Rb0() for big-endian. Endianness is + * Rl0() for little-endian and Rb0() for big-endian. Endianness is * determined at run-time. */ #define Rl0(v,w,x,y,z,i) \ z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=ror(w,2); #define Rb0(v,w,x,y,z,i) \ @@ -161,20 +170,20 @@ ){ unsigned int i, j; j = context->count[0]; if ((context->count[0] += len << 3) < j) - context->count[1] += (len>>29)+1; + context->count[1] += (len>>29)+1; j = (j >> 3) & 63; if ((j + len) > 63) { - (void)memcpy(&context->buffer[j], data, (i = 64-j)); - SHA1Transform(context->state, context->buffer); - for ( ; i + 63 < len; i += 64) - SHA1Transform(context->state, &data[i]); - j = 0; + (void)memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) + SHA1Transform(context->state, &data[i]); + j = 0; } else { - i = 0; + i = 0; } (void)memcpy(&context->buffer[j], &data[i], len - i); } @@ -184,34 +193,35 @@ static void SHA1Final(SHA1Context *context, unsigned char digest[20]){ unsigned int i; unsigned char finalcount[8]; for (i = 0; i < 8; i++) { - finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] - >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ } SHA1Update(context, (const unsigned char *)"\200", 1); while ((context->count[0] & 504) != 448) - SHA1Update(context, (const unsigned char *)"\0", 1); + SHA1Update(context, (const unsigned char *)"\0", 1); SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ if (digest) { - for (i = 0; i < 20; i++) - digest[i] = (unsigned char) - ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + for (i = 0; i < 20; i++) + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); } } +#endif /* ** Convert a digest into base-16. digest should be declared as ** "unsigned char digest[20]" in the calling function. The SHA1 ** digest is stored in the first 20 bytes. zBuf should ** be "char zBuf[41]". */ static void DigestToBase16(unsigned char *digest, char *zBuf){ - static char const zEncode[] = "0123456789abcdef"; + static const char zEncode[] = "0123456789abcdef"; int ix; for(ix=0; ix<20; ix++){ *zBuf++ = zEncode[(*digest>>4)&0xf]; *zBuf++ = zEncode[*digest++ & 0xf]; @@ -248,11 +258,11 @@ sha1sum_step_text(blob_buffer(p), blob_size(p)); } /* ** Finish the incremental SHA1 checksum. Store the result in blob pOut -** if pOut!=0. Also return a pointer to the result. +** if pOut!=0. Also return a pointer to the result. ** ** This resets the incremental checksum preparing for the next round ** of computation. The return pointer points to a static buffer that ** is overwritten by subsequent calls to this function. */ @@ -285,11 +295,11 @@ if( file_wd_islink(zFilename) ){ /* Instead of file content, return sha1 of link destination path */ Blob destinationPath; int rc; - + blob_read_link(&destinationPath, zFilename); rc = sha1sum_blob(&destinationPath, pCksum); blob_reset(&destinationPath); return rc; } @@ -353,11 +363,11 @@ return mprintf("%s", zDigest); } /* ** Convert a cleartext password for a specific user into a SHA1 hash. -** +** ** The algorithm here is: ** ** SHA1( project-code + "/" + login + "/" + password ) ** ** In words: The users login name and password are appended to the @@ -365,11 +375,11 @@ ** ** The result of this function is the shared secret used by a client ** to authenticate to a server for the sync protocol. It is also the ** value stored in the USER.PW field of the database. By mixing in the ** login name and the project id with the hash, different shared secrets -** are obtained even if two users select the same password, or if a +** are obtained even if two users select the same password, or if a ** single user selects the same password for multiple projects. */ char *sha1_shared_secret( const char *zPw, /* The password to encrypt */ const char *zLogin, /* Username */ @@ -447,11 +457,11 @@ */ void sha1sum_test(void){ int i; Blob in; Blob cksum; - + for(i=2; i #include #include #include #include "sqlite3.h" +#if SQLITE_USER_AUTHENTICATION +# include "sqlite3userauth.h" +#endif #include #include #if !defined(_WIN32) && !defined(WIN32) # include @@ -43,41 +60,71 @@ # endif # include # include #endif -#ifdef HAVE_EDITLINE -# include -#endif -#if defined(HAVE_READLINE) && HAVE_READLINE==1 +#if HAVE_READLINE # include # include #endif -#if !defined(HAVE_EDITLINE) && (!defined(HAVE_READLINE) || HAVE_READLINE!=1) -# define add_history(X) -# define read_history(X) -# define write_history(X) -# define stifle_history(X) + +#if HAVE_EDITLINE +# include +#endif + +#if HAVE_EDITLINE || HAVE_READLINE + +# define shell_add_history(X) add_history(X) +# define shell_read_history(X) read_history(X) +# define shell_write_history(X) write_history(X) +# define shell_stifle_history(X) stifle_history(X) +# define shell_readline(X) readline(X) + +#elif HAVE_LINENOISE + +# include "linenoise.h" +# define shell_add_history(X) linenoiseHistoryAdd(X) +# define shell_read_history(X) linenoiseHistoryLoad(X) +# define shell_write_history(X) linenoiseHistorySave(X) +# define shell_stifle_history(X) linenoiseHistorySetMaxLen(X) +# define shell_readline(X) linenoise(X) + +#else + +# define shell_read_history(X) +# define shell_write_history(X) +# define shell_stifle_history(X) + +# define SHELL_USE_LOCAL_GETLINE 1 #endif + #if defined(_WIN32) || defined(WIN32) # include +# include #define isatty(h) _isatty(h) -#define access(f,m) _access((f),(m)) +#ifndef access +# define access(f,m) _access((f),(m)) +#endif #undef popen #define popen _popen #undef pclose #define pclose _pclose #else /* Make sure isatty() has a prototype. */ extern int isatty(int); -/* popen and pclose are not C89 functions and so are sometimes omitted from -** the header */ -extern FILE *popen(const char*,const char*); -extern int pclose(FILE*); +#if !defined(__RTP__) && !defined(_WRS_KERNEL) + /* popen and pclose are not C89 functions and so are sometimes omitted from + ** the header */ + extern FILE *popen(const char*,const char*); + extern int pclose(FILE*); +#else +# define SQLITE_OMIT_POPEN 1 +#endif + #endif #if defined(_WIN32_WCE) /* Windows CE (arm-wince-mingw32ce-gcc) does not provide isatty() * thus we always assume that we have a console. That can be @@ -84,32 +131,78 @@ * overridden with the -batch command line option. */ #define isatty(x) 1 #endif -/* True if the timer is enabled */ -static int enableTimer = 0; - /* ctype macros that work with signed characters */ #define IsSpace(X) isspace((unsigned char)X) #define IsDigit(X) isdigit((unsigned char)X) #define ToLower(X) (char)tolower((unsigned char)X) -#if !defined(_WIN32) && !defined(WIN32) && !defined(_WRS_KERNEL) \ - && !defined(__minux) +/* On Windows, we normally run with output mode of TEXT so that \n characters +** are automatically translated into \r\n. However, this behavior needs +** to be disabled in some cases (ex: when generating CSV output and when +** rendering quoted strings that contain \n characters). The following +** routines take care of that. +*/ +#if defined(_WIN32) || defined(WIN32) +static void setBinaryMode(FILE *out){ + fflush(out); + _setmode(_fileno(out), _O_BINARY); +} +static void setTextMode(FILE *out){ + fflush(out); + _setmode(_fileno(out), _O_TEXT); +} +#else +# define setBinaryMode(X) +# define setTextMode(X) +#endif + + +/* True if the timer is enabled */ +static int enableTimer = 0; + +/* Return the current wall-clock time */ +static sqlite3_int64 timeOfDay(void){ + static sqlite3_vfs *clockVfs = 0; + sqlite3_int64 t; + if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); + if( clockVfs->iVersion>=1 && clockVfs->xCurrentTimeInt64!=0 ){ + clockVfs->xCurrentTimeInt64(clockVfs, &t); + }else{ + double r; + clockVfs->xCurrentTime(clockVfs, &r); + t = (sqlite3_int64)(r*86400000.0); + } + return t; +} + +#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) #include #include + +/* VxWorks does not support getrusage() as far as we can determine */ +#if defined(_WRS_KERNEL) || defined(__RTP__) +struct rusage { + struct timeval ru_utime; /* user CPU time used */ + struct timeval ru_stime; /* system CPU time used */ +}; +#define getrusage(A,B) memset(B,0,sizeof(*B)) +#endif /* Saved resource information for the beginning of an operation */ -static struct rusage sBegin; +static struct rusage sBegin; /* CPU time at start */ +static sqlite3_int64 iBegin; /* Wall-clock time at start */ /* ** Begin timing an operation */ static void beginTimer(void){ if( enableTimer ){ getrusage(RUSAGE_SELF, &sBegin); + iBegin = timeOfDay(); } } /* Return the difference of two time_structs in seconds */ static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ @@ -120,13 +213,15 @@ /* ** Print the timing results. */ static void endTimer(void){ if( enableTimer ){ + sqlite3_int64 iEnd = timeOfDay(); struct rusage sEnd; getrusage(RUSAGE_SELF, &sEnd); - printf("CPU Time: user %f sys %f\n", + printf("Run Time: real %.3f user %f sys %f\n", + (iEnd - iBegin)*0.001, timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); } } @@ -140,11 +235,13 @@ /* Saved resource information for the beginning of an operation */ static HANDLE hProcess; static FILETIME ftKernelBegin; static FILETIME ftUserBegin; -typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME); +static sqlite3_int64 ftWallBegin; +typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, + LPFILETIME, LPFILETIME); static GETPROCTIMES getProcessTimesAddr = NULL; /* ** Check to see if we have timer support. Return 1 if necessary ** support found (or found previously). @@ -151,19 +248,20 @@ */ static int hasTimer(void){ if( getProcessTimesAddr ){ return 1; } else { - /* GetProcessTimes() isn't supported in WIN95 and some other Windows versions. - ** See if the version we are running on has it, and if it does, save off - ** a pointer to it and the current process handle. + /* GetProcessTimes() isn't supported in WIN95 and some other Windows + ** versions. See if the version we are running on has it, and if it + ** does, save off a pointer to it and the current process handle. */ hProcess = GetCurrentProcess(); if( hProcess ){ HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); if( NULL != hinstLib ){ - getProcessTimesAddr = (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); + getProcessTimesAddr = + (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); if( NULL != getProcessTimesAddr ){ return 1; } FreeLibrary(hinstLib); } @@ -176,11 +274,13 @@ ** Begin timing an operation */ static void beginTimer(void){ if( enableTimer && getProcessTimesAddr ){ FILETIME ftCreation, ftExit; - getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelBegin, &ftUserBegin); + getProcessTimesAddr(hProcess,&ftCreation,&ftExit, + &ftKernelBegin,&ftUserBegin); + ftWallBegin = timeOfDay(); } } /* Return the difference of two FILETIME structs in seconds */ static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ @@ -193,12 +293,14 @@ ** Print the timing results. */ static void endTimer(void){ if( enableTimer && getProcessTimesAddr){ FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; - getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelEnd, &ftUserEnd); - printf("CPU Time: user %f sys %f\n", + sqlite3_int64 ftWallEnd = timeOfDay(); + getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); + printf("Run Time: real %.3f user %f sys %f\n", + (ftWallEnd - ftWallBegin)*0.001, timeDiff(&ftUserBegin, &ftUserEnd), timeDiff(&ftKernelBegin, &ftKernelEnd)); } } @@ -266,11 +368,11 @@ ** format string and subsequent arguments are values to be substituted ** in place of % fields. The result of formatting this string ** is written to iotrace. */ #ifdef SQLITE_ENABLE_IOTRACE -static void iotracePrintf(const char *zFormat, ...){ +static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){ va_list ap; char *z; if( iotrace==0 ) return; va_start(ap, zFormat); z = sqlite3_vmprintf(zFormat, ap); @@ -387,62 +489,81 @@ char *zResult; if( in!=0 ){ zResult = local_getline(zPrior, in); }else{ zPrompt = isContinuation ? continuePrompt : mainPrompt; -#if defined(HAVE_READLINE) && HAVE_READLINE==1 - free(zPrior); - zResult = readline(zPrompt); - if( zResult && *zResult ) add_history(zResult); -#else +#if SHELL_USE_LOCAL_GETLINE printf("%s", zPrompt); fflush(stdout); zResult = local_getline(zPrior, stdin); +#else + free(zPrior); + zResult = shell_readline(zPrompt); + if( zResult && *zResult ) shell_add_history(zResult); #endif } return zResult; } -struct previous_mode_data { - int valid; /* Is there legit data in here? */ - int mode; - int showHeader; - int colWidth[100]; +/* +** Shell output mode information from before ".explain on", +** saved so that it can be restored by ".explain off" +*/ +typedef struct SavedModeInfo SavedModeInfo; +struct SavedModeInfo { + int valid; /* Is there legit data in here? */ + int mode; /* Mode prior to ".explain on" */ + int showHeader; /* The ".header" setting prior to ".explain on" */ + int colWidth[100]; /* Column widths prior to ".explain on" */ }; /* -** An pointer to an instance of this structure is passed from -** the main program to the callback. This is used to communicate -** state and mode information. +** State information about the database connection is contained in an +** instance of the following structure. */ -struct callback_data { +typedef struct ShellState ShellState; +struct ShellState { sqlite3 *db; /* The database */ int echoOn; /* True to echo input commands */ + int autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */ int statsOn; /* True to display memory stats before each finalize */ + int scanstatsOn; /* True to display scan stats before each finalize */ + int outCount; /* Revert to stdout when reaching zero */ int cnt; /* Number of records displayed so far */ FILE *out; /* Write results here */ FILE *traceOut; /* Output for sqlite3_trace() */ int nErr; /* Number of errors seen */ int mode; /* An output mode setting */ int writableSchema; /* True if PRAGMA writable_schema=ON */ int showHeader; /* True to show column names in List or Column mode */ + unsigned shellFlgs; /* Various flags */ char *zDestTable; /* Name of destination table when MODE_Insert */ - char separator[20]; /* Separator character for MODE_List */ + char colSeparator[20]; /* Column separator character for several modes */ + char rowSeparator[20]; /* Row separator character for MODE_Ascii */ int colWidth[100]; /* Requested width of each column when in column mode*/ int actualWidth[100]; /* Actual width of each column */ - char nullvalue[20]; /* The text to print when a NULL comes back from + char nullValue[20]; /* The text to print when a NULL comes back from ** the database */ - struct previous_mode_data explainPrev; - /* Holds the mode information just before - ** .explain ON */ + SavedModeInfo normalMode;/* Holds the mode just before .explain ON */ char outfile[FILENAME_MAX]; /* Filename for *out */ const char *zDbFilename; /* name of the database file */ + char *zFreeOnClose; /* Filename to free when closing */ const char *zVfs; /* Name of VFS to use */ sqlite3_stmt *pStmt; /* Current statement if any. */ FILE *pLog; /* Write log output here */ + int *aiIndent; /* Array of indents used in MODE_Explain */ + int nIndent; /* Size of array aiIndent[] */ + int iIndent; /* Index of current op in aiIndent[] */ }; +/* +** These are the allowed shellFlgs values +*/ +#define SHFLG_Scratch 0x00001 /* The --scratch option is used */ +#define SHFLG_Pagecache 0x00002 /* The --pagecache option is used */ +#define SHFLG_Lookaside 0x00004 /* Lookaside memory is used */ + /* ** These are the allowed modes. */ #define MODE_Line 0 /* One column per line. Blank line between records */ #define MODE_Column 1 /* One record per line in neat columns */ @@ -451,10 +572,11 @@ #define MODE_Html 4 /* Generate an XHTML table */ #define MODE_Insert 5 /* Generate SQL "insert" statements */ #define MODE_Tcl 6 /* Generate ANSI-C or TCL quoted elements */ #define MODE_Csv 7 /* Quote strings, numbers are plain */ #define MODE_Explain 8 /* Like MODE_Column, but do not truncate data */ +#define MODE_Ascii 9 /* Use ASCII unit and record separators (0x1F/0x1E) */ static const char *modeDescr[] = { "line", "column", "list", @@ -462,12 +584,26 @@ "html", "insert", "tcl", "csv", "explain", + "ascii", }; +/* +** These are the column/row/line separators used by the various +** import/export modes. +*/ +#define SEP_Column "|" +#define SEP_Row "\n" +#define SEP_Tab "\t" +#define SEP_Space " " +#define SEP_Comma "," +#define SEP_CrLf "\r\n" +#define SEP_Unit "\x1F" +#define SEP_Record "\x1E" + /* ** Number of elements in an array */ #define ArraySize(X) (int)(sizeof(X)/sizeof(X[0])) @@ -483,11 +619,11 @@ /* ** A callback for the sqlite3_log() interface. */ static void shellLog(void *pArg, int iErrCode, const char *zMsg){ - struct callback_data *p = (struct callback_data*)pArg; + ShellState *p = (ShellState*)pArg; if( p->pLog==0 ) return; fprintf(p->pLog, "(%d) %s\n", iErrCode, zMsg); fflush(p->pLog); } @@ -506,10 +642,11 @@ ** Output the given string as a quoted string using SQL quoting conventions. */ static void output_quoted_string(FILE *out, const char *z){ int i; int nSingle = 0; + setBinaryMode(out); for(i=0; z[i]; i++){ if( z[i]=='\'' ) nSingle++; } if( nSingle==0 ){ fprintf(out,"'%s'",z); @@ -528,10 +665,11 @@ break; } } fprintf(out,"'"); } + setTextMode(out); } /* ** Output the given string as a quoted according to C or TCL quoting rules. */ @@ -567,10 +705,11 @@ ** Output the given string with characters that are special to ** HTML escaped. */ static void output_html_string(FILE *out, const char *z){ int i; + if( z==0 ) z = ""; while( *z ){ for(i=0; z[i] && z[i]!='<' && z[i]!='&' && z[i]!='>' @@ -619,25 +758,26 @@ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; /* -** Output a single term of CSV. Actually, p->separator is used for -** the separator, which may or may not be a comma. p->nullvalue is -** the null value. Strings are quoted if necessary. +** Output a single term of CSV. Actually, p->colSeparator is used for +** the separator, which may or may not be a comma. p->nullValue is +** the null value. Strings are quoted if necessary. The separator +** is only issued if bSep is true. */ -static void output_csv(struct callback_data *p, const char *z, int bSep){ +static void output_csv(ShellState *p, const char *z, int bSep){ FILE *out = p->out; if( z==0 ){ - fprintf(out,"%s",p->nullvalue); + fprintf(out,"%s",p->nullValue); }else{ int i; - int nSep = strlen30(p->separator); + int nSep = strlen30(p->colSeparator); for(i=0; z[i]; i++){ if( needCsvQuote[((unsigned char*)z)[i]] - || (z[i]==p->separator[0] && - (nSep==1 || memcmp(z, p->separator, nSep)==0)) ){ + || (z[i]==p->colSeparator[0] && + (nSep==1 || memcmp(z, p->colSeparator, nSep)==0)) ){ i = 0; break; } } if( i==0 ){ @@ -650,45 +790,52 @@ }else{ fprintf(out, "%s", z); } } if( bSep ){ - fprintf(p->out, "%s", p->separator); + fprintf(p->out, "%s", p->colSeparator); } } #ifdef SIGINT /* ** This routine runs when the user presses Ctrl-C */ static void interrupt_handler(int NotUsed){ UNUSED_PARAMETER(NotUsed); - seenInterrupt = 1; + seenInterrupt++; + if( seenInterrupt>2 ) exit(1); if( db ) sqlite3_interrupt(db); } #endif /* ** This is the callback routine that the shell ** invokes for each row of a query result. */ -static int shell_callback(void *pArg, int nArg, char **azArg, char **azCol, int *aiType){ +static int shell_callback( + void *pArg, + int nArg, /* Number of result columns */ + char **azArg, /* Text of each result column */ + char **azCol, /* Column names */ + int *aiType /* Column types */ +){ int i; - struct callback_data *p = (struct callback_data*)pArg; + ShellState *p = (ShellState*)pArg; switch( p->mode ){ case MODE_Line: { int w = 5; if( azArg==0 ) break; for(i=0; iw ) w = len; } - if( p->cnt++>0 ) fprintf(p->out,"\n"); + if( p->cnt++>0 ) fprintf(p->out, "%s", p->rowSeparator); for(i=0; iout,"%*s = %s\n", w, azCol[i], - azArg[i] ? azArg[i] : p->nullvalue); + fprintf(p->out,"%*s = %s%s", w, azCol[i], + azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator); } break; } case MODE_Explain: case MODE_Column: { @@ -701,21 +848,23 @@ w = 0; } if( w==0 ){ w = strlen30(azCol[i] ? azCol[i] : ""); if( w<10 ) w = 10; - n = strlen30(azArg && azArg[i] ? azArg[i] : p->nullvalue); + n = strlen30(azArg && azArg[i] ? azArg[i] : p->nullValue); if( wactualWidth) ){ p->actualWidth[i] = w; } if( p->showHeader ){ if( w<0 ){ - fprintf(p->out,"%*.*s%s",-w,-w,azCol[i], i==nArg-1 ? "\n": " "); + fprintf(p->out,"%*.*s%s",-w,-w,azCol[i], + i==nArg-1 ? p->rowSeparator : " "); }else{ - fprintf(p->out,"%-*.*s%s",w,w,azCol[i], i==nArg-1 ? "\n": " "); + fprintf(p->out,"%-*.*s%s",w,w,azCol[i], + i==nArg-1 ? p->rowSeparator : " "); } } } if( p->showHeader ){ for(i=0; iout,"%-*.*s%s",w,w,"-----------------------------------" "----------------------------------------------------------", - i==nArg-1 ? "\n": " "); + i==nArg-1 ? p->rowSeparator : " "); } } } if( azArg==0 ) break; for(i=0; iactualWidth) ){ w = p->actualWidth[i]; }else{ w = 10; } - if( p->mode==MODE_Explain && azArg[i] && - strlen30(azArg[i])>w ){ + if( p->mode==MODE_Explain && azArg[i] && strlen30(azArg[i])>w ){ w = strlen30(azArg[i]); + } + if( i==1 && p->aiIndent && p->pStmt ){ + if( p->iIndentnIndent ){ + fprintf(p->out, "%*.s", p->aiIndent[p->iIndent], ""); + } + p->iIndent++; } if( w<0 ){ fprintf(p->out,"%*.*s%s",-w,-w, - azArg[i] ? azArg[i] : p->nullvalue, i==nArg-1 ? "\n": " "); + azArg[i] ? azArg[i] : p->nullValue, + i==nArg-1 ? p->rowSeparator : " "); }else{ fprintf(p->out,"%-*.*s%s",w,w, - azArg[i] ? azArg[i] : p->nullvalue, i==nArg-1 ? "\n": " "); + azArg[i] ? azArg[i] : p->nullValue, + i==nArg-1 ? p->rowSeparator : " "); } } break; } case MODE_Semi: case MODE_List: { if( p->cnt++==0 && p->showHeader ){ for(i=0; iout,"%s%s",azCol[i], i==nArg-1 ? "\n" : p->separator); + fprintf(p->out,"%s%s",azCol[i], + i==nArg-1 ? p->rowSeparator : p->colSeparator); } } if( azArg==0 ) break; for(i=0; inullvalue; + if( z==0 ) z = p->nullValue; fprintf(p->out, "%s", z); if( iout, "%s", p->separator); + fprintf(p->out, "%s", p->colSeparator); }else if( p->mode==MODE_Semi ){ - fprintf(p->out, ";\n"); + fprintf(p->out, ";%s", p->rowSeparator); }else{ - fprintf(p->out, "\n"); + fprintf(p->out, "%s", p->rowSeparator); } } break; } case MODE_Html: { @@ -788,44 +945,47 @@ } if( azArg==0 ) break; fprintf(p->out,""); for(i=0; iout,""); - output_html_string(p->out, azArg[i] ? azArg[i] : p->nullvalue); + output_html_string(p->out, azArg[i] ? azArg[i] : p->nullValue); fprintf(p->out,"\n"); } fprintf(p->out,"\n"); break; } case MODE_Tcl: { if( p->cnt++==0 && p->showHeader ){ for(i=0; iout,azCol[i] ? azCol[i] : ""); - if(iout, "%s", p->separator); + if(iout, "%s", p->colSeparator); } - fprintf(p->out,"\n"); + fprintf(p->out, "%s", p->rowSeparator); } if( azArg==0 ) break; for(i=0; iout, azArg[i] ? azArg[i] : p->nullvalue); - if(iout, "%s", p->separator); + output_c_string(p->out, azArg[i] ? azArg[i] : p->nullValue); + if(iout, "%s", p->colSeparator); } - fprintf(p->out,"\n"); + fprintf(p->out, "%s", p->rowSeparator); break; } case MODE_Csv: { + setBinaryMode(p->out); if( p->cnt++==0 && p->showHeader ){ for(i=0; iout,"\n"); + fprintf(p->out, "%s", p->rowSeparator); } - if( azArg==0 ) break; - for(i=0; i0 ){ + for(i=0; iout, "%s", p->rowSeparator); } - fprintf(p->out,"\n"); + setTextMode(p->out); break; } case MODE_Insert: { p->cnt++; if( azArg==0 ) break; @@ -835,11 +995,12 @@ if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ fprintf(p->out,"%sNULL",zSep); }else if( aiType && aiType[i]==SQLITE_TEXT ){ if( zSep[0] ) fprintf(p->out,"%s",zSep); output_quoted_string(p->out, azArg[i]); - }else if( aiType && (aiType[i]==SQLITE_INTEGER || aiType[i]==SQLITE_FLOAT) ){ + }else if( aiType && (aiType[i]==SQLITE_INTEGER + || aiType[i]==SQLITE_FLOAT) ){ fprintf(p->out,"%s%s",zSep, azArg[i]); }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ const void *pBlob = sqlite3_column_blob(p->pStmt, i); int nBlob = sqlite3_column_bytes(p->pStmt, i); if( zSep[0] ) fprintf(p->out,"%s",zSep); @@ -852,10 +1013,26 @@ } } fprintf(p->out,");\n"); break; } + case MODE_Ascii: { + if( p->cnt++==0 && p->showHeader ){ + for(i=0; i0 ) fprintf(p->out, "%s", p->colSeparator); + fprintf(p->out,"%s",azCol[i] ? azCol[i] : ""); + } + fprintf(p->out, "%s", p->rowSeparator); + } + if( azArg==0 ) break; + for(i=0; i0 ) fprintf(p->out, "%s", p->colSeparator); + fprintf(p->out,"%s",azArg[i] ? azArg[i] : p->nullValue); + } + fprintf(p->out, "%s", p->rowSeparator); + break; + } } return 0; } /* @@ -866,15 +1043,15 @@ /* since we don't have type info, call the shell_callback with a NULL value */ return shell_callback(pArg, nArg, azArg, azCol, NULL); } /* -** Set the destination table field of the callback_data structure to +** Set the destination table field of the ShellState structure to ** the name of the table given. Escape any quote characters in the ** table name. */ -static void set_table_name(struct callback_data *p, const char *zName){ +static void set_table_name(ShellState *p, const char *zName){ int i, n; int needQuote; char *z; if( p->zDestTable ){ @@ -960,23 +1137,23 @@ ** then write the semicolon on a separate line. That way, if a ** "--" comment occurs at the end of the statement, the comment ** won't consume the semicolon terminator. */ static int run_table_dump_query( - struct callback_data *p, /* Query context */ + ShellState *p, /* Query context */ const char *zSelect, /* SELECT statement to extract content */ const char *zFirstRow /* Print before first row, if not NULL */ ){ sqlite3_stmt *pSelect; int rc; int nResult; int i; const char *z; - rc = sqlite3_prepare(p->db, zSelect, -1, &pSelect, 0); + rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0); if( rc!=SQLITE_OK || !pSelect ){ fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); - p->nErr++; + if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; return rc; } rc = sqlite3_step(pSelect); nResult = sqlite3_column_count(pSelect); while( rc==SQLITE_ROW ){ @@ -999,11 +1176,11 @@ rc = sqlite3_step(pSelect); } rc = sqlite3_finalize(pSelect); if( rc!=SQLITE_OK ){ fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); - p->nErr++; + if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; } return rc; } /* @@ -1023,71 +1200,91 @@ /* ** Display memory stats. */ static int display_stats( sqlite3 *db, /* Database to query */ - struct callback_data *pArg, /* Pointer to struct callback_data */ + ShellState *pArg, /* Pointer to ShellState */ int bReset /* True to reset the stats */ ){ int iCur; int iHiwtr; if( pArg && pArg->out ){ iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_MEMORY_USED, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Memory Used: %d (max %d) bytes\n", iCur, iHiwtr); + fprintf(pArg->out, + "Memory Used: %d (max %d) bytes\n", + iCur, iHiwtr); iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_MALLOC_COUNT, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Number of Outstanding Allocations: %d (max %d)\n", iCur, iHiwtr); -/* -** Not currently used by the CLI. -** iHiwtr = iCur = -1; -** sqlite3_status(SQLITE_STATUS_PAGECACHE_USED, &iCur, &iHiwtr, bReset); -** fprintf(pArg->out, "Number of Pcache Pages Used: %d (max %d) pages\n", iCur, iHiwtr); -*/ + fprintf(pArg->out, "Number of Outstanding Allocations: %d (max %d)\n", + iCur, iHiwtr); + if( pArg->shellFlgs & SHFLG_Pagecache ){ + iHiwtr = iCur = -1; + sqlite3_status(SQLITE_STATUS_PAGECACHE_USED, &iCur, &iHiwtr, bReset); + fprintf(pArg->out, + "Number of Pcache Pages Used: %d (max %d) pages\n", + iCur, iHiwtr); + } iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Number of Pcache Overflow Bytes: %d (max %d) bytes\n", iCur, iHiwtr); -/* -** Not currently used by the CLI. -** iHiwtr = iCur = -1; -** sqlite3_status(SQLITE_STATUS_SCRATCH_USED, &iCur, &iHiwtr, bReset); -** fprintf(pArg->out, "Number of Scratch Allocations Used: %d (max %d)\n", iCur, iHiwtr); -*/ + fprintf(pArg->out, + "Number of Pcache Overflow Bytes: %d (max %d) bytes\n", + iCur, iHiwtr); + if( pArg->shellFlgs & SHFLG_Scratch ){ + iHiwtr = iCur = -1; + sqlite3_status(SQLITE_STATUS_SCRATCH_USED, &iCur, &iHiwtr, bReset); + fprintf(pArg->out, "Number of Scratch Allocations Used: %d (max %d)\n", + iCur, iHiwtr); + } iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_SCRATCH_OVERFLOW, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Number of Scratch Overflow Bytes: %d (max %d) bytes\n", iCur, iHiwtr); + fprintf(pArg->out, + "Number of Scratch Overflow Bytes: %d (max %d) bytes\n", + iCur, iHiwtr); iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_MALLOC_SIZE, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Largest Allocation: %d bytes\n", iHiwtr); + fprintf(pArg->out, "Largest Allocation: %d bytes\n", + iHiwtr); iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_PAGECACHE_SIZE, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Largest Pcache Allocation: %d bytes\n", iHiwtr); + fprintf(pArg->out, "Largest Pcache Allocation: %d bytes\n", + iHiwtr); iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_SCRATCH_SIZE, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Largest Scratch Allocation: %d bytes\n", iHiwtr); + fprintf(pArg->out, "Largest Scratch Allocation: %d bytes\n", + iHiwtr); #ifdef YYTRACKMAXSTACKDEPTH iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_PARSER_STACK, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Deepest Parser Stack: %d (max %d)\n", iCur, iHiwtr); + fprintf(pArg->out, "Deepest Parser Stack: %d (max %d)\n", + iCur, iHiwtr); #endif } if( pArg && pArg->out && db ){ - iHiwtr = iCur = -1; - sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr); - sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Successful lookaside attempts: %d\n", iHiwtr); - sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Lookaside failures due to size: %d\n", iHiwtr); - sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Lookaside failures due to OOM: %d\n", iHiwtr); + if( pArg->shellFlgs & SHFLG_Lookaside ){ + iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED, + &iCur, &iHiwtr, bReset); + fprintf(pArg->out, "Lookaside Slots Used: %d (max %d)\n", + iCur, iHiwtr); + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT, + &iCur, &iHiwtr, bReset); + fprintf(pArg->out, "Successful lookaside attempts: %d\n", iHiwtr); + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, + &iCur, &iHiwtr, bReset); + fprintf(pArg->out, "Lookaside failures due to size: %d\n", iHiwtr); + sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, + &iCur, &iHiwtr, bReset); + fprintf(pArg->out, "Lookaside failures due to OOM: %d\n", iHiwtr); + } iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Pager Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; + fprintf(pArg->out, "Pager Heap Usage: %d bytes\n",iCur); + iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1); fprintf(pArg->out, "Page cache hits: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); fprintf(pArg->out, "Page cache misses: %d\n", iCur); @@ -1094,29 +1291,175 @@ iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1); fprintf(pArg->out, "Page cache writes: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Schema Heap Usage: %d bytes\n", iCur); + fprintf(pArg->out, "Schema Heap Usage: %d bytes\n",iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Statement Heap/Lookaside Usage: %d bytes\n", iCur); + fprintf(pArg->out, "Statement Heap/Lookaside Usage: %d bytes\n",iCur); } if( pArg && pArg->out && db && pArg->pStmt ){ - iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, bReset); + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, + bReset); fprintf(pArg->out, "Fullscan Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset); fprintf(pArg->out, "Sort Operations: %d\n", iCur); - iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX, bReset); + iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset); fprintf(pArg->out, "Autoindex Inserts: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset); fprintf(pArg->out, "Virtual Machine Steps: %d\n", iCur); } return 0; } + +/* +** Display scan stats. +*/ +static void display_scanstats( + sqlite3 *db, /* Database to query */ + ShellState *pArg /* Pointer to ShellState */ +){ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int i, k, n, mx; + fprintf(pArg->out, "-------- scanstats --------\n"); + mx = 0; + for(k=0; k<=mx; k++){ + double rEstLoop = 1.0; + for(i=n=0; 1; i++){ + sqlite3_stmt *p = pArg->pStmt; + sqlite3_int64 nLoop, nVisit; + double rEst; + int iSid; + const char *zExplain; + if( sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_NLOOP, (void*)&nLoop) ){ + break; + } + sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_SELECTID, (void*)&iSid); + if( iSid>mx ) mx = iSid; + if( iSid!=k ) continue; + if( n==0 ){ + rEstLoop = (double)nLoop; + if( k>0 ) fprintf(pArg->out, "-------- subquery %d -------\n", k); + } + n++; + sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_NVISIT, (void*)&nVisit); + sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_EST, (void*)&rEst); + sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_EXPLAIN, (void*)&zExplain); + fprintf(pArg->out, "Loop %2d: %s\n", n, zExplain); + rEstLoop *= rEst; + fprintf(pArg->out, + " nLoop=%-8lld nRow=%-8lld estRow=%-8lld estRow/Loop=%-8g\n", + nLoop, nVisit, (sqlite3_int64)(rEstLoop+0.5), rEst + ); + } + } + fprintf(pArg->out, "---------------------------\n"); +#endif +} + +/* +** Parameter azArray points to a zero-terminated array of strings. zStr +** points to a single nul-terminated string. Return non-zero if zStr +** is equal, according to strcmp(), to any of the strings in the array. +** Otherwise, return zero. +*/ +static int str_in_array(const char *zStr, const char **azArray){ + int i; + for(i=0; azArray[i]; i++){ + if( 0==strcmp(zStr, azArray[i]) ) return 1; + } + return 0; +} + +/* +** If compiled statement pSql appears to be an EXPLAIN statement, allocate +** and populate the ShellState.aiIndent[] array with the number of +** spaces each opcode should be indented before it is output. +** +** The indenting rules are: +** +** * For each "Next", "Prev", "VNext" or "VPrev" instruction, indent +** all opcodes that occur between the p2 jump destination and the opcode +** itself by 2 spaces. +** +** * For each "Goto", if the jump destination is earlier in the program +** and ends on one of: +** Yield SeekGt SeekLt RowSetRead Rewind +** or if the P1 parameter is one instead of zero, +** then indent all opcodes between the earlier instruction +** and "Goto" by 2 spaces. +*/ +static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ + const char *zSql; /* The text of the SQL statement */ + const char *z; /* Used to check if this is an EXPLAIN */ + int *abYield = 0; /* True if op is an OP_Yield */ + int nAlloc = 0; /* Allocated size of p->aiIndent[], abYield */ + int iOp; /* Index of operation in p->aiIndent[] */ + + const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext", + "NextIfOpen", "PrevIfOpen", 0 }; + const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead", + "Rewind", 0 }; + const char *azGoto[] = { "Goto", 0 }; + + /* Try to figure out if this is really an EXPLAIN statement. If this + ** cannot be verified, return early. */ + zSql = sqlite3_sql(pSql); + if( zSql==0 ) return; + for(z=zSql; *z==' ' || *z=='\t' || *z=='\n' || *z=='\f' || *z=='\r'; z++); + if( sqlite3_strnicmp(z, "explain", 7) ) return; + + for(iOp=0; SQLITE_ROW==sqlite3_step(pSql); iOp++){ + int i; + int iAddr = sqlite3_column_int(pSql, 0); + const char *zOp = (const char*)sqlite3_column_text(pSql, 1); + + /* Set p2 to the P2 field of the current opcode. Then, assuming that + ** p2 is an instruction address, set variable p2op to the index of that + ** instruction in the aiIndent[] array. p2 and p2op may be different if + ** the current instruction is part of a sub-program generated by an + ** SQL trigger or foreign key. */ + int p2 = sqlite3_column_int(pSql, 3); + int p2op = (p2 + (iOp-iAddr)); + + /* Grow the p->aiIndent array as required */ + if( iOp>=nAlloc ){ + nAlloc += 100; + p->aiIndent = (int*)sqlite3_realloc(p->aiIndent, nAlloc*sizeof(int)); + abYield = (int*)sqlite3_realloc(abYield, nAlloc*sizeof(int)); + } + abYield[iOp] = str_in_array(zOp, azYield); + p->aiIndent[iOp] = 0; + p->nIndent = iOp+1; + + if( str_in_array(zOp, azNext) ){ + for(i=p2op; iaiIndent[i] += 2; + } + if( str_in_array(zOp, azGoto) && p2opnIndent + && (abYield[p2op] || sqlite3_column_int(pSql, 2)) + ){ + for(i=p2op+1; iaiIndent[i] += 2; + } + } + + p->iIndent = 0; + sqlite3_free(abYield); + sqlite3_reset(pSql); +} + +/* +** Free the array allocated by explain_data_prepare(). +*/ +static void explain_data_delete(ShellState *p){ + sqlite3_free(p->aiIndent); + p->aiIndent = 0; + p->nIndent = 0; + p->iIndent = 0; +} /* ** Execute a statement or set of statements. Print ** any result rows/columns depending on the current mode ** set via the supplied callback. @@ -1124,16 +1467,16 @@ ** This is very similar to SQLite's built-in sqlite3_exec() ** function except it takes a slightly different callback ** and callback data argument. */ static int shell_exec( - sqlite3 *db, /* An open database */ - const char *zSql, /* SQL to be evaluated */ + sqlite3 *db, /* An open database */ + const char *zSql, /* SQL to be evaluated */ int (*xCallback)(void*,int,char**,char**,int*), /* Callback function */ - /* (not the same as sqlite3_exec) */ - struct callback_data *pArg, /* Pointer to struct callback_data */ - char **pzErrMsg /* Error msg written here */ + /* (not the same as sqlite3_exec) */ + ShellState *pArg, /* Pointer to ShellState */ + char **pzErrMsg /* Error msg written here */ ){ sqlite3_stmt *pStmt = NULL; /* Statement to execute. */ int rc = SQLITE_OK; /* Return Code */ int rc2; const char *zLeftover; /* Tail of unprocessed SQL */ @@ -1166,17 +1509,32 @@ if( pArg && pArg->echoOn ){ const char *zStmtSql = sqlite3_sql(pStmt); fprintf(pArg->out, "%s\n", zStmtSql ? zStmtSql : zSql); } - /* Output TESTCTRL_EXPLAIN text of requested */ + /* Show the EXPLAIN QUERY PLAN if .eqp is on */ + if( pArg && pArg->autoEQP ){ + sqlite3_stmt *pExplain; + char *zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", + sqlite3_sql(pStmt)); + rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); + if( rc==SQLITE_OK ){ + while( sqlite3_step(pExplain)==SQLITE_ROW ){ + fprintf(pArg->out,"--EQP-- %d,", sqlite3_column_int(pExplain, 0)); + fprintf(pArg->out,"%d,", sqlite3_column_int(pExplain, 1)); + fprintf(pArg->out,"%d,", sqlite3_column_int(pExplain, 2)); + fprintf(pArg->out,"%s\n", sqlite3_column_text(pExplain, 3)); + } + } + sqlite3_finalize(pExplain); + sqlite3_free(zEQP); + } + + /* If the shell is currently in ".explain" mode, gather the extra + ** data required to add indents to the output.*/ if( pArg && pArg->mode==MODE_Explain ){ - const char *zExplain = 0; - sqlite3_test_control(SQLITE_TESTCTRL_EXPLAIN_STMT, pStmt, &zExplain); - if( zExplain && zExplain[0] ){ - fprintf(pArg->out, "%s", zExplain); - } + explain_data_prepare(pArg, pStmt); } /* perform the first step. this will tell us if we ** have a result set or not and how wide it is. */ @@ -1202,11 +1560,11 @@ } do{ /* extract the data and data types */ for(i=0; imode==MODE_Insert ){ + if( x==SQLITE_BLOB && pArg && pArg->mode==MODE_Insert ){ azVals[i] = ""; }else{ azVals[i] = (char*)sqlite3_column_text(pStmt, i); } if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){ @@ -1231,15 +1589,22 @@ do{ rc = sqlite3_step(pStmt); } while( rc == SQLITE_ROW ); } } + + explain_data_delete(pArg); /* print usage stats if stats on */ if( pArg && pArg->statsOn ){ display_stats(db, pArg, 0); } + + /* print loop-counters if required */ + if( pArg && pArg->scanstatsOn ){ + display_scanstats(db, pArg); + } /* Finalize the statement just executed. If this fails, save a ** copy of the error message. Otherwise, set zSql to point to the ** next statement to execute. */ rc2 = sqlite3_finalize(pStmt); @@ -1272,11 +1637,11 @@ int rc; const char *zTable; const char *zType; const char *zSql; const char *zPrepStmt = 0; - struct callback_data *p = (struct callback_data *)pArg; + ShellState *p = (ShellState *)pArg; UNUSED_PARAMETER(azCol); if( nArg!=3 ) return 1; zTable = azArg[0]; zType = azArg[1]; @@ -1314,11 +1679,11 @@ zTableInfo = appendText(zTableInfo, "PRAGMA table_info(", 0); zTableInfo = appendText(zTableInfo, zTable, '"'); zTableInfo = appendText(zTableInfo, ");", 0); - rc = sqlite3_prepare(p->db, zTableInfo, -1, &pTableInfo, 0); + rc = sqlite3_prepare_v2(p->db, zTableInfo, -1, &pTableInfo, 0); free(zTableInfo); if( rc!=SQLITE_OK || !pTableInfo ){ return 1; } @@ -1368,11 +1733,11 @@ ** ** If we get a SQLITE_CORRUPT error, rerun the query after appending ** "ORDER BY rowid DESC" to the end. */ static int run_schema_dump_query( - struct callback_data *p, + ShellState *p, const char *zQuery ){ int rc; char *zErr = 0; rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr); @@ -1403,76 +1768,145 @@ /* ** Text of a help message */ static char zHelp[] = ".backup ?DB? FILE Backup DB (default \"main\") to FILE\n" - ".bail ON|OFF Stop after hitting an error. Default OFF\n" + ".bail on|off Stop after hitting an error. Default OFF\n" + ".clone NEWDB Clone data into NEWDB from the existing database\n" ".databases List names and files of attached databases\n" + ".dbinfo ?DB? Show status information about the database\n" ".dump ?TABLE? ... Dump the database in an SQL text format\n" " If TABLE specified, only dump tables matching\n" " LIKE pattern TABLE.\n" - ".echo ON|OFF Turn command echo on or off\n" + ".echo on|off Turn command echo on or off\n" + ".eqp on|off Enable or disable automatic EXPLAIN QUERY PLAN\n" ".exit Exit this program\n" - ".explain ?ON|OFF? Turn output mode suitable for EXPLAIN on or off.\n" + ".explain ?on|off? Turn output mode suitable for EXPLAIN on or off.\n" " With no args, it turns EXPLAIN on.\n" - ".header(s) ON|OFF Turn display of headers on or off\n" + ".fullschema Show schema and the content of sqlite_stat tables\n" + ".headers on|off Turn display of headers on or off\n" ".help Show this message\n" ".import FILE TABLE Import data from FILE into TABLE\n" - ".indices ?TABLE? Show names of all indices\n" - " If TABLE specified, only show indices for tables\n" + ".indexes ?TABLE? Show names of all indexes\n" + " If TABLE specified, only show indexes for tables\n" " matching LIKE pattern TABLE.\n" #ifdef SQLITE_ENABLE_IOTRACE ".iotrace FILE Enable I/O diagnostic logging to FILE\n" #endif #ifndef SQLITE_OMIT_LOAD_EXTENSION ".load FILE ?ENTRY? Load an extension library\n" #endif ".log FILE|off Turn logging on or off. FILE can be stderr/stdout\n" ".mode MODE ?TABLE? Set output mode where MODE is one of:\n" + " ascii Columns/rows delimited by 0x1F and 0x1E\n" " csv Comma-separated values\n" " column Left-aligned columns. (See .width)\n" " html HTML code\n" " insert SQL insert statements for TABLE\n" " line One value per line\n" - " list Values delimited by .separator string\n" + " list Values delimited by .separator strings\n" " tabs Tab-separated values\n" " tcl TCL list elements\n" ".nullvalue STRING Use STRING in place of NULL values\n" - ".output FILENAME Send output to FILENAME\n" - ".output stdout Send output to the screen\n" + ".once FILENAME Output for the next SQL command only to FILENAME\n" + ".open ?FILENAME? Close existing database and reopen FILENAME\n" + ".output ?FILENAME? Send output to FILENAME or stdout\n" ".print STRING... Print literal STRING\n" ".prompt MAIN CONTINUE Replace the standard prompts\n" ".quit Exit this program\n" ".read FILENAME Execute SQL in FILENAME\n" ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE\n" + ".save FILE Write in-memory database into FILE\n" + ".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off\n" ".schema ?TABLE? Show the CREATE statements\n" " If TABLE specified, only show tables matching\n" " LIKE pattern TABLE.\n" - ".separator STRING Change separator used by output mode and .import\n" + ".separator COL ?ROW? Change the column separator and optionally the row\n" + " separator for both the output mode and .import\n" + ".shell CMD ARGS... Run CMD ARGS... in a system shell\n" ".show Show the current values for various settings\n" - ".stats ON|OFF Turn stats on or off\n" + ".stats on|off Turn stats on or off\n" + ".system CMD ARGS... Run CMD ARGS... in a system shell\n" ".tables ?TABLE? List names of tables\n" " If TABLE specified, only list tables matching\n" " LIKE pattern TABLE.\n" ".timeout MS Try opening locked tables for MS milliseconds\n" + ".timer on|off Turn SQL timer on or off\n" ".trace FILE|off Output each SQL statement as it is run\n" ".vfsname ?AUX? Print the name of the VFS stack\n" ".width NUM1 NUM2 ... Set column widths for \"column\" mode\n" -; - -static char zTimerHelp[] = - ".timer ON|OFF Turn the CPU timer measurement on or off\n" + " Negative values right-justify\n" ; /* Forward reference */ -static int process_input(struct callback_data *p, FILE *in); +static int process_input(ShellState *p, FILE *in); +/* +** Implementation of the "readfile(X)" SQL function. The entire content +** of the file named X is read and returned as a BLOB. NULL is returned +** if the file does not exist or is unreadable. +*/ +static void readfileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName; + FILE *in; + long nIn; + void *pBuf; + + zName = (const char*)sqlite3_value_text(argv[0]); + if( zName==0 ) return; + in = fopen(zName, "rb"); + if( in==0 ) return; + fseek(in, 0, SEEK_END); + nIn = ftell(in); + rewind(in); + pBuf = sqlite3_malloc( nIn ); + if( pBuf && 1==fread(pBuf, nIn, 1, in) ){ + sqlite3_result_blob(context, pBuf, nIn, sqlite3_free); + }else{ + sqlite3_free(pBuf); + } + fclose(in); +} + +/* +** Implementation of the "writefile(X,Y)" SQL function. The argument Y +** is written into file X. The number of bytes written is returned. Or +** NULL is returned if something goes wrong, such as being unable to open +** file X for writing. +*/ +static void writefileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + FILE *out; + const char *z; + sqlite3_int64 rc; + const char *zFile; + + zFile = (const char*)sqlite3_value_text(argv[0]); + if( zFile==0 ) return; + out = fopen(zFile, "wb"); + if( out==0 ) return; + z = (const char*)sqlite3_value_blob(argv[1]); + if( z==0 ){ + rc = 0; + }else{ + rc = fwrite(z, 1, sqlite3_value_bytes(argv[1]), out); + } + fclose(out); + sqlite3_result_int64(context, rc); +} /* ** Make sure the database is open. If it is not, then open it. If ** the database fails to open, print an error message and exit. */ -static void open_db(struct callback_data *p){ +static void open_db(ShellState *p, int keepAlive){ if( p->db==0 ){ sqlite3_initialize(); sqlite3_open(p->zDbFilename, &p->db); db = p->db; if( db && sqlite3_errcode(db)==SQLITE_OK ){ @@ -1480,15 +1914,20 @@ shellstaticFunc, 0, 0); } if( db==0 || SQLITE_OK!=sqlite3_errcode(db) ){ fprintf(stderr,"Error: unable to open database \"%s\": %s\n", p->zDbFilename, sqlite3_errmsg(db)); + if( keepAlive ) return; exit(1); } #ifndef SQLITE_OMIT_LOAD_EXTENSION sqlite3_enable_load_extension(p->db, 1); #endif + sqlite3_create_function(db, "readfile", 1, SQLITE_UTF8, 0, + readfileFunc, 0, 0); + sqlite3_create_function(db, "writefile", 2, SQLITE_UTF8, 0, + writefileFunc, 0, 0); } } /* ** Do C-language style dequoting. @@ -1501,10 +1940,11 @@ ** \\ -> backslash */ static void resolve_backslashes(char *z){ int i, j; char c; + while( *z && *z!='\\' ) z++; for(i=j=0; (c = z[i])!=0; i++, j++){ if( c=='\\' ){ c = z[++i]; if( c=='n' ){ c = '\n'; @@ -1526,11 +1966,11 @@ } } } z[j] = c; } - z[j] = 0; + if( j0 && z[i-1]==';' ){ i--; } + fprintf(f, "%.*s;\n", i, z); + } } /* ** A no-op routine that runs with the ".breakpoint" doc-command. This is ** a useful spot to set a debugger breakpoint. @@ -1656,26 +2100,27 @@ static int nCall = 0; nCall++; } /* -** An object used to read a CSV file +** An object used to read a CSV and other files for import. */ -typedef struct CSVReader CSVReader; -struct CSVReader { +typedef struct ImportCtx ImportCtx; +struct ImportCtx { const char *zFile; /* Name of the input file */ FILE *in; /* Read the CSV text from this input stream */ char *z; /* Accumulated text for a field */ int n; /* Number of bytes in z */ int nAlloc; /* Space allocated for z[] */ int nLine; /* Current line number */ int cTerm; /* Character that terminated the most recent field */ - int cSeparator; /* The separator character. (Usually ",") */ + int cColSep; /* The column separator character. (Usually ",") */ + int cRowSep; /* The row separator character. (Usually "\n") */ }; /* Append a single byte to z[] */ -static void csv_append_char(CSVReader *p, int c){ +static void import_append_char(ImportCtx *p, int c){ if( p->n+1>=p->nAlloc ){ p->nAlloc += p->nAlloc + 100; p->z = sqlite3_realloc(p->z, p->nAlloc); if( p->z==0 ){ fprintf(stderr, "out of memory\n"); @@ -1689,41 +2134,44 @@ ** with the option of having a separator other than ",". ** ** + Input comes from p->in. ** + Store results in p->z of length p->n. Space to hold p->z comes ** from sqlite3_malloc(). -** + Use p->cSep as the separator. The default is ",". +** + Use p->cSep as the column separator. The default is ",". +** + Use p->rSep as the row separator. The default is "\n". ** + Keep track of the line number in p->nLine. ** + Store the character that terminates the field in p->cTerm. Store ** EOF on end-of-file. ** + Report syntax errors on stderr */ -static char *csv_read_one_field(CSVReader *p){ - int c, pc; - int cSep = p->cSeparator; +static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ + int c; + int cSep = p->cColSep; + int rSep = p->cRowSep; p->n = 0; c = fgetc(p->in); if( c==EOF || seenInterrupt ){ p->cTerm = EOF; return 0; } if( c=='"' ){ + int pc, ppc; int startLine = p->nLine; int cQuote = c; - pc = 0; + pc = ppc = 0; while( 1 ){ c = fgetc(p->in); - if( c=='\n' ) p->nLine++; + if( c==rSep ) p->nLine++; if( c==cQuote ){ if( pc==cQuote ){ pc = 0; continue; } } if( (c==cSep && pc==cQuote) - || (c=='\n' && pc==cQuote) - || (c=='\n' && pc=='\r' && p->n>=2 && p->z[p->n-2]==cQuote) + || (c==rSep && pc==cQuote) + || (c==rSep && pc=='\r' && ppc==cQuote) || (c==EOF && pc==cQuote) ){ do{ p->n--; }while( p->z[p->n]!=cQuote ); p->cTerm = c; break; @@ -1733,38 +2181,412 @@ p->zFile, p->nLine, cQuote); } if( c==EOF ){ fprintf(stderr, "%s:%d: unterminated %c-quoted field\n", p->zFile, startLine, cQuote); - p->cTerm = EOF; + p->cTerm = c; break; } - csv_append_char(p, c); + import_append_char(p, c); + ppc = pc; pc = c; } }else{ - while( c!=EOF && c!=cSep && c!='\n' ){ - csv_append_char(p, c); + while( c!=EOF && c!=cSep && c!=rSep ){ + import_append_char(p, c); c = fgetc(p->in); } - if( c=='\n' ){ + if( c==rSep ){ p->nLine++; - if( p->n>1 && p->z[p->n-1]=='\r' ) p->n--; + if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--; } p->cTerm = c; } if( p->z ) p->z[p->n] = 0; return p->z; } + +/* Read a single field of ASCII delimited text. +** +** + Input comes from p->in. +** + Store results in p->z of length p->n. Space to hold p->z comes +** from sqlite3_malloc(). +** + Use p->cSep as the column separator. The default is "\x1F". +** + Use p->rSep as the row separator. The default is "\x1E". +** + Keep track of the row number in p->nLine. +** + Store the character that terminates the field in p->cTerm. Store +** EOF on end-of-file. +** + Report syntax errors on stderr +*/ +static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){ + int c; + int cSep = p->cColSep; + int rSep = p->cRowSep; + p->n = 0; + c = fgetc(p->in); + if( c==EOF || seenInterrupt ){ + p->cTerm = EOF; + return 0; + } + while( c!=EOF && c!=cSep && c!=rSep ){ + import_append_char(p, c); + c = fgetc(p->in); + } + if( c==rSep ){ + p->nLine++; + } + p->cTerm = c; + if( p->z ) p->z[p->n] = 0; + return p->z; +} + +/* +** Try to transfer data for table zTable. If an error is seen while +** moving forward, try to go backwards. The backwards movement won't +** work for WITHOUT ROWID tables. +*/ +static void tryToCloneData( + ShellState *p, + sqlite3 *newDb, + const char *zTable +){ + sqlite3_stmt *pQuery = 0; + sqlite3_stmt *pInsert = 0; + char *zQuery = 0; + char *zInsert = 0; + int rc; + int i, j, n; + int nTable = (int)strlen(zTable); + int k = 0; + int cnt = 0; + const int spinRate = 10000; + + zQuery = sqlite3_mprintf("SELECT * FROM \"%w\"", zTable); + rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); + if( rc ){ + fprintf(stderr, "Error %d: %s on [%s]\n", + sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), + zQuery); + goto end_data_xfer; + } + n = sqlite3_column_count(pQuery); + zInsert = sqlite3_malloc(200 + nTable + n*3); + if( zInsert==0 ){ + fprintf(stderr, "out of memory\n"); + goto end_data_xfer; + } + sqlite3_snprintf(200+nTable,zInsert, + "INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable); + i = (int)strlen(zInsert); + for(j=1; jdb, zQuery, -1, &pQuery, 0); + if( rc ){ + fprintf(stderr, "Warning: cannot step \"%s\" backwards", zTable); + break; + } + } /* End for(k=0...) */ + +end_data_xfer: + sqlite3_finalize(pQuery); + sqlite3_finalize(pInsert); + sqlite3_free(zQuery); + sqlite3_free(zInsert); +} + + +/* +** Try to transfer all rows of the schema that match zWhere. For +** each row, invoke xForEach() on the object defined by that row. +** If an error is encountered while moving forward through the +** sqlite_master table, try again moving backwards. +*/ +static void tryToCloneSchema( + ShellState *p, + sqlite3 *newDb, + const char *zWhere, + void (*xForEach)(ShellState*,sqlite3*,const char*) +){ + sqlite3_stmt *pQuery = 0; + char *zQuery = 0; + int rc; + const unsigned char *zName; + const unsigned char *zSql; + char *zErrMsg = 0; + + zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_master" + " WHERE %s", zWhere); + rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); + if( rc ){ + fprintf(stderr, "Error: (%d) %s on [%s]\n", + sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), + zQuery); + goto end_schema_xfer; + } + while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){ + zName = sqlite3_column_text(pQuery, 0); + zSql = sqlite3_column_text(pQuery, 1); + printf("%s... ", zName); fflush(stdout); + sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); + if( zErrMsg ){ + fprintf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + sqlite3_free(zErrMsg); + zErrMsg = 0; + } + if( xForEach ){ + xForEach(p, newDb, (const char*)zName); + } + printf("done\n"); + } + if( rc!=SQLITE_DONE ){ + sqlite3_finalize(pQuery); + sqlite3_free(zQuery); + zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_master" + " WHERE %s ORDER BY rowid DESC", zWhere); + rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); + if( rc ){ + fprintf(stderr, "Error: (%d) %s on [%s]\n", + sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), + zQuery); + goto end_schema_xfer; + } + while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){ + zName = sqlite3_column_text(pQuery, 0); + zSql = sqlite3_column_text(pQuery, 1); + printf("%s... ", zName); fflush(stdout); + sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); + if( zErrMsg ){ + fprintf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + sqlite3_free(zErrMsg); + zErrMsg = 0; + } + if( xForEach ){ + xForEach(p, newDb, (const char*)zName); + } + printf("done\n"); + } + } +end_schema_xfer: + sqlite3_finalize(pQuery); + sqlite3_free(zQuery); +} + +/* +** Open a new database file named "zNewDb". Try to recover as much information +** as possible out of the main database (which might be corrupt) and write it +** into zNewDb. +*/ +static void tryToClone(ShellState *p, const char *zNewDb){ + int rc; + sqlite3 *newDb = 0; + if( access(zNewDb,0)==0 ){ + fprintf(stderr, "File \"%s\" already exists.\n", zNewDb); + return; + } + rc = sqlite3_open(zNewDb, &newDb); + if( rc ){ + fprintf(stderr, "Cannot create output database: %s\n", + sqlite3_errmsg(newDb)); + }else{ + sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0); + sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0); + tryToCloneSchema(p, newDb, "type='table'", tryToCloneData); + tryToCloneSchema(p, newDb, "type!='table'", 0); + sqlite3_exec(newDb, "COMMIT;", 0, 0, 0); + sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); + } + sqlite3_close(newDb); +} + +/* +** Change the output file back to stdout +*/ +static void output_reset(ShellState *p){ + if( p->outfile[0]=='|' ){ +#ifndef SQLITE_OMIT_POPEN + pclose(p->out); +#endif + }else{ + output_file_close(p->out); + } + p->outfile[0] = 0; + p->out = stdout; +} + +/* +** Run an SQL command and return the single integer result. +*/ +static int db_int(ShellState *p, const char *zSql){ + sqlite3_stmt *pStmt; + int res = 0; + sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ + res = sqlite3_column_int(pStmt,0); + } + sqlite3_finalize(pStmt); + return res; +} + +/* +** Convert a 2-byte or 4-byte big-endian integer into a native integer +*/ +unsigned int get2byteInt(unsigned char *a){ + return (a[0]<<8) + a[1]; +} +unsigned int get4byteInt(unsigned char *a){ + return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3]; +} + +/* +** Implementation of the ".info" command. +** +** Return 1 on error, 2 to exit, and 0 otherwise. +*/ +static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ + static const struct { const char *zName; int ofst; } aField[] = { + { "file change counter:", 24 }, + { "database page count:", 28 }, + { "freelist page count:", 36 }, + { "schema cookie:", 40 }, + { "schema format:", 44 }, + { "default cache size:", 48 }, + { "autovacuum top root:", 52 }, + { "incremental vacuum:", 64 }, + { "text encoding:", 56 }, + { "user version:", 60 }, + { "application id:", 68 }, + { "software version:", 96 }, + }; + static const struct { const char *zName; const char *zSql; } aQuery[] = { + { "number of tables:", + "SELECT count(*) FROM %s WHERE type='table'" }, + { "number of indexes:", + "SELECT count(*) FROM %s WHERE type='index'" }, + { "number of triggers:", + "SELECT count(*) FROM %s WHERE type='trigger'" }, + { "number of views:", + "SELECT count(*) FROM %s WHERE type='view'" }, + { "schema size:", + "SELECT total(length(sql)) FROM %s" }, + }; + sqlite3_file *pFile; + int i; + char *zSchemaTab; + char *zDb = nArg>=2 ? azArg[1] : "main"; + unsigned char aHdr[100]; + open_db(p, 0); + if( p->db==0 ) return 1; + sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_FILE_POINTER, &pFile); + if( pFile==0 || pFile->pMethods==0 || pFile->pMethods->xRead==0 ){ + return 1; + } + i = pFile->pMethods->xRead(pFile, aHdr, 100, 0); + if( i!=SQLITE_OK ){ + fprintf(stderr, "unable to read database header\n"); + return 1; + } + i = get2byteInt(aHdr+16); + if( i==1 ) i = 65536; + fprintf(p->out, "%-20s %d\n", "database page size:", i); + fprintf(p->out, "%-20s %d\n", "write format:", aHdr[18]); + fprintf(p->out, "%-20s %d\n", "read format:", aHdr[19]); + fprintf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]); + for(i=0; iout, "%-20s %u", aField[i].zName, val); + switch( ofst ){ + case 56: { + if( val==1 ) fprintf(p->out, " (utf8)"); + if( val==2 ) fprintf(p->out, " (utf16le)"); + if( val==3 ) fprintf(p->out, " (utf16be)"); + } + } + fprintf(p->out, "\n"); + } + if( zDb==0 ){ + zSchemaTab = sqlite3_mprintf("main.sqlite_master"); + }else if( strcmp(zDb,"temp")==0 ){ + zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_master"); + }else{ + zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_master", zDb); + } + for(i=0; iout, "%-20s %d\n", aQuery[i].zName, val); + } + sqlite3_free(zSchemaTab); + return 0; +} + /* ** If an input line begins with "." then invoke this routine to ** process that line. ** ** Return 1 on error, 2 to exit, and 0 otherwise. */ -static int do_meta_command(char *zLine, struct callback_data *p){ +static int do_meta_command(char *zLine, ShellState *p){ int i = 1; int nArg = 0; int n, c; int rc = 0; char *azArg[50]; @@ -1796,11 +2618,13 @@ /* Process the input line. */ if( nArg==0 ) return 0; /* no tokens, no error */ n = strlen30(azArg[0]); c = azArg[0][0]; - if( c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0 ){ + if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0) + || (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0) + ){ const char *zDestFile = 0; const char *zDb = 0; sqlite3 *pDest; sqlite3_backup *pBackup; int j; @@ -1832,11 +2656,11 @@ if( rc!=SQLITE_OK ){ fprintf(stderr, "Error: cannot open \"%s\"\n", zDestFile); sqlite3_close(pDest); return 1; } - open_db(p); + open_db(p, 0); pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb); if( pBackup==0 ){ fprintf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); sqlite3_close(pDest); return 1; @@ -1850,25 +2674,39 @@ rc = 1; } sqlite3_close(pDest); }else - if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 && nArg>1 && nArg<3 ){ - bail_on_error = booleanValue(azArg[1]); + if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 ){ + if( nArg==2 ){ + bail_on_error = booleanValue(azArg[1]); + }else{ + fprintf(stderr, "Usage: .bail on|off\n"); + rc = 1; + } }else /* The undocumented ".breakpoint" command causes a call to the no-op ** routine named test_breakpoint(). */ if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){ test_breakpoint(); }else - if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 && nArg==1 ){ - struct callback_data data; + if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){ + if( nArg==2 ){ + tryToClone(p, azArg[1]); + }else{ + fprintf(stderr, "Usage: .clone FILENAME\n"); + rc = 1; + } + }else + + if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){ + ShellState data; char *zErrMsg = 0; - open_db(p); + open_db(p, 0); memcpy(&data, p, sizeof(data)); data.showHeader = 1; data.mode = MODE_Column; data.colWidth[0] = 3; data.colWidth[1] = 15; @@ -1880,15 +2718,24 @@ sqlite3_free(zErrMsg); rc = 1; } }else - if( c=='d' && strncmp(azArg[0], "dump", n)==0 && nArg<3 ){ - open_db(p); + if( c=='d' && strncmp(azArg[0], "dbinfo", n)==0 ){ + rc = shell_dbinfo_command(p, nArg, azArg); + }else + + if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ + open_db(p, 0); /* When playing back a "dump", the content might appear in an order ** which causes immediate foreign key constraints to be violated. ** So disable foreign-key constraint enforcement to prevent problems. */ + if( nArg!=1 && nArg!=2 ){ + fprintf(stderr, "Usage: .dump ?LIKE-PATTERN?\n"); + rc = 1; + goto meta_command_exit; + } fprintf(p->out, "PRAGMA foreign_keys=OFF;\n"); fprintf(p->out, "BEGIN TRANSACTION;\n"); p->writableSchema = 0; sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); p->nErr = 0; @@ -1929,27 +2776,41 @@ sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); fprintf(p->out, p->nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n"); }else - if( c=='e' && strncmp(azArg[0], "echo", n)==0 && nArg>1 && nArg<3 ){ - p->echoOn = booleanValue(azArg[1]); + if( c=='e' && strncmp(azArg[0], "echo", n)==0 ){ + if( nArg==2 ){ + p->echoOn = booleanValue(azArg[1]); + }else{ + fprintf(stderr, "Usage: .echo on|off\n"); + rc = 1; + } + }else + + if( c=='e' && strncmp(azArg[0], "eqp", n)==0 ){ + if( nArg==2 ){ + p->autoEQP = booleanValue(azArg[1]); + }else{ + fprintf(stderr, "Usage: .eqp on|off\n"); + rc = 1; + } }else if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); rc = 2; }else - if( c=='e' && strncmp(azArg[0], "explain", n)==0 && nArg<3 ){ + if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){ int val = nArg>=2 ? booleanValue(azArg[1]) : 1; if(val == 1) { - if(!p->explainPrev.valid) { - p->explainPrev.valid = 1; - p->explainPrev.mode = p->mode; - p->explainPrev.showHeader = p->showHeader; - memcpy(p->explainPrev.colWidth,p->colWidth,sizeof(p->colWidth)); + if(!p->normalMode.valid) { + p->normalMode.valid = 1; + p->normalMode.mode = p->mode; + p->normalMode.showHeader = p->showHeader; + memcpy(p->normalMode.colWidth,p->colWidth,sizeof(p->colWidth)); } /* We could put this code under the !p->explainValid ** condition so that it does not execute if we are already in ** explain mode. However, always executing it allows us an easy ** was to reset to explain mode in case the user previously @@ -1956,130 +2817,219 @@ ** did an .explain followed by a .width, .mode or .header ** command. */ p->mode = MODE_Explain; p->showHeader = 1; - memset(p->colWidth,0,ArraySize(p->colWidth)); + memset(p->colWidth,0,sizeof(p->colWidth)); p->colWidth[0] = 4; /* addr */ p->colWidth[1] = 13; /* opcode */ p->colWidth[2] = 4; /* P1 */ p->colWidth[3] = 4; /* P2 */ p->colWidth[4] = 4; /* P3 */ p->colWidth[5] = 13; /* P4 */ p->colWidth[6] = 2; /* P5 */ p->colWidth[7] = 13; /* Comment */ - }else if (p->explainPrev.valid) { - p->explainPrev.valid = 0; - p->mode = p->explainPrev.mode; - p->showHeader = p->explainPrev.showHeader; - memcpy(p->colWidth,p->explainPrev.colWidth,sizeof(p->colWidth)); + }else if (p->normalMode.valid) { + p->normalMode.valid = 0; + p->mode = p->normalMode.mode; + p->showHeader = p->normalMode.showHeader; + memcpy(p->colWidth,p->normalMode.colWidth,sizeof(p->colWidth)); + } + }else + + if( c=='f' && strncmp(azArg[0], "fullschema", n)==0 ){ + ShellState data; + char *zErrMsg = 0; + int doStats = 0; + if( nArg!=1 ){ + fprintf(stderr, "Usage: .fullschema\n"); + rc = 1; + goto meta_command_exit; + } + open_db(p, 0); + memcpy(&data, p, sizeof(data)); + data.showHeader = 0; + data.mode = MODE_Semi; + rc = sqlite3_exec(p->db, + "SELECT sql FROM" + " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" + " FROM sqlite_master UNION ALL" + " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_master) " + "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' " + "ORDER BY rowid", + callback, &data, &zErrMsg + ); + if( rc==SQLITE_OK ){ + sqlite3_stmt *pStmt; + rc = sqlite3_prepare_v2(p->db, + "SELECT rowid FROM sqlite_master" + " WHERE name GLOB 'sqlite_stat[134]'", + -1, &pStmt, 0); + doStats = sqlite3_step(pStmt)==SQLITE_ROW; + sqlite3_finalize(pStmt); + } + if( doStats==0 ){ + fprintf(p->out, "/* No STAT tables available */\n"); + }else{ + fprintf(p->out, "ANALYZE sqlite_master;\n"); + sqlite3_exec(p->db, "SELECT 'ANALYZE sqlite_master'", + callback, &data, &zErrMsg); + data.mode = MODE_Insert; + data.zDestTable = "sqlite_stat1"; + shell_exec(p->db, "SELECT * FROM sqlite_stat1", + shell_callback, &data,&zErrMsg); + data.zDestTable = "sqlite_stat3"; + shell_exec(p->db, "SELECT * FROM sqlite_stat3", + shell_callback, &data,&zErrMsg); + data.zDestTable = "sqlite_stat4"; + shell_exec(p->db, "SELECT * FROM sqlite_stat4", + shell_callback, &data, &zErrMsg); + fprintf(p->out, "ANALYZE sqlite_master;\n"); } }else - if( c=='h' && (strncmp(azArg[0], "header", n)==0 || - strncmp(azArg[0], "headers", n)==0) && nArg>1 && nArg<3 ){ - p->showHeader = booleanValue(azArg[1]); + if( c=='h' && strncmp(azArg[0], "headers", n)==0 ){ + if( nArg==2 ){ + p->showHeader = booleanValue(azArg[1]); + }else{ + fprintf(stderr, "Usage: .headers on|off\n"); + rc = 1; + } }else if( c=='h' && strncmp(azArg[0], "help", n)==0 ){ - fprintf(stderr,"%s",zHelp); - if( HAS_TIMER ){ - fprintf(stderr,"%s",zTimerHelp); - } + fprintf(p->out, "%s", zHelp); }else - if( c=='i' && strncmp(azArg[0], "import", n)==0 && nArg==3 ){ - char *zTable = azArg[2]; /* Insert data into this table */ - char *zFile = azArg[1]; /* Name of file to extra content from */ + if( c=='i' && strncmp(azArg[0], "import", n)==0 ){ + char *zTable; /* Insert data into this table */ + char *zFile; /* Name of file to extra content from */ sqlite3_stmt *pStmt = NULL; /* A statement */ int nCol; /* Number of columns in the table */ int nByte; /* Number of bytes in an SQL string */ int i, j; /* Loop counters */ int needCommit; /* True to COMMIT or ROLLBACK at end */ - int nSep; /* Number of bytes in p->separator[] */ + int nSep; /* Number of bytes in p->colSeparator[] */ char *zSql; /* An SQL statement */ - CSVReader sCsv; /* Reader context */ - int (*xCloser)(FILE*); /* Procedure to close th3 connection */ + ImportCtx sCtx; /* Reader context */ + char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ + int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close file */ + if( nArg!=3 ){ + fprintf(stderr, "Usage: .import FILE TABLE\n"); + goto meta_command_exit; + } + zFile = azArg[1]; + zTable = azArg[2]; seenInterrupt = 0; - memset(&sCsv, 0, sizeof(sCsv)); - open_db(p); - nSep = strlen30(p->separator); + memset(&sCtx, 0, sizeof(sCtx)); + open_db(p, 0); + nSep = strlen30(p->colSeparator); + if( nSep==0 ){ + fprintf(stderr, "Error: non-null column separator required for import\n"); + return 1; + } + if( nSep>1 ){ + fprintf(stderr, "Error: multi-character column separators not allowed" + " for import\n"); + return 1; + } + nSep = strlen30(p->rowSeparator); if( nSep==0 ){ - fprintf(stderr, "Error: non-null separator required for import\n"); + fprintf(stderr, "Error: non-null row separator required for import\n"); return 1; + } + if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator, SEP_CrLf)==0 ){ + /* When importing CSV (only), if the row separator is set to the + ** default output row separator, change it to the default input + ** row separator. This avoids having to maintain different input + ** and output row separators. */ + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); + nSep = strlen30(p->rowSeparator); } if( nSep>1 ){ - fprintf(stderr, "Error: multi-character separators not allowed" + fprintf(stderr, "Error: multi-character row separators not allowed" " for import\n"); return 1; } - sCsv.zFile = zFile; - sCsv.nLine = 1; - if( sCsv.zFile[0]=='|' ){ - sCsv.in = popen(sCsv.zFile+1, "r"); - sCsv.zFile = ""; + sCtx.zFile = zFile; + sCtx.nLine = 1; + if( sCtx.zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + fprintf(stderr, "Error: pipes are not supported in this OS\n"); + return 1; +#else + sCtx.in = popen(sCtx.zFile+1, "r"); + sCtx.zFile = ""; xCloser = pclose; +#endif }else{ - sCsv.in = fopen(sCsv.zFile, "rb"); + sCtx.in = fopen(sCtx.zFile, "rb"); xCloser = fclose; } - if( sCsv.in==0 ){ + if( p->mode==MODE_Ascii ){ + xRead = ascii_read_one_field; + }else{ + xRead = csv_read_one_field; + } + if( sCtx.in==0 ){ fprintf(stderr, "Error: cannot open \"%s\"\n", zFile); return 1; } - sCsv.cSeparator = p->separator[0]; + sCtx.cColSep = p->colSeparator[0]; + sCtx.cRowSep = p->rowSeparator[0]; zSql = sqlite3_mprintf("SELECT * FROM %s", zTable); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); - xCloser(sCsv.in); + xCloser(sCtx.in); return 1; } nByte = strlen30(zSql); - rc = sqlite3_prepare(p->db, zSql, -1, &pStmt, 0); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(db))==0 ){ char *zCreate = sqlite3_mprintf("CREATE TABLE %s", zTable); char cSep = '('; - while( csv_read_one_field(&sCsv) ){ - zCreate = sqlite3_mprintf("%z%c\n \"%s\" TEXT", zCreate, cSep, sCsv.z); + while( xRead(&sCtx) ){ + zCreate = sqlite3_mprintf("%z%c\n \"%s\" TEXT", zCreate, cSep, sCtx.z); cSep = ','; - if( sCsv.cTerm!=sCsv.cSeparator ) break; + if( sCtx.cTerm!=sCtx.cColSep ) break; } if( cSep=='(' ){ sqlite3_free(zCreate); - sqlite3_free(sCsv.z); - xCloser(sCsv.in); - fprintf(stderr,"%s: empty file\n", sCsv.zFile); + sqlite3_free(sCtx.z); + xCloser(sCtx.in); + fprintf(stderr,"%s: empty file\n", sCtx.zFile); return 1; } zCreate = sqlite3_mprintf("%z\n)", zCreate); rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); sqlite3_free(zCreate); if( rc ){ fprintf(stderr, "CREATE TABLE %s(...) failed: %s\n", zTable, sqlite3_errmsg(db)); - sqlite3_free(sCsv.z); - xCloser(sCsv.in); + sqlite3_free(sCtx.z); + xCloser(sCtx.in); return 1; } - rc = sqlite3_prepare(p->db, zSql, -1, &pStmt, 0); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); } sqlite3_free(zSql); if( rc ){ if (pStmt) sqlite3_finalize(pStmt); fprintf(stderr,"Error: %s\n", sqlite3_errmsg(db)); - xCloser(sCsv.in); + xCloser(sCtx.in); return 1; } nCol = sqlite3_column_count(pStmt); sqlite3_finalize(pStmt); pStmt = 0; if( nCol==0 ) return 0; /* no columns, no error */ zSql = sqlite3_malloc( nByte*2 + 20 + nCol*2 ); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); - xCloser(sCsv.in); + xCloser(sCtx.in); return 1; } sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); j = strlen30(zSql); for(i=1; idb, zSql, -1, &pStmt, 0); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rc ){ fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db)); if (pStmt) sqlite3_finalize(pStmt); - xCloser(sCsv.in); + xCloser(sCtx.in); return 1; } needCommit = sqlite3_get_autocommit(db); if( needCommit ) sqlite3_exec(db, "BEGIN", 0, 0, 0); do{ - int startLine = sCsv.nLine; + int startLine = sCtx.nLine; for(i=0; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); - if( i=nCol ){ sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); if( rc!=SQLITE_OK ){ - fprintf(stderr, "%s:%d: INSERT failed: %s\n", sCsv.zFile, startLine, + fprintf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile, startLine, sqlite3_errmsg(db)); } } - }while( sCsv.cTerm!=EOF ); + }while( sCtx.cTerm!=EOF ); - xCloser(sCsv.in); - sqlite3_free(sCsv.z); + xCloser(sCtx.in); + sqlite3_free(sCtx.z); sqlite3_finalize(pStmt); if( needCommit ) sqlite3_exec(db, "COMMIT", 0, 0, 0); }else - if( c=='i' && strncmp(azArg[0], "indices", n)==0 && nArg<3 ){ - struct callback_data data; + if( c=='i' && (strncmp(azArg[0], "indices", n)==0 + || strncmp(azArg[0], "indexes", n)==0) ){ + ShellState data; char *zErrMsg = 0; - open_db(p); + open_db(p, 0); memcpy(&data, p, sizeof(data)); data.showHeader = 0; data.mode = MODE_List; if( nArg==1 ){ rc = sqlite3_exec(p->db, @@ -2152,11 +3113,11 @@ "SELECT name FROM sqlite_temp_master " "WHERE type='index' " "ORDER BY 1", callback, &data, &zErrMsg ); - }else{ + }else if( nArg==2 ){ zShellStatic = azArg[1]; rc = sqlite3_exec(p->db, "SELECT name FROM sqlite_master " "WHERE type='index' AND tbl_name LIKE shellstatic() " "UNION ALL " @@ -2164,10 +3125,14 @@ "WHERE type='index' AND tbl_name LIKE shellstatic() " "ORDER BY 1", callback, &data, &zErrMsg ); zShellStatic = 0; + }else{ + fprintf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n"); + rc = 1; + goto meta_command_exit; } if( zErrMsg ){ fprintf(stderr,"Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; @@ -2177,11 +3142,11 @@ } }else #ifdef SQLITE_ENABLE_IOTRACE if( c=='i' && strncmp(azArg[0], "iotrace", n)==0 ){ - extern void (*sqlite3IoTrace)(const char*, ...); + SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...); if( iotrace && iotrace!=stdout ) fclose(iotrace); iotrace = 0; if( nArg<2 ){ sqlite3IoTrace = 0; }else if( strcmp(azArg[1], "-")==0 ){ @@ -2199,107 +3164,152 @@ } }else #endif #ifndef SQLITE_OMIT_LOAD_EXTENSION - if( c=='l' && strncmp(azArg[0], "load", n)==0 && nArg>=2 ){ + if( c=='l' && strncmp(azArg[0], "load", n)==0 ){ const char *zFile, *zProc; char *zErrMsg = 0; + if( nArg<2 ){ + fprintf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n"); + rc = 1; + goto meta_command_exit; + } zFile = azArg[1]; zProc = nArg>=3 ? azArg[2] : 0; - open_db(p); + open_db(p, 0); rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg); if( rc!=SQLITE_OK ){ fprintf(stderr, "Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; } }else #endif - if( c=='l' && strncmp(azArg[0], "log", n)==0 && nArg>=2 ){ - const char *zFile = azArg[1]; - output_file_close(p->pLog); - p->pLog = output_file_open(zFile); + if( c=='l' && strncmp(azArg[0], "log", n)==0 ){ + if( nArg!=2 ){ + fprintf(stderr, "Usage: .log FILENAME\n"); + rc = 1; + }else{ + const char *zFile = azArg[1]; + output_file_close(p->pLog); + p->pLog = output_file_open(zFile); + } }else - if( c=='m' && strncmp(azArg[0], "mode", n)==0 && nArg==2 ){ - int n2 = strlen30(azArg[1]); - if( (n2==4 && strncmp(azArg[1],"line",n2)==0) - || - (n2==5 && strncmp(azArg[1],"lines",n2)==0) ){ + if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){ + const char *zMode = nArg>=2 ? azArg[1] : ""; + int n2 = (int)strlen(zMode); + int c2 = zMode[0]; + if( c2=='l' && n2>2 && strncmp(azArg[1],"lines",n2)==0 ){ p->mode = MODE_Line; - }else if( (n2==6 && strncmp(azArg[1],"column",n2)==0) - || - (n2==7 && strncmp(azArg[1],"columns",n2)==0) ){ + }else if( c2=='c' && strncmp(azArg[1],"columns",n2)==0 ){ p->mode = MODE_Column; - }else if( n2==4 && strncmp(azArg[1],"list",n2)==0 ){ + }else if( c2=='l' && n2>2 && strncmp(azArg[1],"list",n2)==0 ){ p->mode = MODE_List; - }else if( n2==4 && strncmp(azArg[1],"html",n2)==0 ){ + }else if( c2=='h' && strncmp(azArg[1],"html",n2)==0 ){ p->mode = MODE_Html; - }else if( n2==3 && strncmp(azArg[1],"tcl",n2)==0 ){ + }else if( c2=='t' && strncmp(azArg[1],"tcl",n2)==0 ){ p->mode = MODE_Tcl; - sqlite3_snprintf(sizeof(p->separator), p->separator, " "); - }else if( n2==3 && strncmp(azArg[1],"csv",n2)==0 ){ + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space); + }else if( c2=='c' && strncmp(azArg[1],"csv",n2)==0 ){ p->mode = MODE_Csv; - sqlite3_snprintf(sizeof(p->separator), p->separator, ","); - }else if( n2==4 && strncmp(azArg[1],"tabs",n2)==0 ){ + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); + }else if( c2=='t' && strncmp(azArg[1],"tabs",n2)==0 ){ p->mode = MODE_List; - sqlite3_snprintf(sizeof(p->separator), p->separator, "\t"); - }else if( n2==6 && strncmp(azArg[1],"insert",n2)==0 ){ + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab); + }else if( c2=='i' && strncmp(azArg[1],"insert",n2)==0 ){ p->mode = MODE_Insert; - set_table_name(p, "table"); + set_table_name(p, nArg>=3 ? azArg[2] : "table"); + }else if( c2=='a' && strncmp(azArg[1],"ascii",n2)==0 ){ + p->mode = MODE_Ascii; + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit); + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record); }else { fprintf(stderr,"Error: mode should be one of: " - "column csv html insert line list tabs tcl\n"); - rc = 1; - } - }else - - if( c=='m' && strncmp(azArg[0], "mode", n)==0 && nArg==3 ){ - int n2 = strlen30(azArg[1]); - if( n2==6 && strncmp(azArg[1],"insert",n2)==0 ){ - p->mode = MODE_Insert; - set_table_name(p, azArg[2]); - }else { - fprintf(stderr, "Error: invalid arguments: " - " \"%s\". Enter \".help\" for help\n", azArg[2]); - rc = 1; - } - }else - - if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 && nArg==2 ) { - sqlite3_snprintf(sizeof(p->nullvalue), p->nullvalue, - "%.*s", (int)ArraySize(p->nullvalue)-1, azArg[1]); - }else - - if( c=='o' && strncmp(azArg[0], "output", n)==0 && nArg==2 ){ - if( p->outfile[0]=='|' ){ - pclose(p->out); + "ascii column csv html insert line list tabs tcl\n"); + rc = 1; + } + }else + + if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){ + if( nArg==2 ){ + sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, + "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]); + }else{ + fprintf(stderr, "Usage: .nullvalue STRING\n"); + rc = 1; + } + }else + + if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){ + sqlite3 *savedDb = p->db; + const char *zSavedFilename = p->zDbFilename; + char *zNewFilename = 0; + p->db = 0; + if( nArg>=2 ){ + p->zDbFilename = zNewFilename = sqlite3_mprintf("%s", azArg[1]); + } + open_db(p, 1); + if( p->db!=0 ){ + sqlite3_close(savedDb); + sqlite3_free(p->zFreeOnClose); + p->zFreeOnClose = zNewFilename; + }else{ + sqlite3_free(zNewFilename); + p->db = savedDb; + p->zDbFilename = zSavedFilename; + } + }else + + if( c=='o' + && (strncmp(azArg[0], "output", n)==0 || strncmp(azArg[0], "once", n)==0) + ){ + const char *zFile = nArg>=2 ? azArg[1] : "stdout"; + if( nArg>2 ){ + fprintf(stderr, "Usage: .%s FILE\n", azArg[0]); + rc = 1; + goto meta_command_exit; + } + if( n>1 && strncmp(azArg[0], "once", n)==0 ){ + if( nArg<2 ){ + fprintf(stderr, "Usage: .once FILE\n"); + rc = 1; + goto meta_command_exit; + } + p->outCount = 2; }else{ - output_file_close(p->out); + p->outCount = 0; } - p->outfile[0] = 0; - if( azArg[1][0]=='|' ){ - p->out = popen(&azArg[1][1], "w"); + output_reset(p); + if( zFile[0]=='|' ){ +#ifdef SQLITE_OMIT_POPEN + fprintf(stderr,"Error: pipes are not supported in this OS\n"); + rc = 1; + p->out = stdout; +#else + p->out = popen(zFile + 1, "w"); if( p->out==0 ){ - fprintf(stderr,"Error: cannot open pipe \"%s\"\n", &azArg[1][1]); + fprintf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1); p->out = stdout; rc = 1; }else{ - sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", azArg[1]); + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } +#endif }else{ - p->out = output_file_open(azArg[1]); + p->out = output_file_open(zFile); if( p->out==0 ){ - if( strcmp(azArg[1],"off")!=0 ){ - fprintf(stderr,"Error: cannot write to \"%s\"\n", azArg[1]); + if( strcmp(zFile,"off")!=0 ){ + fprintf(stderr,"Error: cannot write to \"%s\"\n", zFile); } p->out = stdout; rc = 1; } else { - sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", azArg[1]); + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } } }else if( c=='p' && n>=3 && strncmp(azArg[0], "print", n)==0 ){ @@ -2309,55 +3319,65 @@ fprintf(p->out, "%s", azArg[i]); } fprintf(p->out, "\n"); }else - if( c=='p' && strncmp(azArg[0], "prompt", n)==0 && (nArg==2 || nArg==3)){ + if( c=='p' && strncmp(azArg[0], "prompt", n)==0 ){ if( nArg >= 2) { strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); } if( nArg >= 3) { strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1); } }else - if( c=='q' && strncmp(azArg[0], "quit", n)==0 && nArg==1 ){ + if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){ rc = 2; }else - if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 && nArg==2 ){ - FILE *alt = fopen(azArg[1], "rb"); + if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 ){ + FILE *alt; + if( nArg!=2 ){ + fprintf(stderr, "Usage: .read FILE\n"); + rc = 1; + goto meta_command_exit; + } + alt = fopen(azArg[1], "rb"); if( alt==0 ){ fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ rc = process_input(p, alt); fclose(alt); } }else - if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 && nArg>1 && nArg<4){ + if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){ const char *zSrcFile; const char *zDb; sqlite3 *pSrc; sqlite3_backup *pBackup; int nTimeout = 0; if( nArg==2 ){ zSrcFile = azArg[1]; zDb = "main"; - }else{ + }else if( nArg==3 ){ zSrcFile = azArg[2]; zDb = azArg[1]; + }else{ + fprintf(stderr, "Usage: .restore ?DB? FILE\n"); + rc = 1; + goto meta_command_exit; } rc = sqlite3_open(zSrcFile, &pSrc); if( rc!=SQLITE_OK ){ fprintf(stderr, "Error: cannot open \"%s\"\n", zSrcFile); sqlite3_close(pSrc); return 1; } - open_db(p); + open_db(p, 0); pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main"); if( pBackup==0 ){ fprintf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); sqlite3_close(pSrc); return 1; @@ -2380,18 +3400,31 @@ rc = 1; } sqlite3_close(pSrc); }else - if( c=='s' && strncmp(azArg[0], "schema", n)==0 && nArg<3 ){ - struct callback_data data; + + if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){ + if( nArg==2 ){ + p->scanstatsOn = booleanValue(azArg[1]); +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS + fprintf(stderr, "Warning: .scanstats not available in this build.\n"); +#endif + }else{ + fprintf(stderr, "Usage: .scanstats on|off\n"); + rc = 1; + } + }else + + if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){ + ShellState data; char *zErrMsg = 0; - open_db(p); + open_db(p, 0); memcpy(&data, p, sizeof(data)); data.showHeader = 0; data.mode = MODE_Semi; - if( nArg>1 ){ + if( nArg==2 ){ int i; for(i=0; azArg[1][i]; i++) azArg[1][i] = ToLower(azArg[1][i]); if( strcmp(azArg[1],"sqlite_master")==0 ){ char *new_argv[2], *new_colv[2]; new_argv[0] = "CREATE TABLE sqlite_master (\n" @@ -2431,20 +3464,24 @@ " AND type!='meta' AND sql NOTNULL " "ORDER BY rowid", callback, &data, &zErrMsg); zShellStatic = 0; } - }else{ + }else if( nArg==1 ){ rc = sqlite3_exec(p->db, "SELECT sql FROM " " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" " FROM sqlite_master UNION ALL" " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_master) " - "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%'" + "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' " "ORDER BY rowid", callback, &data, &zErrMsg ); + }else{ + fprintf(stderr, "Usage: .schema ?LIKE-PATTERN?\n"); + rc = 1; + goto meta_command_exit; } if( zErrMsg ){ fprintf(stderr,"Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; @@ -2453,10 +3490,19 @@ rc = 1; }else{ rc = 0; } }else + + +#if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_SELECTTRACE) + if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){ + extern int sqlite3SelectTrace; + sqlite3SelectTrace = integerValue(azArg[1]); + }else +#endif + #ifdef SQLITE_DEBUG /* Undocumented commands for internal testing. Subject to change ** without notice. */ if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){ @@ -2470,55 +3516,99 @@ if( strncmp(azArg[0]+9, "integer", n-9)==0 ){ int i; sqlite3_int64 v; for(i=1; iout, "%s", zBuf); } } }else #endif - if( c=='s' && strncmp(azArg[0], "separator", n)==0 && nArg==2 ){ - sqlite3_snprintf(sizeof(p->separator), p->separator, - "%.*s", (int)sizeof(p->separator)-1, azArg[1]); + if( c=='s' && strncmp(azArg[0], "separator", n)==0 ){ + if( nArg<2 || nArg>3 ){ + fprintf(stderr, "Usage: .separator COL ?ROW?\n"); + rc = 1; + } + if( nArg>=2 ){ + sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, + "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]); + } + if( nArg>=3 ){ + sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, + "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]); + } + }else + + if( c=='s' + && (strncmp(azArg[0], "shell", n)==0 || strncmp(azArg[0],"system",n)==0) + ){ + char *zCmd; + int i, x; + if( nArg<2 ){ + fprintf(stderr, "Usage: .system COMMAND\n"); + rc = 1; + goto meta_command_exit; + } + zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]); + for(i=2; iout,"%9.9s: %s\n","echo", p->echoOn ? "on" : "off"); - fprintf(p->out,"%9.9s: %s\n","explain", p->explainPrev.valid ? "on" :"off"); - fprintf(p->out,"%9.9s: %s\n","headers", p->showHeader ? "on" : "off"); - fprintf(p->out,"%9.9s: %s\n","mode", modeDescr[p->mode]); - fprintf(p->out,"%9.9s: ", "nullvalue"); - output_c_string(p->out, p->nullvalue); + if( nArg!=1 ){ + fprintf(stderr, "Usage: .show\n"); + rc = 1; + goto meta_command_exit; + } + fprintf(p->out,"%12.12s: %s\n","echo", p->echoOn ? "on" : "off"); + fprintf(p->out,"%12.12s: %s\n","eqp", p->autoEQP ? "on" : "off"); + fprintf(p->out,"%9.9s: %s\n","explain", p->normalMode.valid ? "on" :"off"); + fprintf(p->out,"%12.12s: %s\n","headers", p->showHeader ? "on" : "off"); + fprintf(p->out,"%12.12s: %s\n","mode", modeDescr[p->mode]); + fprintf(p->out,"%12.12s: ", "nullvalue"); + output_c_string(p->out, p->nullValue); fprintf(p->out, "\n"); - fprintf(p->out,"%9.9s: %s\n","output", + fprintf(p->out,"%12.12s: %s\n","output", strlen30(p->outfile) ? p->outfile : "stdout"); - fprintf(p->out,"%9.9s: ", "separator"); - output_c_string(p->out, p->separator); + fprintf(p->out,"%12.12s: ", "colseparator"); + output_c_string(p->out, p->colSeparator); + fprintf(p->out, "\n"); + fprintf(p->out,"%12.12s: ", "rowseparator"); + output_c_string(p->out, p->rowSeparator); fprintf(p->out, "\n"); - fprintf(p->out,"%9.9s: %s\n","stats", p->statsOn ? "on" : "off"); - fprintf(p->out,"%9.9s: ","width"); + fprintf(p->out,"%12.12s: %s\n","stats", p->statsOn ? "on" : "off"); + fprintf(p->out,"%12.12s: ","width"); for (i=0;i<(int)ArraySize(p->colWidth) && p->colWidth[i] != 0;i++) { fprintf(p->out,"%d ",p->colWidth[i]); } fprintf(p->out,"\n"); }else - if( c=='s' && strncmp(azArg[0], "stats", n)==0 && nArg>1 && nArg<3 ){ - p->statsOn = booleanValue(azArg[1]); + if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){ + if( nArg==2 ){ + p->statsOn = booleanValue(azArg[1]); + }else{ + fprintf(stderr, "Usage: .stats on|off\n"); + rc = 1; + } }else - if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 && nArg<3 ){ + if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 ){ sqlite3_stmt *pStmt; char **azResult; int nRow, nAlloc; char *zSql = 0; int ii; - open_db(p); + open_db(p, 0); rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); if( rc ) return rc; zSql = sqlite3_mprintf( "SELECT name FROM sqlite_master" " WHERE type IN ('table','view')" @@ -2583,11 +3673,11 @@ if( nPrintCol<1 ) nPrintCol = 1; nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; for(i=0; iout, "%s%-*s", zSp, maxlen, azResult[j] ? azResult[j] : ""); + fprintf(p->out, "%s%-*s", zSp, maxlen, azResult[j] ? azResult[j]:""); } fprintf(p->out, "\n"); } } for(ii=0; iiout, "%d (0x%08x)\n", rc, rc); } else { fprintf(stderr,"Error: testctrl %s takes no options\n", azArg[1]); @@ -2675,11 +3769,12 @@ } break; /* sqlite3_test_control(int, int) */ case SQLITE_TESTCTRL_ASSERT: - case SQLITE_TESTCTRL_ALWAYS: + case SQLITE_TESTCTRL_ALWAYS: + case SQLITE_TESTCTRL_NEVER_CORRUPT: if( nArg==3 ){ int opt = booleanValue(azArg[2]); rc = sqlite3_test_control(testctrl, opt); fprintf(p->out, "%d (0x%08x)\n", rc, rc); } else { @@ -2699,10 +3794,22 @@ fprintf(stderr,"Error: testctrl %s takes a single char * option\n", azArg[1]); } break; #endif + + case SQLITE_TESTCTRL_IMPOSTER: + if( nArg==5 ){ + rc = sqlite3_test_control(testctrl, p->db, + azArg[2], + integerValue(azArg[3]), + integerValue(azArg[4])); + fprintf(p->out, "%d (0x%08x)\n", rc, rc); + }else{ + fprintf(stderr,"Usage: .testctrl imposter dbName onoff tnum\n"); + } + break; case SQLITE_TESTCTRL_BITVEC_TEST: case SQLITE_TESTCTRL_FAULT_INSTALL: case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: case SQLITE_TESTCTRL_SCRATCHMALLOC: @@ -2712,23 +3819,35 @@ break; } } }else - if( c=='t' && n>4 && strncmp(azArg[0], "timeout", n)==0 && nArg==2 ){ - open_db(p); - sqlite3_busy_timeout(p->db, (int)integerValue(azArg[1])); + if( c=='t' && n>4 && strncmp(azArg[0], "timeout", n)==0 ){ + open_db(p, 0); + sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0); }else - if( HAS_TIMER && c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0 - && nArg==2 - ){ - enableTimer = booleanValue(azArg[1]); + if( c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0 ){ + if( nArg==2 ){ + enableTimer = booleanValue(azArg[1]); + if( enableTimer && !HAS_TIMER ){ + fprintf(stderr, "Error: timer not available on this system.\n"); + enableTimer = 0; + } + }else{ + fprintf(stderr, "Usage: .timer on|off\n"); + rc = 1; + } }else - if( c=='t' && strncmp(azArg[0], "trace", n)==0 && nArg>1 ){ - open_db(p); + if( c=='t' && strncmp(azArg[0], "trace", n)==0 ){ + open_db(p, 0); + if( nArg!=2 ){ + fprintf(stderr, "Usage: .trace FILE|off\n"); + rc = 1; + goto meta_command_exit; + } output_file_close(p->traceOut); p->traceOut = output_file_open(azArg[1]); #if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) if( p->traceOut==0 ){ sqlite3_trace(p->db, 0, 0); @@ -2736,10 +3855,75 @@ sqlite3_trace(p->db, sql_trace_callback, p->traceOut); } #endif }else +#if SQLITE_USER_AUTHENTICATION + if( c=='u' && strncmp(azArg[0], "user", n)==0 ){ + if( nArg<2 ){ + fprintf(stderr, "Usage: .user SUBCOMMAND ...\n"); + rc = 1; + goto meta_command_exit; + } + open_db(p, 0); + if( strcmp(azArg[1],"login")==0 ){ + if( nArg!=4 ){ + fprintf(stderr, "Usage: .user login USER PASSWORD\n"); + rc = 1; + goto meta_command_exit; + } + rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], + (int)strlen(azArg[3])); + if( rc ){ + fprintf(stderr, "Authentication failed for user %s\n", azArg[2]); + rc = 1; + } + }else if( strcmp(azArg[1],"add")==0 ){ + if( nArg!=5 ){ + fprintf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n"); + rc = 1; + goto meta_command_exit; + } + rc = sqlite3_user_add(p->db, azArg[2], + azArg[3], (int)strlen(azArg[3]), + booleanValue(azArg[4])); + if( rc ){ + fprintf(stderr, "User-Add failed: %d\n", rc); + rc = 1; + } + }else if( strcmp(azArg[1],"edit")==0 ){ + if( nArg!=5 ){ + fprintf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n"); + rc = 1; + goto meta_command_exit; + } + rc = sqlite3_user_change(p->db, azArg[2], + azArg[3], (int)strlen(azArg[3]), + booleanValue(azArg[4])); + if( rc ){ + fprintf(stderr, "User-Edit failed: %d\n", rc); + rc = 1; + } + }else if( strcmp(azArg[1],"delete")==0 ){ + if( nArg!=3 ){ + fprintf(stderr, "Usage: .user delete USER\n"); + rc = 1; + goto meta_command_exit; + } + rc = sqlite3_user_delete(p->db, azArg[2]); + if( rc ){ + fprintf(stderr, "User-Delete failed: %d\n", rc); + rc = 1; + } + }else{ + fprintf(stderr, "Usage: .user login|add|edit|delete ...\n"); + rc = 1; + goto meta_command_exit; + } + }else +#endif /* SQLITE_USER_AUTHENTICATION */ + if( c=='v' && strncmp(azArg[0], "version", n)==0 ){ fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/, sqlite3_libversion(), sqlite3_sourceid()); }else @@ -2756,15 +3940,15 @@ }else #if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_WHERETRACE) if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){ extern int sqlite3WhereTrace; - sqlite3WhereTrace = booleanValue(azArg[1]); + sqlite3WhereTrace = nArg>=2 ? booleanValue(azArg[1]) : 0xff; }else #endif - if( c=='w' && strncmp(azArg[0], "width", n)==0 && nArg>1 ){ + if( c=='w' && strncmp(azArg[0], "width", n)==0 ){ int j; assert( nArg<=ArraySize(azArg) ); for(j=1; jcolWidth); j++){ p->colWidth[j-1] = (int)integerValue(azArg[j]); } @@ -2774,10 +3958,15 @@ fprintf(stderr, "Error: unknown command or invalid arguments: " " \"%s\". Enter \".help\" for help\n", azArg[0]); rc = 1; } +meta_command_exit: + if( p->outCount ){ + p->outCount--; + if( p->outCount==0 ) output_reset(p); + } return rc; } /* ** Return TRUE if a semicolon occurs anywhere in the first N characters @@ -2851,11 +4040,11 @@ ** is saved only if input is interactive. An interrupt signal will ** cause this routine to exit immediately, unless input is interactive. ** ** Return the number of errors. */ -static int process_input(struct callback_data *p, FILE *in){ +static int process_input(ShellState *p, FILE *in){ char *zLine = 0; /* A single input line */ char *zSql = 0; /* Accumulated SQL text */ int nLine; /* Length of current line */ int nSql = 0; /* Bytes of zSql[] used */ int nAlloc = 0; /* Allocated zSql[] space */ @@ -2877,11 +4066,14 @@ if( seenInterrupt ){ if( in!=0 ) break; seenInterrupt = 0; } lineno++; - if( nSql==0 && _all_whitespace(zLine) ) continue; + if( nSql==0 && _all_whitespace(zLine) ){ + if( p->echoOn ) printf("%s\n", zLine); + continue; + } if( zLine && zLine[0]=='.' && nSql==0 ){ if( p->echoOn ) printf("%s\n", zLine); rc = do_meta_command(zLine, p); if( rc==2 ){ /* exit requested */ break; @@ -2916,11 +4108,11 @@ nSql += nLine; } if( nSql && line_contains_semicolon(&zSql[nSqlPrior], nSql-nSqlPrior) && sqlite3_complete(zSql) ){ p->cnt = 0; - open_db(p); + open_db(p, 0); BEGIN_TIMER; rc = shell_exec(p->db, zSql, shell_callback, p, &zErrMsg); END_TIMER; if( rc || zErrMsg ){ char zPrefix[100]; @@ -2938,17 +4130,23 @@ fprintf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db)); } errCnt++; } nSql = 0; + if( p->outCount ){ + output_reset(p); + p->outCount = 0; + } }else if( nSql && _all_whitespace(zSql) ){ + if( p->echoOn ) printf("%s\n", zSql); nSql = 0; } } if( nSql ){ if( !_all_whitespace(zSql) ){ fprintf(stderr, "Error: incomplete SQL: %s\n", zSql); + errCnt++; } free(zSql); } free(zLine); return errCnt>0; @@ -2960,11 +4158,12 @@ */ static char *find_home_dir(void){ static char *home_dir = NULL; if( home_dir ) return home_dir; -#if !defined(_WIN32) && !defined(WIN32) && !defined(_WIN32_WCE) && !defined(__RTP__) && !defined(_WRS_KERNEL) +#if !defined(_WIN32) && !defined(WIN32) && !defined(_WIN32_WCE) \ + && !defined(__RTP__) && !defined(_WRS_KERNEL) { struct passwd *pwent; uid_t uid = getuid(); if( (pwent=getpwuid(uid)) != NULL) { home_dir = pwent->pw_dir; @@ -3021,27 +4220,25 @@ ** Read input from the file given by sqliterc_override. Or if that ** parameter is NULL, take input from ~/.sqliterc ** ** Returns the number of errors. */ -static int process_sqliterc( - struct callback_data *p, /* Configuration data */ +static void process_sqliterc( + ShellState *p, /* Configuration data */ const char *sqliterc_override /* Name of config file. NULL to use default */ ){ char *home_dir = NULL; const char *sqliterc = sqliterc_override; char *zBuf = 0; FILE *in = NULL; - int rc = 0; if (sqliterc == NULL) { home_dir = find_home_dir(); if( home_dir==0 ){ -#if !defined(__RTP__) && !defined(_WRS_KERNEL) - fprintf(stderr,"%s: Error: cannot locate your home directory\n", Argv0); -#endif - return 1; + fprintf(stderr, "-- warning: cannot find home directory;" + " cannot read ~/.sqliterc\n"); + return; } sqlite3_initialize(); zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir); sqliterc = zBuf; } @@ -3048,21 +4245,21 @@ in = fopen(sqliterc,"rb"); if( in ){ if( stdin_is_interactive ){ fprintf(stderr,"-- Loading resources from %s\n",sqliterc); } - rc = process_input(p,in); + process_input(p,in); fclose(in); } sqlite3_free(zBuf); - return rc; } /* ** Show available command line options */ static const char zOptions[] = + " -ascii set output mode to 'ascii'\n" " -bail stop after hitting an error\n" " -batch force batch I/O\n" " -column set output mode to 'column'\n" " -cmd COMMAND run \"COMMAND\" before reading stdin\n" " -csv set output mode to 'csv'\n" @@ -3075,16 +4272,20 @@ " -help show this message\n" " -html set output mode to HTML\n" " -interactive force interactive I/O\n" " -line set output mode to 'line'\n" " -list set output mode to 'list'\n" + " -lookaside SIZE N use N entries of SZ bytes for lookaside memory\n" " -mmap N default mmap size set to N\n" #ifdef SQLITE_ENABLE_MULTIPLEX " -multiplex enable the multiplexor VFS\n" #endif + " -newline SEP set output row separator. Default: '\\n'\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" - " -separator SEP set output field separator. Default: '|'\n" + " -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n" + " -scratch SIZE N use N slots of SZ bytes each for scratch memory\n" + " -separator SEP set output column separator. Default: '|'\n" " -stats print memory stats before each finalize\n" " -version show SQLite version\n" " -vfs NAME use NAME as the default VFS\n" #ifdef SQLITE_ENABLE_VFSTRACE " -vfstrace enable tracing of all VFS calls\n" @@ -3104,21 +4305,43 @@ } /* ** Initialize the state information in data */ -static void main_init(struct callback_data *data) { +static void main_init(ShellState *data) { memset(data, 0, sizeof(*data)); data->mode = MODE_List; - memcpy(data->separator,"|", 2); + memcpy(data->colSeparator,SEP_Column, 2); + memcpy(data->rowSeparator,SEP_Row, 2); data->showHeader = 0; + data->shellFlgs = SHFLG_Lookaside; sqlite3_config(SQLITE_CONFIG_URI, 1); sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data); + sqlite3_config(SQLITE_CONFIG_MULTITHREAD); sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> "); sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> "); - sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); +} + +/* +** Output text to the console in a font that attracts extra attention. +*/ +#ifdef _WIN32 +static void printBold(const char *zText){ + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO defaultScreenInfo; + GetConsoleScreenBufferInfo(out, &defaultScreenInfo); + SetConsoleTextAttribute(out, + FOREGROUND_RED|FOREGROUND_INTENSITY + ); + printf("%s", zText); + SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes); +} +#else +static void printBold(const char *zText){ + printf("\033[1m%s\033[0m", zText); } +#endif /* ** Get the argument to an --option. Throw an error and die if no argument ** is available. */ @@ -3129,23 +4352,30 @@ exit(1); } return argv[i]; } -int main(int argc, char **argv){ +int SQLITE_CDECL main(int argc, char **argv){ char *zErrMsg = 0; - struct callback_data data; + ShellState data; const char *zInitFile = 0; - char *zFirstCmd = 0; int i; int rc = 0; + int warnInmemoryDb = 0; + int readStdin = 1; + int nCmd = 0; + char **azCmd = 0; +#if USE_SYSTEM_SQLITE+0!=1 if( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)!=0 ){ fprintf(stderr, "SQLite header and source version mismatch\n%s\n%s\n", sqlite3_sourceid(), SQLITE_SOURCE_ID); exit(1); } +#endif + setBinaryMode(stdin); + setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ Argv0 = argv[0]; main_init(&data); stdin_is_interactive = isatty(0); /* Make sure we have a valid signal handler early, before anything @@ -3152,10 +4382,22 @@ ** else is done. */ #ifdef SIGINT signal(SIGINT, interrupt_handler); #endif + +#ifdef SQLITE_SHELL_DBNAME_PROC + { + /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name + ** of a C-function that will provide the name of the database file. Use + ** this compile-time option to embed this shell program in larger + ** applications. */ + extern void SQLITE_SHELL_DBNAME_PROC(const char**); + SQLITE_SHELL_DBNAME_PROC(&data.zDbFilename); + warnInmemoryDb = 0; + } +#endif /* Do an initial pass through the command-line argument to locate ** the name of the database file, the name of the initialization file, ** the size of the alternative malloc heap, ** and the first command to execute. @@ -3164,23 +4406,27 @@ char *z; z = argv[i]; if( z[0]!='-' ){ if( data.zDbFilename==0 ){ data.zDbFilename = z; - continue; - } - if( zFirstCmd==0 ){ - zFirstCmd = z; - continue; - } - fprintf(stderr,"%s: Error: too many options: \"%s\"\n", Argv0, argv[i]); - fprintf(stderr,"Use -help for a list of options.\n"); - return 1; + }else{ + /* Excesss arguments are interpreted as SQL (or dot-commands) and + ** mean that nothing is read from stdin */ + readStdin = 0; + nCmd++; + azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd); + if( azCmd==0 ){ + fprintf(stderr, "out of memory\n"); + exit(1); + } + azCmd[nCmd-1] = z; + } } if( z[1]=='-' ) z++; if( strcmp(z,"-separator")==0 || strcmp(z,"-nullvalue")==0 + || strcmp(z,"-newline")==0 || strcmp(z,"-cmd")==0 ){ (void)cmdline_option_value(argc, argv, ++i); }else if( strcmp(z,"-init")==0 ){ zInitFile = cmdline_option_value(argc, argv, ++i); @@ -3198,10 +4444,37 @@ zSize = cmdline_option_value(argc, argv, ++i); szHeap = integerValue(zSize); if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000; sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); #endif + }else if( strcmp(z,"-scratch")==0 ){ + int n, sz; + sz = (int)integerValue(cmdline_option_value(argc,argv,++i)); + if( sz>400000 ) sz = 400000; + if( sz<2500 ) sz = 2500; + n = (int)integerValue(cmdline_option_value(argc,argv,++i)); + if( n>10 ) n = 10; + if( n<1 ) n = 1; + sqlite3_config(SQLITE_CONFIG_SCRATCH, malloc(n*sz+1), sz, n); + data.shellFlgs |= SHFLG_Scratch; + }else if( strcmp(z,"-pagecache")==0 ){ + int n, sz; + sz = (int)integerValue(cmdline_option_value(argc,argv,++i)); + if( sz>70000 ) sz = 70000; + if( sz<800 ) sz = 800; + n = (int)integerValue(cmdline_option_value(argc,argv,++i)); + if( n<10 ) n = 10; + sqlite3_config(SQLITE_CONFIG_PAGECACHE, malloc(n*sz+1), sz, n); + data.shellFlgs |= SHFLG_Pagecache; + }else if( strcmp(z,"-lookaside")==0 ){ + int n, sz; + sz = (int)integerValue(cmdline_option_value(argc,argv,++i)); + if( sz<0 ) sz = 0; + n = (int)integerValue(cmdline_option_value(argc,argv,++i)); + if( n<0 ) n = 0; + sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n); + if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside; #ifdef SQLITE_ENABLE_VFSTRACE }else if( strcmp(z,"-vfstrace")==0 ){ extern int vfstrace_register( const char *zTraceName, const char *zOldVfsName, @@ -3230,40 +4503,32 @@ } } if( data.zDbFilename==0 ){ #ifndef SQLITE_OMIT_MEMORYDB data.zDbFilename = ":memory:"; + warnInmemoryDb = argc==1; #else fprintf(stderr,"%s: Error: no database filename specified\n", Argv0); return 1; #endif - /***** Begin Fossil Patch *****/ - { - extern void fossil_open(const char **); - fossil_open(&data.zDbFilename); - } - /***** End Fossil Patch *****/ } data.out = stdout; /* Go ahead and open the database file if it already exists. If the ** file does not exist, delay opening it. This prevents empty database ** files from being created if a user mistypes the database name argument ** to the sqlite command-line tool. */ if( access(data.zDbFilename, 0)==0 ){ - open_db(&data); + open_db(&data, 0); } /* Process the initialization file if there is one. If no -init option ** is given on the command line, look for a file named ~/.sqliterc and ** try to process it. */ - rc = process_sqliterc(&data,zInitFile); - if( rc>0 ){ - return rc; - } + process_sqliterc(&data,zInitFile); /* Make a second pass through the command-line argument and set ** options. This second pass is delayed until after the initialization ** file is processed so that the command-line arguments will override ** settings in the initialization file. @@ -3282,25 +4547,38 @@ data.mode = MODE_Line; }else if( strcmp(z,"-column")==0 ){ data.mode = MODE_Column; }else if( strcmp(z,"-csv")==0 ){ data.mode = MODE_Csv; - memcpy(data.separator,",",2); + memcpy(data.colSeparator,",",2); + }else if( strcmp(z,"-ascii")==0 ){ + data.mode = MODE_Ascii; + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, + SEP_Unit); + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, + SEP_Record); }else if( strcmp(z,"-separator")==0 ){ - sqlite3_snprintf(sizeof(data.separator), data.separator, + sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, + "%s",cmdline_option_value(argc,argv,++i)); + }else if( strcmp(z,"-newline")==0 ){ + sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, "%s",cmdline_option_value(argc,argv,++i)); }else if( strcmp(z,"-nullvalue")==0 ){ - sqlite3_snprintf(sizeof(data.nullvalue), data.nullvalue, + sqlite3_snprintf(sizeof(data.nullValue), data.nullValue, "%s",cmdline_option_value(argc,argv,++i)); }else if( strcmp(z,"-header")==0 ){ data.showHeader = 1; }else if( strcmp(z,"-noheader")==0 ){ data.showHeader = 0; }else if( strcmp(z,"-echo")==0 ){ data.echoOn = 1; + }else if( strcmp(z,"-eqp")==0 ){ + data.autoEQP = 1; }else if( strcmp(z,"-stats")==0 ){ data.statsOn = 1; + }else if( strcmp(z,"-scanstats")==0 ){ + data.scanstatsOn = 1; }else if( strcmp(z,"-bail")==0 ){ bail_on_error = 1; }else if( strcmp(z,"-version")==0 ){ printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid()); return 0; @@ -3308,10 +4586,16 @@ stdin_is_interactive = 1; }else if( strcmp(z,"-batch")==0 ){ stdin_is_interactive = 0; }else if( strcmp(z,"-heap")==0 ){ i++; + }else if( strcmp(z,"-scratch")==0 ){ + i+=2; + }else if( strcmp(z,"-pagecache")==0 ){ + i+=2; + }else if( strcmp(z,"-lookaside")==0 ){ + i+=2; }else if( strcmp(z,"-mmap")==0 ){ i++; }else if( strcmp(z,"-vfs")==0 ){ i++; #ifdef SQLITE_ENABLE_VFSTRACE @@ -3323,17 +4607,21 @@ i++; #endif }else if( strcmp(z,"-help")==0 ){ usage(1); }else if( strcmp(z,"-cmd")==0 ){ + /* Run commands that follow -cmd first and separately from commands + ** that simply appear on the command-line. This seems goofy. It would + ** be better if all commands ran in the order that they appear. But + ** we retain the goofy behavior for historical compatibility. */ if( i==argc-1 ) break; z = cmdline_option_value(argc,argv,++i); if( z[0]=='.' ){ rc = do_meta_command(z, &data); if( rc && bail_on_error ) return rc==2 ? 0 : rc; }else{ - open_db(&data); + open_db(&data, 0); rc = shell_exec(data.db, z, shell_callback, &data, &zErrMsg); if( zErrMsg!=0 ){ fprintf(stderr,"Error: %s\n", zErrMsg); if( bail_on_error ) return rc!=0 ? rc : 1; }else if( rc!=0 ){ @@ -3346,54 +4634,62 @@ fprintf(stderr,"Use -help for a list of options.\n"); return 1; } } - if( zFirstCmd ){ - /* Run just the command that follows the database name - */ - if( zFirstCmd[0]=='.' ){ - rc = do_meta_command(zFirstCmd, &data); - if( rc==2 ) rc = 0; - }else{ - open_db(&data); - rc = shell_exec(data.db, zFirstCmd, shell_callback, &data, &zErrMsg); - if( zErrMsg!=0 ){ - fprintf(stderr,"Error: %s\n", zErrMsg); - return rc!=0 ? rc : 1; - }else if( rc!=0 ){ - fprintf(stderr,"Error: unable to process SQL \"%s\"\n", zFirstCmd); - return rc; - } - } + if( !readStdin ){ + /* Run all arguments that do not begin with '-' as if they were separate + ** command-line inputs, except for the argToSkip argument which contains + ** the database filename. + */ + for(i=0; iError: Bad artifact IDs.

            + fossil_free(zCanonical); + zCanonical = 0; + break; + }else{ + canonical16(p, UUID_SIZE); + p += UUID_SIZE+1; + } + } + zUuid = zCanonical; } style_header("Shunned Artifacts"); if( zUuid && P("sub") ){ + const char *p = zUuid; + int allExist = 1; login_verify_csrf_secret(); - db_multi_exec("DELETE FROM shun WHERE uuid='%s'", zUuid); - if( db_exists("SELECT 1 FROM blob WHERE uuid='%s'", zUuid) ){ - @

            Artifact - @ %s(zUuid) is no - @ longer being shunned.

            + while( *p ){ + db_multi_exec("DELETE FROM shun WHERE uuid=%Q", p); + if( !db_exists("SELECT 1 FROM blob WHERE uuid=%Q", p) ){ + allExist = 0; + } + admin_log("Unshunned %Q", p); + p += UUID_SIZE+1; + } + if( allExist ){ + @

            Artifact(s)
            + for( p = zUuid ; *p ; p += UUID_SIZE+1 ){ + @ %s(p)
            + } + @ are no longer being shunned.

            }else{ - @

            Artifact %s(zUuid) will no longer - @ be shunned. But it does not exist in the repository. It - @ may be necessary to rebuild the repository using the + @

            Artifact(s)
            + for( p = zUuid ; *p ; p += UUID_SIZE+1 ){ + @ %s(p)
            + } + @ will no longer be shunned. But they may not exist in the repository. + @ It may be necessary to rebuild the repository using the @ fossil rebuild command-line before the artifact content @ can pulled in from other repositories.

            } } if( zUuid && P("add") ){ + const char *p = zUuid; int rid, tagid; login_verify_csrf_secret(); - db_multi_exec( - "INSERT OR IGNORE INTO shun(uuid,mtime)" - " VALUES('%s', now())", zUuid); - @

            Artifact - @ %s(zUuid) has been - @ shunned. It will no longer be pushed. - @ It will be removed from the repository the next time the repository - @ is rebuilt using the fossil rebuild command-line

            - db_multi_exec("DELETE FROM attachment WHERE src=%Q", zUuid); - rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zUuid); - if( rid ){ - db_multi_exec("DELETE FROM event WHERE objid=%d", rid); - } - tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='tkt-%q'", zUuid); - if( tagid ){ - db_multi_exec("DELETE FROM ticket WHERE tkt_uuid=%Q", zUuid); - db_multi_exec("DELETE FROM tag WHERE tagid=%d", tagid); - db_multi_exec("DELETE FROM tagxref WHERE tagid=%d", tagid); - } + while( *p ){ + db_multi_exec( + "INSERT OR IGNORE INTO shun(uuid,mtime)" + " VALUES(%Q, now())", p); + db_multi_exec("DELETE FROM attachment WHERE src=%Q", p); + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", p); + if( rid ){ + db_multi_exec("DELETE FROM event WHERE objid=%d", rid); + } + tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='tkt-%q'", p); + if( tagid ){ + db_multi_exec("DELETE FROM ticket WHERE tkt_uuid=%Q", p); + db_multi_exec("DELETE FROM tag WHERE tagid=%d", tagid); + db_multi_exec("DELETE FROM tagxref WHERE tagid=%d", tagid); + } + admin_log("Shunned %Q", p); + p += UUID_SIZE+1; + } + @

            Artifact(s)
            + for( p = zUuid ; *p ; p += UUID_SIZE+1 ){ + @ %s(p)
            + } + @ have been shunned. They will no longer be pushed. + @ They will be removed from the repository the next time the repository + @ is rebuilt using the fossil rebuild command-line

            + } + if( zRcvid ){ + nRcvid = atoi(zRcvid); + numRows = db_int(0, "SELECT min(count(), 10) FROM blob WHERE rcvid=%d", + nRcvid); } @

            A shunned artifact will not be pushed nor accepted in a pull and the @ artifact content will be purged from the repository the next time the @ repository is rebuilt. A list of shunned artifacts can be seen at the @ bottom of this page.

            - @ + @ @ - @

            To shun an artifact, enter its artifact ID (the 40-character SHA1 - @ hash of the artifact) in the - @ following box and press the "Shun" button. This will cause the artifact - @ to be removed from the repository and will prevent the artifact from being + @

            To shun artifacts, enter their artifact IDs (the 40-character SHA1 + @ hash of the artifacts) in the + @ following box and press the "Shun" button. This will cause the artifacts + @ to be removed from the repository and will prevent the artifacts from being @ readded to the repository by subsequent sync operation.

            @ - @

            Note that you must enter the full 40-character artifact ID, not + @

            Note that you must enter the full 40-character artifact IDs, not @ an abbreviation or a symbolic tag.

            @ @

            Warning: Shunning should only be used to remove inappropriate content @ from the repository. Inappropriate content includes such things as @ spam added to Wiki, files that violate copyright or patent agreements, @ or artifacts that by design or accident interfere with the processing @ of the repository. Do not shun artifacts merely to remove them from @ sight - set the "hidden" tag on such artifacts instead.

            - @ + @ @
            @
            login_insert_csrf_secret(); - @ + @ @ @
            @
            @ @ - @

            Enter the UUID of a previous shunned artifact to cause it to be - @ accepted again in the repository. The artifact content is not + @

            Enter the UUIDs of previously shunned artifacts to cause them to be + @ accepted again in the repository. The artifacts content is not @ restored because the content is unknown. The only change is that - @ the formerly shunned artifact will be accepted on subsequent sync + @ the formerly shunned artifacts will be accepted on subsequent sync @ operations.

            @ @
            @
            login_insert_csrf_secret(); - @ + @ @ @
            @
            @ @

            Press the Rebuild button below to rebuild the repository. The @@ -159,22 +238,22 @@ @

            login_insert_csrf_secret(); @ @
            @ - @ + @ @

            Shunned Artifacts:

            @

            - db_prepare(&q, + db_prepare(&q, "SELECT uuid, EXISTS(SELECT 1 FROM blob WHERE blob.uuid=shun.uuid)" " FROM shun ORDER BY uuid"); while( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); int stillExists = db_column_int(&q, 1); cnt++; if( stillExists ){ - @ %s(zUuid)
            + @ %s(zUuid)
            }else{ @ %s(zUuid)
            } } if( cnt==0 ){ @@ -181,10 +260,11 @@ @ no artifacts are shunned on this server } db_finalize(&q); @

            style_footer(); + fossil_free(zCanonical); } /* ** Remove from the BLOB table all artifacts that are in the SHUN table. */ @@ -216,37 +296,51 @@ ** ** Show a listing of RCVFROM table entries. */ void rcvfromlist_page(void){ int ofst = atoi(PD("ofst","0")); + int showAll = P("all")!=0; int cnt; Stmt q; login_check_credentials(); if( !g.perm.Admin ){ - login_needed(); + login_needed(0); + return; } - style_header("Content Sources"); + style_header("Artifact Receipts"); + if( showAll ){ + ofst = 0; + }else{ + style_submenu_element("All", "All", "rcvfromlist?all=1"); + } if( ofst>0 ){ style_submenu_element("Newer", "Newer", "rcvfromlist?ofst=%d", ofst>30 ? ofst-30 : 0); } - db_prepare(&q, - "SELECT rcvid, login, datetime(rcvfrom.mtime), rcvfrom.ipaddr" + db_multi_exec( + "CREATE TEMP TABLE rcvidUsed(x INTEGER PRIMARY KEY);" + "INSERT OR IGNORE INTO rcvidUsed(x) SELECT rcvid FROM blob;" + ); + db_prepare(&q, + "SELECT rcvid, login, datetime(rcvfrom.mtime), rcvfrom.ipaddr," + " EXISTS(SELECT 1 FROM rcvidUsed WHERE x=rcvfrom.rcvid)" " FROM rcvfrom LEFT JOIN user USING(uid)" - " ORDER BY rcvid DESC LIMIT 31 OFFSET %d", - ofst + " ORDER BY rcvid DESC LIMIT %d OFFSET %d", + showAll ? -1 : 31, ofst ); @

            Whenever new artifacts are added to the repository, either by @ push or using the web interface, an entry is made in the RCVFROM table @ to record the source of that artifact. This log facilitates @ finding and fixing attempts to inject illicit content into the @ repository.

            @ @

            Click on the "rcvid" to show a list of specific artifacts received @ by a transaction. After identifying illicit artifacts, remove them - @ using the "Shun" feature.

            + @ using the "Shun" button. If an "rcvid" is not hyperlinked, that means + @ all artifacts associated with that rcvid have already been shunned + @ or purged.

            @ @
            @ @ @ @@ -255,17 +349,22 @@ while( db_step(&q)==SQLITE_ROW ){ int rcvid = db_column_int(&q, 0); const char *zUser = db_column_text(&q, 1); const char *zDate = db_column_text(&q, 2); const char *zIpAddr = db_column_text(&q, 3); - if( cnt==30 ){ + if( cnt==30 && !showAll ){ style_submenu_element("Older", "Older", "rcvfromlist?ofst=%d", ofst+30); }else{ cnt++; @ - @ + if( db_column_int(&q,4) ){ + @ + }else{ + @ + } @ @ @ @ } @@ -284,14 +383,29 @@ int rcvid = atoi(PD("rcvid","0")); Stmt q; login_check_credentials(); if( !g.perm.Admin ){ - login_needed(); + login_needed(0); + return; + } + style_header("Artifact Receipt %d", rcvid); + if( db_exists( + "SELECT 1 FROM blob WHERE rcvid=%d AND" + " NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) + ){ + style_submenu_element("Shun All", "Shun All", + "shun?shun&rcvid=%d#addshun", rcvid); + } + if( db_exists( + "SELECT 1 FROM blob WHERE rcvid=%d AND" + " EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) + ){ + style_submenu_element("Unshun All", "Unshun All", + "shun?accept&rcvid=%d#delshun", rcvid); } - style_header("Content Source %d", rcvid); - db_prepare(&q, + db_prepare(&q, "SELECT login, datetime(rcvfrom.mtime), rcvfrom.ipaddr" " FROM rcvfrom LEFT JOIN user USING(uid)" " WHERE rcvid=%d", rcvid ); @@ -308,22 +422,30 @@ @ @ @ } db_finalize(&q); + db_multi_exec( + "CREATE TEMP TABLE toshow(rid INTEGER PRIMARY KEY);" + "INSERT INTO toshow SELECT rid FROM blob WHERE rcvid=%d", rcvid + ); + describe_artifacts("IN toshow"); db_prepare(&q, - "SELECT rid, uuid, size FROM blob WHERE rcvid=%d", rcvid + "SELECT blob.rid, blob.uuid, blob.size, description.summary\n" + " FROM blob LEFT JOIN description ON (blob.rid=description.rid)" + " WHERE blob.rcvid=%d", rcvid ); @ @ @
            rcvidDateUser
            %d(rcvid) + @ %d(rcvid)%d(rcvid)%s(zDate)%h(zUser)%s(zIpAddr)
            %s(zDate)
            IP Address:%s(zIpAddr)
            Artifacts: while( db_step(&q)==SQLITE_ROW ){ - int rid = db_column_int(&q, 0); const char *zUuid = db_column_text(&q, 1); int size = db_column_int(&q, 2); - @ %s(zUuid) - @ (rid: %d(rid), size: %d(size))
            + const char *zDesc = db_column_text(&q, 3); + if( zDesc==0 ) zDesc = ""; + @ %s(zUuid) + @ %h(zDesc) (size: %d(size))
            } @
            db_finalize(&q); style_footer(); } ADDED src/sitemap.c Index: src/sitemap.c ================================================================== --- /dev/null +++ src/sitemap.c @@ -0,0 +1,114 @@ +/* +** Copyright (c) 2014 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code to implement the sitemap webpage. +*/ +#include "config.h" +#include "sitemap.h" +#include + +/* +** WEBPAGE: sitemap +** +** Show an incomplete list of web pages offered by the Fossil web engine. +*/ +void sitemap_page(void){ + login_check_credentials(); + style_header("Site Map"); + style_adunit_config(ADUNIT_RIGHT_OK); + @

            + @ The following links are just a few of the many web-pages available for + @ this Fossil repository: + @

            + @ + @
              + @
            • %z(href("%R/home"))Home Page + @
                + @
              • %z(href("%R/docsrc"))Search Project Documentation
              • + @
            • + @
            • %z(href("%R/tree"))File Browser
            • + @
                + @
              • %z(href("%R/tree?type=tree&ci=trunk"))Tree-view, + @ Trunk Check-in
              • + @
              • %z(href("%R/tree?type=flat"))Flat-view
              • + @
              • %z(href("%R/fileage?name=trunk"))File ages for Trunk
              • + @
              + @
            • %z(href("%R/timeline?n=200"))Project Timeline
            • + @
                + @
              • %z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10 + @ check-ins
              • + @
              • %z(href("%R/timeline?n=all&namechng"))All check-ins with file name + @ changes
              • + @
              • %z(href("%R/reports"))Activity Reports
              • + @
              + @
            • %z(href("%R/brlist"))Branches
            • + @
                + @
              • %z(href("%R/leaves"))Leaf Check-ins
              • + @
              • %z(href("%R/taglist"))List of Tags
              • + @
              + @ + @
            • %z(href("%R/wikihelp"))Wiki + @
                + @
              • %z(href("%R/wikisrch"))Wiki Search
              • + @
              • %z(href("%R/wcontent"))List of Wiki Pages
              • + @
              • %z(href("%R/timeline?y=w"))Recent activity
              • + @
              • %z(href("%R/wiki_rules"))Wiki Formatting Rules
              • + @
              • %z(href("%R/md_rules"))Markdown Formatting Rules
              • + @
              • %z(href("%R/wiki?name=Sandbox"))Sandbox
              • + @
              • %z(href("%R/attachlist"))List of Attachments
              • + @
              + @
            • + @
            • %z(href("%R/reportlist"))Tickets + @
                + @
              • %z(href("%R/tktsrch"))Ticket Search
              • + @
              • %z(href("%R/timeline?y=t"))Recent activity
              • + @
              • %z(href("%R/attachlist"))List of Attachments
              • + @
              + @
            • + @
            • %z(href("%R/search"))Full-Text Search
            • + @
            • %z(href("%R/login"))Login/Logout/Change Password
            • + @
            • %z(href("%R/stat"))Repository Status + @
                + @
              • %z(href("%R/hash-collisions"))Collisions on SHA1 hash + @ prefixes
              • + @
              • %z(href("%R/urllist"))List of URLs used to access + @ this repository
              • + @
              • %z(href("%R/bloblist"))List of Artifacts
              • + @
            • + @
            • On-line Documentation + @
                + @
              • %z(href("%R/help"))List of All Commands and Web Pages
              • + @
              • %z(href("%R/test-all-help"))All "help" text on a single page
              • + @
              • %z(href("%R/mimetype_list"))Filename suffix to mimetype map
              • + @
            • + @
            • %z(href("%R/setup"))Administration Pages + @
                + @
              • %z(href("%R/modreq"))Pending Moderation Requests
              • + @
              • %z(href("%R/admin_log"))Admin log
              • + @
              • %z(href("%R/cachestat"))Status of the web-page cache
              • + @
            • + @
            • Test Pages + @
                + @
              • %z(href("%R/test_env"))CGI Environment Test
              • + @
              • %z(href("%R/test_timewarps"))List of "Timewarp" Check-ins
              • + @
              • %z(href("%R/test-rename-list"))List of file renames
              • + @
              • %z(href("%R/hash-color-test"))Page to experiment with the automatic + @ colors assigned to branch names + @
            • + @
            + style_footer(); +} Index: src/skins.c ================================================================== --- src/skins.c +++ src/skins.c @@ -15,1249 +15,252 @@ ** ******************************************************************************* ** ** Implementation of the Setup page for "skins". */ -#include #include "config.h" +#include #include "skins.h" -/* @-comment: ## */ -/* -** A black-and-white theme with the project title in a bar across the top -** and no logo image. -*/ -static const char zBuiltinSkin1[] = -@ REPLACE INTO config(name,mtime,value) -@ VALUES('css',now(),'/* General settings for the entire page */ -@ body { -@ margin: 0ex 1ex; -@ padding: 0px; -@ background-color: white; -@ font-family: sans-serif; -@ } -@ -@ /* The project logo in the upper left-hand corner of each page */ -@ div.logo { -@ display: table-row; -@ text-align: center; -@ /* vertical-align: bottom;*/ -@ font-size: 2em; -@ font-weight: bold; -@ background-color: #707070; -@ color: #ffffff; -@ min-width: 200px; -@ white-space: nowrap; -@ } -@ -@ /* The page title centered at the top of each page */ -@ div.title { -@ display: table-cell; -@ font-size: 1.5em; -@ font-weight: bold; -@ text-align: center; -@ padding: 0 0 0 10px; -@ color: #404040; -@ vertical-align: bottom; -@ width: 100%; -@ } -@ -@ /* The login status message in the top right-hand corner */ -@ div.status { -@ display: table-cell; -@ text-align: right; -@ vertical-align: bottom; -@ color: #404040; -@ font-size: 0.8em; -@ font-weight: bold; -@ min-width: 200px; -@ white-space: nowrap; -@ } -@ -@ /* The header across the top of the page */ -@ div.header { -@ display: table; -@ width: 100%; -@ } -@ -@ /* The main menu bar that appears at the top of the page beneath -@ ** the header */ -@ div.mainmenu { -@ padding: 5px 10px 5px 10px; -@ font-size: 0.9em; -@ font-weight: bold; -@ text-align: center; -@ letter-spacing: 1px; -@ background-color: #404040; -@ color: white; -@ } -@ -@ /* The submenu bar that *sometimes* appears below the main menu */ -@ div.submenu, div.sectionmenu { -@ padding: 3px 10px 3px 0px; -@ font-size: 0.9em; -@ text-align: center; -@ background-color: #606060; -@ color: white; -@ } -@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, -@ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited { -@ padding: 3px 10px 3px 10px; -@ color: white; -@ text-decoration: none; -@ } -@ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover { -@ color: #404040; -@ background-color: white; -@ } -@ -@ /* All page content from the bottom of the menu or submenu down to -@ ** the footer */ -@ div.content { -@ padding: 0ex 0ex 0ex 0ex; -@ } -@ /* Hyperlink colors */ -@ div.content a { color: #604000; } -@ div.content a:link { color: #604000;} -@ div.content a:visited { color: #600000; } -@ -@ /* Some pages have section dividers */ -@ div.section { -@ margin-bottom: 0px; -@ margin-top: 1em; -@ padding: 1px 1px 1px 1px; -@ font-size: 1.2em; -@ font-weight: bold; -@ background-color: #404040; -@ color: white; -@ white-space: nowrap; -@ } -@ -@ /* The "Date" that occurs on the left hand side of timelines */ -@ div.divider { -@ background: #a0a0a0; -@ border: 2px #505050 solid; -@ font-size: 1em; font-weight: normal; -@ padding: .25em; -@ margin: .2em 0 .2em 0; -@ float: left; -@ clear: left; -@ white-space: nowrap; -@ } -@ -@ /* The footer at the very bottom of the page */ -@ div.footer { -@ font-size: 0.8em; -@ margin-top: 12px; -@ padding: 5px 10px 5px 10px; -@ text-align: right; -@ background-color: #404040; -@ color: white; -@ } -@ -@ /* The label/value pairs on (for example) the vinfo page */ -@ table.label-value th { -@ vertical-align: top; -@ text-align: right; -@ padding: 0.2ex 2ex; -@ }'); -@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),' -@ -@ -@ $<project_name>: $<title> -@ -@ -@ -@ -@
            -@
            $
            $</div> -@ <div class="status"><th1> -@ if {[info exists login]} { -@ puts "Logged in as $login" -@ } else { -@ puts "Not logged in" -@ } -@ </th1></div> -@ </div> -@ <div class="mainmenu"> -@ <th1> -@ html "<a href=''$home$index_page''>Home</a>\n" -@ if {[anycap jor]} { -@ html "<a href=''$home/timeline''>Timeline</a>\n" -@ } -@ if {[hascap oh]} { -@ html "<a href=''$home/dir?ci=tip''>Files</a>\n" -@ } -@ if {[hascap o]} { -@ html "<a href=''$home/brlist''>Branches</a>\n" -@ html "<a href=''$home/taglist''>Tags</a>\n" -@ } -@ if {[hascap r]} { -@ html "<a href=''$home/reportlist''>Tickets</a>\n" -@ } -@ if {[hascap j]} { -@ html "<a href=''$home/wiki''>Wiki</a>\n" -@ } -@ if {[hascap s]} { -@ html "<a href=''$home/setup''>Admin</a>\n" -@ } elseif {[hascap a]} { -@ html "<a href=''$home/setup_ulist''>Users</a>\n" -@ } -@ if {[info exists login]} { -@ html "<a href=''$home/login''>Logout</a>\n" -@ } else { -@ html "<a href=''$home/login''>Login</a>\n" -@ } -@ </th1></div> -@ '); -@ REPLACE INTO config(name,mtime,value) -@ VALUES('footer',now(),'<div class="footer"> -@ Fossil version $manifest_version $manifest_date -@ </div> -@ </body></html> -@ '); -; - -/* -** A tan theme with the project title above the user identification -** and no logo image. -*/ -static const char zBuiltinSkin2[] = -@ REPLACE INTO config(name,mtime,value) -@ VALUES('css',now(),'/* General settings for the entire page */ -@ body { -@ margin: 0ex 0ex; -@ padding: 0px; -@ background-color: #fef3bc; -@ font-family: sans-serif; -@ } -@ -@ /* The project logo in the upper left-hand corner of each page */ -@ div.logo { -@ display: inline; -@ text-align: center; -@ vertical-align: bottom; -@ font-weight: bold; -@ font-size: 2.5em; -@ color: #a09048; -@ white-space: nowrap; -@ } -@ -@ /* The page title centered at the top of each page */ -@ div.title { -@ display: table-cell; -@ font-size: 2em; -@ font-weight: bold; -@ text-align: left; -@ padding: 0 0 0 5px; -@ color: #a09048; -@ vertical-align: bottom; -@ width: 100%; -@ } -@ -@ /* The login status message in the top right-hand corner */ -@ div.status { -@ display: table-cell; -@ text-align: right; -@ vertical-align: bottom; -@ color: #a09048; -@ padding: 5px 5px 0 0; -@ font-size: 0.8em; -@ font-weight: bold; -@ white-space: nowrap; -@ } -@ -@ /* The header across the top of the page */ -@ div.header { -@ display: table; -@ width: 100%; -@ } -@ -@ /* The main menu bar that appears at the top of the page beneath -@ ** the header */ -@ div.mainmenu { -@ padding: 5px 10px 5px 10px; -@ font-size: 0.9em; -@ font-weight: bold; -@ text-align: center; -@ letter-spacing: 1px; -@ background-color: #a09048; -@ color: black; -@ } -@ -@ /* The submenu bar that *sometimes* appears below the main menu */ -@ div.submenu, div.sectionmenu { -@ padding: 3px 10px 3px 0px; -@ font-size: 0.9em; -@ text-align: center; -@ background-color: #c0af58; -@ color: white; -@ } -@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, -@ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited { -@ padding: 3px 10px 3px 10px; -@ color: white; -@ text-decoration: none; -@ } -@ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover { -@ color: #a09048; -@ background-color: white; -@ } -@ -@ /* All page content from the bottom of the menu or submenu down to -@ ** the footer */ -@ div.content { -@ padding: 1ex 5px; -@ } -@ div.content a { color: #706532; } -@ div.content a:link { color: #706532; } -@ div.content a:visited { color: #704032; } -@ div.content a:hover { background-color: white; color: #706532; } -@ -@ /* Some pages have section dividers */ -@ div.section { -@ margin-bottom: 0px; -@ margin-top: 1em; -@ padding: 3px 3px 0 3px; -@ font-size: 1.2em; -@ font-weight: bold; -@ background-color: #a09048; -@ color: white; -@ white-space: nowrap; -@ } -@ -@ /* The "Date" that occurs on the left hand side of timelines */ -@ div.divider { -@ background: #e1d498; -@ border: 2px #a09048 solid; -@ font-size: 1em; font-weight: normal; -@ padding: .25em; -@ margin: .2em 0 .2em 0; -@ float: left; -@ clear: left; -@ white-space: nowrap; -@ } -@ -@ /* The footer at the very bottom of the page */ -@ div.footer { -@ font-size: 0.8em; -@ margin-top: 12px; -@ padding: 5px 10px 5px 10px; -@ text-align: right; -@ background-color: #a09048; -@ color: white; -@ } -@ -@ /* Hyperlink colors */ -@ div.footer a { color: white; } -@ div.footer a:link { color: white; } -@ div.footer a:visited { color: white; } -@ div.footer a:hover { background-color: white; color: #558195; } -@ -@ /* <verbatim> blocks */ -@ pre.verbatim { -@ background-color: #f5f5f5; -@ padding: 0.5em; -@ white-space: pre-wrap; -@ } -@ -@ /* The label/value pairs on (for example) the ci page */ -@ table.label-value th { -@ vertical-align: top; -@ text-align: right; -@ padding: 0.2ex 2ex; -@ }'); -@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> -@ <head> -@ <base href="$baseurl/$current_page" /> -@ <title>$<project_name>: $<title> -@ -@ -@ -@ -@
            -@
            $</div> -@ <div class="status"> -@ <div class="logo">$<project_name></div><br/> -@ <th1> -@ if {[info exists login]} { -@ puts "Logged in as $login" -@ } else { -@ puts "Not logged in" -@ } -@ </th1></div> -@ </div> -@ <div class="mainmenu"> -@ <th1> -@ html "<a href=''$home$index_page''>Home</a>\n" -@ if {[anycap jor]} { -@ html "<a href=''$home/timeline''>Timeline</a>\n" -@ } -@ if {[hascap oh]} { -@ html "<a href=''$home/dir?ci=tip''>Files</a>\n" -@ } -@ if {[hascap o]} { -@ html "<a href=''$home/brlist''>Branches</a>\n" -@ html "<a href=''$home/taglist''>Tags</a>\n" -@ } -@ if {[hascap r]} { -@ html "<a href=''$home/reportlist''>Tickets</a>\n" -@ } -@ if {[hascap j]} { -@ html "<a href=''$home/wiki''>Wiki</a>\n" -@ } -@ if {[hascap s]} { -@ html "<a href=''$home/setup''>Admin</a>\n" -@ } elseif {[hascap a]} { -@ html "<a href=''$home/setup_ulist''>Users</a>\n" -@ } -@ if {[info exists login]} { -@ html "<a href=''$home/login''>Logout</a>\n" -@ } else { -@ html "<a href=''$home/login''>Login</a>\n" -@ } -@ </th1></div> -@ '); -@ REPLACE INTO config(name,mtime,value) -@ VALUES('footer',now(),'<div class="footer"> -@ Fossil version $manifest_version $manifest_date -@ </div> -@ </body></html> -@ '); -; - -/* -** Black letters on a white or cream background with the main menu -** stuck on the left-hand side. -*/ -static const char zBuiltinSkin3[] = -@ REPLACE INTO config(name,mtime,value) -@ VALUES('css',now(),'/* General settings for the entire page */ -@ body { -@ margin:0px 0px 0px 0px; -@ padding:0px; -@ font-family:verdana, arial, helvetica, "sans serif"; -@ color:#333; -@ background-color:white; -@ } -@ -@ /* consistent colours */ -@ h2 { -@ color: #333; -@ } -@ h3 { -@ color: #333; -@ } -@ -@ /* The project logo in the upper left-hand corner of each page */ -@ div.logo { -@ display: table-cell; -@ text-align: left; -@ vertical-align: bottom; -@ font-weight: bold; -@ color: #333; -@ white-space: nowrap; -@ } -@ -@ /* The page title centered at the top of each page */ -@ div.title { -@ display: table-cell; -@ font-size: 2em; -@ font-weight: bold; -@ text-align: center; -@ color: #333; -@ vertical-align: bottom; -@ width: 100%; -@ } -@ -@ /* The login status message in the top right-hand corner */ -@ div.status { -@ display: table-cell; -@ padding-right: 10px; -@ text-align: right; -@ vertical-align: bottom; -@ padding-bottom: 5px; -@ color: #333; -@ font-size: 0.8em; -@ font-weight: bold; -@ white-space: nowrap; -@ } -@ -@ /* The header across the top of the page */ -@ div.header { -@ margin:10px 0px 10px 0px; -@ padding:1px 0px 0px 20px; -@ border-style:solid; -@ border-color:black; -@ border-width:1px 0px; -@ background-color:#eee; -@ } -@ -@ /* The main menu bar that appears at the top left of the page beneath -@ ** the header. Width must be co-ordinated with the container below */ -@ div.mainmenu { -@ float: left; -@ margin-left: 10px; -@ margin-right: 10px; -@ font-size: 0.9em; -@ font-weight: bold; -@ padding:5px; -@ background-color:#eee; -@ border:1px solid #999; -@ width:8em; -@ } -@ -@ /* Main menu is now a list */ -@ div.mainmenu ul { -@ padding: 0; -@ list-style:none; -@ } -@ div.mainmenu a, div.mainmenu a:visited{ -@ padding: 1px 10px 1px 10px; -@ color: #333; -@ text-decoration: none; -@ } -@ div.mainmenu a:hover { -@ color: #eee; -@ background-color: #333; -@ } -@ -@ /* Container for the sub-menu and content so they don''t spread -@ ** out underneath the main menu */ -@ #container { -@ padding-left: 9em; -@ } -@ -@ /* The submenu bar that *sometimes* appears below the main menu */ -@ div.submenu, div.sectionmenu { -@ padding: 3px 10px 3px 10px; -@ font-size: 0.9em; -@ text-align: center; -@ border:1px solid #999; -@ border-width:1px 0px; -@ background-color: #eee; -@ color: #333; -@ } -@ div.submenu a, div.submenu a:visited, div.sectionmenu>a.button:link, -@ div.sectionmenu>a.button:visited { -@ padding: 3px 10px 3px 10px; -@ color: #333; -@ text-decoration: none; -@ } -@ div.submenu a:hover, div.sectionmenu>a.button:hover { -@ color: #eee; -@ background-color: #333; -@ } -@ -@ /* All page content from the bottom of the menu or submenu down to -@ ** the footer */ -@ div.content { -@ padding: 2ex 1ex 0ex 2ex; -@ } -@ -@ /* Some pages have section dividers */ -@ div.section { -@ margin-bottom: 0px; -@ margin-top: 1em; -@ padding: 1px 1px 1px 1px; -@ font-size: 1.2em; -@ font-weight: bold; -@ border-style:solid; -@ border-color:#999; -@ border-width:1px 0px; -@ background-color: #eee; -@ color: #333; -@ white-space: nowrap; -@ } -@ -@ /* The "Date" that occurs on the left hand side of timelines */ -@ div.divider { -@ background: #eee; -@ border: 2px #999 solid; -@ font-size: 1em; font-weight: normal; -@ padding: .25em; -@ margin: .2em 0 .2em 0; -@ float: left; -@ clear: left; -@ color: #333; -@ white-space: nowrap; -@ } -@ -@ /* The footer at the very bottom of the page */ -@ div.footer { -@ font-size: 0.8em; -@ margin-top: 12px; -@ padding: 5px 10px 5px 10px; -@ text-align: right; -@ background-color: #eee; -@ color: #555; -@ } -@ -@ /* <verbatim> blocks */ -@ pre.verbatim { -@ background-color: #f5f5f5; -@ padding: 0.5em; -@ white-space: pre-wrap; -@ } -@ -@ /* The label/value pairs on (for example) the ci page */ -@ table.label-value th { -@ vertical-align: top; -@ text-align: right; -@ padding: 0.2ex 2ex; -@ }'); -@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> -@ <head> -@ <base href="$baseurl/$current_page" /> -@ <title>$<project_name>: $<title> -@ -@ -@ -@ -@
            -@ -@
            $</div> -@ <div class="status"><th1> -@ if {[info exists login]} { -@ puts "Logged in as $login" -@ } else { -@ puts "Not logged in" -@ } -@ </th1></div> -@ </div> -@ <div class="mainmenu"> -@ <th1> -@ html "<a href=''$home$index_page''>Home</a>\n" -@ if {[anycap jor]} { -@ html "<a href=''$home/timeline''>Timeline</a>\n" -@ } -@ if {[hascap oh]} { -@ html "<a href=''$home/dir?ci=tip''>Files</a>\n" -@ } -@ if {[hascap o]} { -@ html "<a href=''$home/brlist''>Branches</a>\n" -@ html "<a href=''$home/taglist''>Tags</a>\n" -@ } -@ if {[hascap r]} { -@ html "<a href=''$home/reportlist''>Tickets</a>\n" -@ } -@ if {[hascap j]} { -@ html "<a href=''$home/wiki''>Wiki</a>\n" -@ } -@ if {[hascap s]} { -@ html "<a href=''$home/setup''>Admin</a>\n" -@ } elseif {[hascap a]} { -@ html "<a href=''$home/setup_ulist''>Users</a>\n" -@ } -@ if {[info exists login]} { -@ html "<a href=''$home/login''>Logout</a>\n" -@ } else { -@ html "<a href=''$home/login''>Login</a>\n" -@ } -@ </th1></ul></div> -@ <div id="container"> -@ '); -@ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div> -@ <div class="footer"> -@ Fossil version $manifest_version $manifest_date -@ </div> -@ </body></html> -@ '); -; - - -/* -** Shadow boxes and rounded corners. -*/ -static const char zBuiltinSkin4[] = -@ REPLACE INTO config(name,mtime,value) -@ VALUES('css',now(),'/* General settings for the entire page */ -@ html { -@ min-height: 100%; -@ } -@ body { -@ margin: 0ex 1ex; -@ padding: 0px; -@ background-color: white; -@ color: #333; -@ font-family: Verdana, sans-serif; -@ font-size: 0.8em; -@ } -@ -@ /* The project logo in the upper left-hand corner of each page */ -@ div.logo { -@ display: table-cell; -@ text-align: right; -@ vertical-align: bottom; -@ font-weight: normal; -@ white-space: nowrap; -@ } -@ -@ /* Widths */ -@ div.header, div.mainmenu, div.submenu, div.content, div.footer { -@ max-width: 900px; -@ margin: auto; -@ padding: 3px 20px 3px 20px; -@ clear: both; -@ } -@ -@ /* The page title at the top of each page */ -@ div.title { -@ display: table-cell; -@ padding-left: 10px; -@ font-size: 2em; -@ margin: 10px 0 10px -20px; -@ vertical-align: bottom; -@ text-align: left; -@ width: 80%; -@ font-family: Verdana, sans-serif; -@ font-weight: bold; -@ color: #558195; -@ text-shadow: 0px 2px 2px #999999; -@ } -@ -@ /* The login status message in the top right-hand corner */ -@ div.status { -@ display: table-cell; -@ text-align: right; -@ vertical-align: bottom; -@ color: #333; -@ margin-right: -20px; -@ white-space: nowrap; -@ } -@ -@ /* The main menu bar that appears at the top of the page beneath -@ ** the header */ -@ div.mainmenu { -@ text-align: center; -@ color: white; -@ border-top-left-radius: 5px; -@ border-top-right-radius: 5px; -@ vertical-align: middle; -@ padding-top: 8px; -@ padding-bottom: 8px; -@ background-color: #446979; -@ box-shadow: 0px 3px 4px #333333; -@ } -@ -@ /* The submenu bar that *sometimes* appears below the main menu */ -@ div.submenu { -@ padding-top:10px; -@ padding-bottom:0; -@ text-align: right; -@ color: #000; -@ background-color: #fff; -@ height: 1.5em; -@ vertical-align:middle; -@ box-shadow: 0px 3px 4px #999; -@ } -@ div.mainmenu a, div.mainmenu a:visited { -@ padding: 3px 10px 3px 10px; -@ color: white; -@ text-decoration: none; -@ } -@ div.submenu a, div.submenu a:visited, a.button, -@ div.sectionmenu>a.button:link, div.sectinmenu>a.button:visited { -@ padding: 2px 8px; -@ color: #000; -@ font-family: Arial; -@ text-decoration: none; -@ margin:auto; -@ border-radius: 5px; -@ background-color: #e0e0e0; -@ text-shadow: 0px -1px 0px #eee; -@ border: 1px solid #000; -@ } -@ -@ div.mainmenu a:hover { -@ color: #000; -@ background-color: white; -@ } -@ -@ div.submenu a:hover, div.sectionmenu>a.button:hover { -@ background-color: #c0c0c0; -@ } -@ -@ /* All page content from the bottom of the menu or submenu down to -@ ** the footer */ -@ div.content { -@ background-color: #fff; -@ box-shadow: 0px 3px 4px #999; -@ border-bottom-right-radius: 5px; -@ border-bottom-left-radius: 5px; -@ padding-bottom: 1em; -@ min-height:40%; -@ } -@ -@ -@ /* Some pages have section dividers */ -@ div.section { -@ margin-bottom: 0.5em; -@ margin-top: 1em; -@ margin-right: auto; -@ padding: 1px 1px 1px 1px; -@ font-size: 1.2em; -@ font-weight: bold; -@ text-align: center; -@ color: white; -@ border-radius: 5px; -@ background-color: #446979; -@ box-shadow: 0px 3px 4px #333333; -@ white-space: nowrap; -@ } -@ -@ /* The "Date" that occurs on the left hand side of timelines */ -@ div.divider { -@ font-size: 1.2em; -@ font-family: Georgia, serif; -@ font-weight: bold; -@ margin-top: 1em; -@ white-space: nowrap; -@ } -@ -@ /* The footer at the very bottom of the page */ -@ div.footer { -@ font-size: 0.9em; -@ text-align: right; -@ margin-bottom: 1em; -@ color: #666; -@ } -@ -@ /* Hyperlink colors in the footer */ -@ div.footer a { color: white; } -@ div.footer a:link { color: white; } -@ div.footer a:visited { color: white; } -@ div.footer a:hover { background-color: white; color: #558195; } -@ -@ /* <verbatim> blocks */ -@ pre.verbatim, blockquote pre { -@ font-family: Dejavu Sans Mono, Monaco, Lucida Console, monospace; -@ background-color: #f3f3f3; -@ padding: 0.5em; -@ white-space: pre-wrap; -@ } -@ -@ blockquote pre { -@ border: 1px #000 dashed; -@ } -@ -@ /* The label/value pairs on (for example) the ci page */ -@ table.label-value th { -@ vertical-align: top; -@ text-align: right; -@ padding: 0.2ex 2ex; -@ } -@ -@ table.report tr th { -@ padding: 3px 5px; -@ text-transform: capitalize; -@ cursor: pointer; -@ } -@ -@ table.report tr td { -@ padding: 3px 5px; -@ cursor: pointer; -@ } -@ -@ textarea { -@ font-size: 1em; -@ }'); -@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> -@ <head> -@ <base href="$baseurl/$current_page" /> -@ <title>$<project_name>: $<title> -@ -@ -@ -@ -@
            -@ -@
            $</div> -@ <div class="status"><th1> -@ if {[info exists login]} { -@ puts "Logged in as $login" -@ } else { -@ puts "Not logged in" -@ } -@ </th1></div> -@ </div> -@ <div class="mainmenu"> -@ <th1> -@ html "<a href=''$home$index_page''>Home</a>\n" -@ if {[anycap jor]} { -@ html "<a href=''$home/timeline''>Timeline</a>\n" -@ } -@ if {[hascap oh]} { -@ html "<a href=''$home/dir?ci=tip''>Files</a>\n" -@ } -@ if {[hascap o]} { -@ html "<a href=''$home/brlist''>Branches</a>\n" -@ html "<a href=''$home/taglist''>Tags</a>\n" -@ } -@ if {[hascap r]} { -@ html "<a href=''$home/reportlist''>Tickets</a>\n" -@ } -@ if {[hascap j]} { -@ html "<a href=''$home/wiki''>Wiki</a>\n" -@ } -@ if {[hascap s]} { -@ html "<a href=''$home/setup''>Admin</a>\n" -@ } elseif {[hascap a]} { -@ html "<a href=''$home/setup_ulist''>Users</a>\n" -@ } -@ if {[info exists login]} { -@ html "<a href=''$home/login''>Logout</a>\n" -@ } else { -@ html "<a href=''$home/login''>Login</a>\n" -@ } -@ </th1></div> -@ <div id="container"> -@ '); -@ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div> -@ <div class="footer"> -@ Fossil version $manifest_version $manifest_date -@ </div> -@ </body></html> -@ '); -; - - -/* -** This skin is intended to be almost identical to the default one, with the -** following changes to the header and footer: -** -** 1. The logo image in the header has been modified to be a hyperlink to the -** root of the web site containing the repository using the same scheme -** (i.e. HTTP or HTTPS) as the base URL for the repository. The header -** contains a TH1 script block to help accomplish these tasks. -** -** 2. The Fossil version information in the footer has been augmented with -** hyperlinks to the corresponding points on the timeline in the official -** Fossil repository. Additionally, if the Tcl integration feature is -** enabled, the loaded version of Tcl is included, with a hyperlink to the -** official Tcl/Tk web site. The footer also contains a TH1 script block -** to help accomplish these tasks. -*/ -static const char zBuiltinSkin5[] = -@ REPLACE INTO config(name,mtime,value) -@ VALUES('css',now(),'/* General settings for the entire page */ -@ body { -@ margin: 0ex 1ex; -@ padding: 0px; -@ background-color: white; -@ font-family: sans-serif; -@ } -@ -@ /* The project logo in the upper left-hand corner of each page */ -@ div.logo { -@ display: table-cell; -@ text-align: center; -@ vertical-align: bottom; -@ font-weight: bold; -@ color: #558195; -@ min-width: 200px; -@ white-space: nowrap; -@ } -@ -@ /* The page title centered at the top of each page */ -@ div.title { -@ display: table-cell; -@ font-size: 2em; -@ font-weight: bold; -@ text-align: center; -@ padding: 0 0 0 1em; -@ color: #558195; -@ vertical-align: bottom; -@ width: 100%; -@ } -@ -@ /* The login status message in the top right-hand corner */ -@ div.status { -@ display: table-cell; -@ text-align: right; -@ vertical-align: bottom; -@ color: #558195; -@ font-size: 0.8em; -@ font-weight: bold; -@ min-width: 200px; -@ white-space: nowrap; -@ } -@ -@ /* The header across the top of the page */ -@ div.header { -@ display: table; -@ width: 100%; -@ } -@ -@ /* The main menu bar that appears at the top of the page beneath -@ ** the header */ -@ div.mainmenu { -@ padding: 5px 10px 5px 10px; -@ font-size: 0.9em; -@ font-weight: bold; -@ text-align: center; -@ letter-spacing: 1px; -@ background-color: #558195; -@ border-top-left-radius: 8px; -@ border-top-right-radius: 8px; -@ color: white; -@ } -@ -@ /* The submenu bar that *sometimes* appears below the main menu */ -@ div.submenu, div.sectionmenu { -@ padding: 3px 10px 3px 0px; -@ font-size: 0.9em; -@ text-align: center; -@ background-color: #456878; -@ color: white; -@ } -@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, -@ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited { -@ padding: 3px 10px 3px 10px; -@ color: white; -@ text-decoration: none; -@ } -@ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover { -@ color: #558195; -@ background-color: white; -@ } -@ -@ /* All page content from the bottom of the menu or submenu down to -@ ** the footer */ -@ div.content { -@ padding: 0ex 1ex 1ex 1ex; -@ border: solid #aaa; -@ border-width: 1px; -@ } -@ -@ /* Some pages have section dividers */ -@ div.section { -@ margin-bottom: 0px; -@ margin-top: 1em; -@ padding: 1px 1px 1px 1px; -@ font-size: 1.2em; -@ font-weight: bold; -@ background-color: #558195; -@ color: white; -@ white-space: nowrap; -@ } -@ -@ /* The "Date" that occurs on the left hand side of timelines */ -@ div.divider { -@ background: #a1c4d4; -@ border: 2px #558195 solid; -@ font-size: 1em; font-weight: normal; -@ padding: .25em; -@ margin: .2em 0 .2em 0; -@ float: left; -@ clear: left; -@ white-space: nowrap; -@ } -@ -@ /* The footer at the very bottom of the page */ -@ div.footer { -@ clear: both; -@ font-size: 0.8em; -@ padding: 5px 10px 5px 10px; -@ text-align: right; -@ background-color: #558195; -@ border-bottom-left-radius: 8px; -@ border-bottom-right-radius: 8px; -@ color: white; -@ } -@ -@ /* Hyperlink colors in the footer */ -@ div.footer a { color: white; } -@ div.footer a:link { color: white; } -@ div.footer a:visited { color: white; } -@ div.footer a:hover { background-color: white; color: #558195; } -@ -@ /* verbatim blocks */ -@ pre.verbatim { -@ background-color: #f5f5f5; -@ padding: 0.5em; -@ white-space: pre-wrap; -@ } -@ -@ /* The label/value pairs on (for example) the ci page */ -@ table.label-value th { -@ vertical-align: top; -@ text-align: right; -@ padding: 0.2ex 2ex; -@ }'); -@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> -@ <head> -@ <base href="$baseurl/$current_page" /> -@ <title>$<project_name>: $<title> -@ -@ -@ -@ -@
            -@ -@
            $
            $</div> -@ <div class="status"><th1> -@ if {[info exists login]} { -@ puts "Logged in as $login" -@ } else { -@ puts "Not logged in" -@ } -@ </th1></div> -@ </div> -@ <div class="mainmenu"> -@ <th1> -@ html "<a href=''$home$index_page''>Home</a>\n" -@ if {[anycap jor]} { -@ html "<a href=''$home/timeline''>Timeline</a>\n" -@ } -@ if {[hascap oh]} { -@ html "<a href=''$home/dir?ci=tip''>Files</a>\n" -@ } -@ if {[hascap o]} { -@ html "<a href=''$home/brlist''>Branches</a>\n" -@ html "<a href=''$home/taglist''>Tags</a>\n" -@ } -@ if {[hascap r]} { -@ html "<a href=''$home/reportlist''>Tickets</a>\n" -@ } -@ if {[hascap j]} { -@ html "<a href=''$home/wiki''>Wiki</a>\n" -@ } -@ if {[hascap s]} { -@ html "<a href=''$home/setup''>Admin</a>\n" -@ } elseif {[hascap a]} { -@ html "<a href=''$home/setup_ulist''>Users</a>\n" -@ } -@ if {[info exists login]} { -@ html "<a href=''$home/login''>Logout</a>\n" -@ } else { -@ html "<a href=''$home/login''>Login</a>\n" -@ } -@ </th1></div> -@ '); -@ REPLACE INTO config(name,mtime,value) -@ VALUES('footer',now(),'<div class="footer"> -@ <th1> -@ proc getTclVersion {} { -@ if {[catch {tclEval info patchlevel} tclVersion] == 0} { -@ return "<a href=\"http://www.tcl.tk/\">Tcl</a> version $tclVersion" -@ } -@ return "" -@ } -@ proc getVersion { version } { -@ set length [string length $version] -@ return [string range $version 1 [expr {$length - 2}]] -@ } -@ set version [getVersion $manifest_version] -@ set tclVersion [getTclVersion] -@ set fossilUrl http://www.fossil-scm.org -@ </th1> -@ This page was generated in about -@ <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by -@ <a href="$fossilUrl/">Fossil</a> -@ version $release_version $tclVersion -@ <a href="$fossilUrl/index.html/info/$version">$manifest_version</a> -@ <a href="$fossilUrl/fossil/timeline?c=$manifest_date&y=ci">$manifest_date</a> -@ </div> -@ </body></html> -@ '); -; - /* ** An array of available built-in skins. +** +** To add new built-in skins: +** +** 1. Pick a name for the new skin. (Here we use "xyzzy"). +** +** 2. Install files skins/xyzzy/css.txt, skins/xyzzy/header.txt, +** and skins/xyzzy/footer.txt into the source tree. +** +** 3. Rerun "tclsh makemake.tcl" in the src/ folder in order to +** rebuild the makefiles to reference the new CSS, headers, and footers. +** +** 4. Make an entry in the following array for the new skin. */ static struct BuiltinSkin { - const char *zName; - const char *zValue; + const char *zDesc; /* Description of this skin */ + const char *zLabel; /* The directory under skins/ holding this skin */ + char *zSQL; /* Filled in at run-time with SQL to insert this skin */ } aBuiltinSkin[] = { - { "Default", 0 /* Filled in at runtime */ }, - { "Plain Gray, No Logo", zBuiltinSkin1 }, - { "Khaki, No Logo", zBuiltinSkin2 }, - { "Black & White, Menu on Left", zBuiltinSkin3 }, - { "Shadow boxes & Rounded Corners", zBuiltinSkin4 }, - { "Enhanced Default", zBuiltinSkin5 }, + { "Default", "default", 0 }, + { "Blitz", "blitz", 0 }, + { "Blitz, No Logo", "blitz_no_logo", 0 }, + { "Xekri", "xekri", 0 }, + { "Original", "original", 0 }, + { "Enhanced Original", "enhanced1", 0 }, + { "Shadow boxes & Rounded Corners", "rounded1", 0 }, + { "Eagle", "eagle", 0 }, + { "Black & White, Menu on Left", "black_and_white", 0 }, + { "Plain Gray, No Logo", "plain_gray", 0 }, + { "Khaki, No Logo", "khaki", 0 }, +}; + +/* +** Alternative skins can be specified in the CGI script or by options +** on the "http", "ui", and "server" commands. The alternative skin +** name must be one of the aBuiltinSkin[].zLabel names. If there is +** a match, that alternative is used. +** +** The following static variable holds the name of the alternative skin, +** or NULL if the skin should be as configured. +*/ +static struct BuiltinSkin *pAltSkin = 0; +static char *zAltSkinDir = 0; + +/* +** Skin details are a set of key/value pairs that define display +** attributes of the skin that cannot be easily specified using CSS +** or that need to be known on the server-side. +** +** The following array holds the value for all known skin details. +*/ +static struct SkinDetail { + const char *zName; /* Name of the detail */ + char *zValue; /* Value of the detail */ +} aSkinDetail[] = { + { "timeline-arrowheads", "1" }, + { "timeline-circle-nodes", "0" }, + { "timeline-color-graph-lines", "0" }, + { "white-foreground", "0" }, }; + +/* +** Invoke this routine to set the alternative skin. Return NULL if the +** alternative was successfully installed. Return a string listing all +** available skins if zName does not match an available skin. Memory +** for the returned string comes from fossil_malloc() and should be freed +** by the caller. +** +** If the alternative skin name contains one or more '/' characters, then +** it is assumed to be a directory on disk that holds override css.txt, +** footer.txt, and header.txt. This mode can be used for interactive +** development of new skins. +*/ +char *skin_use_alternative(const char *zName){ + int i; + Blob err = BLOB_INITIALIZER; + if( strchr(zName, '/')!=0 ){ + zAltSkinDir = fossil_strdup(zName); + return 0; + } + for(i=0; i<ArraySize(aBuiltinSkin); i++){ + if( fossil_strcmp(aBuiltinSkin[i].zLabel, zName)==0 ){ + pAltSkin = &aBuiltinSkin[i]; + return 0; + } + } + blob_appendf(&err, "available skins: %s", aBuiltinSkin[0].zLabel); + for(i=1; i<ArraySize(aBuiltinSkin); i++){ + blob_append(&err, " ", 1); + blob_append(&err, aBuiltinSkin[i].zLabel, -1); + } + return blob_str(&err); +} + +/* +** Look for the --skin command-line option and process it. Or +** call fossil_fatal() if an unknown skin is specified. +*/ +void skin_override(void){ + const char *zSkin = find_option("skin",0,1); + if( zSkin ){ + char *zErr = skin_use_alternative(zSkin); + if( zErr ) fossil_fatal("%s", zErr); + } +} + +/* +** The following routines return the various components of the skin +** that should be used for the current run. +*/ +const char *skin_get(const char *zWhat){ + const char *zOut; + char *z; + if( zAltSkinDir ){ + char *z = mprintf("%s/%s.txt", zAltSkinDir, zWhat); + if( file_isfile(z) ){ + Blob x; + blob_read_from_file(&x, z); + fossil_free(z); + return blob_str(&x); + } + fossil_free(z); + } + if( pAltSkin ){ + z = mprintf("skins/%s/%s.txt", pAltSkin->zLabel, zWhat); + zOut = builtin_text(z); + fossil_free(z); + }else{ + zOut = db_get(zWhat, 0); + if( zOut==0 ){ + z = mprintf("skins/default/%s.txt", zWhat); + zOut = builtin_text(z); + fossil_free(z); + } + } + return zOut; +} + +/* +** Return a pointer to a SkinDetail element. Return 0 if not found. +*/ +static struct SkinDetail *skin_detail_find(const char *zName){ + int lwr = 0; + int upr = ArraySize(aSkinDetail); + while( upr>=lwr ){ + int mid = (upr+lwr)/2; + int c = fossil_strcmp(aSkinDetail[mid].zName, zName); + if( c==0 ) return &aSkinDetail[mid]; + if( c<0 ){ + lwr = mid+1; + }else{ + upr = mid-1; + } + } + return 0; +} + +/* Initialize the aSkinDetail array using the text in the details.txt +** file. +*/ +static void skin_detail_initialize(void){ + static int isInit = 0; + char *zDetail; + Blob detail, line, key, value; + if( isInit ) return; + isInit = 1; + zDetail = (char*)skin_get("details"); + if( zDetail==0 ) return; + zDetail = fossil_strdup(zDetail); + blob_init(&detail, zDetail, -1); + while( blob_line(&detail, &line) ){ + char *zKey; + int nKey; + struct SkinDetail *pDetail; + if( !blob_token(&line, &key) ) continue; + zKey = blob_buffer(&key); + if( zKey[0]=='#' ) continue; + nKey = blob_size(&key); + if( nKey<2 ) continue; + if( zKey[nKey-1]!=':' ) continue; + zKey[nKey-1] = 0; + pDetail = skin_detail_find(zKey); + if( pDetail==0 ) continue; + if( !blob_token(&line, &value) ) continue; + pDetail->zValue = fossil_strdup(blob_str(&value)); + } + blob_reset(&detail); + fossil_free(zDetail); +} + +/* +** Return a skin detail setting +*/ +const char *skin_detail(const char *zName){ + struct SkinDetail *pDetail; + skin_detail_initialize(); + pDetail = skin_detail_find(zName); + if( pDetail==0 ) fossil_fatal("no such skin detail: %s", zName); + return pDetail->zValue; +} +int skin_detail_boolean(const char *zName){ + return !is_false(skin_detail(zName)); +} + +/* +** Hash function for computing a skin id. +*/ +static unsigned int skin_hash(unsigned int h, const char *z){ + if( z==0 ) return h; + while( z[0] ){ + h = (h<<11) ^ (h<<1) ^ (h>>3) ^ z[0]; + z++; + } + return h; +} + +/* +** Return an identifier that is (probably) different for every skin +** but that is (probably) the same if the skin is unchanged. This +** identifier can be attached to resource URLs to force reloading when +** the resources change but allow the resources to be read from cache +** as long as they are unchanged. +*/ +unsigned int skin_id(const char *zResource){ + unsigned int h = 0; + if( zAltSkinDir ){ + h = skin_hash(0, zAltSkinDir); + }else if( pAltSkin ){ + h = skin_hash(0, pAltSkin->zLabel); + }else{ + char *zMTime = db_get_mtime(zResource, 0, 0); + h = skin_hash(0, zMTime); + fossil_free(zMTime); + } + h = skin_hash(h, MANIFEST_UUID); + return h; +} /* ** For a skin named zSkinName, compute the name of the CONFIG table ** entry where that skin is stored and return it. ** @@ -1275,57 +278,159 @@ } return z; } /* -** Construct and return a string that represents the current skin if -** useDefault==0 or a string for the default skin if useDefault==1. +** Return true if there exists a skin name "zSkinName". +*/ +static int skinExists(const char *zSkinName){ + int i; + if( zSkinName==0 ) return 0; + for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ + if( fossil_strcmp(zSkinName, aBuiltinSkin[i].zDesc)==0 ) return 1; + } + return db_exists("SELECT 1 FROM config WHERE name='skin:%q'", zSkinName); +} + +/* +** Construct and return an string of SQL statements that represents +** a "skin" setting. If zName==0 then return the skin currently +** installed. Otherwise, return one of the built-in skins designated +** by zName. ** ** Memory to hold the returned string is obtained from malloc. */ -static char *getSkin(int useDefault){ +static char *getSkin(const char *zName){ + const char *z; + char *zLabel; + static const char *azType[] = { "css", "header", "footer", "details" }; + int i; Blob val; blob_zero(&val); - blob_appendf(&val, - "REPLACE INTO config(name,value,mtime) VALUES('css',%Q,now());\n", - useDefault ? zDefaultCSS : db_get("css", (char*)zDefaultCSS) - ); - blob_appendf(&val, - "REPLACE INTO config(name,value,mtime) VALUES('header',%Q,now());\n", - useDefault ? zDefaultHeader : db_get("header", (char*)zDefaultHeader) - ); - blob_appendf(&val, - "REPLACE INTO config(name,value,mtime) VALUES('footer',%Q,now());\n", - useDefault ? zDefaultFooter : db_get("footer", (char*)zDefaultFooter) - ); + for(i=0; i<sizeof(azType)/sizeof(azType[0]); i++){ + if( zName ){ + zLabel = mprintf("skins/%s/%s.txt", zName, azType[i]); + z = builtin_text(zLabel); + fossil_free(zLabel); + }else{ + z = db_get(azType[i], 0); + if( z==0 ){ + zLabel = mprintf("skins/default/%s.txt", azType[i]); + z = builtin_text(zLabel); + fossil_free(zLabel); + } + } + blob_appendf(&val, + "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n", + azType[i], z + ); + } return blob_str(&val); } /* -** Construct the default skin string and fill in the corresponding -** entry in aBuildinSkin[] +** Respond to a Rename button press. Return TRUE if a dialog was painted. +** Return FALSE to continue with the main Skins page. +*/ +static int skinRename(void){ + const char *zOldName; + const char *zNewName; + int ex = 0; + if( P("rename")==0 ) return 0; + zOldName = P("sn"); + zNewName = P("newname"); + if( zOldName==0 ) return 0; + if( zNewName==0 || zNewName[0]==0 || (ex = skinExists(zNewName))!=0 ){ + if( zNewName==0 ) zNewName = zOldName; + style_header("Rename A Skin"); + if( ex ){ + @ <p><span class="generalError">There is already another skin + @ named "%h(zNewName)". Choose a different name.</span></p> + } + @ <form action="%s(g.zTop)/setup_skin" method="post"><div> + @ <table border="0"><tr> + @ <tr><td align="right">Current name:<td align="left"><b>%h(zOldName)</b> + @ <tr><td align="right">New name:<td align="left"> + @ <input type="text" size="35" name="newname" value="%h(zNewName)"> + @ <tr><td><td> + @ <input type="hidden" name="sn" value="%h(zOldName)"> + @ <input type="submit" name="rename" value="Rename"> + @ <input type="submit" name="canren" value="Cancel"> + @ </table> + login_insert_csrf_secret(); + @ </div></form> + style_footer(); + return 1; + } + db_multi_exec( + "UPDATE config SET name='skin:%q' WHERE name='skin:%q';", + zNewName, zOldName + ); + return 0; +} + +/* +** Respond to a Save button press. Return TRUE if a dialog was painted. +** Return FALSE to continue with the main Skins page. */ -static void setDefaultSkin(void){ - aBuiltinSkin[0].zValue = getSkin(1); +static int skinSave(const char *zCurrent){ + const char *zNewName; + int ex = 0; + if( P("save")==0 ) return 0; + zNewName = P("svname"); + if( zNewName && zNewName[0]!=0 ){ + } + if( zNewName==0 || zNewName[0]==0 || (ex = skinExists(zNewName))!=0 ){ + if( zNewName==0 ) zNewName = ""; + style_header("Save Current Skin"); + if( ex ){ + @ <p><span class="generalError">There is already another skin + @ named "%h(zNewName)". Choose a different name.</span></p> + } + @ <form action="%s(g.zTop)/setup_skin" method="post"><div> + @ <table border="0"><tr> + @ <tr><td align="right">Name for this skin:<td align="left"> + @ <input type="text" size="35" name="svname" value="%h(zNewName)"> + @ <tr><td><td> + @ <input type="submit" name="save" value="Save"> + @ <input type="submit" name="cansave" value="Cancel"> + @ </table> + login_insert_csrf_secret(); + @ </div></form> + style_footer(); + return 1; + } + db_multi_exec( + "INSERT OR IGNORE INTO config(name, value, mtime)" + "VALUES('skin:%q',%Q,now())", + zNewName, zCurrent + ); + return 0; } /* ** WEBPAGE: setup_skin */ void setup_skin(void){ const char *z; char *zName; char *zErr = 0; - const char *zCurrent; /* Current skin */ - int i; /* Loop counter */ + const char *zCurrent = 0; /* Current skin */ + int i; /* Loop counter */ Stmt q; + int seenCurrent = 0; login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } db_begin_transaction(); + zCurrent = getSkin(0); + for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ + aBuiltinSkin[i].zSQL = getSkin(aBuiltinSkin[i].zLabel); + } /* Process requests to delete a user-defined skin */ if( P("del1") && (zName = skinVarName(P("sn"), 1))!=0 ){ style_header("Confirm Custom Skin Delete"); @ <form action="%s(g.zTop)/setup_skin" method="post"><div> @@ -1340,106 +445,211 @@ return; } if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){ db_multi_exec("DELETE FROM config WHERE name=%Q", zName); } - - setDefaultSkin(); - zCurrent = getSkin(0); - - if( P("save")!=0 && (zName = skinVarName(P("save"),0))!=0 ){ - if( db_exists("SELECT 1 FROM config WHERE name=%Q", zName) - || fossil_strcmp(zName, "Default")==0 ){ - zErr = mprintf("Skin name \"%h\" already exists. " - "Choose a different name.", P("sn")); - }else{ - db_multi_exec("INSERT INTO config(name,value,mtime) VALUES(%Q,%Q,now())", - zName, zCurrent - ); - } - } - - /* The user pressed the "Use This Skin" button. */ + if( skinRename() ) return; + if( skinSave(zCurrent) ) return; + + /* The user pressed one of the "Install" buttons. */ if( P("load") && (z = P("sn"))!=0 && z[0] ){ int seen = 0; + + /* Check to see if the current skin is already saved. If it is, there + ** is no need to create a backup */ + zCurrent = getSkin(0); for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ - if( fossil_strcmp(aBuiltinSkin[i].zValue, zCurrent)==0 ){ + if( fossil_strcmp(aBuiltinSkin[i].zSQL, zCurrent)==0 ){ seen = 1; break; } } if( !seen ){ seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'" " AND value=%Q", zCurrent); - } - if( !seen ){ - db_multi_exec( - "INSERT INTO config(name,value,mtime) VALUES(" - " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S')," - " %Q,now())", zCurrent - ); + if( !seen ){ + db_multi_exec( + "INSERT INTO config(name,value,mtime) VALUES(" + " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S')," + " %Q,now())", zCurrent + ); + } } seen = 0; for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ - if( fossil_strcmp(aBuiltinSkin[i].zName, z)==0 ){ + if( fossil_strcmp(aBuiltinSkin[i].zDesc, z)==0 ){ seen = 1; - zCurrent = aBuiltinSkin[i].zValue; - db_multi_exec("%s", zCurrent); + zCurrent = aBuiltinSkin[i].zSQL; + db_multi_exec("%s", zCurrent/*safe-for-%s*/); break; } } if( !seen ){ zName = skinVarName(z,0); zCurrent = db_get(zName, 0); - db_multi_exec("%s", zCurrent); + db_multi_exec("%s", zCurrent/*safe-for-%s*/); } } style_header("Skins"); if( zErr ){ @ <p><font color="red">%h(zErr)</font></p> } @ <p>A "skin" is a combination of - @ <a href="setup_editcss">CSS</a>, - @ <a href="setup_header">Header</a>, - @ <a href="setup_footer">Footer</a>, and - @ <a href="setup_logo">Logo</a> that determines the look and feel + @ <a href="setup_skinedit?w=0">CSS</a>, + @ <a href="setup_skinedit?w=2">Header</a>, + @ <a href="setup_skinedit?w=1">Footer</a>, and + @ <a href="setup_skinedit?w=3">Details</a> + @ that determines the look and feel @ of the web interface.</p> @ + if( pAltSkin ){ + @ <p class="generalError"> + @ This page is generated using an skin override named + @ "%h(pAltSkin->zLabel)". You can change the skin configuration + @ below, but the changes will not take effect until the Fossil server + @ is restarted without the override.</p> + @ + } @ <h2>Available Skins:</h2> - @ <ol> + @ <table border="0"> for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ - z = aBuiltinSkin[i].zName; - if( fossil_strcmp(aBuiltinSkin[i].zValue, zCurrent)==0 ){ - @ <li><p>%h(z).   <b>Currently In Use</b></p> + z = aBuiltinSkin[i].zDesc; + @ <tr><td>%d(i+1).<td>%h(z)<td>  <td> + if( fossil_strcmp(aBuiltinSkin[i].zSQL, zCurrent)==0 ){ + @ (Currently In Use) + seenCurrent = 1; }else{ - @ <li><form action="%s(g.zTop)/setup_skin" method="post"><div> - @ %h(z).   + @ <form action="%s(g.zTop)/setup_skin" method="post"> @ <input type="hidden" name="sn" value="%h(z)" /> - @ <input type="submit" name="load" value="Use This Skin" /> - @ </div></form></li> + @ <input type="submit" name="load" value="Install" /> + if( pAltSkin==&aBuiltinSkin[i] ){ + @ (Current override) + } + @ </form> } + @ </tr> } db_prepare(&q, "SELECT substr(name, 6), value FROM config" " WHERE name GLOB 'skin:*'" " ORDER BY name" ); while( db_step(&q)==SQLITE_ROW ){ const char *zN = db_column_text(&q, 0); const char *zV = db_column_text(&q, 1); - if( fossil_strcmp(zV, zCurrent)==0 ){ - @ <li><p>%h(zN).   <b>Currently In Use</b></p> - }else{ - @ <li><form action="%s(g.zTop)/setup_skin" method="post"> - @ %h(zN).   - @ <input type="hidden" name="sn" value="%h(zN)"> - @ <input type="submit" name="load" value="Use This Skin"> - @ <input type="submit" name="del1" value="Delete This Skin"> - @ </form></li> - } + i++; + @ <tr><td>%d(i).<td>%h(zN)<td>  <td> + @ <form action="%s(g.zTop)/setup_skin" method="post"> + if( fossil_strcmp(zV, zCurrent)==0 ){ + @ (Currently In Use) + seenCurrent = 1; + }else{ + @ <input type="submit" name="load" value="Install"> + @ <input type="submit" name="del1" value="Delete"> + } + @ <input type="submit" name="rename" value="Rename"> + @ <input type="hidden" name="sn" value="%h(zN)"> + @ </form></tr> } db_finalize(&q); - @ </ol> + if( !seenCurrent ){ + i++; + @ <tr><td>%d(i).<td><i>Current Configuration</i><td>  <td> + @ <form action="%s(g.zTop)/setup_skin" method="post"> + @ <input type="submit" name="save" value="Save"> + @ </form> + } + @ </table> + style_footer(); + db_end_transaction(0); +} + + +/* +** WEBPAGE: setup_skinedit +** +** w=N -- 0=CSS, 1=footer, 2=header, 3=details +*/ +void setup_skinedit(void){ + static const struct sSkinAddr { + const char *zFile; + const char *zTitle; + const char *zSubmenu; + } aSkinAttr[] = { + /* 0 */ { "css", "CSS", "CSS", }, + /* 1 */ { "footer", "Page Footer", "Footer", }, + /* 2 */ { "header", "Page Header", "Header", }, + /* 3 */ { "details", "Display Details", "Details", }, + }; + const char *zBasis; + const char *zContent; + char *zDflt; + int ii; + int j; + + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + ii = atoi(PD("w","0")); + if( ii<0 || ii>ArraySize(aSkinAttr) ) ii = 0; + zBasis = PD("basis","default"); + zDflt = mprintf("skins/%s/%s.txt", zBasis, aSkinAttr[ii].zFile); + db_begin_transaction(); + if( P("revert")!=0 ){ + db_multi_exec("DELETE FROM config WHERE name=%Q", aSkinAttr[ii].zFile); + cgi_replace_parameter(aSkinAttr[ii].zFile, builtin_text(zDflt)); + } + style_header("%s", aSkinAttr[ii].zTitle); + for(j=0; j<ArraySize(aSkinAttr); j++){ + if( j==ii ) continue; + style_submenu_element(aSkinAttr[j].zSubmenu, 0, + "%R/setup_skinedit?w=%d&basis=%h",j,zBasis); + } + style_submenu_element("Skins", 0, "%R/setup_skin"); + @ <form action="%s(g.zTop)/setup_skinedit" method="post"><div> + login_insert_csrf_secret(); + @ <input type='hidden' name='w' value='%d(ii)'> + @ <h2>Edit %s(aSkinAttr[ii].zTitle):</h2> + zContent = textarea_attribute("", 10, 80, aSkinAttr[ii].zFile, + aSkinAttr[ii].zFile, builtin_text(zDflt), 0); + @ <br /> + @ <input type="submit" name="submit" value="Apply Changes" /> + @ <hr /> + @ Baseline: <select size='1' name='basis'> + for(j=0; j<ArraySize(aBuiltinSkin); j++){ + cgi_printf("<option value='%h'%s>%h</option>\n", + aBuiltinSkin[j].zLabel, + fossil_strcmp(zBasis,aBuiltinSkin[j].zLabel)==0 ? " selected" : "", + aBuiltinSkin[j].zDesc + ); + } + @ </select> + @ <input type="submit" name="diff" value="Diff" /> + if( P("diff")!=0 ){ + u64 diffFlags = construct_diff_flags(0,0) | + DIFF_STRIP_EOLCR; + Blob from, to, out; + blob_init(&to, zContent, -1); + blob_init(&from, builtin_text(zDflt), -1); + blob_zero(&out); + @ <input type="submit" name="revert" value="Revert" /><p> + if( diffFlags & DIFF_SIDEBYSIDE ){ + text_diff(&from, &to, &out, 0, diffFlags | DIFF_HTML | DIFF_NOTTOOBIG); + @ %s(blob_str(&out)) + }else{ + text_diff(&from, &to, &out, 0, + diffFlags | DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG); + @ <pre class="udiff"> + @ %s(blob_str(&out)) + @ </pre> + } + blob_reset(&from); + blob_reset(&to); + blob_reset(&out); + } + @ </div></form> style_footer(); db_end_transaction(0); } Index: src/sqlcmd.c ================================================================== --- src/sqlcmd.c +++ src/sqlcmd.c @@ -20,11 +20,16 @@ ** is a copy of the "shell.c" code from SQLite. This file contains logic ** to initialize the code in shell.c. */ #include "config.h" #include "sqlcmd.h" -#include <zlib.h> +#if defined(FOSSIL_ENABLE_MINIZ) +# define MINIZ_HEADER_FILE_ONLY +# include "miniz.c" +#else +# include <zlib.h> +#endif /* ** Implementation of the "content(X)" SQL function. Return the complete ** content of artifact identified by X as a blob. */ @@ -42,11 +47,11 @@ g.db = sqlite3_context_db_handle(context); g.repositoryOpen = 1; rid = name_to_rid(zName); if( rid==0 ) return; if( content_get(rid, &cx) ){ - sqlite3_result_blob(context, blob_buffer(&cx), blob_size(&cx), + sqlite3_result_blob(context, blob_buffer(&cx), blob_size(&cx), SQLITE_TRANSIENT); blob_reset(&cx); } } @@ -101,10 +106,24 @@ sqlite3_result_blob(context, pOut, nOut, sqlite3_free); }else{ sqlite3_result_error(context, "input is not zlib compressed", -1); } } + +/* +** Add the content(), compress(), and decompress() SQL functions to +** database connection db. +*/ +int add_content_sql_commands(sqlite3 *db){ + sqlite3_create_function(db, "content", 1, SQLITE_UTF8, 0, + sqlcmd_content, 0, 0); + sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0, + sqlcmd_compress, 0, 0); + sqlite3_create_function(db, "decompress", 1, SQLITE_UTF8, 0, + sqlcmd_decompress, 0, 0); + return SQLITE_OK; +} /* ** This is the "automatic extension" initializer that runs right after ** the connection to the repository database is opened. Set up the ** database connection to be more useful to the human operator. @@ -112,43 +131,75 @@ static int sqlcmd_autoinit( sqlite3 *db, const char **pzErrMsg, const void *notUsed ){ - sqlite3_create_function(db, "content", 1, SQLITE_ANY, 0, - sqlcmd_content, 0, 0); - sqlite3_create_function(db, "compress", 1, SQLITE_ANY, 0, - sqlcmd_compress, 0, 0); - sqlite3_create_function(db, "decompress", 1, SQLITE_ANY, 0, - sqlcmd_decompress, 0, 0); + add_content_sql_commands(db); + db_add_aux_functions(db); re_add_sql_func(db); + search_sql_setup(db); + g.zMainDbType = "repository"; + foci_register(db); g.repositoryOpen = 1; g.db = db; return SQLITE_OK; } - /* ** COMMAND: sqlite3 ** ** Usage: %fossil sqlite3 ?DATABASE? ?OPTIONS? ** ** Run the standalone sqlite3 command-line shell on DATABASE with OPTIONS. ** If DATABASE is omitted, then the repository that serves the working -** directory is opened. +** directory is opened. See https://www.sqlite.org/cli.html for additional +** information. ** ** WARNING: Careless use of this command can corrupt a Fossil repository ** in ways that are unrecoverable. Be sure you know what you are doing before ** running any SQL commands that modifies the repository database. +** +** The following extensions to the usual SQLite commands are provided: +** +** content(X) Return the contenxt of artifact X. X can be a +** SHA1 hash or prefix or a tag. +** +** compress(X) Compress text X. +** +** decompress(X) Decompress text X. Undoes the work of +** compress(X). +** +** checkin_mtime(X,Y) Return the mtime for the file Y (a BLOB.RID) +** found in check-in X (another BLOB.RID value). +** +** symbolic_name_to_rid(X) Return a the BLOB.RID corresponding to symbolic +** name X. +** +** now() Return the number of seconds since 1970. +** +** REGEXP The REGEXP operator works, unlike in +** standard SQLite. +** +** files_of_checkin The "files_of_check" virtual table is +** available for decoding manifests. +** +** Usage example for files_of_checkin: +** +** CREATE VIRTUAL TABLE temp.foci USING files_of_checkin; +** SELECT * FROM foci WHERE checkinID=symbolic_name_to_rid('trunk'); */ -void sqlite3_cmd(void){ +void cmd_sqlite3(void){ extern int sqlite3_shell(int, char**); db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); db_close(1); sqlite3_shutdown(); sqlite3_shell(g.argc-1, g.argv+1); + sqlite3_cancel_auto_extension((void(*)(void))sqlcmd_autoinit); g.db = 0; + g.zMainDbType = 0; + g.repositoryOpen = 0; + g.localOpen = 0; } /* ** This routine is called by the patched sqlite3 command-line shell in order ** to load the name and database connection for the open Fossil database. Index: src/sqlite3.c ================================================================== --- src/sqlite3.c +++ src/sqlite3.c @@ -1,8 +1,8 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.8.1. By combining all the individual C code files into this +** version 3.8.9. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements ** of 5% or more are commonly seen when SQLite is compiled as a single ** translation unit. @@ -20,13 +20,10 @@ #define SQLITE_CORE 1 #define SQLITE_AMALGAMATION 1 #ifndef SQLITE_PRIVATE # define SQLITE_PRIVATE static #endif -#ifndef SQLITE_API -# define SQLITE_API -#endif /************** Begin file sqliteInt.h ***************************************/ /* ** 2001 September 15 ** ** The author disclaims copyright to this source code. In place of @@ -41,10 +38,95 @@ ** */ #ifndef _SQLITEINT_H_ #define _SQLITEINT_H_ +/* +** Include the header file used to customize the compiler options for MSVC. +** This should be done first so that it can successfully prevent spurious +** compiler warnings due to subsequent content in this file and other files +** that are included by this file. +*/ +/************** Include msvc.h in the middle of sqliteInt.h ******************/ +/************** Begin file msvc.h ********************************************/ +/* +** 2015 January 12 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific to MSVC. +*/ +#ifndef _MSVC_H_ +#define _MSVC_H_ + +#if defined(_MSC_VER) +#pragma warning(disable : 4054) +#pragma warning(disable : 4055) +#pragma warning(disable : 4100) +#pragma warning(disable : 4127) +#pragma warning(disable : 4152) +#pragma warning(disable : 4189) +#pragma warning(disable : 4206) +#pragma warning(disable : 4210) +#pragma warning(disable : 4232) +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) +#pragma warning(disable : 4306) +#pragma warning(disable : 4702) +#pragma warning(disable : 4706) +#endif /* defined(_MSC_VER) */ + +#endif /* _MSVC_H_ */ + +/************** End of msvc.h ************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + +/* +** Special setup for VxWorks +*/ +/************** Include vxworks.h in the middle of sqliteInt.h ***************/ +/************** Begin file vxworks.h *****************************************/ +/* +** 2015-03-02 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific to Wind River's VxWorks +*/ +#if defined(__RTP__) || defined(_WRS_KERNEL) +/* This is VxWorks. Set up things specially for that OS +*/ +#include <vxWorks.h> +#include <pthread.h> /* amalgamator: dontcache */ +#define OS_VXWORKS 1 +#define SQLITE_OS_OTHER 0 +#define SQLITE_HOMEGROWN_RECURSIVE_MUTEX 1 +#define SQLITE_OMIT_LOAD_EXTENSION 1 +#define SQLITE_ENABLE_LOCKING_STYLE 0 +#define HAVE_UTIME 1 +#else +/* This is not VxWorks. */ +#define OS_VXWORKS 0 +#endif /* defined(_WRS_KERNEL) */ + +/************** End of vxworks.h *********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + /* ** These #defines should enable >2GB file support on POSIX if the ** underlying operating system supports it. If the OS lacks ** large file support, or if the OS is windows, these should be no-ops. ** @@ -58,10 +140,15 @@ ** on an older machine (ex: Red Hat 6.0). If you compile on Red Hat 7.2 ** without this option, LFS is enable. But LFS does not exist in the kernel ** in Red Hat 6.0, so the code won't work. Hence, for maximum binary ** portability you should omit LFS. ** +** The previous paragraph was written in 2005. (This paragraph is written +** on 2008-11-28.) These days, all Linux kernels support large files, so +** you should probably leave LFS enabled. But some embedded platforms might +** lack LFS in which case the SQLITE_DISABLE_LFS macro might still be useful. +** ** Similar is true for Mac OS X. LFS is only supported on Mac OS X 9 and later. */ #ifndef SQLITE_DISABLE_LFS # define _LARGE_FILE 1 # ifndef _FILE_OFFSET_BITS @@ -68,485 +155,55 @@ # define _FILE_OFFSET_BITS 64 # endif # define _LARGEFILE_SOURCE 1 #endif -/* -** Include the configuration header output by 'configure' if we're using the -** autoconf-based build -*/ -#ifdef _HAVE_SQLITE_CONFIG_H -#include "config.h" -#endif - -/************** Include sqliteLimit.h in the middle of sqliteInt.h ***********/ -/************** Begin file sqliteLimit.h *************************************/ -/* -** 2007 May 7 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** -** This file defines various limits of what SQLite can process. -*/ - -/* -** The maximum length of a TEXT or BLOB in bytes. This also -** limits the size of a row in a table or index. -** -** The hard limit is the ability of a 32-bit signed integer -** to count the size: 2^31-1 or 2147483647. -*/ -#ifndef SQLITE_MAX_LENGTH -# define SQLITE_MAX_LENGTH 1000000000 -#endif - -/* -** This is the maximum number of -** -** * Columns in a table -** * Columns in an index -** * Columns in a view -** * Terms in the SET clause of an UPDATE statement -** * Terms in the result set of a SELECT statement -** * Terms in the GROUP BY or ORDER BY clauses of a SELECT statement. -** * Terms in the VALUES clause of an INSERT statement -** -** The hard upper limit here is 32676. Most database people will -** tell you that in a well-normalized database, you usually should -** not have more than a dozen or so columns in any table. And if -** that is the case, there is no point in having more than a few -** dozen values in any of the other situations described above. -*/ -#ifndef SQLITE_MAX_COLUMN -# define SQLITE_MAX_COLUMN 2000 -#endif - -/* -** The maximum length of a single SQL statement in bytes. -** -** It used to be the case that setting this value to zero would -** turn the limit off. That is no longer true. It is not possible -** to turn this limit off. -*/ -#ifndef SQLITE_MAX_SQL_LENGTH -# define SQLITE_MAX_SQL_LENGTH 1000000000 -#endif - -/* -** The maximum depth of an expression tree. This is limited to -** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might -** want to place more severe limits on the complexity of an -** expression. -** -** A value of 0 used to mean that the limit was not enforced. -** But that is no longer true. The limit is now strictly enforced -** at all times. -*/ -#ifndef SQLITE_MAX_EXPR_DEPTH -# define SQLITE_MAX_EXPR_DEPTH 1000 -#endif - -/* -** The maximum number of terms in a compound SELECT statement. -** The code generator for compound SELECT statements does one -** level of recursion for each term. A stack overflow can result -** if the number of terms is too large. In practice, most SQL -** never has more than 3 or 4 terms. Use a value of 0 to disable -** any limit on the number of terms in a compount SELECT. -*/ -#ifndef SQLITE_MAX_COMPOUND_SELECT -# define SQLITE_MAX_COMPOUND_SELECT 500 -#endif - -/* -** The maximum number of opcodes in a VDBE program. -** Not currently enforced. -*/ -#ifndef SQLITE_MAX_VDBE_OP -# define SQLITE_MAX_VDBE_OP 25000 -#endif - -/* -** The maximum number of arguments to an SQL function. -*/ -#ifndef SQLITE_MAX_FUNCTION_ARG -# define SQLITE_MAX_FUNCTION_ARG 127 -#endif - -/* -** The maximum number of in-memory pages to use for the main database -** table and for temporary tables. The SQLITE_DEFAULT_CACHE_SIZE -*/ -#ifndef SQLITE_DEFAULT_CACHE_SIZE -# define SQLITE_DEFAULT_CACHE_SIZE 2000 -#endif -#ifndef SQLITE_DEFAULT_TEMP_CACHE_SIZE -# define SQLITE_DEFAULT_TEMP_CACHE_SIZE 500 -#endif - -/* -** The default number of frames to accumulate in the log file before -** checkpointing the database in WAL mode. -*/ -#ifndef SQLITE_DEFAULT_WAL_AUTOCHECKPOINT -# define SQLITE_DEFAULT_WAL_AUTOCHECKPOINT 1000 -#endif - -/* -** The maximum number of attached databases. This must be between 0 -** and 62. The upper bound on 62 is because a 64-bit integer bitmap -** is used internally to track attached databases. -*/ -#ifndef SQLITE_MAX_ATTACHED -# define SQLITE_MAX_ATTACHED 10 -#endif - - -/* -** The maximum value of a ?nnn wildcard that the parser will accept. -*/ -#ifndef SQLITE_MAX_VARIABLE_NUMBER -# define SQLITE_MAX_VARIABLE_NUMBER 999 -#endif - -/* Maximum page size. The upper bound on this value is 65536. This a limit -** imposed by the use of 16-bit offsets within each page. -** -** Earlier versions of SQLite allowed the user to change this value at -** compile time. This is no longer permitted, on the grounds that it creates -** a library that is technically incompatible with an SQLite library -** compiled with a different limit. If a process operating on a database -** with a page-size of 65536 bytes crashes, then an instance of SQLite -** compiled with the default page-size limit will not be able to rollback -** the aborted transaction. This could lead to database corruption. -*/ -#ifdef SQLITE_MAX_PAGE_SIZE -# undef SQLITE_MAX_PAGE_SIZE -#endif -#define SQLITE_MAX_PAGE_SIZE 65536 - - -/* -** The default size of a database page. -*/ -#ifndef SQLITE_DEFAULT_PAGE_SIZE -# define SQLITE_DEFAULT_PAGE_SIZE 1024 -#endif -#if SQLITE_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE -# undef SQLITE_DEFAULT_PAGE_SIZE -# define SQLITE_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE -#endif - -/* -** Ordinarily, if no value is explicitly provided, SQLite creates databases -** with page size SQLITE_DEFAULT_PAGE_SIZE. However, based on certain -** device characteristics (sector-size and atomic write() support), -** SQLite may choose a larger value. This constant is the maximum value -** SQLite will choose on its own. -*/ -#ifndef SQLITE_MAX_DEFAULT_PAGE_SIZE -# define SQLITE_MAX_DEFAULT_PAGE_SIZE 8192 -#endif -#if SQLITE_MAX_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE -# undef SQLITE_MAX_DEFAULT_PAGE_SIZE -# define SQLITE_MAX_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE -#endif - - -/* -** Maximum number of pages in one database file. -** -** This is really just the default value for the max_page_count pragma. -** This value can be lowered (or raised) at run-time using that the -** max_page_count macro. -*/ -#ifndef SQLITE_MAX_PAGE_COUNT -# define SQLITE_MAX_PAGE_COUNT 1073741823 -#endif - -/* -** Maximum length (in bytes) of the pattern in a LIKE or GLOB -** operator. -*/ -#ifndef SQLITE_MAX_LIKE_PATTERN_LENGTH -# define SQLITE_MAX_LIKE_PATTERN_LENGTH 50000 -#endif - -/* -** Maximum depth of recursion for triggers. -** -** A value of 1 means that a trigger program will not be able to itself -** fire any triggers. A value of 0 means that no trigger programs at all -** may be executed. -*/ -#ifndef SQLITE_MAX_TRIGGER_DEPTH -# define SQLITE_MAX_TRIGGER_DEPTH 1000 -#endif - -/************** End of sqliteLimit.h *****************************************/ -/************** Continuing where we left off in sqliteInt.h ******************/ - -/* Disable nuisance warnings on Borland compilers */ -#if defined(__BORLANDC__) -#pragma warn -rch /* unreachable code */ -#pragma warn -ccc /* Condition is always true or false */ -#pragma warn -aus /* Assigned value is never used */ -#pragma warn -csu /* Comparing signed and unsigned */ -#pragma warn -spa /* Suspicious pointer arithmetic */ -#endif - /* Needed for various definitions... */ -#ifndef _GNU_SOURCE +#if defined(__GNUC__) && !defined(_GNU_SOURCE) # define _GNU_SOURCE #endif #if defined(__OpenBSD__) && !defined(_BSD_SOURCE) # define _BSD_SOURCE #endif /* -** Include standard header files as necessary -*/ -#ifdef HAVE_STDINT_H -#include <stdint.h> -#endif -#ifdef HAVE_INTTYPES_H -#include <inttypes.h> -#endif - -/* -** The following macros are used to cast pointers to integers and -** integers to pointers. The way you do this varies from one compiler -** to the next, so we have developed the following set of #if statements -** to generate appropriate macros for a wide range of compilers. -** -** The correct "ANSI" way to do this is to use the intptr_t type. -** Unfortunately, that typedef is not available on all compilers, or -** if it is available, it requires an #include of specific headers -** that vary from one machine to the next. -** -** Ticket #3860: The llvm-gcc-4.2 compiler from Apple chokes on -** the ((void*)&((char*)0)[X]) construct. But MSVC chokes on ((void*)(X)). -** So we have to define the macros in different ways depending on the -** compiler. -*/ -#if defined(__PTRDIFF_TYPE__) /* This case should work for GCC */ -# define SQLITE_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X)) -# define SQLITE_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X)) -#elif !defined(__GNUC__) /* Works for compilers other than LLVM */ -# define SQLITE_INT_TO_PTR(X) ((void*)&((char*)0)[X]) -# define SQLITE_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) -#elif defined(HAVE_STDINT_H) /* Use this case if we have ANSI headers */ -# define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X)) -# define SQLITE_PTR_TO_INT(X) ((int)(intptr_t)(X)) -#else /* Generates a warning - but it always works */ -# define SQLITE_INT_TO_PTR(X) ((void*)(X)) -# define SQLITE_PTR_TO_INT(X) ((int)(X)) -#endif - -/* -** The SQLITE_THREADSAFE macro must be defined as 0, 1, or 2. -** 0 means mutexes are permanently disable and the library is never -** threadsafe. 1 means the library is serialized which is the highest -** level of threadsafety. 2 means the library is multithreaded - multiple -** threads can use SQLite as long as no two threads try to use the same -** database connection at the same time. -** -** Older versions of SQLite used an optional THREADSAFE macro. -** We support that for legacy. -*/ -#if !defined(SQLITE_THREADSAFE) -# if defined(THREADSAFE) -# define SQLITE_THREADSAFE THREADSAFE -# else -# define SQLITE_THREADSAFE 1 /* IMP: R-07272-22309 */ -# endif -#endif - -/* -** Powersafe overwrite is on by default. But can be turned off using -** the -DSQLITE_POWERSAFE_OVERWRITE=0 command-line option. -*/ -#ifndef SQLITE_POWERSAFE_OVERWRITE -# define SQLITE_POWERSAFE_OVERWRITE 1 -#endif - -/* -** The SQLITE_DEFAULT_MEMSTATUS macro must be defined as either 0 or 1. -** It determines whether or not the features related to -** SQLITE_CONFIG_MEMSTATUS are available by default or not. This value can -** be overridden at runtime using the sqlite3_config() API. -*/ -#if !defined(SQLITE_DEFAULT_MEMSTATUS) -# define SQLITE_DEFAULT_MEMSTATUS 1 -#endif - -/* -** Exactly one of the following macros must be defined in order to -** specify which memory allocation subsystem to use. -** -** SQLITE_SYSTEM_MALLOC // Use normal system malloc() -** SQLITE_WIN32_MALLOC // Use Win32 native heap API -** SQLITE_ZERO_MALLOC // Use a stub allocator that always fails -** SQLITE_MEMDEBUG // Debugging version of system malloc() -** -** On Windows, if the SQLITE_WIN32_MALLOC_VALIDATE macro is defined and the -** assert() macro is enabled, each call into the Win32 native heap subsystem -** will cause HeapValidate to be called. If heap validation should fail, an -** assertion will be triggered. -** -** If none of the above are defined, then set SQLITE_SYSTEM_MALLOC as -** the default. -*/ -#if defined(SQLITE_SYSTEM_MALLOC) \ - + defined(SQLITE_WIN32_MALLOC) \ - + defined(SQLITE_ZERO_MALLOC) \ - + defined(SQLITE_MEMDEBUG)>1 -# error "Two or more of the following compile-time configuration options\ - are defined but at most one is allowed:\ - SQLITE_SYSTEM_MALLOC, SQLITE_WIN32_MALLOC, SQLITE_MEMDEBUG,\ - SQLITE_ZERO_MALLOC" -#endif -#if defined(SQLITE_SYSTEM_MALLOC) \ - + defined(SQLITE_WIN32_MALLOC) \ - + defined(SQLITE_ZERO_MALLOC) \ - + defined(SQLITE_MEMDEBUG)==0 -# define SQLITE_SYSTEM_MALLOC 1 -#endif - -/* -** If SQLITE_MALLOC_SOFT_LIMIT is not zero, then try to keep the -** sizes of memory allocations below this value where possible. -*/ -#if !defined(SQLITE_MALLOC_SOFT_LIMIT) -# define SQLITE_MALLOC_SOFT_LIMIT 1024 -#endif - -/* -** We need to define _XOPEN_SOURCE as follows in order to enable -** recursive mutexes on most Unix systems and fchmod() on OpenBSD. -** But _XOPEN_SOURCE define causes problems for Mac OS X, so omit -** it. -*/ -#if !defined(_XOPEN_SOURCE) && !defined(__DARWIN__) && !defined(__APPLE__) -# define _XOPEN_SOURCE 600 -#endif - -/* -** NDEBUG and SQLITE_DEBUG are opposites. It should always be true that -** defined(NDEBUG)==!defined(SQLITE_DEBUG). If this is not currently true, -** make it true by defining or undefining NDEBUG. -** -** Setting NDEBUG makes the code smaller and faster by disabling the -** assert() statements in the code. So we want the default action -** to be for NDEBUG to be set and NDEBUG to be undefined only if SQLITE_DEBUG -** is set. Thus NDEBUG becomes an opt-in rather than an opt-out -** feature. -*/ -#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) -# define NDEBUG 1 -#endif -#if defined(NDEBUG) && defined(SQLITE_DEBUG) -# undef NDEBUG -#endif - -/* -** The testcase() macro is used to aid in coverage testing. When -** doing coverage testing, the condition inside the argument to -** testcase() must be evaluated both true and false in order to -** get full branch coverage. The testcase() macro is inserted -** to help ensure adequate test coverage in places where simple -** condition/decision coverage is inadequate. For example, testcase() -** can be used to make sure boundary values are tested. For -** bitmask tests, testcase() can be used to make sure each bit -** is significant and used at least once. On switch statements -** where multiple cases go to the same block of code, testcase() -** can insure that all cases are evaluated. -** -*/ -#ifdef SQLITE_COVERAGE_TEST -SQLITE_PRIVATE void sqlite3Coverage(int); -# define testcase(X) if( X ){ sqlite3Coverage(__LINE__); } -#else -# define testcase(X) -#endif - -/* -** The TESTONLY macro is used to enclose variable declarations or -** other bits of code that are needed to support the arguments -** within testcase() and assert() macros. -*/ -#if !defined(NDEBUG) || defined(SQLITE_COVERAGE_TEST) -# define TESTONLY(X) X -#else -# define TESTONLY(X) -#endif - -/* -** Sometimes we need a small amount of code such as a variable initialization -** to setup for a later assert() statement. We do not want this code to -** appear when assert() is disabled. The following macro is therefore -** used to contain that setup code. The "VVA" acronym stands for -** "Verification, Validation, and Accreditation". In other words, the -** code within VVA_ONLY() will only run during verification processes. -*/ -#ifndef NDEBUG -# define VVA_ONLY(X) X -#else -# define VVA_ONLY(X) -#endif - -/* -** The ALWAYS and NEVER macros surround boolean expressions which -** are intended to always be true or false, respectively. Such -** expressions could be omitted from the code completely. But they -** are included in a few cases in order to enhance the resilience -** of SQLite to unexpected behavior - to make the code "self-healing" -** or "ductile" rather than being "brittle" and crashing at the first -** hint of unplanned behavior. -** -** In other words, ALWAYS and NEVER are added for defensive code. -** -** When doing coverage testing ALWAYS and NEVER are hard-coded to -** be true and false so that the unreachable code they specify will -** not be counted as untested code. -*/ -#if defined(SQLITE_COVERAGE_TEST) -# define ALWAYS(X) (1) -# define NEVER(X) (0) -#elif !defined(NDEBUG) -# define ALWAYS(X) ((X)?1:(assert(0),0)) -# define NEVER(X) ((X)?(assert(0),1):0) -#else -# define ALWAYS(X) (X) -# define NEVER(X) (X) -#endif - -/* -** Return true (non-zero) if the input is a integer that is too large -** to fit in 32-bits. This macro is used inside of various testcase() -** macros to verify that we have tested SQLite for large-file support. -*/ -#define IS_BIG_INT(X) (((X)&~(i64)0xffffffff)!=0) - -/* -** The macro unlikely() is a hint that surrounds a boolean -** expression that is usually false. Macro likely() surrounds -** a boolean expression that is usually true. These hints could, -** in theory, be used by the compiler to generate better code, but -** currently they are just comments for human readers. -*/ -#define likely(X) (X) -#define unlikely(X) (X) - +** For MinGW, check to see if we can include the header file containing its +** version information, among other things. Normally, this internal MinGW +** header file would [only] be included automatically by other MinGW header +** files; however, the contained version information is now required by this +** header file to work around binary compatibility issues (see below) and +** this is the only known way to reliably obtain it. This entire #if block +** would be completely unnecessary if there was any other way of detecting +** MinGW via their preprocessor (e.g. if they customized their GCC to define +** some MinGW-specific macros). When compiling for MinGW, either the +** _HAVE_MINGW_H or _HAVE__MINGW_H (note the extra underscore) macro must be +** defined; otherwise, detection of conditions specific to MinGW will be +** disabled. +*/ +#if defined(_HAVE_MINGW_H) +# include "mingw.h" +#elif defined(_HAVE__MINGW_H) +# include "_mingw.h" +#endif + +/* +** For MinGW version 4.x (and higher), check to see if the _USE_32BIT_TIME_T +** define is required to maintain binary compatibility with the MSVC runtime +** library in use (e.g. for Windows XP). +*/ +#if !defined(_USE_32BIT_TIME_T) && !defined(_USE_64BIT_TIME_T) && \ + defined(_WIN32) && !defined(_WIN64) && \ + defined(__MINGW_MAJOR_VERSION) && __MINGW_MAJOR_VERSION >= 4 && \ + defined(__MSVCRT__) +# define _USE_32BIT_TIME_T +#endif + +/* The public SQLite interface. The _FILE_OFFSET_BITS macro must appear +** first in QNX. Also, the _USE_32BIT_TIME_T macro must appear first for +** MinGW. +*/ /************** Include sqlite3.h in the middle of sqliteInt.h ***************/ /************** Begin file sqlite3.h *****************************************/ /* ** 2001 September 15 ** @@ -590,25 +247,29 @@ extern "C" { #endif /* -** Add the ability to override 'extern' +** Provide the ability to override linkage features of the interface. */ #ifndef SQLITE_EXTERN # define SQLITE_EXTERN extern #endif - #ifndef SQLITE_API # define SQLITE_API #endif - +#ifndef SQLITE_CDECL +# define SQLITE_CDECL +#endif +#ifndef SQLITE_STDCALL +# define SQLITE_STDCALL +#endif /* ** These no-op macros are used in front of interfaces to mark those ** interfaces as either deprecated or experimental. New applications -** should not use deprecated interfaces - they are support for backwards +** should not use deprecated interfaces - they are supported for backwards ** compatibility only. Application writers should be aware that ** experimental interfaces are subject to change in point releases. ** ** These macros used to resolve to various kinds of compiler magic that ** would generate warning messages when they were used. But that @@ -654,13 +315,13 @@ ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.8.1" -#define SQLITE_VERSION_NUMBER 3008001 -#define SQLITE_SOURCE_ID "2013-09-16 12:57:19 daf6ba413cb3cb6065774ba07495eab4a28b49b0" +#define SQLITE_VERSION "3.8.9" +#define SQLITE_VERSION_NUMBER 3008009 +#define SQLITE_SOURCE_ID "2015-03-30 23:43:56 395bb3e677a6551b06ba96fc58c393132b93d1e8" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version, sqlite3_sourceid ** @@ -689,13 +350,13 @@ ** [SQLITE_SOURCE_ID] C preprocessor macro. ** ** See also: [sqlite_version()] and [sqlite_source_id()]. */ SQLITE_API const char sqlite3_version[] = SQLITE_VERSION; -SQLITE_API const char *sqlite3_libversion(void); -SQLITE_API const char *sqlite3_sourceid(void); -SQLITE_API int sqlite3_libversion_number(void); +SQLITE_API const char *SQLITE_STDCALL sqlite3_libversion(void); +SQLITE_API const char *SQLITE_STDCALL sqlite3_sourceid(void); +SQLITE_API int SQLITE_STDCALL sqlite3_libversion_number(void); /* ** CAPI3REF: Run-Time Library Compilation Options Diagnostics ** ** ^The sqlite3_compileoption_used() function returns 0 or 1 @@ -716,12 +377,12 @@ ** ** See also: SQL functions [sqlite_compileoption_used()] and ** [sqlite_compileoption_get()] and the [compile_options pragma]. */ #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS -SQLITE_API int sqlite3_compileoption_used(const char *zOptName); -SQLITE_API const char *sqlite3_compileoption_get(int N); +SQLITE_API int SQLITE_STDCALL sqlite3_compileoption_used(const char *zOptName); +SQLITE_API const char *SQLITE_STDCALL sqlite3_compileoption_get(int N); #endif /* ** CAPI3REF: Test To See If The Library Is Threadsafe ** @@ -748,19 +409,19 @@ ** This interface only reports on the compile-time mutex setting ** of the [SQLITE_THREADSAFE] flag. If SQLite is compiled with ** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but ** can be fully or partially disabled using a call to [sqlite3_config()] ** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD], -** or [SQLITE_CONFIG_MUTEX]. ^(The return value of the +** or [SQLITE_CONFIG_SERIALIZED]. ^(The return value of the ** sqlite3_threadsafe() function shows only the compile-time setting of ** thread safety, not any run-time changes to that setting made by ** sqlite3_config(). In other words, the return value from sqlite3_threadsafe() ** is unchanged by calls to sqlite3_config().)^ ** ** See the [threading mode] documentation for additional information. */ -SQLITE_API int sqlite3_threadsafe(void); +SQLITE_API int SQLITE_STDCALL sqlite3_threadsafe(void); /* ** CAPI3REF: Database Connection Handle ** KEYWORDS: {database connection} {database connections} ** @@ -816,19 +477,19 @@ /* ** CAPI3REF: Closing A Database Connection ** ** ^The sqlite3_close() and sqlite3_close_v2() routines are destructors ** for the [sqlite3] object. -** ^Calls to sqlite3_close() and sqlite3_close_v2() return SQLITE_OK if +** ^Calls to sqlite3_close() and sqlite3_close_v2() return [SQLITE_OK] if ** the [sqlite3] object is successfully destroyed and all associated ** resources are deallocated. ** ** ^If the database connection is associated with unfinalized prepared ** statements or unfinished sqlite3_backup objects then sqlite3_close() ** will leave the database connection open and return [SQLITE_BUSY]. ** ^If sqlite3_close_v2() is called with unfinalized prepared statements -** and unfinished sqlite3_backups, then the database connection becomes +** and/or unfinished sqlite3_backups, then the database connection becomes ** an unusable "zombie" which will automatically be deallocated when the ** last prepared statement is finalized or the last sqlite3_backup is ** finished. The sqlite3_close_v2() interface is intended for use with ** host languages that are garbage collected, and where the order in which ** destructors are called is arbitrary. @@ -837,11 +498,11 @@ ** [sqlite3_blob_close | close] all [BLOB handles], and ** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated ** with the [sqlite3] object prior to attempting to close the object. ^If ** sqlite3_close_v2() is called on a [database connection] that still has ** outstanding [prepared statements], [BLOB handles], and/or -** [sqlite3_backup] objects then it returns SQLITE_OK but the deallocation +** [sqlite3_backup] objects then it returns [SQLITE_OK] and the deallocation ** of resources is deferred until all [prepared statements], [BLOB handles], ** and [sqlite3_backup] objects are also destroyed. ** ** ^If an [sqlite3] object is destroyed while a transaction is open, ** the transaction is automatically rolled back. @@ -852,12 +513,12 @@ ** from [sqlite3_open()], [sqlite3_open16()], or ** [sqlite3_open_v2()], and not previously closed. ** ^Calling sqlite3_close() or sqlite3_close_v2() with a NULL pointer ** argument is a harmless no-op. */ -SQLITE_API int sqlite3_close(sqlite3*); -SQLITE_API int sqlite3_close_v2(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_close(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_close_v2(sqlite3*); /* ** The type for a callback function. ** This is legacy and deprecated. It is included for historical ** compatibility and is not documented. @@ -917,36 +578,34 @@ ** Restrictions: ** ** <ul> ** <li> The application must insure that the 1st parameter to sqlite3_exec() ** is a valid and open [database connection]. -** <li> The application must not close [database connection] specified by +** <li> The application must not close the [database connection] specified by ** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running. ** <li> The application must not modify the SQL statement text passed into ** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running. ** </ul> */ -SQLITE_API int sqlite3_exec( +SQLITE_API int SQLITE_STDCALL sqlite3_exec( sqlite3*, /* An open database */ const char *sql, /* SQL to be evaluated */ int (*callback)(void*,int,char**,char**), /* Callback function */ void *, /* 1st argument to callback */ char **errmsg /* Error msg written here */ ); /* ** CAPI3REF: Result Codes -** KEYWORDS: SQLITE_OK {error code} {error codes} -** KEYWORDS: {result code} {result codes} +** KEYWORDS: {result code definitions} ** ** Many SQLite functions return an integer result code from the set shown ** here in order to indicate success or failure. ** ** New error codes may be added in future versions of SQLite. ** -** See also: [SQLITE_IOERR_READ | extended result codes], -** [sqlite3_vtab_on_conflict()] [SQLITE_ROLLBACK | result codes]. +** See also: [extended result code definitions] */ #define SQLITE_OK 0 /* Successful result */ /* beginning-of-error-codes */ #define SQLITE_ERROR 1 /* SQL error or missing database */ #define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ @@ -980,30 +639,23 @@ #define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ /* end-of-error-codes */ /* ** CAPI3REF: Extended Result Codes -** KEYWORDS: {extended error code} {extended error codes} -** KEYWORDS: {extended result code} {extended result codes} +** KEYWORDS: {extended result code definitions} ** -** In its default configuration, SQLite API routines return one of 26 integer -** [SQLITE_OK | result codes]. However, experience has shown that many of +** In its default configuration, SQLite API routines return one of 30 integer +** [result codes]. However, experience has shown that many of ** these result codes are too coarse-grained. They do not provide as ** much information about problems as programmers might like. In an effort to ** address this, newer versions of SQLite (version 3.3.8 and later) include ** support for additional result codes that provide more detailed information -** about errors. The extended result codes are enabled or disabled +** about errors. These [extended result codes] are enabled or disabled ** on a per database connection basis using the -** [sqlite3_extended_result_codes()] API. -** -** Some of the available extended result codes are listed here. -** One may expect the number of extended result codes will be expand -** over time. Software that uses extended result codes should expect -** to see new result codes in future releases of SQLite. -** -** The SQLITE_OK result code will never be extended. It will always -** be exactly zero. +** [sqlite3_extended_result_codes()] API. Or, the extended code for +** the most recent error can be obtained using +** [sqlite3_extended_errcode()]. */ #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) #define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) @@ -1038,10 +690,11 @@ #define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8)) #define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8)) #define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) #define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8)) #define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3<<8)) +#define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4<<8)) #define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2<<8)) #define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1<<8)) #define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2<<8)) #define SQLITE_CONSTRAINT_FOREIGNKEY (SQLITE_CONSTRAINT | (3<<8)) #define SQLITE_CONSTRAINT_FUNCTION (SQLITE_CONSTRAINT | (4<<8)) @@ -1048,13 +701,15 @@ #define SQLITE_CONSTRAINT_NOTNULL (SQLITE_CONSTRAINT | (5<<8)) #define SQLITE_CONSTRAINT_PRIMARYKEY (SQLITE_CONSTRAINT | (6<<8)) #define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7<<8)) #define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) #define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) +#define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) #define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) #define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) +#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) /* ** CAPI3REF: Flags For File Open Operations ** ** These bit values are intended for use in the @@ -1104,11 +759,15 @@ ** information is written to disk in the same order as calls ** to xWrite(). The SQLITE_IOCAP_POWERSAFE_OVERWRITE property means that ** after reboot following a crash or power loss, the only bytes in a ** file that were written at the application level might have changed ** and that adjacent bytes, even bytes within the same sector are -** guaranteed to be unchanged. +** guaranteed to be unchanged. The SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN +** flag indicate that a file cannot be deleted when open. The +** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on +** read-only media and cannot be changed even by processes with +** elevated privileges. */ #define SQLITE_IOCAP_ATOMIC 0x00000001 #define SQLITE_IOCAP_ATOMIC512 0x00000002 #define SQLITE_IOCAP_ATOMIC1K 0x00000004 #define SQLITE_IOCAP_ATOMIC2K 0x00000008 @@ -1119,10 +778,11 @@ #define SQLITE_IOCAP_ATOMIC64K 0x00000100 #define SQLITE_IOCAP_SAFE_APPEND 0x00000200 #define SQLITE_IOCAP_SEQUENTIAL 0x00000400 #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 +#define SQLITE_IOCAP_IMMUTABLE 0x00002000 /* ** CAPI3REF: File Locking Levels ** ** SQLite uses one of these integer values as the second @@ -1225,11 +885,11 @@ ** write return values. Potential uses for xFileControl() might be ** functions to enable blocking locks with timeouts, to change the ** locking strategy (for example to use dot-file locks), to inquire ** about the status of a lock, or to break stale locks. The SQLite ** core reserves all opcodes less than 100 for its own use. -** A [SQLITE_FCNTL_LOCKSTATE | list of opcodes] less than 100 is available. +** A [file control opcodes | list of opcodes] less than 100 is available. ** Applications that define a custom xFileControl method should use opcodes ** greater than 100 to avoid conflicts. VFS implementations should ** return [SQLITE_NOTFOUND] for file control opcodes that they do not ** recognize. ** @@ -1298,23 +958,26 @@ /* Additional methods may be added in future releases */ }; /* ** CAPI3REF: Standard File Control Opcodes +** KEYWORDS: {file control opcodes} {file control opcode} ** ** These integer constants are opcodes for the xFileControl method ** of the [sqlite3_io_methods] object and for the [sqlite3_file_control()] ** interface. ** +** <ul> +** <li>[[SQLITE_FCNTL_LOCKSTATE]] ** The [SQLITE_FCNTL_LOCKSTATE] opcode is used for debugging. This ** opcode causes the xFileControl method to write the current state of ** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED], ** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE]) ** into an integer that the pArg argument points to. This capability -** is used during testing and only needs to be supported when SQLITE_TEST -** is defined. -** <ul> +** is used during testing and is only available when the SQLITE_TEST +** compile-time option is used. +** ** <li>[[SQLITE_FCNTL_SIZE_HINT]] ** The [SQLITE_FCNTL_SIZE_HINT] opcode is used by SQLite to give the VFS ** layer a hint of how large the database file will grow to be during the ** current transaction. This hint is not guaranteed to be accurate but it ** is often close. The underlying VFS might choose to preallocate database @@ -1335,19 +998,33 @@ ** to the [sqlite3_file] object associated with a particular database ** connection. See the [sqlite3_file_control()] documentation for ** additional information. ** ** <li>[[SQLITE_FCNTL_SYNC_OMITTED]] -** ^(The [SQLITE_FCNTL_SYNC_OMITTED] opcode is generated internally by -** SQLite and sent to all VFSes in place of a call to the xSync method -** when the database connection has [PRAGMA synchronous] set to OFF.)^ -** Some specialized VFSes need this signal in order to operate correctly -** when [PRAGMA synchronous | PRAGMA synchronous=OFF] is set, but most -** VFSes do not need this signal and should silently ignore this opcode. -** Applications should not call [sqlite3_file_control()] with this -** opcode as doing so may disrupt the operation of the specialized VFSes -** that do require it. +** No longer in use. +** +** <li>[[SQLITE_FCNTL_SYNC]] +** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and +** sent to the VFS immediately before the xSync method is invoked on a +** database file descriptor. Or, if the xSync method is not invoked +** because the user has configured SQLite with +** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place +** of the xSync method. In most cases, the pointer argument passed with +** this file-control is NULL. However, if the database file is being synced +** as part of a multi-database commit, the argument points to a nul-terminated +** string containing the transactions master-journal file name. VFSes that +** do not need this signal should silently ignore this opcode. Applications +** should not call [sqlite3_file_control()] with this opcode as doing so may +** disrupt the operation of the specialized VFSes that do require it. +** +** <li>[[SQLITE_FCNTL_COMMIT_PHASETWO]] +** The [SQLITE_FCNTL_COMMIT_PHASETWO] opcode is generated internally by SQLite +** and sent to the VFS after a transaction has been committed immediately +** but before the database is unlocked. VFSes that do not need this signal +** should silently ignore this opcode. Applications should not call +** [sqlite3_file_control()] with this opcode as doing so may disrupt the +** operation of the specialized VFSes that do require it. ** ** <li>[[SQLITE_FCNTL_WIN32_AV_RETRY]] ** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic ** retry counts and intervals for certain disk I/O operations for the ** windows [VFS] in order to provide robustness in the presence of @@ -1421,11 +1098,13 @@ ** the error message if the pragma fails. ^If the ** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal ** [PRAGMA] processing continues. ^If the [SQLITE_FCNTL_PRAGMA] ** file control returns [SQLITE_OK], then the parser assumes that the ** VFS has handled the PRAGMA itself and the parser generates a no-op -** prepared statement. ^If the [SQLITE_FCNTL_PRAGMA] file control returns +** prepared statement if result string is NULL, or that returns a copy +** of the result string if the string is non-NULL. +** ^If the [SQLITE_FCNTL_PRAGMA] file control returns ** any result code other than [SQLITE_OK] or [SQLITE_NOTFOUND], that means ** that the VFS encountered an error while handling the [PRAGMA] and the ** compilation of the PRAGMA fails with an error. ^The [SQLITE_FCNTL_PRAGMA] ** file control occurs at the beginning of pragma statement analysis and so ** it is able to override built-in [PRAGMA] statements. @@ -1459,16 +1138,43 @@ ** pointer is overwritten with the old value. The limit is not changed if ** the value originally pointed to is negative, and so the current limit ** can be queried by passing in a pointer to a negative number. This ** file-control is used internally to implement [PRAGMA mmap_size]. ** +** <li>[[SQLITE_FCNTL_TRACE]] +** The [SQLITE_FCNTL_TRACE] file control provides advisory information +** to the VFS about what the higher layers of the SQLite stack are doing. +** This file control is used by some VFS activity tracing [shims]. +** The argument is a zero-terminated string. Higher layers in the +** SQLite stack may generate instances of this file control if +** the [SQLITE_USE_FCNTL_TRACE] compile-time option is enabled. +** +** <li>[[SQLITE_FCNTL_HAS_MOVED]] +** The [SQLITE_FCNTL_HAS_MOVED] file control interprets its argument as a +** pointer to an integer and it writes a boolean into that integer depending +** on whether or not the file has been renamed, moved, or deleted since it +** was first opened. +** +** <li>[[SQLITE_FCNTL_WIN32_SET_HANDLE]] +** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This +** opcode causes the xFileControl method to swap the file handle with the one +** pointed to by the pArg argument. This capability is used during testing +** and only needs to be supported when SQLITE_TEST is defined. +** +** <li>[[SQLITE_FCNTL_WAL_BLOCK]] +** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might +** be advantageous to block on the next WAL lock if the lock is not immediately +** available. The WAL subsystem issues this signal during rare +** circumstances in order to fix a problem with priority inversion. +** Applications should <em>not</em> use this file-control. +** ** </ul> */ #define SQLITE_FCNTL_LOCKSTATE 1 -#define SQLITE_GET_LOCKPROXYFILE 2 -#define SQLITE_SET_LOCKPROXYFILE 3 -#define SQLITE_LAST_ERRNO 4 +#define SQLITE_FCNTL_GET_LOCKPROXYFILE 2 +#define SQLITE_FCNTL_SET_LOCKPROXYFILE 3 +#define SQLITE_FCNTL_LAST_ERRNO 4 #define SQLITE_FCNTL_SIZE_HINT 5 #define SQLITE_FCNTL_CHUNK_SIZE 6 #define SQLITE_FCNTL_FILE_POINTER 7 #define SQLITE_FCNTL_SYNC_OMITTED 8 #define SQLITE_FCNTL_WIN32_AV_RETRY 9 @@ -1478,10 +1184,22 @@ #define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 #define SQLITE_FCNTL_PRAGMA 14 #define SQLITE_FCNTL_BUSYHANDLER 15 #define SQLITE_FCNTL_TEMPFILENAME 16 #define SQLITE_FCNTL_MMAP_SIZE 18 +#define SQLITE_FCNTL_TRACE 19 +#define SQLITE_FCNTL_HAS_MOVED 20 +#define SQLITE_FCNTL_SYNC 21 +#define SQLITE_FCNTL_COMMIT_PHASETWO 22 +#define SQLITE_FCNTL_WIN32_SET_HANDLE 23 +#define SQLITE_FCNTL_WAL_BLOCK 24 + +/* deprecated names */ +#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE +#define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE +#define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO + /* ** CAPI3REF: Mutex Handle ** ** The mutex module within SQLite defines [sqlite3_mutex] to be an @@ -1729,11 +1447,11 @@ ** <li> SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED ** <li> SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE ** </ul> ** ** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as -** was given no the corresponding lock. +** was given on the corresponding lock. ** ** The xShmLock method can transition between unlocked and SHARED or ** between unlocked and EXCLUSIVE. It cannot transition between SHARED ** and EXCLUSIVE. */ @@ -1826,14 +1544,14 @@ ** sqlite3_os_init() and sqlite3_os_end(). An application-supplied ** implementation of sqlite3_os_init() or sqlite3_os_end() ** must return [SQLITE_OK] on success and some other [error code] upon ** failure. */ -SQLITE_API int sqlite3_initialize(void); -SQLITE_API int sqlite3_shutdown(void); -SQLITE_API int sqlite3_os_init(void); -SQLITE_API int sqlite3_os_end(void); +SQLITE_API int SQLITE_STDCALL sqlite3_initialize(void); +SQLITE_API int SQLITE_STDCALL sqlite3_shutdown(void); +SQLITE_API int SQLITE_STDCALL sqlite3_os_init(void); +SQLITE_API int SQLITE_STDCALL sqlite3_os_end(void); /* ** CAPI3REF: Configuring The SQLite Library ** ** The sqlite3_config() interface is used to make global configuration @@ -1860,11 +1578,11 @@ ** ** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK]. ** ^If the option is unknown or SQLite is unable to set the option ** then this routine returns a non-zero [error code]. */ -SQLITE_API int sqlite3_config(int, ...); +SQLITE_API int SQLITE_CDECL sqlite3_config(int, ...); /* ** CAPI3REF: Configure database connections ** ** The sqlite3_db_config() interface is used to make configuration @@ -1878,11 +1596,11 @@ ** Subsequent arguments vary depending on the configuration verb. ** ** ^Calls to sqlite3_db_config() return SQLITE_OK if and only if ** the call is considered successful. */ -SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...); +SQLITE_API int SQLITE_CDECL sqlite3_db_config(sqlite3*, int op, ...); /* ** CAPI3REF: Memory Allocation Routines ** ** An instance of this object defines the interface between SQLite @@ -1922,11 +1640,11 @@ ** of 8. Some allocators round up to a larger multiple or to a power of 2. ** Every memory allocation request coming in through [sqlite3_malloc()] ** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, ** that causes the corresponding memory allocation to fail. ** -** The xInit method initializes the memory allocator. (For example, +** The xInit method initializes the memory allocator. For example, ** it might allocate any require mutexes or initialize internal data ** structures. The xShutdown method is invoked (indirectly) by ** [sqlite3_shutdown()] and should deallocate any resources acquired ** by xInit. The pAppData pointer is used as the only parameter to ** xInit and xShutdown. @@ -2012,110 +1730,126 @@ ** it is not possible to set the Serialized [threading mode] and ** [sqlite3_config()] will return [SQLITE_ERROR] if called with the ** SQLITE_CONFIG_SERIALIZED configuration option.</dd> ** ** [[SQLITE_CONFIG_MALLOC]] <dt>SQLITE_CONFIG_MALLOC</dt> -** <dd> ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mem_methods] structure. The argument specifies +** <dd> ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is +** a pointer to an instance of the [sqlite3_mem_methods] structure. +** The argument specifies ** alternative low-level memory allocation routines to be used in place of ** the memory allocation routines built into SQLite.)^ ^SQLite makes ** its own private copy of the content of the [sqlite3_mem_methods] structure ** before the [sqlite3_config()] call returns.</dd> ** ** [[SQLITE_CONFIG_GETMALLOC]] <dt>SQLITE_CONFIG_GETMALLOC</dt> -** <dd> ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mem_methods] structure. The [sqlite3_mem_methods] +** <dd> ^(The SQLITE_CONFIG_GETMALLOC option takes a single argument which +** is a pointer to an instance of the [sqlite3_mem_methods] structure. +** The [sqlite3_mem_methods] ** structure is filled with the currently defined memory allocation routines.)^ ** This option can be used to overload the default memory allocation ** routines with a wrapper that simulations memory allocation failure or ** tracks memory usage, for example. </dd> ** ** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt> -** <dd> ^This option takes single argument of type int, interpreted as a -** boolean, which enables or disables the collection of memory allocation -** statistics. ^(When memory allocation statistics are disabled, the -** following SQLite interfaces become non-operational: +** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, +** interpreted as a boolean, which enables or disables the collection of +** memory allocation statistics. ^(When memory allocation statistics are +** disabled, the following SQLite interfaces become non-operational: ** <ul> ** <li> [sqlite3_memory_used()] ** <li> [sqlite3_memory_highwater()] ** <li> [sqlite3_soft_heap_limit64()] -** <li> [sqlite3_status()] +** <li> [sqlite3_status64()] ** </ul>)^ ** ^Memory allocation statistics are enabled by default unless SQLite is ** compiled with [SQLITE_DEFAULT_MEMSTATUS]=0 in which case memory ** allocation statistics are disabled by default. ** </dd> ** ** [[SQLITE_CONFIG_SCRATCH]] <dt>SQLITE_CONFIG_SCRATCH</dt> -** <dd> ^This option specifies a static memory buffer that SQLite can use for -** scratch memory. There are three arguments: A pointer an 8-byte +** <dd> ^The SQLITE_CONFIG_SCRATCH option specifies a static memory buffer +** that SQLite can use for scratch memory. ^(There are three arguments +** to SQLITE_CONFIG_SCRATCH: A pointer an 8-byte ** aligned memory buffer from which the scratch allocations will be ** drawn, the size of each scratch allocation (sz), -** and the maximum number of scratch allocations (N). The sz -** argument must be a multiple of 16. +** and the maximum number of scratch allocations (N).)^ ** The first argument must be a pointer to an 8-byte aligned buffer ** of at least sz*N bytes of memory. -** ^SQLite will use no more than two scratch buffers per thread. So -** N should be set to twice the expected maximum number of threads. -** ^SQLite will never require a scratch buffer that is more than 6 -** times the database page size. ^If SQLite needs needs additional +** ^SQLite will not use more than one scratch buffers per thread. +** ^SQLite will never request a scratch buffer that is more than 6 +** times the database page size. +** ^If SQLite needs needs additional ** scratch memory beyond what is provided by this configuration option, then -** [sqlite3_malloc()] will be used to obtain the memory needed.</dd> +** [sqlite3_malloc()] will be used to obtain the memory needed.<p> +** ^When the application provides any amount of scratch memory using +** SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary large +** [sqlite3_malloc|heap allocations]. +** This can help [Robson proof|prevent memory allocation failures] due to heap +** fragmentation in low-memory embedded systems. +** </dd> ** ** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt> -** <dd> ^This option specifies a static memory buffer that SQLite can use for -** the database page cache with the default page cache implementation. +** <dd> ^The SQLITE_CONFIG_PAGECACHE option specifies a static memory buffer +** that SQLite can use for the database page cache with the default page +** cache implementation. ** This configuration should not be used if an application-define page -** cache implementation is loaded using the SQLITE_CONFIG_PCACHE2 option. -** There are three arguments to this option: A pointer to 8-byte aligned +** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2] +** configuration option. +** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to +** 8-byte aligned ** memory, the size of each page buffer (sz), and the number of pages (N). ** The sz argument should be the size of the largest database page -** (a power of two between 512 and 32768) plus a little extra for each -** page header. ^The page header size is 20 to 40 bytes depending on -** the host architecture. ^It is harmless, apart from the wasted memory, -** to make sz a little too large. The first -** argument should point to an allocation of at least sz*N bytes of memory. +** (a power of two between 512 and 65536) plus some extra bytes for each +** page header. ^The number of extra bytes needed by the page header +** can be determined using the [SQLITE_CONFIG_PCACHE_HDRSZ] option +** to [sqlite3_config()]. +** ^It is harmless, apart from the wasted memory, +** for the sz parameter to be larger than necessary. The first +** argument should pointer to an 8-byte aligned block of memory that +** is at least sz*N bytes of memory, otherwise subsequent behavior is +** undefined. ** ^SQLite will use the memory provided by the first argument to satisfy its ** memory needs for the first N pages that it adds to cache. ^If additional ** page cache memory is needed beyond what is provided by this option, then -** SQLite goes to [sqlite3_malloc()] for the additional storage space. -** The pointer in the first argument must -** be aligned to an 8-byte boundary or subsequent behavior of SQLite -** will be undefined.</dd> +** SQLite goes to [sqlite3_malloc()] for the additional storage space.</dd> ** ** [[SQLITE_CONFIG_HEAP]] <dt>SQLITE_CONFIG_HEAP</dt> -** <dd> ^This option specifies a static memory buffer that SQLite will use -** for all of its dynamic memory allocation needs beyond those provided -** for by [SQLITE_CONFIG_SCRATCH] and [SQLITE_CONFIG_PAGECACHE]. -** There are three arguments: An 8-byte aligned pointer to the memory, +** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer +** that SQLite will use for all of its dynamic memory allocation needs +** beyond those provided for by [SQLITE_CONFIG_SCRATCH] and +** [SQLITE_CONFIG_PAGECACHE]. +** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled +** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns +** [SQLITE_ERROR] if invoked otherwise. +** ^There are three arguments to SQLITE_CONFIG_HEAP: +** An 8-byte aligned pointer to the memory, ** the number of bytes in the memory buffer, and the minimum allocation size. ** ^If the first pointer (the memory pointer) is NULL, then SQLite reverts ** to using its default memory allocator (the system malloc() implementation), ** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. ^If the -** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or -** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory +** memory pointer is not NULL then the alternative memory ** allocator is engaged to handle all of SQLites memory allocation needs. ** The first pointer (the memory pointer) must be aligned to an 8-byte ** boundary or subsequent behavior of SQLite will be undefined. ** The minimum allocation size is capped at 2**12. Reasonable values ** for the minimum allocation size are 2**5 through 2**8.</dd> ** ** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt> -** <dd> ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mutex_methods] structure. The argument specifies -** alternative low-level mutex routines to be used in place -** the mutex routines built into SQLite.)^ ^SQLite makes a copy of the -** content of the [sqlite3_mutex_methods] structure before the call to +** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a +** pointer to an instance of the [sqlite3_mutex_methods] structure. +** The argument specifies alternative low-level mutex routines to be used +** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of +** the content of the [sqlite3_mutex_methods] structure before the call to ** [sqlite3_config()] returns. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** the entire mutexing subsystem is omitted from the build and hence calls to ** [sqlite3_config()] with the SQLITE_CONFIG_MUTEX configuration option will ** return [SQLITE_ERROR].</dd> ** ** [[SQLITE_CONFIG_GETMUTEX]] <dt>SQLITE_CONFIG_GETMUTEX</dt> -** <dd> ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mutex_methods] structure. The +** <dd> ^(The SQLITE_CONFIG_GETMUTEX option takes a single argument which +** is a pointer to an instance of the [sqlite3_mutex_methods] structure. The ** [sqlite3_mutex_methods] ** structure is filled with the currently defined mutex routines.)^ ** This option can be used to overload the default mutex allocation ** routines with a wrapper used to track mutex usage for performance ** profiling or testing, for example. ^If SQLite is compiled with @@ -2123,29 +1857,29 @@ ** the entire mutexing subsystem is omitted from the build and hence calls to ** [sqlite3_config()] with the SQLITE_CONFIG_GETMUTEX configuration option will ** return [SQLITE_ERROR].</dd> ** ** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt> -** <dd> ^(This option takes two arguments that determine the default -** memory allocation for the lookaside memory allocator on each -** [database connection]. The first argument is the +** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine +** the default size of lookaside memory on each [database connection]. +** The first argument is the ** size of each lookaside buffer slot and the second is the number of -** slots allocated to each database connection.)^ ^(This option sets the -** <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] -** verb to [sqlite3_db_config()] can be used to change the lookaside +** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE +** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] +** option to [sqlite3_db_config()] can be used to change the lookaside ** configuration on individual connections.)^ </dd> ** ** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt> -** <dd> ^(This option takes a single argument which is a pointer to -** an [sqlite3_pcache_methods2] object. This object specifies the interface -** to a custom page cache implementation.)^ ^SQLite makes a copy of the -** object and uses it for page cache memory allocations.</dd> +** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is +** a pointer to an [sqlite3_pcache_methods2] object. This object specifies +** the interface to a custom page cache implementation.)^ +** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.</dd> ** ** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt> -** <dd> ^(This option takes a single argument which is a pointer to an -** [sqlite3_pcache_methods2] object. SQLite copies of the current -** page cache implementation into that object.)^ </dd> +** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which +** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of +** the current page cache implementation into that object.)^ </dd> ** ** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt> ** <dd> The SQLITE_CONFIG_LOG option is used to configure the SQLite ** global [error log]. ** (^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a @@ -2164,31 +1898,33 @@ ** supplied by the application must not invoke any SQLite interface. ** In a multi-threaded application, the application-defined logger ** function must be threadsafe. </dd> ** ** [[SQLITE_CONFIG_URI]] <dt>SQLITE_CONFIG_URI -** <dd> This option takes a single argument of type int. If non-zero, then -** URI handling is globally enabled. If the parameter is zero, then URI handling -** is globally disabled. If URI handling is globally enabled, all filenames -** passed to [sqlite3_open()], [sqlite3_open_v2()], [sqlite3_open16()] or +** <dd>^(The SQLITE_CONFIG_URI option takes a single argument of type int. +** If non-zero, then URI handling is globally enabled. If the parameter is zero, +** then URI handling is globally disabled.)^ ^If URI handling is globally +** enabled, all filenames passed to [sqlite3_open()], [sqlite3_open_v2()], +** [sqlite3_open16()] or ** specified as part of [ATTACH] commands are interpreted as URIs, regardless ** of whether or not the [SQLITE_OPEN_URI] flag is set when the database -** connection is opened. If it is globally disabled, filenames are +** connection is opened. ^If it is globally disabled, filenames are ** only interpreted as URIs if the SQLITE_OPEN_URI flag is set when the -** database connection is opened. By default, URI handling is globally +** database connection is opened. ^(By default, URI handling is globally ** disabled. The default value may be changed by compiling with the -** [SQLITE_USE_URI] symbol defined. +** [SQLITE_USE_URI] symbol defined.)^ ** ** [[SQLITE_CONFIG_COVERING_INDEX_SCAN]] <dt>SQLITE_CONFIG_COVERING_INDEX_SCAN -** <dd> This option takes a single integer argument which is interpreted as -** a boolean in order to enable or disable the use of covering indices for -** full table scans in the query optimizer. The default setting is determined +** <dd>^The SQLITE_CONFIG_COVERING_INDEX_SCAN option takes a single integer +** argument which is interpreted as a boolean in order to enable or disable +** the use of covering indices for full table scans in the query optimizer. +** ^The default setting is determined ** by the [SQLITE_ALLOW_COVERING_INDEX_SCAN] compile-time option, or is "on" ** if that compile-time option is omitted. ** The ability to disable the use of covering indices for full table scans ** is because some incorrectly coded legacy applications might malfunction -** malfunction when the optimization is enabled. Providing the ability to +** when the optimization is enabled. Providing the ability to ** disable the optimization allows the older, buggy application code to work ** without change even with newer versions of SQLite. ** ** [[SQLITE_CONFIG_PCACHE]] [[SQLITE_CONFIG_GETPCACHE]] ** <dt>SQLITE_CONFIG_PCACHE and SQLITE_CONFIG_GETPCACHE @@ -2213,21 +1949,47 @@ ** configuration option can be seen in the "test_sqllog.c" source file in ** the canonical SQLite source tree.</dd> ** ** [[SQLITE_CONFIG_MMAP_SIZE]] ** <dt>SQLITE_CONFIG_MMAP_SIZE -** <dd>SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (sqlite3_int64) values +** <dd>^SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (sqlite3_int64) values ** that are the default mmap size limit (the default setting for ** [PRAGMA mmap_size]) and the maximum allowed mmap size limit. -** The default setting can be overridden by each database connection using +** ^The default setting can be overridden by each database connection using ** either the [PRAGMA mmap_size] command, or by using the -** [SQLITE_FCNTL_MMAP_SIZE] file control. The maximum allowed mmap size -** cannot be changed at run-time. Nor may the maximum allowed mmap size -** exceed the compile-time maximum mmap size set by the -** [SQLITE_MAX_MMAP_SIZE] compile-time option. -** If either argument to this option is negative, then that argument is +** [SQLITE_FCNTL_MMAP_SIZE] file control. ^(The maximum allowed mmap size +** will be silently truncated if necessary so that it does not exceed the +** compile-time maximum mmap size set by the +** [SQLITE_MAX_MMAP_SIZE] compile-time option.)^ +** ^If either argument to this option is negative, then that argument is ** changed to its compile-time default. +** +** [[SQLITE_CONFIG_WIN32_HEAPSIZE]] +** <dt>SQLITE_CONFIG_WIN32_HEAPSIZE +** <dd>^The SQLITE_CONFIG_WIN32_HEAPSIZE option is only available if SQLite is +** compiled for Windows with the [SQLITE_WIN32_MALLOC] pre-processor macro +** defined. ^SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value +** that specifies the maximum size of the created heap. +** +** [[SQLITE_CONFIG_PCACHE_HDRSZ]] +** <dt>SQLITE_CONFIG_PCACHE_HDRSZ +** <dd>^The SQLITE_CONFIG_PCACHE_HDRSZ option takes a single parameter which +** is a pointer to an integer and writes into that integer the number of extra +** bytes per page required for each page in [SQLITE_CONFIG_PAGECACHE]. +** The amount of extra space required can change depending on the compiler, +** target platform, and SQLite version. +** +** [[SQLITE_CONFIG_PMASZ]] +** <dt>SQLITE_CONFIG_PMASZ +** <dd>^The SQLITE_CONFIG_PMASZ option takes a single parameter which +** is an unsigned integer and sets the "Minimum PMA Size" for the multithreaded +** sorter to that integer. The default minimum PMA Size is set by the +** [SQLITE_SORTER_PMASZ] compile-time option. New threads are launched +** to help with sort operations when multithreaded sorting +** is enabled (using the [PRAGMA threads] command) and the amount of content +** to be sorted exceeds the page size times the minimum of the +** [PRAGMA cache_size] setting and this value. ** </dl> */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ #define SQLITE_CONFIG_SERIALIZED 3 /* nil */ @@ -2248,10 +2010,13 @@ #define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ #define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ #define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */ #define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ #define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ +#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */ +#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */ +#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */ /* ** CAPI3REF: Database Connection Configuration Options ** ** These constants are the available integer configuration options that @@ -2319,28 +2084,30 @@ ** ** ^The sqlite3_extended_result_codes() routine enables or disables the ** [extended result codes] feature of SQLite. ^The extended result ** codes are disabled by default for historical compatibility. */ -SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); +SQLITE_API int SQLITE_STDCALL sqlite3_extended_result_codes(sqlite3*, int onoff); /* ** CAPI3REF: Last Insert Rowid ** -** ^Each entry in an SQLite table has a unique 64-bit signed +** ^Each entry in most SQLite tables (except for [WITHOUT ROWID] tables) +** has a unique 64-bit signed ** integer key called the [ROWID | "rowid"]. ^The rowid is always available ** as an undeclared column named ROWID, OID, or _ROWID_ as long as those ** names are not also used by explicitly declared columns. ^If ** the table has a column of type [INTEGER PRIMARY KEY] then that column ** is another alias for the rowid. ** -** ^This routine returns the [rowid] of the most recent -** successful [INSERT] into the database from the [database connection] -** in the first argument. ^As of SQLite version 3.7.7, this routines -** records the last insert rowid of both ordinary tables and [virtual tables]. -** ^If no successful [INSERT]s -** have ever occurred on that database connection, zero is returned. +** ^The sqlite3_last_insert_rowid(D) interface returns the [rowid] of the +** most recent successful [INSERT] into a rowid table or [virtual table] +** on database connection D. +** ^Inserts into [WITHOUT ROWID] tables are not recorded. +** ^If no successful [INSERT]s into rowid tables +** have ever occurred on the database connection D, +** then sqlite3_last_insert_rowid(D) returns zero. ** ** ^(If an [INSERT] occurs within a trigger or within a [virtual table] ** method, then this routine will return the [rowid] of the inserted ** row as long as the trigger or virtual table method is running. ** But once the trigger or virtual table method ends, the value returned @@ -2368,91 +2135,86 @@ ** function is running and thus changes the last insert [rowid], ** then the value returned by [sqlite3_last_insert_rowid()] is ** unpredictable and might not equal either the old or the new ** last insert [rowid]. */ -SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_last_insert_rowid(sqlite3*); /* ** CAPI3REF: Count The Number Of Rows Modified ** -** ^This function returns the number of database rows that were changed -** or inserted or deleted by the most recently completed SQL statement -** on the [database connection] specified by the first parameter. -** ^(Only changes that are directly specified by the [INSERT], [UPDATE], -** or [DELETE] statement are counted. Auxiliary changes caused by -** triggers or [foreign key actions] are not counted.)^ Use the -** [sqlite3_total_changes()] function to find the total number of changes -** including changes caused by triggers and foreign key actions. -** -** ^Changes to a view that are simulated by an [INSTEAD OF trigger] -** are not counted. Only real table changes are counted. -** -** ^(A "row change" is a change to a single row of a single table -** caused by an INSERT, DELETE, or UPDATE statement. Rows that -** are changed as side effects of [REPLACE] constraint resolution, -** rollback, ABORT processing, [DROP TABLE], or by any other -** mechanisms do not count as direct row changes.)^ -** -** A "trigger context" is a scope of execution that begins and -** ends with the script of a [CREATE TRIGGER | trigger]. -** Most SQL statements are -** evaluated outside of any trigger. This is the "top level" -** trigger context. If a trigger fires from the top level, a -** new trigger context is entered for the duration of that one -** trigger. Subtriggers create subcontexts for their duration. -** -** ^Calling [sqlite3_exec()] or [sqlite3_step()] recursively does -** not create a new trigger context. -** -** ^This function returns the number of direct row changes in the -** most recent INSERT, UPDATE, or DELETE statement within the same -** trigger context. -** -** ^Thus, when called from the top level, this function returns the -** number of changes in the most recent INSERT, UPDATE, or DELETE -** that also occurred at the top level. ^(Within the body of a trigger, -** the sqlite3_changes() interface can be called to find the number of -** changes in the most recently completed INSERT, UPDATE, or DELETE -** statement within the body of the same trigger. -** However, the number returned does not include changes -** caused by subtriggers since those have their own context.)^ +** ^This function returns the number of rows modified, inserted or +** deleted by the most recently completed INSERT, UPDATE or DELETE +** statement on the database connection specified by the only parameter. +** ^Executing any other type of SQL statement does not modify the value +** returned by this function. +** +** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are +** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], +** [foreign key actions] or [REPLACE] constraint resolution are not counted. +** +** Changes to a view that are intercepted by +** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value +** returned by sqlite3_changes() immediately after an INSERT, UPDATE or +** DELETE statement run on a view is always zero. Only changes made to real +** tables are counted. +** +** Things are more complicated if the sqlite3_changes() function is +** executed while a trigger program is running. This may happen if the +** program uses the [changes() SQL function], or if some other callback +** function invokes sqlite3_changes() directly. Essentially: +** +** <ul> +** <li> ^(Before entering a trigger program the value returned by +** sqlite3_changes() function is saved. After the trigger program +** has finished, the original value is restored.)^ +** +** <li> ^(Within a trigger program each INSERT, UPDATE and DELETE +** statement sets the value returned by sqlite3_changes() +** upon completion as normal. Of course, this value will not include +** any changes performed by sub-triggers, as the sqlite3_changes() +** value will be saved and restored after each sub-trigger has run.)^ +** </ul> +** +** ^This means that if the changes() SQL function (or similar) is used +** by the first INSERT, UPDATE or DELETE statement within a trigger, it +** returns the value as set when the calling statement began executing. +** ^If it is used by the second or subsequent such statement within a trigger +** program, the value returned reflects the number of rows modified by the +** previous INSERT, UPDATE or DELETE statement within the same trigger. ** ** See also the [sqlite3_total_changes()] interface, the ** [count_changes pragma], and the [changes() SQL function]. ** ** If a separate thread makes changes on the same database connection ** while [sqlite3_changes()] is running then the value returned ** is unpredictable and not meaningful. */ -SQLITE_API int sqlite3_changes(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_changes(sqlite3*); /* ** CAPI3REF: Total Number Of Rows Modified ** -** ^This function returns the number of row changes caused by [INSERT], -** [UPDATE] or [DELETE] statements since the [database connection] was opened. -** ^(The count returned by sqlite3_total_changes() includes all changes -** from all [CREATE TRIGGER | trigger] contexts and changes made by -** [foreign key actions]. However, -** the count does not include changes used to implement [REPLACE] constraints, -** do rollbacks or ABORT processing, or [DROP TABLE] processing. The -** count does not include rows of views that fire an [INSTEAD OF trigger], -** though if the INSTEAD OF trigger makes changes of its own, those changes -** are counted.)^ -** ^The sqlite3_total_changes() function counts the changes as soon as -** the statement that makes them is completed (when the statement handle -** is passed to [sqlite3_reset()] or [sqlite3_finalize()]). -** +** ^This function returns the total number of rows inserted, modified or +** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed +** since the database connection was opened, including those executed as +** part of trigger programs. ^Executing any other type of SQL statement +** does not affect the value returned by sqlite3_total_changes(). +** +** ^Changes made as part of [foreign key actions] are included in the +** count, but those made as part of REPLACE constraint resolution are +** not. ^Changes to a view that are intercepted by INSTEAD OF triggers +** are not counted. +** ** See also the [sqlite3_changes()] interface, the ** [count_changes pragma], and the [total_changes() SQL function]. ** ** If a separate thread makes changes on the same database connection ** while [sqlite3_total_changes()] is running then the value ** returned is unpredictable and not meaningful. */ -SQLITE_API int sqlite3_total_changes(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_total_changes(sqlite3*); /* ** CAPI3REF: Interrupt A Long-Running Query ** ** ^This function causes any pending database operation to abort and @@ -2487,11 +2249,11 @@ ** that are started after the sqlite3_interrupt() call returns. ** ** If the database connection closes while [sqlite3_interrupt()] ** is running then bad things will likely happen. */ -SQLITE_API void sqlite3_interrupt(sqlite3*); +SQLITE_API void SQLITE_STDCALL sqlite3_interrupt(sqlite3*); /* ** CAPI3REF: Determine If An SQL Statement Is Complete ** ** These routines are useful during command-line input to determine if the @@ -2522,37 +2284,44 @@ ** UTF-8 string. ** ** The input to [sqlite3_complete16()] must be a zero-terminated ** UTF-16 string in native byte order. */ -SQLITE_API int sqlite3_complete(const char *sql); -SQLITE_API int sqlite3_complete16(const void *sql); +SQLITE_API int SQLITE_STDCALL sqlite3_complete(const char *sql); +SQLITE_API int SQLITE_STDCALL sqlite3_complete16(const void *sql); /* ** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors +** KEYWORDS: {busy-handler callback} {busy handler} ** -** ^This routine sets a callback function that might be invoked whenever -** an attempt is made to open a database table that another thread -** or process has locked. +** ^The sqlite3_busy_handler(D,X,P) routine sets a callback function X +** that might be invoked with argument P whenever +** an attempt is made to access a database table associated with +** [database connection] D when another thread +** or process has the table locked. +** The sqlite3_busy_handler() interface is used to implement +** [sqlite3_busy_timeout()] and [PRAGMA busy_timeout]. ** -** ^If the busy callback is NULL, then [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] +** ^If the busy callback is NULL, then [SQLITE_BUSY] ** is returned immediately upon encountering the lock. ^If the busy callback ** is not NULL, then the callback might be invoked with two arguments. ** ** ^The first argument to the busy handler is a copy of the void* pointer which ** is the third argument to sqlite3_busy_handler(). ^The second argument to ** the busy handler callback is the number of times that the busy handler has -** been invoked for this locking event. ^If the +** been invoked previously for the same locking event. ^If the ** busy callback returns 0, then no additional attempts are made to -** access the database and [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] is returned. +** access the database and [SQLITE_BUSY] is returned +** to the application. ** ^If the callback returns non-zero, then another attempt -** is made to open the database for reading and the cycle repeats. +** is made to access the database and the cycle repeats. ** ** The presence of a busy handler does not guarantee that it will be invoked ** when there is lock contention. ^If SQLite determines that invoking the busy ** handler could result in a deadlock, it will go ahead and return [SQLITE_BUSY] -** or [SQLITE_IOERR_BLOCKED] instead of invoking the busy handler. +** to the application instead of invoking the +** busy handler. ** Consider a scenario where one process is holding a read lock that ** it is trying to promote to a reserved lock and ** a second process is holding a reserved lock that it is trying ** to promote to an exclusive lock. The first process cannot proceed ** because it is blocked by the second and the second process cannot @@ -2562,58 +2331,47 @@ ** will induce the first process to release its read lock and allow ** the second process to proceed. ** ** ^The default busy callback is NULL. ** -** ^The [SQLITE_BUSY] error is converted to [SQLITE_IOERR_BLOCKED] -** when SQLite is in the middle of a large transaction where all the -** changes will not fit into the in-memory cache. SQLite will -** already hold a RESERVED lock on the database file, but it needs -** to promote this lock to EXCLUSIVE so that it can spill cache -** pages into the database file without harm to concurrent -** readers. ^If it is unable to promote the lock, then the in-memory -** cache will be left in an inconsistent state and so the error -** code is promoted from the relatively benign [SQLITE_BUSY] to -** the more severe [SQLITE_IOERR_BLOCKED]. ^This error code promotion -** forces an automatic rollback of the changes. See the -** <a href="/cvstrac/wiki?p=CorruptionFollowingBusyError"> -** CorruptionFollowingBusyError</a> wiki page for a discussion of why -** this is important. -** ** ^(There can only be a single busy handler defined for each ** [database connection]. Setting a new busy handler clears any ** previously set handler.)^ ^Note that calling [sqlite3_busy_timeout()] -** will also set or clear the busy handler. +** or evaluating [PRAGMA busy_timeout=N] will change the +** busy handler and thus clear any previously set busy handler. ** ** The busy callback should not take any actions which modify the -** database connection that invoked the busy handler. Any such actions +** database connection that invoked the busy handler. In other words, +** the busy handler is not reentrant. Any such actions ** result in undefined behavior. ** ** A busy handler must not close the database connection ** or [prepared statement] that invoked the busy handler. */ -SQLITE_API int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*); +SQLITE_API int SQLITE_STDCALL sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*); /* ** CAPI3REF: Set A Busy Timeout ** ** ^This routine sets a [sqlite3_busy_handler | busy handler] that sleeps ** for a specified amount of time when a table is locked. ^The handler ** will sleep multiple times until at least "ms" milliseconds of sleeping ** have accumulated. ^After at least "ms" milliseconds of sleeping, ** the handler returns 0 which causes [sqlite3_step()] to return -** [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED]. +** [SQLITE_BUSY]. ** ** ^Calling this routine with an argument less than or equal to zero ** turns off all busy handlers. ** ** ^(There can only be a single busy handler for a particular -** [database connection] any any given moment. If another busy handler +** [database connection] at any given moment. If another busy handler ** was defined (using [sqlite3_busy_handler()]) prior to calling ** this routine, that other busy handler is cleared.)^ +** +** See also: [PRAGMA busy_timeout] */ -SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); +SQLITE_API int SQLITE_STDCALL sqlite3_busy_timeout(sqlite3*, int ms); /* ** CAPI3REF: Convenience Routines For Running Queries ** ** This is a legacy interface that is preserved for backwards compatibility. @@ -2683,25 +2441,29 @@ ** interface defined here. As a consequence, errors that occur in the ** wrapper layer outside of the internal [sqlite3_exec()] call are not ** reflected in subsequent calls to [sqlite3_errcode()] or ** [sqlite3_errmsg()]. */ -SQLITE_API int sqlite3_get_table( +SQLITE_API int SQLITE_STDCALL sqlite3_get_table( sqlite3 *db, /* An open database */ const char *zSql, /* SQL to be evaluated */ char ***pazResult, /* Results of the query */ int *pnRow, /* Number of result rows written here */ int *pnColumn, /* Number of result columns written here */ char **pzErrmsg /* Error msg written here */ ); -SQLITE_API void sqlite3_free_table(char **result); +SQLITE_API void SQLITE_STDCALL sqlite3_free_table(char **result); /* ** CAPI3REF: Formatted String Printing Functions ** ** These routines are work-alikes of the "printf()" family of functions ** from the standard C library. +** These routines understand most of the common K&R formatting options, +** plus some additional non-standard formats, detailed below. +** Note that some of the more obscure formatting options from recent +** C-library standards are omitted from this implementation. ** ** ^The sqlite3_mprintf() and sqlite3_vmprintf() routines write their ** results into memory obtained from [sqlite3_malloc()]. ** The strings returned by these two routines should be ** released by [sqlite3_free()]. ^Both routines return a @@ -2730,11 +2492,11 @@ ** ^The sqlite3_vsnprintf() routine is a varargs version of sqlite3_snprintf(). ** ** These routines all implement some additional formatting ** options that are useful for constructing SQL statements. ** All of the usual printf() formatting options apply. In addition, there -** is are "%q", "%Q", and "%z" options. +** is are "%q", "%Q", "%w" and "%z" options. ** ** ^(The %q option works like %s in that it substitutes a nul-terminated ** string from the argument list. But %q also doubles every '\'' character. ** %q is designed for use inside a string literal.)^ By doubling each '\'' ** character it escapes that character and allows it to be inserted into @@ -2782,19 +2544,25 @@ ** sqlite3_free(zSQL); ** </pre></blockquote> ** ** The code above will render a correct SQL statement in the zSQL ** variable even if the zText variable is a NULL pointer. +** +** ^(The "%w" formatting option is like "%q" except that it expects to +** be contained within double-quotes instead of single quotes, and it +** escapes the double-quote character instead of the single-quote +** character.)^ The "%w" formatting option is intended for safely inserting +** table and column names into a constructed SQL statement. ** ** ^(The "%z" formatting option works like "%s" but with the ** addition that after the string has been read and copied into ** the result, [sqlite3_free()] is called on the input string.)^ */ -SQLITE_API char *sqlite3_mprintf(const char*,...); -SQLITE_API char *sqlite3_vmprintf(const char*, va_list); -SQLITE_API char *sqlite3_snprintf(int,char*,const char*, ...); -SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list); +SQLITE_API char *SQLITE_CDECL sqlite3_mprintf(const char*,...); +SQLITE_API char *SQLITE_STDCALL sqlite3_vmprintf(const char*, va_list); +SQLITE_API char *SQLITE_CDECL sqlite3_snprintf(int,char*,const char*, ...); +SQLITE_API char *SQLITE_STDCALL sqlite3_vsnprintf(int,char*,const char*, va_list); /* ** CAPI3REF: Memory Allocation Subsystem ** ** The SQLite core uses these three routines for all of its own @@ -2806,10 +2574,14 @@ ** of memory at least N bytes in length, where N is the parameter. ** ^If sqlite3_malloc() is unable to obtain sufficient free ** memory, it returns a NULL pointer. ^If the parameter N to ** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns ** a NULL pointer. +** +** ^The sqlite3_malloc64(N) routine works just like +** sqlite3_malloc(N) except that N is an unsigned 64-bit integer instead +** of a signed 32-bit integer. ** ** ^Calling sqlite3_free() with a pointer previously returned ** by sqlite3_malloc() or sqlite3_realloc() releases that memory so ** that it might be reused. ^The sqlite3_free() routine is ** a no-op if is called with a NULL pointer. Passing a NULL pointer @@ -2818,28 +2590,42 @@ ** memory might result in a segmentation fault or other severe error. ** Memory corruption, a segmentation fault, or other severe error ** might result if sqlite3_free() is called with a non-NULL pointer that ** was not obtained from sqlite3_malloc() or sqlite3_realloc(). ** -** ^(The sqlite3_realloc() interface attempts to resize a -** prior memory allocation to be at least N bytes, where N is the -** second parameter. The memory allocation to be resized is the first -** parameter.)^ ^ If the first parameter to sqlite3_realloc() +** ^The sqlite3_realloc(X,N) interface attempts to resize a +** prior memory allocation X to be at least N bytes. +** ^If the X parameter to sqlite3_realloc(X,N) ** is a NULL pointer then its behavior is identical to calling -** sqlite3_malloc(N) where N is the second parameter to sqlite3_realloc(). -** ^If the second parameter to sqlite3_realloc() is zero or +** sqlite3_malloc(N). +** ^If the N parameter to sqlite3_realloc(X,N) is zero or ** negative then the behavior is exactly the same as calling -** sqlite3_free(P) where P is the first parameter to sqlite3_realloc(). -** ^sqlite3_realloc() returns a pointer to a memory allocation -** of at least N bytes in size or NULL if sufficient memory is unavailable. +** sqlite3_free(X). +** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation +** of at least N bytes in size or NULL if insufficient memory is available. ** ^If M is the size of the prior allocation, then min(N,M) bytes ** of the prior allocation are copied into the beginning of buffer returned -** by sqlite3_realloc() and the prior allocation is freed. -** ^If sqlite3_realloc() returns NULL, then the prior allocation -** is not freed. +** by sqlite3_realloc(X,N) and the prior allocation is freed. +** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the +** prior allocation is not freed. ** -** ^The memory returned by sqlite3_malloc() and sqlite3_realloc() +** ^The sqlite3_realloc64(X,N) interfaces works the same as +** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead +** of a 32-bit signed integer. +** +** ^If X is a memory allocation previously obtained from sqlite3_malloc(), +** sqlite3_malloc64(), sqlite3_realloc(), or sqlite3_realloc64(), then +** sqlite3_msize(X) returns the size of that memory allocation in bytes. +** ^The value returned by sqlite3_msize(X) might be larger than the number +** of bytes requested when X was allocated. ^If X is a NULL pointer then +** sqlite3_msize(X) returns zero. If X points to something that is not +** the beginning of memory allocation, or if it points to a formerly +** valid memory allocation that has now been freed, then the behavior +** of sqlite3_msize(X) is undefined and possibly harmful. +** +** ^The memory returned by sqlite3_malloc(), sqlite3_realloc(), +** sqlite3_malloc64(), and sqlite3_realloc64() ** is always aligned to at least an 8 byte boundary, or to a ** 4 byte boundary if the [SQLITE_4_BYTE_ALIGNED_MALLOC] compile-time ** option is used. ** ** In SQLite version 3.5.0 and 3.5.1, it was possible to define @@ -2862,13 +2648,16 @@ ** ** The application must not read or write any part of ** a block of memory after it has been released using ** [sqlite3_free()] or [sqlite3_realloc()]. */ -SQLITE_API void *sqlite3_malloc(int); -SQLITE_API void *sqlite3_realloc(void*, int); -SQLITE_API void sqlite3_free(void*); +SQLITE_API void *SQLITE_STDCALL sqlite3_malloc(int); +SQLITE_API void *SQLITE_STDCALL sqlite3_malloc64(sqlite3_uint64); +SQLITE_API void *SQLITE_STDCALL sqlite3_realloc(void*, int); +SQLITE_API void *SQLITE_STDCALL sqlite3_realloc64(void*, sqlite3_uint64); +SQLITE_API void SQLITE_STDCALL sqlite3_free(void*); +SQLITE_API sqlite3_uint64 SQLITE_STDCALL sqlite3_msize(void*); /* ** CAPI3REF: Memory Allocator Statistics ** ** SQLite provides these two interfaces for reporting on the status @@ -2889,12 +2678,12 @@ ** [sqlite3_memory_used()] if and only if the parameter to ** [sqlite3_memory_highwater()] is true. ^The value returned ** by [sqlite3_memory_highwater(1)] is the high-water mark ** prior to the reset. */ -SQLITE_API sqlite3_int64 sqlite3_memory_used(void); -SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_memory_used(void); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_memory_highwater(int resetFlag); /* ** CAPI3REF: Pseudo-Random Number Generator ** ** SQLite contains a high-quality pseudo-random number generator (PRNG) used to @@ -2902,19 +2691,22 @@ ** already uses the largest possible [ROWID]. The PRNG is also used for ** the build-in random() and randomblob() SQL functions. This interface allows ** applications to access the same PRNG for other purposes. ** ** ^A call to this routine stores N bytes of randomness into buffer P. +** ^The P parameter can be a NULL pointer. ** -** ^The first time this routine is invoked (either internally or by -** the application) the PRNG is seeded using randomness obtained -** from the xRandomness method of the default [sqlite3_vfs] object. -** ^On all subsequent invocations, the pseudo-randomness is generated +** ^If this routine has not been previously called or if the previous +** call had N less than one or a NULL pointer for P, then the PRNG is +** seeded using randomness obtained from the xRandomness method of +** the default [sqlite3_vfs] object. +** ^If the previous call to this routine had an N of 1 or more and a +** non-NULL P then the pseudo-randomness is generated ** internally and without recourse to the [sqlite3_vfs] xRandomness ** method. */ -SQLITE_API void sqlite3_randomness(int N, void *P); +SQLITE_API void SQLITE_STDCALL sqlite3_randomness(int N, void *P); /* ** CAPI3REF: Compile-Time Authorization Callbacks ** ** ^This routine registers an authorizer callback with a particular @@ -2992,11 +2784,11 @@ ** [sqlite3_prepare()] or its variants. Authorization is not ** performed during statement evaluation in [sqlite3_step()], unless ** as stated in the previous paragraph, sqlite3_step() invokes ** sqlite3_prepare_v2() to reprepare a statement after a schema change. */ -SQLITE_API int sqlite3_set_authorizer( +SQLITE_API int SQLITE_STDCALL sqlite3_set_authorizer( sqlite3*, int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), void *pUserData ); @@ -3007,12 +2799,12 @@ ** return either [SQLITE_OK] or one of these two constants in order ** to signal SQLite whether or not the action is permitted. See the ** [sqlite3_set_authorizer | authorizer documentation] for additional ** information. ** -** Note that SQLITE_IGNORE is also used as a [SQLITE_ROLLBACK | return code] -** from the [sqlite3_vtab_on_conflict()] interface. +** Note that SQLITE_IGNORE is also used as a [conflict resolution mode] +** returned from the [sqlite3_vtab_on_conflict()] interface. */ #define SQLITE_DENY 1 /* Abort the SQL statement with an error */ #define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */ /* @@ -3066,10 +2858,11 @@ #define SQLITE_CREATE_VTABLE 29 /* Table Name Module Name */ #define SQLITE_DROP_VTABLE 30 /* Table Name Module Name */ #define SQLITE_FUNCTION 31 /* NULL Function Name */ #define SQLITE_SAVEPOINT 32 /* Operation Savepoint Name */ #define SQLITE_COPY 0 /* No longer used */ +#define SQLITE_RECURSIVE 33 /* NULL NULL */ /* ** CAPI3REF: Tracing And Profiling Functions ** ** These routines register callback functions that can be used for @@ -3095,12 +2888,12 @@ ** digits in the time are meaningless. Future versions of SQLite ** might provide greater resolution on the profiler callback. The ** sqlite3_profile() function is considered experimental and is ** subject to change in future versions of SQLite. */ -SQLITE_API void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*); -SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_profile(sqlite3*, +SQLITE_API void *SQLITE_STDCALL sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*); +SQLITE_API SQLITE_EXPERIMENTAL void *SQLITE_STDCALL sqlite3_profile(sqlite3*, void(*xProfile)(void*,const char*,sqlite3_uint64), void*); /* ** CAPI3REF: Query Progress Callbacks ** @@ -3130,11 +2923,11 @@ ** the database connection that invoked the progress handler. ** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their ** database connections for the meaning of "modify" in this paragraph. ** */ -SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); +SQLITE_API void SQLITE_STDCALL sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); /* ** CAPI3REF: Opening A New Database Connection ** ** ^These routines open an SQLite database file as specified by the @@ -3148,13 +2941,13 @@ ** [SQLITE_OK] is returned. Otherwise an [error code] is returned.)^ ^The ** [sqlite3_errmsg()] or [sqlite3_errmsg16()] routines can be used to obtain ** an English language description of the error following a failure of any ** of the sqlite3_open() routines. ** -** ^The default encoding for the database will be UTF-8 if -** sqlite3_open() or sqlite3_open_v2() is called and -** UTF-16 in the native byte order if sqlite3_open16() is used. +** ^The default encoding will be UTF-8 for databases created using +** sqlite3_open() or sqlite3_open_v2(). ^The default encoding for databases +** created using sqlite3_open16() will be UTF-16 in the native byte order. ** ** Whether or not an error occurs when it is opened, resources ** associated with the [database connection] handle should be released by ** passing it to [sqlite3_close()] when it is no longer required. ** @@ -3238,17 +3031,18 @@ ** ^SQLite uses the path component of the URI as the name of the disk file ** which contains the database. ^If the path begins with a '/' character, ** then it is interpreted as an absolute path. ^If the path does not begin ** with a '/' (meaning that the authority section is omitted from the URI) ** then the path is interpreted as a relative path. -** ^On windows, the first component of an absolute path -** is a drive specification (e.g. "C:"). +** ^(On windows, the first component of an absolute path +** is a drive specification (e.g. "C:").)^ ** ** [[core URI query parameters]] ** The query component of a URI may contain parameters that are interpreted ** either by SQLite itself, or by a [VFS | custom VFS implementation]. -** SQLite interprets the following three query parameters: +** SQLite and its built-in [VFSes] interpret the +** following query parameters: ** ** <ul> ** <li> <b>vfs</b>: ^The "vfs" parameter may be used to specify the name of ** a VFS object that provides the operating system interface that should ** be used to access the database file on disk. ^If this option is set to @@ -3278,10 +3072,32 @@ ** sqlite3_open_v2(). ^Setting the cache parameter to "private" is ** equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit. ** ^If sqlite3_open_v2() is used and the "cache" parameter is present in ** a URI filename, its value overrides any behavior requested by setting ** SQLITE_OPEN_PRIVATECACHE or SQLITE_OPEN_SHAREDCACHE flag. +** +** <li> <b>psow</b>: ^The psow parameter indicates whether or not the +** [powersafe overwrite] property does or does not apply to the +** storage media on which the database file resides. +** +** <li> <b>nolock</b>: ^The nolock parameter is a boolean query parameter +** which if set disables file locking in rollback journal modes. This +** is useful for accessing a database on a filesystem that does not +** support locking. Caution: Database corruption might result if two +** or more processes write to the same database and any one of those +** processes uses nolock=1. +** +** <li> <b>immutable</b>: ^The immutable parameter is a boolean query +** parameter that indicates that the database file is stored on +** read-only media. ^When immutable is set, SQLite assumes that the +** database file cannot be changed, even by a process with higher +** privilege, and so the database is opened read-only and all locking +** and change detection is disabled. Caution: Setting the immutable +** property on a database file that does in fact change can result +** in incorrect query results and/or [SQLITE_CORRUPT] errors. +** See also: [SQLITE_IOCAP_IMMUTABLE]. +** ** </ul> ** ** ^Specifying an unknown parameter in the query component of a URI is not an ** error. Future versions of SQLite might understand additional query ** parameters. See "[query parameters with special meaning to SQLite]" for @@ -3307,12 +3123,13 @@ ** in URI filenames. ** <tr><td> file:data.db?mode=ro&cache=private <td> ** Open file "data.db" in the current directory for read-only access. ** Regardless of whether or not shared-cache mode is enabled by ** default, use a private cache. -** <tr><td> file:/home/fred/data.db?vfs=unix-nolock <td> -** Open file "/home/fred/data.db". Use the special VFS "unix-nolock". +** <tr><td> file:/home/fred/data.db?vfs=unix-dotfile <td> +** Open file "/home/fred/data.db". Use the special VFS "unix-dotfile" +** that uses dot-files in place of posix advisory locking. ** <tr><td> file:data.db?mode=readonly <td> ** An error. "readonly" is not a valid option for the "mode" parameter. ** </table> ** ** ^URI hexadecimal escape sequences (%HH) are supported within the path and @@ -3334,19 +3151,19 @@ ** prior to calling sqlite3_open() or sqlite3_open_v2(). Otherwise, various ** features that require the use of temporary files may fail. ** ** See also: [sqlite3_temp_directory] */ -SQLITE_API int sqlite3_open( +SQLITE_API int SQLITE_STDCALL sqlite3_open( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb /* OUT: SQLite db handle */ ); -SQLITE_API int sqlite3_open16( +SQLITE_API int SQLITE_STDCALL sqlite3_open16( const void *filename, /* Database filename (UTF-16) */ sqlite3 **ppDb /* OUT: SQLite db handle */ ); -SQLITE_API int sqlite3_open_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_open_v2( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb, /* OUT: SQLite db handle */ int flags, /* Flags */ const char *zVfs /* Name of VFS module to use */ ); @@ -3388,23 +3205,25 @@ ** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and ** is not a database file pathname pointer that SQLite passed into the xOpen ** VFS method, then the behavior of this routine is undefined and probably ** undesirable. */ -SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam); -SQLITE_API int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault); -SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64); +SQLITE_API const char *SQLITE_STDCALL sqlite3_uri_parameter(const char *zFilename, const char *zParam); +SQLITE_API int SQLITE_STDCALL sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_uri_int64(const char*, const char*, sqlite3_int64); /* ** CAPI3REF: Error Codes And Messages ** -** ^The sqlite3_errcode() interface returns the numeric [result code] or -** [extended result code] for the most recent failed sqlite3_* API call -** associated with a [database connection]. If a prior API call failed -** but the most recent API call succeeded, the return value from -** sqlite3_errcode() is undefined. ^The sqlite3_extended_errcode() +** ^If the most recent sqlite3_* API call associated with +** [database connection] D failed, then the sqlite3_errcode(D) interface +** returns the numeric [result code] or [extended result code] for that +** API call. +** If the most recent API call was successful, +** then the return value from sqlite3_errcode() is undefined. +** ^The sqlite3_extended_errcode() ** interface is the same except that it always returns the ** [extended result code] even when extended result codes are ** disabled. ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language @@ -3431,15 +3250,15 @@ ** ** If an interface fails with SQLITE_MISUSE, that means the interface ** was invoked incorrectly by the application. In that case, the ** error code and message may or may not be set. */ -SQLITE_API int sqlite3_errcode(sqlite3 *db); -SQLITE_API int sqlite3_extended_errcode(sqlite3 *db); -SQLITE_API const char *sqlite3_errmsg(sqlite3*); -SQLITE_API const void *sqlite3_errmsg16(sqlite3*); -SQLITE_API const char *sqlite3_errstr(int); +SQLITE_API int SQLITE_STDCALL sqlite3_errcode(sqlite3 *db); +SQLITE_API int SQLITE_STDCALL sqlite3_extended_errcode(sqlite3 *db); +SQLITE_API const char *SQLITE_STDCALL sqlite3_errmsg(sqlite3*); +SQLITE_API const void *SQLITE_STDCALL sqlite3_errmsg16(sqlite3*); +SQLITE_API const char *SQLITE_STDCALL sqlite3_errstr(int); /* ** CAPI3REF: SQL Statement Object ** KEYWORDS: {prepared statement} {prepared statements} ** @@ -3502,11 +3321,11 @@ ** created by an untrusted script can be contained using the ** [max_page_count] [PRAGMA]. ** ** New run-time limit categories may be added in future releases. */ -SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); +SQLITE_API int SQLITE_STDCALL sqlite3_limit(sqlite3*, int id, int newVal); /* ** CAPI3REF: Run-Time Limit Categories ** KEYWORDS: {limit category} {*limit categories} ** @@ -3554,10 +3373,14 @@ ** ^(<dt>SQLITE_LIMIT_VARIABLE_NUMBER</dt> ** <dd>The maximum index number of any [parameter] in an SQL statement.)^ ** ** [[SQLITE_LIMIT_TRIGGER_DEPTH]] ^(<dt>SQLITE_LIMIT_TRIGGER_DEPTH</dt> ** <dd>The maximum depth of recursion for triggers.</dd>)^ +** +** [[SQLITE_LIMIT_WORKER_THREADS]] ^(<dt>SQLITE_LIMIT_WORKER_THREADS</dt> +** <dd>The maximum number of auxiliary worker threads that a single +** [prepared statement] may start.</dd>)^ ** </dl> */ #define SQLITE_LIMIT_LENGTH 0 #define SQLITE_LIMIT_SQL_LENGTH 1 #define SQLITE_LIMIT_COLUMN 2 @@ -3567,10 +3390,11 @@ #define SQLITE_LIMIT_FUNCTION_ARG 6 #define SQLITE_LIMIT_ATTACHED 7 #define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 #define SQLITE_LIMIT_VARIABLE_NUMBER 9 #define SQLITE_LIMIT_TRIGGER_DEPTH 10 +#define SQLITE_LIMIT_WORKER_THREADS 11 /* ** CAPI3REF: Compiling An SQL Statement ** KEYWORDS: {SQL statement compiler} ** @@ -3584,20 +3408,18 @@ ** The second argument, "zSql", is the statement to be compiled, encoded ** as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2() ** interfaces use UTF-8, and sqlite3_prepare16() and sqlite3_prepare16_v2() ** use UTF-16. ** -** ^If the nByte argument is less than zero, then zSql is read up to the -** first zero terminator. ^If nByte is non-negative, then it is the maximum -** number of bytes read from zSql. ^When nByte is non-negative, the -** zSql string ends at either the first '\000' or '\u0000' character or -** the nByte-th byte, whichever comes first. If the caller knows -** that the supplied string is nul-terminated, then there is a small -** performance advantage to be gained by passing an nByte parameter that -** is equal to the number of bytes in the input string <i>including</i> -** the nul-terminator bytes as this saves SQLite from having to -** make a copy of the input string. +** ^If the nByte argument is negative, then zSql is read up to the +** first zero terminator. ^If nByte is positive, then it is the +** number of bytes read from zSql. ^If nByte is zero, then no prepared +** statement is generated. +** If the caller knows that the supplied string is nul-terminated, then +** there is a small performance advantage to passing an nByte parameter that +** is the number of bytes in the input string <i>including</i> +** the nul-terminator. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte ** past the end of the first SQL statement in zSql. These routines only ** compile the first statement in zSql, so *pzTail is left pointing to ** what remains uncompiled. @@ -3646,36 +3468,35 @@ ** to the [sqlite3_bind_text | bindings] of that [parameter]. ** ^The specific value of WHERE-clause [parameter] might influence the ** choice of query plan if the parameter is the left-hand side of a [LIKE] ** or [GLOB] operator or if the parameter is compared to an indexed column ** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. -** the ** </li> ** </ol> */ -SQLITE_API int sqlite3_prepare( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ ); -SQLITE_API int sqlite3_prepare_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare_v2( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ ); -SQLITE_API int sqlite3_prepare16( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare16( sqlite3 *db, /* Database handle */ const void *zSql, /* SQL statement, UTF-16 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const void **pzTail /* OUT: Pointer to unused portion of zSql */ ); -SQLITE_API int sqlite3_prepare16_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare16_v2( sqlite3 *db, /* Database handle */ const void *zSql, /* SQL statement, UTF-16 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const void **pzTail /* OUT: Pointer to unused portion of zSql */ @@ -3686,11 +3507,11 @@ ** ** ^This interface can be used to retrieve a saved copy of the original ** SQL text used to create a [prepared statement] if that statement was ** compiled using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()]. */ -SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); +SQLITE_API const char *SQLITE_STDCALL sqlite3_sql(sqlite3_stmt *pStmt); /* ** CAPI3REF: Determine If An SQL Statement Writes The Database ** ** ^The sqlite3_stmt_readonly(X) interface returns true (non-zero) if @@ -3717,11 +3538,11 @@ ** database. ^The [ATTACH] and [DETACH] statements also cause ** sqlite3_stmt_readonly() to return true since, while those statements ** change the configuration of a database connection, they do not make ** changes to the content of the database files on disk. */ -SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); +SQLITE_API int SQLITE_STDCALL sqlite3_stmt_readonly(sqlite3_stmt *pStmt); /* ** CAPI3REF: Determine If A Prepared Statement Has Been Reset ** ** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the @@ -3736,11 +3557,11 @@ ** to locate all prepared statements associated with a database ** connection that are in need of being reset. This can be used, ** for example, in diagnostic routines to search for prepared ** statements that are holding a transaction open. */ -SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); +SQLITE_API int SQLITE_STDCALL sqlite3_stmt_busy(sqlite3_stmt*); /* ** CAPI3REF: Dynamically Typed Value Object ** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value} ** @@ -3841,28 +3662,36 @@ ** is negative, then the length of the string is ** the number of bytes up to the first zero terminator. ** If the fourth parameter to sqlite3_bind_blob() is negative, then ** the behavior is undefined. ** If a non-negative fourth parameter is provided to sqlite3_bind_text() -** or sqlite3_bind_text16() then that parameter must be the byte offset +** or sqlite3_bind_text16() or sqlite3_bind_text64() then +** that parameter must be the byte offset ** where the NUL terminator would occur assuming the string were NUL ** terminated. If any NUL characters occur at byte offsets less than ** the value of the fourth parameter then the resulting string value will ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. ** -** ^The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and -** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or +** ^The fifth argument to the BLOB and string binding interfaces +** is a destructor used to dispose of the BLOB or ** string after SQLite has finished with it. ^The destructor is called -** to dispose of the BLOB or string even if the call to sqlite3_bind_blob(), -** sqlite3_bind_text(), or sqlite3_bind_text16() fails. +** to dispose of the BLOB or string even if the call to bind API fails. ** ^If the fifth argument is ** the special value [SQLITE_STATIC], then SQLite assumes that the ** information is in static, unmanaged space and does not need to be freed. ** ^If the fifth argument has the value [SQLITE_TRANSIENT], then ** SQLite makes its own private copy of the data immediately, before ** the sqlite3_bind_*() routine returns. +** +** ^The sixth argument to sqlite3_bind_text64() must be one of +** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] +** to specify the encoding of the text in the third parameter. If +** the sixth argument to sqlite3_bind_text64() is not one of the +** allowed values shown above, or if the text encoding is different +** from the encoding specified by the sixth parameter, then the behavior +** is undefined. ** ** ^The sqlite3_bind_zeroblob() routine binds a BLOB of length N that ** is filled with zeroes. ^A zeroblob uses a fixed amount of memory ** (just an integer to hold its size) while it is being processed. ** Zeroblobs are intended to serve as placeholders for BLOBs whose @@ -3880,25 +3709,32 @@ ** ^Bindings are not cleared by the [sqlite3_reset()] routine. ** ^Unbound parameters are interpreted as NULL. ** ** ^The sqlite3_bind_* routines return [SQLITE_OK] on success or an ** [error code] if anything goes wrong. +** ^[SQLITE_TOOBIG] might be returned if the size of a string or BLOB +** exceeds limits imposed by [sqlite3_limit]([SQLITE_LIMIT_LENGTH]) or +** [SQLITE_MAX_LENGTH]. ** ^[SQLITE_RANGE] is returned if the parameter ** index is out of range. ^[SQLITE_NOMEM] is returned if malloc() fails. ** ** See also: [sqlite3_bind_parameter_count()], ** [sqlite3_bind_parameter_name()], and [sqlite3_bind_parameter_index()]. */ -SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); -SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double); -SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int); -SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); -SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int); -SQLITE_API int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); -SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); -SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); -SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64, + void(*)(void*)); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_double(sqlite3_stmt*, int, double); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_int(sqlite3_stmt*, int, int); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_null(sqlite3_stmt*, int); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*)); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64, + void(*)(void*), unsigned char encoding); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); /* ** CAPI3REF: Number Of SQL Parameters ** ** ^This routine can be used to find the number of [SQL parameters] @@ -3914,11 +3750,11 @@ ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_name()], and ** [sqlite3_bind_parameter_index()]. */ -SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_parameter_count(sqlite3_stmt*); /* ** CAPI3REF: Name Of A Host Parameter ** ** ^The sqlite3_bind_parameter_name(P,N) interface returns @@ -3941,11 +3777,11 @@ ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_count()], and ** [sqlite3_bind_parameter_index()]. */ -SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int); +SQLITE_API const char *SQLITE_STDCALL sqlite3_bind_parameter_name(sqlite3_stmt*, int); /* ** CAPI3REF: Index Of A Parameter With A Given Name ** ** ^Return the index of an SQL parameter given its name. ^The @@ -3957,20 +3793,20 @@ ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_count()], and ** [sqlite3_bind_parameter_index()]. */ -SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); /* ** CAPI3REF: Reset All Bindings On A Prepared Statement ** ** ^Contrary to the intuition of many, [sqlite3_reset()] does not reset ** the [sqlite3_bind_blob | bindings] on a [prepared statement]. ** ^Use this routine to reset all host parameters to NULL. */ -SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); +SQLITE_API int SQLITE_STDCALL sqlite3_clear_bindings(sqlite3_stmt*); /* ** CAPI3REF: Number Of Columns In A Result Set ** ** ^Return the number of columns in the result set returned by the @@ -3977,11 +3813,11 @@ ** [prepared statement]. ^This routine returns 0 if pStmt is an SQL ** statement that does not return data (for example an [UPDATE]). ** ** See also: [sqlite3_data_count()] */ -SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt); +SQLITE_API int SQLITE_STDCALL sqlite3_column_count(sqlite3_stmt *pStmt); /* ** CAPI3REF: Column Names In A Result Set ** ** ^These routines return the name assigned to a particular column @@ -4005,12 +3841,12 @@ ** ^The name of a result column is the value of the "AS" clause for ** that column, if there is an AS clause. If there is no AS clause ** then the name of the column is unspecified and may change from ** one release of SQLite to the next. */ -SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N); -SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_name(sqlite3_stmt*, int N); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_name16(sqlite3_stmt*, int N); /* ** CAPI3REF: Source Of Data In A Query Result ** ** ^These routines provide a means to determine the database, table, and @@ -4053,16 +3889,16 @@ ** If two or more threads call one or more ** [sqlite3_column_database_name | column metadata interfaces] ** for the same [prepared statement] and result column ** at the same time then the results are undefined. */ -SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int); -SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int); -SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt*,int); -SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt*,int); -SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt*,int); -SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int); +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_database_name(sqlite3_stmt*,int); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_database_name16(sqlite3_stmt*,int); +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_table_name(sqlite3_stmt*,int); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_table_name16(sqlite3_stmt*,int); +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_origin_name(sqlite3_stmt*,int); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_origin_name16(sqlite3_stmt*,int); /* ** CAPI3REF: Declared Datatype Of A Query Result ** ** ^(The first parameter is a [prepared statement]. @@ -4089,12 +3925,12 @@ ** data stored in that column is of the declared type. SQLite is ** strongly typed, but the typing is dynamic not static. ^Type ** is associated with individual values, not with the containers ** used to hold those values. */ -SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt*,int); -SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_decltype(sqlite3_stmt*,int); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_decltype16(sqlite3_stmt*,int); /* ** CAPI3REF: Evaluate An SQL Statement ** ** After a [prepared statement] has been prepared using either @@ -4169,11 +4005,11 @@ ** using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead ** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces, ** then the more specific [error codes] are returned directly ** by sqlite3_step(). The use of the "v2" interface is recommended. */ -SQLITE_API int sqlite3_step(sqlite3_stmt*); +SQLITE_API int SQLITE_STDCALL sqlite3_step(sqlite3_stmt*); /* ** CAPI3REF: Number of columns in a result set ** ** ^The sqlite3_data_count(P) interface returns the number of columns in the @@ -4189,11 +4025,11 @@ ** where it always returns zero since each step of that multi-step ** pragma returns 0 columns of data. ** ** See also: [sqlite3_column_count()] */ -SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); +SQLITE_API int SQLITE_STDCALL sqlite3_data_count(sqlite3_stmt *pStmt); /* ** CAPI3REF: Fundamental Datatypes ** KEYWORDS: SQLITE_TEXT ** @@ -4308,23 +4144,23 @@ ** <table border="1"> ** <tr><th> Internal<br>Type <th> Requested<br>Type <th> Conversion ** ** <tr><td> NULL <td> INTEGER <td> Result is 0 ** <tr><td> NULL <td> FLOAT <td> Result is 0.0 -** <tr><td> NULL <td> TEXT <td> Result is NULL pointer -** <tr><td> NULL <td> BLOB <td> Result is NULL pointer +** <tr><td> NULL <td> TEXT <td> Result is a NULL pointer +** <tr><td> NULL <td> BLOB <td> Result is a NULL pointer ** <tr><td> INTEGER <td> FLOAT <td> Convert from integer to float ** <tr><td> INTEGER <td> TEXT <td> ASCII rendering of the integer ** <tr><td> INTEGER <td> BLOB <td> Same as INTEGER->TEXT -** <tr><td> FLOAT <td> INTEGER <td> Convert from float to integer +** <tr><td> FLOAT <td> INTEGER <td> [CAST] to INTEGER ** <tr><td> FLOAT <td> TEXT <td> ASCII rendering of the float -** <tr><td> FLOAT <td> BLOB <td> Same as FLOAT->TEXT -** <tr><td> TEXT <td> INTEGER <td> Use atoi() -** <tr><td> TEXT <td> FLOAT <td> Use atof() +** <tr><td> FLOAT <td> BLOB <td> [CAST] to BLOB +** <tr><td> TEXT <td> INTEGER <td> [CAST] to INTEGER +** <tr><td> TEXT <td> FLOAT <td> [CAST] to REAL ** <tr><td> TEXT <td> BLOB <td> No change -** <tr><td> BLOB <td> INTEGER <td> Convert to TEXT then use atoi() -** <tr><td> BLOB <td> FLOAT <td> Convert to TEXT then use atof() +** <tr><td> BLOB <td> INTEGER <td> [CAST] to INTEGER +** <tr><td> BLOB <td> FLOAT <td> [CAST] to REAL ** <tr><td> BLOB <td> TEXT <td> Add a zero terminator if needed ** </table> ** </blockquote>)^ ** ** The table above makes reference to standard C library functions atoi() @@ -4376,29 +4212,29 @@ ** ** ^The pointers returned are valid until a type conversion occurs as ** described above, or until [sqlite3_step()] or [sqlite3_reset()] or ** [sqlite3_finalize()] is called. ^The memory space used to hold strings ** and BLOBs is freed automatically. Do <b>not</b> pass the pointers returned -** [sqlite3_column_blob()], [sqlite3_column_text()], etc. into +** from [sqlite3_column_blob()], [sqlite3_column_text()], etc. into ** [sqlite3_free()]. ** ** ^(If a memory allocation error occurs during the evaluation of any ** of these routines, a default value is returned. The default value ** is either the integer 0, the floating point number 0.0, or a NULL ** pointer. Subsequent calls to [sqlite3_errcode()] will return ** [SQLITE_NOMEM].)^ */ -SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); -SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); -SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); -SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); -SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); -SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_blob(sqlite3_stmt*, int iCol); +SQLITE_API int SQLITE_STDCALL sqlite3_column_bytes(sqlite3_stmt*, int iCol); +SQLITE_API int SQLITE_STDCALL sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +SQLITE_API double SQLITE_STDCALL sqlite3_column_double(sqlite3_stmt*, int iCol); +SQLITE_API int SQLITE_STDCALL sqlite3_column_int(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_column_int64(sqlite3_stmt*, int iCol); +SQLITE_API const unsigned char *SQLITE_STDCALL sqlite3_column_text(sqlite3_stmt*, int iCol); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_text16(sqlite3_stmt*, int iCol); +SQLITE_API int SQLITE_STDCALL sqlite3_column_type(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_value *SQLITE_STDCALL sqlite3_column_value(sqlite3_stmt*, int iCol); /* ** CAPI3REF: Destroy A Prepared Statement Object ** ** ^The sqlite3_finalize() function is called to delete a [prepared statement]. @@ -4421,11 +4257,11 @@ ** resource leaks. It is a grievous error for the application to try to use ** a prepared statement after it has been finalized. Any use of a prepared ** statement after it has been finalized can result in undefined and ** undesirable behavior such as segfaults and heap corruption. */ -SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); +SQLITE_API int SQLITE_STDCALL sqlite3_finalize(sqlite3_stmt *pStmt); /* ** CAPI3REF: Reset A Prepared Statement Object ** ** The sqlite3_reset() function is called to reset a [prepared statement] @@ -4447,11 +4283,11 @@ ** [sqlite3_reset(S)] returns an appropriate [error code]. ** ** ^The [sqlite3_reset(S)] interface does not change the values ** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. */ -SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); +SQLITE_API int SQLITE_STDCALL sqlite3_reset(sqlite3_stmt *pStmt); /* ** CAPI3REF: Create Or Redefine SQL Functions ** KEYWORDS: {function creation routines} ** KEYWORDS: {application-defined SQL function} @@ -4485,19 +4321,28 @@ ** parameter is less than -1 or greater than 127 then the behavior is ** undefined. ** ** ^The fourth parameter, eTextRep, specifies what ** [SQLITE_UTF8 | text encoding] this SQL function prefers for -** its parameters. Every SQL function implementation must be able to work -** with UTF-8, UTF-16le, or UTF-16be. But some implementations may be -** more efficient with one encoding than another. ^An application may -** invoke sqlite3_create_function() or sqlite3_create_function16() multiple -** times with the same function but with different values of eTextRep. +** its parameters. The application should set this parameter to +** [SQLITE_UTF16LE] if the function implementation invokes +** [sqlite3_value_text16le()] on an input, or [SQLITE_UTF16BE] if the +** implementation invokes [sqlite3_value_text16be()] on an input, or +** [SQLITE_UTF16] if [sqlite3_value_text16()] is used, or [SQLITE_UTF8] +** otherwise. ^The same SQL function may be registered multiple times using +** different preferred text encodings, with different implementations for +** each encoding. ** ^When multiple implementations of the same function are available, SQLite ** will pick the one that involves the least amount of data conversion. -** If there is only a single implementation which does not care what text -** encoding is used, then the fourth argument should be [SQLITE_ANY]. +** +** ^The fourth parameter may optionally be ORed with [SQLITE_DETERMINISTIC] +** to signal that the function will always return the same result given +** the same inputs within a single SQL statement. Most SQL functions are +** deterministic. The built-in [random()] SQL function is an example of a +** function that is not deterministic. The SQLite query planner is able to +** perform additional optimizations on deterministic functions, so use +** of the [SQLITE_DETERMINISTIC] flag is recommended where possible. ** ** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** function can gain access to this pointer using [sqlite3_user_data()].)^ ** ** ^The sixth, seventh and eighth parameters, xFunc, xStep and xFinal, are @@ -4537,31 +4382,31 @@ ** ^An application-defined function is permitted to call other ** SQLite interfaces. However, such calls must not ** close the database connection nor finalize or reset the prepared ** statement in which the function is running. */ -SQLITE_API int sqlite3_create_function( +SQLITE_API int SQLITE_STDCALL sqlite3_create_function( sqlite3 *db, const char *zFunctionName, int nArg, int eTextRep, void *pApp, void (*xFunc)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ); -SQLITE_API int sqlite3_create_function16( +SQLITE_API int SQLITE_STDCALL sqlite3_create_function16( sqlite3 *db, const void *zFunctionName, int nArg, int eTextRep, void *pApp, void (*xFunc)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ); -SQLITE_API int sqlite3_create_function_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_create_function_v2( sqlite3 *db, const char *zFunctionName, int nArg, int eTextRep, void *pApp, @@ -4575,34 +4420,44 @@ ** CAPI3REF: Text Encodings ** ** These constant define integer codes that represent the various ** text encodings supported by SQLite. */ -#define SQLITE_UTF8 1 -#define SQLITE_UTF16LE 2 -#define SQLITE_UTF16BE 3 +#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ +#define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ +#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */ #define SQLITE_UTF16 4 /* Use native byte order */ -#define SQLITE_ANY 5 /* sqlite3_create_function only */ +#define SQLITE_ANY 5 /* Deprecated */ #define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ +/* +** CAPI3REF: Function Flags +** +** These constants may be ORed together with the +** [SQLITE_UTF8 | preferred text encoding] as the fourth argument +** to [sqlite3_create_function()], [sqlite3_create_function16()], or +** [sqlite3_create_function_v2()]. +*/ +#define SQLITE_DETERMINISTIC 0x800 + /* ** CAPI3REF: Deprecated Functions ** DEPRECATED ** ** These functions are [deprecated]. In order to maintain ** backwards compatibility with older code, these functions continue ** to be supported. However, new applications should avoid -** the use of these functions. To help encourage people to avoid -** using these functions, we are not going to tell you what they do. +** the use of these functions. To encourage programmers to avoid +** these functions, we will not explain what they do. */ #ifndef SQLITE_OMIT_DEPRECATED -SQLITE_API SQLITE_DEPRECATED int sqlite3_aggregate_count(sqlite3_context*); -SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*); -SQLITE_API SQLITE_DEPRECATED int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); -SQLITE_API SQLITE_DEPRECATED int sqlite3_global_recover(void); -SQLITE_API SQLITE_DEPRECATED void sqlite3_thread_cleanup(void); -SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), +SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_aggregate_count(sqlite3_context*); +SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_expired(sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_global_recover(void); +SQLITE_API SQLITE_DEPRECATED void SQLITE_STDCALL sqlite3_thread_cleanup(void); +SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), void*,sqlite3_int64); #endif /* ** CAPI3REF: Obtaining SQL Function Parameter Values @@ -4622,11 +4477,11 @@ ** These routines work only with [protected sqlite3_value] objects. ** Any attempt to use these routines on an [unprotected sqlite3_value] ** object results in undefined behavior. ** ** ^These routines work just like the corresponding [column access functions] -** except that these routines take a single [protected sqlite3_value] object +** except that these routines take a single [protected sqlite3_value] object ** pointer instead of a [sqlite3_stmt*] pointer and an integer column number. ** ** ^The sqlite3_value_text16() interface extracts a UTF-16 string ** in the native byte-order of the host machine. ^The ** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces @@ -4647,22 +4502,22 @@ ** or [sqlite3_value_text16()]. ** ** These routines must be called from the same thread as ** the SQL function that supplied the [sqlite3_value*] parameters. */ -SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); -SQLITE_API int sqlite3_value_bytes(sqlite3_value*); -SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); -SQLITE_API double sqlite3_value_double(sqlite3_value*); -SQLITE_API int sqlite3_value_int(sqlite3_value*); -SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); -SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*); -SQLITE_API const void *sqlite3_value_text16(sqlite3_value*); -SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*); -SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*); -SQLITE_API int sqlite3_value_type(sqlite3_value*); -SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_blob(sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_value_bytes(sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_value_bytes16(sqlite3_value*); +SQLITE_API double SQLITE_STDCALL sqlite3_value_double(sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_value_int(sqlite3_value*); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_value_int64(sqlite3_value*); +SQLITE_API const unsigned char *SQLITE_STDCALL sqlite3_value_text(sqlite3_value*); +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16(sqlite3_value*); +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16le(sqlite3_value*); +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16be(sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_value_type(sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_value_numeric_type(sqlite3_value*); /* ** CAPI3REF: Obtain Aggregate Function Context ** ** Implementations of aggregate SQL functions use this @@ -4702,11 +4557,11 @@ ** function. ** ** This routine must be called from the same thread in which ** the aggregate SQL function is running. */ -SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes); +SQLITE_API void *SQLITE_STDCALL sqlite3_aggregate_context(sqlite3_context*, int nBytes); /* ** CAPI3REF: User Data For Functions ** ** ^The sqlite3_user_data() interface returns a copy of @@ -4716,11 +4571,11 @@ ** registered the application defined function. ** ** This routine must be called from the same thread in which ** the application-defined function is running. */ -SQLITE_API void *sqlite3_user_data(sqlite3_context*); +SQLITE_API void *SQLITE_STDCALL sqlite3_user_data(sqlite3_context*); /* ** CAPI3REF: Database Connection For Functions ** ** ^The sqlite3_context_db_handle() interface returns a copy of @@ -4727,11 +4582,11 @@ ** the pointer to the [database connection] (the 1st parameter) ** of the [sqlite3_create_function()] ** and [sqlite3_create_function16()] routines that originally ** registered the application defined function. */ -SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); +SQLITE_API sqlite3 *SQLITE_STDCALL sqlite3_context_db_handle(sqlite3_context*); /* ** CAPI3REF: Function Auxiliary Data ** ** These functions may be used by (non-aggregate) SQL functions to @@ -4779,12 +4634,12 @@ ** values and [parameters] and expressions composed from the same.)^ ** ** These routines must be called from the same thread in which ** the SQL function is running. */ -SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N); -SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); +SQLITE_API void *SQLITE_STDCALL sqlite3_get_auxdata(sqlite3_context*, int N); +SQLITE_API void SQLITE_STDCALL sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); /* ** CAPI3REF: Constants Defining Special Destructor Behavior ** @@ -4869,10 +4724,14 @@ ** ^The sqlite3_result_text(), sqlite3_result_text16(), ** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces ** set the return value of the application-defined function to be ** a text string which is represented as UTF-8, UTF-16 native byte order, ** UTF-16 little endian, or UTF-16 big endian, respectively. +** ^The sqlite3_result_text64() interface sets the return value of an +** application-defined function to be a text string in an encoding +** specified by the fifth (and last) parameter, which must be one +** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. ** ^SQLite takes the text result from the application from ** the 2nd parameter of the sqlite3_result_text* interfaces. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is negative, then SQLite takes result text from the 2nd parameter ** through the first zero character. @@ -4911,26 +4770,30 @@ ** ** If these routines are called from within the different thread ** than the one containing the application-defined function that received ** the [sqlite3_context] pointer, the results are undefined. */ -SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); -SQLITE_API void sqlite3_result_double(sqlite3_context*, double); -SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); -SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); -SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*); -SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*); -SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int); -SQLITE_API void sqlite3_result_int(sqlite3_context*, int); -SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); -SQLITE_API void sqlite3_result_null(sqlite3_context*); -SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); -SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); -SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); -SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); -SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); -SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); +SQLITE_API void SQLITE_STDCALL sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_blob64(sqlite3_context*,const void*, + sqlite3_uint64,void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_double(sqlite3_context*, double); +SQLITE_API void SQLITE_STDCALL sqlite3_result_error(sqlite3_context*, const char*, int); +SQLITE_API void SQLITE_STDCALL sqlite3_result_error16(sqlite3_context*, const void*, int); +SQLITE_API void SQLITE_STDCALL sqlite3_result_error_toobig(sqlite3_context*); +SQLITE_API void SQLITE_STDCALL sqlite3_result_error_nomem(sqlite3_context*); +SQLITE_API void SQLITE_STDCALL sqlite3_result_error_code(sqlite3_context*, int); +SQLITE_API void SQLITE_STDCALL sqlite3_result_int(sqlite3_context*, int); +SQLITE_API void SQLITE_STDCALL sqlite3_result_int64(sqlite3_context*, sqlite3_int64); +SQLITE_API void SQLITE_STDCALL sqlite3_result_null(sqlite3_context*); +SQLITE_API void SQLITE_STDCALL sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64, + void(*)(void*), unsigned char encoding); +SQLITE_API void SQLITE_STDCALL sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_value(sqlite3_context*, sqlite3_value*); +SQLITE_API void SQLITE_STDCALL sqlite3_result_zeroblob(sqlite3_context*, int n); /* ** CAPI3REF: Define New Collating Sequences ** ** ^These functions add, remove, or modify a [collation] associated @@ -5007,26 +4870,26 @@ ** is unfortunate but cannot be changed without breaking backwards ** compatibility. ** ** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()]. */ -SQLITE_API int sqlite3_create_collation( +SQLITE_API int SQLITE_STDCALL sqlite3_create_collation( sqlite3*, const char *zName, int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); -SQLITE_API int sqlite3_create_collation_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_create_collation_v2( sqlite3*, const char *zName, int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*), void(*xDestroy)(void*) ); -SQLITE_API int sqlite3_create_collation16( +SQLITE_API int SQLITE_STDCALL sqlite3_create_collation16( sqlite3*, const void *zName, int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) @@ -5056,16 +4919,16 @@ ** ** The callback function should register the desired collation using ** [sqlite3_create_collation()], [sqlite3_create_collation16()], or ** [sqlite3_create_collation_v2()]. */ -SQLITE_API int sqlite3_collation_needed( +SQLITE_API int SQLITE_STDCALL sqlite3_collation_needed( sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const char*) ); -SQLITE_API int sqlite3_collation_needed16( +SQLITE_API int SQLITE_STDCALL sqlite3_collation_needed16( sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const void*) ); @@ -5075,15 +4938,15 @@ ** called right after sqlite3_open(). ** ** The code to implement this API is not available in the public release ** of SQLite. */ -SQLITE_API int sqlite3_key( +SQLITE_API int SQLITE_STDCALL sqlite3_key( sqlite3 *db, /* Database to be rekeyed */ const void *pKey, int nKey /* The key */ ); -SQLITE_API int sqlite3_key_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_key_v2( sqlite3 *db, /* Database to be rekeyed */ const char *zDbName, /* Name of the database */ const void *pKey, int nKey /* The key */ ); @@ -5093,35 +4956,35 @@ ** database is decrypted. ** ** The code to implement this API is not available in the public release ** of SQLite. */ -SQLITE_API int sqlite3_rekey( +SQLITE_API int SQLITE_STDCALL sqlite3_rekey( sqlite3 *db, /* Database to be rekeyed */ const void *pKey, int nKey /* The new key */ ); -SQLITE_API int sqlite3_rekey_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_rekey_v2( sqlite3 *db, /* Database to be rekeyed */ const char *zDbName, /* Name of the database */ const void *pKey, int nKey /* The new key */ ); /* ** Specify the activation key for a SEE database. Unless ** activated, none of the SEE routines will work. */ -SQLITE_API void sqlite3_activate_see( +SQLITE_API void SQLITE_STDCALL sqlite3_activate_see( const char *zPassPhrase /* Activation phrase */ ); #endif #ifdef SQLITE_ENABLE_CEROD /* ** Specify the activation key for a CEROD database. Unless ** activated, none of the CEROD routines will work. */ -SQLITE_API void sqlite3_activate_cerod( +SQLITE_API void SQLITE_STDCALL sqlite3_activate_cerod( const char *zPassPhrase /* Activation phrase */ ); #endif /* @@ -5139,11 +5002,11 @@ ** method of the default [sqlite3_vfs] object. If the xSleep() method ** of the default VFS is not implemented correctly, or not implemented at ** all, then the behavior of sqlite3_sleep() may deviate from the description ** in the previous paragraphs. */ -SQLITE_API int sqlite3_sleep(int); +SQLITE_API int SQLITE_STDCALL sqlite3_sleep(int); /* ** CAPI3REF: Name Of The Folder Holding Temporary Files ** ** ^(If this global variable is made to point to a string which is @@ -5150,10 +5013,17 @@ ** the name of a folder (a.k.a. directory), then all temporary files ** created by SQLite when using a built-in [sqlite3_vfs | VFS] ** will be placed in that directory.)^ ^If this variable ** is a NULL pointer, then SQLite performs a search for an appropriate ** temporary file directory. +** +** Applications are strongly discouraged from using this global variable. +** It is required to set a temporary folder on Windows Runtime (WinRT). +** But for all other platforms, it is highly recommended that applications +** neither read nor write this variable. This global variable is a relic +** that exists for backwards compatibility of legacy applications and should +** be avoided in new projects. ** ** It is not safe to read or modify this variable in more than one ** thread at a time. It is not safe to read or modify this variable ** if a [database connection] is being used at the same time in a separate ** thread. @@ -5169,10 +5039,15 @@ ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be ** made NULL or made to point to memory obtained from [sqlite3_malloc] ** or else the use of the [temp_store_directory pragma] should be avoided. +** Except when requested by the [temp_store_directory pragma], SQLite +** does not free the memory that sqlite3_temp_directory points to. If +** the application wants that memory to be freed, it must do +** so itself, taking care to only do so after all [database connection] +** objects have been destroyed. ** ** <b>Note to Windows Runtime users:</b> The temporary directory must be set ** prior to calling [sqlite3_open] or [sqlite3_open_v2]. Otherwise, various ** features that require the use of temporary files may fail. Here is an ** example of how to do this using C++ with the Windows Runtime: @@ -5245,11 +5120,11 @@ ** ** If another thread changes the autocommit status of the database ** connection while this routine is running, then the return value ** is undefined. */ -SQLITE_API int sqlite3_get_autocommit(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_get_autocommit(sqlite3*); /* ** CAPI3REF: Find The Database Handle Of A Prepared Statement ** ** ^The sqlite3_db_handle interface returns the [database connection] handle @@ -5257,11 +5132,11 @@ ** returned by sqlite3_db_handle is the same [database connection] ** that was the first argument ** to the [sqlite3_prepare_v2()] call (or its variants) that was used to ** create the statement in the first place. */ -SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); +SQLITE_API sqlite3 *SQLITE_STDCALL sqlite3_db_handle(sqlite3_stmt*); /* ** CAPI3REF: Return The Filename For A Database Connection ** ** ^The sqlite3_db_filename(D,N) interface returns a pointer to a filename @@ -5273,20 +5148,20 @@ ** ^The filename returned by this function is the output of the ** xFullPathname method of the [VFS]. ^In other words, the filename ** will be an absolute pathname, even if the filename used ** to open the database originally was a URI or relative pathname. */ -SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName); +SQLITE_API const char *SQLITE_STDCALL sqlite3_db_filename(sqlite3 *db, const char *zDbName); /* ** CAPI3REF: Determine if a database is read-only ** ** ^The sqlite3_db_readonly(D,N) interface returns 1 if the database N ** of connection D is read-only, 0 if it is read/write, or -1 if N is not ** the name of a database on connection D. */ -SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName); +SQLITE_API int SQLITE_STDCALL sqlite3_db_readonly(sqlite3 *db, const char *zDbName); /* ** CAPI3REF: Find the next prepared statement ** ** ^This interface returns a pointer to the next [prepared statement] after @@ -5297,11 +5172,11 @@ ** ** The [database connection] pointer D in a call to ** [sqlite3_next_stmt(D,S)] must refer to an open database ** connection and in particular must not be a NULL pointer. */ -SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); +SQLITE_API sqlite3_stmt *SQLITE_STDCALL sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); /* ** CAPI3REF: Commit And Rollback Notification Callbacks ** ** ^The sqlite3_commit_hook() interface registers a callback @@ -5345,24 +5220,25 @@ ** ^The rollback callback is not invoked if a transaction is ** automatically rolled back because the database connection is closed. ** ** See also the [sqlite3_update_hook()] interface. */ -SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); -SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); +SQLITE_API void *SQLITE_STDCALL sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); +SQLITE_API void *SQLITE_STDCALL sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); /* ** CAPI3REF: Data Change Notification Callbacks ** ** ^The sqlite3_update_hook() interface registers a callback function ** with the [database connection] identified by the first argument -** to be invoked whenever a row is updated, inserted or deleted. +** to be invoked whenever a row is updated, inserted or deleted in +** a rowid table. ** ^Any callback set by a previous call to this function ** for the same database connection is overridden. ** ** ^The second argument is a pointer to the function to invoke when a -** row is updated, inserted or deleted. +** row is updated, inserted or deleted in a rowid table. ** ^The first argument to the callback is a copy of the third argument ** to sqlite3_update_hook(). ** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], ** or [SQLITE_UPDATE], depending on the operation that caused the callback ** to be invoked. @@ -5371,10 +5247,11 @@ ** ^The final callback parameter is the [rowid] of the row. ** ^In the case of an update, this is the [rowid] after the update takes place. ** ** ^(The update hook is not invoked when internal system tables are ** modified (i.e. sqlite_master and sqlite_sequence).)^ +** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified. ** ** ^In the current implementation, the update hook ** is not invoked when duplication rows are deleted because of an ** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook ** invoked when rows are deleted using the [truncate optimization]. @@ -5394,11 +5271,11 @@ ** the first call on D. ** ** See also the [sqlite3_commit_hook()] and [sqlite3_rollback_hook()] ** interfaces. */ -SQLITE_API void *sqlite3_update_hook( +SQLITE_API void *SQLITE_STDCALL sqlite3_update_hook( sqlite3*, void(*)(void *,int ,char const *,char const *,sqlite3_int64), void* ); @@ -5423,17 +5300,22 @@ ** successfully. An [error code] is returned otherwise.)^ ** ** ^Shared cache is disabled by default. But this might change in ** future releases of SQLite. Applications that care about shared ** cache setting should set it explicitly. +** +** Note: This method is disabled on MacOS X 10.7 and iOS version 5.0 +** and will always return SQLITE_MISUSE. On those systems, +** shared cache mode should be enabled per-database connection via +** [sqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE]. ** ** This interface is threadsafe on processors where writing a ** 32-bit integer is atomic. ** ** See Also: [SQLite Shared-Cache Mode] */ -SQLITE_API int sqlite3_enable_shared_cache(int); +SQLITE_API int SQLITE_STDCALL sqlite3_enable_shared_cache(int); /* ** CAPI3REF: Attempt To Free Heap Memory ** ** ^The sqlite3_release_memory() interface attempts to free N bytes @@ -5445,24 +5327,24 @@ ** ^The sqlite3_release_memory() routine is a no-op returning zero ** if SQLite is not compiled with [SQLITE_ENABLE_MEMORY_MANAGEMENT]. ** ** See also: [sqlite3_db_release_memory()] */ -SQLITE_API int sqlite3_release_memory(int); +SQLITE_API int SQLITE_STDCALL sqlite3_release_memory(int); /* ** CAPI3REF: Free Memory Used By A Database Connection ** ** ^The sqlite3_db_release_memory(D) interface attempts to free as much heap ** memory as possible from database connection D. Unlike the -** [sqlite3_release_memory()] interface, this interface is effect even -** when then [SQLITE_ENABLE_MEMORY_MANAGEMENT] compile-time option is +** [sqlite3_release_memory()] interface, this interface is in effect even +** when the [SQLITE_ENABLE_MEMORY_MANAGEMENT] compile-time option is ** omitted. ** ** See also: [sqlite3_release_memory()] */ -SQLITE_API int sqlite3_db_release_memory(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_db_release_memory(sqlite3*); /* ** CAPI3REF: Impose A Limit On Heap Size ** ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the @@ -5510,11 +5392,11 @@ ** the use of [SQLITE_ENABLE_MEMORY_MANAGEMENT]. ** ** The circumstances under which SQLite will enforce the soft heap limit may ** changes in future releases of SQLite. */ -SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_soft_heap_limit64(sqlite3_int64 N); /* ** CAPI3REF: Deprecated Soft Heap Limit Interface ** DEPRECATED ** @@ -5521,30 +5403,37 @@ ** This is a deprecated version of the [sqlite3_soft_heap_limit64()] ** interface. This routine is provided for historical compatibility ** only. All new applications should use the ** [sqlite3_soft_heap_limit64()] interface rather than this one. */ -SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); +SQLITE_API SQLITE_DEPRECATED void SQLITE_STDCALL sqlite3_soft_heap_limit(int N); /* ** CAPI3REF: Extract Metadata About A Column Of A Table ** -** ^This routine returns metadata about a specific column of a specific -** database table accessible using the [database connection] handle -** passed as the first function argument. +** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns +** information about column C of table T in database D +** on [database connection] X.)^ ^The sqlite3_table_column_metadata() +** interface returns SQLITE_OK and fills in the non-NULL pointers in +** the final five arguments with appropriate values if the specified +** column exists. ^The sqlite3_table_column_metadata() interface returns +** SQLITE_ERROR and if the specified column does not exist. +** ^If the column-name parameter to sqlite3_table_column_metadata() is a +** NULL pointer, then this routine simply checks for the existance of the +** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it +** does not. ** ** ^The column is identified by the second, third and fourth parameters to -** this function. ^The second parameter is either the name of the database +** this function. ^(The second parameter is either the name of the database ** (i.e. "main", "temp", or an attached database) containing the specified -** table or NULL. ^If it is NULL, then all attached databases are searched +** table or NULL.)^ ^If it is NULL, then all attached databases are searched ** for the table using the same algorithm used by the database engine to ** resolve unqualified table references. ** ** ^The third and fourth parameters to this function are the table and column -** name of the desired column, respectively. Neither of these parameters -** may be NULL. +** name of the desired column, respectively. ** ** ^Metadata is returned by writing to the memory locations passed as the 5th ** and subsequent parameters to this function. ^Any of these arguments may be ** NULL, in which case the corresponding element of metadata is omitted. ** @@ -5559,38 +5448,35 @@ ** <tr><td> 9th <td> int <td> True if column is [AUTOINCREMENT] ** </table> ** </blockquote>)^ ** ** ^The memory pointed to by the character pointers returned for the -** declaration type and collation sequence is valid only until the next +** declaration type and collation sequence is valid until the next ** call to any SQLite API function. ** ** ^If the specified table is actually a view, an [error code] is returned. ** -** ^If the specified column is "rowid", "oid" or "_rowid_" and an +** ^If the specified column is "rowid", "oid" or "_rowid_" and the table +** is not a [WITHOUT ROWID] table and an ** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output ** parameters are set for the explicitly declared column. ^(If there is no -** explicitly declared [INTEGER PRIMARY KEY] column, then the output -** parameters are set as follows: +** [INTEGER PRIMARY KEY] column, then the outputs +** for the [rowid] are set as follows: ** ** <pre> ** data type: "INTEGER" ** collation sequence: "BINARY" ** not null: 0 ** primary key: 1 ** auto increment: 0 ** </pre>)^ ** -** ^(This function may load one or more schemas from database files. If an -** error occurs during this process, or if the requested table or column -** cannot be found, an [error code] is returned and an error message left -** in the [database connection] (to be retrieved using sqlite3_errmsg()).)^ -** -** ^This API is only available if the library was compiled with the -** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined. +** ^This function causes all database schemas to be read from disk and +** parsed, if that has not already been done, and returns an error if +** any errors are encountered while loading the schema. */ -SQLITE_API int sqlite3_table_column_metadata( +SQLITE_API int SQLITE_STDCALL sqlite3_table_column_metadata( sqlite3 *db, /* Connection handle */ const char *zDbName, /* Database name or NULL */ const char *zTableName, /* Table name */ const char *zColumnName, /* Column name */ char const **pzDataType, /* OUTPUT: Declared data type */ @@ -5632,11 +5518,11 @@ ** [sqlite3_enable_load_extension()] prior to calling this API, ** otherwise an error will be returned. ** ** See also the [load_extension() SQL function]. */ -SQLITE_API int sqlite3_load_extension( +SQLITE_API int SQLITE_STDCALL sqlite3_load_extension( sqlite3 *db, /* Load the extension into this database connection */ const char *zFile, /* Name of the shared library containing extension */ const char *zProc, /* Entry point. Derived from zFile if 0 */ char **pzErrMsg /* Put error message here if not 0 */ ); @@ -5652,11 +5538,11 @@ ** ^Extension loading is off by default. ** ^Call the sqlite3_enable_load_extension() routine with onoff==1 ** to turn extension loading on and call it with onoff==0 to turn ** it back off again. */ -SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); +SQLITE_API int SQLITE_STDCALL sqlite3_enable_load_extension(sqlite3 *db, int onoff); /* ** CAPI3REF: Automatically Load Statically Linked Extensions ** ** ^This interface causes the xEntryPoint() function to be invoked for @@ -5690,11 +5576,11 @@ ** will be called more than once for each database connection that is opened. ** ** See also: [sqlite3_reset_auto_extension()] ** and [sqlite3_cancel_auto_extension()] */ -SQLITE_API int sqlite3_auto_extension(void (*xEntryPoint)(void)); +SQLITE_API int SQLITE_STDCALL sqlite3_auto_extension(void (*xEntryPoint)(void)); /* ** CAPI3REF: Cancel Automatic Extension Loading ** ** ^The [sqlite3_cancel_auto_extension(X)] interface unregisters the @@ -5702,19 +5588,19 @@ ** [sqlite3_auto_extension(X)]. ^The [sqlite3_cancel_auto_extension(X)] ** routine returns 1 if initialization routine X was successfully ** unregistered and it returns 0 if X was not on the list of initialization ** routines. */ -SQLITE_API int sqlite3_cancel_auto_extension(void (*xEntryPoint)(void)); +SQLITE_API int SQLITE_STDCALL sqlite3_cancel_auto_extension(void (*xEntryPoint)(void)); /* ** CAPI3REF: Reset Automatic Extension Loading ** ** ^This interface disables all automatic extensions previously ** registered using [sqlite3_auto_extension()]. */ -SQLITE_API void sqlite3_reset_auto_extension(void); +SQLITE_API void SQLITE_STDCALL sqlite3_reset_auto_extension(void); /* ** The interface to the virtual-table mechanism is currently considered ** to be experimental. The interface might change in incompatible ways. ** If this is a problem for you, do not use the interface at this time. @@ -5828,14 +5714,26 @@ ** ** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in ** the correct order to satisfy the ORDER BY clause so that no separate ** sorting step is required. ** -** ^The estimatedCost value is an estimate of the cost of doing the -** particular lookup. A full scan of a table with N entries should have -** a cost of N. A binary search of a table of N entries should have a -** cost of approximately log(N). +** ^The estimatedCost value is an estimate of the cost of a particular +** strategy. A cost of N indicates that the cost of the strategy is similar +** to a linear scan of an SQLite table with N rows. A cost of log(N) +** indicates that the expense of the operation is similar to that of a +** binary search on a unique indexed field of an SQLite table with N rows. +** +** ^The estimatedRows value is an estimate of the number of rows that +** will be returned by the strategy. +** +** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info +** structure for SQLite version 3.8.2. If a virtual table extension is +** used with an SQLite version earlier than 3.8.2, the results of attempting +** to read or write the estimatedRows field are undefined (but are likely +** to included crashing the application). The estimatedRows field should +** therefore only be used if [sqlite3_libversion_number()] returns a +** value greater than or equal to 3008002. */ struct sqlite3_index_info { /* Inputs */ int nConstraint; /* Number of entries in aConstraint */ struct sqlite3_index_constraint { @@ -5856,11 +5754,13 @@ } *aConstraintUsage; int idxNum; /* Number used to identify the index */ char *idxStr; /* String, possibly obtained from sqlite3_malloc */ int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ int orderByConsumed; /* True if output is already ordered */ - double estimatedCost; /* Estimated cost of using this index */ + double estimatedCost; /* Estimated cost of using this index */ + /* Fields below are only available in SQLite 3.8.2 and later */ + sqlite3_int64 estimatedRows; /* Estimated number of rows returned */ }; /* ** CAPI3REF: Virtual Table Constraint Operator Codes ** @@ -5899,17 +5799,17 @@ ** be invoked if the call to sqlite3_create_module_v2() fails. ** ^The sqlite3_create_module() ** interface is equivalent to sqlite3_create_module_v2() with a NULL ** destructor. */ -SQLITE_API int sqlite3_create_module( +SQLITE_API int SQLITE_STDCALL sqlite3_create_module( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *p, /* Methods for the module */ void *pClientData /* Client data for xCreate/xConnect */ ); -SQLITE_API int sqlite3_create_module_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_create_module_v2( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *p, /* Methods for the module */ void *pClientData, /* Client data for xCreate/xConnect */ void(*xDestroy)(void*) /* Module destructor function */ @@ -5933,11 +5833,11 @@ ** is delivered up to the client application, the string will be automatically ** freed by sqlite3_free() and the zErrMsg field will be zeroed. */ struct sqlite3_vtab { const sqlite3_module *pModule; /* The module for this virtual table */ - int nRef; /* NO LONGER USED */ + int nRef; /* Number of open cursors */ char *zErrMsg; /* Error message from sqlite3_mprintf() */ /* Virtual table implementations will typically add additional fields */ }; /* @@ -5968,11 +5868,11 @@ ** ^The [xCreate] and [xConnect] methods of a ** [virtual table module] call this interface ** to declare the format (the names and datatypes of the columns) of ** the virtual tables they implement. */ -SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL); +SQLITE_API int SQLITE_STDCALL sqlite3_declare_vtab(sqlite3*, const char *zSQL); /* ** CAPI3REF: Overload A Function For A Virtual Table ** ** ^(Virtual tables can provide alternative implementations of functions @@ -5986,11 +5886,11 @@ ** of the new function always causes an exception to be thrown. So ** the new function is not good for anything by itself. Its only ** purpose is to be a placeholder function that can be overloaded ** by a [virtual table]. */ -SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); +SQLITE_API int SQLITE_STDCALL sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); /* ** The interface to the virtual-table mechanism defined above (back up ** to a comment remarkably similar to this one) is currently considered ** to be experimental. The interface might change in incompatible ways. @@ -6022,31 +5922,47 @@ ** in other words, the same BLOB that would be selected by: ** ** <pre> ** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow; ** </pre>)^ +** +** ^(Parameter zDb is not the filename that contains the database, but +** rather the symbolic name of the database. For attached databases, this is +** the name that appears after the AS keyword in the [ATTACH] statement. +** For the main database file, the database name is "main". For TEMP +** tables, the database name is "temp".)^ ** ** ^If the flags parameter is non-zero, then the BLOB is opened for read -** and write access. ^If it is zero, the BLOB is opened for read access. -** ^It is not possible to open a column that is part of an index or primary -** key for writing. ^If [foreign key constraints] are enabled, it is -** not possible to open a column that is part of a [child key] for writing. -** -** ^Note that the database name is not the filename that contains -** the database but rather the symbolic name of the database that -** appears after the AS keyword when the database is connected using [ATTACH]. -** ^For the main database file, the database name is "main". -** ^For TEMP tables, the database name is "temp". -** -** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is written -** to *ppBlob. Otherwise an [error code] is returned and *ppBlob is set -** to be a null pointer.)^ -** ^This function sets the [database connection] error code and message -** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()] and related -** functions. ^Note that the *ppBlob variable is always initialized in a -** way that makes it safe to invoke [sqlite3_blob_close()] on *ppBlob -** regardless of the success or failure of this routine. +** and write access. ^If the flags parameter is zero, the BLOB is opened for +** read-only access. +** +** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored +** in *ppBlob. Otherwise an [error code] is returned and, unless the error +** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided +** the API is not misused, it is always safe to call [sqlite3_blob_close()] +** on *ppBlob after this function it returns. +** +** This function fails with SQLITE_ERROR if any of the following are true: +** <ul> +** <li> ^(Database zDb does not exist)^, +** <li> ^(Table zTable does not exist within database zDb)^, +** <li> ^(Table zTable is a WITHOUT ROWID table)^, +** <li> ^(Column zColumn does not exist)^, +** <li> ^(Row iRow is not present in the table)^, +** <li> ^(The specified column of row iRow contains a value that is not +** a TEXT or BLOB value)^, +** <li> ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE +** constraint and the blob is being opened for read/write access)^, +** <li> ^([foreign key constraints | Foreign key constraints] are enabled, +** column zColumn is part of a [child key] definition and the blob is +** being opened for read/write access)^. +** </ul> +** +** ^Unless it returns SQLITE_MISUSE, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** ** ** ^(If the row that a BLOB handle points to is modified by an ** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects ** then the BLOB handle is marked as "expired". ** This is true if any column of the row is changed, even a column @@ -6061,18 +5977,17 @@ ** the opened blob. ^The size of a blob may not be changed by this ** interface. Use the [UPDATE] SQL command to change the size of a ** blob. ** ** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces -** and the built-in [zeroblob] SQL function can be used, if desired, -** to create an empty, zero-filled blob in which to read or write using -** this interface. +** and the built-in [zeroblob] SQL function may be used to create a +** zero-filled blob to read or write using the incremental-blob interface. ** ** To avoid a resource leak, every open [BLOB handle] should eventually ** be released by a call to [sqlite3_blob_close()]. */ -SQLITE_API int sqlite3_blob_open( +SQLITE_API int SQLITE_STDCALL sqlite3_blob_open( sqlite3*, const char *zDb, const char *zTable, const char *zColumn, sqlite3_int64 iRow, @@ -6100,35 +6015,33 @@ ** SQLITE_ABORT. ^Calling [sqlite3_blob_bytes()] on an aborted blob handle ** always returns zero. ** ** ^This function sets the database handle error code and message. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); /* ** CAPI3REF: Close A BLOB Handle ** -** ^Closes an open [BLOB handle]. -** -** ^Closing a BLOB shall cause the current transaction to commit -** if there are no other BLOBs, no pending prepared statements, and the -** database connection is in [autocommit mode]. -** ^If any writes were made to the BLOB, they might be held in cache -** until the close operation if they will fit. -** -** ^(Closing the BLOB often forces the changes -** out to disk and so if any I/O errors occur, they will likely occur -** at the time when the BLOB is closed. Any errors that occur during -** closing are reported as a non-zero return value.)^ -** -** ^(The BLOB is closed unconditionally. Even if this routine returns -** an error code, the BLOB is still closed.)^ -** -** ^Calling this routine with a null pointer (such as would be returned -** by a failed call to [sqlite3_blob_open()]) is a harmless no-op. -*/ -SQLITE_API int sqlite3_blob_close(sqlite3_blob *); +** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed +** unconditionally. Even if this routine returns an error code, the +** handle is still closed.)^ +** +** ^If the blob handle being closed was opened for read-write access, and if +** the database is in auto-commit mode and there are no other open read-write +** blob handles or active write statements, the current transaction is +** committed. ^If an error occurs while committing the transaction, an error +** code is returned and the transaction rolled back. +** +** Calling this function with an argument that is not a NULL pointer or an +** open blob handle results in undefined behaviour. ^Calling this routine +** with a null pointer (such as would be returned by a failed call to +** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function +** is passed a valid open blob handle, the values returned by the +** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_blob_close(sqlite3_blob *); /* ** CAPI3REF: Return The Size Of An Open BLOB ** ** ^Returns the size in bytes of the BLOB accessible via the @@ -6139,11 +6052,11 @@ ** This routine only works on a [BLOB handle] which has been created ** by a prior successful call to [sqlite3_blob_open()] and which has not ** been closed by [sqlite3_blob_close()]. Passing any other pointer in ** to this routine results in undefined and probably undesirable behavior. */ -SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *); +SQLITE_API int SQLITE_STDCALL sqlite3_blob_bytes(sqlite3_blob *); /* ** CAPI3REF: Read Data From A BLOB Incrementally ** ** ^(This function is used to read data from an open [BLOB handle] into a @@ -6167,49 +6080,52 @@ ** been closed by [sqlite3_blob_close()]. Passing any other pointer in ** to this routine results in undefined and probably undesirable behavior. ** ** See also: [sqlite3_blob_write()]. */ -SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); +SQLITE_API int SQLITE_STDCALL sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); /* ** CAPI3REF: Write Data Into A BLOB Incrementally ** -** ^This function is used to write data into an open [BLOB handle] from a -** caller-supplied buffer. ^N bytes of data are copied from the buffer Z -** into the open BLOB, starting at offset iOffset. +** ^(This function is used to write data into an open [BLOB handle] from a +** caller-supplied buffer. N bytes of data are copied from the buffer Z +** into the open BLOB, starting at offset iOffset.)^ +** +** ^(On success, sqlite3_blob_write() returns SQLITE_OK. +** Otherwise, an [error code] or an [extended error code] is returned.)^ +** ^Unless SQLITE_MISUSE is returned, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** ^If the [BLOB handle] passed as the first argument was not opened for ** writing (the flags parameter to [sqlite3_blob_open()] was zero), ** this function returns [SQLITE_READONLY]. ** -** ^This function may only modify the contents of the BLOB; it is +** This function may only modify the contents of the BLOB; it is ** not possible to increase the size of a BLOB using this API. ** ^If offset iOffset is less than N bytes from the end of the BLOB, -** [SQLITE_ERROR] is returned and no data is written. ^If N is -** less than zero [SQLITE_ERROR] is returned and no data is written. -** The size of the BLOB (and hence the maximum value of N+iOffset) -** can be determined using the [sqlite3_blob_bytes()] interface. +** [SQLITE_ERROR] is returned and no data is written. The size of the +** BLOB (and hence the maximum value of N+iOffset) can be determined +** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less +** than zero [SQLITE_ERROR] is returned and no data is written. ** ** ^An attempt to write to an expired [BLOB handle] fails with an ** error code of [SQLITE_ABORT]. ^Writes to the BLOB that occurred ** before the [BLOB handle] expired are not rolled back by the ** expiration of the handle, though of course those changes might ** have been overwritten by the statement that expired the BLOB handle ** or by other independent statements. ** -** ^(On success, sqlite3_blob_write() returns SQLITE_OK. -** Otherwise, an [error code] or an [extended error code] is returned.)^ -** ** This routine only works on a [BLOB handle] which has been created ** by a prior successful call to [sqlite3_blob_open()] and which has not ** been closed by [sqlite3_blob_close()]. Passing any other pointer in ** to this routine results in undefined and probably undesirable behavior. ** ** See also: [sqlite3_blob_read()]. */ -SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); +SQLITE_API int SQLITE_STDCALL sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); /* ** CAPI3REF: Virtual File System Objects ** ** A virtual filesystem (VFS) is an [sqlite3_vfs] object @@ -6236,13 +6152,13 @@ ** ** ^Unregister a VFS with the sqlite3_vfs_unregister() interface. ** ^(If the default VFS is unregistered, another VFS is chosen as ** the default. The choice for the new VFS is arbitrary.)^ */ -SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName); -SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); -SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); +SQLITE_API sqlite3_vfs *SQLITE_STDCALL sqlite3_vfs_find(const char *zVfsName); +SQLITE_API int SQLITE_STDCALL sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); +SQLITE_API int SQLITE_STDCALL sqlite3_vfs_unregister(sqlite3_vfs*); /* ** CAPI3REF: Mutexes ** ** The SQLite core uses these routines for thread @@ -6250,129 +6166,127 @@ ** use by SQLite, code that links against SQLite is ** permitted to use any of these routines. ** ** The SQLite source code contains multiple implementations ** of these mutex routines. An appropriate implementation -** is selected automatically at compile-time. ^(The following +** is selected automatically at compile-time. The following ** implementations are available in the SQLite core: ** ** <ul> ** <li> SQLITE_MUTEX_PTHREADS ** <li> SQLITE_MUTEX_W32 ** <li> SQLITE_MUTEX_NOOP -** </ul>)^ +** </ul> ** -** ^The SQLITE_MUTEX_NOOP implementation is a set of routines +** The SQLITE_MUTEX_NOOP implementation is a set of routines ** that does no real locking and is appropriate for use in -** a single-threaded application. ^The SQLITE_MUTEX_PTHREADS and +** a single-threaded application. The SQLITE_MUTEX_PTHREADS and ** SQLITE_MUTEX_W32 implementations are appropriate for use on Unix ** and Windows. ** -** ^(If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor +** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor ** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex ** implementation is included with the library. In this case the ** application must supply a custom mutex implementation using the ** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function ** before calling sqlite3_initialize() or any other public sqlite3_ -** function that calls sqlite3_initialize().)^ +** function that calls sqlite3_initialize(). ** ** ^The sqlite3_mutex_alloc() routine allocates a new -** mutex and returns a pointer to it. ^If it returns NULL -** that means that a mutex could not be allocated. ^SQLite -** will unwind its stack and return an error. ^(The argument -** to sqlite3_mutex_alloc() is one of these integer constants: +** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc() +** routine returns NULL if it is unable to allocate the requested +** mutex. The argument to sqlite3_mutex_alloc() must one of these +** integer constants: ** ** <ul> ** <li> SQLITE_MUTEX_FAST ** <li> SQLITE_MUTEX_RECURSIVE ** <li> SQLITE_MUTEX_STATIC_MASTER ** <li> SQLITE_MUTEX_STATIC_MEM -** <li> SQLITE_MUTEX_STATIC_MEM2 +** <li> SQLITE_MUTEX_STATIC_OPEN ** <li> SQLITE_MUTEX_STATIC_PRNG ** <li> SQLITE_MUTEX_STATIC_LRU -** <li> SQLITE_MUTEX_STATIC_LRU2 -** </ul>)^ +** <li> SQLITE_MUTEX_STATIC_PMEM +** <li> SQLITE_MUTEX_STATIC_APP1 +** <li> SQLITE_MUTEX_STATIC_APP2 +** <li> SQLITE_MUTEX_STATIC_APP3 +** </ul> ** ** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) ** cause sqlite3_mutex_alloc() to create ** a new mutex. ^The new mutex is recursive when SQLITE_MUTEX_RECURSIVE ** is used but not necessarily so when SQLITE_MUTEX_FAST is used. ** The mutex implementation does not need to make a distinction ** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does -** not want to. ^SQLite will only request a recursive mutex in -** cases where it really needs one. ^If a faster non-recursive mutex +** not want to. SQLite will only request a recursive mutex in +** cases where it really needs one. If a faster non-recursive mutex ** implementation is available on the host platform, the mutex subsystem ** might return such a mutex in response to SQLITE_MUTEX_FAST. ** ** ^The other allowed parameters to sqlite3_mutex_alloc() (anything other ** than SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return -** a pointer to a static preexisting mutex. ^Six static mutexes are +** a pointer to a static preexisting mutex. ^Nine static mutexes are ** used by the current version of SQLite. Future versions of SQLite ** may add additional static mutexes. Static mutexes are for internal ** use by SQLite only. Applications that use SQLite mutexes should ** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or ** SQLITE_MUTEX_RECURSIVE. ** ** ^Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST ** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() -** returns a different mutex on every call. ^But for the static +** returns a different mutex on every call. ^For the static ** mutex types, the same mutex is returned on every call that has ** the same type number. ** ** ^The sqlite3_mutex_free() routine deallocates a previously -** allocated dynamic mutex. ^SQLite is careful to deallocate every -** dynamic mutex that it allocates. The dynamic mutexes must not be in -** use when they are deallocated. Attempting to deallocate a static -** mutex results in undefined behavior. ^SQLite never deallocates -** a static mutex. +** allocated dynamic mutex. Attempting to deallocate a static +** mutex results in undefined behavior. ** ** ^The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt ** to enter a mutex. ^If another thread is already within the mutex, ** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return ** SQLITE_BUSY. ^The sqlite3_mutex_try() interface returns [SQLITE_OK] ** upon successful entry. ^(Mutexes created using ** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread. -** In such cases the, +** In such cases, the ** mutex must be exited an equal number of times before another thread -** can enter.)^ ^(If the same thread tries to enter any other -** kind of mutex more than once, the behavior is undefined. -** SQLite will never exhibit -** such behavior in its own use of mutexes.)^ +** can enter.)^ If the same thread tries to enter any mutex other +** than an SQLITE_MUTEX_RECURSIVE more than once, the behavior is undefined. ** ** ^(Some systems (for example, Windows 95) do not support the operation ** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() -** will always return SQLITE_BUSY. The SQLite core only ever uses -** sqlite3_mutex_try() as an optimization so this is acceptable behavior.)^ +** will always return SQLITE_BUSY. The SQLite core only ever uses +** sqlite3_mutex_try() as an optimization so this is acceptable +** behavior.)^ ** ** ^The sqlite3_mutex_leave() routine exits a mutex that was -** previously entered by the same thread. ^(The behavior +** previously entered by the same thread. The behavior ** is undefined if the mutex is not currently entered by the -** calling thread or is not currently allocated. SQLite will -** never do either.)^ +** calling thread or is not currently allocated. ** ** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or ** sqlite3_mutex_leave() is a NULL pointer, then all three routines ** behave as no-ops. ** ** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. */ -SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int); -SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*); -SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*); -SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*); -SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); +SQLITE_API sqlite3_mutex *SQLITE_STDCALL sqlite3_mutex_alloc(int); +SQLITE_API void SQLITE_STDCALL sqlite3_mutex_free(sqlite3_mutex*); +SQLITE_API void SQLITE_STDCALL sqlite3_mutex_enter(sqlite3_mutex*); +SQLITE_API int SQLITE_STDCALL sqlite3_mutex_try(sqlite3_mutex*); +SQLITE_API void SQLITE_STDCALL sqlite3_mutex_leave(sqlite3_mutex*); /* ** CAPI3REF: Mutex Methods Object ** ** An instance of this structure defines the low-level routines ** used to allocate and use mutexes. ** ** Usually, the default mutex implementations provided by SQLite are -** sufficient, however the user has the option of substituting a custom +** sufficient, however the application has the option of substituting a custom ** implementation for specialized deployments or systems for which SQLite -** does not provide a suitable implementation. In this case, the user +** does not provide a suitable implementation. In this case, the application ** creates and populates an instance of this structure to pass ** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option. ** Additionally, an instance of this structure can be used as an ** output variable when querying the system for the current mutex ** implementation, using the [SQLITE_CONFIG_GETMUTEX] option. @@ -6409,17 +6323,17 @@ ** by this structure are not required to handle this case, the results ** of passing a NULL pointer instead of a valid mutex handle are undefined ** (i.e. it is acceptable to provide an implementation that segfaults if ** it is passed a NULL pointer). ** -** The xMutexInit() method must be threadsafe. ^It must be harmless to +** The xMutexInit() method must be threadsafe. It must be harmless to ** invoke xMutexInit() multiple times within the same process and without ** intervening calls to xMutexEnd(). Second and subsequent calls to ** xMutexInit() must be no-ops. ** -** ^xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] -** and its associates). ^Similarly, xMutexAlloc() must not use SQLite memory +** xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] +** and its associates). Similarly, xMutexAlloc() must not use SQLite memory ** allocation for a static mutex. ^However xMutexAlloc() may use SQLite ** memory allocation for a fast or recursive mutex. ** ** ^SQLite will invoke the xMutexEnd() method when [sqlite3_shutdown()] is ** called, but only if the prior call to xMutexInit returned SQLITE_OK. @@ -6441,38 +6355,38 @@ /* ** CAPI3REF: Mutex Verification Routines ** ** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines -** are intended for use inside assert() statements. ^The SQLite core +** are intended for use inside assert() statements. The SQLite core ** never uses these routines except inside an assert() and applications -** are advised to follow the lead of the core. ^The SQLite core only +** are advised to follow the lead of the core. The SQLite core only ** provides implementations for these routines when it is compiled -** with the SQLITE_DEBUG flag. ^External mutex implementations +** with the SQLITE_DEBUG flag. External mutex implementations ** are only required to provide these routines if SQLITE_DEBUG is ** defined and if NDEBUG is not defined. ** -** ^These routines should return true if the mutex in their argument +** These routines should return true if the mutex in their argument ** is held or not held, respectively, by the calling thread. ** -** ^The implementation is not required to provide versions of these +** The implementation is not required to provide versions of these ** routines that actually work. If the implementation does not provide working ** versions of these routines, it should at least provide stubs that always ** return true so that one does not get spurious assertion failures. ** -** ^If the argument to sqlite3_mutex_held() is a NULL pointer then +** If the argument to sqlite3_mutex_held() is a NULL pointer then ** the routine should return 1. This seems counter-intuitive since ** clearly the mutex cannot be held if it does not exist. But ** the reason the mutex does not exist is because the build is not ** using mutexes. And we do not want the assert() containing the ** call to sqlite3_mutex_held() to fail, so a non-zero return is -** the appropriate thing to do. ^The sqlite3_mutex_notheld() +** the appropriate thing to do. The sqlite3_mutex_notheld() ** interface should also return 1 when given a NULL pointer. */ #ifndef NDEBUG -SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); -SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); +SQLITE_API int SQLITE_STDCALL sqlite3_mutex_held(sqlite3_mutex*); +SQLITE_API int SQLITE_STDCALL sqlite3_mutex_notheld(sqlite3_mutex*); #endif /* ** CAPI3REF: Mutex Types ** @@ -6491,10 +6405,13 @@ #define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ #define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_random() */ #define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ #define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ #define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ +#define SQLITE_MUTEX_STATIC_APP1 8 /* For use by application */ +#define SQLITE_MUTEX_STATIC_APP2 9 /* For use by application */ +#define SQLITE_MUTEX_STATIC_APP3 10 /* For use by application */ /* ** CAPI3REF: Retrieve the mutex for a database connection ** ** ^This interface returns a pointer the [sqlite3_mutex] object that @@ -6501,11 +6418,11 @@ ** serializes access to the [database connection] given in the argument ** when the [threading mode] is Serialized. ** ^If the [threading mode] is Single-thread or Multi-thread then this ** routine returns a NULL pointer. */ -SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*); +SQLITE_API sqlite3_mutex *SQLITE_STDCALL sqlite3_db_mutex(sqlite3*); /* ** CAPI3REF: Low-Level Control Of Database Files ** ** ^The [sqlite3_file_control()] interface makes a direct call to the @@ -6535,11 +6452,11 @@ ** an incorrect zDbName and an SQLITE_ERROR return from the underlying ** xFileControl method. ** ** See also: [SQLITE_FCNTL_LOCKSTATE] */ -SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); +SQLITE_API int SQLITE_STDCALL sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); /* ** CAPI3REF: Testing Interface ** ** ^The sqlite3_test_control() interface is used to read out internal @@ -6554,11 +6471,11 @@ ** The details of the operation codes, their meanings, the parameters ** they take, and what they do are all subject to change without notice. ** Unlike most of the SQLite API, this function is not guaranteed to ** operate consistently from one release to the next. */ -SQLITE_API int sqlite3_test_control(int op, ...); +SQLITE_API int SQLITE_CDECL sqlite3_test_control(int op, ...); /* ** CAPI3REF: Testing Interface Operation Codes ** ** These constants are the valid operation code parameters used @@ -6582,17 +6499,23 @@ #define SQLITE_TESTCTRL_RESERVE 14 #define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_ISKEYWORD 16 #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 #define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 -#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 -#define SQLITE_TESTCTRL_LAST 19 +#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */ +#define SQLITE_TESTCTRL_NEVER_CORRUPT 20 +#define SQLITE_TESTCTRL_VDBE_COVERAGE 21 +#define SQLITE_TESTCTRL_BYTEORDER 22 +#define SQLITE_TESTCTRL_ISINIT 23 +#define SQLITE_TESTCTRL_SORTER_MMAP 24 +#define SQLITE_TESTCTRL_IMPOSTER 25 +#define SQLITE_TESTCTRL_LAST 25 /* ** CAPI3REF: SQLite Runtime Status ** -** ^This interface is used to retrieve runtime status information +** ^These interfaces are used to retrieve runtime status information ** about the performance of SQLite, and optionally to reset various ** highwater marks. ^The first argument is an integer code for ** the specific parameter to measure. ^(Recognized integer codes ** are of the form [status parameters | SQLITE_STATUS_...].)^ ** ^The current value of the parameter is returned into *pCurrent. @@ -6602,23 +6525,26 @@ ** value. For those parameters ** nothing is written into *pHighwater and the resetFlag is ignored.)^ ** ^(Other parameters record only the highwater mark and not the current ** value. For these latter parameters nothing is written into *pCurrent.)^ ** -** ^The sqlite3_status() routine returns SQLITE_OK on success and a -** non-zero [error code] on failure. +** ^The sqlite3_status() and sqlite3_status64() routines return +** SQLITE_OK on success and a non-zero [error code] on failure. ** -** This routine is threadsafe but is not atomic. This routine can be -** called while other threads are running the same or different SQLite -** interfaces. However the values returned in *pCurrent and -** *pHighwater reflect the status of SQLite at different points in time -** and it is possible that another thread might change the parameter -** in between the times when *pCurrent and *pHighwater are written. +** If either the current value or the highwater mark is too large to +** be represented by a 32-bit integer, then the values returned by +** sqlite3_status() are undefined. ** ** See also: [sqlite3_db_status()] */ -SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); +SQLITE_API int SQLITE_STDCALL sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); +SQLITE_API int SQLITE_STDCALL sqlite3_status64( + int op, + sqlite3_int64 *pCurrent, + sqlite3_int64 *pHighwater, + int resetFlag +); /* ** CAPI3REF: Status Parameters ** KEYWORDS: {status parameters} @@ -6732,11 +6658,11 @@ ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a ** non-zero [error code] on failure. ** ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. */ -SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); +SQLITE_API int SQLITE_STDCALL sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); /* ** CAPI3REF: Status Parameters for database connections ** KEYWORDS: {SQLITE_DBSTATUS options} ** @@ -6774,25 +6700,25 @@ ** memory already being in use. ** Only the high-water value is meaningful; ** the current value is always zero.)^ ** ** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt> -** <dd>This parameter returns the approximate number of of bytes of heap +** <dd>This parameter returns the approximate number of bytes of heap ** memory used by all pager caches associated with the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. ** ** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt> -** <dd>This parameter returns the approximate number of of bytes of heap +** <dd>This parameter returns the approximate number of bytes of heap ** memory used to store the schema for all databases associated ** with the connection - main, temp, and any [ATTACH]-ed databases.)^ ** ^The full amount of memory used by the schemas is reported, even if the ** schema memory is shared with other database connections due to ** [shared cache mode] being enabled. ** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0. ** ** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt> -** <dd>This parameter returns the approximate number of of bytes of heap +** <dd>This parameter returns the approximate number of bytes of heap ** and lookaside memory used by all prepared statements associated with ** the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_STMT_USED is always 0. ** </dd> ** @@ -6861,11 +6787,11 @@ ** ^If the resetFlg is true, then the counter is reset to zero after this ** interface call returns. ** ** See also: [sqlite3_status()] and [sqlite3_db_status()]. */ -SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); +SQLITE_API int SQLITE_STDCALL sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); /* ** CAPI3REF: Status Parameters for prepared statements ** KEYWORDS: {SQLITE_STMTSTATUS counter} {SQLITE_STMTSTATUS counters} ** @@ -7187,10 +7113,14 @@ ** sqlite3_backup_init(D,N,S,M) identify the [database connection] ** and database name of the source database, respectively. ** ^The source and destination [database connections] (parameters S and D) ** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. +** +** ^A call to sqlite3_backup_init() will fail, returning SQLITE_ERROR, if +** there is already a read or read-write transaction open on the +** destination database. ** ** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is ** returned and an error code and error message are stored in the ** destination [database connection] D. ** ^The error code and message for the failed call to sqlite3_backup_init() @@ -7280,24 +7210,24 @@ ** ** ^A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from sqlite3_backup_step() ** is not a permanent error and does not affect the return value of ** sqlite3_backup_finish(). ** -** [[sqlite3_backup__remaining()]] [[sqlite3_backup_pagecount()]] +** [[sqlite3_backup_remaining()]] [[sqlite3_backup_pagecount()]] ** <b>sqlite3_backup_remaining() and sqlite3_backup_pagecount()</b> ** -** ^Each call to sqlite3_backup_step() sets two values inside -** the [sqlite3_backup] object: the number of pages still to be backed -** up and the total number of pages in the source database file. -** The sqlite3_backup_remaining() and sqlite3_backup_pagecount() interfaces -** retrieve these two values, respectively. -** -** ^The values returned by these functions are only updated by -** sqlite3_backup_step(). ^If the source database is modified during a backup -** operation, then the values are not updated to account for any extra -** pages that need to be updated or the size of the source database file -** changing. +** ^The sqlite3_backup_remaining() routine returns the number of pages still +** to be backed up at the conclusion of the most recent sqlite3_backup_step(). +** ^The sqlite3_backup_pagecount() routine returns the total number of pages +** in the source database at the conclusion of the most recent +** sqlite3_backup_step(). +** ^(The values returned by these functions are only updated by +** sqlite3_backup_step(). If the source database is modified in a way that +** changes the size of the source database or the number of pages remaining, +** those changes are not reflected in the output of sqlite3_backup_pagecount() +** and sqlite3_backup_remaining() until after the next +** sqlite3_backup_step().)^ ** ** <b>Concurrent Usage of Database Handles</b> ** ** ^The source [database connection] may be used by the application for other ** purposes while a backup operation is underway or being initialized. @@ -7326,20 +7256,20 @@ ** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount() ** APIs are not strictly speaking threadsafe. If they are invoked at the ** same time as another thread is invoking sqlite3_backup_step() it is ** possible that they return invalid values. */ -SQLITE_API sqlite3_backup *sqlite3_backup_init( +SQLITE_API sqlite3_backup *SQLITE_STDCALL sqlite3_backup_init( sqlite3 *pDest, /* Destination database handle */ const char *zDestName, /* Destination database name */ sqlite3 *pSource, /* Source database handle */ const char *zSourceName /* Source database name */ ); -SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage); -SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p); -SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p); -SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); +SQLITE_API int SQLITE_STDCALL sqlite3_backup_step(sqlite3_backup *p, int nPage); +SQLITE_API int SQLITE_STDCALL sqlite3_backup_finish(sqlite3_backup *p); +SQLITE_API int SQLITE_STDCALL sqlite3_backup_remaining(sqlite3_backup *p); +SQLITE_API int SQLITE_STDCALL sqlite3_backup_pagecount(sqlite3_backup *p); /* ** CAPI3REF: Unlock Notification ** ** ^When running in shared-cache mode, a database operation may fail with @@ -7451,11 +7381,11 @@ ** by an sqlite3_step() call. ^(If there is a blocking connection, then the ** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in ** the special "DROP TABLE/INDEX" case, the extended error code is just ** SQLITE_LOCKED.)^ */ -SQLITE_API int sqlite3_unlock_notify( +SQLITE_API int SQLITE_STDCALL sqlite3_unlock_notify( sqlite3 *pBlocked, /* Waiting connection */ void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */ void *pNotifyArg /* Argument to pass to xNotify */ ); @@ -7466,12 +7396,12 @@ ** ^The [sqlite3_stricmp()] and [sqlite3_strnicmp()] APIs allow applications ** and extensions to compare the contents of two buffers containing UTF-8 ** strings in a case-independent fashion, using the same definition of "case ** independence" that SQLite uses internally when comparing identifiers. */ -SQLITE_API int sqlite3_stricmp(const char *, const char *); -SQLITE_API int sqlite3_strnicmp(const char *, const char *, int); +SQLITE_API int SQLITE_STDCALL sqlite3_stricmp(const char *, const char *); +SQLITE_API int SQLITE_STDCALL sqlite3_strnicmp(const char *, const char *, int); /* ** CAPI3REF: String Globbing * ** ^The [sqlite3_strglob(P,X)] interface returns zero if string X matches @@ -7482,11 +7412,11 @@ ** sensitive. ** ** Note that this routine returns zero on a match and non-zero if the strings ** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()]. */ -SQLITE_API int sqlite3_strglob(const char *zGlob, const char *zStr); +SQLITE_API int SQLITE_STDCALL sqlite3_strglob(const char *zGlob, const char *zStr); /* ** CAPI3REF: Error Logging Interface ** ** ^The [sqlite3_log()] interface writes a message into the [error log] @@ -7505,22 +7435,20 @@ ** will not use dynamically allocated memory. The log message is stored in ** a fixed-length buffer on the stack. If the log message is longer than ** a few hundred characters, it will be truncated to the length of the ** buffer. */ -SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); +SQLITE_API void SQLITE_CDECL sqlite3_log(int iErrCode, const char *zFormat, ...); /* ** CAPI3REF: Write-Ahead Log Commit Hook ** ** ^The [sqlite3_wal_hook()] function is used to register a callback that -** will be invoked each time a database connection commits data to a -** [write-ahead log] (i.e. whenever a transaction is committed in -** [journal_mode | journal_mode=WAL mode]). +** is invoked each time data is committed to a database in wal mode. ** -** ^The callback is invoked by SQLite after the commit has taken place and -** the associated write-lock on the database released, so the implementation +** ^(The callback is invoked by SQLite after the commit has taken place and +** the associated write-lock on the database released)^, so the implementation ** may read, write or [checkpoint] the database as required. ** ** ^The first parameter passed to the callback function when it is invoked ** is a copy of the third parameter passed to sqlite3_wal_hook() when ** registering the callback. ^The second is a copy of the database handle. @@ -7542,11 +7470,11 @@ ** previously registered write-ahead log callback. ^Note that the ** [sqlite3_wal_autocheckpoint()] interface and the ** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will ** those overwrite any prior [sqlite3_wal_hook()] settings. */ -SQLITE_API void *sqlite3_wal_hook( +SQLITE_API void *SQLITE_STDCALL sqlite3_wal_hook( sqlite3*, int(*)(void *,sqlite3*,const char*,int), void* ); @@ -7566,126 +7494,155 @@ ** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism ** configured by this function. ** ** ^The [wal_autocheckpoint pragma] can be used to invoke this interface ** from SQL. +** +** ^Checkpoints initiated by this mechanism are +** [sqlite3_wal_checkpoint_v2|PASSIVE]. ** ** ^Every new [database connection] defaults to having the auto-checkpoint ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] ** pages. The use of this interface ** is only necessary if the default setting is found to be suboptimal ** for a particular application. */ -SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); - -/* -** CAPI3REF: Checkpoint a database -** -** ^The [sqlite3_wal_checkpoint(D,X)] interface causes database named X -** on [database connection] D to be [checkpointed]. ^If X is NULL or an -** empty string, then a checkpoint is run on all databases of -** connection D. ^If the database connection D is not in -** [WAL | write-ahead log mode] then this interface is a harmless no-op. -** -** ^The [wal_checkpoint pragma] can be used to invoke this interface -** from SQL. ^The [sqlite3_wal_autocheckpoint()] interface and the -** [wal_autocheckpoint pragma] can be used to cause this interface to be -** run whenever the WAL reaches a certain size threshold. -** -** See also: [sqlite3_wal_checkpoint_v2()] -*/ -SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); - -/* -** CAPI3REF: Checkpoint a database -** -** Run a checkpoint operation on WAL database zDb attached to database -** handle db. The specific operation is determined by the value of the -** eMode parameter: -** -** <dl> -** <dt>SQLITE_CHECKPOINT_PASSIVE<dd> -** Checkpoint as many frames as possible without waiting for any database -** readers or writers to finish. Sync the db file if all frames in the log -** are checkpointed. This mode is the same as calling -** sqlite3_wal_checkpoint(). The busy-handler callback is never invoked. -** -** <dt>SQLITE_CHECKPOINT_FULL<dd> -** This mode blocks (calls the busy-handler callback) until there is no -** database writer and all readers are reading from the most recent database -** snapshot. It then checkpoints all frames in the log file and syncs the -** database file. This call blocks database writers while it is running, -** but not database readers. -** -** <dt>SQLITE_CHECKPOINT_RESTART<dd> -** This mode works the same way as SQLITE_CHECKPOINT_FULL, except after -** checkpointing the log file it blocks (calls the busy-handler callback) -** until all readers are reading from the database file only. This ensures -** that the next client to write to the database file restarts the log file -** from the beginning. This call blocks database writers while it is running, -** but not database readers. -** </dl> -** -** If pnLog is not NULL, then *pnLog is set to the total number of frames in -** the log file before returning. If pnCkpt is not NULL, then *pnCkpt is set to -** the total number of checkpointed frames (including any that were already -** checkpointed when this function is called). *pnLog and *pnCkpt may be -** populated even if sqlite3_wal_checkpoint_v2() returns other than SQLITE_OK. -** If no values are available because of an error, they are both set to -1 -** before returning to communicate this to the caller. -** -** All calls obtain an exclusive "checkpoint" lock on the database file. If -** any other process is running a checkpoint operation at the same time, the -** lock cannot be obtained and SQLITE_BUSY is returned. Even if there is a -** busy-handler configured, it will not be invoked in this case. -** -** The SQLITE_CHECKPOINT_FULL and RESTART modes also obtain the exclusive -** "writer" lock on the database file. If the writer lock cannot be obtained -** immediately, and a busy-handler is configured, it is invoked and the writer -** lock retried until either the busy-handler returns 0 or the lock is -** successfully obtained. The busy-handler is also invoked while waiting for -** database readers as described above. If the busy-handler returns 0 before -** the writer lock is obtained or while waiting for database readers, the -** checkpoint operation proceeds from that point in the same way as -** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible -** without blocking any further. SQLITE_BUSY is returned in this case. -** -** If parameter zDb is NULL or points to a zero length string, then the -** specified operation is attempted on all WAL databases. In this case the -** values written to output parameters *pnLog and *pnCkpt are undefined. If -** an SQLITE_BUSY error is encountered when processing one or more of the -** attached WAL databases, the operation is still attempted on any remaining -** attached databases and SQLITE_BUSY is returned to the caller. If any other -** error occurs while processing an attached database, processing is abandoned -** and the error code returned to the caller immediately. If no error -** (SQLITE_BUSY or otherwise) is encountered while processing the attached -** databases, SQLITE_OK is returned. -** -** If database zDb is the name of an attached database that is not in WAL -** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. If -** zDb is not NULL (or a zero length string) and is not the name of any -** attached database, SQLITE_ERROR is returned to the caller. -*/ -SQLITE_API int sqlite3_wal_checkpoint_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_wal_autocheckpoint(sqlite3 *db, int N); + +/* +** CAPI3REF: Checkpoint a database +** +** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to +** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^ +** +** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the +** [write-ahead log] for database X on [database connection] D to be +** transferred into the database file and for the write-ahead log to +** be reset. See the [checkpointing] documentation for addition +** information. +** +** This interface used to be the only way to cause a checkpoint to +** occur. But then the newer and more powerful [sqlite3_wal_checkpoint_v2()] +** interface was added. This interface is retained for backwards +** compatibility and as a convenience for applications that need to manually +** start a callback but which do not need the full power (and corresponding +** complication) of [sqlite3_wal_checkpoint_v2()]. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); + +/* +** CAPI3REF: Checkpoint a database +** +** ^(The sqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint +** operation on database X of [database connection] D in mode M. Status +** information is written back into integers pointed to by L and C.)^ +** ^(The M parameter must be a valid [checkpoint mode]:)^ +** +** <dl> +** <dt>SQLITE_CHECKPOINT_PASSIVE<dd> +** ^Checkpoint as many frames as possible without waiting for any database +** readers or writers to finish, then sync the database file if all frames +** in the log were checkpointed. ^The [busy-handler callback] +** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. +** ^On the other hand, passive mode might leave the checkpoint unfinished +** if there are concurrent readers or writers. +** +** <dt>SQLITE_CHECKPOINT_FULL<dd> +** ^This mode blocks (it invokes the +** [sqlite3_busy_handler|busy-handler callback]) until there is no +** database writer and all readers are reading from the most recent database +** snapshot. ^It then checkpoints all frames in the log file and syncs the +** database file. ^This mode blocks new database writers while it is pending, +** but new database readers are allowed to continue unimpeded. +** +** <dt>SQLITE_CHECKPOINT_RESTART<dd> +** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition +** that after checkpointing the log file it blocks (calls the +** [busy-handler callback]) +** until all readers are reading from the database file only. ^This ensures +** that the next writer will restart the log file from the beginning. +** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new +** database writer attempts while it is pending, but does not impede readers. +** +** <dt>SQLITE_CHECKPOINT_TRUNCATE<dd> +** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the +** addition that it also truncates the log file to zero bytes just prior +** to a successful return. +** </dl> +** +** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in +** the log file or to -1 if the checkpoint could not run because +** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not +** NULL,then *pnCkpt is set to the total number of checkpointed frames in the +** log file (including any that were already checkpointed before the function +** was called) or to -1 if the checkpoint could not run due to an error or +** because the database is not in WAL mode. ^Note that upon successful +** completion of an SQLITE_CHECKPOINT_TRUNCATE, the log file will have been +** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero. +** +** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If +** any other process is running a checkpoint operation at the same time, the +** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a +** busy-handler configured, it will not be invoked in this case. +** +** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the +** exclusive "writer" lock on the database file. ^If the writer lock cannot be +** obtained immediately, and a busy-handler is configured, it is invoked and +** the writer lock retried until either the busy-handler returns 0 or the lock +** is successfully obtained. ^The busy-handler is also invoked while waiting for +** database readers as described above. ^If the busy-handler returns 0 before +** the writer lock is obtained or while waiting for database readers, the +** checkpoint operation proceeds from that point in the same way as +** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible +** without blocking any further. ^SQLITE_BUSY is returned in this case. +** +** ^If parameter zDb is NULL or points to a zero length string, then the +** specified operation is attempted on all WAL databases [attached] to +** [database connection] db. In this case the +** values written to output parameters *pnLog and *pnCkpt are undefined. ^If +** an SQLITE_BUSY error is encountered when processing one or more of the +** attached WAL databases, the operation is still attempted on any remaining +** attached databases and SQLITE_BUSY is returned at the end. ^If any other +** error occurs while processing an attached database, processing is abandoned +** and the error code is returned to the caller immediately. ^If no error +** (SQLITE_BUSY or otherwise) is encountered while processing the attached +** databases, SQLITE_OK is returned. +** +** ^If database zDb is the name of an attached database that is not in WAL +** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. ^If +** zDb is not NULL (or a zero length string) and is not the name of any +** attached database, SQLITE_ERROR is returned to the caller. +** +** ^Unless it returns SQLITE_MISUSE, +** the sqlite3_wal_checkpoint_v2() interface +** sets the error information that is queried by +** [sqlite3_errcode()] and [sqlite3_errmsg()]. +** +** ^The [PRAGMA wal_checkpoint] command can be used to invoke this interface +** from SQL. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_wal_checkpoint_v2( sqlite3 *db, /* Database handle */ const char *zDb, /* Name of attached database (or NULL) */ int eMode, /* SQLITE_CHECKPOINT_* value */ int *pnLog, /* OUT: Size of WAL log in frames */ int *pnCkpt /* OUT: Total number of frames checkpointed */ ); /* -** CAPI3REF: Checkpoint operation parameters +** CAPI3REF: Checkpoint Mode Values +** KEYWORDS: {checkpoint mode} ** -** These constants can be used as the 3rd parameter to -** [sqlite3_wal_checkpoint_v2()]. See the [sqlite3_wal_checkpoint_v2()] -** documentation for additional information about the meaning and use of -** each of these values. +** These constants define all valid values for the "checkpoint mode" passed +** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. +** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the +** meaning of each of these checkpoint modes. */ -#define SQLITE_CHECKPOINT_PASSIVE 0 -#define SQLITE_CHECKPOINT_FULL 1 -#define SQLITE_CHECKPOINT_RESTART 2 +#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ +#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ +#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */ +#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ /* ** CAPI3REF: Virtual Table Interface Configuration ** ** This function may be called by either the [xConnect] or [xCreate] method @@ -7697,11 +7654,11 @@ ** ** At present, there is only one option that may be configured using ** this function. (See [SQLITE_VTAB_CONSTRAINT_SUPPORT].) Further options ** may be added in the future. */ -SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); +SQLITE_API int SQLITE_CDECL sqlite3_vtab_config(sqlite3*, int op, ...); /* ** CAPI3REF: Virtual Table Configuration Options ** ** These macros define the various options to the @@ -7750,14 +7707,15 @@ ** value returned is one of [SQLITE_ROLLBACK], [SQLITE_IGNORE], [SQLITE_FAIL], ** [SQLITE_ABORT], or [SQLITE_REPLACE], according to the [ON CONFLICT] mode ** of the SQL statement that triggered the call to the [xUpdate] method of the ** [virtual table]. */ -SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); +SQLITE_API int SQLITE_STDCALL sqlite3_vtab_on_conflict(sqlite3 *); /* ** CAPI3REF: Conflict resolution modes +** KEYWORDS: {conflict resolution mode} ** ** These constants are returned by [sqlite3_vtab_on_conflict()] to ** inform a [virtual table] implementation what the [ON CONFLICT] mode ** is for the SQL statement being evaluated. ** @@ -7769,10 +7727,110 @@ /* #define SQLITE_IGNORE 2 // Also used by sqlite3_authorizer() callback */ #define SQLITE_FAIL 3 /* #define SQLITE_ABORT 4 // Also an error code */ #define SQLITE_REPLACE 5 +/* +** CAPI3REF: Prepared Statement Scan Status Opcodes +** KEYWORDS: {scanstatus options} +** +** The following constants can be used for the T parameter to the +** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a +** different metric for sqlite3_stmt_scanstatus() to return. +** +** When the value returned to V is a string, space to hold that string is +** managed by the prepared statement S and will be automatically freed when +** S is finalized. +** +** <dl> +** [[SQLITE_SCANSTAT_NLOOP]] <dt>SQLITE_SCANSTAT_NLOOP</dt> +** <dd>^The [sqlite3_int64] variable pointed to by the T parameter will be +** set to the total number of times that the X-th loop has run.</dd> +** +** [[SQLITE_SCANSTAT_NVISIT]] <dt>SQLITE_SCANSTAT_NVISIT</dt> +** <dd>^The [sqlite3_int64] variable pointed to by the T parameter will be set +** to the total number of rows examined by all iterations of the X-th loop.</dd> +** +** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt> +** <dd>^The "double" variable pointed to by the T parameter will be set to the +** query planner's estimate for the average number of rows output from each +** iteration of the X-th loop. If the query planner's estimates was accurate, +** then this value will approximate the quotient NVISIT/NLOOP and the +** product of this value for all prior loops with the same SELECTID will +** be the NLOOP value for the current loop. +** +** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt> +** <dd>^The "const char *" variable pointed to by the T parameter will be set +** to a zero-terminated UTF-8 string containing the name of the index or table +** used for the X-th loop. +** +** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt> +** <dd>^The "const char *" variable pointed to by the T parameter will be set +** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] +** description for the X-th loop. +** +** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECT</dt> +** <dd>^The "int" variable pointed to by the T parameter will be set to the +** "select-id" for the X-th loop. The select-id identifies which query or +** subquery the loop is part of. The main query has a select-id of zero. +** The select-id is the same value as is output in the first column +** of an [EXPLAIN QUERY PLAN] query. +** </dl> +*/ +#define SQLITE_SCANSTAT_NLOOP 0 +#define SQLITE_SCANSTAT_NVISIT 1 +#define SQLITE_SCANSTAT_EST 2 +#define SQLITE_SCANSTAT_NAME 3 +#define SQLITE_SCANSTAT_EXPLAIN 4 +#define SQLITE_SCANSTAT_SELECTID 5 + +/* +** CAPI3REF: Prepared Statement Scan Status +** +** This interface returns information about the predicted and measured +** performance for pStmt. Advanced applications can use this +** interface to compare the predicted and the measured performance and +** issue warnings and/or rerun [ANALYZE] if discrepancies are found. +** +** Since this interface is expected to be rarely used, it is only +** available if SQLite is compiled using the [SQLITE_ENABLE_STMT_SCANSTATUS] +** compile-time option. +** +** The "iScanStatusOp" parameter determines which status information to return. +** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior +** of this interface is undefined. +** ^The requested measurement is written into a variable pointed to by +** the "pOut" parameter. +** Parameter "idx" identifies the specific loop to retrieve statistics for. +** Loops are numbered starting from zero. ^If idx is out of range - less than +** zero or greater than or equal to the total number of loops used to implement +** the statement - a non-zero value is returned and the variable that pOut +** points to is unchanged. +** +** ^Statistics might not be available for all loops in all statements. ^In cases +** where there exist loops with no available statistics, this function behaves +** as if the loop did not exist - it returns non-zero and leave the variable +** that pOut points to unchanged. +** +** See also: [sqlite3_stmt_scanstatus_reset()] +*/ +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + void *pOut /* Result written here */ +); + +/* +** CAPI3REF: Zero Scan-Status Counters +** +** ^Zero all [sqlite3_stmt_scanstatus()] related event counters. +** +** This API is only available if the library is built with pre-processor +** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined. +*/ +SQLITE_API SQLITE_EXPERIMENTAL void SQLITE_STDCALL sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. @@ -7806,25 +7864,31 @@ #if 0 extern "C" { #endif typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry; +typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info; + +/* The double-precision datatype used by RTree depends on the +** SQLITE_RTREE_INT_ONLY compile-time option. +*/ +#ifdef SQLITE_RTREE_INT_ONLY + typedef sqlite3_int64 sqlite3_rtree_dbl; +#else + typedef double sqlite3_rtree_dbl; +#endif /* ** Register a geometry callback named zGeom that can be used as part of an ** R-Tree geometry query as follows: ** ** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zGeom(... params ...) */ -SQLITE_API int sqlite3_rtree_geometry_callback( +SQLITE_API int SQLITE_STDCALL sqlite3_rtree_geometry_callback( sqlite3 *db, const char *zGeom, -#ifdef SQLITE_RTREE_INT_ONLY - int (*xGeom)(sqlite3_rtree_geometry*, int n, sqlite3_int64 *a, int *pRes), -#else - int (*xGeom)(sqlite3_rtree_geometry*, int n, double *a, int *pRes), -#endif + int (*xGeom)(sqlite3_rtree_geometry*, int, sqlite3_rtree_dbl*,int*), void *pContext ); /* @@ -7832,15 +7896,64 @@ ** argument to callbacks registered using rtree_geometry_callback(). */ struct sqlite3_rtree_geometry { void *pContext; /* Copy of pContext passed to s_r_g_c() */ int nParam; /* Size of array aParam[] */ - double *aParam; /* Parameters passed to SQL geom function */ + sqlite3_rtree_dbl *aParam; /* Parameters passed to SQL geom function */ void *pUser; /* Callback implementation user data */ void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */ }; +/* +** Register a 2nd-generation geometry callback named zScore that can be +** used as part of an R-Tree geometry query as follows: +** +** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zQueryFunc(... params ...) +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_rtree_query_callback( + sqlite3 *db, + const char *zQueryFunc, + int (*xQueryFunc)(sqlite3_rtree_query_info*), + void *pContext, + void (*xDestructor)(void*) +); + + +/* +** A pointer to a structure of the following type is passed as the +** argument to scored geometry callback registered using +** sqlite3_rtree_query_callback(). +** +** Note that the first 5 fields of this structure are identical to +** sqlite3_rtree_geometry. This structure is a subclass of +** sqlite3_rtree_geometry. +*/ +struct sqlite3_rtree_query_info { + void *pContext; /* pContext from when function registered */ + int nParam; /* Number of function parameters */ + sqlite3_rtree_dbl *aParam; /* value of function parameters */ + void *pUser; /* callback can use this, if desired */ + void (*xDelUser)(void*); /* function to free pUser */ + sqlite3_rtree_dbl *aCoord; /* Coordinates of node or entry to check */ + unsigned int *anQueue; /* Number of pending entries in the queue */ + int nCoord; /* Number of coordinates */ + int iLevel; /* Level of current node or entry */ + int mxLevel; /* The largest iLevel value in the tree */ + sqlite3_int64 iRowid; /* Rowid for current entry */ + sqlite3_rtree_dbl rParentScore; /* Score of parent node */ + int eParentWithin; /* Visibility of parent node */ + int eWithin; /* OUT: Visiblity */ + sqlite3_rtree_dbl rScore; /* OUT: Write the score here */ +}; + +/* +** Allowed values for sqlite3_rtree_query.eWithin and .eParentWithin. +*/ +#define NOT_WITHIN 0 /* Object completely outside of query region */ +#define PARTLY_WITHIN 1 /* Object partially overlaps query region */ +#define FULLY_WITHIN 2 /* Object fully contained within query region */ + #if 0 } /* end of the 'extern "C"' block */ #endif @@ -7847,10 +7960,497 @@ #endif /* ifndef _SQLITE3RTREE_H_ */ /************** End of sqlite3.h *********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ + +/* +** Include the configuration header output by 'configure' if we're using the +** autoconf-based build +*/ +#ifdef _HAVE_SQLITE_CONFIG_H +#include "config.h" +#endif + +/************** Include sqliteLimit.h in the middle of sqliteInt.h ***********/ +/************** Begin file sqliteLimit.h *************************************/ +/* +** 2007 May 7 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file defines various limits of what SQLite can process. +*/ + +/* +** The maximum length of a TEXT or BLOB in bytes. This also +** limits the size of a row in a table or index. +** +** The hard limit is the ability of a 32-bit signed integer +** to count the size: 2^31-1 or 2147483647. +*/ +#ifndef SQLITE_MAX_LENGTH +# define SQLITE_MAX_LENGTH 1000000000 +#endif + +/* +** This is the maximum number of +** +** * Columns in a table +** * Columns in an index +** * Columns in a view +** * Terms in the SET clause of an UPDATE statement +** * Terms in the result set of a SELECT statement +** * Terms in the GROUP BY or ORDER BY clauses of a SELECT statement. +** * Terms in the VALUES clause of an INSERT statement +** +** The hard upper limit here is 32676. Most database people will +** tell you that in a well-normalized database, you usually should +** not have more than a dozen or so columns in any table. And if +** that is the case, there is no point in having more than a few +** dozen values in any of the other situations described above. +*/ +#ifndef SQLITE_MAX_COLUMN +# define SQLITE_MAX_COLUMN 2000 +#endif + +/* +** The maximum length of a single SQL statement in bytes. +** +** It used to be the case that setting this value to zero would +** turn the limit off. That is no longer true. It is not possible +** to turn this limit off. +*/ +#ifndef SQLITE_MAX_SQL_LENGTH +# define SQLITE_MAX_SQL_LENGTH 1000000000 +#endif + +/* +** The maximum depth of an expression tree. This is limited to +** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might +** want to place more severe limits on the complexity of an +** expression. +** +** A value of 0 used to mean that the limit was not enforced. +** But that is no longer true. The limit is now strictly enforced +** at all times. +*/ +#ifndef SQLITE_MAX_EXPR_DEPTH +# define SQLITE_MAX_EXPR_DEPTH 1000 +#endif + +/* +** The maximum number of terms in a compound SELECT statement. +** The code generator for compound SELECT statements does one +** level of recursion for each term. A stack overflow can result +** if the number of terms is too large. In practice, most SQL +** never has more than 3 or 4 terms. Use a value of 0 to disable +** any limit on the number of terms in a compount SELECT. +*/ +#ifndef SQLITE_MAX_COMPOUND_SELECT +# define SQLITE_MAX_COMPOUND_SELECT 500 +#endif + +/* +** The maximum number of opcodes in a VDBE program. +** Not currently enforced. +*/ +#ifndef SQLITE_MAX_VDBE_OP +# define SQLITE_MAX_VDBE_OP 25000 +#endif + +/* +** The maximum number of arguments to an SQL function. +*/ +#ifndef SQLITE_MAX_FUNCTION_ARG +# define SQLITE_MAX_FUNCTION_ARG 127 +#endif + +/* +** The suggested maximum number of in-memory pages to use for +** the main database table and for temporary tables. +** +** IMPLEMENTATION-OF: R-31093-59126 The default suggested cache size +** is 2000 pages. +** IMPLEMENTATION-OF: R-48205-43578 The default suggested cache size can be +** altered using the SQLITE_DEFAULT_CACHE_SIZE compile-time options. +*/ +#ifndef SQLITE_DEFAULT_CACHE_SIZE +# define SQLITE_DEFAULT_CACHE_SIZE 2000 +#endif + +/* +** The default number of frames to accumulate in the log file before +** checkpointing the database in WAL mode. +*/ +#ifndef SQLITE_DEFAULT_WAL_AUTOCHECKPOINT +# define SQLITE_DEFAULT_WAL_AUTOCHECKPOINT 1000 +#endif + +/* +** The maximum number of attached databases. This must be between 0 +** and 62. The upper bound on 62 is because a 64-bit integer bitmap +** is used internally to track attached databases. +*/ +#ifndef SQLITE_MAX_ATTACHED +# define SQLITE_MAX_ATTACHED 10 +#endif + + +/* +** The maximum value of a ?nnn wildcard that the parser will accept. +*/ +#ifndef SQLITE_MAX_VARIABLE_NUMBER +# define SQLITE_MAX_VARIABLE_NUMBER 999 +#endif + +/* Maximum page size. The upper bound on this value is 65536. This a limit +** imposed by the use of 16-bit offsets within each page. +** +** Earlier versions of SQLite allowed the user to change this value at +** compile time. This is no longer permitted, on the grounds that it creates +** a library that is technically incompatible with an SQLite library +** compiled with a different limit. If a process operating on a database +** with a page-size of 65536 bytes crashes, then an instance of SQLite +** compiled with the default page-size limit will not be able to rollback +** the aborted transaction. This could lead to database corruption. +*/ +#ifdef SQLITE_MAX_PAGE_SIZE +# undef SQLITE_MAX_PAGE_SIZE +#endif +#define SQLITE_MAX_PAGE_SIZE 65536 + + +/* +** The default size of a database page. +*/ +#ifndef SQLITE_DEFAULT_PAGE_SIZE +# define SQLITE_DEFAULT_PAGE_SIZE 1024 +#endif +#if SQLITE_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE +# undef SQLITE_DEFAULT_PAGE_SIZE +# define SQLITE_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE +#endif + +/* +** Ordinarily, if no value is explicitly provided, SQLite creates databases +** with page size SQLITE_DEFAULT_PAGE_SIZE. However, based on certain +** device characteristics (sector-size and atomic write() support), +** SQLite may choose a larger value. This constant is the maximum value +** SQLite will choose on its own. +*/ +#ifndef SQLITE_MAX_DEFAULT_PAGE_SIZE +# define SQLITE_MAX_DEFAULT_PAGE_SIZE 8192 +#endif +#if SQLITE_MAX_DEFAULT_PAGE_SIZE>SQLITE_MAX_PAGE_SIZE +# undef SQLITE_MAX_DEFAULT_PAGE_SIZE +# define SQLITE_MAX_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE +#endif + + +/* +** Maximum number of pages in one database file. +** +** This is really just the default value for the max_page_count pragma. +** This value can be lowered (or raised) at run-time using that the +** max_page_count macro. +*/ +#ifndef SQLITE_MAX_PAGE_COUNT +# define SQLITE_MAX_PAGE_COUNT 1073741823 +#endif + +/* +** Maximum length (in bytes) of the pattern in a LIKE or GLOB +** operator. +*/ +#ifndef SQLITE_MAX_LIKE_PATTERN_LENGTH +# define SQLITE_MAX_LIKE_PATTERN_LENGTH 50000 +#endif + +/* +** Maximum depth of recursion for triggers. +** +** A value of 1 means that a trigger program will not be able to itself +** fire any triggers. A value of 0 means that no trigger programs at all +** may be executed. +*/ +#ifndef SQLITE_MAX_TRIGGER_DEPTH +# define SQLITE_MAX_TRIGGER_DEPTH 1000 +#endif + +/************** End of sqliteLimit.h *****************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + +/* Disable nuisance warnings on Borland compilers */ +#if defined(__BORLANDC__) +#pragma warn -rch /* unreachable code */ +#pragma warn -ccc /* Condition is always true or false */ +#pragma warn -aus /* Assigned value is never used */ +#pragma warn -csu /* Comparing signed and unsigned */ +#pragma warn -spa /* Suspicious pointer arithmetic */ +#endif + +/* +** Include standard header files as necessary +*/ +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif +#ifdef HAVE_INTTYPES_H +#include <inttypes.h> +#endif + +/* +** The following macros are used to cast pointers to integers and +** integers to pointers. The way you do this varies from one compiler +** to the next, so we have developed the following set of #if statements +** to generate appropriate macros for a wide range of compilers. +** +** The correct "ANSI" way to do this is to use the intptr_t type. +** Unfortunately, that typedef is not available on all compilers, or +** if it is available, it requires an #include of specific headers +** that vary from one machine to the next. +** +** Ticket #3860: The llvm-gcc-4.2 compiler from Apple chokes on +** the ((void*)&((char*)0)[X]) construct. But MSVC chokes on ((void*)(X)). +** So we have to define the macros in different ways depending on the +** compiler. +*/ +#if defined(__PTRDIFF_TYPE__) /* This case should work for GCC */ +# define SQLITE_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X)) +# define SQLITE_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X)) +#elif !defined(__GNUC__) /* Works for compilers other than LLVM */ +# define SQLITE_INT_TO_PTR(X) ((void*)&((char*)0)[X]) +# define SQLITE_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) +#elif defined(HAVE_STDINT_H) /* Use this case if we have ANSI headers */ +# define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X)) +# define SQLITE_PTR_TO_INT(X) ((int)(intptr_t)(X)) +#else /* Generates a warning - but it always works */ +# define SQLITE_INT_TO_PTR(X) ((void*)(X)) +# define SQLITE_PTR_TO_INT(X) ((int)(X)) +#endif + +/* +** A macro to hint to the compiler that a function should not be +** inlined. +*/ +#if defined(__GNUC__) +# define SQLITE_NOINLINE __attribute__((noinline)) +#elif defined(_MSC_VER) && _MSC_VER>=1310 +# define SQLITE_NOINLINE __declspec(noinline) +#else +# define SQLITE_NOINLINE +#endif + +/* +** The SQLITE_THREADSAFE macro must be defined as 0, 1, or 2. +** 0 means mutexes are permanently disable and the library is never +** threadsafe. 1 means the library is serialized which is the highest +** level of threadsafety. 2 means the library is multithreaded - multiple +** threads can use SQLite as long as no two threads try to use the same +** database connection at the same time. +** +** Older versions of SQLite used an optional THREADSAFE macro. +** We support that for legacy. +*/ +#if !defined(SQLITE_THREADSAFE) +# if defined(THREADSAFE) +# define SQLITE_THREADSAFE THREADSAFE +# else +# define SQLITE_THREADSAFE 1 /* IMP: R-07272-22309 */ +# endif +#endif + +/* +** Powersafe overwrite is on by default. But can be turned off using +** the -DSQLITE_POWERSAFE_OVERWRITE=0 command-line option. +*/ +#ifndef SQLITE_POWERSAFE_OVERWRITE +# define SQLITE_POWERSAFE_OVERWRITE 1 +#endif + +/* +** EVIDENCE-OF: R-25715-37072 Memory allocation statistics are enabled by +** default unless SQLite is compiled with SQLITE_DEFAULT_MEMSTATUS=0 in +** which case memory allocation statistics are disabled by default. +*/ +#if !defined(SQLITE_DEFAULT_MEMSTATUS) +# define SQLITE_DEFAULT_MEMSTATUS 1 +#endif + +/* +** Exactly one of the following macros must be defined in order to +** specify which memory allocation subsystem to use. +** +** SQLITE_SYSTEM_MALLOC // Use normal system malloc() +** SQLITE_WIN32_MALLOC // Use Win32 native heap API +** SQLITE_ZERO_MALLOC // Use a stub allocator that always fails +** SQLITE_MEMDEBUG // Debugging version of system malloc() +** +** On Windows, if the SQLITE_WIN32_MALLOC_VALIDATE macro is defined and the +** assert() macro is enabled, each call into the Win32 native heap subsystem +** will cause HeapValidate to be called. If heap validation should fail, an +** assertion will be triggered. +** +** If none of the above are defined, then set SQLITE_SYSTEM_MALLOC as +** the default. +*/ +#if defined(SQLITE_SYSTEM_MALLOC) \ + + defined(SQLITE_WIN32_MALLOC) \ + + defined(SQLITE_ZERO_MALLOC) \ + + defined(SQLITE_MEMDEBUG)>1 +# error "Two or more of the following compile-time configuration options\ + are defined but at most one is allowed:\ + SQLITE_SYSTEM_MALLOC, SQLITE_WIN32_MALLOC, SQLITE_MEMDEBUG,\ + SQLITE_ZERO_MALLOC" +#endif +#if defined(SQLITE_SYSTEM_MALLOC) \ + + defined(SQLITE_WIN32_MALLOC) \ + + defined(SQLITE_ZERO_MALLOC) \ + + defined(SQLITE_MEMDEBUG)==0 +# define SQLITE_SYSTEM_MALLOC 1 +#endif + +/* +** If SQLITE_MALLOC_SOFT_LIMIT is not zero, then try to keep the +** sizes of memory allocations below this value where possible. +*/ +#if !defined(SQLITE_MALLOC_SOFT_LIMIT) +# define SQLITE_MALLOC_SOFT_LIMIT 1024 +#endif + +/* +** We need to define _XOPEN_SOURCE as follows in order to enable +** recursive mutexes on most Unix systems and fchmod() on OpenBSD. +** But _XOPEN_SOURCE define causes problems for Mac OS X, so omit +** it. +*/ +#if !defined(_XOPEN_SOURCE) && !defined(__DARWIN__) && !defined(__APPLE__) +# define _XOPEN_SOURCE 600 +#endif + +/* +** NDEBUG and SQLITE_DEBUG are opposites. It should always be true that +** defined(NDEBUG)==!defined(SQLITE_DEBUG). If this is not currently true, +** make it true by defining or undefining NDEBUG. +** +** Setting NDEBUG makes the code smaller and faster by disabling the +** assert() statements in the code. So we want the default action +** to be for NDEBUG to be set and NDEBUG to be undefined only if SQLITE_DEBUG +** is set. Thus NDEBUG becomes an opt-in rather than an opt-out +** feature. +*/ +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif +#if defined(NDEBUG) && defined(SQLITE_DEBUG) +# undef NDEBUG +#endif + +/* +** Enable SQLITE_ENABLE_EXPLAIN_COMMENTS if SQLITE_DEBUG is turned on. +*/ +#if !defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) && defined(SQLITE_DEBUG) +# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 +#endif + +/* +** The testcase() macro is used to aid in coverage testing. When +** doing coverage testing, the condition inside the argument to +** testcase() must be evaluated both true and false in order to +** get full branch coverage. The testcase() macro is inserted +** to help ensure adequate test coverage in places where simple +** condition/decision coverage is inadequate. For example, testcase() +** can be used to make sure boundary values are tested. For +** bitmask tests, testcase() can be used to make sure each bit +** is significant and used at least once. On switch statements +** where multiple cases go to the same block of code, testcase() +** can insure that all cases are evaluated. +** +*/ +#ifdef SQLITE_COVERAGE_TEST +SQLITE_PRIVATE void sqlite3Coverage(int); +# define testcase(X) if( X ){ sqlite3Coverage(__LINE__); } +#else +# define testcase(X) +#endif + +/* +** The TESTONLY macro is used to enclose variable declarations or +** other bits of code that are needed to support the arguments +** within testcase() and assert() macros. +*/ +#if !defined(NDEBUG) || defined(SQLITE_COVERAGE_TEST) +# define TESTONLY(X) X +#else +# define TESTONLY(X) +#endif + +/* +** Sometimes we need a small amount of code such as a variable initialization +** to setup for a later assert() statement. We do not want this code to +** appear when assert() is disabled. The following macro is therefore +** used to contain that setup code. The "VVA" acronym stands for +** "Verification, Validation, and Accreditation". In other words, the +** code within VVA_ONLY() will only run during verification processes. +*/ +#ifndef NDEBUG +# define VVA_ONLY(X) X +#else +# define VVA_ONLY(X) +#endif + +/* +** The ALWAYS and NEVER macros surround boolean expressions which +** are intended to always be true or false, respectively. Such +** expressions could be omitted from the code completely. But they +** are included in a few cases in order to enhance the resilience +** of SQLite to unexpected behavior - to make the code "self-healing" +** or "ductile" rather than being "brittle" and crashing at the first +** hint of unplanned behavior. +** +** In other words, ALWAYS and NEVER are added for defensive code. +** +** When doing coverage testing ALWAYS and NEVER are hard-coded to +** be true and false so that the unreachable code they specify will +** not be counted as untested code. +*/ +#if defined(SQLITE_COVERAGE_TEST) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif + +/* +** Return true (non-zero) if the input is an integer that is too large +** to fit in 32-bits. This macro is used inside of various testcase() +** macros to verify that we have tested SQLite for large-file support. +*/ +#define IS_BIG_INT(X) (((X)&~(i64)0xffffffff)!=0) + +/* +** The macro unlikely() is a hint that surrounds a boolean +** expression that is usually false. Macro likely() surrounds +** a boolean expression that is usually true. These hints could, +** in theory, be used by the compiler to generate better code, but +** currently they are just comments for human readers. +*/ +#define likely(X) (X) +#define unlikely(X) (X) + /************** Include hash.h in the middle of sqliteInt.h ******************/ /************** Begin file hash.h ********************************************/ /* ** 2001 September 22 ** @@ -7910,19 +8510,19 @@ ** be opaque because it is used by macros. */ struct HashElem { HashElem *next, *prev; /* Next and previous elements in the table */ void *data; /* Data associated with this element */ - const char *pKey; int nKey; /* Key associated with this element */ + const char *pKey; /* Key associated with this element */ }; /* ** Access routines. To delete, insert a NULL pointer. */ SQLITE_PRIVATE void sqlite3HashInit(Hash*); -SQLITE_PRIVATE void *sqlite3HashInsert(Hash*, const char *pKey, int nKey, void *pData); -SQLITE_PRIVATE void *sqlite3HashFind(const Hash*, const char *pKey, int nKey); +SQLITE_PRIVATE void *sqlite3HashInsert(Hash*, const char *pKey, void *pData); +SQLITE_PRIVATE void *sqlite3HashFind(const Hash*, const char *pKey); SQLITE_PRIVATE void sqlite3HashClear(Hash*); /* ** Macros for looping over all elements of a hash table. The idiom is ** like this: @@ -7950,167 +8550,169 @@ /************** End of hash.h ************************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ /************** Include parse.h in the middle of sqliteInt.h *****************/ /************** Begin file parse.h *******************************************/ -#define TK_SEMI 1 -#define TK_EXPLAIN 2 -#define TK_QUERY 3 -#define TK_PLAN 4 -#define TK_BEGIN 5 -#define TK_TRANSACTION 6 -#define TK_DEFERRED 7 -#define TK_IMMEDIATE 8 -#define TK_EXCLUSIVE 9 -#define TK_COMMIT 10 -#define TK_END 11 -#define TK_ROLLBACK 12 -#define TK_SAVEPOINT 13 -#define TK_RELEASE 14 -#define TK_TO 15 -#define TK_TABLE 16 -#define TK_CREATE 17 -#define TK_IF 18 -#define TK_NOT 19 -#define TK_EXISTS 20 -#define TK_TEMP 21 -#define TK_LP 22 -#define TK_RP 23 -#define TK_AS 24 -#define TK_COMMA 25 -#define TK_ID 26 -#define TK_INDEXED 27 -#define TK_ABORT 28 -#define TK_ACTION 29 -#define TK_AFTER 30 -#define TK_ANALYZE 31 -#define TK_ASC 32 -#define TK_ATTACH 33 -#define TK_BEFORE 34 -#define TK_BY 35 -#define TK_CASCADE 36 -#define TK_CAST 37 -#define TK_COLUMNKW 38 -#define TK_CONFLICT 39 -#define TK_DATABASE 40 -#define TK_DESC 41 -#define TK_DETACH 42 -#define TK_EACH 43 -#define TK_FAIL 44 -#define TK_FOR 45 -#define TK_IGNORE 46 -#define TK_INITIALLY 47 -#define TK_INSTEAD 48 -#define TK_LIKE_KW 49 -#define TK_MATCH 50 -#define TK_NO 51 -#define TK_KEY 52 -#define TK_OF 53 -#define TK_OFFSET 54 -#define TK_PRAGMA 55 -#define TK_RAISE 56 -#define TK_REPLACE 57 -#define TK_RESTRICT 58 -#define TK_ROW 59 -#define TK_TRIGGER 60 -#define TK_VACUUM 61 -#define TK_VIEW 62 -#define TK_VIRTUAL 63 -#define TK_REINDEX 64 -#define TK_RENAME 65 -#define TK_CTIME_KW 66 -#define TK_ANY 67 -#define TK_OR 68 -#define TK_AND 69 -#define TK_IS 70 -#define TK_BETWEEN 71 -#define TK_IN 72 -#define TK_ISNULL 73 -#define TK_NOTNULL 74 -#define TK_NE 75 -#define TK_EQ 76 -#define TK_GT 77 -#define TK_LE 78 -#define TK_LT 79 -#define TK_GE 80 -#define TK_ESCAPE 81 -#define TK_BITAND 82 -#define TK_BITOR 83 -#define TK_LSHIFT 84 -#define TK_RSHIFT 85 -#define TK_PLUS 86 -#define TK_MINUS 87 -#define TK_STAR 88 -#define TK_SLASH 89 -#define TK_REM 90 -#define TK_CONCAT 91 -#define TK_COLLATE 92 -#define TK_BITNOT 93 -#define TK_STRING 94 -#define TK_JOIN_KW 95 -#define TK_CONSTRAINT 96 -#define TK_DEFAULT 97 -#define TK_NULL 98 -#define TK_PRIMARY 99 -#define TK_UNIQUE 100 -#define TK_CHECK 101 -#define TK_REFERENCES 102 -#define TK_AUTOINCR 103 -#define TK_ON 104 -#define TK_INSERT 105 -#define TK_DELETE 106 -#define TK_UPDATE 107 -#define TK_SET 108 -#define TK_DEFERRABLE 109 -#define TK_FOREIGN 110 -#define TK_DROP 111 -#define TK_UNION 112 -#define TK_ALL 113 -#define TK_EXCEPT 114 -#define TK_INTERSECT 115 -#define TK_SELECT 116 -#define TK_DISTINCT 117 -#define TK_DOT 118 -#define TK_FROM 119 -#define TK_JOIN 120 -#define TK_USING 121 -#define TK_ORDER 122 -#define TK_GROUP 123 -#define TK_HAVING 124 -#define TK_LIMIT 125 -#define TK_WHERE 126 -#define TK_INTO 127 -#define TK_VALUES 128 -#define TK_INTEGER 129 -#define TK_FLOAT 130 -#define TK_BLOB 131 -#define TK_REGISTER 132 -#define TK_VARIABLE 133 -#define TK_CASE 134 -#define TK_WHEN 135 -#define TK_THEN 136 -#define TK_ELSE 137 -#define TK_INDEX 138 -#define TK_ALTER 139 -#define TK_ADD 140 -#define TK_TO_TEXT 141 -#define TK_TO_BLOB 142 -#define TK_TO_NUMERIC 143 -#define TK_TO_INT 144 -#define TK_TO_REAL 145 -#define TK_ISNOT 146 -#define TK_END_OF_FILE 147 -#define TK_ILLEGAL 148 -#define TK_SPACE 149 -#define TK_UNCLOSED_STRING 150 -#define TK_FUNCTION 151 -#define TK_COLUMN 152 -#define TK_AGG_FUNCTION 153 -#define TK_AGG_COLUMN 154 -#define TK_CONST_FUNC 155 -#define TK_UMINUS 156 -#define TK_UPLUS 157 +#define TK_SEMI 1 +#define TK_EXPLAIN 2 +#define TK_QUERY 3 +#define TK_PLAN 4 +#define TK_BEGIN 5 +#define TK_TRANSACTION 6 +#define TK_DEFERRED 7 +#define TK_IMMEDIATE 8 +#define TK_EXCLUSIVE 9 +#define TK_COMMIT 10 +#define TK_END 11 +#define TK_ROLLBACK 12 +#define TK_SAVEPOINT 13 +#define TK_RELEASE 14 +#define TK_TO 15 +#define TK_TABLE 16 +#define TK_CREATE 17 +#define TK_IF 18 +#define TK_NOT 19 +#define TK_EXISTS 20 +#define TK_TEMP 21 +#define TK_LP 22 +#define TK_RP 23 +#define TK_AS 24 +#define TK_WITHOUT 25 +#define TK_COMMA 26 +#define TK_ID 27 +#define TK_INDEXED 28 +#define TK_ABORT 29 +#define TK_ACTION 30 +#define TK_AFTER 31 +#define TK_ANALYZE 32 +#define TK_ASC 33 +#define TK_ATTACH 34 +#define TK_BEFORE 35 +#define TK_BY 36 +#define TK_CASCADE 37 +#define TK_CAST 38 +#define TK_COLUMNKW 39 +#define TK_CONFLICT 40 +#define TK_DATABASE 41 +#define TK_DESC 42 +#define TK_DETACH 43 +#define TK_EACH 44 +#define TK_FAIL 45 +#define TK_FOR 46 +#define TK_IGNORE 47 +#define TK_INITIALLY 48 +#define TK_INSTEAD 49 +#define TK_LIKE_KW 50 +#define TK_MATCH 51 +#define TK_NO 52 +#define TK_KEY 53 +#define TK_OF 54 +#define TK_OFFSET 55 +#define TK_PRAGMA 56 +#define TK_RAISE 57 +#define TK_RECURSIVE 58 +#define TK_REPLACE 59 +#define TK_RESTRICT 60 +#define TK_ROW 61 +#define TK_TRIGGER 62 +#define TK_VACUUM 63 +#define TK_VIEW 64 +#define TK_VIRTUAL 65 +#define TK_WITH 66 +#define TK_REINDEX 67 +#define TK_RENAME 68 +#define TK_CTIME_KW 69 +#define TK_ANY 70 +#define TK_OR 71 +#define TK_AND 72 +#define TK_IS 73 +#define TK_BETWEEN 74 +#define TK_IN 75 +#define TK_ISNULL 76 +#define TK_NOTNULL 77 +#define TK_NE 78 +#define TK_EQ 79 +#define TK_GT 80 +#define TK_LE 81 +#define TK_LT 82 +#define TK_GE 83 +#define TK_ESCAPE 84 +#define TK_BITAND 85 +#define TK_BITOR 86 +#define TK_LSHIFT 87 +#define TK_RSHIFT 88 +#define TK_PLUS 89 +#define TK_MINUS 90 +#define TK_STAR 91 +#define TK_SLASH 92 +#define TK_REM 93 +#define TK_CONCAT 94 +#define TK_COLLATE 95 +#define TK_BITNOT 96 +#define TK_STRING 97 +#define TK_JOIN_KW 98 +#define TK_CONSTRAINT 99 +#define TK_DEFAULT 100 +#define TK_NULL 101 +#define TK_PRIMARY 102 +#define TK_UNIQUE 103 +#define TK_CHECK 104 +#define TK_REFERENCES 105 +#define TK_AUTOINCR 106 +#define TK_ON 107 +#define TK_INSERT 108 +#define TK_DELETE 109 +#define TK_UPDATE 110 +#define TK_SET 111 +#define TK_DEFERRABLE 112 +#define TK_FOREIGN 113 +#define TK_DROP 114 +#define TK_UNION 115 +#define TK_ALL 116 +#define TK_EXCEPT 117 +#define TK_INTERSECT 118 +#define TK_SELECT 119 +#define TK_VALUES 120 +#define TK_DISTINCT 121 +#define TK_DOT 122 +#define TK_FROM 123 +#define TK_JOIN 124 +#define TK_USING 125 +#define TK_ORDER 126 +#define TK_GROUP 127 +#define TK_HAVING 128 +#define TK_LIMIT 129 +#define TK_WHERE 130 +#define TK_INTO 131 +#define TK_INTEGER 132 +#define TK_FLOAT 133 +#define TK_BLOB 134 +#define TK_VARIABLE 135 +#define TK_CASE 136 +#define TK_WHEN 137 +#define TK_THEN 138 +#define TK_ELSE 139 +#define TK_INDEX 140 +#define TK_ALTER 141 +#define TK_ADD 142 +#define TK_TO_TEXT 143 +#define TK_TO_BLOB 144 +#define TK_TO_NUMERIC 145 +#define TK_TO_INT 146 +#define TK_TO_REAL 147 +#define TK_ISNOT 148 +#define TK_END_OF_FILE 149 +#define TK_ILLEGAL 150 +#define TK_SPACE 151 +#define TK_UNCLOSED_STRING 152 +#define TK_FUNCTION 153 +#define TK_COLUMN 154 +#define TK_AGG_FUNCTION 155 +#define TK_AGG_COLUMN 156 +#define TK_UMINUS 157 +#define TK_UPLUS 158 +#define TK_REGISTER 159 /************** End of parse.h ***********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ #include <stdio.h> #include <stdlib.h> @@ -8174,10 +8776,31 @@ */ #ifndef SQLITE_TEMP_STORE # define SQLITE_TEMP_STORE 1 # define SQLITE_TEMP_STORE_xc 1 /* Exclude from ctime.c */ #endif + +/* +** If no value has been provided for SQLITE_MAX_WORKER_THREADS, or if +** SQLITE_TEMP_STORE is set to 3 (never use temporary files), set it +** to zero. +*/ +#if SQLITE_TEMP_STORE==3 || SQLITE_THREADSAFE==0 +# undef SQLITE_MAX_WORKER_THREADS +# define SQLITE_MAX_WORKER_THREADS 0 +#endif +#ifndef SQLITE_MAX_WORKER_THREADS +# define SQLITE_MAX_WORKER_THREADS 8 +#endif +#ifndef SQLITE_DEFAULT_WORKER_THREADS +# define SQLITE_DEFAULT_WORKER_THREADS 0 +#endif +#if SQLITE_DEFAULT_WORKER_THREADS>SQLITE_MAX_WORKER_THREADS +# undef SQLITE_MAX_WORKER_THREADS +# define SQLITE_MAX_WORKER_THREADS SQLITE_DEFAULT_WORKER_THREADS +#endif + /* ** GCC does not define the offsetof() macro so we'll have to do it ** ourselves. */ @@ -8189,10 +8812,15 @@ ** Macros to compute minimum and maximum of two numbers. */ #define MIN(A,B) ((A)<(B)?(A):(B)) #define MAX(A,B) ((A)>(B)?(A):(B)) +/* +** Swap two objects of type TYPE. +*/ +#define SWAP(TYPE,A,B) {TYPE t=A; A=B; B=t;} + /* ** Check to see if this machine uses EBCDIC. (Yes, believe it or ** not, there are still machines out there that use EBCDIC.) */ #if 'A' == '\301' @@ -8272,28 +8900,84 @@ typedef u64 tRowcnt; /* 64-bit only if requested at compile-time */ #else typedef u32 tRowcnt; /* 32-bit is the default */ #endif +/* +** Estimated quantities used for query planning are stored as 16-bit +** logarithms. For quantity X, the value stored is 10*log2(X). This +** gives a possible range of values of approximately 1.0e986 to 1e-986. +** But the allowed values are "grainy". Not every value is representable. +** For example, quantities 16 and 17 are both represented by a LogEst +** of 40. However, since LogEst quantities are suppose to be estimates, +** not exact values, this imprecision is not a problem. +** +** "LogEst" is short for "Logarithmic Estimate". +** +** Examples: +** 1 -> 0 20 -> 43 10000 -> 132 +** 2 -> 10 25 -> 46 25000 -> 146 +** 3 -> 16 100 -> 66 1000000 -> 199 +** 4 -> 20 1000 -> 99 1048576 -> 200 +** 10 -> 33 1024 -> 100 4294967296 -> 320 +** +** The LogEst can be negative to indicate fractional values. +** Examples: +** +** 0.5 -> -10 0.1 -> -33 0.0625 -> -40 +*/ +typedef INT16_TYPE LogEst; + +/* +** Set the SQLITE_PTRSIZE macro to the number of bytes in a pointer +*/ +#ifndef SQLITE_PTRSIZE +# if defined(__SIZEOF_POINTER__) +# define SQLITE_PTRSIZE __SIZEOF_POINTER__ +# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(_M_ARM) || defined(__arm__) || defined(__x86) +# define SQLITE_PTRSIZE 4 +# else +# define SQLITE_PTRSIZE 8 +# endif +#endif + /* ** Macros to determine whether the machine is big or little endian, -** evaluated at runtime. +** and whether or not that determination is run-time or compile-time. +** +** For best performance, an attempt is made to guess at the byte-order +** using C-preprocessor macros. If that is unsuccessful, or if +** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined +** at run-time. */ #ifdef SQLITE_AMALGAMATION SQLITE_PRIVATE const int sqlite3one = 1; #else SQLITE_PRIVATE const int sqlite3one; #endif -#if defined(i386) || defined(__i386__) || defined(_M_IX86)\ - || defined(__x86_64) || defined(__x86_64__) +#if (defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ + defined(__arm__)) && !defined(SQLITE_RUNTIME_BYTEORDER) +# define SQLITE_BYTEORDER 1234 # define SQLITE_BIGENDIAN 0 # define SQLITE_LITTLEENDIAN 1 # define SQLITE_UTF16NATIVE SQLITE_UTF16LE -#else +#endif +#if (defined(sparc) || defined(__ppc__)) \ + && !defined(SQLITE_RUNTIME_BYTEORDER) +# define SQLITE_BYTEORDER 4321 +# define SQLITE_BIGENDIAN 1 +# define SQLITE_LITTLEENDIAN 0 +# define SQLITE_UTF16NATIVE SQLITE_UTF16BE +#endif +#if !defined(SQLITE_BYTEORDER) +# define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */ # define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0) # define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1) -# define SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE) +# define SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE) #endif /* ** Constants for the largest and smallest possible 64-bit signed integers. ** These macros are designed to work correctly on both 32-bit and 64-bit @@ -8317,11 +9001,11 @@ ** Assert that the pointer X is aligned to an 8-byte boundary. This ** macro is used only within assert() to verify that the code gets ** all alignment restrictions correct. ** ** Except, if SQLITE_4_BYTE_ALIGNED_MALLOC is defined, then the -** underlying malloc() implemention might return us 4-byte aligned +** underlying malloc() implementation might return us 4-byte aligned ** pointers. In that case, only verify 4-byte alignment. */ #ifdef SQLITE_4_BYTE_ALIGNED_MALLOC # define EIGHT_BYTE_ALIGNMENT(X) ((((char*)(X) - (char*)0)&3)==0) #else @@ -8384,10 +9068,20 @@ # define SQLITE_ENABLE_STAT3_OR_STAT4 1 #elif SQLITE_ENABLE_STAT3_OR_STAT4 # undef SQLITE_ENABLE_STAT3_OR_STAT4 #endif +/* +** SELECTTRACE_ENABLED will be either 1 or 0 depending on whether or not +** the Select query generator tracing logic is turned on. +*/ +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_SELECTTRACE) +# define SELECTTRACE_ENABLED 1 +#else +# define SELECTTRACE_ENABLED 0 +#endif + /* ** An instance of the following structure is used to store the busy-handler ** callback for a given sqlite handle. ** ** The sqlite.busyHandler member of the sqlite struct contains the busy @@ -8457,12 +9151,12 @@ */ #ifdef SQLITE_OMIT_WSD #define SQLITE_WSD const #define GLOBAL(t,v) (*(t*)sqlite3_wsd_find((void*)&(v), sizeof(v))) #define sqlite3GlobalConfig GLOBAL(struct Sqlite3Config, sqlite3Config) -SQLITE_API int sqlite3_wsd_init(int N, int J); -SQLITE_API void *sqlite3_wsd_find(void *K, int L); +SQLITE_API int SQLITE_STDCALL sqlite3_wsd_init(int N, int J); +SQLITE_API void *SQLITE_STDCALL sqlite3_wsd_find(void *K, int L); #else #define SQLITE_WSD #define GLOBAL(t,v) v #define sqlite3GlobalConfig sqlite3Config #endif @@ -8512,27 +9206,31 @@ typedef struct Lookaside Lookaside; typedef struct LookasideSlot LookasideSlot; typedef struct Module Module; typedef struct NameContext NameContext; typedef struct Parse Parse; +typedef struct PrintfArguments PrintfArguments; typedef struct RowSet RowSet; typedef struct Savepoint Savepoint; typedef struct Select Select; +typedef struct SQLiteThread SQLiteThread; typedef struct SelectDest SelectDest; typedef struct SrcList SrcList; typedef struct StrAccum StrAccum; typedef struct Table Table; typedef struct TableLock TableLock; typedef struct Token Token; +typedef struct TreeView TreeView; typedef struct Trigger Trigger; typedef struct TriggerPrg TriggerPrg; typedef struct TriggerStep TriggerStep; typedef struct UnpackedRecord UnpackedRecord; typedef struct VTable VTable; typedef struct VtabCtx VtabCtx; typedef struct Walker Walker; typedef struct WhereInfo WhereInfo; +typedef struct With With; /* ** Defer sourcing vdbe.h and btree.h until after the "u8" and ** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque ** pointer types (i.e. FuncDef) defined above. @@ -8558,11 +9256,11 @@ #define _BTREE_H_ /* TODO: This definition is just included so other modules compile. It ** needs to be revisited. */ -#define SQLITE_N_BTREE_META 10 +#define SQLITE_N_BTREE_META 16 /* ** If defined as non-zero, auto-vacuum is enabled by default. Otherwise ** it must be turned on for each database using "PRAGMA auto_vacuum = 1". */ @@ -8602,29 +9300,29 @@ #define BTREE_SINGLE 4 /* The file contains at most 1 b-tree */ #define BTREE_UNORDERED 8 /* Use of a hash implementation is OK */ SQLITE_PRIVATE int sqlite3BtreeClose(Btree*); SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree*,int); -SQLITE_PRIVATE int sqlite3BtreeSetMmapLimit(Btree*,sqlite3_int64); +#if SQLITE_MAX_MMAP_SIZE>0 +SQLITE_PRIVATE int sqlite3BtreeSetMmapLimit(Btree*,sqlite3_int64); +#endif SQLITE_PRIVATE int sqlite3BtreeSetPagerFlags(Btree*,unsigned); SQLITE_PRIVATE int sqlite3BtreeSyncDisabled(Btree*); SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix); SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*); SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree*,int); SQLITE_PRIVATE u32 sqlite3BtreeLastPage(Btree*); SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree*,int); -SQLITE_PRIVATE int sqlite3BtreeGetReserve(Btree*); -#if defined(SQLITE_HAS_CODEC) || defined(SQLITE_DEBUG) +SQLITE_PRIVATE int sqlite3BtreeGetOptimalReserve(Btree*); SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p); -#endif SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *, int); SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *); SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree*,int); SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree*, int); SQLITE_PRIVATE int sqlite3BtreeCommit(Btree*); -SQLITE_PRIVATE int sqlite3BtreeRollback(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeRollback(Btree*,int,int); SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree*,int); SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree*, int*, int flags); SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree*); SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree*); SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree*); @@ -8652,11 +9350,12 @@ #define BTREE_INTKEY 1 /* Table has only 64-bit signed integer keys */ #define BTREE_BLOBKEY 2 /* Table has keys only - no data */ SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree*, int, int*); SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree*, int, int*); -SQLITE_PRIVATE void sqlite3BtreeTripAllCursors(Btree*, int); +SQLITE_PRIVATE int sqlite3BtreeClearTableOfCursor(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeTripAllCursors(Btree*, int, int); SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *pBtree, int idx, u32 *pValue); SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value); SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p); @@ -8670,10 +9369,15 @@ ** offset = 36 + (idx * 4) ** ** For example, the free-page-count field is located at byte offset 36 of ** the database file header. The incr-vacuum-flag field is located at ** byte offset 64 (== 36+4*7). +** +** The BTREE_DATA_VERSION value is not really a value stored in the header. +** It is a read-only number computed by the pager. But we merge it with +** the header value access routines since its access pattern is the same. +** Call it a "virtual meta value". */ #define BTREE_FREE_PAGE_COUNT 0 #define BTREE_SCHEMA_VERSION 1 #define BTREE_FILE_FORMAT 2 #define BTREE_DEFAULT_CACHE_SIZE 3 @@ -8680,16 +9384,27 @@ #define BTREE_LARGEST_ROOT_PAGE 4 #define BTREE_TEXT_ENCODING 5 #define BTREE_USER_VERSION 6 #define BTREE_INCR_VACUUM 7 #define BTREE_APPLICATION_ID 8 +#define BTREE_DATA_VERSION 15 /* A virtual meta-value */ /* ** Values that may be OR'd together to form the second argument of an ** sqlite3BtreeCursorHints() call. +** +** The BTREE_BULKLOAD flag is set on index cursors when the index is going +** to be filled with content that is already in sorted order. +** +** The BTREE_SEEK_EQ flag is set on cursors that will get OP_SeekGE or +** OP_SeekLE opcodes for a range search, but where the range of entries +** selected will all have the same key. In other words, the cursor will +** be used only for equality key searches. +** */ -#define BTREE_BULKLOAD 0x00000001 +#define BTREE_BULKLOAD 0x00000001 /* Used to full index in sorted order */ +#define BTREE_SEEK_EQ 0x00000002 /* EQ seeks only - no range seeks */ SQLITE_PRIVATE int sqlite3BtreeCursor( Btree*, /* BTree containing table to open */ int iTable, /* Index of root page */ int wrFlag, /* 1 for writing. 0 for read-only */ @@ -8705,11 +9420,12 @@ UnpackedRecord *pUnKey, i64 intKey, int bias, int *pRes ); -SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor*, int*); +SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor*, int*); SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const void *pKey, i64 nKey, const void *pData, int nData, int nZero, int bias, int seekResult); SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes); @@ -8717,25 +9433,28 @@ SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*); SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeKeySize(BtCursor*, i64 *pSize); SQLITE_PRIVATE int sqlite3BtreeKey(BtCursor*, u32 offset, u32 amt, void*); -SQLITE_PRIVATE const void *sqlite3BtreeKeyFetch(BtCursor*, int *pAmt); -SQLITE_PRIVATE const void *sqlite3BtreeDataFetch(BtCursor*, int *pAmt); +SQLITE_PRIVATE const void *sqlite3BtreeKeyFetch(BtCursor*, u32 *pAmt); +SQLITE_PRIVATE const void *sqlite3BtreeDataFetch(BtCursor*, u32 *pAmt); SQLITE_PRIVATE int sqlite3BtreeDataSize(BtCursor*, u32 *pSize); SQLITE_PRIVATE int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*); -SQLITE_PRIVATE void sqlite3BtreeSetCachedRowid(BtCursor*, sqlite3_int64); -SQLITE_PRIVATE sqlite3_int64 sqlite3BtreeGetCachedRowid(BtCursor*); SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*); SQLITE_PRIVATE struct Pager *sqlite3BtreePager(Btree*); SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*); -SQLITE_PRIVATE void sqlite3BtreeCacheOverflow(BtCursor *); +SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *); SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *); SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBt, int iVersion); SQLITE_PRIVATE void sqlite3BtreeCursorHints(BtCursor *, unsigned int mask); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask); +#endif +SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *pBt); +SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void); #ifndef NDEBUG SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor*); #endif @@ -8858,17 +9577,20 @@ KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */ int *ai; /* Used when p4type is P4_INTARRAY */ SubProgram *pProgram; /* Used when p4type is P4_SUBPROGRAM */ int (*xAdvance)(BtCursor *, int *); } p4; -#ifdef SQLITE_DEBUG +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS char *zComment; /* Comment to improve readability */ #endif #ifdef VDBE_PROFILE - int cnt; /* Number of times this instruction was executed */ + u32 cnt; /* Number of times this instruction was executed */ u64 cycles; /* Total time spent executing this instruction */ #endif +#ifdef SQLITE_VDBE_COVERAGE + int iSrcLine; /* Source-code line that generated this opcode */ +#endif }; typedef struct VdbeOp VdbeOp; /* @@ -8914,19 +9636,15 @@ #define P4_INT32 (-14) /* P4 is a 32-bit signed integer */ #define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ #define P4_SUBPROGRAM (-18) /* P4 is a pointer to a SubProgram structure */ #define P4_ADVANCE (-19) /* P4 is a pointer to BtreeNext() or BtreePrev() */ -/* When adding a P4 argument using P4_KEYINFO, a copy of the KeyInfo structure -** is made. That copy is freed when the Vdbe is finalized. But if the -** argument is P4_KEYINFO_HANDOFF, the passed in pointer is used. It still -** gets freed when the Vdbe is finalized so it still should be obtained -** from a single sqliteMalloc(). But no copy is made and the calling -** function should *not* try to free the KeyInfo. -*/ -#define P4_KEYINFO_HANDOFF (-16) -#define P4_KEYINFO_STATIC (-17) +/* Error message codes for OP_Halt */ +#define P5_ConstraintNotNull 1 +#define P5_ConstraintUnique 2 +#define P5_ConstraintCheck 3 +#define P5_ConstraintFK 4 /* ** The Vdbe.aColName array contains 5n Mem structures, where n is the ** number of columns of data returned by the statement. */ @@ -8959,160 +9677,167 @@ */ /************** Include opcodes.h in the middle of vdbe.h ********************/ /************** Begin file opcodes.h *****************************************/ /* Automatically generated. Do not edit */ /* See the mkopcodeh.awk script for details */ -#define OP_Function 1 -#define OP_Savepoint 2 -#define OP_AutoCommit 3 -#define OP_Transaction 4 -#define OP_SorterNext 5 -#define OP_Prev 6 -#define OP_Next 7 -#define OP_AggStep 8 -#define OP_Checkpoint 9 -#define OP_JournalMode 10 -#define OP_Vacuum 11 -#define OP_VFilter 12 -#define OP_VUpdate 13 -#define OP_Goto 14 -#define OP_Gosub 15 -#define OP_Return 16 -#define OP_Yield 17 -#define OP_HaltIfNull 18 -#define OP_Not 19 /* same as TK_NOT */ -#define OP_Halt 20 -#define OP_Integer 21 -#define OP_Int64 22 -#define OP_String 23 -#define OP_Null 24 -#define OP_Blob 25 -#define OP_Variable 26 -#define OP_Move 27 -#define OP_Copy 28 -#define OP_SCopy 29 -#define OP_ResultRow 30 -#define OP_CollSeq 31 -#define OP_AddImm 32 -#define OP_MustBeInt 33 -#define OP_RealAffinity 34 -#define OP_Permutation 35 -#define OP_Compare 36 -#define OP_Jump 37 -#define OP_Once 38 -#define OP_If 39 -#define OP_IfNot 40 -#define OP_Column 41 -#define OP_Affinity 42 -#define OP_MakeRecord 43 -#define OP_Count 44 -#define OP_ReadCookie 45 -#define OP_SetCookie 46 -#define OP_VerifyCookie 47 -#define OP_OpenRead 48 -#define OP_OpenWrite 49 -#define OP_OpenAutoindex 50 -#define OP_OpenEphemeral 51 -#define OP_SorterOpen 52 -#define OP_OpenPseudo 53 -#define OP_Close 54 -#define OP_SeekLt 55 -#define OP_SeekLe 56 -#define OP_SeekGe 57 -#define OP_SeekGt 58 -#define OP_Seek 59 -#define OP_NotFound 60 -#define OP_Found 61 -#define OP_IsUnique 62 -#define OP_NotExists 63 -#define OP_Sequence 64 -#define OP_NewRowid 65 -#define OP_Insert 66 -#define OP_InsertInt 67 -#define OP_Or 68 /* same as TK_OR */ -#define OP_And 69 /* same as TK_AND */ -#define OP_Delete 70 -#define OP_ResetCount 71 -#define OP_SorterCompare 72 -#define OP_IsNull 73 /* same as TK_ISNULL */ -#define OP_NotNull 74 /* same as TK_NOTNULL */ -#define OP_Ne 75 /* same as TK_NE */ -#define OP_Eq 76 /* same as TK_EQ */ -#define OP_Gt 77 /* same as TK_GT */ -#define OP_Le 78 /* same as TK_LE */ -#define OP_Lt 79 /* same as TK_LT */ -#define OP_Ge 80 /* same as TK_GE */ -#define OP_SorterData 81 -#define OP_BitAnd 82 /* same as TK_BITAND */ -#define OP_BitOr 83 /* same as TK_BITOR */ -#define OP_ShiftLeft 84 /* same as TK_LSHIFT */ -#define OP_ShiftRight 85 /* same as TK_RSHIFT */ -#define OP_Add 86 /* same as TK_PLUS */ -#define OP_Subtract 87 /* same as TK_MINUS */ -#define OP_Multiply 88 /* same as TK_STAR */ -#define OP_Divide 89 /* same as TK_SLASH */ -#define OP_Remainder 90 /* same as TK_REM */ -#define OP_Concat 91 /* same as TK_CONCAT */ -#define OP_RowKey 92 -#define OP_BitNot 93 /* same as TK_BITNOT */ -#define OP_String8 94 /* same as TK_STRING */ -#define OP_RowData 95 -#define OP_Rowid 96 -#define OP_NullRow 97 -#define OP_Last 98 -#define OP_SorterSort 99 -#define OP_Sort 100 -#define OP_Rewind 101 -#define OP_SorterInsert 102 -#define OP_IdxInsert 103 -#define OP_IdxDelete 104 -#define OP_IdxRowid 105 -#define OP_IdxLT 106 -#define OP_IdxGE 107 -#define OP_Destroy 108 -#define OP_Clear 109 -#define OP_CreateIndex 110 -#define OP_CreateTable 111 -#define OP_ParseSchema 112 -#define OP_LoadAnalysis 113 -#define OP_DropTable 114 -#define OP_DropIndex 115 -#define OP_DropTrigger 116 -#define OP_IntegrityCk 117 -#define OP_RowSetAdd 118 -#define OP_RowSetRead 119 -#define OP_RowSetTest 120 -#define OP_Program 121 -#define OP_Param 122 -#define OP_FkCounter 123 -#define OP_FkIfZero 124 -#define OP_MemMax 125 -#define OP_IfPos 126 -#define OP_IfNeg 127 -#define OP_IfZero 128 -#define OP_AggFinal 129 -#define OP_Real 130 /* same as TK_FLOAT */ -#define OP_IncrVacuum 131 -#define OP_Expire 132 -#define OP_TableLock 133 -#define OP_VBegin 134 -#define OP_VCreate 135 -#define OP_VDestroy 136 -#define OP_VOpen 137 -#define OP_VColumn 138 -#define OP_VNext 139 -#define OP_VRename 140 -#define OP_ToText 141 /* same as TK_TO_TEXT */ -#define OP_ToBlob 142 /* same as TK_TO_BLOB */ -#define OP_ToNumeric 143 /* same as TK_TO_NUMERIC*/ -#define OP_ToInt 144 /* same as TK_TO_INT */ -#define OP_ToReal 145 /* same as TK_TO_REAL */ -#define OP_Pagecount 146 -#define OP_MaxPgcnt 147 -#define OP_Trace 148 -#define OP_Noop 149 -#define OP_Explain 150 +#define OP_Function 1 /* synopsis: r[P3]=func(r[P2@P5]) */ +#define OP_Savepoint 2 +#define OP_AutoCommit 3 +#define OP_Transaction 4 +#define OP_SorterNext 5 +#define OP_PrevIfOpen 6 +#define OP_NextIfOpen 7 +#define OP_Prev 8 +#define OP_Next 9 +#define OP_AggStep 10 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_Checkpoint 11 +#define OP_JournalMode 12 +#define OP_Vacuum 13 +#define OP_VFilter 14 /* synopsis: iplan=r[P3] zplan='P4' */ +#define OP_VUpdate 15 /* synopsis: data=r[P3@P2] */ +#define OP_Goto 16 +#define OP_Gosub 17 +#define OP_Return 18 +#define OP_Not 19 /* same as TK_NOT, synopsis: r[P2]= !r[P1] */ +#define OP_InitCoroutine 20 +#define OP_EndCoroutine 21 +#define OP_Yield 22 +#define OP_HaltIfNull 23 /* synopsis: if r[P3]=null halt */ +#define OP_Halt 24 +#define OP_Integer 25 /* synopsis: r[P2]=P1 */ +#define OP_Int64 26 /* synopsis: r[P2]=P4 */ +#define OP_String 27 /* synopsis: r[P2]='P4' (len=P1) */ +#define OP_Null 28 /* synopsis: r[P2..P3]=NULL */ +#define OP_SoftNull 29 /* synopsis: r[P1]=NULL */ +#define OP_Blob 30 /* synopsis: r[P2]=P4 (len=P1) */ +#define OP_Variable 31 /* synopsis: r[P2]=parameter(P1,P4) */ +#define OP_Move 32 /* synopsis: r[P2@P3]=r[P1@P3] */ +#define OP_Copy 33 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ +#define OP_SCopy 34 /* synopsis: r[P2]=r[P1] */ +#define OP_ResultRow 35 /* synopsis: output=r[P1@P2] */ +#define OP_CollSeq 36 +#define OP_AddImm 37 /* synopsis: r[P1]=r[P1]+P2 */ +#define OP_MustBeInt 38 +#define OP_RealAffinity 39 +#define OP_Cast 40 /* synopsis: affinity(r[P1]) */ +#define OP_Permutation 41 +#define OP_Compare 42 /* synopsis: r[P1@P3] <-> r[P2@P3] */ +#define OP_Jump 43 +#define OP_Once 44 +#define OP_If 45 +#define OP_IfNot 46 +#define OP_Column 47 /* synopsis: r[P3]=PX */ +#define OP_Affinity 48 /* synopsis: affinity(r[P1@P2]) */ +#define OP_MakeRecord 49 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ +#define OP_Count 50 /* synopsis: r[P2]=count() */ +#define OP_ReadCookie 51 +#define OP_SetCookie 52 +#define OP_ReopenIdx 53 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenRead 54 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenWrite 55 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenAutoindex 56 /* synopsis: nColumn=P2 */ +#define OP_OpenEphemeral 57 /* synopsis: nColumn=P2 */ +#define OP_SorterOpen 58 +#define OP_SequenceTest 59 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */ +#define OP_OpenPseudo 60 /* synopsis: P3 columns in r[P2] */ +#define OP_Close 61 +#define OP_SeekLT 62 /* synopsis: key=r[P3@P4] */ +#define OP_SeekLE 63 /* synopsis: key=r[P3@P4] */ +#define OP_SeekGE 64 /* synopsis: key=r[P3@P4] */ +#define OP_SeekGT 65 /* synopsis: key=r[P3@P4] */ +#define OP_Seek 66 /* synopsis: intkey=r[P2] */ +#define OP_NoConflict 67 /* synopsis: key=r[P3@P4] */ +#define OP_NotFound 68 /* synopsis: key=r[P3@P4] */ +#define OP_Found 69 /* synopsis: key=r[P3@P4] */ +#define OP_NotExists 70 /* synopsis: intkey=r[P3] */ +#define OP_Or 71 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */ +#define OP_And 72 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */ +#define OP_Sequence 73 /* synopsis: r[P2]=cursor[P1].ctr++ */ +#define OP_NewRowid 74 /* synopsis: r[P2]=rowid */ +#define OP_Insert 75 /* synopsis: intkey=r[P3] data=r[P2] */ +#define OP_IsNull 76 /* same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */ +#define OP_NotNull 77 /* same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */ +#define OP_Ne 78 /* same as TK_NE, synopsis: if r[P1]!=r[P3] goto P2 */ +#define OP_Eq 79 /* same as TK_EQ, synopsis: if r[P1]==r[P3] goto P2 */ +#define OP_Gt 80 /* same as TK_GT, synopsis: if r[P1]>r[P3] goto P2 */ +#define OP_Le 81 /* same as TK_LE, synopsis: if r[P1]<=r[P3] goto P2 */ +#define OP_Lt 82 /* same as TK_LT, synopsis: if r[P1]<r[P3] goto P2 */ +#define OP_Ge 83 /* same as TK_GE, synopsis: if r[P1]>=r[P3] goto P2 */ +#define OP_InsertInt 84 /* synopsis: intkey=P3 data=r[P2] */ +#define OP_BitAnd 85 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */ +#define OP_BitOr 86 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */ +#define OP_ShiftLeft 87 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<<r[P1] */ +#define OP_ShiftRight 88 /* same as TK_RSHIFT, synopsis: r[P3]=r[P2]>>r[P1] */ +#define OP_Add 89 /* same as TK_PLUS, synopsis: r[P3]=r[P1]+r[P2] */ +#define OP_Subtract 90 /* same as TK_MINUS, synopsis: r[P3]=r[P2]-r[P1] */ +#define OP_Multiply 91 /* same as TK_STAR, synopsis: r[P3]=r[P1]*r[P2] */ +#define OP_Divide 92 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */ +#define OP_Remainder 93 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */ +#define OP_Concat 94 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */ +#define OP_Delete 95 +#define OP_BitNot 96 /* same as TK_BITNOT, synopsis: r[P1]= ~r[P1] */ +#define OP_String8 97 /* same as TK_STRING, synopsis: r[P2]='P4' */ +#define OP_ResetCount 98 +#define OP_SorterCompare 99 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ +#define OP_SorterData 100 /* synopsis: r[P2]=data */ +#define OP_RowKey 101 /* synopsis: r[P2]=key */ +#define OP_RowData 102 /* synopsis: r[P2]=data */ +#define OP_Rowid 103 /* synopsis: r[P2]=rowid */ +#define OP_NullRow 104 +#define OP_Last 105 +#define OP_SorterSort 106 +#define OP_Sort 107 +#define OP_Rewind 108 +#define OP_SorterInsert 109 +#define OP_IdxInsert 110 /* synopsis: key=r[P2] */ +#define OP_IdxDelete 111 /* synopsis: key=r[P2@P3] */ +#define OP_IdxRowid 112 /* synopsis: r[P2]=rowid */ +#define OP_IdxLE 113 /* synopsis: key=r[P3@P4] */ +#define OP_IdxGT 114 /* synopsis: key=r[P3@P4] */ +#define OP_IdxLT 115 /* synopsis: key=r[P3@P4] */ +#define OP_IdxGE 116 /* synopsis: key=r[P3@P4] */ +#define OP_Destroy 117 +#define OP_Clear 118 +#define OP_ResetSorter 119 +#define OP_CreateIndex 120 /* synopsis: r[P2]=root iDb=P1 */ +#define OP_CreateTable 121 /* synopsis: r[P2]=root iDb=P1 */ +#define OP_ParseSchema 122 +#define OP_LoadAnalysis 123 +#define OP_DropTable 124 +#define OP_DropIndex 125 +#define OP_DropTrigger 126 +#define OP_IntegrityCk 127 +#define OP_RowSetAdd 128 /* synopsis: rowset(P1)=r[P2] */ +#define OP_RowSetRead 129 /* synopsis: r[P3]=rowset(P1) */ +#define OP_RowSetTest 130 /* synopsis: if r[P3] in rowset(P1) goto P2 */ +#define OP_Program 131 +#define OP_Param 132 +#define OP_Real 133 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ +#define OP_FkCounter 134 /* synopsis: fkctr[P1]+=P2 */ +#define OP_FkIfZero 135 /* synopsis: if fkctr[P1]==0 goto P2 */ +#define OP_MemMax 136 /* synopsis: r[P1]=max(r[P1],r[P2]) */ +#define OP_IfPos 137 /* synopsis: if r[P1]>0 goto P2 */ +#define OP_IfNeg 138 /* synopsis: r[P1]+=P3, if r[P1]<0 goto P2 */ +#define OP_IfNotZero 139 /* synopsis: if r[P1]!=0 then r[P1]+=P3, goto P2 */ +#define OP_DecrJumpZero 140 /* synopsis: if (--r[P1])==0 goto P2 */ +#define OP_JumpZeroIncr 141 /* synopsis: if (r[P1]++)==0 ) goto P2 */ +#define OP_AggFinal 142 /* synopsis: accum=r[P1] N=P2 */ +#define OP_IncrVacuum 143 +#define OP_Expire 144 +#define OP_TableLock 145 /* synopsis: iDb=P1 root=P2 write=P3 */ +#define OP_VBegin 146 +#define OP_VCreate 147 +#define OP_VDestroy 148 +#define OP_VOpen 149 +#define OP_VColumn 150 /* synopsis: r[P3]=vcolumn(P2) */ +#define OP_VNext 151 +#define OP_VRename 152 +#define OP_Pagecount 153 +#define OP_MaxPgcnt 154 +#define OP_Init 155 /* synopsis: Start at P2 */ +#define OP_Noop 156 +#define OP_Explain 157 /* Properties such as "out2" or "jump" that are specified in ** comments following the "case" for each opcode in the vdbe.c ** are encoded into bitvectors as follows: @@ -9124,52 +9849,55 @@ #define OPFLG_IN3 0x0010 /* in3: P3 is an input */ #define OPFLG_OUT2 0x0020 /* out2: P2 is an output */ #define OPFLG_OUT3 0x0040 /* out3: P3 is an output */ #define OPFLG_INITIALIZER {\ /* 0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,\ -/* 8 */ 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x01,\ -/* 16 */ 0x04, 0x04, 0x10, 0x24, 0x00, 0x02, 0x02, 0x02,\ -/* 24 */ 0x02, 0x02, 0x02, 0x00, 0x00, 0x24, 0x00, 0x00,\ -/* 32 */ 0x04, 0x05, 0x04, 0x00, 0x00, 0x01, 0x01, 0x05,\ -/* 40 */ 0x05, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,\ -/* 48 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11,\ -/* 56 */ 0x11, 0x11, 0x11, 0x08, 0x11, 0x11, 0x11, 0x11,\ -/* 64 */ 0x02, 0x02, 0x00, 0x00, 0x4c, 0x4c, 0x00, 0x00,\ -/* 72 */ 0x00, 0x05, 0x05, 0x15, 0x15, 0x15, 0x15, 0x15,\ -/* 80 */ 0x15, 0x00, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c,\ -/* 88 */ 0x4c, 0x4c, 0x4c, 0x4c, 0x00, 0x24, 0x02, 0x00,\ -/* 96 */ 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x08, 0x08,\ -/* 104 */ 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x02, 0x02,\ -/* 112 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x45,\ -/* 120 */ 0x15, 0x01, 0x02, 0x00, 0x01, 0x08, 0x05, 0x05,\ -/* 128 */ 0x05, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,\ -/* 136 */ 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x04, 0x04,\ -/* 144 */ 0x04, 0x04, 0x02, 0x02, 0x00, 0x00, 0x00,} +/* 8 */ 0x01, 0x01, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00,\ +/* 16 */ 0x01, 0x01, 0x04, 0x24, 0x01, 0x04, 0x05, 0x10,\ +/* 24 */ 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02,\ +/* 32 */ 0x00, 0x00, 0x20, 0x00, 0x00, 0x04, 0x05, 0x04,\ +/* 40 */ 0x04, 0x00, 0x00, 0x01, 0x01, 0x05, 0x05, 0x00,\ +/* 48 */ 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x00, 0x00,\ +/* 56 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11,\ +/* 64 */ 0x11, 0x11, 0x08, 0x11, 0x11, 0x11, 0x11, 0x4c,\ +/* 72 */ 0x4c, 0x02, 0x02, 0x00, 0x05, 0x05, 0x15, 0x15,\ +/* 80 */ 0x15, 0x15, 0x15, 0x15, 0x00, 0x4c, 0x4c, 0x4c,\ +/* 88 */ 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x00,\ +/* 96 */ 0x24, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,\ +/* 104 */ 0x00, 0x01, 0x01, 0x01, 0x01, 0x08, 0x08, 0x00,\ +/* 112 */ 0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x00, 0x00,\ +/* 120 */ 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 128 */ 0x0c, 0x45, 0x15, 0x01, 0x02, 0x02, 0x00, 0x01,\ +/* 136 */ 0x08, 0x05, 0x05, 0x05, 0x05, 0x05, 0x00, 0x01,\ +/* 144 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,\ +/* 152 */ 0x00, 0x02, 0x02, 0x01, 0x00, 0x00,} /************** End of opcodes.h *********************************************/ /************** Continuing where we left off in vdbe.h ***********************/ /* ** Prototypes for the VDBE interface. See comments on the implementation ** for a description of what each of these routines does. */ -SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(sqlite3*); +SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse*); SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe*,int); SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe*,int,int); SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe*,int,int,int); SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe*,int,int,int,int); SQLITE_PRIVATE int sqlite3VdbeAddOp4(Vdbe*,int,int,int,int,const char *zP4,int); SQLITE_PRIVATE int sqlite3VdbeAddOp4Int(Vdbe*,int,int,int,int,int); -SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp); +SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp, int iLineno); SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*); SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, u32 addr, int P1); SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, u32 addr, int P2); SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe*, u32 addr, int P3); SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe*, u8 P5); SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe*, int addr); SQLITE_PRIVATE void sqlite3VdbeChangeToNoop(Vdbe*, int addr); +SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe*, u8 op); SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N); +SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse*, Index*); SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe*, int); SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe*, int); SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe*); @@ -9178,11 +9906,10 @@ SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe*, int); SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe*); #ifdef SQLITE_DEBUG SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *, int); -SQLITE_PRIVATE void sqlite3VdbeTrace(Vdbe*,FILE*); #endif SQLITE_PRIVATE void sqlite3VdbeResetStepResult(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe*); SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe*,int); @@ -9195,28 +9922,87 @@ SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe*, int, u8); SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe*, int); #ifndef SQLITE_OMIT_TRACE SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*); #endif +SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*); SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,UnpackedRecord*); SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo *, char *, int, char **); +typedef int (*RecordCompare)(int,const void*,UnpackedRecord*); +SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord*); + #ifndef SQLITE_OMIT_TRIGGER SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *); #endif - -#ifndef NDEBUG +/* Use SQLITE_ENABLE_COMMENTS to enable generation of extra comments on +** each VDBE opcode. +** +** Use the SQLITE_ENABLE_MODULE_COMMENTS macro to see some extra no-op +** comments in VDBE programs that show key decision points in the code +** generator. +*/ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe*, const char*, ...); # define VdbeComment(X) sqlite3VdbeComment X SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...); # define VdbeNoopComment(X) sqlite3VdbeNoopComment X +# ifdef SQLITE_ENABLE_MODULE_COMMENTS +# define VdbeModuleComment(X) sqlite3VdbeNoopComment X +# else +# define VdbeModuleComment(X) +# endif #else # define VdbeComment(X) # define VdbeNoopComment(X) +# define VdbeModuleComment(X) +#endif + +/* +** The VdbeCoverage macros are used to set a coverage testing point +** for VDBE branch instructions. The coverage testing points are line +** numbers in the sqlite3.c source file. VDBE branch coverage testing +** only works with an amalagmation build. That's ok since a VDBE branch +** coverage build designed for testing the test suite only. No application +** should ever ship with VDBE branch coverage measuring turned on. +** +** VdbeCoverage(v) // Mark the previously coded instruction +** // as a branch +** +** VdbeCoverageIf(v, conditional) // Mark previous if conditional true +** +** VdbeCoverageAlwaysTaken(v) // Previous branch is always taken +** +** VdbeCoverageNeverTaken(v) // Previous branch is never taken +** +** Every VDBE branch operation must be tagged with one of the macros above. +** If not, then when "make test" is run with -DSQLITE_VDBE_COVERAGE and +** -DSQLITE_DEBUG then an ALWAYS() will fail in the vdbeTakeBranch() +** routine in vdbe.c, alerting the developer to the missed tag. +*/ +#ifdef SQLITE_VDBE_COVERAGE +SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe*,int); +# define VdbeCoverage(v) sqlite3VdbeSetLineNumber(v,__LINE__) +# define VdbeCoverageIf(v,x) if(x)sqlite3VdbeSetLineNumber(v,__LINE__) +# define VdbeCoverageAlwaysTaken(v) sqlite3VdbeSetLineNumber(v,2); +# define VdbeCoverageNeverTaken(v) sqlite3VdbeSetLineNumber(v,1); +# define VDBE_OFFSET_LINENO(x) (__LINE__+x) +#else +# define VdbeCoverage(v) +# define VdbeCoverageIf(v,x) +# define VdbeCoverageAlwaysTaken(v) +# define VdbeCoverageNeverTaken(v) +# define VDBE_OFFSET_LINENO(x) 0 +#endif + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +SQLITE_PRIVATE void sqlite3VdbeScanStatus(Vdbe*, int, int, int, LogEst, const char*); +#else +# define sqlite3VdbeScanStatus(a,b,c,d,e) #endif #endif /************** End of vdbe.h ************************************************/ @@ -9359,10 +10145,11 @@ SQLITE_PRIVATE int sqlite3PagerAcquire(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag); #define sqlite3PagerGet(A,B,C) sqlite3PagerAcquire(A,B,C,0) SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno); SQLITE_PRIVATE void sqlite3PagerRef(DbPage*); SQLITE_PRIVATE void sqlite3PagerUnref(DbPage*); +SQLITE_PRIVATE void sqlite3PagerUnrefNotNull(DbPage*); /* Operations on page references. */ SQLITE_PRIVATE int sqlite3PagerWrite(DbPage*); SQLITE_PRIVATE void sqlite3PagerDontWrite(DbPage*); SQLITE_PRIVATE int sqlite3PagerMovepage(Pager*,DbPage*,Pgno,int); @@ -9373,11 +10160,11 @@ /* Functions used to manage pager transactions and savepoints. */ SQLITE_PRIVATE void sqlite3PagerPagecount(Pager*, int*); SQLITE_PRIVATE int sqlite3PagerBegin(Pager*, int exFlag, int); SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int); SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager*); -SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zMaster); SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager*); SQLITE_PRIVATE int sqlite3PagerRollback(Pager*); SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int n); SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint); SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager); @@ -9394,10 +10181,11 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager); #endif /* Functions used to query pager state and configuration. */ SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager*); +SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager*); SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*); SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager*); SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager*, int); SQLITE_PRIVATE const sqlite3_vfs *sqlite3PagerVfs(Pager*); SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager*); @@ -9409,10 +10197,12 @@ SQLITE_PRIVATE void sqlite3PagerClearCache(Pager *); SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *); /* Functions used to truncate the database file. */ SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); + +SQLITE_PRIVATE void sqlite3PagerRekey(DbPage*, Pgno, u16); #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) SQLITE_PRIVATE void *sqlite3PagerCodec(DbPage *); #endif @@ -9505,31 +10295,33 @@ /* Create a new pager cache. ** Under memory stress, invoke xStress to try to make pages clean. ** Only clean and unpinned pages can be reclaimed. */ -SQLITE_PRIVATE void sqlite3PcacheOpen( +SQLITE_PRIVATE int sqlite3PcacheOpen( int szPage, /* Size of every page */ int szExtra, /* Extra space associated with each page */ int bPurgeable, /* True if pages are on backing store */ int (*xStress)(void*, PgHdr*), /* Call to try to make pages clean */ void *pStress, /* Argument to xStress */ PCache *pToInit /* Preallocated space for the PCache */ ); /* Modify the page-size after the cache has been created. */ -SQLITE_PRIVATE void sqlite3PcacheSetPageSize(PCache *, int); +SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *, int); /* Return the size in bytes of a PCache object. Used to preallocate ** storage space. */ SQLITE_PRIVATE int sqlite3PcacheSize(void); /* One release per successful fetch. Page is pinned until released. ** Reference counted. */ -SQLITE_PRIVATE int sqlite3PcacheFetch(PCache*, Pgno, int createFlag, PgHdr**); +SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch(PCache*, Pgno, int createFlag); +SQLITE_PRIVATE int sqlite3PcacheFetchStress(PCache*, Pgno, sqlite3_pcache_page**); +SQLITE_PRIVATE PgHdr *sqlite3PcacheFetchFinish(PCache*, Pgno, sqlite3_pcache_page *pPage); SQLITE_PRIVATE void sqlite3PcacheRelease(PgHdr*); SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr*); /* Remove page from cache */ SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr*); /* Make sure page is marked dirty */ SQLITE_PRIVATE void sqlite3PcacheMakeClean(PgHdr*); /* Mark a single page as clean */ @@ -9595,10 +10387,14 @@ SQLITE_PRIVATE void sqlite3PcacheStats(int*,int*,int*,int*); #endif SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); +/* Return the header size */ +SQLITE_PRIVATE int sqlite3HeaderSizePcache(void); +SQLITE_PRIVATE int sqlite3HeaderSizePcache1(void); + #endif /* _PCACHE_H_ */ /************** End of pcache.h **********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ @@ -9625,87 +10421,75 @@ */ #ifndef _SQLITE_OS_H_ #define _SQLITE_OS_H_ /* -** Figure out if we are dealing with Unix, Windows, or some other -** operating system. After the following block of preprocess macros, -** all of SQLITE_OS_UNIX, SQLITE_OS_WIN, and SQLITE_OS_OTHER -** will defined to either 1 or 0. One of the four will be 1. The other -** three will be 0. +** Attempt to automatically detect the operating system and setup the +** necessary pre-processor macros for it. +*/ +/************** Include os_setup.h in the middle of os.h *********************/ +/************** Begin file os_setup.h ****************************************/ +/* +** 2013 November 25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains pre-processor directives related to operating system +** detection and/or setup. +*/ +#ifndef _OS_SETUP_H_ +#define _OS_SETUP_H_ + +/* +** Figure out if we are dealing with Unix, Windows, or some other operating +** system. +** +** After the following block of preprocess macros, all of SQLITE_OS_UNIX, +** SQLITE_OS_WIN, and SQLITE_OS_OTHER will defined to either 1 or 0. One of +** the three will be 1. The other two will be 0. */ #if defined(SQLITE_OS_OTHER) -# if SQLITE_OS_OTHER==1 -# undef SQLITE_OS_UNIX -# define SQLITE_OS_UNIX 0 -# undef SQLITE_OS_WIN -# define SQLITE_OS_WIN 0 -# else -# undef SQLITE_OS_OTHER -# endif +# if SQLITE_OS_OTHER==1 +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +# else +# undef SQLITE_OS_OTHER +# endif #endif #if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER) -# define SQLITE_OS_OTHER 0 -# ifndef SQLITE_OS_WIN -# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(__BORLANDC__) -# define SQLITE_OS_WIN 1 -# define SQLITE_OS_UNIX 0 -# else -# define SQLITE_OS_WIN 0 -# define SQLITE_OS_UNIX 1 +# define SQLITE_OS_OTHER 0 +# ifndef SQLITE_OS_WIN +# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \ + defined(__MINGW32__) || defined(__BORLANDC__) +# define SQLITE_OS_WIN 1 +# define SQLITE_OS_UNIX 0 +# else +# define SQLITE_OS_WIN 0 +# define SQLITE_OS_UNIX 1 +# endif +# else +# define SQLITE_OS_UNIX 0 +# endif +#else +# ifndef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 # endif -# else -# define SQLITE_OS_UNIX 0 -# endif -#else -# ifndef SQLITE_OS_WIN -# define SQLITE_OS_WIN 0 -# endif -#endif - -#if SQLITE_OS_WIN -# include <windows.h> -#endif - -/* -** Determine if we are dealing with Windows NT. -** -** We ought to be able to determine if we are compiling for win98 or winNT -** using the _WIN32_WINNT macro as follows: -** -** #if defined(_WIN32_WINNT) -** # define SQLITE_OS_WINNT 1 -** #else -** # define SQLITE_OS_WINNT 0 -** #endif -** -** However, vs2005 does not set _WIN32_WINNT by default, as it ought to, -** so the above test does not work. We'll just assume that everything is -** winNT unless the programmer explicitly says otherwise by setting -** SQLITE_OS_WINNT to 0. -*/ -#if SQLITE_OS_WIN && !defined(SQLITE_OS_WINNT) -# define SQLITE_OS_WINNT 1 -#endif - -/* -** Determine if we are dealing with WindowsCE - which has a much -** reduced API. -*/ -#if defined(_WIN32_WCE) -# define SQLITE_OS_WINCE 1 -#else -# define SQLITE_OS_WINCE 0 -#endif - -/* -** Determine if we are dealing with WinRT, which provides only a subset of -** the full Win32 API. -*/ -#if !defined(SQLITE_OS_WINRT) -# define SQLITE_OS_WINRT 0 -#endif +#endif + +#endif /* _OS_SETUP_H_ */ + +/************** End of os_setup.h ********************************************/ +/************** Continuing where we left off in os.h *************************/ /* If the SET_FULLSYNC macro is not defined above, then make it ** a no-op */ #ifndef SET_FULLSYNC @@ -9797,11 +10581,11 @@ ** SHARED_SIZE is the number of bytes available in the pool from which ** a random byte is selected for a shared lock. The pool of bytes for ** shared locks begins at SHARED_FIRST. ** ** The same locking strategy and -** byte ranges are used for Unix. This leaves open the possiblity of having +** byte ranges are used for Unix. This leaves open the possibility of having ** clients on win95, winNT, and unix all talking to the same shared file ** and all locking correctly. To do so would require that samba (or whatever ** tool is being used for file sharing) implements locks correctly between ** windows and unix. I'm guessing that isn't likely to happen, but by ** using the same locking range we are at least open to the possibility. @@ -9916,11 +10700,11 @@ /* ** Figure out what version of the code to use. The choices are ** ** SQLITE_MUTEX_OMIT No mutex logic. Not even stubs. The -** mutexes implemention cannot be overridden +** mutexes implementation cannot be overridden ** at start-time. ** ** SQLITE_MUTEX_NOOP For single-threaded applications. No ** mutual exclusion is provided. But this ** implementation can be overridden at @@ -10005,22 +10789,22 @@ Hash trigHash; /* All triggers indexed by name */ Hash fkeyHash; /* All foreign keys by referenced table name */ Table *pSeqTab; /* The sqlite_sequence table used by AUTOINCREMENT */ u8 file_format; /* Schema format version for this file */ u8 enc; /* Text encoding used by this database */ - u16 flags; /* Flags associated with this schema */ + u16 schemaFlags; /* Flags associated with this schema */ int cache_size; /* Number of pages to use in the cache */ }; /* ** These macros can be used to test, set, or clear bits in the ** Db.pSchema->flags field. */ -#define DbHasProperty(D,I,P) (((D)->aDb[I].pSchema->flags&(P))==(P)) -#define DbHasAnyProperty(D,I,P) (((D)->aDb[I].pSchema->flags&(P))!=0) -#define DbSetProperty(D,I,P) (D)->aDb[I].pSchema->flags|=(P) -#define DbClearProperty(D,I,P) (D)->aDb[I].pSchema->flags&=~(P) +#define DbHasProperty(D,I,P) (((D)->aDb[I].pSchema->schemaFlags&(P))==(P)) +#define DbHasAnyProperty(D,I,P) (((D)->aDb[I].pSchema->schemaFlags&(P))!=0) +#define DbSetProperty(D,I,P) (D)->aDb[I].pSchema->schemaFlags|=(P) +#define DbClearProperty(D,I,P) (D)->aDb[I].pSchema->schemaFlags&=~(P) /* ** Allowed values for the DB.pSchema->flags field. ** ** The DB_SchemaLoaded flag is set after the database schema has been @@ -10036,11 +10820,11 @@ /* ** The number of different kinds of things that can be limited ** using the sqlite3_limit() interface. */ -#define SQLITE_N_LIMIT (SQLITE_LIMIT_TRIGGER_DEPTH+1) +#define SQLITE_N_LIMIT (SQLITE_LIMIT_WORKER_THREADS+1) /* ** Lookaside malloc is a set of fixed-size buffers that can be used ** to satisfy small transient memory allocation requests for objects ** associated with a particular database connection. The use of @@ -10082,10 +10866,49 @@ ** Collisions are on the FuncDef.pHash chain. */ struct FuncDefHash { FuncDef *a[23]; /* Hash table for functions */ }; + +#ifdef SQLITE_USER_AUTHENTICATION +/* +** Information held in the "sqlite3" database connection object and used +** to manage user authentication. +*/ +typedef struct sqlite3_userauth sqlite3_userauth; +struct sqlite3_userauth { + u8 authLevel; /* Current authentication level */ + int nAuthPW; /* Size of the zAuthPW in bytes */ + char *zAuthPW; /* Password used to authenticate */ + char *zAuthUser; /* User name used to authenticate */ +}; + +/* Allowed values for sqlite3_userauth.authLevel */ +#define UAUTH_Unknown 0 /* Authentication not yet checked */ +#define UAUTH_Fail 1 /* User authentication failed */ +#define UAUTH_User 2 /* Authenticated as a normal user */ +#define UAUTH_Admin 3 /* Authenticated as an administrator */ + +/* Functions used only by user authorization logic */ +SQLITE_PRIVATE int sqlite3UserAuthTable(const char*); +SQLITE_PRIVATE int sqlite3UserAuthCheckLogin(sqlite3*,const char*,u8*); +SQLITE_PRIVATE void sqlite3UserAuthInit(sqlite3*); +SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**); + +#endif /* SQLITE_USER_AUTHENTICATION */ + +/* +** typedef for the authorization callback function. +*/ +#ifdef SQLITE_USER_AUTHENTICATION + typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, + const char*, const char*); +#else + typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, + const char*); +#endif + /* ** Each database connection is an instance of the following structure. */ struct sqlite3 { @@ -10100,10 +10923,11 @@ i64 szMmap; /* Default mmap_size setting */ unsigned int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */ int errCode; /* Most recent error code (SQLITE_*) */ int errMask; /* & result codes with this before returning */ u16 dbOptFlags; /* Flags to enable/disable optimizations */ + u8 enc; /* Text encoding */ u8 autoCommit; /* The auto-commit flag. */ u8 temp_store; /* 1: file 2: memory 0: default */ u8 mallocFailed; /* True if we have seen a malloc failure */ u8 dfltLockMode; /* Default locking-mode for attached dbs */ signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */ @@ -10113,20 +10937,23 @@ int nextPagesize; /* Pagesize after VACUUM if >0 */ u32 magic; /* Magic number for detect library misuse */ int nChange; /* Value returned by sqlite3_changes() */ int nTotalChange; /* Value returned by sqlite3_total_changes() */ int aLimit[SQLITE_N_LIMIT]; /* Limits */ + int nMaxSorterMmap; /* Maximum size of regions mapped by sorter */ struct sqlite3InitInfo { /* Information used during initialization */ int newTnum; /* Rootpage of table being initialized */ u8 iDb; /* Which db file is being initialized */ u8 busy; /* TRUE if currently initializing */ u8 orphanTrigger; /* Last statement is orphaned TEMP trigger */ + u8 imposterTable; /* Building an imposter table */ } init; int nVdbeActive; /* Number of VDBEs currently running */ int nVdbeRead; /* Number of active VDBEs that read or write */ int nVdbeWrite; /* Number of active VDBEs that read and write */ int nVdbeExec; /* Number of nested calls to VdbeExec() */ + int nVDestroy; /* Number of active OP_VDestroy operations */ int nExtension; /* Number of loaded extensions */ void **aExtension; /* Array of shared library handles */ void (*xTrace)(void*,const char*); /* Trace function */ void *pTraceArg; /* Argument to the trace function */ void (*xProfile)(void*,const char*,u64); /* Profiling function */ @@ -10149,12 +10976,11 @@ volatile int isInterrupted; /* True if sqlite3_interrupt has been called */ double notUsed1; /* Spacer */ } u1; Lookaside lookaside; /* Lookaside malloc configuration */ #ifndef SQLITE_OMIT_AUTHORIZATION - int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); - /* Access authorization function */ + sqlite3_xauth xAuth; /* Access authorization function */ void *pAuthArg; /* 1st argument to the access auth function */ #endif #ifndef SQLITE_OMIT_PROGRESS_CALLBACK int (*xProgress)(void *); /* The progress callback */ void *pProgressArg; /* Argument to the progress callback */ @@ -10176,11 +11002,10 @@ int nSavepoint; /* Number of non-transaction savepoints */ int nStatement; /* Number of nested statement-transactions */ i64 nDeferredCons; /* Net deferred constraints this transaction. */ i64 nDeferredImmCons; /* Net deferred immediate constraints */ int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ - #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY /* The following variables are all protected by the STATIC_MASTER ** mutex, not by sqlite3.mutex. They are used by code in notify.c. ** ** When X.pUnlockConnection==Y, that means that X is waiting for Y to @@ -10194,16 +11019,20 @@ sqlite3 *pUnlockConnection; /* Connection to watch for unlock */ void *pUnlockArg; /* Argument to xUnlockNotify */ void (*xUnlockNotify)(void **, int); /* Unlock notify callback */ sqlite3 *pNextBlocked; /* Next in list of all blocked connections */ #endif +#ifdef SQLITE_USER_AUTHENTICATION + sqlite3_userauth auth; /* User authentication information */ +#endif }; /* ** A macro to discover the encoding of a database. */ -#define ENC(db) ((db)->aDb[0].pSchema->enc) +#define SCHEMA_ENC(db) ((db)->aDb[0].pSchema->enc) +#define ENC(db) ((db)->enc) /* ** Possible values for the sqlite3.flags. */ #define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */ @@ -10233,10 +11062,11 @@ #define SQLITE_PreferBuiltin 0x00200000 /* Preference to built-in funcs */ #define SQLITE_LoadExtension 0x00400000 /* Enable load_extension */ #define SQLITE_EnableTrigger 0x00800000 /* True to enable triggers */ #define SQLITE_DeferFKs 0x01000000 /* Defer all FK constraints */ #define SQLITE_QueryOnly 0x02000000 /* Disable database changes */ +#define SQLITE_VdbeEQP 0x04000000 /* Debug EXPLAIN QUERY PLAN */ /* ** Bits of the sqlite3.dbOptFlags field that are used by the ** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to @@ -10244,19 +11074,18 @@ */ #define SQLITE_QueryFlattener 0x0001 /* Query flattening */ #define SQLITE_ColumnCache 0x0002 /* Column cache */ #define SQLITE_GroupByOrder 0x0004 /* GROUPBY cover of ORDERBY */ #define SQLITE_FactorOutConst 0x0008 /* Constant factoring */ -#define SQLITE_IdxRealAsInt 0x0010 /* Store REAL as INT in indices */ +/* not used 0x0010 // Was: SQLITE_IdxRealAsInt */ #define SQLITE_DistinctOpt 0x0020 /* DISTINCT using indexes */ #define SQLITE_CoverIdxScan 0x0040 /* Covering index scans */ #define SQLITE_OrderByIdxJoin 0x0080 /* ORDER BY of joins via index */ #define SQLITE_SubqCoroutine 0x0100 /* Evaluate subqueries as coroutines */ #define SQLITE_Transitive 0x0200 /* Transitive constraints */ #define SQLITE_OmitNoopJoin 0x0400 /* Omit unused tables in joins */ -#define SQLITE_Stat3 0x0800 /* Use the SQLITE_STAT3 table */ -#define SQLITE_AdjustOutEst 0x1000 /* Adjust output estimates using WHERE */ +#define SQLITE_Stat34 0x0800 /* Use STAT3 or STAT4 data */ #define SQLITE_AllOpts 0xffff /* All optimizations */ /* ** Macros for testing whether or not optimizations are enabled or disabled. */ @@ -10266,10 +11095,16 @@ #else #define OptimizationDisabled(db, mask) 0 #define OptimizationEnabled(db, mask) 1 #endif +/* +** Return true if it OK to factor constant expressions into the initialization +** code. The argument is a Parse object for the code generator. +*/ +#define ConstFactorOk(P) ((P)->okConstFactor) + /* ** Possible values for the sqlite.magic field. ** The numbers are obtained at random and have no special meaning, other ** than being distinct from one another. */ @@ -10332,10 +11167,12 @@ #define SQLITE_FUNC_LENGTH 0x040 /* Built-in length() function */ #define SQLITE_FUNC_TYPEOF 0x080 /* Built-in typeof() function */ #define SQLITE_FUNC_COUNT 0x100 /* Built-in count(*) aggregate */ #define SQLITE_FUNC_COALESCE 0x200 /* Built-in coalesce() or ifnull() */ #define SQLITE_FUNC_UNLIKELY 0x400 /* Built-in unlikely() function */ +#define SQLITE_FUNC_CONSTANT 0x800 /* Constant inputs give a constant output */ +#define SQLITE_FUNC_MINMAX 0x1000 /* True for min() and max() aggregates */ /* ** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are ** used to create the initializers for the FuncDef structures. ** @@ -10344,10 +11181,13 @@ ** implemented by C function xFunc that accepts nArg arguments. The ** value passed as iArg is cast to a (void*) and made available ** as the user-data (sqlite3_user_data()) for the function. If ** argument bNC is true, then the SQLITE_FUNC_NEEDCOLL flag is set. ** +** VFUNCTION(zName, nArg, iArg, bNC, xFunc) +** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag. +** ** AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal) ** Used to create an aggregate function definition implemented by ** the C functions xStep and xFinal. The first four parameters ** are interpreted in the same way as the first 4 parameters to ** FUNCTION(). @@ -10359,22 +11199,29 @@ ** available as the function user-data (sqlite3_user_data()). The ** FuncDef.flags variable is set to the value passed as the flags ** parameter. */ #define FUNCTION(zName, nArg, iArg, bNC, xFunc) \ + {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0} +#define VFUNCTION(zName, nArg, iArg, bNC, xFunc) \ {nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0} #define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags) \ - {nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags, \ + {nArg,SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0} #define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \ - {nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ pArg, 0, xFunc, 0, 0, #zName, 0, 0} #define LIKEFUNC(zName, nArg, arg, flags) \ - {nArg, SQLITE_UTF8|flags, (void *)arg, 0, likeFunc, 0, 0, #zName, 0, 0} + {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \ + (void *)arg, 0, likeFunc, 0, 0, #zName, 0, 0} #define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal) \ {nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL), \ + SQLITE_INT_TO_PTR(arg), 0, 0, xStep,xFinal,#zName,0,0} +#define AGGREGATE2(zName, nArg, arg, nc, xStep, xFinal, extraFlags) \ + {nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|extraFlags, \ SQLITE_INT_TO_PTR(arg), 0, 0, xStep,xFinal,#zName,0,0} /* ** All current savepoints are stored in a linked list starting at ** sqlite3.pSavepoint. The first element in the list is the most recently @@ -10419,11 +11266,12 @@ char *zDflt; /* Original text of the default value */ char *zType; /* Data type for this column */ char *zColl; /* Collating sequence. If NULL, use the default */ u8 notNull; /* An OE_ code for handling a NOT NULL constraint */ char affinity; /* One of the SQLITE_AFF_... values */ - u16 colFlags; /* Boolean properties. See COLFLAG_ defines below */ + u8 szEst; /* Estimated size of this column. INT==1 */ + u8 colFlags; /* Boolean properties. See COLFLAG_ defines below */ }; /* Allowed values for Column.colFlags: */ #define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */ @@ -10457,38 +11305,44 @@ ** ** These used to have mnemonic name like 'i' for SQLITE_AFF_INTEGER and ** 't' for SQLITE_AFF_TEXT. But we can save a little space and improve ** the speed a little by numbering the values consecutively. ** -** But rather than start with 0 or 1, we begin with 'a'. That way, +** But rather than start with 0 or 1, we begin with 'A'. That way, ** when multiple affinity types are concatenated into a string and ** used as the P4 operand, they will be more readable. ** ** Note also that the numeric types are grouped together so that testing -** for a numeric type is a single comparison. +** for a numeric type is a single comparison. And the NONE type is first. */ -#define SQLITE_AFF_TEXT 'a' -#define SQLITE_AFF_NONE 'b' -#define SQLITE_AFF_NUMERIC 'c' -#define SQLITE_AFF_INTEGER 'd' -#define SQLITE_AFF_REAL 'e' +#define SQLITE_AFF_NONE 'A' +#define SQLITE_AFF_TEXT 'B' +#define SQLITE_AFF_NUMERIC 'C' +#define SQLITE_AFF_INTEGER 'D' +#define SQLITE_AFF_REAL 'E' #define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) /* ** The SQLITE_AFF_MASK values masks off the significant bits of an ** affinity value. */ -#define SQLITE_AFF_MASK 0x67 +#define SQLITE_AFF_MASK 0x47 /* ** Additional bit values that can be ORed with an affinity without ** changing the affinity. +** +** The SQLITE_NOTNULL flag is a combination of NULLEQ and JUMPIFNULL. +** It causes an assert() to fire if either operand to a comparison +** operator is NULL. It is added to certain comparison operators to +** prove that the operands are always NOT NULL. */ -#define SQLITE_JUMPIFNULL 0x08 /* jumps if either operand is NULL */ -#define SQLITE_STOREP2 0x10 /* Store result in reg[P2] rather than jump */ +#define SQLITE_JUMPIFNULL 0x10 /* jumps if either operand is NULL */ +#define SQLITE_STOREP2 0x20 /* Store result in reg[P2] rather than jump */ #define SQLITE_NULLEQ 0x80 /* NULL=NULL */ +#define SQLITE_NOTNULL 0x90 /* Assert that operands are never NULL */ /* ** An object of this type is created for each virtual table present in ** the database schema. ** @@ -10578,15 +11432,19 @@ FKey *pFKey; /* Linked list of all foreign keys in this table */ char *zColAff; /* String defining the affinity of each column */ #ifndef SQLITE_OMIT_CHECK ExprList *pCheck; /* All CHECK constraints */ #endif - tRowcnt nRowEst; /* Estimated rows in table - from sqlite_stat1 table */ + LogEst nRowLogEst; /* Estimated rows in table - from sqlite_stat1 table */ int tnum; /* Root BTree node for this table (see note above) */ i16 iPKey; /* If not negative, use aCol[iPKey] as the primary key */ i16 nCol; /* Number of columns in this table */ u16 nRef; /* Number of pointers to this Table */ + LogEst szTabRow; /* Estimated size of each table row in bytes */ +#ifdef SQLITE_ENABLE_COSTMULT + LogEst costMult; /* Cost multiplier for using this table */ +#endif u8 tabFlags; /* Mask of TF_* values */ u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ #ifndef SQLITE_OMIT_ALTERTABLE int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */ #endif @@ -10599,17 +11457,18 @@ Schema *pSchema; /* Schema that contains this table */ Table *pNextZombie; /* Next on the Parse.pZombieTab list */ }; /* -** Allowed values for Tabe.tabFlags. +** Allowed values for Table.tabFlags. */ #define TF_Readonly 0x01 /* Read-only system table */ #define TF_Ephemeral 0x02 /* An ephemeral table */ #define TF_HasPrimaryKey 0x04 /* Table has a primary key */ #define TF_Autoincrement 0x08 /* Integer primary key is autoincrement */ #define TF_Virtual 0x10 /* Is a virtual table */ +#define TF_WithoutRowid 0x20 /* No rowid used. PRIMARY KEY is the key */ /* ** Test to see whether or not a table is a virtual table. This is ** done as a macro so that it will be optimized out when virtual @@ -10621,10 +11480,13 @@ #else # define IsVirtual(X) 0 # define IsHiddenColumn(X) 0 #endif +/* Does the table have a rowid */ +#define HasRowid(X) (((X)->tabFlags & TF_WithoutRowid)==0) + /* ** Each foreign key constraint is an instance of the following structure. ** ** A foreign key is associated with two tables. The "from" table is ** the table that contains the REFERENCES clause that creates the foreign @@ -10635,30 +11497,39 @@ ** a INTEGER PRIMARY KEY, ** b INTEGER CONSTRAINT fk1 REFERENCES ex2(x) ** ); ** ** For foreign key "fk1", the from-table is "ex1" and the to-table is "ex2". +** Equivalent names: +** +** from-table == child-table +** to-table == parent-table ** ** Each REFERENCES clause generates an instance of the following structure ** which is attached to the from-table. The to-table need not exist when ** the from-table is created. The existence of the to-table is not checked. +** +** The list of all parents for child Table X is held at X.pFKey. +** +** A list of all children for a table named Z (which might not even exist) +** is held in Schema.fkeyHash with a hash key of Z. */ struct FKey { Table *pFrom; /* Table containing the REFERENCES clause (aka: Child) */ - FKey *pNextFrom; /* Next foreign key in pFrom */ + FKey *pNextFrom; /* Next FKey with the same in pFrom. Next parent of pFrom */ char *zTo; /* Name of table that the key points to (aka: Parent) */ - FKey *pNextTo; /* Next foreign key on table named zTo */ - FKey *pPrevTo; /* Previous foreign key on table named zTo */ + FKey *pNextTo; /* Next with the same zTo. Next child of zTo. */ + FKey *pPrevTo; /* Previous with the same zTo */ int nCol; /* Number of columns in this key */ /* EV: R-30323-21917 */ - u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ - u8 aAction[2]; /* ON DELETE and ON UPDATE actions, respectively */ - Trigger *apTrigger[2]; /* Triggers for aAction[] actions */ - struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ - int iFrom; /* Index of column in pFrom */ - char *zCol; /* Name of column in zTo. If 0 use PRIMARY KEY */ - } aCol[1]; /* One entry for each of nCol column s */ + u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ + u8 aAction[2]; /* ON DELETE and ON UPDATE actions, respectively */ + Trigger *apTrigger[2];/* Triggers for aAction[] actions */ + struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ + int iFrom; /* Index of column in pFrom */ + char *zCol; /* Name of column in zTo. If NULL use PRIMARY KEY */ + } aCol[1]; /* One entry for each of nCol columns */ }; /* ** SQLite supports many different ways to resolve a constraint ** error. ROLLBACK processing means that a constraint violation @@ -10694,11 +11565,11 @@ #define OE_Restrict 6 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */ #define OE_SetNull 7 /* Set the foreign key value to NULL */ #define OE_SetDflt 8 /* Set the foreign key value to its default */ #define OE_Cascade 9 /* Cascade the changes */ -#define OE_Default 99 /* Do whatever the default action is */ +#define OE_Default 10 /* Do whatever the default action is */ /* ** An instance of the following structure is passed as the first ** argument to sqlite3VdbeKeyCompare and is used to control the @@ -10707,13 +11578,15 @@ ** Note that aSortOrder[] and aColl[] have nField+1 slots. There ** are nField slots for the columns of an index then one extra slot ** for the rowid at the end. */ struct KeyInfo { - sqlite3 *db; /* The database connection */ + u32 nRef; /* Number of references to this KeyInfo object */ u8 enc; /* Text encoding - one of the SQLITE_UTF* values */ - u16 nField; /* Maximum index for aColl[] and aSortOrder[] */ + u16 nField; /* Number of key columns in the index */ + u16 nXField; /* Number of columns beyond the key columns */ + sqlite3 *db; /* The database connection */ u8 *aSortOrder; /* Sort order for each column. */ CollSeq *aColl[1]; /* Collating sequence for each term of the key */ }; /* @@ -10727,25 +11600,24 @@ ** the OP_MakeRecord opcode of the VDBE and is disassembled by the ** OP_Column opcode. ** ** This structure holds a record that has already been disassembled ** into its constituent fields. +** +** The r1 and r2 member variables are only used by the optimized comparison +** functions vdbeRecordCompareInt() and vdbeRecordCompareString(). */ struct UnpackedRecord { KeyInfo *pKeyInfo; /* Collation and sort-order information */ u16 nField; /* Number of entries in apMem[] */ - u8 flags; /* Boolean settings. UNPACKED_... below */ - i64 rowid; /* Used by UNPACKED_PREFIX_SEARCH */ + i8 default_rc; /* Comparison result if keys are equal */ + u8 errCode; /* Error detected by xRecordCompare (CORRUPT or NOMEM) */ Mem *aMem; /* Values */ + int r1; /* Value to return if (lhs > rhs) */ + int r2; /* Value to return if (rhs < lhs) */ }; -/* -** Allowed values of UnpackedRecord.flags -*/ -#define UNPACKED_INCRKEY 0x01 /* Make this key an epsilon larger */ -#define UNPACKED_PREFIX_MATCH 0x02 /* A prefix match is considered OK */ -#define UNPACKED_PREFIX_SEARCH 0x04 /* Ignore final (rowid) field */ /* ** Each SQL index is represented in memory by an ** instance of the following structure. ** @@ -10771,33 +11643,53 @@ ** algorithm to employ whenever an attempt is made to insert a non-unique ** element. */ struct Index { char *zName; /* Name of this index */ - int *aiColumn; /* Which columns are used by this index. 1st is 0 */ - tRowcnt *aiRowEst; /* From ANALYZE: Est. rows selected by each column */ + i16 *aiColumn; /* Which columns are used by this index. 1st is 0 */ + LogEst *aiRowLogEst; /* From ANALYZE: Est. rows selected by each column */ Table *pTable; /* The SQL table being indexed */ char *zColAff; /* String defining the affinity of each column */ Index *pNext; /* The next index associated with the same table */ Schema *pSchema; /* Schema containing this index */ u8 *aSortOrder; /* for each column: True==DESC, False==ASC */ char **azColl; /* Array of collation sequence names for index */ Expr *pPartIdxWhere; /* WHERE clause for partial indices */ int tnum; /* DB Page containing root of this index */ - u16 nColumn; /* Number of columns in table used by this index */ + LogEst szIdxRow; /* Estimated average row size in bytes */ + u16 nKeyCol; /* Number of columns forming the key */ + u16 nColumn; /* Number of columns stored in the index */ u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ - unsigned autoIndex:2; /* 1==UNIQUE, 2==PRIMARY KEY, 0==CREATE INDEX */ + unsigned idxType:2; /* 1==UNIQUE, 2==PRIMARY KEY, 0==CREATE INDEX */ unsigned bUnordered:1; /* Use this index for == or IN queries only */ unsigned uniqNotNull:1; /* True if UNIQUE and NOT NULL for all columns */ + unsigned isResized:1; /* True if resizeIndexObject() has been called */ + unsigned isCovering:1; /* True if this is a covering index */ + unsigned noSkipScan:1; /* Do not try to use skip-scan if true */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 int nSample; /* Number of elements in aSample[] */ int nSampleCol; /* Size of IndexSample.anEq[] and so on */ tRowcnt *aAvgEq; /* Average nEq values for keys not in aSample */ IndexSample *aSample; /* Samples of the left-most key */ + tRowcnt *aiRowEst; /* Non-logarithmic stat1 data for this index */ + tRowcnt nRowEst0; /* Non-logarithmic number of rows in the index */ #endif }; +/* +** Allowed values for Index.idxType +*/ +#define SQLITE_IDXTYPE_APPDEF 0 /* Created using CREATE INDEX */ +#define SQLITE_IDXTYPE_UNIQUE 1 /* Implements a UNIQUE constraint */ +#define SQLITE_IDXTYPE_PRIMARYKEY 2 /* Is the PRIMARY KEY for the table */ + +/* Return true if index X is a PRIMARY KEY index */ +#define IsPrimaryKeyIndex(X) ((X)->idxType==SQLITE_IDXTYPE_PRIMARYKEY) + +/* Return true if index X is a UNIQUE index */ +#define IsUniqueIndex(X) ((X)->onError!=OE_None) + /* ** Each sample stored in the sqlite_stat3 table is represented in memory ** using a structure of this type. See documentation at the top of the ** analyze.c source file for additional information. */ @@ -10841,10 +11733,11 @@ u8 useSortingIdx; /* In direct mode, reference the sorting index rather ** than the source table */ int sortingIdx; /* Cursor number of the sorting index */ int sortingIdxPTab; /* Cursor number of pseudo-table */ int nSortingColumn; /* Number of columns in the sorting index */ + int mnReg, mxReg; /* Range of registers allocated for aCol and aFunc */ ExprList *pGroupBy; /* The group by clause */ struct AggInfo_col { /* For each column used in source tables */ Table *pTab; /* Source table */ int iTable; /* Cursor number of the source table */ int iColumn; /* Column number within the source table */ @@ -10974,11 +11867,11 @@ int nHeight; /* Height of the tree headed by this node */ #endif int iTable; /* TK_COLUMN: cursor number of table holding column ** TK_REGISTER: register number ** TK_TRIGGER: 1 -> new, 0 -> old - ** EP_Unlikely: 1000 times likelihood */ + ** EP_Unlikely: 134217728 times likelihood */ ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid. ** TK_VARIABLE: variable number (always >= 1). */ i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ i16 iRightJoinTable; /* If EP_FromJoin, the right table of the join */ u8 op2; /* TK_REGISTER: original value of Expr.op @@ -10989,29 +11882,37 @@ }; /* ** The following are the meanings of bits in the Expr.flags field. */ -#define EP_FromJoin 0x000001 /* Originated in ON or USING clause of a join */ +#define EP_FromJoin 0x000001 /* Originates in ON/USING clause of outer join */ #define EP_Agg 0x000002 /* Contains one or more aggregate functions */ #define EP_Resolved 0x000004 /* IDs have been resolved to COLUMNs */ #define EP_Error 0x000008 /* Expression contains one or more errors */ #define EP_Distinct 0x000010 /* Aggregate function with DISTINCT keyword */ #define EP_VarSelect 0x000020 /* pSelect is correlated, not constant */ #define EP_DblQuoted 0x000040 /* token.z was originally in "..." */ #define EP_InfixFunc 0x000080 /* True for an infix function: LIKE, GLOB, etc */ -#define EP_Collate 0x000100 /* Tree contains a TK_COLLATE opeartor */ -#define EP_FixedDest 0x000200 /* Result needed in a specific register */ +#define EP_Collate 0x000100 /* Tree contains a TK_COLLATE operator */ +#define EP_Generic 0x000200 /* Ignore COLLATE or affinity on this tree */ #define EP_IntValue 0x000400 /* Integer value contained in u.iValue */ #define EP_xIsSelect 0x000800 /* x.pSelect is valid (otherwise x.pList is) */ #define EP_Skip 0x001000 /* COLLATE, AS, or UNLIKELY */ #define EP_Reduced 0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */ #define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */ #define EP_Static 0x008000 /* Held in memory not obtained from malloc() */ #define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */ #define EP_NoReduce 0x020000 /* Cannot EXPRDUP_REDUCE this Expr */ #define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */ +#define EP_ConstFunc 0x080000 /* Node is a SQLITE_FUNC_CONSTANT function */ +#define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */ +#define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */ + +/* +** Combinations of two or more EP_* flags +*/ +#define EP_Propagate (EP_Collate|EP_Subquery) /* Propagate these bits up tree */ /* ** These macros can be used to test, set, or clear bits in the ** Expr.flags field. */ @@ -11061,20 +11962,25 @@ ** of the result column in the form: DATABASE.TABLE.COLUMN. This later ** form is used for name resolution with nested FROM clauses. */ struct ExprList { int nExpr; /* Number of expressions on the list */ - int iECursor; /* VDBE Cursor associated with this ExprList */ struct ExprList_item { /* For each expression in the list */ Expr *pExpr; /* The list of expressions */ char *zName; /* Token associated with this expression */ char *zSpan; /* Original text of the expression */ u8 sortOrder; /* 1 for DESC or 0 for ASC */ unsigned done :1; /* A flag to indicate when processing is finished */ unsigned bSpanIsTab :1; /* zSpan holds DB.TABLE.COLUMN */ - u16 iOrderByCol; /* For ORDER BY, column number in result set */ - u16 iAlias; /* Index into Parse.aAlias[] for zName */ + unsigned reusable :1; /* Constant expression is reusable */ + union { + struct { + u16 iOrderByCol; /* For ORDER BY, column number in result set */ + u16 iAlias; /* Index into Parse.aAlias[] for zName */ + } x; + int iConstExprReg; /* Register in which Expr value is cached */ + } u; } *a; /* Alloc a power of two greater or equal to nExpr */ }; /* ** An instance of this structure is used by the parser to record both @@ -11126,10 +12032,11 @@ /* ** A bit in a Bitmask */ #define MASKBIT(n) (((Bitmask)1)<<(n)) +#define MASKBIT32(n) (((unsigned int)1)<<(n)) /* ** The following structure describes the FROM clause of a SELECT statement. ** Each table or subquery in the FROM clause is a separate element of ** the SrcList.a[] array. @@ -11147,25 +12054,27 @@ ** ** In the colUsed field, the high-order bit (bit 63) is set if the table ** contains more than 63 columns and the 64-th or later column is used. */ struct SrcList { - u8 nSrc; /* Number of tables or subqueries in the FROM clause */ - u8 nAlloc; /* Number of entries allocated in a[] below */ + int nSrc; /* Number of tables or subqueries in the FROM clause */ + u32 nAlloc; /* Number of entries allocated in a[] below */ struct SrcList_item { Schema *pSchema; /* Schema to which this item is fixed */ char *zDatabase; /* Name of database holding this table */ char *zName; /* Name of the table */ char *zAlias; /* The "B" part of a "A AS B" phrase. zName is the "A" */ Table *pTab; /* An SQL table corresponding to zName */ Select *pSelect; /* A SELECT statement used in place of a table name */ int addrFillSub; /* Address of subroutine to manifest a subquery */ int regReturn; /* Register holding return address of addrFillSub */ + int regResult; /* Registers holding results of a co-routine */ u8 jointype; /* Type of join between this able and the previous */ unsigned notIndexed :1; /* True if there is a NOT INDEXED clause */ unsigned isCorrelated :1; /* True if sub-query is correlated */ unsigned viaCoroutine :1; /* Implemented as a co-routine */ + unsigned isRecursive :1; /* True for recursive reference in WITH */ #ifndef SQLITE_OMIT_EXPLAIN u8 iSelectId; /* If pSelect!=0, the id of the sub-select in EQP */ #endif int iCursor; /* The VDBE cursor number used to access this table */ Expr *pOn; /* The ON clause of a join */ @@ -11198,14 +12107,16 @@ #define WHERE_ONEPASS_DESIRED 0x0004 /* Want to do one-pass UPDATE/DELETE */ #define WHERE_DUPLICATES_OK 0x0008 /* Ok to return a row more than once */ #define WHERE_OMIT_OPEN_CLOSE 0x0010 /* Table cursors are already open */ #define WHERE_FORCE_TABLE 0x0020 /* Do not use an index-only search */ #define WHERE_ONETABLE_ONLY 0x0040 /* Only code the 1st table in pTabList */ -#define WHERE_AND_ONLY 0x0080 /* Don't use indices for OR terms */ +#define WHERE_NO_AUTOINDEX 0x0080 /* Disallow automatic indexes */ #define WHERE_GROUPBY 0x0100 /* pOrderBy is really a GROUP BY */ #define WHERE_DISTINCTBY 0x0200 /* pOrderby is really a DISTINCT clause */ #define WHERE_WANT_DISTINCT 0x0400 /* All output needs to be distinct */ +#define WHERE_SORTBYGROUP 0x0800 /* Support sqlite3WhereIsSorted() */ +#define WHERE_REOPEN_IDX 0x1000 /* Try to use OP_ReopenIdx */ /* Allowed return values from sqlite3WhereIsDistinct() */ #define WHERE_DISTINCT_NOOP 0 /* DISTINCT keyword not used */ #define WHERE_DISTINCT_UNIQUE 1 /* No duplicates */ @@ -11239,21 +12150,26 @@ ExprList *pEList; /* Optional list of result-set columns */ AggInfo *pAggInfo; /* Information about aggregates at this level */ NameContext *pNext; /* Next outer name context. NULL for outermost */ int nRef; /* Number of names resolved by this context */ int nErr; /* Number of errors encountered while resolving names */ - u8 ncFlags; /* Zero or more NC_* flags defined below */ + u16 ncFlags; /* Zero or more NC_* flags defined below */ }; /* ** Allowed values for the NameContext, ncFlags field. +** +** Note: NC_MinMaxAgg must have the same value as SF_MinMaxAgg and +** SQLITE_FUNC_MINMAX. +** */ -#define NC_AllowAgg 0x01 /* Aggregate functions are allowed here */ -#define NC_HasAgg 0x02 /* One or more aggregate functions seen */ -#define NC_IsCheck 0x04 /* True if resolving names in a CHECK constraint */ -#define NC_InAggFunc 0x08 /* True if analyzing arguments to an agg func */ -#define NC_PartIdx 0x10 /* True if resolving a partial index WHERE */ +#define NC_AllowAgg 0x0001 /* Aggregate functions are allowed here */ +#define NC_HasAgg 0x0002 /* One or more aggregate functions seen */ +#define NC_IsCheck 0x0004 /* True if resolving names in a CHECK constraint */ +#define NC_InAggFunc 0x0008 /* True if analyzing arguments to an agg func */ +#define NC_PartIdx 0x0010 /* True if resolving a partial index WHERE */ +#define NC_MinMaxAgg 0x1000 /* min/max aggregates seen. See note above */ /* ** An instance of the following structure contains all information ** needed to generate code for a single SELECT statement. ** @@ -11276,22 +12192,25 @@ struct Select { ExprList *pEList; /* The fields of the result */ u8 op; /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */ u16 selFlags; /* Various SF_* values */ int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */ - int addrOpenEphm[3]; /* OP_OpenEphem opcodes related to this select */ +#if SELECTTRACE_ENABLED + char zSelName[12]; /* Symbolic name of this SELECT use for debugging */ +#endif + int addrOpenEphm[2]; /* OP_OpenEphem opcodes related to this select */ u64 nSelectRow; /* Estimated number of result rows */ SrcList *pSrc; /* The FROM clause */ Expr *pWhere; /* The WHERE clause */ ExprList *pGroupBy; /* The GROUP BY clause */ Expr *pHaving; /* The HAVING clause */ ExprList *pOrderBy; /* The ORDER BY clause */ Select *pPrior; /* Prior select in a compound select statement */ Select *pNext; /* Next select to the left in a compound */ - Select *pRightmost; /* Right-most select in a compound select statement */ Expr *pLimit; /* LIMIT expression. NULL means not used. */ Expr *pOffset; /* OFFSET expression. NULL means not used. */ + With *pWith; /* WITH clause attached to this select. Or NULL. */ }; /* ** Allowed values for Select.selFlags. The "SF" prefix stands for ** "Select Flag". @@ -11300,46 +12219,113 @@ #define SF_Resolved 0x0002 /* Identifiers have been resolved */ #define SF_Aggregate 0x0004 /* Contains aggregate functions */ #define SF_UsesEphemeral 0x0008 /* Uses the OpenEphemeral opcode */ #define SF_Expanded 0x0010 /* sqlite3SelectExpand() called on this */ #define SF_HasTypeInfo 0x0020 /* FROM subqueries have Table metadata */ -#define SF_UseSorter 0x0040 /* Sort using a sorter */ +#define SF_Compound 0x0040 /* Part of a compound query */ #define SF_Values 0x0080 /* Synthesized from VALUES clause */ -#define SF_Materialize 0x0100 /* Force materialization of views */ +#define SF_AllValues 0x0100 /* All terms of compound are VALUES */ #define SF_NestedFrom 0x0200 /* Part of a parenthesized FROM clause */ #define SF_MaybeConvert 0x0400 /* Need convertCompoundSelectToSubquery() */ +#define SF_Recursive 0x0800 /* The recursive part of a recursive CTE */ +#define SF_MinMaxAgg 0x1000 /* Aggregate containing min() or max() */ /* -** The results of a select can be distributed in several ways. The -** "SRT" prefix means "SELECT Result Type". +** The results of a SELECT can be distributed in several ways, as defined +** by one of the following macros. The "SRT" prefix means "SELECT Result +** Type". +** +** SRT_Union Store results as a key in a temporary index +** identified by pDest->iSDParm. +** +** SRT_Except Remove results from the temporary index pDest->iSDParm. +** +** SRT_Exists Store a 1 in memory cell pDest->iSDParm if the result +** set is not empty. +** +** SRT_Discard Throw the results away. This is used by SELECT +** statements within triggers whose only purpose is +** the side-effects of functions. +** +** All of the above are free to ignore their ORDER BY clause. Those that +** follow must honor the ORDER BY clause. +** +** SRT_Output Generate a row of output (using the OP_ResultRow +** opcode) for each row in the result set. +** +** SRT_Mem Only valid if the result is a single column. +** Store the first column of the first result row +** in register pDest->iSDParm then abandon the rest +** of the query. This destination implies "LIMIT 1". +** +** SRT_Set The result must be a single column. Store each +** row of result as the key in table pDest->iSDParm. +** Apply the affinity pDest->affSdst before storing +** results. Used to implement "IN (SELECT ...)". +** +** SRT_EphemTab Create an temporary table pDest->iSDParm and store +** the result there. The cursor is left open after +** returning. This is like SRT_Table except that +** this destination uses OP_OpenEphemeral to create +** the table first. +** +** SRT_Coroutine Generate a co-routine that returns a new row of +** results each time it is invoked. The entry point +** of the co-routine is stored in register pDest->iSDParm +** and the result row is stored in pDest->nDest registers +** starting with pDest->iSdst. +** +** SRT_Table Store results in temporary table pDest->iSDParm. +** SRT_Fifo This is like SRT_EphemTab except that the table +** is assumed to already be open. SRT_Fifo has +** the additional property of being able to ignore +** the ORDER BY clause. +** +** SRT_DistFifo Store results in a temporary table pDest->iSDParm. +** But also use temporary table pDest->iSDParm+1 as +** a record of all prior results and ignore any duplicate +** rows. Name means: "Distinct Fifo". +** +** SRT_Queue Store results in priority queue pDest->iSDParm (really +** an index). Append a sequence number so that all entries +** are distinct. +** +** SRT_DistQueue Store results in priority queue pDest->iSDParm only if +** the same record has never been stored before. The +** index at pDest->iSDParm+1 hold all prior stores. */ #define SRT_Union 1 /* Store result as keys in an index */ #define SRT_Except 2 /* Remove result from a UNION index */ #define SRT_Exists 3 /* Store 1 if the result is not empty */ #define SRT_Discard 4 /* Do not save the results anywhere */ +#define SRT_Fifo 5 /* Store result as data with an automatic rowid */ +#define SRT_DistFifo 6 /* Like SRT_Fifo, but unique results only */ +#define SRT_Queue 7 /* Store result in an queue */ +#define SRT_DistQueue 8 /* Like SRT_Queue, but unique results only */ /* The ORDER BY clause is ignored for all of the above */ -#define IgnorableOrderby(X) ((X->eDest)<=SRT_Discard) - -#define SRT_Output 5 /* Output each row of result */ -#define SRT_Mem 6 /* Store result in a memory cell */ -#define SRT_Set 7 /* Store results as keys in an index */ -#define SRT_Table 8 /* Store result as data with an automatic rowid */ -#define SRT_EphemTab 9 /* Create transient tab and store like SRT_Table */ -#define SRT_Coroutine 10 /* Generate a single row of result */ +#define IgnorableOrderby(X) ((X->eDest)<=SRT_DistQueue) + +#define SRT_Output 9 /* Output each row of result */ +#define SRT_Mem 10 /* Store result in a memory cell */ +#define SRT_Set 11 /* Store results as keys in an index */ +#define SRT_EphemTab 12 /* Create transient tab and store like SRT_Table */ +#define SRT_Coroutine 13 /* Generate a single row of result */ +#define SRT_Table 14 /* Store result as data with an automatic rowid */ /* ** An instance of this object describes where to put of the results of ** a SELECT statement. */ struct SelectDest { - u8 eDest; /* How to dispose of the results. On of SRT_* above. */ - char affSdst; /* Affinity used when eDest==SRT_Set */ - int iSDParm; /* A parameter used by the eDest disposal method */ - int iSdst; /* Base register where results are written */ - int nSdst; /* Number of registers allocated */ + u8 eDest; /* How to dispose of the results. On of SRT_* above. */ + char affSdst; /* Affinity used when eDest==SRT_Set */ + int iSDParm; /* A parameter used by the eDest disposal method */ + int iSdst; /* Base register where results are written */ + int nSdst; /* Number of registers allocated */ + ExprList *pOrderBy; /* Key columns for SRT_Queue and SRT_DistQueue */ }; /* ** During code generation of statements that do inserts into AUTOINCREMENT ** tables, the following information is attached to the Table.u.autoInc.p @@ -11391,13 +12377,23 @@ /* ** The yDbMask datatype for the bitmask of all attached databases. */ #if SQLITE_MAX_ATTACHED>30 - typedef sqlite3_uint64 yDbMask; + typedef unsigned char yDbMask[(SQLITE_MAX_ATTACHED+9)/8]; +# define DbMaskTest(M,I) (((M)[(I)/8]&(1<<((I)&7)))!=0) +# define DbMaskZero(M) memset((M),0,sizeof(M)) +# define DbMaskSet(M,I) (M)[(I)/8]|=(1<<((I)&7)) +# define DbMaskAllZero(M) sqlite3DbMaskAllZero(M) +# define DbMaskNonZero(M) (sqlite3DbMaskAllZero(M)==0) #else typedef unsigned int yDbMask; +# define DbMaskTest(M,I) (((M)&(((yDbMask)1)<<(I)))!=0) +# define DbMaskZero(M) (M)=0 +# define DbMaskSet(M,I) (M)|=(((yDbMask)1)<<(I)) +# define DbMaskAllZero(M) (M)==0 +# define DbMaskNonZero(M) (M)!=0 #endif /* ** An SQL parser context. A copy of this structure is passed through ** the parser and down into all the parser action routine in order to @@ -11421,65 +12417,79 @@ int rc; /* Return code from execution */ u8 colNamesSet; /* TRUE after OP_ColumnName has been issued to pVdbe */ u8 checkSchema; /* Causes schema cookie check after an error */ u8 nested; /* Number of nested calls to the parser/code generator */ u8 nTempReg; /* Number of temporary registers in aTempReg[] */ - u8 nTempInUse; /* Number of aTempReg[] currently checked out */ - u8 nColCache; /* Number of entries in aColCache[] */ - u8 iColCache; /* Next entry in aColCache[] to replace */ u8 isMultiWrite; /* True if statement may modify/insert multiple rows */ u8 mayAbort; /* True if statement may throw an ABORT exception */ u8 hasCompound; /* Need to invoke convertCompoundSelectToSubquery() */ + u8 okConstFactor; /* OK to factor out constants */ int aTempReg[8]; /* Holding area for temporary registers */ int nRangeReg; /* Size of the temporary register block */ int iRangeReg; /* First register in temporary register block */ int nErr; /* Number of errors seen */ int nTab; /* Number of previously allocated VDBE cursors */ int nMem; /* Number of memory cells used so far */ int nSet; /* Number of sets used so far */ int nOnce; /* Number of OP_Once instructions so far */ + int nOpAlloc; /* Number of slots allocated for Vdbe.aOp[] */ + int iFixedOp; /* Never back out opcodes iFixedOp-1 or earlier */ int ckBase; /* Base register of data during check constraints */ int iPartIdxTab; /* Table corresponding to a partial index */ int iCacheLevel; /* ColCache valid when aColCache[].iLevel<=iCacheLevel */ int iCacheCnt; /* Counter used to generate aColCache[].lru values */ + int nLabel; /* Number of labels used */ + int *aLabel; /* Space to hold the labels */ struct yColCache { int iTable; /* Table cursor number */ - int iColumn; /* Table column number */ + i16 iColumn; /* Table column number */ u8 tempReg; /* iReg is a temp register that needs to be freed */ int iLevel; /* Nesting level */ int iReg; /* Reg with value of this column. 0 means none. */ int lru; /* Least recently used entry has the smallest value */ } aColCache[SQLITE_N_COLCACHE]; /* One for each column cache entry */ + ExprList *pConstExpr;/* Constant expressions */ + Token constraintName;/* Name of the constraint currently being parsed */ yDbMask writeMask; /* Start a write transaction on these databases */ yDbMask cookieMask; /* Bitmask of schema verified databases */ - int cookieGoto; /* Address of OP_Goto to cookie verifier subroutine */ int cookieValue[SQLITE_MAX_ATTACHED+2]; /* Values of cookies to verify */ int regRowid; /* Register holding rowid of CREATE TABLE entry */ int regRoot; /* Register holding root page number for new objects */ int nMaxArg; /* Max args passed to user function by sub-program */ - Token constraintName;/* Name of the constraint currently being parsed */ +#if SELECTTRACE_ENABLED + int nSelect; /* Number of SELECT statements seen */ + int nSelectIndent; /* How far to indent SELECTTRACE() output */ +#endif #ifndef SQLITE_OMIT_SHARED_CACHE int nTableLock; /* Number of locks in aTableLock */ TableLock *aTableLock; /* Required table locks for shared-cache mode */ #endif AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */ /* Information used while coding trigger programs. */ Parse *pToplevel; /* Parse structure for main program (or NULL) */ Table *pTriggerTab; /* Table triggers are being coded for */ + int addrCrTab; /* Address of OP_CreateTable opcode on CREATE TABLE */ + int addrSkipPK; /* Address of instruction to skip PRIMARY KEY index */ u32 nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */ u32 oldmask; /* Mask of old.* columns referenced */ u32 newmask; /* Mask of new.* columns referenced */ u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ u8 disableTriggers; /* True to disable triggers */ - /* Above is constant between recursions. Below is reset before and after - ** each recursion */ + /************************************************************************ + ** Above is constant between recursions. Below is reset before and after + ** each recursion. The boundary between these two regions is determined + ** using offsetof(Parse,nVar) so the nVar field must be the first field + ** in the recursive region. + ************************************************************************/ int nVar; /* Number of '?' variables seen in the SQL so far */ int nzVar; /* Number of available slots in azVar[] */ + u8 iPkSortOrder; /* ASC or DESC for INTEGER PRIMARY KEY */ + u8 bFreeWith; /* True if pWith should be freed with parser */ u8 explain; /* True if the EXPLAIN flag is found on the query */ #ifndef SQLITE_OMIT_VIRTUALTABLE u8 declareVtab; /* True if inside sqlite3_declare_vtab() */ int nVtabLock; /* Number of virtual tables to lock */ #endif @@ -11489,11 +12499,10 @@ int iSelectId; /* ID of current select for EXPLAIN output */ int iNextSelectId; /* Next available select ID for EXPLAIN output */ #endif char **azVar; /* Pointers to names of parameters */ Vdbe *pReprepare; /* VM being reprepared (sqlite3Reprepare()) */ - int *aAlias; /* Register used to hold aliased result */ const char *zTail; /* All SQL text past the last semicolon parsed */ Table *pNewTable; /* A table being constructed by CREATE TABLE */ Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */ const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */ Token sNameToken; /* Token with unqualified schema object name */ @@ -11502,10 +12511,11 @@ Token sArg; /* Complete text of a module argument */ Table **apVtabLock; /* Pointer to virtual tables needing locking */ #endif Table *pZombieTab; /* List of Table objects to delete after code gen */ TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ + With *pWith; /* Current WITH clause, or NULL */ }; /* ** Return true if currently inside an sqlite3_declare_vtab() call. */ @@ -11526,19 +12536,20 @@ /* ** Bitfield flags for P5 value in various opcodes. */ #define OPFLAG_NCHANGE 0x01 /* Set to update db->nChange */ +#define OPFLAG_EPHEM 0x01 /* OP_Column: Ephemeral output is ok */ #define OPFLAG_LASTROWID 0x02 /* Set to update db->lastRowid */ #define OPFLAG_ISUPDATE 0x04 /* This OP_Insert is an sql UPDATE */ #define OPFLAG_APPEND 0x08 /* This is likely to be an append */ #define OPFLAG_USESEEKRESULT 0x10 /* Try to avoid a seek in BtreeInsert() */ -#define OPFLAG_CLEARCACHE 0x20 /* Clear pseudo-table cache in OP_Column */ #define OPFLAG_LENGTHARG 0x40 /* OP_Column only used for length() */ #define OPFLAG_TYPEOFARG 0x80 /* OP_Column only used for typeof() */ #define OPFLAG_BULKCSR 0x01 /* OP_Open** used to open bulk cursor */ -#define OPFLAG_P2ISREG 0x02 /* P2 to OP_Open** is a register number */ +#define OPFLAG_SEEKEQ 0x02 /* OP_Open** cursor uses EQ seek only */ +#define OPFLAG_P2ISREG 0x04 /* P2 to OP_Open** is a register number */ #define OPFLAG_PERMUTE 0x01 /* OP_Compare: use the permutation */ /* * Each trigger present in the database schema is stored as an instance of * struct Trigger. @@ -11621,11 +12632,11 @@ u8 orconf; /* OE_Rollback etc. */ Trigger *pTrig; /* The trigger that this step is a part of */ Select *pSelect; /* SELECT statment or RHS of INSERT INTO .. SELECT ... */ Token target; /* Target table for DELETE, UPDATE, INSERT */ Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */ - ExprList *pExprList; /* SET clause for UPDATE. VALUES clause for INSERT */ + ExprList *pExprList; /* SET clause for UPDATE. */ IdList *pIdList; /* Column names for INSERT */ TriggerStep *pNext; /* Next in the link-list */ TriggerStep *pLast; /* Last element in link-list. Valid for 1st elem only */ }; @@ -11636,10 +12647,11 @@ */ typedef struct DbFixer DbFixer; struct DbFixer { Parse *pParse; /* The parsing context. Error messages written here */ Schema *pSchema; /* Fix items to this schema */ + int bVarOnly; /* Check for variable references only */ const char *zDb; /* Make sure all objects are contained in this database */ const char *zType; /* Type of the container - used for error messages */ const Token *pName; /* Name of the container - used for error messages */ }; @@ -11681,10 +12693,11 @@ int bCoreMutex; /* True to enable core mutexing */ int bFullMutex; /* True to enable full mutexing */ int bOpenUri; /* True to interpret filenames as URIs */ int bUseCis; /* Use covering indices for full-scans */ int mxStrlen; /* Maximum string length */ + int neverCorrupt; /* Database is always well-formed */ int szLookaside; /* Default lookaside buffer size */ int nLookaside; /* Default lookaside buffer count */ sqlite3_mem_methods m; /* Low-level memory allocation interface */ sqlite3_mutex_methods mutex; /* Low-level mutex interface */ sqlite3_pcache_methods2 pcache2; /* Low-level page-cache interface */ @@ -11699,40 +12712,71 @@ void *pPage; /* Page cache memory */ int szPage; /* Size of each page in pPage[] */ int nPage; /* Number of pages in pPage[] */ int mxParserStack; /* maximum depth of the parser stack */ int sharedCacheEnabled; /* true if shared-cache mode enabled */ + u32 szPma; /* Maximum Sorter PMA size */ /* The above might be initialized to non-zero. The following need to always ** initially be zero, however. */ int isInit; /* True after initialization has finished */ int inProgress; /* True while initialization in progress */ int isMutexInit; /* True after mutexes are initialized */ int isMallocInit; /* True after malloc is initialized */ int isPCacheInit; /* True after malloc is initialized */ - sqlite3_mutex *pInitMutex; /* Mutex used by sqlite3_initialize() */ int nRefInitMutex; /* Number of users of pInitMutex */ + sqlite3_mutex *pInitMutex; /* Mutex used by sqlite3_initialize() */ void (*xLog)(void*,int,const char*); /* Function for logging */ void *pLogArg; /* First argument to xLog() */ - int bLocaltimeFault; /* True to fail localtime() calls */ #ifdef SQLITE_ENABLE_SQLLOG void(*xSqllog)(void*,sqlite3*,const char*, int); void *pSqllogArg; #endif +#ifdef SQLITE_VDBE_COVERAGE + /* The following callback (if not NULL) is invoked on every VDBE branch + ** operation. Set the callback using SQLITE_TESTCTRL_VDBE_COVERAGE. + */ + void (*xVdbeBranch)(void*,int iSrcLine,u8 eThis,u8 eMx); /* Callback */ + void *pVdbeBranchArg; /* 1st argument */ +#endif +#ifndef SQLITE_OMIT_BUILTIN_TEST + int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */ +#endif + int bLocaltimeFault; /* True to fail localtime() calls */ }; +/* +** This macro is used inside of assert() statements to indicate that +** the assert is only valid on a well-formed database. Instead of: +** +** assert( X ); +** +** One writes: +** +** assert( X || CORRUPT_DB ); +** +** CORRUPT_DB is true during normal operation. CORRUPT_DB does not indicate +** that the database is definitely corrupt, only that it might be corrupt. +** For most test cases, CORRUPT_DB is set to false using a special +** sqlite3_test_control(). This enables assert() statements to prove +** things that are always true for well-formed databases. +*/ +#define CORRUPT_DB (sqlite3Config.neverCorrupt==0) + /* ** Context pointer passed down through the tree-walk. */ struct Walker { int (*xExprCallback)(Walker*, Expr*); /* Callback for expressions */ int (*xSelectCallback)(Walker*,Select*); /* Callback for SELECTs */ + void (*xSelectCallback2)(Walker*,Select*);/* Second callback for SELECTs */ Parse *pParse; /* Parser context. */ int walkerDepth; /* Number of subqueries */ - u8 bSelectDepthFirst; /* Do subqueries first */ + u8 eCode; /* A small processing code */ union { /* Extra data for callback */ NameContext *pNC; /* Naming context */ - int i; /* Integer value */ + int n; /* A counter */ + int iCur; /* A cursor number */ SrcList *pSrcList; /* FROM clause */ struct SrcCount *pSrcCount; /* Counting column references */ } u; }; @@ -11749,10 +12793,36 @@ */ #define WRC_Continue 0 /* Continue down into children */ #define WRC_Prune 1 /* Omit children but continue walking siblings */ #define WRC_Abort 2 /* Abandon the tree walk */ +/* +** An instance of this structure represents a set of one or more CTEs +** (common table expressions) created by a single WITH clause. +*/ +struct With { + int nCte; /* Number of CTEs in the WITH clause */ + With *pOuter; /* Containing WITH clause, or NULL */ + struct Cte { /* For each CTE in the WITH clause.... */ + char *zName; /* Name of this CTE */ + ExprList *pCols; /* List of explicit column names, or NULL */ + Select *pSelect; /* The definition of this CTE */ + const char *zErr; /* Error message for circular references */ + } a[1]; +}; + +#ifdef SQLITE_DEBUG +/* +** An instance of the TreeView object is used for printing the content of +** data structures on sqlite3DebugPrintf() using a tree-like view. +*/ +struct TreeView { + int iLevel; /* Which level of the tree we are on */ + u8 bLine[100]; /* Draw vertical in column i if bLine[i] is true */ +}; +#endif /* SQLITE_DEBUG */ + /* ** Assuming zIn points to the first byte of a UTF-8 character, ** advance zIn to point to the first byte of the next UTF-8 character. */ #define SQLITE_SKIP_UTF8(zIn) { \ @@ -11776,15 +12846,15 @@ #define SQLITE_CANTOPEN_BKPT sqlite3CantopenError(__LINE__) /* ** FTS4 is really an extension for FTS3. It is enabled using the -** SQLITE_ENABLE_FTS3 macro. But to avoid confusion we also all -** the SQLITE_ENABLE_FTS4 macro to serve as an alisse for SQLITE_ENABLE_FTS3. +** SQLITE_ENABLE_FTS3 macro. But to avoid confusion we also call +** the SQLITE_ENABLE_FTS4 macro to serve as an alias for SQLITE_ENABLE_FTS3. */ #if defined(SQLITE_ENABLE_FTS4) && !defined(SQLITE_ENABLE_FTS3) -# define SQLITE_ENABLE_FTS3 +# define SQLITE_ENABLE_FTS3 1 #endif /* ** The ctype.h header is needed for non-ASCII systems. It is also ** needed by FTS3 when FTS3 is included in the amalgamation. @@ -11814,10 +12884,11 @@ # define sqlite3Isalpha(x) isalpha((unsigned char)(x)) # define sqlite3Isdigit(x) isdigit((unsigned char)(x)) # define sqlite3Isxdigit(x) isxdigit((unsigned char)(x)) # define sqlite3Tolower(x) tolower((unsigned char)(x)) #endif +SQLITE_PRIVATE int sqlite3IsIdChar(u8); /* ** Internal function prototypes */ #define sqlite3StrICmp sqlite3_stricmp @@ -11824,19 +12895,19 @@ SQLITE_PRIVATE int sqlite3Strlen30(const char*); #define sqlite3StrNICmp sqlite3_strnicmp SQLITE_PRIVATE int sqlite3MallocInit(void); SQLITE_PRIVATE void sqlite3MallocEnd(void); -SQLITE_PRIVATE void *sqlite3Malloc(int); -SQLITE_PRIVATE void *sqlite3MallocZero(int); -SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3*, int); -SQLITE_PRIVATE void *sqlite3DbMallocRaw(sqlite3*, int); +SQLITE_PRIVATE void *sqlite3Malloc(u64); +SQLITE_PRIVATE void *sqlite3MallocZero(u64); +SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3*, u64); +SQLITE_PRIVATE void *sqlite3DbMallocRaw(sqlite3*, u64); SQLITE_PRIVATE char *sqlite3DbStrDup(sqlite3*,const char*); -SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3*,const char*, int); -SQLITE_PRIVATE void *sqlite3Realloc(void*, int); -SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, int); -SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, int); +SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3*,const char*, u64); +SQLITE_PRIVATE void *sqlite3Realloc(void*, u64); +SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64); +SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, u64); SQLITE_PRIVATE void sqlite3DbFree(sqlite3*, void*); SQLITE_PRIVATE int sqlite3MallocSize(void*); SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, void*); SQLITE_PRIVATE void *sqlite3ScratchMalloc(int); SQLITE_PRIVATE void sqlite3ScratchFree(void*); @@ -11878,24 +12949,39 @@ SQLITE_PRIVATE sqlite3_mutex *sqlite3MutexAlloc(int); SQLITE_PRIVATE int sqlite3MutexInit(void); SQLITE_PRIVATE int sqlite3MutexEnd(void); #endif -SQLITE_PRIVATE int sqlite3StatusValue(int); -SQLITE_PRIVATE void sqlite3StatusAdd(int, int); +SQLITE_PRIVATE sqlite3_int64 sqlite3StatusValue(int); +SQLITE_PRIVATE void sqlite3StatusUp(int, int); +SQLITE_PRIVATE void sqlite3StatusDown(int, int); SQLITE_PRIVATE void sqlite3StatusSet(int, int); +/* Access to mutexes used by sqlite3_status() */ +SQLITE_PRIVATE sqlite3_mutex *sqlite3Pcache1Mutex(void); +SQLITE_PRIVATE sqlite3_mutex *sqlite3MallocMutex(void); + #ifndef SQLITE_OMIT_FLOATING_POINT SQLITE_PRIVATE int sqlite3IsNaN(double); #else # define sqlite3IsNaN(X) 0 #endif -SQLITE_PRIVATE void sqlite3VXPrintf(StrAccum*, int, const char*, va_list); -#ifndef SQLITE_OMIT_TRACE -SQLITE_PRIVATE void sqlite3XPrintf(StrAccum*, const char*, ...); -#endif +/* +** An instance of the following structure holds information about SQL +** functions arguments that are the parameters to the printf() function. +*/ +struct PrintfArguments { + int nArg; /* Total number of arguments */ + int nUsed; /* Number of arguments used so far */ + sqlite3_value **apArg; /* The argument values */ +}; + +#define SQLITE_PRINTF_INTERNAL 0x01 +#define SQLITE_PRINTF_SQLFUNC 0x02 +SQLITE_PRIVATE void sqlite3VXPrintf(StrAccum*, u32, const char*, va_list); +SQLITE_PRIVATE void sqlite3XPrintf(StrAccum*, u32, const char*, ...); SQLITE_PRIVATE char *sqlite3MPrintf(sqlite3*,const char*, ...); SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3*,const char*, va_list); SQLITE_PRIVATE char *sqlite3MAppendf(sqlite3*,char*,const char*,...); #if defined(SQLITE_TEST) || defined(SQLITE_DEBUG) SQLITE_PRIVATE void sqlite3DebugPrintf(const char*, ...); @@ -11902,29 +12988,18 @@ #endif #if defined(SQLITE_TEST) SQLITE_PRIVATE void *sqlite3TestTextToPtr(const char*); #endif -/* Output formatting for SQLITE_TESTCTRL_EXPLAIN */ -#if defined(SQLITE_ENABLE_TREE_EXPLAIN) -SQLITE_PRIVATE void sqlite3ExplainBegin(Vdbe*); -SQLITE_PRIVATE void sqlite3ExplainPrintf(Vdbe*, const char*, ...); -SQLITE_PRIVATE void sqlite3ExplainNL(Vdbe*); -SQLITE_PRIVATE void sqlite3ExplainPush(Vdbe*); -SQLITE_PRIVATE void sqlite3ExplainPop(Vdbe*); -SQLITE_PRIVATE void sqlite3ExplainFinish(Vdbe*); -SQLITE_PRIVATE void sqlite3ExplainSelect(Vdbe*, Select*); -SQLITE_PRIVATE void sqlite3ExplainExpr(Vdbe*, Expr*); -SQLITE_PRIVATE void sqlite3ExplainExprList(Vdbe*, ExprList*); -SQLITE_PRIVATE const char *sqlite3VdbeExplanation(Vdbe*); -#else -# define sqlite3ExplainBegin(X) -# define sqlite3ExplainSelect(A,B) -# define sqlite3ExplainExpr(A,B) -# define sqlite3ExplainExprList(A,B) -# define sqlite3ExplainFinish(X) -# define sqlite3VdbeExplanation(X) 0 +#if defined(SQLITE_DEBUG) +SQLITE_PRIVATE TreeView *sqlite3TreeViewPush(TreeView*,u8); +SQLITE_PRIVATE void sqlite3TreeViewPop(TreeView*); +SQLITE_PRIVATE void sqlite3TreeViewLine(TreeView*, const char*, ...); +SQLITE_PRIVATE void sqlite3TreeViewItem(TreeView*, const char*, u8); +SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView*, const Expr*, u8); +SQLITE_PRIVATE void sqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*); +SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView*, const Select*, u8); #endif SQLITE_PRIVATE void sqlite3SetString(char **, sqlite3*, const char*, ...); SQLITE_PRIVATE void sqlite3ErrorMsg(Parse*, const char*, ...); @@ -11947,10 +13022,11 @@ SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3*, Expr*); SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*); SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,Token*,int); SQLITE_PRIVATE void sqlite3ExprListSetSpan(Parse*,ExprList*,ExprSpan*); SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3*, ExprList*); +SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList*); SQLITE_PRIVATE int sqlite3Init(sqlite3*, char**); SQLITE_PRIVATE int sqlite3InitCallback(void*, int, char**, char**); SQLITE_PRIVATE void sqlite3Pragma(Parse*,Token*,Token*,Token*,int); SQLITE_PRIVATE void sqlite3ResetAllSchemasOfConnection(sqlite3*); SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3*,int); @@ -11957,23 +13033,31 @@ SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3*); SQLITE_PRIVATE void sqlite3BeginParse(Parse*,int); SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3*); SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse*,Select*); SQLITE_PRIVATE void sqlite3OpenMasterTable(Parse *, int); +SQLITE_PRIVATE Index *sqlite3PrimaryKeyIndex(Table*); +SQLITE_PRIVATE i16 sqlite3ColumnOfIndex(Index*, i16); SQLITE_PRIVATE void sqlite3StartTable(Parse*,Token*,Token*,int,int,int,int); SQLITE_PRIVATE void sqlite3AddColumn(Parse*,Token*); SQLITE_PRIVATE void sqlite3AddNotNull(Parse*, int); SQLITE_PRIVATE void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int); SQLITE_PRIVATE void sqlite3AddCheckConstraint(Parse*, Expr*); SQLITE_PRIVATE void sqlite3AddColumnType(Parse*,Token*); SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse*,ExprSpan*); SQLITE_PRIVATE void sqlite3AddCollateType(Parse*, Token*); -SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,Select*); +SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*); SQLITE_PRIVATE int sqlite3ParseUri(const char*,const char*,unsigned int*, sqlite3_vfs**,char**,char **); SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3*,const char*); SQLITE_PRIVATE int sqlite3CodeOnce(Parse *); + +#ifdef SQLITE_OMIT_BUILTIN_TEST +# define sqlite3FaultSim(X) SQLITE_OK +#else +SQLITE_PRIVATE int sqlite3FaultSim(int); +#endif SQLITE_PRIVATE Bitvec *sqlite3BitvecCreate(u32); SQLITE_PRIVATE int sqlite3BitvecTest(Bitvec*, u32); SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec*, u32); SQLITE_PRIVATE void sqlite3BitvecClear(Bitvec*, u32, void*); @@ -11982,11 +13066,11 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int,int*); SQLITE_PRIVATE RowSet *sqlite3RowSetInit(sqlite3*, void*, unsigned int); SQLITE_PRIVATE void sqlite3RowSetClear(RowSet*); SQLITE_PRIVATE void sqlite3RowSetInsert(RowSet*, i64); -SQLITE_PRIVATE int sqlite3RowSetTest(RowSet*, u8 iBatch, i64); +SQLITE_PRIVATE int sqlite3RowSetTest(RowSet*, int iBatch, i64); SQLITE_PRIVATE int sqlite3RowSetNext(RowSet*, i64*); SQLITE_PRIVATE void sqlite3CreateView(Parse*,Token*,Token*,Token*,Select*,int,int); #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) @@ -11993,10 +13077,13 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse*,Table*); #else # define sqlite3ViewGetColumnNames(A,B) 0 #endif +#if SQLITE_MAX_ATTACHED>30 +SQLITE_PRIVATE int sqlite3DbMaskAllZero(yDbMask); +#endif SQLITE_PRIVATE void sqlite3DropTable(Parse*, SrcList*, int, int); SQLITE_PRIVATE void sqlite3CodeDropTable(Parse*, Table*, int, int); SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3*, Table*); #ifndef SQLITE_OMIT_AUTOINCREMENT SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse); @@ -12003,12 +13090,11 @@ SQLITE_PRIVATE void sqlite3AutoincrementEnd(Parse *pParse); #else # define sqlite3AutoincrementBegin(X) # define sqlite3AutoincrementEnd(X) #endif -SQLITE_PRIVATE int sqlite3CodeCoroutine(Parse*, Select*, SelectDest*); -SQLITE_PRIVATE void sqlite3Insert(Parse*, SrcList*, ExprList*, Select*, IdList*, int); +SQLITE_PRIVATE void sqlite3Insert(Parse*, SrcList*, Select*, IdList*, int); SQLITE_PRIVATE void *sqlite3ArrayAllocate(sqlite3*,void*,int,int*,int*); SQLITE_PRIVATE IdList *sqlite3IdListAppend(sqlite3*, IdList*, Token*); SQLITE_PRIVATE int sqlite3IdListIndex(IdList*,const char*); SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge(sqlite3*, SrcList*, int, int); SQLITE_PRIVATE SrcList *sqlite3SrcListAppend(sqlite3*, SrcList*, Token*, Token*); @@ -12018,10 +13104,11 @@ SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *, struct SrcList_item *); SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList*); SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse*, SrcList*); SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3*, IdList*); SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3*, SrcList*); +SQLITE_PRIVATE Index *sqlite3AllocateIndexObject(sqlite3*,i16,int,char**); SQLITE_PRIVATE Index *sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*, Expr*, int, int); SQLITE_PRIVATE void sqlite3DropIndex(Parse*, SrcList*, int); SQLITE_PRIVATE int sqlite3Select(Parse*, Select*, SelectDest*); SQLITE_PRIVATE Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*, @@ -12038,28 +13125,32 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,u16,int); SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo*); SQLITE_PRIVATE u64 sqlite3WhereOutputRowCount(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo*); +SQLITE_PRIVATE int sqlite3WhereIsSorted(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereContinueLabel(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereBreakLabel(WhereInfo*); -SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo*); +SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo*, int*); SQLITE_PRIVATE int sqlite3ExprCodeGetColumn(Parse*, Table*, int, int, int, u8); SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable(Vdbe*, Table*, int, int, int); SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse*, int, int, int); SQLITE_PRIVATE void sqlite3ExprCacheStore(Parse*, int, int, int); SQLITE_PRIVATE void sqlite3ExprCachePush(Parse*); -SQLITE_PRIVATE void sqlite3ExprCachePop(Parse*, int); +SQLITE_PRIVATE void sqlite3ExprCachePop(Parse*); SQLITE_PRIVATE void sqlite3ExprCacheRemove(Parse*, int, int); SQLITE_PRIVATE void sqlite3ExprCacheClear(Parse*); SQLITE_PRIVATE void sqlite3ExprCacheAffinityChange(Parse*, int, int); -SQLITE_PRIVATE int sqlite3ExprCode(Parse*, Expr*, int); +SQLITE_PRIVATE void sqlite3ExprCode(Parse*, Expr*, int); +SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int); +SQLITE_PRIVATE void sqlite3ExprCodeAtInit(Parse*, Expr*, int, u8); SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*); SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int); -SQLITE_PRIVATE int sqlite3ExprCodeAndCache(Parse*, Expr*, int); -SQLITE_PRIVATE void sqlite3ExprCodeConstants(Parse*, Expr*); -SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int); +SQLITE_PRIVATE void sqlite3ExprCodeAndCache(Parse*, Expr*, int); +SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, u8); +#define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */ +#define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse*, Expr*, int, int); SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse*, Expr*, int, int); SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3*,const char*, const char*); SQLITE_PRIVATE Table *sqlite3LocateTable(Parse*,int isView,const char*, const char*); SQLITE_PRIVATE Table *sqlite3LocateTableItem(Parse*,int isView,struct SrcList_item *); @@ -12076,11 +13167,10 @@ SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*); SQLITE_PRIVATE int sqlite3FunctionUsesThisSrc(Expr*, SrcList*); SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse*); SQLITE_PRIVATE void sqlite3PrngSaveState(void); SQLITE_PRIVATE void sqlite3PrngRestoreState(void); -SQLITE_PRIVATE void sqlite3PrngResetState(void); SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3*,int); SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse*, int); SQLITE_PRIVATE void sqlite3CodeVerifyNamedSchema(Parse*, const char *zDb); SQLITE_PRIVATE void sqlite3BeginTransaction(Parse*, int); SQLITE_PRIVATE void sqlite3CommitTransaction(Parse*); @@ -12088,32 +13178,40 @@ SQLITE_PRIVATE void sqlite3Savepoint(Parse*, int, Token*); SQLITE_PRIVATE void sqlite3CloseSavepoints(sqlite3 *); SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3*); SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr*); SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr*); -SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*); +SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*, u8); +SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int); SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr*, int*); SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*); -SQLITE_PRIVATE void sqlite3ExprCodeIsNullJump(Vdbe*, const Expr*, int, int); SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); SQLITE_PRIVATE int sqlite3IsRowid(const char*); -SQLITE_PRIVATE void sqlite3GenerateRowDelete(Parse*, Table*, int, int, int, Trigger *, int); -SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int*); -SQLITE_PRIVATE int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*); -SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(Parse*,Table*,int,int, - int*,int,int,int,int,int*); -SQLITE_PRIVATE void sqlite3CompleteInsertion(Parse*, Table*, int, int, int*, int, int, int); -SQLITE_PRIVATE int sqlite3OpenTableAndIndices(Parse*, Table*, int, int); +SQLITE_PRIVATE void sqlite3GenerateRowDelete(Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8); +SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*); +SQLITE_PRIVATE int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*,Index*,int); +SQLITE_PRIVATE void sqlite3ResolvePartIdxLabel(Parse*,int); +SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(Parse*,Table*,int*,int,int,int,int, + u8,u8,int,int*); +SQLITE_PRIVATE void sqlite3CompleteInsertion(Parse*,Table*,int,int,int,int*,int,int,int); +SQLITE_PRIVATE int sqlite3OpenTableAndIndices(Parse*, Table*, int, int, u8*, int*, int*); SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse*, int, int); SQLITE_PRIVATE void sqlite3MultiWrite(Parse*); SQLITE_PRIVATE void sqlite3MayAbort(Parse*); -SQLITE_PRIVATE void sqlite3HaltConstraint(Parse*, int, int, char*, int); +SQLITE_PRIVATE void sqlite3HaltConstraint(Parse*, int, int, char*, i8, u8); +SQLITE_PRIVATE void sqlite3UniqueConstraint(Parse*, int, Index*); +SQLITE_PRIVATE void sqlite3RowidConstraint(Parse*, int, Table*); SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,Expr*,int); SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,ExprList*,int); SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,SrcList*,int); SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,IdList*); SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,Select*,int); +#if SELECTTRACE_ENABLED +SQLITE_PRIVATE void sqlite3SelectSetName(Select*,const char*); +#else +# define sqlite3SelectSetName(A,B) +#endif SQLITE_PRIVATE void sqlite3FuncDefInsert(FuncDefHash*, FuncDef*); SQLITE_PRIVATE FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,int,u8,u8); SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(sqlite3*); SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void); SQLITE_PRIVATE void sqlite3RegisterGlobalFunctions(void); @@ -12138,11 +13236,11 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, int, int, int); void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*); SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*); SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*); SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(sqlite3*,Token*, IdList*, - ExprList*,Select*,u8); + Select*,u8); SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(sqlite3*,Token*,ExprList*, Expr*, u8); SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*); SQLITE_PRIVATE void sqlite3DeleteTrigger(sqlite3*, Trigger*); SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); SQLITE_PRIVATE u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int); @@ -12174,11 +13272,11 @@ # define sqlite3AuthContextPush(a,b,c) # define sqlite3AuthContextPop(a) ((void)(a)) #endif SQLITE_PRIVATE void sqlite3Attach(Parse*, Expr*, Expr*, Expr*); SQLITE_PRIVATE void sqlite3Detach(Parse*, Expr*); -SQLITE_PRIVATE int sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*); +SQLITE_PRIVATE void sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*); SQLITE_PRIVATE int sqlite3FixSrcList(DbFixer*, SrcList*); SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*); SQLITE_PRIVATE int sqlite3FixExpr(DbFixer*, Expr*); SQLITE_PRIVATE int sqlite3FixExprList(DbFixer*, ExprList*); SQLITE_PRIVATE int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); @@ -12186,72 +13284,64 @@ SQLITE_PRIVATE int sqlite3GetInt32(const char *, int*); SQLITE_PRIVATE int sqlite3Atoi(const char*); SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *pData, int nChar); SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *pData, int nByte); SQLITE_PRIVATE u32 sqlite3Utf8Read(const u8**); +SQLITE_PRIVATE LogEst sqlite3LogEst(u64); +SQLITE_PRIVATE LogEst sqlite3LogEstAdd(LogEst,LogEst); +#ifndef SQLITE_OMIT_VIRTUALTABLE +SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double); +#endif +SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst); /* ** Routines to read and write variable-length integers. These used to ** be defined locally, but now we use the varint routines in the util.c -** file. Code should use the MACRO forms below, as the Varint32 versions -** are coded to assume the single byte case is already handled (which -** the MACRO form does). +** file. */ SQLITE_PRIVATE int sqlite3PutVarint(unsigned char*, u64); -SQLITE_PRIVATE int sqlite3PutVarint32(unsigned char*, u32); SQLITE_PRIVATE u8 sqlite3GetVarint(const unsigned char *, u64 *); SQLITE_PRIVATE u8 sqlite3GetVarint32(const unsigned char *, u32 *); SQLITE_PRIVATE int sqlite3VarintLen(u64 v); /* -** The header of a record consists of a sequence variable-length integers. -** These integers are almost always small and are encoded as a single byte. -** The following macros take advantage this fact to provide a fast encode -** and decode of the integers in a record header. It is faster for the common -** case where the integer is a single byte. It is a little slower when the -** integer is two or more bytes. But overall it is faster. -** -** The following expressions are equivalent: -** -** x = sqlite3GetVarint32( A, &B ); -** x = sqlite3PutVarint32( A, B ); -** -** x = getVarint32( A, B ); -** x = putVarint32( A, B ); -** +** The common case is for a varint to be a single byte. They following +** macros handle the common case without a procedure call, but then call +** the procedure for larger varints. */ #define getVarint32(A,B) \ (u8)((*(A)<(u8)0x80)?((B)=(u32)*(A)),1:sqlite3GetVarint32((A),(u32 *)&(B))) #define putVarint32(A,B) \ (u8)(((u32)(B)<(u32)0x80)?(*(A)=(unsigned char)(B)),1:\ - sqlite3PutVarint32((A),(B))) + sqlite3PutVarint((A),(B))) #define getVarint sqlite3GetVarint #define putVarint sqlite3PutVarint SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(Vdbe *, Index *); -SQLITE_PRIVATE void sqlite3TableAffinityStr(Vdbe *, Table *); +SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int); SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2); SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity); SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr); SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8); -SQLITE_PRIVATE void sqlite3Error(sqlite3*, int, const char*,...); +SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*); +SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...); +SQLITE_PRIVATE void sqlite3Error(sqlite3*,int); SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3*, const char *z, int n); SQLITE_PRIVATE u8 sqlite3HexToInt(int h); SQLITE_PRIVATE int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); -#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) || \ - defined(SQLITE_DEBUG_OS_TRACE) +#if defined(SQLITE_TEST) SQLITE_PRIVATE const char *sqlite3ErrName(int); #endif SQLITE_PRIVATE const char *sqlite3ErrStr(int); SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse); SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int); SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName); SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr); -SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, Token*); +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int); SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*); SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*); SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *); SQLITE_PRIVATE int sqlite3CheckObjectName(Parse *, const char *); SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *, int); @@ -12262,16 +13352,17 @@ #ifdef SQLITE_ENABLE_8_3_NAMES SQLITE_PRIVATE void sqlite3FileSuffix3(const char*, char*); #else # define sqlite3FileSuffix3(X,Y) #endif -SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z,int); +SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z,u8); SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value*, u8); SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value*, u8); SQLITE_PRIVATE void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*)); +SQLITE_PRIVATE void sqlite3ValueSetNull(sqlite3_value*); SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value*); SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *); SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8); SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **); SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8); @@ -12302,11 +13393,11 @@ SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*); SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *, Table *, int, int); SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *, Token *); SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *, SrcList *); SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*); -SQLITE_PRIVATE char sqlite3AffinityType(const char*); +SQLITE_PRIVATE char sqlite3AffinityType(const char*, u8*); SQLITE_PRIVATE void sqlite3Analyze(Parse*, Token*, Token*); SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler*); SQLITE_PRIVATE int sqlite3FindDb(sqlite3*, Token*); SQLITE_PRIVATE int sqlite3FindDbName(sqlite3 *, const char *); SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3*,int iDB); @@ -12316,12 +13407,17 @@ SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3*,Expr*,int*,char*); SQLITE_PRIVATE void sqlite3MinimumFileFormat(Parse*, int, int); SQLITE_PRIVATE void sqlite3SchemaClear(void *); SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *, Btree *); SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *); -SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3*,int); -SQLITE_PRIVATE KeyInfo *sqlite3IndexKeyinfo(Parse *, Index *); +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3*,int,int); +SQLITE_PRIVATE void sqlite3KeyInfoUnref(KeyInfo*); +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoRef(KeyInfo*); +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse*, Index*); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3KeyInfoIsWriteable(KeyInfo*); +#endif SQLITE_PRIVATE int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*), FuncDestructor *pDestructor ); @@ -12328,11 +13424,12 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int); SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *); SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum*, char*, int, int); SQLITE_PRIVATE void sqlite3StrAccumAppend(StrAccum*,const char*,int); -SQLITE_PRIVATE void sqlite3AppendSpace(StrAccum*,int); +SQLITE_PRIVATE void sqlite3StrAccumAppendAll(StrAccum*,const char*); +SQLITE_PRIVATE void sqlite3AppendChar(StrAccum*,int,char); SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum*); SQLITE_PRIVATE void sqlite3StrAccumReset(StrAccum*); SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest*,int,int); SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int); @@ -12340,17 +13437,19 @@ SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *); #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 SQLITE_PRIVATE void sqlite3AnalyzeFunctions(void); SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue(Parse*,Index*,UnpackedRecord**,Expr*,u8,int,int*); +SQLITE_PRIVATE int sqlite3Stat4ValueFromExpr(Parse*, Expr*, u8, sqlite3_value**); SQLITE_PRIVATE void sqlite3Stat4ProbeFree(UnpackedRecord*); +SQLITE_PRIVATE int sqlite3Stat4Column(sqlite3*, const void*, int, int, sqlite3_value**); #endif /* ** The interface to the LEMON-generated parser */ -SQLITE_PRIVATE void *sqlite3ParserAlloc(void*(*)(size_t)); +SQLITE_PRIVATE void *sqlite3ParserAlloc(void*(*)(u64)); SQLITE_PRIVATE void sqlite3ParserFree(void*, void(*)(void*)); SQLITE_PRIVATE void sqlite3Parser(void*, int, Token, Parse*); #ifdef YYTRACKMAXSTACKDEPTH SQLITE_PRIVATE int sqlite3ParserStackPeak(void*); #endif @@ -12409,19 +13508,28 @@ SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*); SQLITE_PRIVATE void sqlite3InvalidFunction(sqlite3_context*,int,sqlite3_value**); SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*); SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe*, const char*, int); SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *); +SQLITE_PRIVATE void sqlite3ParserReset(Parse*); SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*); SQLITE_PRIVATE void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*); SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *); SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3*); SQLITE_PRIVATE const char *sqlite3JournalModename(int); #ifndef SQLITE_OMIT_WAL SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3*, int, int, int*, int*); SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int); #endif +#ifndef SQLITE_OMIT_CTE +SQLITE_PRIVATE With *sqlite3WithAdd(Parse*,With*,Token*,ExprList*,Select*); +SQLITE_PRIVATE void sqlite3WithDelete(sqlite3*,With*); +SQLITE_PRIVATE void sqlite3WithPush(Parse*, With*, u8); +#else +#define sqlite3WithPush(x,y,z) +#define sqlite3WithDelete(x,y) +#endif /* Declarations for functions in fkey.c. All of these are replaced by ** no-op macros if OMIT_FOREIGN_KEY is defined. In this case no foreign ** key functionality is available. If OMIT_TRIGGER is defined but ** OMIT_FOREIGN_KEY is not, only some of the functions are no-oped. In @@ -12435,14 +13543,14 @@ SQLITE_PRIVATE int sqlite3FkRequired(Parse*, Table*, int*, int); SQLITE_PRIVATE u32 sqlite3FkOldmask(Parse*, Table*); SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *); #else #define sqlite3FkActions(a,b,c,d,e,f) - #define sqlite3FkCheck(a,b,c,d) + #define sqlite3FkCheck(a,b,c,d,e,f) #define sqlite3FkDropTable(a,b,c) - #define sqlite3FkOldmask(a,b) 0 - #define sqlite3FkRequired(a,b,c,d,e,f) 0 + #define sqlite3FkOldmask(a,b) 0 + #define sqlite3FkRequired(a,b,c,d) 0 #endif #ifndef SQLITE_OMIT_FOREIGN_KEY SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *, Table*); SQLITE_PRIVATE int sqlite3FkLocateIndex(Parse*,Table*,FKey*,Index**,int**); #else @@ -12468,15 +13576,25 @@ #else #define sqlite3BeginBenignMalloc() #define sqlite3EndBenignMalloc() #endif -#define IN_INDEX_ROWID 1 -#define IN_INDEX_EPH 2 -#define IN_INDEX_INDEX_ASC 3 -#define IN_INDEX_INDEX_DESC 4 -SQLITE_PRIVATE int sqlite3FindInIndex(Parse *, Expr *, int*); +/* +** Allowed return values from sqlite3FindInIndex() +*/ +#define IN_INDEX_ROWID 1 /* Search the rowid of the table */ +#define IN_INDEX_EPH 2 /* Search an ephemeral b-tree */ +#define IN_INDEX_INDEX_ASC 3 /* Existing index ASCENDING */ +#define IN_INDEX_INDEX_DESC 4 /* Existing index DESCENDING */ +#define IN_INDEX_NOOP 5 /* No table available. Use comparisons */ +/* +** Allowed flags for the 3rd parameter to sqlite3FindInIndex(). +*/ +#define IN_INDEX_NOOP_OK 0x0001 /* OK to return IN_INDEX_NOOP */ +#define IN_INDEX_MEMBERSHIP 0x0002 /* IN operator used for membership test */ +#define IN_INDEX_LOOP 0x0004 /* IN operator used as a loop */ +SQLITE_PRIVATE int sqlite3FindInIndex(Parse *, Expr *, u32, int*); #ifdef SQLITE_ENABLE_ATOMIC_WRITE SQLITE_PRIVATE int sqlite3JournalOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int); SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *); SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *); @@ -12488,16 +13606,15 @@ SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *); SQLITE_PRIVATE int sqlite3MemJournalSize(void); SQLITE_PRIVATE int sqlite3IsMemJournal(sqlite3_file *); +SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p); #if SQLITE_MAX_EXPR_DEPTH>0 -SQLITE_PRIVATE void sqlite3ExprSetHeight(Parse *pParse, Expr *p); SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *); SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse*, int); #else - #define sqlite3ExprSetHeight(x,y) #define sqlite3SelectExprHeight(x) 0 #define sqlite3ExprCheckHeight(x,y) #endif SQLITE_PRIVATE u32 sqlite3Get4byte(const u8*); @@ -12523,11 +13640,11 @@ ** print I/O tracing messages. */ #ifdef SQLITE_ENABLE_IOTRACE # define IOTRACE(A) if( sqlite3IoTrace ){ sqlite3IoTrace A; } SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe*); -SQLITE_PRIVATE void (*sqlite3IoTrace)(const char*,...); +SQLITE_API SQLITE_EXTERN void (SQLITE_CDECL *sqlite3IoTrace)(const char*,...); #else # define IOTRACE(A) # define sqlite3VdbeIOTraceSql(X) #endif @@ -12567,14 +13684,21 @@ # define sqlite3MemdebugSetType(X,Y) /* no-op */ # define sqlite3MemdebugHasType(X,Y) 1 # define sqlite3MemdebugNoType(X,Y) 1 #endif #define MEMTYPE_HEAP 0x01 /* General heap allocations */ -#define MEMTYPE_LOOKASIDE 0x02 /* Might have been lookaside memory */ +#define MEMTYPE_LOOKASIDE 0x02 /* Heap that might have been lookaside */ #define MEMTYPE_SCRATCH 0x04 /* Scratch allocations */ #define MEMTYPE_PCACHE 0x08 /* Page cache allocations */ -#define MEMTYPE_DB 0x10 /* Uses sqlite3DbMalloc, not sqlite_malloc */ + +/* +** Threading interface +*/ +#if SQLITE_MAX_WORKER_THREADS>0 +SQLITE_PRIVATE int sqlite3ThreadCreate(SQLiteThread**,void*(*)(void*),void*); +SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread*, void**); +#endif #endif /* _SQLITEINT_H_ */ /************** End of sqliteInt.h *******************************************/ /************** Begin file global.c ******************************************/ @@ -12588,11 +13712,11 @@ ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** -** This file contains definitions of global variables and contants. +** This file contains definitions of global variables and constants. */ /* An array to map all upper-case characters into their corresponding ** lower-case character. ** @@ -12623,20 +13747,20 @@ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, /* 1x */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, /* 2x */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, /* 3x */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, /* 4x */ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, /* 5x */ - 96, 97, 66, 67, 68, 69, 70, 71, 72, 73,106,107,108,109,110,111, /* 6x */ - 112, 81, 82, 83, 84, 85, 86, 87, 88, 89,122,123,124,125,126,127, /* 7x */ + 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, /* 6x */ + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, /* 7x */ 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, /* 8x */ - 144,145,146,147,148,149,150,151,152,153,154,155,156,157,156,159, /* 9x */ + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, /* 9x */ 160,161,162,163,164,165,166,167,168,169,170,171,140,141,142,175, /* Ax */ 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, /* Bx */ 192,129,130,131,132,133,134,135,136,137,202,203,204,205,206,207, /* Cx */ 208,145,146,147,148,149,150,151,152,153,218,219,220,221,222,223, /* Dx */ - 224,225,162,163,164,165,166,167,168,169,232,203,204,205,206,207, /* Ex */ - 239,240,241,242,243,244,245,246,247,248,249,219,220,221,222,255, /* Fx */ + 224,225,162,163,164,165,166,167,168,169,234,235,236,237,238,239, /* Ex */ + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255, /* Fx */ #endif }; /* ** The following 256 byte lookup table is used to support SQLites built-in @@ -12706,17 +13830,39 @@ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* f0..f7 ........ */ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 /* f8..ff ........ */ }; #endif +/* EVIDENCE-OF: R-02982-34736 In order to maintain full backwards +** compatibility for legacy applications, the URI filename capability is +** disabled by default. +** +** EVIDENCE-OF: R-38799-08373 URI filenames can be enabled or disabled +** using the SQLITE_USE_URI=1 or SQLITE_USE_URI=0 compile-time options. +** +** EVIDENCE-OF: R-43642-56306 By default, URI handling is globally +** disabled. The default value may be changed by compiling with the +** SQLITE_USE_URI symbol defined. +*/ #ifndef SQLITE_USE_URI # define SQLITE_USE_URI 0 #endif +/* EVIDENCE-OF: R-38720-18127 The default setting is determined by the +** SQLITE_ALLOW_COVERING_INDEX_SCAN compile-time option, or is "on" if +** that compile-time option is omitted. +*/ #ifndef SQLITE_ALLOW_COVERING_INDEX_SCAN # define SQLITE_ALLOW_COVERING_INDEX_SCAN 1 #endif + +/* The minimum PMA size is set to this value multiplied by the database +** page size in bytes. +*/ +#ifndef SQLITE_SORTER_PMASZ +# define SQLITE_SORTER_PMASZ 250 +#endif /* ** The following singleton contains the global configuration for ** the SQLite library. */ @@ -12725,10 +13871,11 @@ 1, /* bCoreMutex */ SQLITE_THREADSAFE==1, /* bFullMutex */ SQLITE_USE_URI, /* bOpenUri */ SQLITE_ALLOW_COVERING_INDEX_SCAN, /* bUseCis */ 0x7ffffffe, /* mxStrlen */ + 0, /* neverCorrupt */ 128, /* szLookaside */ 500, /* nLookaside */ {0,0,0,0,0,0,0,0}, /* m */ {0,0,0,0,0,0,0,0,0}, /* mutex */ {0,0,0,0,0,0,0,0,0,0,0,0,0},/* pcache2 */ @@ -12743,27 +13890,34 @@ (void*)0, /* pPage */ 0, /* szPage */ 0, /* nPage */ 0, /* mxParserStack */ 0, /* sharedCacheEnabled */ + SQLITE_SORTER_PMASZ, /* szPma */ /* All the rest should always be initialized to zero */ 0, /* isInit */ 0, /* inProgress */ 0, /* isMutexInit */ 0, /* isMallocInit */ 0, /* isPCacheInit */ - 0, /* pInitMutex */ 0, /* nRefInitMutex */ + 0, /* pInitMutex */ 0, /* xLog */ 0, /* pLogArg */ - 0, /* bLocaltimeFault */ #ifdef SQLITE_ENABLE_SQLLOG 0, /* xSqllog */ - 0 /* pSqllogArg */ + 0, /* pSqllogArg */ #endif +#ifdef SQLITE_VDBE_COVERAGE + 0, /* xVdbeBranch */ + 0, /* pVbeBranchArg */ +#endif +#ifndef SQLITE_OMIT_BUILTIN_TEST + 0, /* xTestCallback */ +#endif + 0 /* bLocaltimeFault */ }; - /* ** Hash table for global functions - functions common to all ** database connections. After initialization, this table is ** read-only. @@ -12792,12 +13946,12 @@ ** than 1 GiB. The sqlite3_test_control() interface can be used to ** move the pending byte. ** ** IMPORTANT: Changing the pending byte to any value other than ** 0x40000000 results in an incompatible database file format! -** Changing the pending byte during operating results in undefined -** and dileterious behavior. +** Changing the pending byte during operation will result in undefined +** and incorrect behavior. */ #ifndef SQLITE_OMIT_WSD SQLITE_PRIVATE int sqlite3PendingByte = 0x40000000; #endif @@ -12843,340 +13997,355 @@ /* These macros are provided to "stringify" the value of the define ** for those options in which the value is meaningful. */ #define CTIMEOPT_VAL_(opt) #opt #define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) -#ifdef SQLITE_32BIT_ROWID +#if SQLITE_32BIT_ROWID "32BIT_ROWID", #endif -#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC +#if SQLITE_4_BYTE_ALIGNED_MALLOC "4_BYTE_ALIGNED_MALLOC", #endif -#ifdef SQLITE_CASE_SENSITIVE_LIKE +#if SQLITE_CASE_SENSITIVE_LIKE "CASE_SENSITIVE_LIKE", #endif -#ifdef SQLITE_CHECK_PAGES +#if SQLITE_CHECK_PAGES "CHECK_PAGES", #endif -#ifdef SQLITE_COVERAGE_TEST +#if SQLITE_COVERAGE_TEST "COVERAGE_TEST", #endif -#ifdef SQLITE_DEBUG +#if SQLITE_DEBUG "DEBUG", #endif -#ifdef SQLITE_DEFAULT_LOCKING_MODE +#if SQLITE_DEFAULT_LOCKING_MODE "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE), #endif #if defined(SQLITE_DEFAULT_MMAP_SIZE) && !defined(SQLITE_DEFAULT_MMAP_SIZE_xc) "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), #endif -#ifdef SQLITE_DISABLE_DIRSYNC +#if SQLITE_DISABLE_DIRSYNC "DISABLE_DIRSYNC", #endif -#ifdef SQLITE_DISABLE_LFS +#if SQLITE_DISABLE_LFS "DISABLE_LFS", #endif -#ifdef SQLITE_ENABLE_ATOMIC_WRITE +#if SQLITE_ENABLE_API_ARMOR + "ENABLE_API_ARMOR", +#endif +#if SQLITE_ENABLE_ATOMIC_WRITE "ENABLE_ATOMIC_WRITE", #endif -#ifdef SQLITE_ENABLE_CEROD +#if SQLITE_ENABLE_CEROD "ENABLE_CEROD", #endif -#ifdef SQLITE_ENABLE_COLUMN_METADATA +#if SQLITE_ENABLE_COLUMN_METADATA "ENABLE_COLUMN_METADATA", #endif -#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT +#if SQLITE_ENABLE_EXPENSIVE_ASSERT "ENABLE_EXPENSIVE_ASSERT", #endif -#ifdef SQLITE_ENABLE_FTS1 +#if SQLITE_ENABLE_FTS1 "ENABLE_FTS1", #endif -#ifdef SQLITE_ENABLE_FTS2 +#if SQLITE_ENABLE_FTS2 "ENABLE_FTS2", #endif -#ifdef SQLITE_ENABLE_FTS3 +#if SQLITE_ENABLE_FTS3 "ENABLE_FTS3", #endif -#ifdef SQLITE_ENABLE_FTS3_PARENTHESIS +#if SQLITE_ENABLE_FTS3_PARENTHESIS "ENABLE_FTS3_PARENTHESIS", #endif -#ifdef SQLITE_ENABLE_FTS4 +#if SQLITE_ENABLE_FTS4 "ENABLE_FTS4", #endif -#ifdef SQLITE_ENABLE_ICU +#if SQLITE_ENABLE_ICU "ENABLE_ICU", #endif -#ifdef SQLITE_ENABLE_IOTRACE +#if SQLITE_ENABLE_IOTRACE "ENABLE_IOTRACE", #endif -#ifdef SQLITE_ENABLE_LOAD_EXTENSION +#if SQLITE_ENABLE_LOAD_EXTENSION "ENABLE_LOAD_EXTENSION", #endif -#ifdef SQLITE_ENABLE_LOCKING_STYLE +#if SQLITE_ENABLE_LOCKING_STYLE "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE), #endif -#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT +#if SQLITE_ENABLE_MEMORY_MANAGEMENT "ENABLE_MEMORY_MANAGEMENT", #endif -#ifdef SQLITE_ENABLE_MEMSYS3 +#if SQLITE_ENABLE_MEMSYS3 "ENABLE_MEMSYS3", #endif -#ifdef SQLITE_ENABLE_MEMSYS5 +#if SQLITE_ENABLE_MEMSYS5 "ENABLE_MEMSYS5", #endif -#ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK +#if SQLITE_ENABLE_OVERSIZE_CELL_CHECK "ENABLE_OVERSIZE_CELL_CHECK", #endif -#ifdef SQLITE_ENABLE_RTREE +#if SQLITE_ENABLE_RTREE "ENABLE_RTREE", #endif #if defined(SQLITE_ENABLE_STAT4) "ENABLE_STAT4", #elif defined(SQLITE_ENABLE_STAT3) "ENABLE_STAT3", #endif -#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY +#if SQLITE_ENABLE_UNLOCK_NOTIFY "ENABLE_UNLOCK_NOTIFY", #endif -#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT +#if SQLITE_ENABLE_UPDATE_DELETE_LIMIT "ENABLE_UPDATE_DELETE_LIMIT", #endif -#ifdef SQLITE_HAS_CODEC +#if SQLITE_HAS_CODEC "HAS_CODEC", #endif -#ifdef SQLITE_HAVE_ISNAN +#if HAVE_ISNAN || SQLITE_HAVE_ISNAN "HAVE_ISNAN", #endif -#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX +#if SQLITE_HOMEGROWN_RECURSIVE_MUTEX "HOMEGROWN_RECURSIVE_MUTEX", #endif -#ifdef SQLITE_IGNORE_AFP_LOCK_ERRORS +#if SQLITE_IGNORE_AFP_LOCK_ERRORS "IGNORE_AFP_LOCK_ERRORS", #endif -#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS +#if SQLITE_IGNORE_FLOCK_LOCK_ERRORS "IGNORE_FLOCK_LOCK_ERRORS", #endif #ifdef SQLITE_INT64_TYPE "INT64_TYPE", #endif -#ifdef SQLITE_LOCK_TRACE +#if SQLITE_LOCK_TRACE "LOCK_TRACE", #endif #if defined(SQLITE_MAX_MMAP_SIZE) && !defined(SQLITE_MAX_MMAP_SIZE_xc) "MAX_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE), #endif #ifdef SQLITE_MAX_SCHEMA_RETRY "MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY), #endif -#ifdef SQLITE_MEMDEBUG +#if SQLITE_MEMDEBUG "MEMDEBUG", #endif -#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT +#if SQLITE_MIXED_ENDIAN_64BIT_FLOAT "MIXED_ENDIAN_64BIT_FLOAT", #endif -#ifdef SQLITE_NO_SYNC +#if SQLITE_NO_SYNC "NO_SYNC", #endif -#ifdef SQLITE_OMIT_ALTERTABLE +#if SQLITE_OMIT_ALTERTABLE "OMIT_ALTERTABLE", #endif -#ifdef SQLITE_OMIT_ANALYZE +#if SQLITE_OMIT_ANALYZE "OMIT_ANALYZE", #endif -#ifdef SQLITE_OMIT_ATTACH +#if SQLITE_OMIT_ATTACH "OMIT_ATTACH", #endif -#ifdef SQLITE_OMIT_AUTHORIZATION +#if SQLITE_OMIT_AUTHORIZATION "OMIT_AUTHORIZATION", #endif -#ifdef SQLITE_OMIT_AUTOINCREMENT +#if SQLITE_OMIT_AUTOINCREMENT "OMIT_AUTOINCREMENT", #endif -#ifdef SQLITE_OMIT_AUTOINIT +#if SQLITE_OMIT_AUTOINIT "OMIT_AUTOINIT", #endif -#ifdef SQLITE_OMIT_AUTOMATIC_INDEX +#if SQLITE_OMIT_AUTOMATIC_INDEX "OMIT_AUTOMATIC_INDEX", #endif -#ifdef SQLITE_OMIT_AUTORESET +#if SQLITE_OMIT_AUTORESET "OMIT_AUTORESET", #endif -#ifdef SQLITE_OMIT_AUTOVACUUM +#if SQLITE_OMIT_AUTOVACUUM "OMIT_AUTOVACUUM", #endif -#ifdef SQLITE_OMIT_BETWEEN_OPTIMIZATION +#if SQLITE_OMIT_BETWEEN_OPTIMIZATION "OMIT_BETWEEN_OPTIMIZATION", #endif -#ifdef SQLITE_OMIT_BLOB_LITERAL +#if SQLITE_OMIT_BLOB_LITERAL "OMIT_BLOB_LITERAL", #endif -#ifdef SQLITE_OMIT_BTREECOUNT +#if SQLITE_OMIT_BTREECOUNT "OMIT_BTREECOUNT", #endif -#ifdef SQLITE_OMIT_BUILTIN_TEST +#if SQLITE_OMIT_BUILTIN_TEST "OMIT_BUILTIN_TEST", #endif -#ifdef SQLITE_OMIT_CAST +#if SQLITE_OMIT_CAST "OMIT_CAST", #endif -#ifdef SQLITE_OMIT_CHECK +#if SQLITE_OMIT_CHECK "OMIT_CHECK", #endif -#ifdef SQLITE_OMIT_COMPLETE +#if SQLITE_OMIT_COMPLETE "OMIT_COMPLETE", #endif -#ifdef SQLITE_OMIT_COMPOUND_SELECT +#if SQLITE_OMIT_COMPOUND_SELECT "OMIT_COMPOUND_SELECT", #endif -#ifdef SQLITE_OMIT_DATETIME_FUNCS +#if SQLITE_OMIT_CTE + "OMIT_CTE", +#endif +#if SQLITE_OMIT_DATETIME_FUNCS "OMIT_DATETIME_FUNCS", #endif -#ifdef SQLITE_OMIT_DECLTYPE +#if SQLITE_OMIT_DECLTYPE "OMIT_DECLTYPE", #endif -#ifdef SQLITE_OMIT_DEPRECATED +#if SQLITE_OMIT_DEPRECATED "OMIT_DEPRECATED", #endif -#ifdef SQLITE_OMIT_DISKIO +#if SQLITE_OMIT_DISKIO "OMIT_DISKIO", #endif -#ifdef SQLITE_OMIT_EXPLAIN +#if SQLITE_OMIT_EXPLAIN "OMIT_EXPLAIN", #endif -#ifdef SQLITE_OMIT_FLAG_PRAGMAS +#if SQLITE_OMIT_FLAG_PRAGMAS "OMIT_FLAG_PRAGMAS", #endif -#ifdef SQLITE_OMIT_FLOATING_POINT +#if SQLITE_OMIT_FLOATING_POINT "OMIT_FLOATING_POINT", #endif -#ifdef SQLITE_OMIT_FOREIGN_KEY +#if SQLITE_OMIT_FOREIGN_KEY "OMIT_FOREIGN_KEY", #endif -#ifdef SQLITE_OMIT_GET_TABLE +#if SQLITE_OMIT_GET_TABLE "OMIT_GET_TABLE", #endif -#ifdef SQLITE_OMIT_INCRBLOB +#if SQLITE_OMIT_INCRBLOB "OMIT_INCRBLOB", #endif -#ifdef SQLITE_OMIT_INTEGRITY_CHECK +#if SQLITE_OMIT_INTEGRITY_CHECK "OMIT_INTEGRITY_CHECK", #endif -#ifdef SQLITE_OMIT_LIKE_OPTIMIZATION +#if SQLITE_OMIT_LIKE_OPTIMIZATION "OMIT_LIKE_OPTIMIZATION", #endif -#ifdef SQLITE_OMIT_LOAD_EXTENSION +#if SQLITE_OMIT_LOAD_EXTENSION "OMIT_LOAD_EXTENSION", #endif -#ifdef SQLITE_OMIT_LOCALTIME +#if SQLITE_OMIT_LOCALTIME "OMIT_LOCALTIME", #endif -#ifdef SQLITE_OMIT_LOOKASIDE +#if SQLITE_OMIT_LOOKASIDE "OMIT_LOOKASIDE", #endif -#ifdef SQLITE_OMIT_MEMORYDB +#if SQLITE_OMIT_MEMORYDB "OMIT_MEMORYDB", #endif -#ifdef SQLITE_OMIT_OR_OPTIMIZATION +#if SQLITE_OMIT_OR_OPTIMIZATION "OMIT_OR_OPTIMIZATION", #endif -#ifdef SQLITE_OMIT_PAGER_PRAGMAS +#if SQLITE_OMIT_PAGER_PRAGMAS "OMIT_PAGER_PRAGMAS", #endif -#ifdef SQLITE_OMIT_PRAGMA +#if SQLITE_OMIT_PRAGMA "OMIT_PRAGMA", #endif -#ifdef SQLITE_OMIT_PROGRESS_CALLBACK +#if SQLITE_OMIT_PROGRESS_CALLBACK "OMIT_PROGRESS_CALLBACK", #endif -#ifdef SQLITE_OMIT_QUICKBALANCE +#if SQLITE_OMIT_QUICKBALANCE "OMIT_QUICKBALANCE", #endif -#ifdef SQLITE_OMIT_REINDEX +#if SQLITE_OMIT_REINDEX "OMIT_REINDEX", #endif -#ifdef SQLITE_OMIT_SCHEMA_PRAGMAS +#if SQLITE_OMIT_SCHEMA_PRAGMAS "OMIT_SCHEMA_PRAGMAS", #endif -#ifdef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS +#if SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS "OMIT_SCHEMA_VERSION_PRAGMAS", #endif -#ifdef SQLITE_OMIT_SHARED_CACHE +#if SQLITE_OMIT_SHARED_CACHE "OMIT_SHARED_CACHE", #endif -#ifdef SQLITE_OMIT_SUBQUERY +#if SQLITE_OMIT_SUBQUERY "OMIT_SUBQUERY", #endif -#ifdef SQLITE_OMIT_TCL_VARIABLE +#if SQLITE_OMIT_TCL_VARIABLE "OMIT_TCL_VARIABLE", #endif -#ifdef SQLITE_OMIT_TEMPDB +#if SQLITE_OMIT_TEMPDB "OMIT_TEMPDB", #endif -#ifdef SQLITE_OMIT_TRACE +#if SQLITE_OMIT_TRACE "OMIT_TRACE", #endif -#ifdef SQLITE_OMIT_TRIGGER +#if SQLITE_OMIT_TRIGGER "OMIT_TRIGGER", #endif -#ifdef SQLITE_OMIT_TRUNCATE_OPTIMIZATION +#if SQLITE_OMIT_TRUNCATE_OPTIMIZATION "OMIT_TRUNCATE_OPTIMIZATION", #endif -#ifdef SQLITE_OMIT_UTF16 +#if SQLITE_OMIT_UTF16 "OMIT_UTF16", #endif -#ifdef SQLITE_OMIT_VACUUM +#if SQLITE_OMIT_VACUUM "OMIT_VACUUM", #endif -#ifdef SQLITE_OMIT_VIEW +#if SQLITE_OMIT_VIEW "OMIT_VIEW", #endif -#ifdef SQLITE_OMIT_VIRTUALTABLE +#if SQLITE_OMIT_VIRTUALTABLE "OMIT_VIRTUALTABLE", #endif -#ifdef SQLITE_OMIT_WAL +#if SQLITE_OMIT_WAL "OMIT_WAL", #endif -#ifdef SQLITE_OMIT_WSD +#if SQLITE_OMIT_WSD "OMIT_WSD", #endif -#ifdef SQLITE_OMIT_XFER_OPT +#if SQLITE_OMIT_XFER_OPT "OMIT_XFER_OPT", #endif -#ifdef SQLITE_PERFORMANCE_TRACE +#if SQLITE_PERFORMANCE_TRACE "PERFORMANCE_TRACE", #endif -#ifdef SQLITE_PROXY_DEBUG +#if SQLITE_PROXY_DEBUG "PROXY_DEBUG", #endif -#ifdef SQLITE_RTREE_INT_ONLY +#if SQLITE_RTREE_INT_ONLY "RTREE_INT_ONLY", #endif -#ifdef SQLITE_SECURE_DELETE +#if SQLITE_SECURE_DELETE "SECURE_DELETE", #endif -#ifdef SQLITE_SMALL_STACK +#if SQLITE_SMALL_STACK "SMALL_STACK", #endif -#ifdef SQLITE_SOUNDEX +#if SQLITE_SOUNDEX "SOUNDEX", #endif -#ifdef SQLITE_TCL +#if SQLITE_SYSTEM_MALLOC + "SYSTEM_MALLOC", +#endif +#if SQLITE_TCL "TCL", #endif #if defined(SQLITE_TEMP_STORE) && !defined(SQLITE_TEMP_STORE_xc) "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE), #endif -#ifdef SQLITE_TEST +#if SQLITE_TEST "TEST", #endif #if defined(SQLITE_THREADSAFE) "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), #endif -#ifdef SQLITE_USE_ALLOCA +#if SQLITE_USE_ALLOCA "USE_ALLOCA", #endif -#ifdef SQLITE_ZERO_MALLOC +#if SQLITE_USER_AUTHENTICATION + "USER_AUTHENTICATION", +#endif +#if SQLITE_WIN32_MALLOC + "WIN32_MALLOC", +#endif +#if SQLITE_ZERO_MALLOC "ZERO_MALLOC" #endif }; /* @@ -13184,20 +14353,27 @@ ** was used and false if not. ** ** The name can optionally begin with "SQLITE_" but the "SQLITE_" prefix ** is not required for a match. */ -SQLITE_API int sqlite3_compileoption_used(const char *zOptName){ +SQLITE_API int SQLITE_STDCALL sqlite3_compileoption_used(const char *zOptName){ int i, n; + +#if SQLITE_ENABLE_API_ARMOR + if( zOptName==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif if( sqlite3StrNICmp(zOptName, "SQLITE_", 7)==0 ) zOptName += 7; n = sqlite3Strlen30(zOptName); /* Since ArraySize(azCompileOpt) is normally in single digits, a ** linear search is adequate. No need for a binary search. */ for(i=0; i<ArraySize(azCompileOpt); i++){ if( sqlite3StrNICmp(zOptName, azCompileOpt[i], n)==0 - && sqlite3CtypeMap[(unsigned char)azCompileOpt[i][n]]==0 + && sqlite3IsIdChar((unsigned char)azCompileOpt[i][n])==0 ){ return 1; } } return 0; @@ -13205,11 +14381,11 @@ /* ** Return the N-th compile-time option string. If N is out of range, ** return a NULL pointer. */ -SQLITE_API const char *sqlite3_compileoption_get(int N){ +SQLITE_API const char *SQLITE_STDCALL sqlite3_compileoption_get(int N){ if( N>=0 && N<ArraySize(azCompileOpt) ){ return azCompileOpt[N]; } return 0; } @@ -13271,11 +14447,11 @@ typedef struct VdbeOp Op; /* ** Boolean values */ -typedef unsigned char Bool; +typedef unsigned Bool; /* Opaque type used by code in vdbesort.c */ typedef struct VdbeSorter VdbeSorter; /* Opaque type used by the explainer */ @@ -13288,43 +14464,41 @@ ** A cursor is a pointer into a single BTree within a database file. ** The cursor can seek to a BTree entry with a particular key, or ** loop over all entries of the Btree. You can also insert new BTree ** entries or retrieve the key or data from the entry that the cursor ** is currently pointing to. +** +** Cursors can also point to virtual tables, sorters, or "pseudo-tables". +** A pseudo-table is a single-row table implemented by registers. ** ** Every cursor that the virtual machine has open is represented by an ** instance of the following structure. */ struct VdbeCursor { BtCursor *pCursor; /* The cursor structure of the backend */ Btree *pBt; /* Separate file holding temporary table */ KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */ - int iDb; /* Index of cursor database in db->aDb[] (or -1) */ + int seekResult; /* Result of previous sqlite3BtreeMoveto() */ int pseudoTableReg; /* Register holding pseudotable content. */ - int nField; /* Number of fields in the header */ - Bool zeroed; /* True if zeroed out and ready for reuse */ - Bool rowidIsValid; /* True if lastRowid is valid */ - Bool atFirst; /* True if pointing to first entry */ - Bool useRandomRowid; /* Generate new record numbers semi-randomly */ - Bool nullRow; /* True if pointing to a row with no data */ - Bool deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */ - Bool isTable; /* True if a table requiring integer keys */ - Bool isIndex; /* True if an index containing keys only - no data */ - Bool isOrdered; /* True if the underlying table is BTREE_UNORDERED */ - Bool isSorter; /* True if a new-style sorter */ - Bool multiPseudo; /* Multi-register pseudo-cursor */ + i16 nField; /* Number of fields in the header */ + u16 nHdrParsed; /* Number of header fields parsed so far */ +#ifdef SQLITE_DEBUG + u8 seekOp; /* Most recent seek operation on this cursor */ +#endif + i8 iDb; /* Index of cursor database in db->aDb[] (or -1) */ + u8 nullRow; /* True if pointing to a row with no data */ + u8 deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */ + Bool isEphemeral:1; /* True for an ephemeral table */ + Bool useRandomRowid:1;/* Generate new record numbers semi-randomly */ + Bool isTable:1; /* True if a table requiring integer keys */ + Bool isOrdered:1; /* True if the underlying table is BTREE_UNORDERED */ + Pgno pgnoRoot; /* Root page of the open btree cursor */ sqlite3_vtab_cursor *pVtabCursor; /* The cursor for a virtual table */ - const sqlite3_module *pModule; /* Module for cursor pVtabCursor */ i64 seqCount; /* Sequence counter */ i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */ - i64 lastRowid; /* Last rowid from a Next or NextIdx operation */ VdbeSorter *pSorter; /* Sorter object for OP_SorterOpen cursors */ - /* Result of last sqlite3BtreeMoveto() done by an OP_NotExists or - ** OP_IsUnique opcode on this cursor. */ - int seekResult; - /* Cached information about the header for the data record that the ** cursor is currently pointing to. Only valid if cacheStatus matches ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of ** CACHE_STALE and so setting cacheStatus=CACHE_STALE guarantees that ** the cache is out of date. @@ -13331,14 +14505,19 @@ ** ** aRow might point to (ephemeral) data for the current row, or it might ** be NULL. */ u32 cacheStatus; /* Cache is valid if this matches Vdbe.cacheCtr */ - int payloadSize; /* Total number of bytes in the record */ - u32 *aType; /* Type values for all entries in the record */ - u32 *aOffset; /* Cached offsets to the start of each columns data */ - u8 *aRow; /* Data for the current row, if all on one page */ + u32 payloadSize; /* Total number of bytes in the record */ + u32 szRow; /* Byte available in aRow */ + u32 iHdrOffset; /* Offset to next unparsed byte of the header */ + const u8 *aRow; /* Data for the current row, if all on one page */ + u32 *aOffset; /* Pointer to aType[nField] */ + u32 aType[1]; /* Type values for all entries in the record */ + /* 2*nField extra array elements allocated for aType[], beyond the one + ** static element declared in the structure. nField total array slots for + ** aType[] and nField+1 array slots for aOffset[] */ }; typedef struct VdbeCursor VdbeCursor; /* ** When a sub-program is executed (OP_Program), a structure of this type @@ -13364,10 +14543,11 @@ typedef struct VdbeFrame VdbeFrame; struct VdbeFrame { Vdbe *v; /* VM this frame belongs to */ VdbeFrame *pParent; /* Parent of this frame, or NULL if parent is main */ Op *aOp; /* Program instructions for parent frame */ + i64 *anExec; /* Event counters from parent frame */ Mem *aMem; /* Array of memory cells for parent frame */ u8 *aOnceFlag; /* Array of OP_Once flags for parent frame */ VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */ void *token; /* Copy of SubProgram.token */ i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */ @@ -13376,11 +14556,12 @@ int nOp; /* Size of aOp array */ int nMem; /* Number of entries in aMem */ int nOnceFlag; /* Number of entries in aOnceFlag */ int nChildMem; /* Number of memory cells for child frame */ int nChildCsr; /* Number of cursors for child frame */ - int nChange; /* Statement changes (Vdbe.nChanges) */ + int nChange; /* Statement changes (Vdbe.nChange) */ + int nDbChange; /* Value of db->nChange */ }; #define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))]) /* @@ -13392,30 +14573,32 @@ ** Internally, the vdbe manipulates nearly all SQL values as Mem ** structures. Each Mem struct may cache multiple representations (string, ** integer etc.) of the same value. */ struct Mem { - sqlite3 *db; /* The associated database connection */ - char *z; /* String or BLOB value */ - double r; /* Real value */ - union { + union MemValue { + double r; /* Real value used when MEM_Real is set in flags */ i64 i; /* Integer value used when MEM_Int is set in flags */ int nZero; /* Used when bit MEM_Zero is set in flags */ FuncDef *pDef; /* Used only when flags==MEM_Agg */ RowSet *pRowSet; /* Used only when flags==MEM_RowSet */ VdbeFrame *pFrame; /* Used when flags==MEM_Frame */ } u; - int n; /* Number of characters in string value, excluding '\0' */ u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */ - u8 type; /* One of SQLITE_NULL, SQLITE_TEXT, SQLITE_INTEGER, etc */ u8 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */ + int n; /* Number of characters in string value, excluding '\0' */ + char *z; /* String or BLOB value */ + /* ShallowCopy only needs to copy the information above */ + char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */ + int szMalloc; /* Size of the zMalloc allocation */ + u32 uTemp; /* Transient storage for serial_type in OP_MakeRecord */ + sqlite3 *db; /* The associated database connection */ + void (*xDel)(void*);/* Destructor for Mem.z - only valid if MEM_Dyn */ #ifdef SQLITE_DEBUG Mem *pScopyFrom; /* This Mem is a shallow copy of pScopyFrom */ void *pFiller; /* So that sizeof(Mem) is a multiple of 8 */ #endif - void (*xDel)(void *); /* If not null, call this function to delete Mem.z */ - char *zMalloc; /* Dynamic buffer allocated by sqlite3_malloc() */ }; /* One or more of the following flags are set to indicate the validOK ** representations of the value stored in the Mem struct. ** @@ -13431,13 +14614,14 @@ #define MEM_Null 0x0001 /* Value is NULL */ #define MEM_Str 0x0002 /* Value is a string */ #define MEM_Int 0x0004 /* Value is an integer */ #define MEM_Real 0x0008 /* Value is a real number */ #define MEM_Blob 0x0010 /* Value is a BLOB */ +#define MEM_AffMask 0x001f /* Mask of affinity bits */ #define MEM_RowSet 0x0020 /* Value is a RowSet object */ #define MEM_Frame 0x0040 /* Value is a VdbeFrame object */ -#define MEM_Invalid 0x0080 /* Value is undefined */ +#define MEM_Undefined 0x0080 /* Value is undefined */ #define MEM_Cleared 0x0100 /* NULL set by OP_Null, not from data */ #define MEM_TypeMask 0x01ff /* Mask of type bits */ /* Whenever Mem contains a valid string or blob representation, one of @@ -13444,11 +14628,11 @@ ** the following flags must be set to determine the memory management ** policy for Mem.z. The MEM_Term flag tells us whether or not the ** string is \000 or \u0000 terminated */ #define MEM_Term 0x0200 /* String rep is nul terminated */ -#define MEM_Dyn 0x0400 /* Need to call sqliteFree() on Mem.z */ +#define MEM_Dyn 0x0400 /* Need to call Mem.xDel() on Mem.z */ #define MEM_Static 0x0800 /* Mem.z points to a static string */ #define MEM_Ephem 0x1000 /* Mem.z points to an ephemeral string */ #define MEM_Agg 0x2000 /* Mem.z points to an agg function context */ #define MEM_Zero 0x4000 /* Mem.i contains count of 0s appended to blob */ #ifdef SQLITE_OMIT_INCRBLOB @@ -13465,15 +14649,15 @@ /* ** Return true if a memory cell is not marked as invalid. This macro ** is for use inside assert() statements only. */ #ifdef SQLITE_DEBUG -#define memIsValid(M) ((M)->flags & MEM_Invalid)==0 +#define memIsValid(M) ((M)->flags & MEM_Undefined)==0 #endif /* -** Each auxilliary data pointer stored by a user defined function +** Each auxiliary data pointer stored by a user defined function ** implementation calling sqlite3_set_auxdata() is stored in an instance ** of this structure. All such structures associated with a single VM ** are stored in a linked list headed at Vdbe.pAuxData. All are destroyed ** when the VM is halted (if not before). */ @@ -13484,11 +14668,11 @@ void (*xDelete)(void *); /* Destructor for the aux data */ AuxData *pNext; /* Next element in list */ }; /* -** The "context" argument for a installable function. A pointer to an +** The "context" argument for an installable function. A pointer to an ** instance of this structure is the first argument to the routines used ** implement the SQL functions. ** ** There is a typedef for this structure in sqlite.h. So all routines, ** even the public interface to SQLite, can use a pointer to this structure. @@ -13497,18 +14681,17 @@ ** ** This structure is defined inside of vdbeInt.h because it uses substructures ** (Mem) which are only defined there. */ struct sqlite3_context { - FuncDef *pFunc; /* Pointer to function information. MUST BE FIRST */ - Mem s; /* The return value is stored here */ + Mem *pOut; /* The return value is stored here */ + FuncDef *pFunc; /* Pointer to function information */ Mem *pMem; /* Memory cell used to store aggregate context */ - CollSeq *pColl; /* Collating sequence */ Vdbe *pVdbe; /* The VM that owns this context */ int iOp; /* Instruction number of OP_Function */ int isError; /* Error code returned by the function. */ - u8 skipFlag; /* Skip skip accumulator loading if true */ + u8 skipFlag; /* Skip accumulator loading if true */ u8 fErrorOrAux; /* isError!=0 or pVdbe->pAuxData modified */ }; /* ** An Explain object accumulates indented output which is helpful @@ -13525,38 +14708,37 @@ /* A bitfield type for use inside of structures. Always follow with :N where ** N is the number of bits. */ typedef unsigned bft; /* Bit Field Type */ +typedef struct ScanStatus ScanStatus; +struct ScanStatus { + int addrExplain; /* OP_Explain for loop */ + int addrLoop; /* Address of "loops" counter */ + int addrVisit; /* Address of "rows visited" counter */ + int iSelectID; /* The "Select-ID" for this loop */ + LogEst nEst; /* Estimated output rows per loop */ + char *zName; /* Name of table or index */ +}; + /* ** An instance of the virtual machine. This structure contains the complete ** state of the virtual machine. ** ** The "sqlite3_stmt" structure pointer that is returned by sqlite3_prepare() ** is really a pointer to an instance of this structure. -** -** The Vdbe.inVtabMethod variable is set to non-zero for the duration of -** any virtual table method invocations made by the vdbe program. It is -** set to 2 for xDestroy method calls and 1 for all other methods. This -** variable is used for two purposes: to allow xDestroy methods to execute -** "DROP TABLE" statements and to prevent some nasty side effects of -** malloc failure when SQLite is invoked recursively by a virtual table -** method function. */ struct Vdbe { sqlite3 *db; /* The database connection that owns this statement */ Op *aOp; /* Space to hold the virtual machine's program */ Mem *aMem; /* The memory locations */ Mem **apArg; /* Arguments to currently executing user function */ Mem *aColName; /* Column names to return */ Mem *pResultSet; /* Pointer to an array of results */ + Parse *pParse; /* Parsing context used to create this Vdbe */ int nMem; /* Number of memory locations currently allocated */ int nOp; /* Number of instructions in the program */ - int nOpAlloc; /* Number of slots allocated for aOp[] */ - int nLabel; /* Number of labels used */ - int *aLabel; /* Space to hold the labels */ - u16 nResColumn; /* Number of columns in one row of the result set */ int nCursor; /* Number of slots in apCsr[] */ u32 magic; /* Magic number for sanity checking */ char *zErrMsg; /* Error message written here */ Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */ VdbeCursor **apCsr; /* One element of this array for each open cursor */ @@ -13565,14 +14747,17 @@ ynVar nVar; /* Number of entries in aVar[] */ ynVar nzVar; /* Number of entries in azVar[] */ u32 cacheCtr; /* VdbeCursor row cache generation counter */ int pc; /* The program counter */ int rc; /* Value to return */ +#ifdef SQLITE_DEBUG + int rcApp; /* errcode set by sqlite3_result_error_code() */ +#endif + u16 nResColumn; /* Number of columns in one row of the result set */ u8 errorAction; /* Recovery action to do in case of an error */ u8 minWriteFileFormat; /* Minimum file format for writable database files */ bft explain:2; /* True if EXPLAIN present on SQL command */ - bft inVtabMethod:2; /* See comments above */ bft changeCntOn:1; /* True to update the change-counter */ bft expired:1; /* True if the VM needs to be recompiled */ bft runOnlyOnce:1; /* Automatically expire on reset */ bft usesStmtJournal:1; /* True if uses a statement journal */ bft readOnly:1; /* True for statements that do not write */ @@ -13591,25 +14776,23 @@ i64 nFkConstraint; /* Number of imm. FK constraints this VM */ i64 nStmtDefCons; /* Number of def. constraints when stmt started */ i64 nStmtDefImmCons; /* Number of def. imm constraints when stmt started */ char *zSql; /* Text of the SQL statement that generated this */ void *pFree; /* Free this when deleting the vdbe */ -#ifdef SQLITE_DEBUG - FILE *trace; /* Write an execution trace here, if not NULL */ -#endif -#ifdef SQLITE_ENABLE_TREE_EXPLAIN - Explain *pExplain; /* The explainer */ - char *zExplain; /* Explanation of data structures */ -#endif VdbeFrame *pFrame; /* Parent frame */ VdbeFrame *pDelFrame; /* List of frame objects to free on VM reset */ int nFrame; /* Number of frames in pFrame list */ u32 expmask; /* Binding to these vars invalidates VM */ SubProgram *pProgram; /* Linked list of all sub-programs used by VM */ int nOnceFlag; /* Size of array aOnceFlag[] */ u8 *aOnceFlag; /* Flags for OP_Once */ AuxData *pAuxData; /* Linked list of auxdata allocations */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + i64 *anExec; /* Number of times each op has been executed */ + int nScan; /* Entries in aScan[] */ + ScanStatus *aScan; /* Scan definitions for sqlite3_stmt_scanstatus() */ +#endif }; /* ** The following are allowed values for Vdbe.magic */ @@ -13622,23 +14805,23 @@ ** Function prototypes */ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *, VdbeCursor*); void sqliteVdbePopStack(Vdbe*,int); SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor*); +SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor*); #if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE*, int, Op*); #endif SQLITE_PRIVATE u32 sqlite3VdbeSerialTypeLen(u32); SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem*, int); -SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(unsigned char*, int, Mem*, int); +SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(unsigned char*, Mem*, u32); SQLITE_PRIVATE u32 sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*); SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(Vdbe*, int, int); int sqlite2BtreeKeyCompare(BtCursor *, const void *, int, int, int *); -SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare(VdbeCursor*,UnpackedRecord*,int*); -SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3*, BtCursor *, i64 *); -SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*); +SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare(sqlite3*,VdbeCursor*,UnpackedRecord*,int*); +SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3*, BtCursor*, i64*); SQLITE_PRIVATE int sqlite3VdbeExec(Vdbe*); SQLITE_PRIVATE int sqlite3VdbeList(Vdbe*); SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe*); SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *, int); SQLITE_PRIVATE int sqlite3VdbeMemTooBig(Mem*); @@ -13651,43 +14834,44 @@ #ifdef SQLITE_OMIT_FLOATING_POINT # define sqlite3VdbeMemSetDouble sqlite3VdbeMemSetInt64 #else SQLITE_PRIVATE void sqlite3VdbeMemSetDouble(Mem*, double); #endif +SQLITE_PRIVATE void sqlite3VdbeMemInit(Mem*,sqlite3*,u16); SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem*); SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem*,int); SQLITE_PRIVATE void sqlite3VdbeMemSetRowSet(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem*); -SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem*, int); +SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem*, u8, u8); SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem*); SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem*); SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem*); -SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(BtCursor*,int,int,int,Mem*); +SQLITE_PRIVATE void sqlite3VdbeMemCast(Mem*,u8,u8); +SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,int,Mem*); SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p); -SQLITE_PRIVATE void sqlite3VdbeMemReleaseExternal(Mem *p); -#define VdbeMemRelease(X) \ - if((X)->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet|MEM_Frame)) \ - sqlite3VdbeMemReleaseExternal(X); +#define VdbeMemDynamic(X) \ + (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet|MEM_Frame))!=0) SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem*, FuncDef*); SQLITE_PRIVATE const char *sqlite3OpcodeName(int); SQLITE_PRIVATE int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve); +SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int n); SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *, int); SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame*); SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *); -SQLITE_PRIVATE void sqlite3VdbeMemStoreType(Mem *pMem); SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p); -SQLITE_PRIVATE int sqlite3VdbeSorterInit(sqlite3 *, VdbeCursor *); +SQLITE_PRIVATE int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *); +SQLITE_PRIVATE void sqlite3VdbeSorterReset(sqlite3 *, VdbeSorter *); SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *, VdbeCursor *); SQLITE_PRIVATE int sqlite3VdbeSorterRowkey(const VdbeCursor *, Mem *); SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *, const VdbeCursor *, int *); -SQLITE_PRIVATE int sqlite3VdbeSorterRewind(sqlite3 *, const VdbeCursor *, int *); -SQLITE_PRIVATE int sqlite3VdbeSorterWrite(sqlite3 *, const VdbeCursor *, Mem *); -SQLITE_PRIVATE int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int *); +SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *, int *); +SQLITE_PRIVATE int sqlite3VdbeSorterWrite(const VdbeCursor *, Mem *); +SQLITE_PRIVATE int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int, int *); #if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0 SQLITE_PRIVATE void sqlite3VdbeEnter(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeLeave(Vdbe*); #else @@ -13695,10 +14879,11 @@ # define sqlite3VdbeLeave(X) #endif #ifdef SQLITE_DEBUG SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe*,Mem*); +SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem*); #endif #ifndef SQLITE_OMIT_FOREIGN_KEY SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *, int); #else @@ -13728,13 +14913,35 @@ /* ** Variables in which to record status information. */ typedef struct sqlite3StatType sqlite3StatType; static SQLITE_WSD struct sqlite3StatType { - int nowValue[10]; /* Current value */ - int mxValue[10]; /* Maximum value */ +#if SQLITE_PTRSIZE>4 + sqlite3_int64 nowValue[10]; /* Current value */ + sqlite3_int64 mxValue[10]; /* Maximum value */ +#else + u32 nowValue[10]; /* Current value */ + u32 mxValue[10]; /* Maximum value */ +#endif } sqlite3Stat = { {0,}, {0,} }; + +/* +** Elements of sqlite3Stat[] are protected by either the memory allocator +** mutex, or by the pcache1 mutex. The following array determines which. +*/ +static const char statMutex[] = { + 0, /* SQLITE_STATUS_MEMORY_USED */ + 1, /* SQLITE_STATUS_PAGECACHE_USED */ + 1, /* SQLITE_STATUS_PAGECACHE_OVERFLOW */ + 0, /* SQLITE_STATUS_SCRATCH_USED */ + 0, /* SQLITE_STATUS_SCRATCH_OVERFLOW */ + 0, /* SQLITE_STATUS_MALLOC_SIZE */ + 0, /* SQLITE_STATUS_PARSER_STACK */ + 1, /* SQLITE_STATUS_PAGECACHE_SIZE */ + 0, /* SQLITE_STATUS_SCRATCH_SIZE */ + 0, /* SQLITE_STATUS_MALLOC_COUNT */ +}; /* The "wsdStat" macro will resolve to the status information ** state vector. If writable static data is unsupported on the target, ** we have to locate the state vector at run-time. In the more common @@ -13748,74 +14955,128 @@ # define wsdStatInit # define wsdStat sqlite3Stat #endif /* -** Return the current value of a status parameter. +** Return the current value of a status parameter. The caller must +** be holding the appropriate mutex. */ -SQLITE_PRIVATE int sqlite3StatusValue(int op){ +SQLITE_PRIVATE sqlite3_int64 sqlite3StatusValue(int op){ wsdStatInit; assert( op>=0 && op<ArraySize(wsdStat.nowValue) ); + assert( op>=0 && op<ArraySize(statMutex) ); + assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex() + : sqlite3MallocMutex()) ); return wsdStat.nowValue[op]; } /* -** Add N to the value of a status record. It is assumed that the -** caller holds appropriate locks. +** Add N to the value of a status record. The caller must hold the +** appropriate mutex. (Locking is checked by assert()). +** +** The StatusUp() routine can accept positive or negative values for N. +** The value of N is added to the current status value and the high-water +** mark is adjusted if necessary. +** +** The StatusDown() routine lowers the current value by N. The highwater +** mark is unchanged. N must be non-negative for StatusDown(). */ -SQLITE_PRIVATE void sqlite3StatusAdd(int op, int N){ +SQLITE_PRIVATE void sqlite3StatusUp(int op, int N){ wsdStatInit; assert( op>=0 && op<ArraySize(wsdStat.nowValue) ); + assert( op>=0 && op<ArraySize(statMutex) ); + assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex() + : sqlite3MallocMutex()) ); wsdStat.nowValue[op] += N; if( wsdStat.nowValue[op]>wsdStat.mxValue[op] ){ wsdStat.mxValue[op] = wsdStat.nowValue[op]; } } +SQLITE_PRIVATE void sqlite3StatusDown(int op, int N){ + wsdStatInit; + assert( N>=0 ); + assert( op>=0 && op<ArraySize(statMutex) ); + assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex() + : sqlite3MallocMutex()) ); + assert( op>=0 && op<ArraySize(wsdStat.nowValue) ); + wsdStat.nowValue[op] -= N; +} /* -** Set the value of a status to X. +** Set the value of a status to X. The highwater mark is adjusted if +** necessary. The caller must hold the appropriate mutex. */ SQLITE_PRIVATE void sqlite3StatusSet(int op, int X){ wsdStatInit; assert( op>=0 && op<ArraySize(wsdStat.nowValue) ); + assert( op>=0 && op<ArraySize(statMutex) ); + assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex() + : sqlite3MallocMutex()) ); wsdStat.nowValue[op] = X; if( wsdStat.nowValue[op]>wsdStat.mxValue[op] ){ wsdStat.mxValue[op] = wsdStat.nowValue[op]; } } /* ** Query status information. -** -** This implementation assumes that reading or writing an aligned -** 32-bit integer is an atomic operation. If that assumption is not true, -** then this routine is not threadsafe. */ -SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag){ +SQLITE_API int SQLITE_STDCALL sqlite3_status64( + int op, + sqlite3_int64 *pCurrent, + sqlite3_int64 *pHighwater, + int resetFlag +){ + sqlite3_mutex *pMutex; wsdStatInit; if( op<0 || op>=ArraySize(wsdStat.nowValue) ){ return SQLITE_MISUSE_BKPT; } +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCurrent==0 || pHighwater==0 ) return SQLITE_MISUSE_BKPT; +#endif + pMutex = statMutex[op] ? sqlite3Pcache1Mutex() : sqlite3MallocMutex(); + sqlite3_mutex_enter(pMutex); *pCurrent = wsdStat.nowValue[op]; *pHighwater = wsdStat.mxValue[op]; if( resetFlag ){ wsdStat.mxValue[op] = wsdStat.nowValue[op]; } + sqlite3_mutex_leave(pMutex); + (void)pMutex; /* Prevent warning when SQLITE_THREADSAFE=0 */ return SQLITE_OK; +} +SQLITE_API int SQLITE_STDCALL sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag){ + sqlite3_int64 iCur, iHwtr; + int rc; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCurrent==0 || pHighwater==0 ) return SQLITE_MISUSE_BKPT; +#endif + rc = sqlite3_status64(op, &iCur, &iHwtr, resetFlag); + if( rc==0 ){ + *pCurrent = (int)iCur; + *pHighwater = (int)iHwtr; + } + return rc; } /* ** Query status information for a single database connection */ -SQLITE_API int sqlite3_db_status( +SQLITE_API int SQLITE_STDCALL sqlite3_db_status( sqlite3 *db, /* The database connection whose status is desired */ int op, /* Status verb */ int *pCurrent, /* Write current value here */ int *pHighwater, /* Write high-water mark here */ int resetFlag /* Reset high-water mark if true */ ){ int rc = SQLITE_OK; /* Return code */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwater==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif sqlite3_mutex_enter(db->mutex); switch( op ){ case SQLITE_DBSTATUS_LOOKASIDE_USED: { *pCurrent = db->lookaside.nOut; *pHighwater = db->lookaside.mxOut; @@ -13920,11 +15181,11 @@ sqlite3VdbeClearObject(db, pVdbe); sqlite3DbFree(db, pVdbe); } db->pnBytesFreed = 0; - *pHighwater = 0; + *pHighwater = 0; /* IMP: R-64479-57858 */ *pCurrent = nByte; break; } @@ -13945,21 +15206,23 @@ if( db->aDb[i].pBt ){ Pager *pPager = sqlite3BtreePager(db->aDb[i].pBt); sqlite3PagerCacheStat(pPager, op, resetFlag, &nRet); } } - *pHighwater = 0; + *pHighwater = 0; /* IMP: R-42420-56072 */ + /* IMP: R-54100-20147 */ + /* IMP: R-29431-39229 */ *pCurrent = nRet; break; } /* Set *pCurrent to non-zero if there are unresolved deferred foreign ** key constraints. Set *pCurrent to zero if all foreign key constraints ** have been satisfied. The *pHighwater is always set to zero. */ case SQLITE_DBSTATUS_DEFERRED_FKS: { - *pHighwater = 0; + *pHighwater = 0; /* IMP: R-11967-56545 */ *pCurrent = db->nDeferredImmCons>0 || db->nDeferredCons>0; break; } default: { @@ -13988,26 +15251,26 @@ ** ** There is only one exported symbol in this file - the function ** sqlite3RegisterDateTimeFunctions() found at the bottom of the file. ** All other code has file scope. ** -** SQLite processes all times and dates as Julian Day numbers. The +** SQLite processes all times and dates as julian day numbers. The ** dates and times are stored as the number of days since noon ** in Greenwich on November 24, 4714 B.C. according to the Gregorian ** calendar system. ** ** 1970-01-01 00:00:00 is JD 2440587.5 ** 2000-01-01 00:00:00 is JD 2451544.5 ** -** This implemention requires years to be expressed as a 4-digit number +** This implementation requires years to be expressed as a 4-digit number ** which means that only dates between 0000-01-01 and 9999-12-31 can ** be represented, even though julian day numbers allow a much wider ** range of dates. ** ** The Gregorian calendar system is used for all dates and times, ** even those that predate the Gregorian calendar. Historians usually -** use the Julian calendar for dates prior to 1582-10-15 and for some +** use the julian calendar for dates prior to 1582-10-15 and for some ** dates afterwards, depending on locale. Beware of this difference. ** ** The conversion algorithms are implemented based on descriptions ** in the following text: ** @@ -14275,11 +15538,11 @@ return 1; } } /* -** Attempt to parse the given string into a Julian Day Number. Return +** Attempt to parse the given string into a julian day number. Return ** the number of errors. ** ** The following are acceptable forms for the input string: ** ** YYYY-MM-DD HH:MM:SS.FFF +/-HH:MM @@ -14383,12 +15646,13 @@ ** ** If the user has not indicated to use localtime_r() or localtime_s() ** already, check for an MSVC build environment that provides ** localtime_s(). */ -#if !defined(HAVE_LOCALTIME_R) && !defined(HAVE_LOCALTIME_S) && \ - defined(_MSC_VER) && defined(_CRT_INSECURE_DEPRECATE) +#if !HAVE_LOCALTIME_R && !HAVE_LOCALTIME_S \ + && defined(_MSC_VER) && defined(_CRT_INSECURE_DEPRECATE) +#undef HAVE_LOCALTIME_S #define HAVE_LOCALTIME_S 1 #endif #ifndef SQLITE_OMIT_LOCALTIME /* @@ -14397,15 +15661,18 @@ ** is available. This routine returns 0 on success and ** non-zero on any kind of error. ** ** If the sqlite3GlobalConfig.bLocaltimeFault variable is true then this ** routine will always fail. +** +** EVIDENCE-OF: R-62172-00036 In this implementation, the standard C +** library function localtime_r() is used to assist in the calculation of +** local time. */ static int osLocaltime(time_t *t, struct tm *pTm){ int rc; -#if (!defined(HAVE_LOCALTIME_R) || !HAVE_LOCALTIME_R) \ - && (!defined(HAVE_LOCALTIME_S) || !HAVE_LOCALTIME_S) +#if !HAVE_LOCALTIME_R && !HAVE_LOCALTIME_S struct tm *pX; #if SQLITE_THREADSAFE>0 sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); #endif sqlite3_mutex_enter(mutex); @@ -14418,11 +15685,11 @@ rc = pX==0; #else #ifndef SQLITE_OMIT_BUILTIN_TEST if( sqlite3GlobalConfig.bLocaltimeFault ) return 1; #endif -#if defined(HAVE_LOCALTIME_R) && HAVE_LOCALTIME_R +#if HAVE_LOCALTIME_R rc = localtime_r(t, pTm)==0; #else rc = localtime_s(pTm, t); #endif /* HAVE_LOCALTIME_R */ #endif /* HAVE_LOCALTIME_R || HAVE_LOCALTIME_S */ @@ -14453,10 +15720,15 @@ memset(&sLocal, 0, sizeof(sLocal)); x = *p; computeYMD_HMS(&x); if( x.Y<1971 || x.Y>=2038 ){ + /* EVIDENCE-OF: R-55269-29598 The localtime_r() C function normally only + ** works for years between 1970 and 2037. For dates outside this range, + ** SQLite attempts to map the year into an equivalent year within this + ** range, do the calculation, then map the year back. + */ x.Y = 2000; x.M = 1; x.D = 1; x.h = 0; x.m = 0; @@ -14837,11 +16109,11 @@ ** ** %d day of month ** %f ** fractional seconds SS.SSS ** %H hour 00-24 ** %j day of year 000-366 -** %J ** Julian day number +** %J ** julian day number ** %m month 01-12 ** %M minute 00-59 ** %s seconds since 1970-01-01 ** %S seconds 00-59 ** %w day of week 0-6 sunday==0 @@ -14857,12 +16129,14 @@ DateTime x; u64 n; size_t i,j; char *z; sqlite3 *db; - const char *zFmt = (const char*)sqlite3_value_text(argv[0]); + const char *zFmt; char zBuf[100]; + if( argc==0 ) return; + zFmt = (const char*)sqlite3_value_text(argv[0]); if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return; db = sqlite3_context_db_handle(context); for(i=0, n=1; zFmt[i]; i++, n++){ if( zFmt[i]=='%' ){ switch( zFmt[i+1] ){ @@ -15052,11 +16326,11 @@ UNUSED_PARAMETER(argv); iT = sqlite3StmtCurrentTime(context); if( iT<=0 ) return; t = iT/1000 - 10000*(sqlite3_int64)21086676; -#ifdef HAVE_GMTIME_R +#if HAVE_GMTIME_R pTm = gmtime_r(&t, &sNow); #else sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); pTm = gmtime(&t); if( pTm ) memcpy(&sNow, pTm, sizeof(sNow)); @@ -15208,11 +16482,25 @@ ** really care if the VFS receives and understands the information since it ** is only a hint and can be safely ignored. The sqlite3OsFileControlHint() ** routine has no return value since the return value would be meaningless. */ SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){ - DO_OS_MALLOC_TEST(id); +#ifdef SQLITE_TEST + if( op!=SQLITE_FCNTL_COMMIT_PHASETWO ){ + /* Faults are not injected into COMMIT_PHASETWO because, assuming SQLite + ** is using a regular VFS, it is called after the corresponding + ** transaction has been committed. Injecting a fault at this point + ** confuses the test scripts - the COMMIT comand returns SQLITE_NOMEM + ** but the transaction is committed anyway. + ** + ** The core must call OsFileControl() though, not OsFileControlHint(), + ** as if a custom VFS (e.g. zipvfs) returns an error here, it probably + ** means the commit really has failed and an error should be returned + ** to the user. */ + DO_OS_MALLOC_TEST(id); + } +#endif return id->pMethods->xFileControl(id, op, pArg); } SQLITE_PRIVATE void sqlite3OsFileControlHint(sqlite3_file *id, int op, void *pArg){ (void)id->pMethods->xFileControl(id, op, pArg); } @@ -15396,11 +16684,11 @@ /* ** Locate a VFS by name. If no name is given, simply return the ** first VFS on the list. */ -SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfs){ +SQLITE_API sqlite3_vfs *SQLITE_STDCALL sqlite3_vfs_find(const char *zVfs){ sqlite3_vfs *pVfs = 0; #if SQLITE_THREADSAFE sqlite3_mutex *mutex; #endif #ifndef SQLITE_OMIT_AUTOINIT @@ -15442,16 +16730,20 @@ /* ** Register a VFS with the system. It is harmless to register the same ** VFS multiple times. The new VFS becomes the default if makeDflt is ** true. */ -SQLITE_API int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){ +SQLITE_API int SQLITE_STDCALL sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){ MUTEX_LOGIC(sqlite3_mutex *mutex;) #ifndef SQLITE_OMIT_AUTOINIT int rc = sqlite3_initialize(); if( rc ) return rc; #endif +#ifdef SQLITE_ENABLE_API_ARMOR + if( pVfs==0 ) return SQLITE_MISUSE_BKPT; +#endif + MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) sqlite3_mutex_enter(mutex); vfsUnlink(pVfs); if( makeDflt || vfsList==0 ){ pVfs->pNext = vfsList; @@ -15466,11 +16758,11 @@ } /* ** Unregister a VFS so that it is no longer accessible. */ -SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs *pVfs){ +SQLITE_API int SQLITE_STDCALL sqlite3_vfs_unregister(sqlite3_vfs *pVfs){ #if SQLITE_THREADSAFE sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); #endif sqlite3_mutex_enter(mutex); vfsUnlink(pVfs); @@ -15678,20 +16970,10 @@ ** This version of the memory allocator is the default. It is ** used when no other memory allocator is specified using compile-time ** macros. */ #ifdef SQLITE_SYSTEM_MALLOC - -/* -** The MSVCRT has malloc_usable_size() but it is called _msize(). -** The use of _msize() is automatic, but can be disabled by compiling -** with -DSQLITE_WITHOUT_MSIZE -*/ -#if defined(_MSC_VER) && !defined(SQLITE_WITHOUT_MSIZE) -# define SQLITE_MALLOCSIZE _msize -#endif - #if defined(__APPLE__) && !defined(SQLITE_WITHOUT_ZONEMALLOC) /* ** Use the zone allocator available on apple products unless the ** SQLITE_WITHOUT_ZONEMALLOC symbol is defined. @@ -15710,25 +16992,51 @@ /* ** Use standard C library malloc and free on non-Apple systems. ** Also used by Apple systems if SQLITE_WITHOUT_ZONEMALLOC is defined. */ -#define SQLITE_MALLOC(x) malloc(x) -#define SQLITE_FREE(x) free(x) -#define SQLITE_REALLOC(x,y) realloc((x),(y)) - -#if (defined(_MSC_VER) && !defined(SQLITE_WITHOUT_MSIZE)) \ - || (defined(HAVE_MALLOC_H) && defined(HAVE_MALLOC_USABLE_SIZE)) -# include <malloc.h> /* Needed for malloc_usable_size on linux */ -#endif -#ifdef HAVE_MALLOC_USABLE_SIZE -# ifndef SQLITE_MALLOCSIZE -# define SQLITE_MALLOCSIZE(x) malloc_usable_size(x) -# endif -#else -# undef SQLITE_MALLOCSIZE -#endif +#define SQLITE_MALLOC(x) malloc(x) +#define SQLITE_FREE(x) free(x) +#define SQLITE_REALLOC(x,y) realloc((x),(y)) + +/* +** The malloc.h header file is needed for malloc_usable_size() function +** on some systems (e.g. Linux). +*/ +#if HAVE_MALLOC_H && HAVE_MALLOC_USABLE_SIZE +# define SQLITE_USE_MALLOC_H 1 +# define SQLITE_USE_MALLOC_USABLE_SIZE 1 +/* +** The MSVCRT has malloc_usable_size(), but it is called _msize(). The +** use of _msize() is automatic, but can be disabled by compiling with +** -DSQLITE_WITHOUT_MSIZE. Using the _msize() function also requires +** the malloc.h header file. +*/ +#elif defined(_MSC_VER) && !defined(SQLITE_WITHOUT_MSIZE) +# define SQLITE_USE_MALLOC_H +# define SQLITE_USE_MSIZE +#endif + +/* +** Include the malloc.h header file, if necessary. Also set define macro +** SQLITE_MALLOCSIZE to the appropriate function name, which is _msize() +** for MSVC and malloc_usable_size() for most other systems (e.g. Linux). +** The memory size function can always be overridden manually by defining +** the macro SQLITE_MALLOCSIZE to the desired function name. +*/ +#if defined(SQLITE_USE_MALLOC_H) +# include <malloc.h> +# if defined(SQLITE_USE_MALLOC_USABLE_SIZE) +# if !defined(SQLITE_MALLOCSIZE) +# define SQLITE_MALLOCSIZE(x) malloc_usable_size(x) +# endif +# elif defined(SQLITE_USE_MSIZE) +# if !defined(SQLITE_MALLOCSIZE) +# define SQLITE_MALLOCSIZE _msize +# endif +# endif +#endif /* defined(SQLITE_USE_MALLOC_H) */ #endif /* __APPLE__ or not __APPLE__ */ /* ** Like malloc(), but remember the size of the allocation @@ -15801,11 +17109,11 @@ ** Like realloc(). Resize an allocation previously obtained from ** sqlite3MemMalloc(). ** ** For this low-level interface, we know that pPrior!=0. Cases where ** pPrior==0 while have been intercepted by higher-level routine and -** redirected to xMalloc. Similarly, we know that nByte>0 becauses +** redirected to xMalloc. Similarly, we know that nByte>0 because ** cases where nByte<=0 will have been intercepted by higher-level ** routines and redirected to xFree. */ static void *sqlite3MemRealloc(void *pPrior, int nByte){ #ifdef SQLITE_MALLOCSIZE @@ -16304,11 +17612,11 @@ ** allocation p. Also return true if p==NULL. ** ** This routine is designed for use within an assert() statement, to ** verify the type of an allocation. For example: ** -** assert( sqlite3MemdebugHasType(p, MEMTYPE_DB) ); +** assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); */ SQLITE_PRIVATE int sqlite3MemdebugHasType(void *p, u8 eType){ int rc = 1; if( p && sqlite3GlobalConfig.m.xMalloc==sqlite3MemMalloc ){ struct MemBlockHdr *pHdr; @@ -16326,11 +17634,11 @@ ** allocation p. Also return true if p==NULL. ** ** This routine is designed for use within an assert() statement, to ** verify the type of an allocation. For example: ** -** assert( sqlite3MemdebugNoType(p, MEMTYPE_DB) ); +** assert( sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) ); */ SQLITE_PRIVATE int sqlite3MemdebugNoType(void *p, u8 eType){ int rc = 1; if( p && sqlite3GlobalConfig.m.xMalloc==sqlite3MemMalloc ){ struct MemBlockHdr *pHdr; @@ -17158,11 +18466,11 @@ ** This memory allocator uses the following algorithm: ** ** 1. All memory allocations sizes are rounded up to a power of 2. ** ** 2. If two adjacent free blocks are the halves of a larger block, -** then the two blocks are coalesed into the single larger block. +** then the two blocks are coalesced into the single larger block. ** ** 3. New memory is allocated from the first available free block. ** ** This algorithm is described in: J. M. Robson. "Bounds for Some Functions ** Concerning Dynamic Storage Allocation". Journal of the Association for @@ -17331,36 +18639,17 @@ ** works for chunks that are currently checked out. */ static int memsys5Size(void *p){ int iSize = 0; if( p ){ - int i = ((u8 *)p-mem5.zPool)/mem5.szAtom; + int i = (int)(((u8 *)p-mem5.zPool)/mem5.szAtom); assert( i>=0 && i<mem5.nBlock ); iSize = mem5.szAtom * (1 << (mem5.aCtrl[i]&CTRL_LOGSIZE)); } return iSize; } -/* -** Find the first entry on the freelist iLogsize. Unlink that -** entry and return its index. -*/ -static int memsys5UnlinkFirst(int iLogsize){ - int i; - int iFirst; - - assert( iLogsize>=0 && iLogsize<=LOGMAX ); - i = iFirst = mem5.aiFreelist[iLogsize]; - assert( iFirst>=0 ); - while( i>0 ){ - if( i<iFirst ) iFirst = i; - i = MEM5LINK(i)->next; - } - memsys5Unlink(iFirst, iLogsize); - return iFirst; -} - /* ** Return a block of memory of at least nBytes in size. ** Return NULL if unable. Return NULL if nBytes==0. ** ** The caller guarantees that nByte is positive. @@ -17396,17 +18685,18 @@ /* Make sure mem5.aiFreelist[iLogsize] contains at least one free ** block. If not, then split a block of the next larger power of ** two in order to create a new free block of size iLogsize. */ - for(iBin=iLogsize; mem5.aiFreelist[iBin]<0 && iBin<=LOGMAX; iBin++){} + for(iBin=iLogsize; iBin<=LOGMAX && mem5.aiFreelist[iBin]<0; iBin++){} if( iBin>LOGMAX ){ testcase( sqlite3GlobalConfig.xLog!=0 ); sqlite3_log(SQLITE_NOMEM, "failed to allocate %u bytes", nByte); return 0; } - i = memsys5UnlinkFirst(iBin); + i = mem5.aiFreelist[iBin]; + memsys5Unlink(i, iBin); while( iBin>iLogsize ){ int newSize; iBin--; newSize = 1 << iBin; @@ -17421,10 +18711,16 @@ mem5.totalExcess += iFullSz - nByte; mem5.currentCount++; mem5.currentOut += iFullSz; if( mem5.maxCount<mem5.currentCount ) mem5.maxCount = mem5.currentCount; if( mem5.maxOut<mem5.currentOut ) mem5.maxOut = mem5.currentOut; + +#ifdef SQLITE_DEBUG + /* Make sure the allocated memory does not assume that it is set to zero + ** or retains a value from a previous allocation */ + memset(&mem5.zPool[i*mem5.szAtom], 0xAA, iFullSz); +#endif /* Return a pointer to the allocated memory. */ return (void*)&mem5.zPool[i*mem5.szAtom]; } @@ -17436,11 +18732,11 @@ int iBlock; /* Set iBlock to the index of the block pointed to by pOld in ** the array of mem5.szAtom byte blocks pointed to by mem5.zPool. */ - iBlock = ((u8 *)pOld-mem5.zPool)/mem5.szAtom; + iBlock = (int)(((u8 *)pOld-mem5.zPool)/mem5.szAtom); /* Check that the pointer pOld points to a valid, non-free block. */ assert( iBlock>=0 && iBlock<mem5.nBlock ); assert( ((u8 *)pOld-mem5.zPool)%mem5.szAtom==0 ); assert( (mem5.aCtrl[iBlock] & CTRL_FREE)==0 ); @@ -17479,10 +18775,17 @@ mem5.aCtrl[iBlock] = CTRL_FREE | iLogsize; mem5.aCtrl[iBuddy] = 0; } size *= 2; } + +#ifdef SQLITE_DEBUG + /* Overwrite freed memory with the 0x55 bit pattern to verify that it is + ** not used after being freed */ + memset(&mem5.zPool[iBlock*mem5.szAtom], 0x55, size); +#endif + memsys5Link(iBlock, iLogsize); } /* ** Allocate nBytes of memory. @@ -17791,13 +19094,14 @@ } /* ** Retrieve a pointer to a static mutex or allocate a new dynamic one. */ -SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int id){ +SQLITE_API sqlite3_mutex *SQLITE_STDCALL sqlite3_mutex_alloc(int id){ #ifndef SQLITE_OMIT_AUTOINIT - if( sqlite3_initialize() ) return 0; + if( id<=SQLITE_MUTEX_RECURSIVE && sqlite3_initialize() ) return 0; + if( id>SQLITE_MUTEX_RECURSIVE && sqlite3MutexInit() ) return 0; #endif return sqlite3GlobalConfig.mutex.xMutexAlloc(id); } SQLITE_PRIVATE sqlite3_mutex *sqlite3MutexAlloc(int id){ @@ -17809,31 +19113,31 @@ } /* ** Free a dynamic mutex. */ -SQLITE_API void sqlite3_mutex_free(sqlite3_mutex *p){ +SQLITE_API void SQLITE_STDCALL sqlite3_mutex_free(sqlite3_mutex *p){ if( p ){ sqlite3GlobalConfig.mutex.xMutexFree(p); } } /* ** Obtain the mutex p. If some other thread already has the mutex, block ** until it can be obtained. */ -SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex *p){ +SQLITE_API void SQLITE_STDCALL sqlite3_mutex_enter(sqlite3_mutex *p){ if( p ){ sqlite3GlobalConfig.mutex.xMutexEnter(p); } } /* ** Obtain the mutex p. If successful, return SQLITE_OK. Otherwise, if another ** thread holds the mutex and it cannot be obtained, return SQLITE_BUSY. */ -SQLITE_API int sqlite3_mutex_try(sqlite3_mutex *p){ +SQLITE_API int SQLITE_STDCALL sqlite3_mutex_try(sqlite3_mutex *p){ int rc = SQLITE_OK; if( p ){ return sqlite3GlobalConfig.mutex.xMutexTry(p); } return rc; @@ -17843,11 +19147,11 @@ ** The sqlite3_mutex_leave() routine exits a mutex that was previously ** entered by the same thread. The behavior is undefined if the mutex ** is not currently entered. If a NULL pointer is passed as an argument ** this function is a no-op. */ -SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex *p){ +SQLITE_API void SQLITE_STDCALL sqlite3_mutex_leave(sqlite3_mutex *p){ if( p ){ sqlite3GlobalConfig.mutex.xMutexLeave(p); } } @@ -17854,14 +19158,14 @@ #ifndef NDEBUG /* ** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are ** intended for use inside assert() statements. */ -SQLITE_API int sqlite3_mutex_held(sqlite3_mutex *p){ +SQLITE_API int SQLITE_STDCALL sqlite3_mutex_held(sqlite3_mutex *p){ return p==0 || sqlite3GlobalConfig.mutex.xMutexHeld(p); } -SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex *p){ +SQLITE_API int SQLITE_STDCALL sqlite3_mutex_notheld(sqlite3_mutex *p){ return p==0 || sqlite3GlobalConfig.mutex.xMutexNotheld(p); } #endif #endif /* !defined(SQLITE_MUTEX_OMIT) */ @@ -17974,11 +19278,11 @@ ** The sqlite3_mutex_alloc() routine allocates a new ** mutex and returns a pointer to it. If it returns NULL ** that means that a mutex could not be allocated. */ static sqlite3_mutex *debugMutexAlloc(int id){ - static sqlite3_debug_mutex aStatic[6]; + static sqlite3_debug_mutex aStatic[SQLITE_MUTEX_STATIC_APP3 - 1]; sqlite3_debug_mutex *pNew = 0; switch( id ){ case SQLITE_MUTEX_FAST: case SQLITE_MUTEX_RECURSIVE: { pNew = sqlite3Malloc(sizeof(*pNew)); @@ -17987,12 +19291,16 @@ pNew->cnt = 0; } break; } default: { - assert( id-2 >= 0 ); - assert( id-2 < (int)(sizeof(aStatic)/sizeof(aStatic[0])) ); +#ifdef SQLITE_ENABLE_API_ARMOR + if( id-2<0 || id-2>=ArraySize(aStatic) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif pNew = &aStatic[id-2]; pNew->id = id; break; } } @@ -18003,12 +19311,17 @@ ** This routine deallocates a previously allocated mutex. */ static void debugMutexFree(sqlite3_mutex *pX){ sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX; assert( p->cnt==0 ); - assert( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ); - sqlite3_free(p); + if( p->id==SQLITE_MUTEX_RECURSIVE || p->id==SQLITE_MUTEX_FAST ){ + sqlite3_free(p); + }else{ +#ifdef SQLITE_ENABLE_API_ARMOR + (void)SQLITE_MISUSE_BKPT; +#endif + } } /* ** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt ** to enter a mutex. If another thread is already within the mutex, @@ -18115,12 +19428,14 @@ /* ** Each recursive mutex is an instance of the following structure. */ struct sqlite3_mutex { pthread_mutex_t mutex; /* Mutex controlling the lock */ -#if SQLITE_MUTEX_NREF +#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR) int id; /* Mutex type */ +#endif +#if SQLITE_MUTEX_NREF volatile int nRef; /* Number of entrances */ volatile pthread_t owner; /* Thread that is within this mutex */ int trace; /* True to trace changes */ #endif }; @@ -18171,14 +19486,17 @@ ** <ul> ** <li> SQLITE_MUTEX_FAST ** <li> SQLITE_MUTEX_RECURSIVE ** <li> SQLITE_MUTEX_STATIC_MASTER ** <li> SQLITE_MUTEX_STATIC_MEM -** <li> SQLITE_MUTEX_STATIC_MEM2 +** <li> SQLITE_MUTEX_STATIC_OPEN ** <li> SQLITE_MUTEX_STATIC_PRNG ** <li> SQLITE_MUTEX_STATIC_LRU ** <li> SQLITE_MUTEX_STATIC_PMEM +** <li> SQLITE_MUTEX_STATIC_APP1 +** <li> SQLITE_MUTEX_STATIC_APP2 +** <li> SQLITE_MUTEX_STATIC_APP3 ** </ul> ** ** The first two constants cause sqlite3_mutex_alloc() to create ** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE ** is used but not necessarily so when SQLITE_MUTEX_FAST is used. @@ -18208,10 +19526,13 @@ SQLITE3_MUTEX_INITIALIZER, SQLITE3_MUTEX_INITIALIZER, SQLITE3_MUTEX_INITIALIZER, SQLITE3_MUTEX_INITIALIZER, SQLITE3_MUTEX_INITIALIZER, + SQLITE3_MUTEX_INITIALIZER, + SQLITE3_MUTEX_INITIALIZER, + SQLITE3_MUTEX_INITIALIZER, SQLITE3_MUTEX_INITIALIZER }; sqlite3_mutex *p; switch( iType ){ case SQLITE_MUTEX_RECURSIVE: { @@ -18227,36 +19548,34 @@ pthread_mutexattr_init(&recursiveAttr); pthread_mutexattr_settype(&recursiveAttr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&p->mutex, &recursiveAttr); pthread_mutexattr_destroy(&recursiveAttr); #endif -#if SQLITE_MUTEX_NREF - p->id = iType; -#endif } break; } case SQLITE_MUTEX_FAST: { p = sqlite3MallocZero( sizeof(*p) ); if( p ){ -#if SQLITE_MUTEX_NREF - p->id = iType; -#endif pthread_mutex_init(&p->mutex, 0); } break; } default: { - assert( iType-2 >= 0 ); - assert( iType-2 < ArraySize(staticMutexes) ); +#ifdef SQLITE_ENABLE_API_ARMOR + if( iType-2<0 || iType-2>=ArraySize(staticMutexes) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif p = &staticMutexes[iType-2]; -#if SQLITE_MUTEX_NREF - p->id = iType; -#endif break; } } +#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR) + if( p ) p->id = iType; +#endif return p; } /* @@ -18264,13 +19583,22 @@ ** allocated mutex. SQLite is careful to deallocate every ** mutex that it allocates. */ static void pthreadMutexFree(sqlite3_mutex *p){ assert( p->nRef==0 ); - assert( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ); - pthread_mutex_destroy(&p->mutex); - sqlite3_free(p); +#if SQLITE_ENABLE_API_ARMOR + if( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ) +#endif + { + pthread_mutex_destroy(&p->mutex); + sqlite3_free(p); + } +#ifdef SQLITE_ENABLE_API_ARMOR + else{ + (void)SQLITE_MISUSE_BKPT; + } +#endif } /* ** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt ** to enter a mutex. If another thread is already within the mutex, @@ -18438,16 +19766,328 @@ ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file contains the C functions that implement mutexes for win32 +** This file contains the C functions that implement mutexes for Win32. */ + +#if SQLITE_OS_WIN +/* +** Include code that is common to all os_*.c files +*/ +/************** Include os_common.h in the middle of mutex_w32.c *************/ +/************** Begin file os_common.h ***************************************/ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains macros and a little bit of code that is common to +** all of the platform-specific files (os_*.c) and is #included into those +** files. +** +** This file should be #included by the os_*.c files only. It is not a +** general purpose header file. +*/ +#ifndef _OS_COMMON_H_ +#define _OS_COMMON_H_ + +/* +** At least two bugs have slipped in because we changed the MEMORY_DEBUG +** macro to SQLITE_DEBUG and some older makefiles have not yet made the +** switch. The following code should catch this problem at compile-time. +*/ +#ifdef MEMORY_DEBUG +# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." +#endif + +#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) +# ifndef SQLITE_DEBUG_OS_TRACE +# define SQLITE_DEBUG_OS_TRACE 0 +# endif + int sqlite3OSTrace = SQLITE_DEBUG_OS_TRACE; +# define OSTRACE(X) if( sqlite3OSTrace ) sqlite3DebugPrintf X +#else +# define OSTRACE(X) +#endif + +/* +** Macros for performance tracing. Normally turned off. Only works +** on i486 hardware. +*/ +#ifdef SQLITE_PERFORMANCE_TRACE + +/* +** hwtime.h contains inline assembler code for implementing +** high-performance timing routines. +*/ +/************** Include hwtime.h in the middle of os_common.h ****************/ +/************** Begin file hwtime.h ******************************************/ +/* +** 2008 May 27 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains inline asm code for retrieving "high-performance" +** counters for x86 class CPUs. +*/ +#ifndef _HWTIME_H_ +#define _HWTIME_H_ + +/* +** The following routine only works on pentium-class (or newer) processors. +** It uses the RDTSC opcode to read the cycle count value out of the +** processor and returns that value. This can be used for high-res +** profiling. +*/ +#if (defined(__GNUC__) || defined(_MSC_VER)) && \ + (defined(i386) || defined(__i386__) || defined(_M_IX86)) + + #if defined(__GNUC__) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned int lo, hi; + __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); + return (sqlite_uint64)hi << 32 | lo; + } + + #elif defined(_MSC_VER) + + __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ + __asm { + rdtsc + ret ; return value at EDX:EAX + } + } + + #endif + +#elif (defined(__GNUC__) && defined(__x86_64__)) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned long val; + __asm__ __volatile__ ("rdtsc" : "=A" (val)); + return val; + } + +#elif (defined(__GNUC__) && defined(__ppc__)) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned long long retval; + unsigned long junk; + __asm__ __volatile__ ("\n\ + 1: mftbu %1\n\ + mftb %L0\n\ + mftbu %0\n\ + cmpw %0,%1\n\ + bne 1b" + : "=r" (retval), "=r" (junk)); + return retval; + } + +#else + + #error Need implementation of sqlite3Hwtime() for your platform. + + /* + ** To compile without implementing sqlite3Hwtime() for your platform, + ** you can remove the above #error and use the following + ** stub function. You will lose timing support for many + ** of the debugging and testing utilities, but it should at + ** least compile and run. + */ +SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } + +#endif + +#endif /* !defined(_HWTIME_H_) */ + +/************** End of hwtime.h **********************************************/ +/************** Continuing where we left off in os_common.h ******************/ + +static sqlite_uint64 g_start; +static sqlite_uint64 g_elapsed; +#define TIMER_START g_start=sqlite3Hwtime() +#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start +#define TIMER_ELAPSED g_elapsed +#else +#define TIMER_START +#define TIMER_END +#define TIMER_ELAPSED ((sqlite_uint64)0) +#endif + +/* +** If we compile with the SQLITE_TEST macro set, then the following block +** of code will give us the ability to simulate a disk I/O error. This +** is used for testing the I/O recovery logic. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_io_error_hit = 0; /* Total number of I/O Errors */ +SQLITE_API int sqlite3_io_error_hardhit = 0; /* Number of non-benign errors */ +SQLITE_API int sqlite3_io_error_pending = 0; /* Count down to first I/O error */ +SQLITE_API int sqlite3_io_error_persist = 0; /* True if I/O errors persist */ +SQLITE_API int sqlite3_io_error_benign = 0; /* True if errors are benign */ +SQLITE_API int sqlite3_diskfull_pending = 0; +SQLITE_API int sqlite3_diskfull = 0; +#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X) +#define SimulateIOError(CODE) \ + if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ + || sqlite3_io_error_pending-- == 1 ) \ + { local_ioerr(); CODE; } +static void local_ioerr(){ + IOTRACE(("IOERR\n")); + sqlite3_io_error_hit++; + if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++; +} +#define SimulateDiskfullError(CODE) \ + if( sqlite3_diskfull_pending ){ \ + if( sqlite3_diskfull_pending == 1 ){ \ + local_ioerr(); \ + sqlite3_diskfull = 1; \ + sqlite3_io_error_hit = 1; \ + CODE; \ + }else{ \ + sqlite3_diskfull_pending--; \ + } \ + } +#else +#define SimulateIOErrorBenign(X) +#define SimulateIOError(A) +#define SimulateDiskfullError(A) +#endif + +/* +** When testing, keep a count of the number of open files. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_open_file_count = 0; +#define OpenCounter(X) sqlite3_open_file_count+=(X) +#else +#define OpenCounter(X) +#endif + +#endif /* !defined(_OS_COMMON_H_) */ + +/************** End of os_common.h *******************************************/ +/************** Continuing where we left off in mutex_w32.c ******************/ + +/* +** Include the header file for the Windows VFS. +*/ +/************** Include os_win.h in the middle of mutex_w32.c ****************/ +/************** Begin file os_win.h ******************************************/ +/* +** 2013 November 25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific to Windows. +*/ +#ifndef _OS_WIN_H_ +#define _OS_WIN_H_ + +/* +** Include the primary Windows SDK header file. +*/ +#include "windows.h" + +#ifdef __CYGWIN__ +# include <sys/cygwin.h> +# include <errno.h> /* amalgamator: dontcache */ +#endif + +/* +** Determine if we are dealing with Windows NT. +** +** We ought to be able to determine if we are compiling for Windows 9x or +** Windows NT using the _WIN32_WINNT macro as follows: +** +** #if defined(_WIN32_WINNT) +** # define SQLITE_OS_WINNT 1 +** #else +** # define SQLITE_OS_WINNT 0 +** #endif +** +** However, Visual Studio 2005 does not set _WIN32_WINNT by default, as +** it ought to, so the above test does not work. We'll just assume that +** everything is Windows NT unless the programmer explicitly says otherwise +** by setting SQLITE_OS_WINNT to 0. +*/ +#if SQLITE_OS_WIN && !defined(SQLITE_OS_WINNT) +# define SQLITE_OS_WINNT 1 +#endif + +/* +** Determine if we are dealing with Windows CE - which has a much reduced +** API. +*/ +#if defined(_WIN32_WCE) +# define SQLITE_OS_WINCE 1 +#else +# define SQLITE_OS_WINCE 0 +#endif + +/* +** Determine if we are dealing with WinRT, which provides only a subset of +** the full Win32 API. +*/ +#if !defined(SQLITE_OS_WINRT) +# define SQLITE_OS_WINRT 0 +#endif + +/* +** For WinCE, some API function parameters do not appear to be declared as +** volatile. +*/ +#if SQLITE_OS_WINCE +# define SQLITE_WIN32_VOLATILE +#else +# define SQLITE_WIN32_VOLATILE volatile +#endif + +/* +** For some Windows sub-platforms, the _beginthreadex() / _endthreadex() +** functions are not available (e.g. those not using MSVC, Cygwin, etc). +*/ +#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ + SQLITE_THREADSAFE>0 && !defined(__CYGWIN__) +# define SQLITE_OS_WIN_THREADS 1 +#else +# define SQLITE_OS_WIN_THREADS 0 +#endif + +#endif /* _OS_WIN_H_ */ + +/************** End of os_win.h **********************************************/ +/************** Continuing where we left off in mutex_w32.c ******************/ +#endif /* ** The code in this file is only used if we are compiling multithreaded -** on a win32 system. +** on a Win32 system. */ #ifdef SQLITE_MUTEX_W32 /* ** Each recursive mutex is an instance of the following structure. @@ -18456,94 +20096,75 @@ CRITICAL_SECTION mutex; /* Mutex controlling the lock */ int id; /* Mutex type */ #ifdef SQLITE_DEBUG volatile int nRef; /* Number of enterances */ volatile DWORD owner; /* Thread holding this mutex */ - int trace; /* True to trace changes */ + volatile int trace; /* True to trace changes */ #endif }; + +/* +** These are the initializer values used when declaring a "static" mutex +** on Win32. It should be noted that all mutexes require initialization +** on the Win32 platform. +*/ #define SQLITE_W32_MUTEX_INITIALIZER { 0 } + #ifdef SQLITE_DEBUG -#define SQLITE3_MUTEX_INITIALIZER { SQLITE_W32_MUTEX_INITIALIZER, 0, 0L, (DWORD)0, 0 } +#define SQLITE3_MUTEX_INITIALIZER { SQLITE_W32_MUTEX_INITIALIZER, 0, \ + 0L, (DWORD)0, 0 } #else #define SQLITE3_MUTEX_INITIALIZER { SQLITE_W32_MUTEX_INITIALIZER, 0 } #endif -/* -** Return true (non-zero) if we are running under WinNT, Win2K, WinXP, -** or WinCE. Return false (zero) for Win95, Win98, or WinME. -** -** Here is an interesting observation: Win95, Win98, and WinME lack -** the LockFileEx() API. But we can still statically link against that -** API as long as we don't call it win running Win95/98/ME. A call to -** this routine is used to determine if the host is Win95/98/ME or -** WinNT/2K/XP so that we will know whether or not we can safely call -** the LockFileEx() API. -** -** mutexIsNT() is only used for the TryEnterCriticalSection() API call, -** which is only available if your application was compiled with -** _WIN32_WINNT defined to a value >= 0x0400. Currently, the only -** call to TryEnterCriticalSection() is #ifdef'ed out, so #ifdef -** this out as well. -*/ -#if 0 -#if SQLITE_OS_WINCE || SQLITE_OS_WINRT -# define mutexIsNT() (1) -#else - static int mutexIsNT(void){ - static int osType = 0; - if( osType==0 ){ - OSVERSIONINFO sInfo; - sInfo.dwOSVersionInfoSize = sizeof(sInfo); - GetVersionEx(&sInfo); - osType = sInfo.dwPlatformId==VER_PLATFORM_WIN32_NT ? 2 : 1; - } - return osType==2; - } -#endif /* SQLITE_OS_WINCE || SQLITE_OS_WINRT */ -#endif - #ifdef SQLITE_DEBUG /* ** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are ** intended for use only inside assert() statements. */ static int winMutexHeld(sqlite3_mutex *p){ return p->nRef!=0 && p->owner==GetCurrentThreadId(); } + static int winMutexNotheld2(sqlite3_mutex *p, DWORD tid){ return p->nRef==0 || p->owner!=tid; } + static int winMutexNotheld(sqlite3_mutex *p){ - DWORD tid = GetCurrentThreadId(); + DWORD tid = GetCurrentThreadId(); return winMutexNotheld2(p, tid); } #endif - /* ** Initialize and deinitialize the mutex subsystem. */ -static sqlite3_mutex winMutex_staticMutexes[6] = { +static sqlite3_mutex winMutex_staticMutexes[] = { + SQLITE3_MUTEX_INITIALIZER, + SQLITE3_MUTEX_INITIALIZER, + SQLITE3_MUTEX_INITIALIZER, SQLITE3_MUTEX_INITIALIZER, SQLITE3_MUTEX_INITIALIZER, SQLITE3_MUTEX_INITIALIZER, SQLITE3_MUTEX_INITIALIZER, SQLITE3_MUTEX_INITIALIZER, SQLITE3_MUTEX_INITIALIZER }; + static int winMutex_isInit = 0; -/* As winMutexInit() and winMutexEnd() are called as part -** of the sqlite3_initialize and sqlite3_shutdown() -** processing, the "interlocked" magic is probably not -** strictly necessary. +static int winMutex_isNt = -1; /* <0 means "need to query" */ + +/* As the winMutexInit() and winMutexEnd() functions are called as part +** of the sqlite3_initialize() and sqlite3_shutdown() processing, the +** "interlocked" magic used here is probably not strictly necessary. */ -static LONG winMutex_lock = 0; +static LONG SQLITE_WIN32_VOLATILE winMutex_lock = 0; -SQLITE_API void sqlite3_win32_sleep(DWORD milliseconds); /* os_win.c */ +SQLITE_API int SQLITE_STDCALL sqlite3_win32_is_nt(void); /* os_win.c */ +SQLITE_API void SQLITE_STDCALL sqlite3_win32_sleep(DWORD milliseconds); /* os_win.c */ -static int winMutexInit(void){ +static int winMutexInit(void){ /* The first to increment to 1 does actual initialization */ if( InterlockedCompareExchange(&winMutex_lock, 1, 0)==0 ){ int i; for(i=0; i<ArraySize(winMutex_staticMutexes); i++){ #if SQLITE_OS_WINRT @@ -18552,20 +20173,21 @@ InitializeCriticalSection(&winMutex_staticMutexes[i].mutex); #endif } winMutex_isInit = 1; }else{ - /* Someone else is in the process of initing the static mutexes */ + /* Another thread is (in the process of) initializing the static + ** mutexes */ while( !winMutex_isInit ){ sqlite3_win32_sleep(1); } } - return SQLITE_OK; + return SQLITE_OK; } -static int winMutexEnd(void){ - /* The first to decrement to 0 does actual shutdown +static int winMutexEnd(void){ + /* The first to decrement to 0 does actual shutdown ** (which should be the last to shutdown.) */ if( InterlockedCompareExchange(&winMutex_lock, 0, 1)==1 ){ if( winMutex_isInit==1 ){ int i; for(i=0; i<ArraySize(winMutex_staticMutexes); i++){ @@ -18572,11 +20194,11 @@ DeleteCriticalSection(&winMutex_staticMutexes[i].mutex); } winMutex_isInit = 0; } } - return SQLITE_OK; + return SQLITE_OK; } /* ** The sqlite3_mutex_alloc() routine allocates a new ** mutex and returns a pointer to it. If it returns NULL @@ -18587,14 +20209,17 @@ ** <ul> ** <li> SQLITE_MUTEX_FAST ** <li> SQLITE_MUTEX_RECURSIVE ** <li> SQLITE_MUTEX_STATIC_MASTER ** <li> SQLITE_MUTEX_STATIC_MEM -** <li> SQLITE_MUTEX_STATIC_MEM2 +** <li> SQLITE_MUTEX_STATIC_OPEN ** <li> SQLITE_MUTEX_STATIC_PRNG ** <li> SQLITE_MUTEX_STATIC_LRU ** <li> SQLITE_MUTEX_STATIC_PMEM +** <li> SQLITE_MUTEX_STATIC_APP1 +** <li> SQLITE_MUTEX_STATIC_APP2 +** <li> SQLITE_MUTEX_STATIC_APP3 ** </ul> ** ** The first two constants cause sqlite3_mutex_alloc() to create ** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE ** is used but not necessarily so when SQLITE_MUTEX_FAST is used. @@ -18613,11 +20238,11 @@ ** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or ** SQLITE_MUTEX_RECURSIVE. ** ** Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST ** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() -** returns a different mutex on every call. But for the static +** returns a different mutex on every call. But for the static ** mutex types, the same mutex is returned on every call that has ** the same type number. */ static sqlite3_mutex *winMutexAlloc(int iType){ sqlite3_mutex *p; @@ -18624,13 +20249,16 @@ switch( iType ){ case SQLITE_MUTEX_FAST: case SQLITE_MUTEX_RECURSIVE: { p = sqlite3MallocZero( sizeof(*p) ); - if( p ){ -#ifdef SQLITE_DEBUG + if( p ){ p->id = iType; +#ifdef SQLITE_DEBUG +#ifdef SQLITE_WIN32_MUTEX_TRACE_DYNAMIC + p->trace = 1; +#endif #endif #if SQLITE_OS_WINRT InitializeCriticalSectionEx(&p->mutex, 0, 0); #else InitializeCriticalSection(&p->mutex); @@ -18637,16 +20265,22 @@ #endif } break; } default: { - assert( winMutex_isInit==1 ); - assert( iType-2 >= 0 ); - assert( iType-2 < ArraySize(winMutex_staticMutexes) ); +#ifdef SQLITE_ENABLE_API_ARMOR + if( iType-2<0 || iType-2>=ArraySize(winMutex_staticMutexes) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif p = &winMutex_staticMutexes[iType-2]; -#ifdef SQLITE_DEBUG p->id = iType; +#ifdef SQLITE_DEBUG +#ifdef SQLITE_WIN32_MUTEX_TRACE_STATIC + p->trace = 1; +#endif #endif break; } } return p; @@ -18659,13 +20293,18 @@ ** mutex that it allocates. */ static void winMutexFree(sqlite3_mutex *p){ assert( p ); assert( p->nRef==0 && p->owner==0 ); - assert( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ); - DeleteCriticalSection(&p->mutex); - sqlite3_free(p); + if( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ){ + DeleteCriticalSection(&p->mutex); + sqlite3_free(p); + }else{ +#ifdef SQLITE_ENABLE_API_ARMOR + (void)SQLITE_MISUSE_BKPT; +#endif + } } /* ** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt ** to enter a mutex. If another thread is already within the mutex, @@ -18676,53 +20315,71 @@ ** mutex must be exited an equal number of times before another thread ** can enter. If the same thread tries to enter any other kind of mutex ** more than once, the behavior is undefined. */ static void winMutexEnter(sqlite3_mutex *p){ +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + DWORD tid = GetCurrentThreadId(); +#endif #ifdef SQLITE_DEBUG - DWORD tid = GetCurrentThreadId(); + assert( p ); assert( p->id==SQLITE_MUTEX_RECURSIVE || winMutexNotheld2(p, tid) ); +#else + assert( p ); #endif + assert( winMutex_isInit==1 ); EnterCriticalSection(&p->mutex); #ifdef SQLITE_DEBUG assert( p->nRef>0 || p->owner==0 ); - p->owner = tid; + p->owner = tid; p->nRef++; if( p->trace ){ - printf("enter mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef); + OSTRACE(("ENTER-MUTEX tid=%lu, mutex=%p (%d), nRef=%d\n", + tid, p, p->trace, p->nRef)); } #endif } + static int winMutexTry(sqlite3_mutex *p){ -#ifndef NDEBUG - DWORD tid = GetCurrentThreadId(); +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + DWORD tid = GetCurrentThreadId(); #endif int rc = SQLITE_BUSY; + assert( p ); assert( p->id==SQLITE_MUTEX_RECURSIVE || winMutexNotheld2(p, tid) ); /* ** The sqlite3_mutex_try() routine is very rarely used, and when it ** is used it is merely an optimization. So it is OK for it to always - ** fail. + ** fail. ** ** The TryEnterCriticalSection() interface is only available on WinNT. ** And some windows compilers complain if you try to use it without ** first doing some #defines that prevent SQLite from building on Win98. ** For that reason, we will omit this optimization for now. See ** ticket #2685. */ -#if 0 - if( mutexIsNT() && TryEnterCriticalSection(&p->mutex) ){ +#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0400 + assert( winMutex_isInit==1 ); + assert( winMutex_isNt>=-1 && winMutex_isNt<=1 ); + if( winMutex_isNt<0 ){ + winMutex_isNt = sqlite3_win32_is_nt(); + } + assert( winMutex_isNt==0 || winMutex_isNt==1 ); + if( winMutex_isNt && TryEnterCriticalSection(&p->mutex) ){ +#ifdef SQLITE_DEBUG p->owner = tid; p->nRef++; +#endif rc = SQLITE_OK; } #else UNUSED_PARAMETER(p); #endif #ifdef SQLITE_DEBUG - if( rc==SQLITE_OK && p->trace ){ - printf("try mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef); + if( p->trace ){ + OSTRACE(("TRY-MUTEX tid=%lu, mutex=%p (%d), owner=%lu, nRef=%d, rc=%s\n", + tid, p, p->trace, p->owner, p->nRef, sqlite3ErrName(rc))); } #endif return rc; } @@ -18731,22 +20388,27 @@ ** previously entered by the same thread. The behavior ** is undefined if the mutex is not currently entered or ** is not currently allocated. SQLite will never do either. */ static void winMutexLeave(sqlite3_mutex *p){ -#ifndef NDEBUG +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) DWORD tid = GetCurrentThreadId(); +#endif + assert( p ); +#ifdef SQLITE_DEBUG assert( p->nRef>0 ); assert( p->owner==tid ); p->nRef--; if( p->nRef==0 ) p->owner = 0; assert( p->nRef==0 || p->id==SQLITE_MUTEX_RECURSIVE ); #endif + assert( winMutex_isInit==1 ); LeaveCriticalSection(&p->mutex); #ifdef SQLITE_DEBUG if( p->trace ){ - printf("leave mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef); + OSTRACE(("LEAVE-MUTEX tid=%lu, mutex=%p (%d), nRef=%d\n", + tid, p, p->trace, p->nRef)); } #endif } SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ @@ -18764,13 +20426,13 @@ #else 0, 0 #endif }; - return &sMutex; } + #endif /* SQLITE_MUTEX_W32 */ /************** End of mutex_w32.c *******************************************/ /************** Begin file malloc.c ******************************************/ /* @@ -18792,11 +20454,11 @@ /* ** Attempt to release up to n bytes of non-essential memory currently ** held by SQLite. An example of non-essential memory is memory used to ** cache database pages that are not currently in use. */ -SQLITE_API int sqlite3_release_memory(int n){ +SQLITE_API int SQLITE_STDCALL sqlite3_release_memory(int n){ #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT return sqlite3PcacheReleaseMemory(n); #else /* IMPLEMENTATION-OF: R-34391-24921 The sqlite3_release_memory() routine ** is a no-op returning zero if SQLite is not compiled with @@ -18846,10 +20508,17 @@ */ int nearlyFull; } mem0 = { 0, 0, 0, 0, 0, 0, 0, 0 }; #define mem0 GLOBAL(struct Mem0Global, mem0) + +/* +** Return the memory allocator mutex. sqlite3_status() needs it. +*/ +SQLITE_PRIVATE sqlite3_mutex *sqlite3MallocMutex(void){ + return mem0.mutex; +} /* ** This routine runs when the memory allocator sees that the ** total memory allocation is about to exceed the soft heap ** limit. @@ -18869,11 +20538,11 @@ static int sqlite3MemoryAlarm( void(*xCallback)(void *pArg, sqlite3_int64 used,int N), void *pArg, sqlite3_int64 iThreshold ){ - int nUsed; + sqlite3_int64 nUsed; sqlite3_mutex_enter(mem0.mutex); mem0.alarmCallback = xCallback; mem0.alarmArg = pArg; mem0.alarmThreshold = iThreshold; nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); @@ -18885,11 +20554,11 @@ #ifndef SQLITE_OMIT_DEPRECATED /* ** Deprecated external interface. Internal/core SQLite code ** should call sqlite3MemoryAlarm. */ -SQLITE_API int sqlite3_memory_alarm( +SQLITE_API int SQLITE_STDCALL sqlite3_memory_alarm( void(*xCallback)(void *pArg, sqlite3_int64 used,int N), void *pArg, sqlite3_int64 iThreshold ){ return sqlite3MemoryAlarm(xCallback, pArg, iThreshold); @@ -18898,11 +20567,11 @@ /* ** Set the soft heap-size limit for the library. Passing a zero or ** negative value indicates no limit. */ -SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 n){ +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_soft_heap_limit64(sqlite3_int64 n){ sqlite3_int64 priorLimit; sqlite3_int64 excess; #ifndef SQLITE_OMIT_AUTOINIT int rc = sqlite3_initialize(); if( rc ) return -1; @@ -18918,19 +20587,20 @@ } excess = sqlite3_memory_used() - n; if( excess>0 ) sqlite3_release_memory((int)(excess & 0x7fffffff)); return priorLimit; } -SQLITE_API void sqlite3_soft_heap_limit(int n){ +SQLITE_API void SQLITE_STDCALL sqlite3_soft_heap_limit(int n){ if( n<0 ) n = 0; sqlite3_soft_heap_limit64(n); } /* ** Initialize the memory allocation subsystem. */ SQLITE_PRIVATE int sqlite3MallocInit(void){ + int rc; if( sqlite3GlobalConfig.m.xMalloc==0 ){ sqlite3MemSetDefault(); } memset(&mem0, 0, sizeof(mem0)); if( sqlite3GlobalConfig.bCoreMutex ){ @@ -18962,11 +20632,13 @@ || sqlite3GlobalConfig.nPage<1 ){ sqlite3GlobalConfig.pPage = 0; sqlite3GlobalConfig.szPage = 0; sqlite3GlobalConfig.nPage = 0; } - return sqlite3GlobalConfig.m.xInit(sqlite3GlobalConfig.m.pAppData); + rc = sqlite3GlobalConfig.m.xInit(sqlite3GlobalConfig.m.pAppData); + if( rc!=SQLITE_OK ) memset(&mem0, 0, sizeof(mem0)); + return rc; } /* ** Return true if the heap is currently under memory pressure - in other ** words if the amount of heap used is close to the limit set by @@ -18987,11 +20659,11 @@ } /* ** Return the amount of memory currently checked out. */ -SQLITE_API sqlite3_int64 sqlite3_memory_used(void){ +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_memory_used(void){ int n, mx; sqlite3_int64 res; sqlite3_status(SQLITE_STATUS_MEMORY_USED, &n, &mx, 0); res = (sqlite3_int64)n; /* Work around bug in Borland C. Ticket #3216 */ return res; @@ -19000,11 +20672,11 @@ /* ** Return the maximum amount of memory that has ever been ** checked out since either the beginning of this process ** or since the most recent reset. */ -SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag){ +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_memory_highwater(int resetFlag){ int n, mx; sqlite3_int64 res; sqlite3_status(SQLITE_STATUS_MEMORY_USED, &n, &mx, resetFlag); res = (sqlite3_int64)mx; /* Work around bug in Borland C. Ticket #3216 */ return res; @@ -19038,11 +20710,11 @@ void *p; assert( sqlite3_mutex_held(mem0.mutex) ); nFull = sqlite3GlobalConfig.m.xRoundup(n); sqlite3StatusSet(SQLITE_STATUS_MALLOC_SIZE, n); if( mem0.alarmCallback!=0 ){ - int nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); + sqlite3_int64 nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); if( nUsed >= mem0.alarmThreshold - nFull ){ mem0.nearlyFull = 1; sqlite3MallocAlarm(nFull); }else{ mem0.nearlyFull = 0; @@ -19055,49 +20727,53 @@ p = sqlite3GlobalConfig.m.xMalloc(nFull); } #endif if( p ){ nFull = sqlite3MallocSize(p); - sqlite3StatusAdd(SQLITE_STATUS_MEMORY_USED, nFull); - sqlite3StatusAdd(SQLITE_STATUS_MALLOC_COUNT, 1); + sqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nFull); + sqlite3StatusUp(SQLITE_STATUS_MALLOC_COUNT, 1); } *pp = p; return nFull; } /* ** Allocate memory. This routine is like sqlite3_malloc() except that it ** assumes the memory subsystem has already been initialized. */ -SQLITE_PRIVATE void *sqlite3Malloc(int n){ +SQLITE_PRIVATE void *sqlite3Malloc(u64 n){ void *p; - if( n<=0 /* IMP: R-65312-04917 */ - || n>=0x7fffff00 - ){ + if( n==0 || n>=0x7fffff00 ){ /* A memory allocation of a number of bytes which is near the maximum ** signed integer value might cause an integer overflow inside of the ** xMalloc(). Hence we limit the maximum size to 0x7fffff00, giving ** 255 bytes of overhead. SQLite itself will never use anything near ** this amount. The only way to reach the limit is with sqlite3_malloc() */ p = 0; }else if( sqlite3GlobalConfig.bMemstat ){ sqlite3_mutex_enter(mem0.mutex); - mallocWithAlarm(n, &p); + mallocWithAlarm((int)n, &p); sqlite3_mutex_leave(mem0.mutex); }else{ - p = sqlite3GlobalConfig.m.xMalloc(n); + p = sqlite3GlobalConfig.m.xMalloc((int)n); } - assert( EIGHT_BYTE_ALIGNMENT(p) ); /* IMP: R-04675-44850 */ + assert( EIGHT_BYTE_ALIGNMENT(p) ); /* IMP: R-11148-40995 */ return p; } /* ** This version of the memory allocation is for use by the application. ** First make sure the memory subsystem is initialized, then do the ** allocation. */ -SQLITE_API void *sqlite3_malloc(int n){ +SQLITE_API void *SQLITE_STDCALL sqlite3_malloc(int n){ +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + return n<=0 ? 0 : sqlite3Malloc(n); +} +SQLITE_API void *SQLITE_STDCALL sqlite3_malloc64(sqlite3_uint64 n){ #ifndef SQLITE_OMIT_AUTOINIT if( sqlite3_initialize() ) return 0; #endif return sqlite3Malloc(n); } @@ -19124,38 +20800,37 @@ SQLITE_PRIVATE void *sqlite3ScratchMalloc(int n){ void *p; assert( n>0 ); sqlite3_mutex_enter(mem0.mutex); + sqlite3StatusSet(SQLITE_STATUS_SCRATCH_SIZE, n); if( mem0.nScratchFree && sqlite3GlobalConfig.szScratch>=n ){ p = mem0.pScratchFree; mem0.pScratchFree = mem0.pScratchFree->pNext; mem0.nScratchFree--; - sqlite3StatusAdd(SQLITE_STATUS_SCRATCH_USED, 1); - sqlite3StatusSet(SQLITE_STATUS_SCRATCH_SIZE, n); - sqlite3_mutex_leave(mem0.mutex); - }else{ - if( sqlite3GlobalConfig.bMemstat ){ - sqlite3StatusSet(SQLITE_STATUS_SCRATCH_SIZE, n); - n = mallocWithAlarm(n, &p); - if( p ) sqlite3StatusAdd(SQLITE_STATUS_SCRATCH_OVERFLOW, n); - sqlite3_mutex_leave(mem0.mutex); - }else{ - sqlite3_mutex_leave(mem0.mutex); - p = sqlite3GlobalConfig.m.xMalloc(n); + sqlite3StatusUp(SQLITE_STATUS_SCRATCH_USED, 1); + sqlite3_mutex_leave(mem0.mutex); + }else{ + sqlite3_mutex_leave(mem0.mutex); + p = sqlite3Malloc(n); + if( sqlite3GlobalConfig.bMemstat && p ){ + sqlite3_mutex_enter(mem0.mutex); + sqlite3StatusUp(SQLITE_STATUS_SCRATCH_OVERFLOW, sqlite3MallocSize(p)); + sqlite3_mutex_leave(mem0.mutex); } sqlite3MemdebugSetType(p, MEMTYPE_SCRATCH); } assert( sqlite3_mutex_notheld(mem0.mutex) ); #if SQLITE_THREADSAFE==0 && !defined(NDEBUG) - /* Verify that no more than two scratch allocations per thread - ** are outstanding at one time. (This is only checked in the - ** single-threaded case since checking in the multi-threaded case - ** would be much more complicated.) */ - assert( scratchAllocOut<=1 ); + /* EVIDENCE-OF: R-12970-05880 SQLite will not use more than one scratch + ** buffers per thread. + ** + ** This can only be checked in single-threaded mode. + */ + assert( scratchAllocOut==0 ); if( p ) scratchAllocOut++; #endif return p; } @@ -19178,23 +20853,23 @@ sqlite3_mutex_enter(mem0.mutex); pSlot->pNext = mem0.pScratchFree; mem0.pScratchFree = pSlot; mem0.nScratchFree++; assert( mem0.nScratchFree <= (u32)sqlite3GlobalConfig.nScratch ); - sqlite3StatusAdd(SQLITE_STATUS_SCRATCH_USED, -1); + sqlite3StatusDown(SQLITE_STATUS_SCRATCH_USED, 1); sqlite3_mutex_leave(mem0.mutex); }else{ /* Release memory back to the heap */ assert( sqlite3MemdebugHasType(p, MEMTYPE_SCRATCH) ); - assert( sqlite3MemdebugNoType(p, ~MEMTYPE_SCRATCH) ); + assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_SCRATCH) ); sqlite3MemdebugSetType(p, MEMTYPE_HEAP); if( sqlite3GlobalConfig.bMemstat ){ int iSize = sqlite3MallocSize(p); sqlite3_mutex_enter(mem0.mutex); - sqlite3StatusAdd(SQLITE_STATUS_SCRATCH_OVERFLOW, -iSize); - sqlite3StatusAdd(SQLITE_STATUS_MEMORY_USED, -iSize); - sqlite3StatusAdd(SQLITE_STATUS_MALLOC_COUNT, -1); + sqlite3StatusDown(SQLITE_STATUS_SCRATCH_OVERFLOW, iSize); + sqlite3StatusDown(SQLITE_STATUS_MEMORY_USED, iSize); + sqlite3StatusDown(SQLITE_STATUS_MALLOC_COUNT, 1); sqlite3GlobalConfig.m.xFree(p); sqlite3_mutex_leave(mem0.mutex); }else{ sqlite3GlobalConfig.m.xFree(p); } @@ -19205,11 +20880,11 @@ /* ** TRUE if p is a lookaside memory allocation from db */ #ifndef SQLITE_OMIT_LOOKASIDE static int isLookaside(sqlite3 *db, void *p){ - return p && p>=db->lookaside.pStart && p<db->lookaside.pEnd; + return p>=db->lookaside.pStart && p<db->lookaside.pEnd; } #else #define isLookaside(A,B) 0 #endif @@ -19217,42 +20892,59 @@ ** Return the size of a memory allocation previously obtained from ** sqlite3Malloc() or sqlite3_malloc(). */ SQLITE_PRIVATE int sqlite3MallocSize(void *p){ assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); - assert( sqlite3MemdebugNoType(p, MEMTYPE_DB) ); return sqlite3GlobalConfig.m.xSize(p); } SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, void *p){ - assert( db==0 || sqlite3_mutex_held(db->mutex) ); - if( db && isLookaside(db, p) ){ - return db->lookaside.sz; + if( db==0 ){ + assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); + assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); + return sqlite3MallocSize(p); }else{ - assert( sqlite3MemdebugHasType(p, MEMTYPE_DB) ); - assert( sqlite3MemdebugHasType(p, MEMTYPE_LOOKASIDE|MEMTYPE_HEAP) ); - assert( db!=0 || sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) ); - return sqlite3GlobalConfig.m.xSize(p); + assert( sqlite3_mutex_held(db->mutex) ); + if( isLookaside(db, p) ){ + return db->lookaside.sz; + }else{ + assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + return sqlite3GlobalConfig.m.xSize(p); + } } +} +SQLITE_API sqlite3_uint64 SQLITE_STDCALL sqlite3_msize(void *p){ + assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); + assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); + return (sqlite3_uint64)sqlite3GlobalConfig.m.xSize(p); } /* ** Free memory previously obtained from sqlite3Malloc(). */ -SQLITE_API void sqlite3_free(void *p){ +SQLITE_API void SQLITE_STDCALL sqlite3_free(void *p){ if( p==0 ) return; /* IMP: R-49053-54554 */ - assert( sqlite3MemdebugNoType(p, MEMTYPE_DB) ); assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); + assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); if( sqlite3GlobalConfig.bMemstat ){ sqlite3_mutex_enter(mem0.mutex); - sqlite3StatusAdd(SQLITE_STATUS_MEMORY_USED, -sqlite3MallocSize(p)); - sqlite3StatusAdd(SQLITE_STATUS_MALLOC_COUNT, -1); + sqlite3StatusDown(SQLITE_STATUS_MEMORY_USED, sqlite3MallocSize(p)); + sqlite3StatusDown(SQLITE_STATUS_MALLOC_COUNT, 1); sqlite3GlobalConfig.m.xFree(p); sqlite3_mutex_leave(mem0.mutex); }else{ sqlite3GlobalConfig.m.xFree(p); } } + +/* +** Add the size of memory allocation "p" to the count in +** *db->pnBytesFreed. +*/ +static SQLITE_NOINLINE void measureAllocationSize(sqlite3 *db, void *p){ + *db->pnBytesFreed += sqlite3DbMallocSize(db,p); +} /* ** Free memory that might be associated with a particular database ** connection. */ @@ -19259,11 +20951,11 @@ SQLITE_PRIVATE void sqlite3DbFree(sqlite3 *db, void *p){ assert( db==0 || sqlite3_mutex_held(db->mutex) ); if( p==0 ) return; if( db ){ if( db->pnBytesFreed ){ - *db->pnBytesFreed += sqlite3DbMallocSize(db, p); + measureAllocationSize(db, p); return; } if( isLookaside(db, p) ){ LookasideSlot *pBuf = (LookasideSlot*)p; #if SQLITE_DEBUG @@ -19274,28 +20966,30 @@ db->lookaside.pFree = pBuf; db->lookaside.nOut--; return; } } - assert( sqlite3MemdebugHasType(p, MEMTYPE_DB) ); - assert( sqlite3MemdebugHasType(p, MEMTYPE_LOOKASIDE|MEMTYPE_HEAP) ); + assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); assert( db!=0 || sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) ); sqlite3MemdebugSetType(p, MEMTYPE_HEAP); sqlite3_free(p); } /* ** Change the size of an existing memory allocation */ -SQLITE_PRIVATE void *sqlite3Realloc(void *pOld, int nBytes){ +SQLITE_PRIVATE void *sqlite3Realloc(void *pOld, u64 nBytes){ int nOld, nNew, nDiff; void *pNew; + assert( sqlite3MemdebugHasType(pOld, MEMTYPE_HEAP) ); + assert( sqlite3MemdebugNoType(pOld, (u8)~MEMTYPE_HEAP) ); if( pOld==0 ){ - return sqlite3Malloc(nBytes); /* IMP: R-28354-25769 */ + return sqlite3Malloc(nBytes); /* IMP: R-04300-56712 */ } - if( nBytes<=0 ){ - sqlite3_free(pOld); /* IMP: R-31593-10574 */ + if( nBytes==0 ){ + sqlite3_free(pOld); /* IMP: R-26507-47431 */ return 0; } if( nBytes>=0x7fffff00 ){ /* The 0x7ffff00 limit term is explained in comments on sqlite3Malloc() */ return 0; @@ -19302,45 +20996,50 @@ } nOld = sqlite3MallocSize(pOld); /* IMPLEMENTATION-OF: R-46199-30249 SQLite guarantees that the second ** argument to xRealloc is always a value returned by a prior call to ** xRoundup. */ - nNew = sqlite3GlobalConfig.m.xRoundup(nBytes); + nNew = sqlite3GlobalConfig.m.xRoundup((int)nBytes); if( nOld==nNew ){ pNew = pOld; }else if( sqlite3GlobalConfig.bMemstat ){ sqlite3_mutex_enter(mem0.mutex); - sqlite3StatusSet(SQLITE_STATUS_MALLOC_SIZE, nBytes); + sqlite3StatusSet(SQLITE_STATUS_MALLOC_SIZE, (int)nBytes); nDiff = nNew - nOld; if( sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >= mem0.alarmThreshold-nDiff ){ sqlite3MallocAlarm(nDiff); } - assert( sqlite3MemdebugHasType(pOld, MEMTYPE_HEAP) ); - assert( sqlite3MemdebugNoType(pOld, ~MEMTYPE_HEAP) ); pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); if( pNew==0 && mem0.alarmCallback ){ - sqlite3MallocAlarm(nBytes); + sqlite3MallocAlarm((int)nBytes); pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); } if( pNew ){ nNew = sqlite3MallocSize(pNew); - sqlite3StatusAdd(SQLITE_STATUS_MEMORY_USED, nNew-nOld); + sqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nNew-nOld); } sqlite3_mutex_leave(mem0.mutex); }else{ pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); } - assert( EIGHT_BYTE_ALIGNMENT(pNew) ); /* IMP: R-04675-44850 */ + assert( EIGHT_BYTE_ALIGNMENT(pNew) ); /* IMP: R-11148-40995 */ return pNew; } /* ** The public interface to sqlite3Realloc. Make sure that the memory ** subsystem is initialized prior to invoking sqliteRealloc. */ -SQLITE_API void *sqlite3_realloc(void *pOld, int n){ +SQLITE_API void *SQLITE_STDCALL sqlite3_realloc(void *pOld, int n){ +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return 0; +#endif + if( n<0 ) n = 0; /* IMP: R-26507-47431 */ + return sqlite3Realloc(pOld, n); +} +SQLITE_API void *SQLITE_STDCALL sqlite3_realloc64(void *pOld, sqlite3_uint64 n){ #ifndef SQLITE_OMIT_AUTOINIT if( sqlite3_initialize() ) return 0; #endif return sqlite3Realloc(pOld, n); } @@ -19347,26 +21046,26 @@ /* ** Allocate and zero memory. */ -SQLITE_PRIVATE void *sqlite3MallocZero(int n){ +SQLITE_PRIVATE void *sqlite3MallocZero(u64 n){ void *p = sqlite3Malloc(n); if( p ){ - memset(p, 0, n); + memset(p, 0, (size_t)n); } return p; } /* ** Allocate and zero memory. If the allocation fails, make ** the mallocFailed flag in the connection pointer. */ -SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3 *db, int n){ +SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3 *db, u64 n){ void *p = sqlite3DbMallocRaw(db, n); if( p ){ - memset(p, 0, n); + memset(p, 0, (size_t)n); } return p; } /* @@ -19385,11 +21084,11 @@ ** if( b ) a[10] = 9; ** ** In other words, if a subsequent malloc (ex: "b") worked, it is assumed ** that all prior mallocs (ex: "a") worked too. */ -SQLITE_PRIVATE void *sqlite3DbMallocRaw(sqlite3 *db, int n){ +SQLITE_PRIVATE void *sqlite3DbMallocRaw(sqlite3 *db, u64 n){ void *p; assert( db==0 || sqlite3_mutex_held(db->mutex) ); assert( db==0 || db->pnBytesFreed==0 ); #ifndef SQLITE_OMIT_LOOKASIDE if( db ){ @@ -19420,20 +21119,20 @@ #endif p = sqlite3Malloc(n); if( !p && db ){ db->mallocFailed = 1; } - sqlite3MemdebugSetType(p, MEMTYPE_DB | - ((db && db->lookaside.bEnabled) ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP)); + sqlite3MemdebugSetType(p, + (db && db->lookaside.bEnabled) ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP); return p; } /* ** Resize the block of memory pointed to by p to n bytes. If the ** resize fails, set the mallocFailed flag in the connection object. */ -SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *db, void *p, int n){ +SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *db, void *p, u64 n){ void *pNew = 0; assert( db!=0 ); assert( sqlite3_mutex_held(db->mutex) ); if( db->mallocFailed==0 ){ if( p==0 ){ @@ -19447,19 +21146,18 @@ if( pNew ){ memcpy(pNew, p, db->lookaside.sz); sqlite3DbFree(db, p); } }else{ - assert( sqlite3MemdebugHasType(p, MEMTYPE_DB) ); - assert( sqlite3MemdebugHasType(p, MEMTYPE_LOOKASIDE|MEMTYPE_HEAP) ); + assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); sqlite3MemdebugSetType(p, MEMTYPE_HEAP); - pNew = sqlite3_realloc(p, n); + pNew = sqlite3_realloc64(p, n); if( !pNew ){ - sqlite3MemdebugSetType(p, MEMTYPE_DB|MEMTYPE_HEAP); db->mallocFailed = 1; } - sqlite3MemdebugSetType(pNew, MEMTYPE_DB | + sqlite3MemdebugSetType(pNew, (db->lookaside.bEnabled ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP)); } } return pNew; } @@ -19466,11 +21164,11 @@ /* ** Attempt to reallocate p. If the reallocation fails, then free p ** and set the mallocFailed flag in the database connection. */ -SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *db, void *p, int n){ +SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *db, void *p, u64 n){ void *pNew; pNew = sqlite3DbRealloc(db, p, n); if( !pNew ){ sqlite3DbFree(db, p); } @@ -19496,19 +21194,19 @@ if( zNew ){ memcpy(zNew, z, n); } return zNew; } -SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3 *db, const char *z, int n){ +SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3 *db, const char *z, u64 n){ char *zNew; if( z==0 ){ return 0; } assert( (n&0x7fffffff)==n ); zNew = sqlite3DbMallocRaw(db, n+1); if( zNew ){ - memcpy(zNew, z, n); + memcpy(zNew, z, (size_t)n); zNew[n] = 0; } return zNew; } @@ -19526,10 +21224,18 @@ va_end(ap); sqlite3DbFree(db, *pz); *pz = z; } +/* +** Take actions at the end of an API call to indicate an OOM error +*/ +static SQLITE_NOINLINE int apiOomError(sqlite3 *db){ + db->mallocFailed = 0; + sqlite3Error(db, SQLITE_NOMEM); + return SQLITE_NOMEM; +} /* ** This function must be called before exiting any API function (i.e. ** returning control to the user) that has called sqlite3_malloc or ** sqlite3_realloc. @@ -19546,16 +21252,15 @@ /* If the db handle is not NULL, then we must hold the connection handle ** mutex here. Otherwise the read (and possible write) of db->mallocFailed ** is unsafe, as is the call to sqlite3Error(). */ assert( !db || sqlite3_mutex_held(db->mutex) ); - if( db && (db->mallocFailed || rc==SQLITE_IOERR_NOMEM) ){ - sqlite3Error(db, SQLITE_NOMEM, 0); - db->mallocFailed = 0; - rc = SQLITE_NOMEM; + if( db==0 ) return rc & 0xff; + if( db->mallocFailed || rc==SQLITE_IOERR_NOMEM ){ + return apiOomError(db); } - return rc & (db ? db->errMask : 0xff); + return rc & db->errMask; } /************** End of malloc.c **********************************************/ /************** Begin file printf.c ******************************************/ /* @@ -19692,22 +21397,33 @@ return (char)digit; } #endif /* SQLITE_OMIT_FLOATING_POINT */ /* -** Append N space characters to the given string buffer. +** Set the StrAccum object to an error mode. */ -SQLITE_PRIVATE void sqlite3AppendSpace(StrAccum *pAccum, int N){ - static const char zSpaces[] = " "; - while( N>=(int)sizeof(zSpaces)-1 ){ - sqlite3StrAccumAppend(pAccum, zSpaces, sizeof(zSpaces)-1); - N -= sizeof(zSpaces)-1; - } - if( N>0 ){ - sqlite3StrAccumAppend(pAccum, zSpaces, N); - } -} +static void setStrAccumError(StrAccum *p, u8 eError){ + p->accError = eError; + p->nAlloc = 0; +} + +/* +** Extra argument values from a PrintfArguments object +*/ +static sqlite3_int64 getIntArg(PrintfArguments *p){ + if( p->nArg<=p->nUsed ) return 0; + return sqlite3_value_int64(p->apArg[p->nUsed++]); +} +static double getDoubleArg(PrintfArguments *p){ + if( p->nArg<=p->nUsed ) return 0.0; + return sqlite3_value_double(p->apArg[p->nUsed++]); +} +static char *getTextArg(PrintfArguments *p){ + if( p->nArg<=p->nUsed ) return 0; + return (char*)sqlite3_value_text(p->apArg[p->nUsed++]); +} + /* ** On machines with a small stack size, you can redefine the ** SQLITE_PRINT_BUF_SIZE to be something smaller, if desired. */ @@ -19718,14 +21434,14 @@ /* ** Render a string given by "fmt" into the StrAccum object. */ SQLITE_PRIVATE void sqlite3VXPrintf( - StrAccum *pAccum, /* Accumulate results here */ - int useExtended, /* Allow extended %-conversions */ - const char *fmt, /* Format string */ - va_list ap /* arguments */ + StrAccum *pAccum, /* Accumulate results here */ + u32 bFlags, /* SQLITE_PRINTF_* flags */ + const char *fmt, /* Format string */ + va_list ap /* arguments */ ){ int c; /* Next character in the format string */ char *bufpt; /* Pointer to the conversion buffer */ int precision; /* Precision of the current field */ int length; /* Length of the field */ @@ -19739,35 +21455,48 @@ etByte flag_zeropad; /* True if field width constant starts with zero */ etByte flag_long; /* True if "l" flag is present */ etByte flag_longlong; /* True if the "ll" flag is present */ etByte done; /* Loop termination flag */ etByte xtype = 0; /* Conversion paradigm */ + u8 bArgList; /* True for SQLITE_PRINTF_SQLFUNC */ + u8 useIntern; /* Ok to use internal conversions (ex: %T) */ char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ sqlite_uint64 longvalue; /* Value for integer types */ LONGDOUBLE_TYPE realvalue; /* Value for real types */ const et_info *infop; /* Pointer to the appropriate info structure */ char *zOut; /* Rendering buffer */ int nOut; /* Size of the rendering buffer */ - char *zExtra; /* Malloced memory used by some conversion */ + char *zExtra = 0; /* Malloced memory used by some conversion */ #ifndef SQLITE_OMIT_FLOATING_POINT int exp, e2; /* exponent of real numbers */ int nsd; /* Number of significant digits returned */ double rounder; /* Used for rounding floating point values */ etByte flag_dp; /* True if decimal point should be shown */ etByte flag_rtz; /* True if trailing zeros should be removed */ #endif + PrintfArguments *pArgList = 0; /* Arguments for SQLITE_PRINTF_SQLFUNC */ char buf[etBUFSIZE]; /* Conversion buffer */ bufpt = 0; + if( bFlags ){ + if( (bArgList = (bFlags & SQLITE_PRINTF_SQLFUNC))!=0 ){ + pArgList = va_arg(ap, PrintfArguments*); + } + useIntern = bFlags & SQLITE_PRINTF_INTERNAL; + }else{ + bArgList = useIntern = 0; + } for(; (c=(*fmt))!=0; ++fmt){ if( c!='%' ){ - int amt; bufpt = (char *)fmt; - amt = 1; - while( (c=(*++fmt))!='%' && c!=0 ) amt++; - sqlite3StrAccumAppend(pAccum, bufpt, amt); - if( c==0 ) break; +#if HAVE_STRCHRNUL + fmt = strchrnul(fmt, '%'); +#else + do{ fmt++; }while( *fmt && *fmt != '%' ); +#endif + sqlite3StrAccumAppend(pAccum, bufpt, (int)(fmt - bufpt)); + if( *fmt==0 ) break; } if( (c=(*++fmt))==0 ){ sqlite3StrAccumAppend(pAccum, "%", 1); break; } @@ -19787,11 +21516,15 @@ } }while( !done && (c=(*++fmt))!=0 ); /* Get the field width */ width = 0; if( c=='*' ){ - width = va_arg(ap,int); + if( bArgList ){ + width = (int)getIntArg(pArgList); + }else{ + width = va_arg(ap,int); + } if( width<0 ){ flag_leftjustify = 1; width = -width; } c = *++fmt; @@ -19804,11 +21537,15 @@ /* Get the precision */ if( c=='.' ){ precision = 0; c = *++fmt; if( c=='*' ){ - precision = va_arg(ap,int); + if( bArgList ){ + precision = (int)getIntArg(pArgList); + }else{ + precision = va_arg(ap,int); + } if( precision<0 ) precision = -precision; c = *++fmt; }else{ while( c>='0' && c<='9' ){ precision = precision*10 + c - '0'; @@ -19835,19 +21572,18 @@ infop = &fmtinfo[0]; xtype = etINVALID; for(idx=0; idx<ArraySize(fmtinfo); idx++){ if( c==fmtinfo[idx].fmttype ){ infop = &fmtinfo[idx]; - if( useExtended || (infop->flags & FLAG_INTERN)==0 ){ + if( useIntern || (infop->flags & FLAG_INTERN)==0 ){ xtype = infop->type; }else{ return; } break; } } - zExtra = 0; /* ** At this point, variables are initialized as follows: ** ** flag_alternateform TRUE if a '#' is present. @@ -19875,11 +21611,13 @@ /* Fall through into the next case */ case etORDINAL: case etRADIX: if( infop->flags & FLAG_SIGNED ){ i64 v; - if( flag_longlong ){ + if( bArgList ){ + v = getIntArg(pArgList); + }else if( flag_longlong ){ v = va_arg(ap,i64); }else if( flag_long ){ v = va_arg(ap,long int); }else{ v = va_arg(ap,int); @@ -19896,11 +21634,13 @@ if( flag_plussign ) prefix = '+'; else if( flag_blanksign ) prefix = ' '; else prefix = 0; } }else{ - if( flag_longlong ){ + if( bArgList ){ + longvalue = (u64)getIntArg(pArgList); + }else if( flag_longlong ){ longvalue = va_arg(ap,u64); }else if( flag_long ){ longvalue = va_arg(ap,unsigned long int); }else{ longvalue = va_arg(ap,unsigned int); @@ -19916,11 +21656,11 @@ zOut = buf; }else{ nOut = precision + 10; zOut = zExtra = sqlite3Malloc( nOut ); if( zOut==0 ){ - pAccum->accError = STRACCUM_NOMEM; + setStrAccumError(pAccum, STRACCUM_NOMEM); return; } } bufpt = &zOut[nOut-1]; if( xtype==etORDINAL ){ @@ -19931,14 +21671,12 @@ } *(--bufpt) = zOrd[x*2+1]; *(--bufpt) = zOrd[x*2]; } { - register const char *cset; /* Use registers for speed */ - register int base; - cset = &aDigits[infop->charset]; - base = infop->base; + const char *cset = &aDigits[infop->charset]; + u8 base = infop->base; do{ /* Convert to ascii */ *(--bufpt) = cset[longvalue%base]; longvalue = longvalue/base; }while( longvalue>0 ); } @@ -19956,11 +21694,15 @@ length = (int)(&zOut[nOut-1]-bufpt); break; case etFLOAT: case etEXP: case etGENERIC: - realvalue = va_arg(ap,double); + if( bArgList ){ + realvalue = getDoubleArg(pArgList); + }else{ + realvalue = va_arg(ap,double); + } #ifdef SQLITE_OMIT_FLOATING_POINT length = 0; #else if( precision<0 ) precision = 6; /* Set default precision */ if( realvalue<0.0 ){ @@ -20028,11 +21770,11 @@ e2 = exp; } if( MAX(e2,0)+precision+width > etBUFSIZE - 15 ){ bufpt = zExtra = sqlite3Malloc( MAX(e2,0)+precision+width+15 ); if( bufpt==0 ){ - pAccum->accError = STRACCUM_NOMEM; + setStrAccumError(pAccum, STRACCUM_NOMEM); return; } } zOut = bufpt; nsd = 16 + flag_altform2*10; @@ -20111,35 +21853,49 @@ length = width; } #endif /* !defined(SQLITE_OMIT_FLOATING_POINT) */ break; case etSIZE: - *(va_arg(ap,int*)) = pAccum->nChar; + if( !bArgList ){ + *(va_arg(ap,int*)) = pAccum->nChar; + } length = width = 0; break; case etPERCENT: buf[0] = '%'; bufpt = buf; length = 1; break; case etCHARX: - c = va_arg(ap,int); - buf[0] = (char)c; - if( precision>=0 ){ - for(idx=1; idx<precision; idx++) buf[idx] = (char)c; - length = precision; + if( bArgList ){ + bufpt = getTextArg(pArgList); + c = bufpt ? bufpt[0] : 0; }else{ - length =1; + c = va_arg(ap,int); } + if( precision>1 ){ + width -= precision-1; + if( width>1 && !flag_leftjustify ){ + sqlite3AppendChar(pAccum, width-1, ' '); + width = 0; + } + sqlite3AppendChar(pAccum, precision-1, c); + } + length = 1; + buf[0] = c; bufpt = buf; break; case etSTRING: case etDYNSTRING: - bufpt = va_arg(ap,char*); + if( bArgList ){ + bufpt = getTextArg(pArgList); + }else{ + bufpt = va_arg(ap,char*); + } if( bufpt==0 ){ bufpt = ""; - }else if( xtype==etDYNSTRING ){ + }else if( xtype==etDYNSTRING && !bArgList ){ zExtra = bufpt; } if( precision>=0 ){ for(length=0; length<precision && bufpt[length]; length++){} }else{ @@ -20151,11 +21907,17 @@ case etSQLESCAPE3: { int i, j, k, n, isnull; int needQuote; char ch; char q = ((xtype==etSQLESCAPE3)?'"':'\''); /* Quote character */ - char *escarg = va_arg(ap,char*); + char *escarg; + + if( bArgList ){ + escarg = getTextArg(pArgList); + }else{ + escarg = va_arg(ap,char*); + } isnull = escarg==0; if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)"); k = precision; for(i=n=0; k!=0 && (ch=escarg[i])!=0; i++, k--){ if( ch==q ) n++; @@ -20163,11 +21925,11 @@ needQuote = !isnull && xtype==etSQLESCAPE2; n += i + 1 + needQuote*2; if( n>etBUFSIZE ){ bufpt = zExtra = sqlite3Malloc( n ); if( bufpt==0 ){ - pAccum->accError = STRACCUM_NOMEM; + setStrAccumError(pAccum, STRACCUM_NOMEM); return; } }else{ bufpt = buf; } @@ -20186,26 +21948,28 @@ ** if( precision>=0 && precision<length ) length = precision; */ break; } case etTOKEN: { Token *pToken = va_arg(ap, Token*); - if( pToken ){ + assert( bArgList==0 ); + if( pToken && pToken->n ){ sqlite3StrAccumAppend(pAccum, (const char*)pToken->z, pToken->n); } length = width = 0; break; } case etSRCLIST: { SrcList *pSrc = va_arg(ap, SrcList*); int k = va_arg(ap, int); struct SrcList_item *pItem = &pSrc->a[k]; + assert( bArgList==0 ); assert( k>=0 && k<pSrc->nSrc ); if( pItem->zDatabase ){ - sqlite3StrAccumAppend(pAccum, pItem->zDatabase, -1); + sqlite3StrAccumAppendAll(pAccum, pItem->zDatabase); sqlite3StrAccumAppend(pAccum, ".", 1); } - sqlite3StrAccumAppend(pAccum, pItem->zName, -1); + sqlite3StrAccumAppendAll(pAccum, pItem->zName); length = width = 0; break; } default: { assert( xtype==etINVALID ); @@ -20215,84 +21979,125 @@ /* ** The text of the conversion is pointed to by "bufpt" and is ** "length" characters long. The field width is "width". Do ** the output. */ - if( !flag_leftjustify ){ - register int nspace; - nspace = width-length; - if( nspace>0 ){ - sqlite3AppendSpace(pAccum, nspace); - } - } - if( length>0 ){ - sqlite3StrAccumAppend(pAccum, bufpt, length); - } - if( flag_leftjustify ){ - register int nspace; - nspace = width-length; - if( nspace>0 ){ - sqlite3AppendSpace(pAccum, nspace); - } - } - sqlite3_free(zExtra); + width -= length; + if( width>0 && !flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' '); + sqlite3StrAccumAppend(pAccum, bufpt, length); + if( width>0 && flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' '); + + if( zExtra ){ + sqlite3_free(zExtra); + zExtra = 0; + } }/* End for loop over the format string */ } /* End of function */ /* -** Append N bytes of text from z to the StrAccum object. +** Enlarge the memory allocation on a StrAccum object so that it is +** able to accept at least N more bytes of text. +** +** Return the number of bytes of text that StrAccum is able to accept +** after the attempted enlargement. The value returned might be zero. */ -SQLITE_PRIVATE void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){ - assert( z!=0 || N==0 ); +static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ + char *zNew; + assert( p->nChar+N >= p->nAlloc ); /* Only called if really needed */ if( p->accError ){ testcase(p->accError==STRACCUM_TOOBIG); testcase(p->accError==STRACCUM_NOMEM); - return; + return 0; } - assert( p->zText!=0 || p->nChar==0 ); - if( N<=0 ){ - if( N==0 || z[0]==0 ) return; - N = sqlite3Strlen30(z); + if( !p->useMalloc ){ + N = p->nAlloc - p->nChar - 1; + setStrAccumError(p, STRACCUM_TOOBIG); + return N; + }else{ + char *zOld = (p->zText==p->zBase ? 0 : p->zText); + i64 szNew = p->nChar; + szNew += N + 1; + if( szNew+p->nChar<=p->mxAlloc ){ + /* Force exponential buffer size growth as long as it does not overflow, + ** to avoid having to call this routine too often */ + szNew += p->nChar; + } + if( szNew > p->mxAlloc ){ + sqlite3StrAccumReset(p); + setStrAccumError(p, STRACCUM_TOOBIG); + return 0; + }else{ + p->nAlloc = (int)szNew; + } + if( p->useMalloc==1 ){ + zNew = sqlite3DbRealloc(p->db, zOld, p->nAlloc); + }else{ + zNew = sqlite3_realloc(zOld, p->nAlloc); + } + if( zNew ){ + assert( p->zText!=0 || p->nChar==0 ); + if( zOld==0 && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar); + p->zText = zNew; + p->nAlloc = sqlite3DbMallocSize(p->db, zNew); + }else{ + sqlite3StrAccumReset(p); + setStrAccumError(p, STRACCUM_NOMEM); + return 0; + } } + return N; +} + +/* +** Append N copies of character c to the given string buffer. +*/ +SQLITE_PRIVATE void sqlite3AppendChar(StrAccum *p, int N, char c){ + if( p->nChar+N >= p->nAlloc && (N = sqlite3StrAccumEnlarge(p, N))<=0 ) return; + while( (N--)>0 ) p->zText[p->nChar++] = c; +} + +/* +** The StrAccum "p" is not large enough to accept N new bytes of z[]. +** So enlarge if first, then do the append. +** +** This is a helper routine to sqlite3StrAccumAppend() that does special-case +** work (enlarging the buffer) using tail recursion, so that the +** sqlite3StrAccumAppend() routine can use fast calling semantics. +*/ +static void SQLITE_NOINLINE enlargeAndAppend(StrAccum *p, const char *z, int N){ + N = sqlite3StrAccumEnlarge(p, N); + if( N>0 ){ + memcpy(&p->zText[p->nChar], z, N); + p->nChar += N; + } +} + +/* +** Append N bytes of text from z to the StrAccum object. Increase the +** size of the memory allocation for StrAccum if necessary. +*/ +SQLITE_PRIVATE void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){ + assert( z!=0 ); + assert( p->zText!=0 || p->nChar==0 || p->accError ); + assert( N>=0 ); + assert( p->accError==0 || p->nAlloc==0 ); if( p->nChar+N >= p->nAlloc ){ - char *zNew; - if( !p->useMalloc ){ - p->accError = STRACCUM_TOOBIG; - N = p->nAlloc - p->nChar - 1; - if( N<=0 ){ - return; - } - }else{ - char *zOld = (p->zText==p->zBase ? 0 : p->zText); - i64 szNew = p->nChar; - szNew += N + 1; - if( szNew > p->mxAlloc ){ - sqlite3StrAccumReset(p); - p->accError = STRACCUM_TOOBIG; - return; - }else{ - p->nAlloc = (int)szNew; - } - if( p->useMalloc==1 ){ - zNew = sqlite3DbRealloc(p->db, zOld, p->nAlloc); - }else{ - zNew = sqlite3_realloc(zOld, p->nAlloc); - } - if( zNew ){ - if( zOld==0 && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar); - p->zText = zNew; - }else{ - p->accError = STRACCUM_NOMEM; - sqlite3StrAccumReset(p); - return; - } - } - } - assert( p->zText ); - memcpy(&p->zText[p->nChar], z, N); - p->nChar += N; -} + enlargeAndAppend(p,z,N); + }else{ + assert( p->zText ); + p->nChar += N; + memcpy(&p->zText[p->nChar-N], z, N); + } +} + +/* +** Append the complete text of zero-terminated string z[] to the p string. +*/ +SQLITE_PRIVATE void sqlite3StrAccumAppendAll(StrAccum *p, const char *z){ + sqlite3StrAccumAppend(p, z, sqlite3Strlen30(z)); +} + /* ** Finish off a string by making sure it is zero-terminated. ** Return a pointer to the resulting string. Return a NULL ** pointer if any kind of error was encountered. @@ -20307,11 +22112,11 @@ p->zText = sqlite3_malloc(p->nChar+1); } if( p->zText ){ memcpy(p->zText, p->zBase, p->nChar+1); }else{ - p->accError = STRACCUM_NOMEM; + setStrAccumError(p, STRACCUM_NOMEM); } } } return p->zText; } @@ -20353,11 +22158,11 @@ StrAccum acc; assert( db!=0 ); sqlite3StrAccumInit(&acc, zBase, sizeof(zBase), db->aLimit[SQLITE_LIMIT_LENGTH]); acc.db = db; - sqlite3VXPrintf(&acc, 1, zFormat, ap); + sqlite3VXPrintf(&acc, SQLITE_PRINTF_INTERNAL, zFormat, ap); z = sqlite3StrAccumFinish(&acc); if( acc.accError==STRACCUM_NOMEM ){ db->mallocFailed = 1; } return z; @@ -20376,11 +22181,11 @@ return z; } /* ** Like sqlite3MPrintf(), but call sqlite3DbFree() on zStr after formatting -** the string and before returnning. This routine is intended to be used +** the string and before returning. This routine is intended to be used ** to modify an existing string. For example: ** ** x = sqlite3MPrintf(db, x, "prefix %s suffix", x); ** */ @@ -20396,14 +22201,21 @@ /* ** Print into memory obtained from sqlite3_malloc(). Omit the internal ** %-conversion extensions. */ -SQLITE_API char *sqlite3_vmprintf(const char *zFormat, va_list ap){ +SQLITE_API char *SQLITE_STDCALL sqlite3_vmprintf(const char *zFormat, va_list ap){ char *z; char zBase[SQLITE_PRINT_BUF_SIZE]; StrAccum acc; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( zFormat==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif #ifndef SQLITE_OMIT_AUTOINIT if( sqlite3_initialize() ) return 0; #endif sqlite3StrAccumInit(&acc, zBase, sizeof(zBase), SQLITE_MAX_LENGTH); acc.useMalloc = 2; @@ -20414,11 +22226,11 @@ /* ** Print into memory obtained from sqlite3_malloc()(). Omit the internal ** %-conversion extensions. */ -SQLITE_API char *sqlite3_mprintf(const char *zFormat, ...){ +SQLITE_API char *SQLITE_CDECL sqlite3_mprintf(const char *zFormat, ...){ va_list ap; char *z; #ifndef SQLITE_OMIT_AUTOINIT if( sqlite3_initialize() ) return 0; #endif @@ -20439,19 +22251,26 @@ ** this without breaking compatibility, so we just have to live with the ** mistake. ** ** sqlite3_vsnprintf() is the varargs version. */ -SQLITE_API char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_list ap){ +SQLITE_API char *SQLITE_STDCALL sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_list ap){ StrAccum acc; if( n<=0 ) return zBuf; +#ifdef SQLITE_ENABLE_API_ARMOR + if( zBuf==0 || zFormat==0 ) { + (void)SQLITE_MISUSE_BKPT; + if( zBuf ) zBuf[0] = 0; + return zBuf; + } +#endif sqlite3StrAccumInit(&acc, zBuf, n, 0); acc.useMalloc = 0; sqlite3VXPrintf(&acc, 0, zFormat, ap); return sqlite3StrAccumFinish(&acc); } -SQLITE_API char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ +SQLITE_API char *SQLITE_CDECL sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ char *z; va_list ap; va_start(ap,zFormat); z = sqlite3_vsnprintf(n, zBuf, zFormat, ap); va_end(ap); @@ -20479,11 +22298,11 @@ } /* ** Format and write a message to the log if logging is enabled. */ -SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...){ +SQLITE_API void SQLITE_CDECL sqlite3_log(int iErrCode, const char *zFormat, ...){ va_list ap; /* Vararg list */ if( sqlite3GlobalConfig.xLog ){ va_start(ap, zFormat); renderLogMsg(iErrCode, zFormat, ap); va_end(ap); @@ -20509,21 +22328,82 @@ fprintf(stdout,"%s", zBuf); fflush(stdout); } #endif -#ifndef SQLITE_OMIT_TRACE +#ifdef SQLITE_DEBUG +/************************************************************************* +** Routines for implementing the "TreeView" display of hierarchical +** data structures for debugging. +** +** The main entry points (coded elsewhere) are: +** sqlite3TreeViewExpr(0, pExpr, 0); +** sqlite3TreeViewExprList(0, pList, 0, 0); +** sqlite3TreeViewSelect(0, pSelect, 0); +** Insert calls to those routines while debugging in order to display +** a diagram of Expr, ExprList, and Select objects. +** +*/ +/* Add a new subitem to the tree. The moreToFollow flag indicates that this +** is not the last item in the tree. */ +SQLITE_PRIVATE TreeView *sqlite3TreeViewPush(TreeView *p, u8 moreToFollow){ + if( p==0 ){ + p = sqlite3_malloc( sizeof(*p) ); + if( p==0 ) return 0; + memset(p, 0, sizeof(*p)); + }else{ + p->iLevel++; + } + assert( moreToFollow==0 || moreToFollow==1 ); + if( p->iLevel<sizeof(p->bLine) ) p->bLine[p->iLevel] = moreToFollow; + return p; +} +/* Finished with one layer of the tree */ +SQLITE_PRIVATE void sqlite3TreeViewPop(TreeView *p){ + if( p==0 ) return; + p->iLevel--; + if( p->iLevel<0 ) sqlite3_free(p); +} +/* Generate a single line of output for the tree, with a prefix that contains +** all the appropriate tree lines */ +SQLITE_PRIVATE void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ + va_list ap; + int i; + StrAccum acc; + char zBuf[500]; + sqlite3StrAccumInit(&acc, zBuf, sizeof(zBuf), 0); + acc.useMalloc = 0; + if( p ){ + for(i=0; i<p->iLevel && i<sizeof(p->bLine)-1; i++){ + sqlite3StrAccumAppend(&acc, p->bLine[i] ? "| " : " ", 4); + } + sqlite3StrAccumAppend(&acc, p->bLine[i] ? "|-- " : "'-- ", 4); + } + va_start(ap, zFormat); + sqlite3VXPrintf(&acc, 0, zFormat, ap); + va_end(ap); + if( zBuf[acc.nChar-1]!='\n' ) sqlite3StrAccumAppend(&acc, "\n", 1); + sqlite3StrAccumFinish(&acc); + fprintf(stdout,"%s", zBuf); + fflush(stdout); +} +/* Shorthand for starting a new tree item that consists of a single label */ +SQLITE_PRIVATE void sqlite3TreeViewItem(TreeView *p, const char *zLabel, u8 moreToFollow){ + p = sqlite3TreeViewPush(p, moreToFollow); + sqlite3TreeViewLine(p, "%s", zLabel); +} +#endif /* SQLITE_DEBUG */ + /* ** variable-argument wrapper around sqlite3VXPrintf(). */ -SQLITE_PRIVATE void sqlite3XPrintf(StrAccum *p, const char *zFormat, ...){ +SQLITE_PRIVATE void sqlite3XPrintf(StrAccum *p, u32 bFlags, const char *zFormat, ...){ va_list ap; va_start(ap,zFormat); - sqlite3VXPrintf(p, 1, zFormat, ap); + sqlite3VXPrintf(p, bFlags, zFormat, ap); va_end(ap); } -#endif /************** End of printf.c **********************************************/ /************** Begin file random.c ******************************************/ /* ** 2001 September 15 @@ -20554,11 +22434,11 @@ } sqlite3Prng; /* ** Return N random bytes. */ -SQLITE_API void sqlite3_randomness(int N, void *pBuf){ +SQLITE_API void SQLITE_STDCALL sqlite3_randomness(int N, void *pBuf){ unsigned char t; unsigned char *zBuf = pBuf; /* The "wsdPrng" macro will resolve to the pseudo-random number generator ** state vector. If writable static data is unsupported on the target, @@ -20572,13 +22452,27 @@ #else # define wsdPrng sqlite3Prng #endif #if SQLITE_THREADSAFE - sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PRNG); + sqlite3_mutex *mutex; +#endif + +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return; +#endif + +#if SQLITE_THREADSAFE + mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PRNG); +#endif + sqlite3_mutex_enter(mutex); -#endif + if( N<=0 || pBuf==0 ){ + wsdPrng.isInit = 0; + sqlite3_mutex_leave(mutex); + return; + } /* Initialize the state of the random number generator once, ** the first time this routine is called. The seed value does ** not need to contain a lot of randomness since we are not ** trying to do secure encryption or anything like that... @@ -20603,19 +22497,20 @@ wsdPrng.s[i] = t; } wsdPrng.isInit = 1; } - while( N-- ){ + assert( N>0 ); + do{ wsdPrng.i++; t = wsdPrng.s[wsdPrng.i]; wsdPrng.j += t; wsdPrng.s[wsdPrng.i] = wsdPrng.s[wsdPrng.j]; wsdPrng.s[wsdPrng.j] = t; t += wsdPrng.s[wsdPrng.i]; *(zBuf++) = wsdPrng.s[t]; - } + }while( --N ); sqlite3_mutex_leave(mutex); } #ifndef SQLITE_OMIT_BUILTIN_TEST /* @@ -20640,16 +22535,279 @@ &GLOBAL(struct sqlite3PrngType, sqlite3Prng), &GLOBAL(struct sqlite3PrngType, sqlite3SavedPrng), sizeof(sqlite3Prng) ); } -SQLITE_PRIVATE void sqlite3PrngResetState(void){ - GLOBAL(struct sqlite3PrngType, sqlite3Prng).isInit = 0; -} #endif /* SQLITE_OMIT_BUILTIN_TEST */ /************** End of random.c **********************************************/ +/************** Begin file threads.c *****************************************/ +/* +** 2012 July 21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file presents a simple cross-platform threading interface for +** use internally by SQLite. +** +** A "thread" can be created using sqlite3ThreadCreate(). This thread +** runs independently of its creator until it is joined using +** sqlite3ThreadJoin(), at which point it terminates. +** +** Threads do not have to be real. It could be that the work of the +** "thread" is done by the main thread at either the sqlite3ThreadCreate() +** or sqlite3ThreadJoin() call. This is, in fact, what happens in +** single threaded systems. Nothing in SQLite requires multiple threads. +** This interface exists so that applications that want to take advantage +** of multiple cores can do so, while also allowing applications to stay +** single-threaded if desired. +*/ +#if SQLITE_OS_WIN +#endif + +#if SQLITE_MAX_WORKER_THREADS>0 + +/********************************* Unix Pthreads ****************************/ +#if SQLITE_OS_UNIX && defined(SQLITE_MUTEX_PTHREADS) && SQLITE_THREADSAFE>0 + +#define SQLITE_THREADS_IMPLEMENTED 1 /* Prevent the single-thread code below */ +/* #include <pthread.h> */ + +/* A running thread */ +struct SQLiteThread { + pthread_t tid; /* Thread ID */ + int done; /* Set to true when thread finishes */ + void *pOut; /* Result returned by the thread */ + void *(*xTask)(void*); /* The thread routine */ + void *pIn; /* Argument to the thread */ +}; + +/* Create a new thread */ +SQLITE_PRIVATE int sqlite3ThreadCreate( + SQLiteThread **ppThread, /* OUT: Write the thread object here */ + void *(*xTask)(void*), /* Routine to run in a separate thread */ + void *pIn /* Argument passed into xTask() */ +){ + SQLiteThread *p; + int rc; + + assert( ppThread!=0 ); + assert( xTask!=0 ); + /* This routine is never used in single-threaded mode */ + assert( sqlite3GlobalConfig.bCoreMutex!=0 ); + + *ppThread = 0; + p = sqlite3Malloc(sizeof(*p)); + if( p==0 ) return SQLITE_NOMEM; + memset(p, 0, sizeof(*p)); + p->xTask = xTask; + p->pIn = pIn; + if( sqlite3FaultSim(200) ){ + rc = 1; + }else{ + rc = pthread_create(&p->tid, 0, xTask, pIn); + } + if( rc ){ + p->done = 1; + p->pOut = xTask(pIn); + } + *ppThread = p; + return SQLITE_OK; +} + +/* Get the results of the thread */ +SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ + int rc; + + assert( ppOut!=0 ); + if( NEVER(p==0) ) return SQLITE_NOMEM; + if( p->done ){ + *ppOut = p->pOut; + rc = SQLITE_OK; + }else{ + rc = pthread_join(p->tid, ppOut) ? SQLITE_ERROR : SQLITE_OK; + } + sqlite3_free(p); + return rc; +} + +#endif /* SQLITE_OS_UNIX && defined(SQLITE_MUTEX_PTHREADS) */ +/******************************** End Unix Pthreads *************************/ + + +/********************************* Win32 Threads ****************************/ +#if SQLITE_OS_WIN_THREADS + +#define SQLITE_THREADS_IMPLEMENTED 1 /* Prevent the single-thread code below */ +#include <process.h> + +/* A running thread */ +struct SQLiteThread { + void *tid; /* The thread handle */ + unsigned id; /* The thread identifier */ + void *(*xTask)(void*); /* The routine to run as a thread */ + void *pIn; /* Argument to xTask */ + void *pResult; /* Result of xTask */ +}; + +/* Thread procedure Win32 compatibility shim */ +static unsigned __stdcall sqlite3ThreadProc( + void *pArg /* IN: Pointer to the SQLiteThread structure */ +){ + SQLiteThread *p = (SQLiteThread *)pArg; + + assert( p!=0 ); +#if 0 + /* + ** This assert appears to trigger spuriously on certain + ** versions of Windows, possibly due to _beginthreadex() + ** and/or CreateThread() not fully setting their thread + ** ID parameter before starting the thread. + */ + assert( p->id==GetCurrentThreadId() ); +#endif + assert( p->xTask!=0 ); + p->pResult = p->xTask(p->pIn); + + _endthreadex(0); + return 0; /* NOT REACHED */ +} + +/* Create a new thread */ +SQLITE_PRIVATE int sqlite3ThreadCreate( + SQLiteThread **ppThread, /* OUT: Write the thread object here */ + void *(*xTask)(void*), /* Routine to run in a separate thread */ + void *pIn /* Argument passed into xTask() */ +){ + SQLiteThread *p; + + assert( ppThread!=0 ); + assert( xTask!=0 ); + *ppThread = 0; + p = sqlite3Malloc(sizeof(*p)); + if( p==0 ) return SQLITE_NOMEM; + if( sqlite3GlobalConfig.bCoreMutex==0 ){ + memset(p, 0, sizeof(*p)); + }else{ + p->xTask = xTask; + p->pIn = pIn; + p->tid = (void*)_beginthreadex(0, 0, sqlite3ThreadProc, p, 0, &p->id); + if( p->tid==0 ){ + memset(p, 0, sizeof(*p)); + } + } + if( p->xTask==0 ){ + p->id = GetCurrentThreadId(); + p->pResult = xTask(pIn); + } + *ppThread = p; + return SQLITE_OK; +} + +SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject); /* os_win.c */ + +/* Get the results of the thread */ +SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ + DWORD rc; + BOOL bRc; + + assert( ppOut!=0 ); + if( NEVER(p==0) ) return SQLITE_NOMEM; + if( p->xTask==0 ){ + assert( p->id==GetCurrentThreadId() ); + rc = WAIT_OBJECT_0; + assert( p->tid==0 ); + }else{ + assert( p->id!=0 && p->id!=GetCurrentThreadId() ); + rc = sqlite3Win32Wait((HANDLE)p->tid); + assert( rc!=WAIT_IO_COMPLETION ); + bRc = CloseHandle((HANDLE)p->tid); + assert( bRc ); + } + if( rc==WAIT_OBJECT_0 ) *ppOut = p->pResult; + sqlite3_free(p); + return (rc==WAIT_OBJECT_0) ? SQLITE_OK : SQLITE_ERROR; +} + +#endif /* SQLITE_OS_WIN_THREADS */ +/******************************** End Win32 Threads *************************/ + + +/********************************* Single-Threaded **************************/ +#ifndef SQLITE_THREADS_IMPLEMENTED +/* +** This implementation does not actually create a new thread. It does the +** work of the thread in the main thread, when either the thread is created +** or when it is joined +*/ + +/* A running thread */ +struct SQLiteThread { + void *(*xTask)(void*); /* The routine to run as a thread */ + void *pIn; /* Argument to xTask */ + void *pResult; /* Result of xTask */ +}; + +/* Create a new thread */ +SQLITE_PRIVATE int sqlite3ThreadCreate( + SQLiteThread **ppThread, /* OUT: Write the thread object here */ + void *(*xTask)(void*), /* Routine to run in a separate thread */ + void *pIn /* Argument passed into xTask() */ +){ + SQLiteThread *p; + + assert( ppThread!=0 ); + assert( xTask!=0 ); + *ppThread = 0; + p = sqlite3Malloc(sizeof(*p)); + if( p==0 ) return SQLITE_NOMEM; + if( (SQLITE_PTR_TO_INT(p)/17)&1 ){ + p->xTask = xTask; + p->pIn = pIn; + }else{ + p->xTask = 0; + p->pResult = xTask(pIn); + } + *ppThread = p; + return SQLITE_OK; +} + +/* Get the results of the thread */ +SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ + + assert( ppOut!=0 ); + if( NEVER(p==0) ) return SQLITE_NOMEM; + if( p->xTask ){ + *ppOut = p->xTask(p->pIn); + }else{ + *ppOut = p->pResult; + } + sqlite3_free(p); + +#if defined(SQLITE_TEST) + { + void *pTstAlloc = sqlite3Malloc(10); + if (!pTstAlloc) return SQLITE_NOMEM; + sqlite3_free(pTstAlloc); + } +#endif + + return SQLITE_OK; +} + +#endif /* !defined(SQLITE_THREADS_IMPLEMENTED) */ +/****************************** End Single-Threaded *************************/ +#endif /* SQLITE_MAX_WORKER_THREADS>0 */ + +/************** End of threads.c *********************************************/ /************** Begin file utf.c *********************************************/ /* ** 2004 April 13 ** ** The author disclaims copyright to this source code. In place of @@ -20795,12 +22953,12 @@ ** * Bytes in the range of 0x80 through 0xbf which occur as the first ** byte of a character are interpreted as single-byte characters ** and rendered as themselves even though they are technically ** invalid characters. ** -** * This routine accepts an infinite number of different UTF8 encodings -** for unicode values 0x80 and greater. It do not change over-length +** * This routine accepts over-length UTF8 encodings +** for unicode values 0x80 and greater. It does not change over-length ** encodings to 0xfffd as some systems recommend. */ #define READ_UTF8(zIn, zTerm, c) \ c = *(zIn++); \ if( c>=0xc0 ){ \ @@ -20846,11 +23004,11 @@ /* ** This routine transforms the internal text encoding used by pMem to ** desiredEnc. It is an error if the string is already of the desired ** encoding, or if *pMem does not contain a string value. */ -SQLITE_PRIVATE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desiredEnc){ +SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desiredEnc){ int len; /* Maximum length of output string in bytes */ unsigned char *zOut; /* Output buffer */ unsigned char *zIn; /* Input iterator */ unsigned char *zTerm; /* End of input */ unsigned char *z; /* Output iterator */ @@ -20961,16 +23119,17 @@ pMem->n = (int)(z - zOut); } *z = 0; assert( (pMem->n+(desiredEnc==SQLITE_UTF8?1:2))<=len ); + c = pMem->flags; sqlite3VdbeMemRelease(pMem); - pMem->flags &= ~(MEM_Static|MEM_Dyn|MEM_Ephem); + pMem->flags = MEM_Str|MEM_Term|(c&MEM_AffMask); pMem->enc = desiredEnc; - pMem->flags |= (MEM_Term|MEM_Dyn); pMem->z = (char*)zOut; pMem->zMalloc = pMem->z; + pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->z); translate_out: #if defined(TRANSLATE_TRACE) && defined(SQLITE_DEBUG) { char zBuf[100]; @@ -21092,11 +23251,10 @@ sqlite3VdbeMemRelease(&m); m.z = 0; } assert( (m.flags & MEM_Term)!=0 || db->mallocFailed ); assert( (m.flags & MEM_Str)!=0 || db->mallocFailed ); - assert( (m.flags & MEM_Dyn)!=0 || db->mallocFailed ); assert( m.z || db->mallocFailed ); return m.z; } /* @@ -21196,11 +23354,11 @@ ** This file contains functions for allocating memory, comparing ** strings, and stuff like that. ** */ /* #include <stdarg.h> */ -#ifdef SQLITE_HAVE_ISNAN +#if HAVE_ISNAN || SQLITE_HAVE_ISNAN # include <math.h> #endif /* ** Routine needed to support the testcase() macro. @@ -21210,20 +23368,38 @@ static unsigned dummy = 0; dummy += (unsigned)x; } #endif +/* +** Give a callback to the test harness that can be used to simulate faults +** in places where it is difficult or expensive to do so purely by means +** of inputs. +** +** The intent of the integer argument is to let the fault simulator know +** which of multiple sqlite3FaultSim() calls has been hit. +** +** Return whatever integer value the test callback returns, or return +** SQLITE_OK if no test callback is installed. +*/ +#ifndef SQLITE_OMIT_BUILTIN_TEST +SQLITE_PRIVATE int sqlite3FaultSim(int iTest){ + int (*xCallback)(int) = sqlite3GlobalConfig.xTestCallback; + return xCallback ? xCallback(iTest) : SQLITE_OK; +} +#endif + #ifndef SQLITE_OMIT_FLOATING_POINT /* ** Return true if the floating point value is Not a Number (NaN). ** ** Use the math library isnan() function if compiled with SQLITE_HAVE_ISNAN. ** Otherwise, we have our own implementation that works on most systems. */ SQLITE_PRIVATE int sqlite3IsNaN(double x){ int rc; /* The value return */ -#if !defined(SQLITE_HAVE_ISNAN) +#if !SQLITE_HAVE_ISNAN && !HAVE_ISNAN /* ** Systems that support the isnan() library function should probably ** make use of it by compiling with -DSQLITE_HAVE_ISNAN. But we have ** found that many systems do not have a working isnan() function so ** this implementation is provided as an alternative. @@ -21249,13 +23425,13 @@ # error SQLite will not work correctly with the -ffast-math option of GCC. #endif volatile double y = x; volatile double z = y; rc = (y!=z); -#else /* if defined(SQLITE_HAVE_ISNAN) */ +#else /* if HAVE_ISNAN */ rc = isnan(x); -#endif /* SQLITE_HAVE_ISNAN */ +#endif /* HAVE_ISNAN */ testcase( rc ); return rc; } #endif /* SQLITE_OMIT_FLOATING_POINT */ @@ -21271,10 +23447,19 @@ const char *z2 = z; if( z==0 ) return 0; while( *z2 ){ z2++; } return 0x3fffffff & (int)(z2 - z); } + +/* +** Set the current error code to err_code and clear any prior error message. +*/ +SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code){ + assert( db!=0 ); + db->errCode = err_code; + if( db->pErr ) sqlite3ValueSetNull(db->pErr); +} /* ** Set the most recent error code and error string for the sqlite ** handle "db". The error code is set to "err_code". ** @@ -21293,23 +23478,22 @@ ** ** To clear the most recent error for sqlite handle "db", sqlite3Error ** should be called with err_code set to SQLITE_OK and zFormat set ** to NULL. */ -SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code, const char *zFormat, ...){ - if( db && (db->pErr || (db->pErr = sqlite3ValueNew(db))!=0) ){ - db->errCode = err_code; - if( zFormat ){ - char *z; - va_list ap; - va_start(ap, zFormat); - z = sqlite3VMPrintf(db, zFormat, ap); - va_end(ap); - sqlite3ValueSetStr(db->pErr, -1, z, SQLITE_UTF8, SQLITE_DYNAMIC); - }else{ - sqlite3ValueSetStr(db->pErr, 0, 0, SQLITE_UTF8, SQLITE_STATIC); - } +SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3 *db, int err_code, const char *zFormat, ...){ + assert( db!=0 ); + db->errCode = err_code; + if( zFormat==0 ){ + sqlite3Error(db, err_code); + }else if( db->pErr || (db->pErr = sqlite3ValueNew(db))!=0 ){ + char *z; + va_list ap; + va_start(ap, zFormat); + z = sqlite3VMPrintf(db, zFormat, ap); + va_end(ap); + sqlite3ValueSetStr(db->pErr, -1, z, SQLITE_UTF8, SQLITE_DYNAMIC); } } /* ** Add an error message to pParse->zErrMsg and increment pParse->nErr. @@ -21319,16 +23503,16 @@ ** %z A string that should be freed after use ** %d Insert an integer ** %T Insert a token ** %S Insert the first element of a SrcList ** -** This function should be used to report any error that occurs whilst +** This function should be used to report any error that occurs while ** compiling an SQL statement (i.e. within sqlite3_prepare()). The ** last thing the sqlite3_prepare() function does is copy the error ** stored by this function into the database handle using sqlite3Error(). -** Function sqlite3Error() should be used during statement execution -** (sqlite3_step() etc.). +** Functions sqlite3Error() or sqlite3ErrorWithMsg() should be used +** during statement execution (sqlite3_step() etc.). */ SQLITE_PRIVATE void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){ char *zMsg; va_list ap; sqlite3 *db = pParse->db; @@ -21357,11 +23541,11 @@ ** The return value is -1 if no dequoting occurs or the length of the ** dequoted string, exclusive of the zero terminator, if dequoting does ** occur. ** ** 2002-Feb-14: This routine is extended to remove MS-Access style -** brackets from around identifers. For example: "[a-b-c]" becomes +** brackets from around identifiers. For example: "[a-b-c]" becomes ** "a-b-c". */ SQLITE_PRIVATE int sqlite3Dequote(char *z){ char quote; int i, j; @@ -21402,19 +23586,29 @@ ** sqlite3_strnicmp() APIs allow applications and extensions to compare ** the contents of two buffers containing UTF-8 strings in a ** case-independent fashion, using the same definition of "case ** independence" that SQLite uses internally when comparing identifiers. */ -SQLITE_API int sqlite3_stricmp(const char *zLeft, const char *zRight){ +SQLITE_API int SQLITE_STDCALL sqlite3_stricmp(const char *zLeft, const char *zRight){ register unsigned char *a, *b; + if( zLeft==0 ){ + return zRight ? -1 : 0; + }else if( zRight==0 ){ + return 1; + } a = (unsigned char *)zLeft; b = (unsigned char *)zRight; while( *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; } return UpperToLower[*a] - UpperToLower[*b]; } -SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){ +SQLITE_API int SQLITE_STDCALL sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){ register unsigned char *a, *b; + if( zLeft==0 ){ + return zRight ? -1 : 0; + }else if( zRight==0 ){ + return 1; + } a = (unsigned char *)zLeft; b = (unsigned char *)zRight; while( N-- > 0 && *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; } return N<0 ? 0 : UpperToLower[*a] - UpperToLower[*b]; } @@ -21637,23 +23831,23 @@ testcase( c==(+1) ); } return c; } - /* -** Convert zNum to a 64-bit signed integer. +** Convert zNum to a 64-bit signed integer. zNum must be decimal. This +** routine does *not* accept hexadecimal notation. ** ** If the zNum value is representable as a 64-bit twos-complement ** integer, then write that value into *pNum and return 0. ** -** If zNum is exactly 9223372036854665808, return 2. This special -** case is broken out because while 9223372036854665808 cannot be a -** signed 64-bit integer, its negative -9223372036854665808 can be. +** If zNum is exactly 9223372036854775808, return 2. This special +** case is broken out because while 9223372036854775808 cannot be a +** signed 64-bit integer, its negative -9223372036854775808 can be. ** ** If zNum is too big for a 64-bit integer and is not -** 9223372036854665808 or if zNum contains any non-numeric text, +** 9223372036854775808 or if zNum contains any non-numeric text, ** then return 1. ** ** length is the number of bytes in the string (bytes, not characters). ** The string is not necessarily zero-terminated. The encoding is ** given by enc. @@ -21691,11 +23885,11 @@ while( zNum<zEnd && zNum[0]=='0' ){ zNum+=incr; } /* Skip leading zeros. */ for(i=0; &zNum[i]<zEnd && (c=zNum[i])>='0' && c<='9'; i+=incr){ u = u*10 + c - '0'; } if( u>LARGEST_INT64 ){ - *pNum = SMALLEST_INT64; + *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64; }else if( neg ){ *pNum = -(i64)u; }else{ *pNum = (i64)u; } @@ -21722,19 +23916,52 @@ return 1; }else{ /* zNum is exactly 9223372036854775808. Fits if negative. The ** special case 2 overflow if positive */ assert( u-1==LARGEST_INT64 ); - assert( (*pNum)==SMALLEST_INT64 ); return neg ? 0 : 2; } } } + +/* +** Transform a UTF-8 integer literal, in either decimal or hexadecimal, +** into a 64-bit signed integer. This routine accepts hexadecimal literals, +** whereas sqlite3Atoi64() does not. +** +** Returns: +** +** 0 Successful transformation. Fits in a 64-bit signed integer. +** 1 Integer too large for a 64-bit signed integer or is malformed +** 2 Special case of 9223372036854775808 +*/ +SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char *z, i64 *pOut){ +#ifndef SQLITE_OMIT_HEX_INTEGER + if( z[0]=='0' + && (z[1]=='x' || z[1]=='X') + && sqlite3Isxdigit(z[2]) + ){ + u64 u = 0; + int i, k; + for(i=2; z[i]=='0'; i++){} + for(k=i; sqlite3Isxdigit(z[k]); k++){ + u = u*16 + sqlite3HexToInt(z[k]); + } + memcpy(pOut, &u, 8); + return (z[k]==0 && k-i<=16) ? 0 : 1; + }else +#endif /* SQLITE_OMIT_HEX_INTEGER */ + { + return sqlite3Atoi64(z, pOut, sqlite3Strlen30(z), SQLITE_UTF8); + } +} /* ** If zNum represents an integer that will fit in 32-bits, then set ** *pValue to that integer and return true. Otherwise return false. +** +** This routine accepts both decimal and hexadecimal notation for integers. ** ** Any non-numeric characters that following zNum are ignored. ** This is different from sqlite3Atoi64() which requires the ** input number to be zero-terminated. */ @@ -21746,11 +23973,29 @@ neg = 1; zNum++; }else if( zNum[0]=='+' ){ zNum++; } - while( zNum[0]=='0' ) zNum++; +#ifndef SQLITE_OMIT_HEX_INTEGER + else if( zNum[0]=='0' + && (zNum[1]=='x' || zNum[1]=='X') + && sqlite3Isxdigit(zNum[2]) + ){ + u32 u = 0; + zNum += 2; + while( zNum[0]=='0' ) zNum++; + for(i=0; sqlite3Isxdigit(zNum[i]) && i<8; i++){ + u = u*16 + sqlite3HexToInt(zNum[i]); + } + if( (u&0x80000000)==0 && sqlite3Isxdigit(zNum[i])==0 ){ + memcpy(pValue, &u, 4); + return 1; + }else{ + return 0; + } + } +#endif for(i=0; i<11 && (c = zNum[i] - '0')>=0 && c<=9; i++){ v = v*10 + c; } /* The longest decimal representation of a 32 bit integer is 10 digits: @@ -21810,11 +24055,11 @@ ** A variable-length integer consists of the lower 7 bits of each byte ** for all bytes that have the 8th bit set and one byte with the 8th ** bit clear. Except, if we get to the 9th byte, it stores the full ** 8 bits and is the last byte. */ -SQLITE_PRIVATE int sqlite3PutVarint(unsigned char *p, u64 v){ +static int SQLITE_NOINLINE putVarint64(unsigned char *p, u64 v){ int i, j, n; u8 buf[10]; if( v & (((u64)0xff000000)<<32) ){ p[8] = (u8)v; v >>= 8; @@ -21834,32 +24079,21 @@ for(i=0, j=n-1; j>=0; j--, i++){ p[i] = buf[j]; } return n; } - -/* -** This routine is a faster version of sqlite3PutVarint() that only -** works for 32-bit positive integers and which is optimized for -** the common case of small integers. A MACRO version, putVarint32, -** is provided which inlines the single-byte case. All code should use -** the MACRO version as this function assumes the single-byte case has -** already been handled. -*/ -SQLITE_PRIVATE int sqlite3PutVarint32(unsigned char *p, u32 v){ -#ifndef putVarint32 - if( (v & ~0x7f)==0 ){ - p[0] = v; +SQLITE_PRIVATE int sqlite3PutVarint(unsigned char *p, u64 v){ + if( v<=0x7f ){ + p[0] = v&0x7f; return 1; } -#endif - if( (v & ~0x3fff)==0 ){ - p[0] = (u8)((v>>7) | 0x80); - p[1] = (u8)(v & 0x7f); + if( v<=0x3fff ){ + p[0] = ((v>>7)&0x7f)|0x80; + p[1] = v&0x7f; return 2; } - return sqlite3PutVarint(p, v); + return putVarint64(p,v); } /* ** Bitmasks used by sqlite3GetVarint(). These precomputed constants ** are defined here rather than simply putting the constant expressions @@ -22182,11 +24416,12 @@ /* ** Read or write a four-byte big-endian integer value. */ SQLITE_PRIVATE u32 sqlite3Get4byte(const u8 *p){ - return (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3]; + testcase( p[0]&0x80 ); + return ((unsigned)p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3]; } SQLITE_PRIVATE void sqlite3Put4byte(unsigned char *p, u32 v){ p[0] = (u8)(v>>24); p[1] = (u8)(v>>16); p[2] = (u8)(v>>8); @@ -22303,17 +24538,16 @@ testcase( iB==-1 ); testcase( iB==0 ); if( iB>=0 ){ testcase( iA>0 && LARGEST_INT64 - iA == iB ); testcase( iA>0 && LARGEST_INT64 - iA == iB - 1 ); if( iA>0 && LARGEST_INT64 - iA < iB ) return 1; - *pA += iB; }else{ testcase( iA<0 && -(iA + LARGEST_INT64) == iB + 1 ); testcase( iA<0 && -(iA + LARGEST_INT64) == iB + 2 ); if( iA<0 && -(iA + LARGEST_INT64) > iB + 1 ) return 1; - *pA += iB; } + *pA += iB; return 0; } SQLITE_PRIVATE int sqlite3SubInt64(i64 *pA, i64 iB){ testcase( iB==SMALLEST_INT64+1 ); if( iB==SMALLEST_INT64 ){ @@ -22333,13 +24567,22 @@ iA1 = iA/TWOPOWER32; iA0 = iA % TWOPOWER32; iB1 = iB/TWOPOWER32; iB0 = iB % TWOPOWER32; - if( iA1*iB1 != 0 ) return 1; - assert( iA1*iB0==0 || iA0*iB1==0 ); - r = iA1*iB0 + iA0*iB1; + if( iA1==0 ){ + if( iB1==0 ){ + *pA *= iB; + return 0; + } + r = iA0*iB1; + }else if( iB1==0 ){ + r = iA1*iB0; + }else{ + /* If both iA1 and iB1 are non-zero, overflow will result */ + return 1; + } testcase( r==(-TWOPOWER31)-1 ); testcase( r==(-TWOPOWER31) ); testcase( r==TWOPOWER31 ); testcase( r==TWOPOWER31-1 ); if( r<(-TWOPOWER31) || r>=TWOPOWER31 ) return 1; @@ -22387,10 +24630,89 @@ for(i=sz-1; i>0 && z[i]!='/' && z[i]!='.'; i--){} if( z[i]=='.' && ALWAYS(sz>i+4) ) memmove(&z[i+1], &z[sz-3], 4); } } #endif + +/* +** Find (an approximate) sum of two LogEst values. This computation is +** not a simple "+" operator because LogEst is stored as a logarithmic +** value. +** +*/ +SQLITE_PRIVATE LogEst sqlite3LogEstAdd(LogEst a, LogEst b){ + static const unsigned char x[] = { + 10, 10, /* 0,1 */ + 9, 9, /* 2,3 */ + 8, 8, /* 4,5 */ + 7, 7, 7, /* 6,7,8 */ + 6, 6, 6, /* 9,10,11 */ + 5, 5, 5, /* 12-14 */ + 4, 4, 4, 4, /* 15-18 */ + 3, 3, 3, 3, 3, 3, /* 19-24 */ + 2, 2, 2, 2, 2, 2, 2, /* 25-31 */ + }; + if( a>=b ){ + if( a>b+49 ) return a; + if( a>b+31 ) return a+1; + return a+x[a-b]; + }else{ + if( b>a+49 ) return b; + if( b>a+31 ) return b+1; + return b+x[b-a]; + } +} + +/* +** Convert an integer into a LogEst. In other words, compute an +** approximation for 10*log2(x). +*/ +SQLITE_PRIVATE LogEst sqlite3LogEst(u64 x){ + static LogEst a[] = { 0, 2, 3, 5, 6, 7, 8, 9 }; + LogEst y = 40; + if( x<8 ){ + if( x<2 ) return 0; + while( x<8 ){ y -= 10; x <<= 1; } + }else{ + while( x>255 ){ y += 40; x >>= 4; } + while( x>15 ){ y += 10; x >>= 1; } + } + return a[x&7] + y - 10; +} + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Convert a double into a LogEst +** In other words, compute an approximation for 10*log2(x). +*/ +SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double x){ + u64 a; + LogEst e; + assert( sizeof(x)==8 && sizeof(a)==8 ); + if( x<=1 ) return 0; + if( x<=2000000000 ) return sqlite3LogEst((u64)x); + memcpy(&a, &x, 8); + e = (a>>52) - 1022; + return e*10; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +/* +** Convert a LogEst into an integer. +*/ +SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){ + u64 n; + if( x<10 ) return 1; + n = x%10; + x /= 10; + if( n>=5 ) n -= 2; + else if( n>=1 ) n -= 1; + if( x>=3 ){ + return x>60 ? (u64)LARGEST_INT64 : (n+8)<<(x-3); + } + return (n+8)>>(3-x); +} /************** End of util.c ************************************************/ /************** Begin file hash.c ********************************************/ /* ** 2001 September 22 @@ -22443,16 +24765,15 @@ } /* ** The hashing function. */ -static unsigned int strHash(const char *z, int nKey){ - int h = 0; - assert( nKey>=0 ); - while( nKey > 0 ){ - h = (h<<3) ^ h ^ sqlite3UpperToLower[(unsigned char)*z++]; - nKey--; +static unsigned int strHash(const char *z){ + unsigned int h = 0; + unsigned char c; + while( (c = (unsigned char)*z++)!=0 ){ + h = (h<<3) ^ h ^ sqlite3UpperToLower[c]; } return h; } @@ -22520,40 +24841,45 @@ sqlite3_free(pH->ht); pH->ht = new_ht; pH->htsize = new_size = sqlite3MallocSize(new_ht)/sizeof(struct _ht); memset(new_ht, 0, new_size*sizeof(struct _ht)); for(elem=pH->first, pH->first=0; elem; elem = next_elem){ - unsigned int h = strHash(elem->pKey, elem->nKey) % new_size; + unsigned int h = strHash(elem->pKey) % new_size; next_elem = elem->next; insertElement(pH, &new_ht[h], elem); } return 1; } /* This function (for internal use only) locates an element in an -** hash table that matches the given key. The hash for this key has -** already been computed and is passed as the 4th parameter. +** hash table that matches the given key. The hash for this key is +** also computed and returned in the *pH parameter. */ -static HashElem *findElementGivenHash( +static HashElem *findElementWithHash( const Hash *pH, /* The pH to be searched */ const char *pKey, /* The key we are searching for */ - int nKey, /* Bytes in key (not counting zero terminator) */ - unsigned int h /* The hash for this key. */ + unsigned int *pHash /* Write the hash value here */ ){ HashElem *elem; /* Used to loop thru the element list */ int count; /* Number of elements left to test */ + unsigned int h; /* The computed hash */ if( pH->ht ){ - struct _ht *pEntry = &pH->ht[h]; + struct _ht *pEntry; + h = strHash(pKey) % pH->htsize; + pEntry = &pH->ht[h]; elem = pEntry->chain; count = pEntry->count; }else{ + h = 0; elem = pH->first; count = pH->count; } - while( count-- && ALWAYS(elem) ){ - if( elem->nKey==nKey && sqlite3StrNICmp(elem->pKey,pKey,nKey)==0 ){ + *pHash = h; + while( count-- ){ + assert( elem!=0 ); + if( sqlite3StrICmp(elem->pKey,pKey)==0 ){ return elem; } elem = elem->next; } return 0; @@ -22592,30 +24918,24 @@ sqlite3HashClear(pH); } } /* Attempt to locate an element of the hash table pH with a key -** that matches pKey,nKey. Return the data for this element if it is +** that matches pKey. Return the data for this element if it is ** found, or NULL if there is no match. */ -SQLITE_PRIVATE void *sqlite3HashFind(const Hash *pH, const char *pKey, int nKey){ +SQLITE_PRIVATE void *sqlite3HashFind(const Hash *pH, const char *pKey){ HashElem *elem; /* The element that matches key */ unsigned int h; /* A hash on key */ assert( pH!=0 ); assert( pKey!=0 ); - assert( nKey>=0 ); - if( pH->ht ){ - h = strHash(pKey, nKey) % pH->htsize; - }else{ - h = 0; - } - elem = findElementGivenHash(pH, pKey, nKey, h); + elem = findElementWithHash(pH, pKey, &h); return elem ? elem->data : 0; } -/* Insert an element into the hash table pH. The key is pKey,nKey +/* Insert an element into the hash table pH. The key is pKey ** and the data is "data". ** ** If no element exists with a matching key, then a new ** element is created and NULL is returned. ** @@ -22625,213 +24945,213 @@ ** the new data is returned and the hash table is unchanged. ** ** If the "data" parameter to this function is NULL, then the ** element corresponding to "key" is removed from the hash table. */ -SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, int nKey, void *data){ +SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){ unsigned int h; /* the hash of the key modulo hash table size */ HashElem *elem; /* Used to loop thru the element list */ HashElem *new_elem; /* New element added to the pH */ assert( pH!=0 ); assert( pKey!=0 ); - assert( nKey>=0 ); - if( pH->htsize ){ - h = strHash(pKey, nKey) % pH->htsize; - }else{ - h = 0; - } - elem = findElementGivenHash(pH,pKey,nKey,h); + elem = findElementWithHash(pH,pKey,&h); if( elem ){ void *old_data = elem->data; if( data==0 ){ removeElementGivenHash(pH,elem,h); }else{ elem->data = data; elem->pKey = pKey; - assert(nKey==elem->nKey); } return old_data; } if( data==0 ) return 0; new_elem = (HashElem*)sqlite3Malloc( sizeof(HashElem) ); if( new_elem==0 ) return data; new_elem->pKey = pKey; - new_elem->nKey = nKey; new_elem->data = data; pH->count++; if( pH->count>=10 && pH->count > 2*pH->htsize ){ if( rehash(pH, pH->count*2) ){ assert( pH->htsize>0 ); - h = strHash(pKey, nKey) % pH->htsize; + h = strHash(pKey) % pH->htsize; } } - if( pH->ht ){ - insertElement(pH, &pH->ht[h], new_elem); - }else{ - insertElement(pH, 0, new_elem); - } + insertElement(pH, pH->ht ? &pH->ht[h] : 0, new_elem); return 0; } /************** End of hash.c ************************************************/ /************** Begin file opcodes.c *****************************************/ /* Automatically generated. Do not edit */ /* See the mkopcodec.awk script for details. */ -#if !defined(SQLITE_OMIT_EXPLAIN) || !defined(NDEBUG) || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG) +#if !defined(SQLITE_OMIT_EXPLAIN) || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG) +#if defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) || defined(SQLITE_DEBUG) +# define OpHelp(X) "\0" X +#else +# define OpHelp(X) +#endif SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ static const char *const azName[] = { "?", - /* 1 */ "Function", - /* 2 */ "Savepoint", - /* 3 */ "AutoCommit", - /* 4 */ "Transaction", - /* 5 */ "SorterNext", - /* 6 */ "Prev", - /* 7 */ "Next", - /* 8 */ "AggStep", - /* 9 */ "Checkpoint", - /* 10 */ "JournalMode", - /* 11 */ "Vacuum", - /* 12 */ "VFilter", - /* 13 */ "VUpdate", - /* 14 */ "Goto", - /* 15 */ "Gosub", - /* 16 */ "Return", - /* 17 */ "Yield", - /* 18 */ "HaltIfNull", - /* 19 */ "Not", - /* 20 */ "Halt", - /* 21 */ "Integer", - /* 22 */ "Int64", - /* 23 */ "String", - /* 24 */ "Null", - /* 25 */ "Blob", - /* 26 */ "Variable", - /* 27 */ "Move", - /* 28 */ "Copy", - /* 29 */ "SCopy", - /* 30 */ "ResultRow", - /* 31 */ "CollSeq", - /* 32 */ "AddImm", - /* 33 */ "MustBeInt", - /* 34 */ "RealAffinity", - /* 35 */ "Permutation", - /* 36 */ "Compare", - /* 37 */ "Jump", - /* 38 */ "Once", - /* 39 */ "If", - /* 40 */ "IfNot", - /* 41 */ "Column", - /* 42 */ "Affinity", - /* 43 */ "MakeRecord", - /* 44 */ "Count", - /* 45 */ "ReadCookie", - /* 46 */ "SetCookie", - /* 47 */ "VerifyCookie", - /* 48 */ "OpenRead", - /* 49 */ "OpenWrite", - /* 50 */ "OpenAutoindex", - /* 51 */ "OpenEphemeral", - /* 52 */ "SorterOpen", - /* 53 */ "OpenPseudo", - /* 54 */ "Close", - /* 55 */ "SeekLt", - /* 56 */ "SeekLe", - /* 57 */ "SeekGe", - /* 58 */ "SeekGt", - /* 59 */ "Seek", - /* 60 */ "NotFound", - /* 61 */ "Found", - /* 62 */ "IsUnique", - /* 63 */ "NotExists", - /* 64 */ "Sequence", - /* 65 */ "NewRowid", - /* 66 */ "Insert", - /* 67 */ "InsertInt", - /* 68 */ "Or", - /* 69 */ "And", - /* 70 */ "Delete", - /* 71 */ "ResetCount", - /* 72 */ "SorterCompare", - /* 73 */ "IsNull", - /* 74 */ "NotNull", - /* 75 */ "Ne", - /* 76 */ "Eq", - /* 77 */ "Gt", - /* 78 */ "Le", - /* 79 */ "Lt", - /* 80 */ "Ge", - /* 81 */ "SorterData", - /* 82 */ "BitAnd", - /* 83 */ "BitOr", - /* 84 */ "ShiftLeft", - /* 85 */ "ShiftRight", - /* 86 */ "Add", - /* 87 */ "Subtract", - /* 88 */ "Multiply", - /* 89 */ "Divide", - /* 90 */ "Remainder", - /* 91 */ "Concat", - /* 92 */ "RowKey", - /* 93 */ "BitNot", - /* 94 */ "String8", - /* 95 */ "RowData", - /* 96 */ "Rowid", - /* 97 */ "NullRow", - /* 98 */ "Last", - /* 99 */ "SorterSort", - /* 100 */ "Sort", - /* 101 */ "Rewind", - /* 102 */ "SorterInsert", - /* 103 */ "IdxInsert", - /* 104 */ "IdxDelete", - /* 105 */ "IdxRowid", - /* 106 */ "IdxLT", - /* 107 */ "IdxGE", - /* 108 */ "Destroy", - /* 109 */ "Clear", - /* 110 */ "CreateIndex", - /* 111 */ "CreateTable", - /* 112 */ "ParseSchema", - /* 113 */ "LoadAnalysis", - /* 114 */ "DropTable", - /* 115 */ "DropIndex", - /* 116 */ "DropTrigger", - /* 117 */ "IntegrityCk", - /* 118 */ "RowSetAdd", - /* 119 */ "RowSetRead", - /* 120 */ "RowSetTest", - /* 121 */ "Program", - /* 122 */ "Param", - /* 123 */ "FkCounter", - /* 124 */ "FkIfZero", - /* 125 */ "MemMax", - /* 126 */ "IfPos", - /* 127 */ "IfNeg", - /* 128 */ "IfZero", - /* 129 */ "AggFinal", - /* 130 */ "Real", - /* 131 */ "IncrVacuum", - /* 132 */ "Expire", - /* 133 */ "TableLock", - /* 134 */ "VBegin", - /* 135 */ "VCreate", - /* 136 */ "VDestroy", - /* 137 */ "VOpen", - /* 138 */ "VColumn", - /* 139 */ "VNext", - /* 140 */ "VRename", - /* 141 */ "ToText", - /* 142 */ "ToBlob", - /* 143 */ "ToNumeric", - /* 144 */ "ToInt", - /* 145 */ "ToReal", - /* 146 */ "Pagecount", - /* 147 */ "MaxPgcnt", - /* 148 */ "Trace", - /* 149 */ "Noop", - /* 150 */ "Explain", + /* 1 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"), + /* 2 */ "Savepoint" OpHelp(""), + /* 3 */ "AutoCommit" OpHelp(""), + /* 4 */ "Transaction" OpHelp(""), + /* 5 */ "SorterNext" OpHelp(""), + /* 6 */ "PrevIfOpen" OpHelp(""), + /* 7 */ "NextIfOpen" OpHelp(""), + /* 8 */ "Prev" OpHelp(""), + /* 9 */ "Next" OpHelp(""), + /* 10 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 11 */ "Checkpoint" OpHelp(""), + /* 12 */ "JournalMode" OpHelp(""), + /* 13 */ "Vacuum" OpHelp(""), + /* 14 */ "VFilter" OpHelp("iplan=r[P3] zplan='P4'"), + /* 15 */ "VUpdate" OpHelp("data=r[P3@P2]"), + /* 16 */ "Goto" OpHelp(""), + /* 17 */ "Gosub" OpHelp(""), + /* 18 */ "Return" OpHelp(""), + /* 19 */ "Not" OpHelp("r[P2]= !r[P1]"), + /* 20 */ "InitCoroutine" OpHelp(""), + /* 21 */ "EndCoroutine" OpHelp(""), + /* 22 */ "Yield" OpHelp(""), + /* 23 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), + /* 24 */ "Halt" OpHelp(""), + /* 25 */ "Integer" OpHelp("r[P2]=P1"), + /* 26 */ "Int64" OpHelp("r[P2]=P4"), + /* 27 */ "String" OpHelp("r[P2]='P4' (len=P1)"), + /* 28 */ "Null" OpHelp("r[P2..P3]=NULL"), + /* 29 */ "SoftNull" OpHelp("r[P1]=NULL"), + /* 30 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), + /* 31 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), + /* 32 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), + /* 33 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), + /* 34 */ "SCopy" OpHelp("r[P2]=r[P1]"), + /* 35 */ "ResultRow" OpHelp("output=r[P1@P2]"), + /* 36 */ "CollSeq" OpHelp(""), + /* 37 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), + /* 38 */ "MustBeInt" OpHelp(""), + /* 39 */ "RealAffinity" OpHelp(""), + /* 40 */ "Cast" OpHelp("affinity(r[P1])"), + /* 41 */ "Permutation" OpHelp(""), + /* 42 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), + /* 43 */ "Jump" OpHelp(""), + /* 44 */ "Once" OpHelp(""), + /* 45 */ "If" OpHelp(""), + /* 46 */ "IfNot" OpHelp(""), + /* 47 */ "Column" OpHelp("r[P3]=PX"), + /* 48 */ "Affinity" OpHelp("affinity(r[P1@P2])"), + /* 49 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), + /* 50 */ "Count" OpHelp("r[P2]=count()"), + /* 51 */ "ReadCookie" OpHelp(""), + /* 52 */ "SetCookie" OpHelp(""), + /* 53 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), + /* 54 */ "OpenRead" OpHelp("root=P2 iDb=P3"), + /* 55 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), + /* 56 */ "OpenAutoindex" OpHelp("nColumn=P2"), + /* 57 */ "OpenEphemeral" OpHelp("nColumn=P2"), + /* 58 */ "SorterOpen" OpHelp(""), + /* 59 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"), + /* 60 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), + /* 61 */ "Close" OpHelp(""), + /* 62 */ "SeekLT" OpHelp("key=r[P3@P4]"), + /* 63 */ "SeekLE" OpHelp("key=r[P3@P4]"), + /* 64 */ "SeekGE" OpHelp("key=r[P3@P4]"), + /* 65 */ "SeekGT" OpHelp("key=r[P3@P4]"), + /* 66 */ "Seek" OpHelp("intkey=r[P2]"), + /* 67 */ "NoConflict" OpHelp("key=r[P3@P4]"), + /* 68 */ "NotFound" OpHelp("key=r[P3@P4]"), + /* 69 */ "Found" OpHelp("key=r[P3@P4]"), + /* 70 */ "NotExists" OpHelp("intkey=r[P3]"), + /* 71 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"), + /* 72 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"), + /* 73 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), + /* 74 */ "NewRowid" OpHelp("r[P2]=rowid"), + /* 75 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), + /* 76 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"), + /* 77 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"), + /* 78 */ "Ne" OpHelp("if r[P1]!=r[P3] goto P2"), + /* 79 */ "Eq" OpHelp("if r[P1]==r[P3] goto P2"), + /* 80 */ "Gt" OpHelp("if r[P1]>r[P3] goto P2"), + /* 81 */ "Le" OpHelp("if r[P1]<=r[P3] goto P2"), + /* 82 */ "Lt" OpHelp("if r[P1]<r[P3] goto P2"), + /* 83 */ "Ge" OpHelp("if r[P1]>=r[P3] goto P2"), + /* 84 */ "InsertInt" OpHelp("intkey=P3 data=r[P2]"), + /* 85 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"), + /* 86 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"), + /* 87 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<<r[P1]"), + /* 88 */ "ShiftRight" OpHelp("r[P3]=r[P2]>>r[P1]"), + /* 89 */ "Add" OpHelp("r[P3]=r[P1]+r[P2]"), + /* 90 */ "Subtract" OpHelp("r[P3]=r[P2]-r[P1]"), + /* 91 */ "Multiply" OpHelp("r[P3]=r[P1]*r[P2]"), + /* 92 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"), + /* 93 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"), + /* 94 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"), + /* 95 */ "Delete" OpHelp(""), + /* 96 */ "BitNot" OpHelp("r[P1]= ~r[P1]"), + /* 97 */ "String8" OpHelp("r[P2]='P4'"), + /* 98 */ "ResetCount" OpHelp(""), + /* 99 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), + /* 100 */ "SorterData" OpHelp("r[P2]=data"), + /* 101 */ "RowKey" OpHelp("r[P2]=key"), + /* 102 */ "RowData" OpHelp("r[P2]=data"), + /* 103 */ "Rowid" OpHelp("r[P2]=rowid"), + /* 104 */ "NullRow" OpHelp(""), + /* 105 */ "Last" OpHelp(""), + /* 106 */ "SorterSort" OpHelp(""), + /* 107 */ "Sort" OpHelp(""), + /* 108 */ "Rewind" OpHelp(""), + /* 109 */ "SorterInsert" OpHelp(""), + /* 110 */ "IdxInsert" OpHelp("key=r[P2]"), + /* 111 */ "IdxDelete" OpHelp("key=r[P2@P3]"), + /* 112 */ "IdxRowid" OpHelp("r[P2]=rowid"), + /* 113 */ "IdxLE" OpHelp("key=r[P3@P4]"), + /* 114 */ "IdxGT" OpHelp("key=r[P3@P4]"), + /* 115 */ "IdxLT" OpHelp("key=r[P3@P4]"), + /* 116 */ "IdxGE" OpHelp("key=r[P3@P4]"), + /* 117 */ "Destroy" OpHelp(""), + /* 118 */ "Clear" OpHelp(""), + /* 119 */ "ResetSorter" OpHelp(""), + /* 120 */ "CreateIndex" OpHelp("r[P2]=root iDb=P1"), + /* 121 */ "CreateTable" OpHelp("r[P2]=root iDb=P1"), + /* 122 */ "ParseSchema" OpHelp(""), + /* 123 */ "LoadAnalysis" OpHelp(""), + /* 124 */ "DropTable" OpHelp(""), + /* 125 */ "DropIndex" OpHelp(""), + /* 126 */ "DropTrigger" OpHelp(""), + /* 127 */ "IntegrityCk" OpHelp(""), + /* 128 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), + /* 129 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), + /* 130 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), + /* 131 */ "Program" OpHelp(""), + /* 132 */ "Param" OpHelp(""), + /* 133 */ "Real" OpHelp("r[P2]=P4"), + /* 134 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), + /* 135 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), + /* 136 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), + /* 137 */ "IfPos" OpHelp("if r[P1]>0 goto P2"), + /* 138 */ "IfNeg" OpHelp("r[P1]+=P3, if r[P1]<0 goto P2"), + /* 139 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]+=P3, goto P2"), + /* 140 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), + /* 141 */ "JumpZeroIncr" OpHelp("if (r[P1]++)==0 ) goto P2"), + /* 142 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), + /* 143 */ "IncrVacuum" OpHelp(""), + /* 144 */ "Expire" OpHelp(""), + /* 145 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), + /* 146 */ "VBegin" OpHelp(""), + /* 147 */ "VCreate" OpHelp(""), + /* 148 */ "VDestroy" OpHelp(""), + /* 149 */ "VOpen" OpHelp(""), + /* 150 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), + /* 151 */ "VNext" OpHelp(""), + /* 152 */ "VRename" OpHelp(""), + /* 153 */ "Pagecount" OpHelp(""), + /* 154 */ "MaxPgcnt" OpHelp(""), + /* 155 */ "Init" OpHelp("Start at P2"), + /* 156 */ "Noop" OpHelp(""), + /* 157 */ "Explain" OpHelp(""), }; return azName[i]; } #endif @@ -22907,48 +25227,10 @@ # else # define SQLITE_ENABLE_LOCKING_STYLE 0 # endif #endif -/* -** Define the OS_VXWORKS pre-processor macro to 1 if building on -** vxworks, or 0 otherwise. -*/ -#ifndef OS_VXWORKS -# if defined(__RTP__) || defined(_WRS_KERNEL) -# define OS_VXWORKS 1 -# else -# define OS_VXWORKS 0 -# endif -#endif - -/* -** These #defines should enable >2GB file support on Posix if the -** underlying operating system supports it. If the OS lacks -** large file support, these should be no-ops. -** -** Large file support can be disabled using the -DSQLITE_DISABLE_LFS switch -** on the compiler command line. This is necessary if you are compiling -** on a recent machine (ex: RedHat 7.2) but you want your code to work -** on an older machine (ex: RedHat 6.0). If you compile on RedHat 7.2 -** without this option, LFS is enable. But LFS does not exist in the kernel -** in RedHat 6.0, so the code won't work. Hence, for maximum binary -** portability you should omit LFS. -** -** The previous paragraph was written in 2005. (This paragraph is written -** on 2008-11-28.) These days, all Linux kernels support large files, so -** you should probably leave LFS enabled. But some embedded platforms might -** lack LFS in which case the SQLITE_DISABLE_LFS macro might still be useful. -*/ -#ifndef SQLITE_DISABLE_LFS -# define _LARGE_FILE 1 -# ifndef _FILE_OFFSET_BITS -# define _FILE_OFFSET_BITS 64 -# endif -# define _LARGEFILE_SOURCE 1 -#endif - /* ** standard include files. */ #include <sys/types.h> #include <sys/stat.h> @@ -22956,26 +25238,26 @@ #include <unistd.h> /* #include <time.h> */ #include <sys/time.h> #include <errno.h> #if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 -#include <sys/mman.h> +# include <sys/mman.h> #endif - #if SQLITE_ENABLE_LOCKING_STYLE # include <sys/ioctl.h> -# if OS_VXWORKS -# include <semaphore.h> -# include <limits.h> -# else -# include <sys/file.h> -# include <sys/param.h> -# endif +# include <sys/file.h> +# include <sys/param.h> #endif /* SQLITE_ENABLE_LOCKING_STYLE */ -#if defined(__APPLE__) || (SQLITE_ENABLE_LOCKING_STYLE && !OS_VXWORKS) +#if OS_VXWORKS +/* # include <sys/ioctl.h> */ +# include <semaphore.h> +# include <limits.h> +#endif /* OS_VXWORKS */ + +#if defined(__APPLE__) || SQLITE_ENABLE_LOCKING_STYLE # include <sys/mount.h> #endif #ifdef HAVE_UTIME # include <utime.h> @@ -23011,10 +25293,14 @@ /* ** Maximum supported path-length. */ #define MAX_PATHNAME 512 + +/* Always cast the getpid() return type for compatibility with +** kernel modules in VxWorks. */ +#define osGetpid(X) (pid_t)getpid() /* ** Only set the lastErrno if the error code is a real error and not ** a normal expected return code of SQLITE_BUSY or SQLITE_OK */ @@ -23096,10 +25382,16 @@ */ char aPadding[32]; #endif }; +/* This variable holds the process id (pid) from when the xRandomness() +** method was called. If xOpen() is called from a different process id, +** indicating that a fork() has occurred, the PRNG will be reset. +*/ +static pid_t randomnessPid = 0; + /* ** Allowed values for the unixFile.ctrlFlags bitmask: */ #define UNIXFILE_EXCL 0x01 /* Connections from one process only */ #define UNIXFILE_RDONLY 0x02 /* Connection is read only */ @@ -23111,11 +25403,12 @@ #endif #define UNIXFILE_PSOW 0x10 /* SQLITE_IOCAP_POWERSAFE_OVERWRITE */ #define UNIXFILE_DELETE 0x20 /* Delete on close */ #define UNIXFILE_URI 0x40 /* Filename might have query parameters */ #define UNIXFILE_NOLOCK 0x80 /* Do no file locking */ -#define UNIXFILE_WARNED 0x0100 /* verifyDbFile() warnings have been issued */ +#define UNIXFILE_WARNED 0x0100 /* verifyDbFile() warnings issued */ +#define UNIXFILE_BLOCK 0x0200 /* Next SHM lock might block */ /* ** Include code that is common to all os_*.c files */ /************** Include os_common.h in the middle of os_unix.c ***************/ @@ -23364,10 +25657,18 @@ # else # define HAVE_MREMAP 0 # endif #endif +/* +** Explicitly call the 64-bit version of lseek() on Android. Otherwise, lseek() +** is the 32-bit version, even if _FILE_OFFSET_BITS=64 is defined. +*/ +#ifdef __ANDROID__ +# define lseek lseek64 +#endif + /* ** Different Unix systems declare open() in different ways. Same use ** open(const char*,int,mode_t). Others use open(const char*,int,...). ** The difference is important when using a pointer to the function. ** @@ -23382,15 +25683,20 @@ ** On some systems, calls to fchown() will trigger a message in a security ** log if they come from non-root processes. So avoid calling fchown() if ** we are not running as root. */ static int posixFchown(int fd, uid_t uid, gid_t gid){ +#if OS_VXWORKS + return 0; +#else return geteuid() ? 0 : fchown(fd,uid,gid); +#endif } /* Forward reference */ static int openDirectory(const char*, int*); +static int unixGetpagesize(void); /* ** Many system calls are accessed through pointer-to-functions so that ** they may be overridden at runtime to facilitate fault injection during ** testing and sandboxing. The following array holds the names and pointers @@ -23508,10 +25814,13 @@ { "mremap", (sqlite3_syscall_ptr)mremap, 0 }, #else { "mremap", (sqlite3_syscall_ptr)0, 0 }, #endif #define osMremap ((void*(*)(void*,size_t,size_t,int,...))aSyscall[23].pCurrent) + { "getpagesize", (sqlite3_syscall_ptr)unixGetpagesize, 0 }, +#define osGetpagesize ((int(*)(void))aSyscall[24].pCurrent) + #endif }; /* End of the overrideable system calls */ /* @@ -23688,11 +25997,11 @@ #if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) /* ** Helper function for printing out trace information from debugging -** binaries. This returns the string represetation of the supplied +** binaries. This returns the string representation of the supplied ** integer lock-type. */ static const char *azFileLock(int eFileLock){ switch( eFileLock ){ case NO_LOCK: return "NONE"; @@ -23765,13 +26074,26 @@ #define osFcntl lockTrace #endif /* SQLITE_LOCK_TRACE */ /* ** Retry ftruncate() calls that fail due to EINTR +** +** All calls to ftruncate() within this file should be made through +** this wrapper. On the Android platform, bypassing the logic below +** could lead to a corrupt database. */ static int robust_ftruncate(int h, sqlite3_int64 sz){ int rc; +#ifdef __ANDROID__ + /* On Android, ftruncate() always uses 32-bit offsets, even if + ** _FILE_OFFSET_BITS=64 is defined. This means it is unsafe to attempt to + ** truncate a file to any size larger than 2GiB. Silently ignore any + ** such attempts. */ + if( sz>(sqlite3_int64)0x7FFFFFFF ){ + rc = SQLITE_OK; + }else +#endif do{ rc = osFtruncate(h,sz); }while( rc<0 && errno==EINTR ); return rc; } /* @@ -23821,20 +26143,10 @@ } /* else fall through */ case EPERM: return SQLITE_PERM; - /* EDEADLK is only possible if a call to fcntl(F_SETLKW) is made. And - ** this module never makes such a call. And the code in SQLite itself - ** asserts that SQLITE_IOERR_BLOCKED is never returned. For these reasons - ** this case is also commented out. If the system does set errno to EDEADLK, - ** the default SQLITE_IOERR_XXX code will be returned. */ -#if 0 - case EDEADLK: - return SQLITE_IOERR_BLOCKED; -#endif - #if EOPNOTSUPP!=ENOTSUP case EOPNOTSUPP: /* something went terribly awry, unless during file system support * introspection, in which it actually means what it says */ #endif @@ -24223,10 +26535,18 @@ if( osClose(h) ){ unixLogErrorAtLine(SQLITE_IOERR_CLOSE, "close", pFile ? pFile->zPath : 0, lineno); } } + +/* +** Set the pFile->lastErrno. Do this in a subroutine as that provides +** a convenient place to set a breakpoint. +*/ +static void storeLastErrno(unixFile *pFile, int error){ + pFile->lastErrno = error; +} /* ** Close all file descriptors accumuated in the unixInodeInfo->pUnused list. */ static void closePendingFds(unixFile *pFile){ @@ -24297,11 +26617,11 @@ ** create a unique name for the file. */ fd = pFile->h; rc = osFstat(fd, &statbuf); if( rc!=0 ){ - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); #ifdef EOVERFLOW if( pFile->lastErrno==EOVERFLOW ) return SQLITE_NOLFS; #endif return SQLITE_IOERR; } @@ -24318,16 +26638,16 @@ ** the first page of the database, no damage is done. */ if( statbuf.st_size==0 && (pFile->fsFlags & SQLITE_FSFLAGS_IS_MSDOS)!=0 ){ do{ rc = osWrite(fd, "S", 1); }while( rc<0 && errno==EINTR ); if( rc!=1 ){ - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); return SQLITE_IOERR; } rc = osFstat(fd, &statbuf); if( rc!=0 ){ - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); return SQLITE_IOERR; } } #endif @@ -24358,10 +26678,23 @@ pInode->nRef++; } *ppInode = pInode; return SQLITE_OK; } + +/* +** Return TRUE if pFile has been renamed or unlinked since it was first opened. +*/ +static int fileHasMoved(unixFile *pFile){ +#if OS_VXWORKS + return pFile->pInode!=0 && pFile->pId!=pFile->pInode->fileId.pId; +#else + struct stat buf; + return pFile->pInode!=0 && + (osStat(pFile->zPath, &buf)!=0 || buf.st_ino!=pFile->pInode->fileId.ino); +#endif +} /* ** Check a unixFile that is a database. Verify the following: ** @@ -24393,14 +26726,11 @@ if( buf.st_nlink>1 ){ sqlite3_log(SQLITE_WARNING, "multiple links to file: %s", pFile->zPath); pFile->ctrlFlags |= UNIXFILE_WARNED; return; } - if( pFile->pInode!=0 - && ((rc = osStat(pFile->zPath, &buf))!=0 - || buf.st_ino!=pFile->pInode->fileId.ino) - ){ + if( fileHasMoved(pFile) ){ sqlite3_log(SQLITE_WARNING, "file renamed while open: %s", pFile->zPath); pFile->ctrlFlags |= UNIXFILE_WARNED; return; } } @@ -24436,11 +26766,11 @@ lock.l_start = RESERVED_BYTE; lock.l_len = 1; lock.l_type = F_WRLCK; if( osFcntl(pFile->h, F_GETLK, &lock) ){ rc = SQLITE_IOERR_CHECKRESERVEDLOCK; - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); } else if( lock.l_type!=F_UNLCK ){ reserved = 1; } } #endif @@ -24569,11 +26899,12 @@ int tErrno = 0; assert( pFile ); OSTRACE(("LOCK %d %s was %s(%s,%d) pid=%d (unix)\n", pFile->h, azFileLock(eFileLock), azFileLock(pFile->eFileLock), - azFileLock(pFile->pInode->eFileLock), pFile->pInode->nShared , getpid())); + azFileLock(pFile->pInode->eFileLock), pFile->pInode->nShared, + osGetpid(0))); /* If there is already a lock of this type or more restrictive on the ** unixFile, do nothing. Don't use the end_lock: exit path, as ** unixEnterMutex() hasn't been called yet. */ @@ -24636,11 +26967,11 @@ lock.l_start = PENDING_BYTE; if( unixFileLock(pFile, &lock) ){ tErrno = errno; rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); if( rc!=SQLITE_BUSY ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } goto end_lock; } } @@ -24671,11 +27002,11 @@ rc = SQLITE_IOERR_UNLOCK; } if( rc ){ if( rc!=SQLITE_BUSY ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } goto end_lock; }else{ pFile->eFileLock = SHARED_LOCK; pInode->nLock++; @@ -24704,11 +27035,11 @@ if( unixFileLock(pFile, &lock) ){ tErrno = errno; rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); if( rc!=SQLITE_BUSY ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } } } @@ -24777,11 +27108,11 @@ int rc = SQLITE_OK; assert( pFile ); OSTRACE(("UNLOCK %d %d was %d(%d,%d) pid=%d (unix)\n", pFile->h, eFileLock, pFile->eFileLock, pFile->pInode->eFileLock, pFile->pInode->nShared, - getpid())); + osGetpid(0))); assert( eFileLock<=SHARED_LOCK ); if( pFile->eFileLock<=eFileLock ){ return SQLITE_OK; } @@ -24811,11 +27142,10 @@ ** 2: [....W] ** 3: [RRRRW] ** 4: [RRRR.] */ if( eFileLock==SHARED_LOCK ){ - #if !defined(__APPLE__) || !SQLITE_ENABLE_LOCKING_STYLE (void)handleNFSUnlock; assert( handleNFSUnlock==0 ); #endif #if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE @@ -24829,11 +27159,11 @@ lock.l_len = divSize; if( unixFileLock(pFile, &lock)==(-1) ){ tErrno = errno; rc = SQLITE_IOERR_UNLOCK; if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } goto end_unlock; } lock.l_type = F_RDLCK; lock.l_whence = SEEK_SET; @@ -24841,11 +27171,11 @@ lock.l_len = divSize; if( unixFileLock(pFile, &lock)==(-1) ){ tErrno = errno; rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_RDLOCK); if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } goto end_unlock; } lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; @@ -24853,11 +27183,11 @@ lock.l_len = SHARED_SIZE-divSize; if( unixFileLock(pFile, &lock)==(-1) ){ tErrno = errno; rc = SQLITE_IOERR_UNLOCK; if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } goto end_unlock; } }else #endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */ @@ -24872,11 +27202,11 @@ ** indicates that the other process is not following the locking ** protocol. If this happens, return SQLITE_IOERR_RDLOCK. Returning ** SQLITE_BUSY would confuse the upper layer (in practice it causes ** an assert to fail). */ rc = SQLITE_IOERR_RDLOCK; - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); goto end_unlock; } } } lock.l_type = F_UNLCK; @@ -24885,11 +27215,11 @@ lock.l_len = 2L; assert( PENDING_BYTE+1==RESERVED_BYTE ); if( unixFileLock(pFile, &lock)==0 ){ pInode->eFileLock = SHARED_LOCK; }else{ rc = SQLITE_IOERR_UNLOCK; - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); goto end_unlock; } } if( eFileLock==NO_LOCK ){ /* Decrement the shared lock counter. Release the lock using an @@ -24903,11 +27233,11 @@ lock.l_start = lock.l_len = 0L; if( unixFileLock(pFile, &lock)==0 ){ pInode->eFileLock = NO_LOCK; }else{ rc = SQLITE_IOERR_UNLOCK; - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); pInode->eFileLock = NO_LOCK; pFile->eFileLock = NO_LOCK; } } @@ -24934,11 +27264,13 @@ ** ** If the locking level of the file descriptor is already at or below ** the requested locking level, this routine is a no-op. */ static int unixUnlock(sqlite3_file *id, int eFileLock){ +#if SQLITE_MAX_MMAP_SIZE>0 assert( eFileLock==SHARED_LOCK || ((unixFile *)id)->nFetchOut==0 ); +#endif return posixUnlock(id, eFileLock, 0); } #if SQLITE_MAX_MMAP_SIZE>0 static int unixMapfile(unixFile *pFd, i64 nByte); @@ -24970,10 +27302,17 @@ osUnlink(pFile->pId->zCanonicalName); } vxworksReleaseFileId(pFile->pId); pFile->pId = 0; } +#endif +#ifdef SQLITE_UNLINK_AFTER_CLOSE + if( pFile->ctrlFlags & UNIXFILE_DELETE ){ + osUnlink(pFile->zPath); + sqlite3_free(*(char**)&pFile->zPath); + pFile->zPath = 0; + } #endif OSTRACE(("CLOSE %-3d\n", pFile->h)); OpenCounter(-1); sqlite3_free(pFile->pUnused); memset(pFile, 0, sizeof(unixFile)); @@ -25169,11 +27508,11 @@ if( EEXIST == tErrno ){ rc = SQLITE_BUSY; } else { rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } } return rc; } @@ -25196,11 +27535,11 @@ char *zLockFile = (char *)pFile->lockingContext; int rc; assert( pFile ); OSTRACE(("UNLOCK %d %d was %d pid=%d (dotlock)\n", pFile->h, eFileLock, - pFile->eFileLock, getpid())); + pFile->eFileLock, osGetpid(0))); assert( eFileLock<=SHARED_LOCK ); /* no-op if possible */ if( pFile->eFileLock==eFileLock ){ return SQLITE_OK; @@ -25223,11 +27562,11 @@ rc = 0; if( ENOENT != tErrno ){ rc = SQLITE_IOERR_UNLOCK; } if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } return rc; } pFile->eFileLock = NO_LOCK; return SQLITE_OK; @@ -25259,14 +27598,13 @@ ** a single exclusive lock. In other words, SHARED, RESERVED, and ** PENDING locks are the same thing as an EXCLUSIVE lock. SQLite ** still works when you do this, but concurrency is reduced since ** only a single process can be reading the database at a time. ** -** Omit this section if SQLITE_ENABLE_LOCKING_STYLE is turned off or if -** compiling for VXWORKS. +** Omit this section if SQLITE_ENABLE_LOCKING_STYLE is turned off */ -#if SQLITE_ENABLE_LOCKING_STYLE && !OS_VXWORKS +#if SQLITE_ENABLE_LOCKING_STYLE /* ** Retry flock() calls that fail with EINTR */ #ifdef EINTR @@ -25310,21 +27648,21 @@ if ( lrc ) { int tErrno = errno; /* unlock failed with an error */ lrc = SQLITE_IOERR_UNLOCK; if( IS_LOCK_ERROR(lrc) ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); rc = lrc; } } } else { int tErrno = errno; reserved = 1; /* someone else might have it reserved */ lrc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); if( IS_LOCK_ERROR(lrc) ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); rc = lrc; } } } OSTRACE(("TEST WR-LOCK %d %d %d (flock)\n", pFile->h, rc, reserved)); @@ -25386,11 +27724,11 @@ if (robust_flock(pFile->h, LOCK_EX | LOCK_NB)) { int tErrno = errno; /* didn't get, must be busy */ rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } } else { /* got it, set the type and return ok */ pFile->eFileLock = eFileLock; } @@ -25415,11 +27753,11 @@ static int flockUnlock(sqlite3_file *id, int eFileLock) { unixFile *pFile = (unixFile*)id; assert( pFile ); OSTRACE(("UNLOCK %d %d was %d pid=%d (flock)\n", pFile->h, eFileLock, - pFile->eFileLock, getpid())); + pFile->eFileLock, osGetpid(0))); assert( eFileLock<=SHARED_LOCK ); /* no-op if possible */ if( pFile->eFileLock==eFileLock ){ return SQLITE_OK; @@ -25476,11 +27814,11 @@ ** This routine checks if there is a RESERVED lock held on the specified ** file by this or any other process. If such a lock is held, set *pResOut ** to a non-zero value otherwise *pResOut is set to zero. The return value ** is set to SQLITE_OK unless an I/O error occurs during lock checking. */ -static int semCheckReservedLock(sqlite3_file *id, int *pResOut) { +static int semXCheckReservedLock(sqlite3_file *id, int *pResOut) { int rc = SQLITE_OK; int reserved = 0; unixFile *pFile = (unixFile*)id; SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); @@ -25493,17 +27831,16 @@ } /* Otherwise see if some other process holds it. */ if( !reserved ){ sem_t *pSem = pFile->pInode->pSem; - struct stat statBuf; if( sem_trywait(pSem)==-1 ){ int tErrno = errno; if( EAGAIN != tErrno ){ rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_CHECKRESERVEDLOCK); - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } else { /* someone else has the lock when we are in NO_LOCK */ reserved = (pFile->eFileLock < SHARED_LOCK); } }else{ @@ -25544,13 +27881,12 @@ ** access the file. ** ** This routine will only increase a lock. Use the sqlite3OsUnlock() ** routine to lower a locking level. */ -static int semLock(sqlite3_file *id, int eFileLock) { +static int semXLock(sqlite3_file *id, int eFileLock) { unixFile *pFile = (unixFile*)id; - int fd; sem_t *pSem = pFile->pInode->pSem; int rc = SQLITE_OK; /* if we already have a lock, it is exclusive. ** Just adjust level and punt on outta here. */ @@ -25578,18 +27914,18 @@ ** must be either NO_LOCK or SHARED_LOCK. ** ** If the locking level of the file descriptor is already at or below ** the requested locking level, this routine is a no-op. */ -static int semUnlock(sqlite3_file *id, int eFileLock) { +static int semXUnlock(sqlite3_file *id, int eFileLock) { unixFile *pFile = (unixFile*)id; sem_t *pSem = pFile->pInode->pSem; assert( pFile ); assert( pSem ); OSTRACE(("UNLOCK %d %d was %d pid=%d (sem)\n", pFile->h, eFileLock, - pFile->eFileLock, getpid())); + pFile->eFileLock, osGetpid(0))); assert( eFileLock<=SHARED_LOCK ); /* no-op if possible */ if( pFile->eFileLock==eFileLock ){ return SQLITE_OK; @@ -25604,11 +27940,11 @@ /* no, really unlock. */ if ( sem_post(pSem)==-1 ) { int rc, tErrno = errno; rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } return rc; } pFile->eFileLock = NO_LOCK; return SQLITE_OK; @@ -25615,14 +27951,14 @@ } /* ** Close a file. */ -static int semClose(sqlite3_file *id) { +static int semXClose(sqlite3_file *id) { if( id ){ unixFile *pFile = (unixFile*)id; - semUnlock(id, NO_LOCK); + semXUnlock(id, NO_LOCK); assert( pFile ); unixEnterMutex(); releaseInodeInfo(pFile); unixLeaveMutex(); closeUnixFile(id); @@ -25706,11 +28042,11 @@ #else rc = sqliteErrorFromPosixError(tErrno, setLockFlag ? SQLITE_IOERR_LOCK : SQLITE_IOERR_UNLOCK); #endif /* SQLITE_IGNORE_AFP_LOCK_ERRORS */ if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; + storeLastErrno(pFile, tErrno); } return rc; } else { return SQLITE_OK; } @@ -25799,11 +28135,11 @@ afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; assert( pFile ); OSTRACE(("LOCK %d %s was %s(%s,%d) pid=%d (afp)\n", pFile->h, azFileLock(eFileLock), azFileLock(pFile->eFileLock), - azFileLock(pInode->eFileLock), pInode->nShared , getpid())); + azFileLock(pInode->eFileLock), pInode->nShared , osGetpid(0))); /* If there is already a lock of this type or more restrictive on the ** unixFile, do nothing. Don't use the afp_end_lock: exit path, as ** unixEnterMutex() hasn't been called yet. */ @@ -25889,11 +28225,11 @@ } /* Drop the temporary PENDING lock */ lrc2 = afpSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 0); if( IS_LOCK_ERROR(lrc1) ) { - pFile->lastErrno = lrc1Errno; + storeLastErrno(pFile, lrc1Errno); rc = lrc1; goto afp_end_lock; } else if( IS_LOCK_ERROR(lrc2) ){ rc = lrc2; goto afp_end_lock; @@ -25985,11 +28321,11 @@ #endif assert( pFile ); OSTRACE(("UNLOCK %d %d was %d(%d,%d) pid=%d (afp)\n", pFile->h, eFileLock, pFile->eFileLock, pFile->pInode->eFileLock, pFile->pInode->nShared, - getpid())); + osGetpid(0))); assert( eFileLock<=SHARED_LOCK ); if( pFile->eFileLock<=eFileLock ){ return SQLITE_OK; } @@ -26148,11 +28484,11 @@ ** bytes into pBuf. Return the number of bytes actually read. ** ** NB: If you define USE_PREAD or USE_PREAD64, then it might also ** be necessary to define _XOPEN_SOURCE to be 500. This varies from ** one system to another. Since SQLite does not define USE_PREAD -** any any form by default, we will not attempt to define _XOPEN_SOURCE. +** in any form by default, we will not attempt to define _XOPEN_SOURCE. ** See tickets #2741 and #2681. ** ** To avoid stomping the errno value on a failed read the lastErrno value ** is set before returning. */ @@ -26176,23 +28512,23 @@ #else newOffset = lseek(id->h, offset, SEEK_SET); SimulateIOError( newOffset-- ); if( newOffset!=offset ){ if( newOffset == -1 ){ - ((unixFile*)id)->lastErrno = errno; + storeLastErrno((unixFile*)id, errno); }else{ - ((unixFile*)id)->lastErrno = 0; + storeLastErrno((unixFile*)id, 0); } return -1; } got = osRead(id->h, pBuf, cnt); #endif if( got==cnt ) break; if( got<0 ){ if( errno==EINTR ){ got = 1; continue; } prior = 0; - ((unixFile*)id)->lastErrno = errno; + storeLastErrno((unixFile*)id, errno); break; }else if( got>0 ){ cnt -= got; offset += got; prior += got; @@ -26253,11 +28589,11 @@ return SQLITE_OK; }else if( got<0 ){ /* lastErrno set by seekAndRead */ return SQLITE_IOERR_READ; }else{ - pFile->lastErrno = 0; /* not a system error */ + storeLastErrno(pFile, 0); /* not a system error */ /* Unread parts of the buffer must be zero-filled */ memset(&((char*)pBuf)[got], 0, amt-got); return SQLITE_IOERR_SHORT_READ; } } @@ -26282,13 +28618,13 @@ assert( fd>2 ); nBuf &= 0x1ffff; TIMER_START; #if defined(USE_PREAD) - do{ rc = osPwrite(fd, pBuf, nBuf, iOff); }while( rc<0 && errno==EINTR ); + do{ rc = (int)osPwrite(fd, pBuf, nBuf, iOff); }while( rc<0 && errno==EINTR ); #elif defined(USE_PREAD64) - do{ rc = osPwrite64(fd, pBuf, nBuf, iOff);}while( rc<0 && errno==EINTR); + do{ rc = (int)osPwrite64(fd, pBuf, nBuf, iOff);}while( rc<0 && errno==EINTR); #else do{ i64 iSeek = lseek(fd, iOff, SEEK_SET); SimulateIOError( iSeek-- ); @@ -26394,11 +28730,11 @@ if( amt>0 ){ if( wrote<0 && pFile->lastErrno!=ENOSPC ){ /* lastErrno set by seekAndWrite */ return SQLITE_IOERR_WRITE; }else{ - pFile->lastErrno = 0; /* not a system error */ + storeLastErrno(pFile, 0); /* not a system error */ return SQLITE_FULL; } } return SQLITE_OK; @@ -26415,13 +28751,13 @@ /* ** We do not trust systems to provide a working fdatasync(). Some do. ** Others do no. To be safe, we will stick with the (slightly slower) ** fsync(). If you know that your system does support fdatasync() correctly, -** then simply compile with -Dfdatasync=fdatasync +** then simply compile with -Dfdatasync=fdatasync or -DHAVE_FDATASYNC */ -#if !defined(fdatasync) +#if !defined(fdatasync) && !HAVE_FDATASYNC # define fdatasync fsync #endif /* ** Define HAVE_FULLFSYNC to 0 or 1 depending on whether or not @@ -26603,11 +28939,11 @@ assert( pFile ); OSTRACE(("SYNC %-3d\n", pFile->h)); rc = full_fsync(pFile->h, isFullsync, isDataOnly); SimulateIOError( rc=1 ); if( rc ){ - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); return unixLogError(SQLITE_IOERR_FSYNC, "full_fsync", pFile->zPath); } /* Also fsync the directory containing the file if the DIRSYNC flag ** is set. This is a one-time occurrence. Many systems (examples: AIX) @@ -26645,13 +28981,13 @@ */ if( pFile->szChunk>0 ){ nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk; } - rc = robust_ftruncate(pFile->h, (off_t)nByte); + rc = robust_ftruncate(pFile->h, nByte); if( rc ){ - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); return unixLogError(SQLITE_IOERR_TRUNCATE, "ftruncate", pFile->zPath); }else{ #ifdef SQLITE_DEBUG /* If we are doing a normal write to a database file (as opposed to ** doing a hot-journal rollback or a write to some file other than a @@ -26687,11 +29023,11 @@ struct stat buf; assert( id ); rc = osFstat(((unixFile*)id)->h, &buf); SimulateIOError( rc=1 ); if( rc!=0 ){ - ((unixFile*)id)->lastErrno = errno; + storeLastErrno((unixFile*)id, errno); return SQLITE_IOERR_FSTAT; } *pSize = buf.st_size; /* When opening a zero-size database, the findInodeInfo() procedure @@ -26723,11 +29059,13 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){ if( pFile->szChunk>0 ){ i64 nSize; /* Required file size */ struct stat buf; /* Used to hold return values of fstat() */ - if( osFstat(pFile->h, &buf) ) return SQLITE_IOERR_FSTAT; + if( osFstat(pFile->h, &buf) ){ + return SQLITE_IOERR_FSTAT; + } nSize = ((nByte+pFile->szChunk-1) / pFile->szChunk) * pFile->szChunk; if( nSize>(i64)buf.st_size ){ #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE @@ -26738,28 +29076,32 @@ do{ err = osFallocate(pFile->h, buf.st_size, nSize-buf.st_size); }while( err==EINTR ); if( err ) return SQLITE_IOERR_WRITE; #else - /* If the OS does not have posix_fallocate(), fake it. First use - ** ftruncate() to set the file size, then write a single byte to - ** the last byte in each block within the extended region. This - ** is the same technique used by glibc to implement posix_fallocate() - ** on systems that do not have a real fallocate() system call. + /* If the OS does not have posix_fallocate(), fake it. Write a + ** single byte to the last byte in each block that falls entirely + ** within the extended region. Then, if required, a single byte + ** at offset (nSize-1), to set the size of the file correctly. + ** This is a similar technique to that used by glibc on systems + ** that do not have a real fallocate() call. */ int nBlk = buf.st_blksize; /* File-system block size */ + int nWrite = 0; /* Number of bytes written by seekAndWrite */ i64 iWrite; /* Next offset to write to */ - if( robust_ftruncate(pFile->h, nSize) ){ - pFile->lastErrno = errno; - return unixLogError(SQLITE_IOERR_TRUNCATE, "ftruncate", pFile->zPath); - } iWrite = ((buf.st_size + 2*nBlk - 1)/nBlk)*nBlk-1; - while( iWrite<nSize ){ - int nWrite = seekAndWrite(pFile, iWrite, "", 1); + assert( iWrite>=buf.st_size ); + assert( (iWrite/nBlk)==((buf.st_size+nBlk-1)/nBlk) ); + assert( ((iWrite+1)%nBlk)==0 ); + for(/*no-op*/; iWrite<nSize; iWrite+=nBlk ){ + nWrite = seekAndWrite(pFile, iWrite, "", 1); if( nWrite!=1 ) return SQLITE_IOERR_WRITE; - iWrite += nBlk; + } + if( nWrite==0 || (nSize%nBlk) ){ + nWrite = seekAndWrite(pFile, nSize-1, "", 1); + if( nWrite!=1 ) return SQLITE_IOERR_WRITE; } #endif } } @@ -26766,11 +29108,11 @@ #if SQLITE_MAX_MMAP_SIZE>0 if( pFile->mmapSizeMax>0 && nByte>pFile->mmapSize ){ int rc; if( pFile->szChunk<=0 ){ if( robust_ftruncate(pFile->h, nByte) ){ - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); return unixLogError(SQLITE_IOERR_TRUNCATE, "ftruncate", pFile->zPath); } } rc = unixMapfile(pFile, nByte); @@ -26780,11 +29122,11 @@ return SQLITE_OK; } /* -** If *pArg is inititially negative then this is a query. Set *pArg to +** If *pArg is initially negative then this is a query. Set *pArg to ** 1 or 0 depending on whether or not bit mask of pFile->ctrlFlags is set. ** ** If *pArg is 0 or 1, then clear or set the mask bit of pFile->ctrlFlags. */ static void unixModeBit(unixFile *pFile, unsigned char mask, int *pArg){ @@ -26804,15 +29146,19 @@ ** Information and control of an open file handle. */ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ unixFile *pFile = (unixFile*)id; switch( op ){ + case SQLITE_FCNTL_WAL_BLOCK: { + pFile->ctrlFlags |= UNIXFILE_BLOCK; + return SQLITE_OK; + } case SQLITE_FCNTL_LOCKSTATE: { *(int*)pArg = pFile->eFileLock; return SQLITE_OK; } - case SQLITE_LAST_ERRNO: { + case SQLITE_FCNTL_LAST_ERRNO: { *(int*)pArg = pFile->lastErrno; return SQLITE_OK; } case SQLITE_FCNTL_CHUNK_SIZE: { pFile->szChunk = *(int *)pArg; @@ -26842,10 +29188,14 @@ if( zTFile ){ unixGetTempname(pFile->pVfs->mxPathname, zTFile); *(char**)pArg = zTFile; } return SQLITE_OK; + } + case SQLITE_FCNTL_HAS_MOVED: { + *(int*)pArg = fileHasMoved(pFile); + return SQLITE_OK; } #if SQLITE_MAX_MMAP_SIZE>0 case SQLITE_FCNTL_MMAP_SIZE: { i64 newLimit = *(i64*)pArg; int rc = SQLITE_OK; @@ -26873,12 +29223,12 @@ ((unixFile*)id)->dbUpdate = 0; return SQLITE_OK; } #endif #if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) - case SQLITE_SET_LOCKPROXYFILE: - case SQLITE_GET_LOCKPROXYFILE: { + case SQLITE_FCNTL_SET_LOCKPROXYFILE: + case SQLITE_FCNTL_GET_LOCKPROXYFILE: { return proxyFileControl(id,op,pArg); } #endif /* SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) */ } return SQLITE_NOTFOUND; @@ -26983,11 +29333,11 @@ /* ** Return the device characteristics for the file. ** ** This VFS is set up to return SQLITE_IOCAP_POWERSAFE_OVERWRITE by default. -** However, that choice is contraversial since technically the underlying +** However, that choice is controversial since technically the underlying ** file system does not always provide powersafe overwrites. (In other ** words, after a power-loss event, parts of the file that were never ** written might end up being altered.) However, non-PSOW behavior is very, ** very rare. And asserting PSOW makes a large reduction in the amount ** of required I/O for journaling, since a lot of padding is eliminated. @@ -27005,12 +29355,31 @@ rc |= SQLITE_IOCAP_POWERSAFE_OVERWRITE; } return rc; } +#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 + +/* +** Return the system page size. +** +** This function should not be called directly by other code in this file. +** Instead, it should be called via macro osGetpagesize(). +*/ +static int unixGetpagesize(void){ +#if OS_VXWORKS + return 1024; +#elif defined(_BSD_SOURCE) + return getpagesize(); +#else + return (int)sysconf(_SC_PAGESIZE); +#endif +} + +#endif /* !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 */ + #ifndef SQLITE_OMIT_WAL - /* ** Object used to represent an shared memory buffer. ** ** When multiple threads all reference the same wal-index, each thread @@ -27090,44 +29459,49 @@ ** ** Locks block if the mask is exactly UNIX_SHM_C and are non-blocking ** otherwise. */ static int unixShmSystemLock( - unixShmNode *pShmNode, /* Apply locks to this open shared-memory segment */ + unixFile *pFile, /* Open connection to the WAL file */ int lockType, /* F_UNLCK, F_RDLCK, or F_WRLCK */ int ofst, /* First byte of the locking range */ int n /* Number of bytes to lock */ ){ - struct flock f; /* The posix advisory locking structure */ - int rc = SQLITE_OK; /* Result code form fcntl() */ + unixShmNode *pShmNode; /* Apply locks to this open shared-memory segment */ + struct flock f; /* The posix advisory locking structure */ + int rc = SQLITE_OK; /* Result code form fcntl() */ /* Access to the unixShmNode object is serialized by the caller */ + pShmNode = pFile->pInode->pShmNode; assert( sqlite3_mutex_held(pShmNode->mutex) || pShmNode->nRef==0 ); /* Shared locks never span more than one byte */ assert( n==1 || lockType!=F_RDLCK ); /* Locks are within range */ assert( n>=1 && n<SQLITE_SHM_NLOCK ); if( pShmNode->h>=0 ){ + int lkType; /* Initialize the locking parameters */ memset(&f, 0, sizeof(f)); f.l_type = lockType; f.l_whence = SEEK_SET; f.l_start = ofst; f.l_len = n; - rc = osFcntl(pShmNode->h, F_SETLK, &f); + lkType = (pFile->ctrlFlags & UNIXFILE_BLOCK)!=0 ? F_SETLKW : F_SETLK; + rc = osFcntl(pShmNode->h, lkType, &f); rc = (rc!=(-1)) ? SQLITE_OK : SQLITE_BUSY; + pFile->ctrlFlags &= ~UNIXFILE_BLOCK; } /* Update the global lock state and do debug tracing */ #ifdef SQLITE_DEBUG { u16 mask; OSTRACE(("SHM-LOCK ")); - mask = (1<<(ofst+n)) - (1<<ofst); + mask = ofst>31 ? 0xffff : (1<<(ofst+n)) - (1<<ofst); if( rc==SQLITE_OK ){ if( lockType==F_UNLCK ){ OSTRACE(("unlock %d ok", ofst)); pShmNode->exclMask &= ~mask; pShmNode->sharedMask &= ~mask; @@ -27157,10 +29531,26 @@ #endif return rc; } +/* +** Return the minimum number of 32KB shm regions that should be mapped at +** a time, assuming that each mapping must be an integer multiple of the +** current system page-size. +** +** Usually, this is 1. The exception seems to be systems that are configured +** to use 64KB pages - in this case each mapping must cover at least two +** shm regions. +*/ +static int unixShmRegionPerMap(void){ + int shmsz = 32*1024; /* SHM region size */ + int pgsz = osGetpagesize(); /* System page size */ + assert( ((pgsz-1)&pgsz)==0 ); /* Page size must be a power of 2 */ + if( pgsz<shmsz ) return 1; + return pgsz/shmsz; +} /* ** Purge the unixShmNodeList list of all entries with unixShmNode.nRef==0. ** ** This is not a VFS shared-memory method; it is a utility function called @@ -27168,14 +29558,15 @@ */ static void unixShmPurge(unixFile *pFd){ unixShmNode *p = pFd->pInode->pShmNode; assert( unixMutexHeld() ); if( p && p->nRef==0 ){ + int nShmPerMap = unixShmRegionPerMap(); int i; assert( p->pInode==pFd->pInode ); sqlite3_mutex_free(p->mutex); - for(i=0; i<p->nRegion; i++){ + for(i=0; i<p->nRegion; i+=nShmPerMap){ if( p->h>=0 ){ osMunmap(p->apRegion[i], p->szRegion); }else{ sqlite3_free(p->apRegion[i]); } @@ -27245,10 +29636,13 @@ unixEnterMutex(); pInode = pDbFd->pInode; pShmNode = pInode->pShmNode; if( pShmNode==0 ){ struct stat sStat; /* fstat() info for database file */ +#ifndef SQLITE_SHM_DIRECTORY + const char *zBasePath = pDbFd->zPath; +#endif /* Call fstat() to figure out the permissions on the database file. If ** a new *-shm file is created, an attempt will be made to create it ** with the same permissions. */ @@ -27258,11 +29652,11 @@ } #ifdef SQLITE_SHM_DIRECTORY nShmFilename = sizeof(SQLITE_SHM_DIRECTORY) + 31; #else - nShmFilename = 6 + (int)strlen(pDbFd->zPath); + nShmFilename = 6 + (int)strlen(zBasePath); #endif pShmNode = sqlite3_malloc( sizeof(*pShmNode) + nShmFilename ); if( pShmNode==0 ){ rc = SQLITE_NOMEM; goto shm_open_err; @@ -27272,11 +29666,11 @@ #ifdef SQLITE_SHM_DIRECTORY sqlite3_snprintf(nShmFilename, zShmFilename, SQLITE_SHM_DIRECTORY "/sqlite-shm-%x-%x", (u32)sStat.st_ino, (u32)sStat.st_dev); #else - sqlite3_snprintf(nShmFilename, zShmFilename, "%s-shm", pDbFd->zPath); + sqlite3_snprintf(nShmFilename, zShmFilename, "%s-shm", zBasePath); sqlite3FileSuffix3(pDbFd->zPath, zShmFilename); #endif pShmNode->h = -1; pDbFd->pInode->pShmNode = pShmNode; pShmNode->pInode = pDbFd->pInode; @@ -27306,17 +29700,17 @@ /* Check to see if another process is holding the dead-man switch. ** If not, truncate the file to zero length. */ rc = SQLITE_OK; - if( unixShmSystemLock(pShmNode, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){ + if( unixShmSystemLock(pDbFd, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){ if( robust_ftruncate(pShmNode->h, 0) ){ rc = unixLogError(SQLITE_IOERR_SHMOPEN, "ftruncate", zShmFilename); } } if( rc==SQLITE_OK ){ - rc = unixShmSystemLock(pShmNode, F_RDLCK, UNIX_SHM_DMS, 1); + rc = unixShmSystemLock(pDbFd, F_RDLCK, UNIX_SHM_DMS, 1); } if( rc ) goto shm_open_err; } } @@ -27378,10 +29772,12 @@ ){ unixFile *pDbFd = (unixFile*)fd; unixShm *p; unixShmNode *pShmNode; int rc = SQLITE_OK; + int nShmPerMap = unixShmRegionPerMap(); + int nReqRegion; /* If the shared-memory file has not yet been opened, open it now. */ if( pDbFd->pShm==0 ){ rc = unixOpenSharedMemory(pDbFd); if( rc!=SQLITE_OK ) return rc; @@ -27393,13 +29789,16 @@ assert( szRegion==pShmNode->szRegion || pShmNode->nRegion==0 ); assert( pShmNode->pInode==pDbFd->pInode ); assert( pShmNode->h>=0 || pDbFd->pInode->bProcessLock==1 ); assert( pShmNode->h<0 || pDbFd->pInode->bProcessLock==0 ); - if( pShmNode->nRegion<=iRegion ){ + /* Minimum number of regions required to be mapped. */ + nReqRegion = ((iRegion+nShmPerMap) / nShmPerMap) * nShmPerMap; + + if( pShmNode->nRegion<nReqRegion ){ char **apNew; /* New apRegion[] array */ - int nByte = (iRegion+1)*szRegion; /* Minimum required file size */ + int nByte = nReqRegion*szRegion; /* Minimum required file size */ struct stat sStat; /* Used by fstat() */ pShmNode->szRegion = szRegion; if( pShmNode->h>=0 ){ @@ -27444,21 +29843,23 @@ } } /* Map the requested memory region into this processes address space. */ apNew = (char **)sqlite3_realloc( - pShmNode->apRegion, (iRegion+1)*sizeof(char *) + pShmNode->apRegion, nReqRegion*sizeof(char *) ); if( !apNew ){ rc = SQLITE_IOERR_NOMEM; goto shmpage_out; } pShmNode->apRegion = apNew; - while(pShmNode->nRegion<=iRegion){ + while( pShmNode->nRegion<nReqRegion ){ + int nMap = szRegion*nShmPerMap; + int i; void *pMem; if( pShmNode->h>=0 ){ - pMem = osMmap(0, szRegion, + pMem = osMmap(0, nMap, pShmNode->isReadonly ? PROT_READ : PROT_READ|PROT_WRITE, MAP_SHARED, pShmNode->h, szRegion*(i64)pShmNode->nRegion ); if( pMem==MAP_FAILED ){ rc = unixLogError(SQLITE_IOERR_SHMMAP, "mmap", pShmNode->zFilename); @@ -27470,12 +29871,15 @@ rc = SQLITE_NOMEM; goto shmpage_out; } memset(pMem, 0, szRegion); } - pShmNode->apRegion[pShmNode->nRegion] = pMem; - pShmNode->nRegion++; + + for(i=0; i<nShmPerMap; i++){ + pShmNode->apRegion[pShmNode->nRegion+i] = &((char*)pMem)[szRegion*i]; + } + pShmNode->nRegion += nShmPerMap; } } shmpage_out: if( pShmNode->nRegion>iRegion ){ @@ -27534,11 +29938,11 @@ allMask |= pX->sharedMask; } /* Unlock the system-level locks */ if( (mask & allMask)==0 ){ - rc = unixShmSystemLock(pShmNode, F_UNLCK, ofst+UNIX_SHM_BASE, n); + rc = unixShmSystemLock(pDbFd, F_UNLCK, ofst+UNIX_SHM_BASE, n); }else{ rc = SQLITE_OK; } /* Undo the local locks */ @@ -27562,11 +29966,11 @@ } /* Get shared locks at the system level, if necessary */ if( rc==SQLITE_OK ){ if( (allShared & mask)==0 ){ - rc = unixShmSystemLock(pShmNode, F_RDLCK, ofst+UNIX_SHM_BASE, n); + rc = unixShmSystemLock(pDbFd, F_RDLCK, ofst+UNIX_SHM_BASE, n); }else{ rc = SQLITE_OK; } } @@ -27587,20 +29991,20 @@ /* Get the exclusive locks at the system level. Then if successful ** also mark the local connection as being locked. */ if( rc==SQLITE_OK ){ - rc = unixShmSystemLock(pShmNode, F_WRLCK, ofst+UNIX_SHM_BASE, n); + rc = unixShmSystemLock(pDbFd, F_WRLCK, ofst+UNIX_SHM_BASE, n); if( rc==SQLITE_OK ){ assert( (p->sharedMask & mask)==0 ); p->exclMask |= mask; } } } sqlite3_mutex_leave(pShmNode->mutex); OSTRACE(("SHM-LOCK shmid-%d, pid-%d got %03x,%03x\n", - p->id, getpid(), p->sharedMask, p->exclMask)); + p->id, osGetpid(0), p->sharedMask, p->exclMask)); return rc; } /* ** Implement a memory barrier or memory fence on shared memory. @@ -27655,11 +30059,13 @@ ** shared-memory file, too */ unixEnterMutex(); assert( pShmNode->nRef>0 ); pShmNode->nRef--; if( pShmNode->nRef==0 ){ - if( deleteFlag && pShmNode->h>=0 ) osUnlink(pShmNode->zFilename); + if( deleteFlag && pShmNode->h>=0 ){ + osUnlink(pShmNode->zFilename); + } unixShmPurge(pDbFd); } unixLeaveMutex(); return SQLITE_OK; @@ -27685,23 +30091,10 @@ pFd->mmapSize = 0; pFd->mmapSizeActual = 0; } } -/* -** Return the system page size. -*/ -static int unixGetPagesize(void){ -#if HAVE_MREMAP - return 512; -#elif defined(_BSD_SOURCE) - return getpagesize(); -#else - return (int)sysconf(_SC_PAGESIZE); -#endif -} - /* ** Attempt to set the size of the memory mapping maintained by file ** descriptor pFd to nNew bytes. Any existing mapping is discarded. ** ** If successful, this function sets the following variables: @@ -27734,12 +30127,16 @@ assert( MAP_FAILED!=0 ); if( (pFd->ctrlFlags & UNIXFILE_RDONLY)==0 ) flags |= PROT_WRITE; if( pOrig ){ - const int szSyspage = unixGetPagesize(); +#if HAVE_MREMAP + i64 nReuse = pFd->mmapSize; +#else + const int szSyspage = osGetpagesize(); i64 nReuse = (pFd->mmapSize & ~(szSyspage-1)); +#endif u8 *pReq = &pOrig[nReuse]; /* Unmap any pages of the existing mapping that cannot be reused. */ if( nReuse!=nOrig ){ osMunmap(pReq, nOrig-nReuse); @@ -27874,14 +30271,14 @@ ** Or, if the third argument is NULL, then this function is being called ** to inform the VFS layer that, according to POSIX, any existing mapping ** may now be invalid and should be unmapped. */ static int unixUnfetch(sqlite3_file *fd, i64 iOff, void *p){ +#if SQLITE_MAX_MMAP_SIZE>0 unixFile *pFd = (unixFile *)fd; /* The underlying database file */ UNUSED_PARAMETER(iOff); -#if SQLITE_MAX_MMAP_SIZE>0 /* If p==0 (unmap the entire file) then there must be no outstanding ** xFetch references. Or, if p!=0 (meaning it is an xFetch reference), ** then there must be at least one outstanding. */ assert( (p==0)==(pFd->nFetchOut==0) ); @@ -27893,10 +30290,14 @@ }else{ unixUnmapfile(pFd); } assert( pFd->nFetchOut>=0 ); +#else + UNUSED_PARAMETER(fd); + UNUSED_PARAMETER(p); + UNUSED_PARAMETER(iOff); #endif return SQLITE_OK; } /* @@ -27916,11 +30317,11 @@ ** Most finder functions return a pointer to a fixed sqlite3_io_methods ** object. The only interesting finder-function is autolockIoFinder, which ** looks at the filesystem type and tries to guess the best locking ** strategy from that. ** -** For finder-funtion F, two objects are created: +** For finder-function F, two objects are created: ** ** (1) The real finder-function named "FImpt()". ** ** (2) A constant pointer to this function named just "F". ** @@ -27937,11 +30338,11 @@ ** methods CLOSE, LOCK, UNLOCK, CKRESLOCK. ** ** * An I/O method finder function called FINDER that returns a pointer ** to the METHOD object in the previous bullet. */ -#define IOMETHODS(FINDER, METHOD, VERSION, CLOSE, LOCK, UNLOCK, CKLOCK) \ +#define IOMETHODS(FINDER,METHOD,VERSION,CLOSE,LOCK,UNLOCK,CKLOCK,SHMMAP) \ static const sqlite3_io_methods METHOD = { \ VERSION, /* iVersion */ \ CLOSE, /* xClose */ \ unixRead, /* xRead */ \ unixWrite, /* xWrite */ \ @@ -27952,11 +30353,11 @@ UNLOCK, /* xUnlock */ \ CKLOCK, /* xCheckReservedLock */ \ unixFileControl, /* xFileControl */ \ unixSectorSize, /* xSectorSize */ \ unixDeviceCharacteristics, /* xDeviceCapabilities */ \ - unixShmMap, /* xShmMap */ \ + SHMMAP, /* xShmMap */ \ unixShmLock, /* xShmLock */ \ unixShmBarrier, /* xShmBarrier */ \ unixShmUnmap, /* xShmUnmap */ \ unixFetch, /* xFetch */ \ unixUnfetch, /* xUnfetch */ \ @@ -27978,52 +30379,57 @@ posixIoMethods, /* sqlite3_io_methods object name */ 3, /* shared memory and mmap are enabled */ unixClose, /* xClose method */ unixLock, /* xLock method */ unixUnlock, /* xUnlock method */ - unixCheckReservedLock /* xCheckReservedLock method */ + unixCheckReservedLock, /* xCheckReservedLock method */ + unixShmMap /* xShmMap method */ ) IOMETHODS( nolockIoFinder, /* Finder function name */ nolockIoMethods, /* sqlite3_io_methods object name */ - 1, /* shared memory is disabled */ + 3, /* shared memory is disabled */ nolockClose, /* xClose method */ nolockLock, /* xLock method */ nolockUnlock, /* xUnlock method */ - nolockCheckReservedLock /* xCheckReservedLock method */ + nolockCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ ) IOMETHODS( dotlockIoFinder, /* Finder function name */ dotlockIoMethods, /* sqlite3_io_methods object name */ 1, /* shared memory is disabled */ dotlockClose, /* xClose method */ dotlockLock, /* xLock method */ dotlockUnlock, /* xUnlock method */ - dotlockCheckReservedLock /* xCheckReservedLock method */ + dotlockCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ ) -#if SQLITE_ENABLE_LOCKING_STYLE && !OS_VXWORKS +#if SQLITE_ENABLE_LOCKING_STYLE IOMETHODS( flockIoFinder, /* Finder function name */ flockIoMethods, /* sqlite3_io_methods object name */ 1, /* shared memory is disabled */ flockClose, /* xClose method */ flockLock, /* xLock method */ flockUnlock, /* xUnlock method */ - flockCheckReservedLock /* xCheckReservedLock method */ + flockCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ ) #endif #if OS_VXWORKS IOMETHODS( semIoFinder, /* Finder function name */ semIoMethods, /* sqlite3_io_methods object name */ 1, /* shared memory is disabled */ - semClose, /* xClose method */ - semLock, /* xLock method */ - semUnlock, /* xUnlock method */ - semCheckReservedLock /* xCheckReservedLock method */ + semXClose, /* xClose method */ + semXLock, /* xLock method */ + semXUnlock, /* xUnlock method */ + semXCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ ) #endif #if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE IOMETHODS( @@ -28031,11 +30437,12 @@ afpIoMethods, /* sqlite3_io_methods object name */ 1, /* shared memory is disabled */ afpClose, /* xClose method */ afpLock, /* xLock method */ afpUnlock, /* xUnlock method */ - afpCheckReservedLock /* xCheckReservedLock method */ + afpCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ ) #endif /* ** The proxy locking method is a "super-method" in the sense that it @@ -28056,11 +30463,12 @@ proxyIoMethods, /* sqlite3_io_methods object name */ 1, /* shared memory is disabled */ proxyClose, /* xClose method */ proxyLock, /* xLock method */ proxyUnlock, /* xUnlock method */ - proxyCheckReservedLock /* xCheckReservedLock method */ + proxyCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ ) #endif /* nfs lockd on OSX 10.3+ doesn't clear write locks when a read lock is set */ #if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE @@ -28069,11 +30477,12 @@ nfsIoMethods, /* sqlite3_io_methods object name */ 1, /* shared memory is disabled */ unixClose, /* xClose method */ unixLock, /* xLock method */ nfsUnlock, /* xUnlock method */ - unixCheckReservedLock /* xCheckReservedLock method */ + unixCheckReservedLock, /* xCheckReservedLock method */ + 0 /* xShmMap method */ ) #endif #if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE /* @@ -28139,19 +30548,17 @@ static const sqlite3_io_methods *(*const autolockIoFinder)(const char*,unixFile*) = autolockIoFinderImpl; #endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */ -#if OS_VXWORKS && SQLITE_ENABLE_LOCKING_STYLE -/* -** This "finder" function attempts to determine the best locking strategy -** for the database file "filePath". It then returns the sqlite3_io_methods -** object that implements that strategy. -** -** This is for VXWorks only. +#if OS_VXWORKS +/* +** This "finder" function for VxWorks checks to see if posix advisory +** locking works. If it does, then that is what is used. If it does not +** work, then fallback to named semaphore locking. */ -static const sqlite3_io_methods *autolockIoFinderImpl( +static const sqlite3_io_methods *vxworksIoFinderImpl( const char *filePath, /* name of the database file */ unixFile *pNew /* the open file object */ ){ struct flock lockInfo; @@ -28173,16 +30580,16 @@ }else{ return &semIoMethods; } } static const sqlite3_io_methods - *(*const autolockIoFinder)(const char*,unixFile*) = autolockIoFinderImpl; + *(*const vxworksIoFinder)(const char*,unixFile*) = vxworksIoFinderImpl; -#endif /* OS_VXWORKS && SQLITE_ENABLE_LOCKING_STYLE */ +#endif /* OS_VXWORKS */ /* -** An abstract type for a pointer to a IO method finder function: +** An abstract type for a pointer to an IO method finder function: */ typedef const sqlite3_io_methods *(*finder_type)(const char*,unixFile*); /**************************************************************************** @@ -28357,11 +30764,11 @@ } unixLeaveMutex(); } #endif - pNew->lastErrno = 0; + storeLastErrno(pNew, 0); #if OS_VXWORKS if( rc!=SQLITE_OK ){ if( h>=0 ) robust_close(pNew, h, __LINE__); h = -1; osUnlink(zFilename); @@ -28492,11 +30899,11 @@ ** For this reason, if an error occurs in the stat() call here, it is ** ignored and -1 is returned. The caller will try to open a new file ** descriptor on the same path, fail, and return an error to SQLite. ** ** Even if a subsequent open() call does succeed, the consequences of - ** not searching for a resusable file descriptor are not dire. */ + ** not searching for a reusable file descriptor are not dire. */ if( 0==osStat(zPath, &sStat) ){ unixInodeInfo *pInode; unixEnterMutex(); pInode = inodeList; @@ -28523,11 +30930,11 @@ ** to create new files with. If no error occurs, then SQLITE_OK is returned ** and a value suitable for passing as the third argument to open(2) is ** written to *pMode. If an IO error occurs, an SQLite error code is ** returned and the value of *pMode is not modified. ** -** In most cases cases, this routine sets *pMode to 0, which will become +** In most cases, this routine sets *pMode to 0, which will become ** an indication to robust_open() to create the file using ** SQLITE_DEFAULT_FILE_PERMISSIONS adjusted by the umask. ** But if the file being opened is a WAL or regular journal file, then ** this function queries the file-system for the permissions on the ** corresponding database file and sets *pMode to this value. Whenever @@ -28682,10 +31089,20 @@ assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_MASTER_JOURNAL || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL ); + + /* Detect a pid change and reset the PRNG. There is a race condition + ** here such that two or more threads all trying to open databases at + ** the same instant might all reset the PRNG. But multiple resets + ** are harmless. + */ + if( randomnessPid!=osGetpid(0) ){ + randomnessPid = osGetpid(0); + sqlite3_randomness(0,0); + } memset(p, 0, sizeof(unixFile)); if( eType==SQLITE_OPEN_MAIN_DB ){ UnixUnusedFd *pUnused; @@ -28774,10 +31191,16 @@ } if( isDelete ){ #if OS_VXWORKS zPath = zName; +#elif defined(SQLITE_UNLINK_AFTER_CLOSE) + zPath = sqlite3_mprintf("%s", zName); + if( zPath==0 ){ + robust_close(p, fd, __LINE__); + return SQLITE_NOMEM; + } #else osUnlink(zName); #endif } #if SQLITE_ENABLE_LOCKING_STYLE @@ -28789,17 +31212,20 @@ noLock = eType!=SQLITE_OPEN_MAIN_DB; #if defined(__APPLE__) || SQLITE_ENABLE_LOCKING_STYLE if( fstatfs(fd, &fsInfo) == -1 ){ - ((unixFile*)pFile)->lastErrno = errno; + storeLastErrno(p, errno); robust_close(p, fd, __LINE__); return SQLITE_IOERR_ACCESS; } if (0 == strncmp("msdos", fsInfo.f_fstypename, 5)) { ((unixFile*)pFile)->fsFlags |= SQLITE_FSFLAGS_IS_MSDOS; } + if (0 == strncmp("exfat", fsInfo.f_fstypename, 5)) { + ((unixFile*)pFile)->fsFlags |= SQLITE_FSFLAGS_IS_MSDOS; + } #endif /* Set up appropriate ctrlFlags */ if( isDelete ) ctrlFlags |= UNIXFILE_DELETE; if( isReadonly ) ctrlFlags |= UNIXFILE_RDONLY; @@ -28818,23 +31244,10 @@ /* SQLITE_FORCE_PROXY_LOCKING==1 means force always use proxy, 0 means ** never use proxy, NULL means use proxy for non-local files only. */ if( envforce!=NULL ){ useProxy = atoi(envforce)>0; }else{ - if( statfs(zPath, &fsInfo) == -1 ){ - /* In theory, the close(fd) call is sub-optimal. If the file opened - ** with fd is a database file, and there are other connections open - ** on that file that are currently holding advisory locks on it, - ** then the call to close() will cancel those locks. In practice, - ** we're assuming that statfs() doesn't fail very often. At least - ** not while other file descriptors opened by the same process on - ** the same file are working. */ - p->lastErrno = errno; - robust_close(p, fd, __LINE__); - rc = SQLITE_IOERR_ACCESS; - goto open_finished; - } useProxy = !(fsInfo.f_flags&MNT_LOCAL); } if( useProxy ){ rc = fillInUnixFile(pVfs, fd, pFile, zPath, ctrlFlags); if( rc==SQLITE_OK ){ @@ -28874,11 +31287,15 @@ ){ int rc = SQLITE_OK; UNUSED_PARAMETER(NotUsed); SimulateIOError(return SQLITE_IOERR_DELETE); if( osUnlink(zPath)==(-1) ){ - if( errno==ENOENT ){ + if( errno==ENOENT +#if OS_VXWORKS + || osAccess(zPath,0)!=0 +#endif + ){ rc = SQLITE_IOERR_DELETE_NOENT; }else{ rc = unixLogError(SQLITE_IOERR_DELETE, "unlink", zPath); } return rc; @@ -29070,22 +31487,22 @@ ** When testing, initializing zBuf[] to zero is all we do. That means ** that we always use the same random number sequence. This makes the ** tests repeatable. */ memset(zBuf, 0, nBuf); + randomnessPid = osGetpid(0); #if !defined(SQLITE_TEST) { - int pid, fd, got; + int fd, got; fd = robust_open("/dev/urandom", O_RDONLY, 0); if( fd<0 ){ time_t t; time(&t); memcpy(zBuf, &t, sizeof(t)); - pid = getpid(); - memcpy(&zBuf[sizeof(t)], &pid, sizeof(pid)); - assert( sizeof(t)+sizeof(pid)<=(size_t)nBuf ); - nBuf = sizeof(t) + sizeof(pid); + memcpy(&zBuf[sizeof(t)], &randomnessPid, sizeof(randomnessPid)); + assert( sizeof(t)+sizeof(randomnessPid)<=(size_t)nBuf ); + nBuf = sizeof(t) + sizeof(randomnessPid); }else{ do{ got = osRead(fd, zBuf, nBuf); }while( got<0 && errno==EINTR ); robust_close(0, fd, __LINE__); } } @@ -29252,13 +31669,14 @@ ** Using proxy locks ** ----------------- ** ** C APIs ** -** sqlite3_file_control(db, dbname, SQLITE_SET_LOCKPROXYFILE, +** sqlite3_file_control(db, dbname, SQLITE_FCNTL_SET_LOCKPROXYFILE, ** <proxy_path> | ":auto:"); -** sqlite3_file_control(db, dbname, SQLITE_GET_LOCKPROXYFILE, &<proxy_path>); +** sqlite3_file_control(db, dbname, SQLITE_FCNTL_GET_LOCKPROXYFILE, +** &<proxy_path>); ** ** ** SQL pragmas ** ** PRAGMA [database.]lock_proxy_file=<proxy_path> | :auto: @@ -29295,11 +31713,11 @@ ** by taking an sqlite-style shared lock on the conch file, reading the ** contents and comparing the host's unique host ID (see below) and lock ** proxy path against the values stored in the conch. The conch file is ** stored in the same directory as the database file and the file name ** is patterned after the database file name as ".<databasename>-conch". -** If the conch file does not exist, or it's contents do not match the +** If the conch file does not exist, or its contents do not match the ** host ID and/or proxy path, then the lock is escalated to an exclusive ** lock and the conch file contents is updated with the host ID and proxy ** path and the lock is downgraded to a shared lock again. If the conch ** is held by another process (with a shared lock), the exclusive lock ** will fail and SQLITE_BUSY is returned. @@ -29347,11 +31765,11 @@ ** ** As mentioned above, when compiled with SQLITE_PREFER_PROXY_LOCKING, ** setting the environment variable SQLITE_FORCE_PROXY_LOCKING to 1 will ** force proxy locking to be used for every database file opened, and 0 ** will force automatic proxy locking to be disabled for all database -** files (explicity calling the SQLITE_SET_LOCKPROXYFILE pragma or +** files (explicitly calling the SQLITE_FCNTL_SET_LOCKPROXYFILE pragma or ** sqlite_file_control API is not affected by SQLITE_FORCE_PROXY_LOCKING). */ /* ** Proxy locking is only available on MacOSX @@ -29368,10 +31786,11 @@ char *conchFilePath; /* Name of the conch file */ unixFile *lockProxy; /* Open proxy lock file */ char *lockProxyPath; /* Name of the proxy lock file */ char *dbPath; /* Name of the open file */ int conchHeld; /* 1 if the conch is held, -1 if lockless */ + int nFails; /* Number of conch taking failures */ void *oldLockingContext; /* Original lockingcontext to restore on close */ sqlite3_io_methods const *pOldMethod; /* Original I/O methods for close */ }; /* @@ -29389,11 +31808,11 @@ #else # ifdef _CS_DARWIN_USER_TEMP_DIR { if( !confstr(_CS_DARWIN_USER_TEMP_DIR, lPath, maxLen) ){ OSTRACE(("GETLOCKPATH failed %s errno=%d pid=%d\n", - lPath, errno, getpid())); + lPath, errno, osGetpid(0))); return SQLITE_IOERR_LOCK; } len = strlcat(lPath, "sqliteplocks", maxLen); } # else @@ -29411,11 +31830,11 @@ char c = dbPath[i]; lPath[i+len] = (c=='/')?'_':c; } lPath[i+len]='\0'; strlcat(lPath, ":auto:", maxLen); - OSTRACE(("GETLOCKPATH proxy lock path=%s pid=%d\n", lPath, getpid())); + OSTRACE(("GETLOCKPATH proxy lock path=%s pid=%d\n", lPath, osGetpid(0))); return SQLITE_OK; } /* ** Creates the lock file and any missing directories in lockPath @@ -29438,20 +31857,20 @@ if( osMkdir(buf, SQLITE_DEFAULT_PROXYDIR_PERMISSIONS) ){ int err=errno; if( err!=EEXIST ) { OSTRACE(("CREATELOCKPATH FAILED creating %s, " "'%s' proxy lock path=%s pid=%d\n", - buf, strerror(err), lockPath, getpid())); + buf, strerror(err), lockPath, osGetpid(0))); return err; } } } start=i+1; } buf[i] = lockPath[i]; } - OSTRACE(("CREATELOCKPATH proxy lock path=%s pid=%d\n", lockPath, getpid())); + OSTRACE(("CREATELOCKPATH proxy lock path=%s pid=%d\n", lockPath, osGetpid(0))); return 0; } /* ** Create a new VFS file descriptor (stored in memory obtained from @@ -29556,14 +31975,14 @@ ** bytes of writable memory. */ static int proxyGetHostID(unsigned char *pHostID, int *pError){ assert(PROXY_HOSTIDLEN == sizeof(uuid_t)); memset(pHostID, 0, PROXY_HOSTIDLEN); -#if defined(__MAX_OS_X_VERSION_MIN_REQUIRED)\ - && __MAC_OS_X_VERSION_MIN_REQUIRED<1050 +# if defined(__APPLE__) && ((__MAC_OS_X_VERSION_MIN_REQUIRED > 1050) || \ + (__IPHONE_OS_VERSION_MIN_REQUIRED > 2000)) { - static const struct timespec timeout = {1, 0}; /* 1 sec timeout */ + struct timespec timeout = {1, 0}; /* 1 sec timeout */ if( gethostuuid(pHostID, &timeout) ){ int err = errno; if( pError ){ *pError = err; } @@ -29674,11 +32093,11 @@ * 10 sec and try again * 3rd try: break the lock unless the mod time has changed. */ struct stat buf; if( osFstat(conchFile->h, &buf) ){ - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); return SQLITE_IOERR_LOCK; } if( nTries==1 ){ conchModTime = buf.st_mtimespec; @@ -29694,11 +32113,11 @@ if( nTries==2 ){ char tBuf[PROXY_MAXCONCHLEN]; int len = osPread(conchFile->h, tBuf, PROXY_MAXCONCHLEN, 0); if( len<0 ){ - pFile->lastErrno = errno; + storeLastErrno(pFile, errno); return SQLITE_IOERR_LOCK; } if( len>PROXY_PATHINDEX && tBuf[0]==(char)PROXY_CONCHVERSION){ /* don't break the lock if the host id doesn't match */ if( 0!=memcmp(&tBuf[PROXY_HEADERLEN], myHostID, PROXY_HOSTIDLEN) ){ @@ -29714,11 +32133,11 @@ assert( nTries==3 ); if( 0==proxyBreakConchLock(pFile, myHostID) ){ rc = SQLITE_OK; if( lockType==EXCLUSIVE_LOCK ){ - rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, SHARED_LOCK); + rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, SHARED_LOCK); } if( !rc ){ rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, lockType); } } @@ -29752,15 +32171,16 @@ int readLen = 0; int tryOldLockPath = 0; int forceNewLockPath = 0; OSTRACE(("TAKECONCH %d for %s pid=%d\n", conchFile->h, - (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), getpid())); + (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), + osGetpid(0))); rc = proxyGetHostID(myHostID, &pError); if( (rc&0xff)==SQLITE_IOERR ){ - pFile->lastErrno = pError; + storeLastErrno(pFile, pError); goto end_takeconch; } rc = proxyConchLock(pFile, myHostID, SHARED_LOCK); if( rc!=SQLITE_OK ){ goto end_takeconch; @@ -29767,11 +32187,11 @@ } /* read the existing conch file */ readLen = seekAndRead((unixFile*)conchFile, 0, readBuf, PROXY_MAXCONCHLEN); if( readLen<0 ){ /* I/O error: lastErrno set by seekAndRead */ - pFile->lastErrno = conchFile->lastErrno; + storeLastErrno(pFile, conchFile->lastErrno); rc = SQLITE_IOERR_READ; goto end_takeconch; }else if( readLen<=(PROXY_HEADERLEN+PROXY_HOSTIDLEN) || readBuf[0]!=(char)PROXY_CONCHVERSION ){ /* a short read or version format mismatch means we need to create a new @@ -29840,20 +32260,21 @@ rc = SQLITE_BUSY; } else { rc = proxyConchLock(pFile, myHostID, EXCLUSIVE_LOCK); } }else{ - rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, EXCLUSIVE_LOCK); + rc = proxyConchLock(pFile, myHostID, EXCLUSIVE_LOCK); } if( rc==SQLITE_OK ){ char writeBuffer[PROXY_MAXCONCHLEN]; int writeSize = 0; writeBuffer[0] = (char)PROXY_CONCHVERSION; memcpy(&writeBuffer[PROXY_HEADERLEN], myHostID, PROXY_HOSTIDLEN); if( pCtx->lockProxyPath!=NULL ){ - strlcpy(&writeBuffer[PROXY_PATHINDEX], pCtx->lockProxyPath, MAXPATHLEN); + strlcpy(&writeBuffer[PROXY_PATHINDEX], pCtx->lockProxyPath, + MAXPATHLEN); }else{ strlcpy(&writeBuffer[PROXY_PATHINDEX], tempLockPath, MAXPATHLEN); } writeSize = PROXY_PATHINDEX + strlen(&writeBuffer[PROXY_PATHINDEX]); robust_ftruncate(conchFile->h, writeSize); @@ -29961,11 +32382,11 @@ pCtx = (proxyLockingContext *)pFile->lockingContext; conchFile = pCtx->conchFile; OSTRACE(("RELEASECONCH %d for %s pid=%d\n", conchFile->h, (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), - getpid())); + osGetpid(0))); if( pCtx->conchHeld>0 ){ rc = conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK); } pCtx->conchHeld = 0; OSTRACE(("RELEASECONCH %d %s\n", conchFile->h, @@ -30061,11 +32482,12 @@ #if defined(__APPLE__) if( pFile->pMethod == &afpIoMethods ){ /* afp style keeps a reference to the db path in the filePath field ** of the struct */ assert( (int)strlen((char*)pFile->lockingContext)<=MAXPATHLEN ); - strlcpy(dbPath, ((afpLockingContext *)pFile->lockingContext)->dbPath, MAXPATHLEN); + strlcpy(dbPath, ((afpLockingContext *)pFile->lockingContext)->dbPath, + MAXPATHLEN); } else #endif if( pFile->pMethod == &dotlockIoMethods ){ /* dot lock style uses the locking context to store the dot lock ** file path */ @@ -30102,11 +32524,11 @@ }else{ lockPath=(char *)path; } OSTRACE(("TRANSPROXY %d for %s pid=%d\n", pFile->h, - (lockPath ? lockPath : ":auto:"), getpid())); + (lockPath ? lockPath : ":auto:"), osGetpid(0))); pCtx = sqlite3_malloc( sizeof(*pCtx) ); if( pCtx==0 ){ return SQLITE_NOMEM; } @@ -30174,11 +32596,11 @@ ** This routine handles sqlite3_file_control() calls that are specific ** to proxy locking. */ static int proxyFileControl(sqlite3_file *id, int op, void *pArg){ switch( op ){ - case SQLITE_GET_LOCKPROXYFILE: { + case SQLITE_FCNTL_GET_LOCKPROXYFILE: { unixFile *pFile = (unixFile*)id; if( pFile->pMethod == &proxyIoMethods ){ proxyLockingContext *pCtx = (proxyLockingContext*)pFile->lockingContext; proxyTakeConch(pFile); if( pCtx->lockProxyPath ){ @@ -30189,17 +32611,20 @@ } else { *(const char **)pArg = NULL; } return SQLITE_OK; } - case SQLITE_SET_LOCKPROXYFILE: { + case SQLITE_FCNTL_SET_LOCKPROXYFILE: { unixFile *pFile = (unixFile*)id; int rc = SQLITE_OK; int isProxyStyle = (pFile->pMethod == &proxyIoMethods); if( pArg==NULL || (const char *)pArg==0 ){ if( isProxyStyle ){ - /* turn off proxy locking - not supported */ + /* turn off proxy locking - not supported. If support is added for + ** switching proxy locking mode off then it will need to fail if + ** the journal mode is WAL mode. + */ rc = SQLITE_ERROR /*SQLITE_PROTOCOL? SQLITE_MISUSE?*/; }else{ /* turn off proxy locking - already off - NOOP */ rc = SQLITE_OK; } @@ -30386,11 +32811,11 @@ ** This routine is called once during SQLite initialization and by a ** single thread. The memory allocation and mutex subsystems have not ** necessarily been initialized when this routine is called, and so they ** should not be used. */ -SQLITE_API int sqlite3_os_init(void){ +SQLITE_API int SQLITE_STDCALL sqlite3_os_init(void){ /* ** The following macro defines an initializer for an sqlite3_vfs object. ** The name of the VFS is NAME. The pAppData is a pointer to a pointer ** to the "finder" function. (pAppData is a pointer to a pointer because ** silly C90 rules prohibit a void* from being cast to a function pointer @@ -30440,26 +32865,28 @@ ** Note that the sqlite3_vfs.pNext field of the VFS object is modified ** by the SQLite core when the VFS is registered. So the following ** array cannot be const. */ static sqlite3_vfs aVfs[] = { -#if SQLITE_ENABLE_LOCKING_STYLE && (OS_VXWORKS || defined(__APPLE__)) +#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) UNIXVFS("unix", autolockIoFinder ), +#elif OS_VXWORKS + UNIXVFS("unix", vxworksIoFinder ), #else UNIXVFS("unix", posixIoFinder ), #endif UNIXVFS("unix-none", nolockIoFinder ), UNIXVFS("unix-dotfile", dotlockIoFinder ), UNIXVFS("unix-excl", posixIoFinder ), #if OS_VXWORKS UNIXVFS("unix-namedsem", semIoFinder ), #endif -#if SQLITE_ENABLE_LOCKING_STYLE +#if SQLITE_ENABLE_LOCKING_STYLE || OS_VXWORKS UNIXVFS("unix-posix", posixIoFinder ), -#if !OS_VXWORKS +#endif +#if SQLITE_ENABLE_LOCKING_STYLE UNIXVFS("unix-flock", flockIoFinder ), -#endif #endif #if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) UNIXVFS("unix-afp", afpIoFinder ), UNIXVFS("unix-nfs", nfsIoFinder ), UNIXVFS("unix-proxy", proxyIoFinder ), @@ -30467,11 +32894,11 @@ }; unsigned int i; /* Loop counter */ /* Double-check that the aSyscall[] array has been constructed ** correctly. See ticket [bb3a86e890c8e96ab] */ - assert( ArraySize(aSyscall)==24 ); + assert( ArraySize(aSyscall)==25 ); /* Register all VFSes defined in the aVfs[] array */ for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){ sqlite3_vfs_register(&aVfs[i], i==0); } @@ -30483,11 +32910,11 @@ ** ** Some operating systems might need to do some cleanup in this routine, ** to release dynamically allocated objects. But not on unix. ** This routine is a no-op for unix. */ -SQLITE_API int sqlite3_os_end(void){ +SQLITE_API int SQLITE_STDCALL sqlite3_os_end(void){ return SQLITE_OK; } #endif /* SQLITE_OS_UNIX */ @@ -30507,15 +32934,10 @@ ** ** This file contains code that is specific to Windows. */ #if SQLITE_OS_WIN /* This file is used for Windows only */ -#ifdef __CYGWIN__ -# include <sys/cygwin.h> -# include <errno.h> /* amalgamator: keep */ -#endif - /* ** Include code that is common to all os_*.c files */ /************** Include os_common.h in the middle of os_win.c ****************/ /************** Begin file os_common.h ***************************************/ @@ -30725,18 +33147,27 @@ #endif /* !defined(_OS_COMMON_H_) */ /************** End of os_common.h *******************************************/ /************** Continuing where we left off in os_win.c *********************/ +/* +** Include the header file for the Windows VFS. +*/ + /* ** Compiling and using WAL mode requires several APIs that are only ** available in Windows platforms based on the NT kernel. */ #if !SQLITE_OS_WINNT && !defined(SQLITE_OMIT_WAL) # error "WAL mode requires support from the Windows NT kernel, compile\ with SQLITE_OMIT_WAL." #endif + +#if !SQLITE_OS_WINNT && SQLITE_MAX_MMAP_SIZE>0 +# error "Memory mapped files require support from the Windows NT kernel,\ + compile with SQLITE_MAX_MMAP_SIZE=0." +#endif /* ** Are most of the Win32 ANSI APIs available (i.e. with certain exceptions ** based on the sub-platform)? */ @@ -30759,38 +33190,76 @@ #if !defined(SQLITE_WIN32_HAS_ANSI) && !defined(SQLITE_WIN32_HAS_WIDE) # error "At least one of SQLITE_WIN32_HAS_ANSI and SQLITE_WIN32_HAS_WIDE\ must be defined." #endif +/* +** Define the required Windows SDK version constants if they are not +** already available. +*/ +#ifndef NTDDI_WIN8 +# define NTDDI_WIN8 0x06020000 +#endif + +#ifndef NTDDI_WINBLUE +# define NTDDI_WINBLUE 0x06030000 +#endif + +/* +** Check to see if the GetVersionEx[AW] functions are deprecated on the +** target system. GetVersionEx was first deprecated in Win8.1. +*/ +#ifndef SQLITE_WIN32_GETVERSIONEX +# if defined(NTDDI_VERSION) && NTDDI_VERSION >= NTDDI_WINBLUE +# define SQLITE_WIN32_GETVERSIONEX 0 /* GetVersionEx() is deprecated */ +# else +# define SQLITE_WIN32_GETVERSIONEX 1 /* GetVersionEx() is current */ +# endif +#endif + +/* +** This constant should already be defined (in the "WinDef.h" SDK file). +*/ +#ifndef MAX_PATH +# define MAX_PATH (260) +#endif + /* ** Maximum pathname length (in chars) for Win32. This should normally be ** MAX_PATH. */ #ifndef SQLITE_WIN32_MAX_PATH_CHARS # define SQLITE_WIN32_MAX_PATH_CHARS (MAX_PATH) #endif +/* +** This constant should already be defined (in the "WinNT.h" SDK file). +*/ +#ifndef UNICODE_STRING_MAX_CHARS +# define UNICODE_STRING_MAX_CHARS (32767) +#endif + /* ** Maximum pathname length (in chars) for WinNT. This should normally be -** 32767. +** UNICODE_STRING_MAX_CHARS. */ #ifndef SQLITE_WINNT_MAX_PATH_CHARS -# define SQLITE_WINNT_MAX_PATH_CHARS (32767) +# define SQLITE_WINNT_MAX_PATH_CHARS (UNICODE_STRING_MAX_CHARS) #endif /* ** Maximum pathname length (in bytes) for Win32. The MAX_PATH macro is in -** characters, so we allocate 3 bytes per character assuming worst-case of +** characters, so we allocate 4 bytes per character assuming worst-case of ** 4-bytes-per-character for UTF8. */ #ifndef SQLITE_WIN32_MAX_PATH_BYTES # define SQLITE_WIN32_MAX_PATH_BYTES (SQLITE_WIN32_MAX_PATH_CHARS*4) #endif /* ** Maximum pathname length (in bytes) for WinNT. This should normally be -** 32767 * sizeof(WCHAR). +** UNICODE_STRING_MAX_CHARS * sizeof(WCHAR). */ #ifndef SQLITE_WINNT_MAX_PATH_BYTES # define SQLITE_WINNT_MAX_PATH_BYTES \ (sizeof(WCHAR) * SQLITE_WINNT_MAX_PATH_CHARS) #endif @@ -30813,30 +33282,27 @@ /* ** This macro is used when a local variable is set to a value that is ** [sometimes] not used by the code (e.g. via conditional compilation). */ #ifndef UNUSED_VARIABLE_VALUE -# define UNUSED_VARIABLE_VALUE(x) (void)(x) +# define UNUSED_VARIABLE_VALUE(x) (void)(x) #endif /* -** Returns the string that should be used as the directory separator. +** Returns the character that should be used as the directory separator. */ -#ifndef winGetDirDep -# ifdef __CYGWIN__ -# define winGetDirDep() "/" -# else -# define winGetDirDep() "\\" -# endif +#ifndef winGetDirSep +# define winGetDirSep() '\\' #endif /* ** Do we need to manually define the Win32 file mapping APIs for use with WAL -** mode (e.g. these APIs are available in the Windows CE SDK; however, they -** are not present in the header file)? +** mode or memory mapped files (e.g. these APIs are available in the Windows +** CE SDK; however, they are not present in the header file)? */ -#if SQLITE_WIN32_FILEMAPPING_API && !defined(SQLITE_OMIT_WAL) +#if SQLITE_WIN32_FILEMAPPING_API && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) /* ** Two of the file mapping APIs are different under WinRT. Figure out which ** set we need. */ #if SQLITE_OS_WINRT @@ -30860,17 +33326,17 @@ /* ** This file mapping API is common to both Win32 and WinRT. */ WINBASEAPI BOOL WINAPI UnmapViewOfFile(LPCVOID); -#endif /* SQLITE_WIN32_FILEMAPPING_API && !defined(SQLITE_OMIT_WAL) */ +#endif /* SQLITE_WIN32_FILEMAPPING_API */ /* ** Some Microsoft compilers lack this definition. */ #ifndef INVALID_FILE_ATTRIBUTES -# define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +# define INVALID_FILE_ATTRIBUTES ((DWORD)-1) #endif #ifndef FILE_FLAG_MASK # define FILE_FLAG_MASK (0xFF3C0000) #endif @@ -30916,11 +33382,11 @@ #endif const char *zPath; /* Full pathname of this file */ int szChunk; /* Chunk size configured by FCNTL_CHUNK_SIZE */ #if SQLITE_OS_WINCE LPWSTR zDeleteOnClose; /* Name of file to delete when closing */ - HANDLE hMutex; /* Mutex used to control access to shared lock */ + HANDLE hMutex; /* Mutex used to control access to shared lock */ HANDLE hShared; /* Shared memory segment used for locking */ winceLock local; /* Locks obtained by this instance of winFile */ winceLock *shared; /* Global shared lock memory for the file */ #endif #if SQLITE_MAX_MMAP_SIZE>0 @@ -31016,34 +33482,45 @@ ** sqlite3_mem_methods implementation. */ typedef struct winMemData winMemData; struct winMemData { #ifndef NDEBUG - u32 magic; /* Magic number to detect structure corruption. */ + u32 magic1; /* Magic number to detect structure corruption. */ #endif HANDLE hHeap; /* The handle to our heap. */ BOOL bOwned; /* Do we own the heap (i.e. destroy it on shutdown)? */ +#ifndef NDEBUG + u32 magic2; /* Magic number to detect structure corruption. */ +#endif }; #ifndef NDEBUG -#define WINMEM_MAGIC 0x42b2830b +#define WINMEM_MAGIC1 0x42b2830b +#define WINMEM_MAGIC2 0xbd4d7cf4 #endif static struct winMemData win_mem_data = { #ifndef NDEBUG - WINMEM_MAGIC, + WINMEM_MAGIC1, #endif NULL, FALSE +#ifndef NDEBUG + ,WINMEM_MAGIC2 +#endif }; #ifndef NDEBUG -#define winMemAssertMagic() assert( win_mem_data.magic==WINMEM_MAGIC ) +#define winMemAssertMagic1() assert( win_mem_data.magic1==WINMEM_MAGIC1 ) +#define winMemAssertMagic2() assert( win_mem_data.magic2==WINMEM_MAGIC2 ) +#define winMemAssertMagic() winMemAssertMagic1(); winMemAssertMagic2(); #else #define winMemAssertMagic() #endif -#define winMemGetHeap() win_mem_data.hHeap +#define winMemGetDataPtr() &win_mem_data +#define winMemGetHeap() win_mem_data.hHeap +#define winMemGetOwned() win_mem_data.bOwned static void *winMemMalloc(int nBytes); static void winMemFree(void *pPrior); static void *winMemRealloc(void *pPrior, int nBytes); static int winMemSize(void *p); @@ -31065,14 +33542,13 @@ ** ** In order to facilitate testing on a WinNT system, the test fixture ** can manually set this value to 1 to emulate Win98 behavior. */ #ifdef SQLITE_TEST -SQLITE_API int sqlite3_os_type = 0; -#elif !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ - defined(SQLITE_WIN32_HAS_ANSI) && defined(SQLITE_WIN32_HAS_WIDE) -static int sqlite3_os_type = 0; +SQLITE_API LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0; +#else +static LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0; #endif #ifndef SYSCALL # define SYSCALL sqlite3_syscall_ptr #endif @@ -31143,21 +33619,21 @@ #define osCreateFileW ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD, \ LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[5].pCurrent) #if (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_ANSI) && \ - !defined(SQLITE_OMIT_WAL)) + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) { "CreateFileMappingA", (SYSCALL)CreateFileMappingA, 0 }, #else { "CreateFileMappingA", (SYSCALL)0, 0 }, #endif #define osCreateFileMappingA ((HANDLE(WINAPI*)(HANDLE,LPSECURITY_ATTRIBUTES, \ DWORD,DWORD,DWORD,LPCSTR))aSyscall[6].pCurrent) #if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ - !defined(SQLITE_OMIT_WAL)) + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) { "CreateFileMappingW", (SYSCALL)CreateFileMappingW, 0 }, #else { "CreateFileMappingW", (SYSCALL)0, 0 }, #endif @@ -31373,20 +33849,22 @@ { "GetTickCount", (SYSCALL)0, 0 }, #endif #define osGetTickCount ((DWORD(WINAPI*)(VOID))aSyscall[33].pCurrent) -#if defined(SQLITE_WIN32_HAS_ANSI) +#if defined(SQLITE_WIN32_HAS_ANSI) && defined(SQLITE_WIN32_GETVERSIONEX) && \ + SQLITE_WIN32_GETVERSIONEX { "GetVersionExA", (SYSCALL)GetVersionExA, 0 }, #else { "GetVersionExA", (SYSCALL)0, 0 }, #endif #define osGetVersionExA ((BOOL(WINAPI*)( \ LPOSVERSIONINFOA))aSyscall[34].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ + defined(SQLITE_WIN32_GETVERSIONEX) && SQLITE_WIN32_GETVERSIONEX { "GetVersionExW", (SYSCALL)GetVersionExW, 0 }, #else { "GetVersionExW", (SYSCALL)0, 0 }, #endif @@ -31436,44 +33914,52 @@ #endif #define osHeapValidate ((BOOL(WINAPI*)(HANDLE,DWORD, \ LPCVOID))aSyscall[42].pCurrent) +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT + { "HeapCompact", (SYSCALL)HeapCompact, 0 }, +#else + { "HeapCompact", (SYSCALL)0, 0 }, +#endif + +#define osHeapCompact ((UINT(WINAPI*)(HANDLE,DWORD))aSyscall[43].pCurrent) + #if defined(SQLITE_WIN32_HAS_ANSI) && !defined(SQLITE_OMIT_LOAD_EXTENSION) { "LoadLibraryA", (SYSCALL)LoadLibraryA, 0 }, #else { "LoadLibraryA", (SYSCALL)0, 0 }, #endif -#define osLoadLibraryA ((HMODULE(WINAPI*)(LPCSTR))aSyscall[43].pCurrent) +#define osLoadLibraryA ((HMODULE(WINAPI*)(LPCSTR))aSyscall[44].pCurrent) #if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ !defined(SQLITE_OMIT_LOAD_EXTENSION) { "LoadLibraryW", (SYSCALL)LoadLibraryW, 0 }, #else { "LoadLibraryW", (SYSCALL)0, 0 }, #endif -#define osLoadLibraryW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[44].pCurrent) +#define osLoadLibraryW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[45].pCurrent) #if !SQLITE_OS_WINRT { "LocalFree", (SYSCALL)LocalFree, 0 }, #else { "LocalFree", (SYSCALL)0, 0 }, #endif -#define osLocalFree ((HLOCAL(WINAPI*)(HLOCAL))aSyscall[45].pCurrent) +#define osLocalFree ((HLOCAL(WINAPI*)(HLOCAL))aSyscall[46].pCurrent) #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT { "LockFile", (SYSCALL)LockFile, 0 }, #else { "LockFile", (SYSCALL)0, 0 }, #endif #ifndef osLockFile #define osLockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ - DWORD))aSyscall[46].pCurrent) + DWORD))aSyscall[47].pCurrent) #endif #if !SQLITE_OS_WINCE { "LockFileEx", (SYSCALL)LockFileEx, 0 }, #else @@ -31480,218 +33966,252 @@ { "LockFileEx", (SYSCALL)0, 0 }, #endif #ifndef osLockFileEx #define osLockFileEx ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD,DWORD, \ - LPOVERLAPPED))aSyscall[47].pCurrent) + LPOVERLAPPED))aSyscall[48].pCurrent) #endif -#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && !defined(SQLITE_OMIT_WAL)) +#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) { "MapViewOfFile", (SYSCALL)MapViewOfFile, 0 }, #else { "MapViewOfFile", (SYSCALL)0, 0 }, #endif #define osMapViewOfFile ((LPVOID(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ - SIZE_T))aSyscall[48].pCurrent) + SIZE_T))aSyscall[49].pCurrent) { "MultiByteToWideChar", (SYSCALL)MultiByteToWideChar, 0 }, #define osMultiByteToWideChar ((int(WINAPI*)(UINT,DWORD,LPCSTR,int,LPWSTR, \ - int))aSyscall[49].pCurrent) + int))aSyscall[50].pCurrent) { "QueryPerformanceCounter", (SYSCALL)QueryPerformanceCounter, 0 }, #define osQueryPerformanceCounter ((BOOL(WINAPI*)( \ - LARGE_INTEGER*))aSyscall[50].pCurrent) + LARGE_INTEGER*))aSyscall[51].pCurrent) { "ReadFile", (SYSCALL)ReadFile, 0 }, #define osReadFile ((BOOL(WINAPI*)(HANDLE,LPVOID,DWORD,LPDWORD, \ - LPOVERLAPPED))aSyscall[51].pCurrent) + LPOVERLAPPED))aSyscall[52].pCurrent) { "SetEndOfFile", (SYSCALL)SetEndOfFile, 0 }, -#define osSetEndOfFile ((BOOL(WINAPI*)(HANDLE))aSyscall[52].pCurrent) +#define osSetEndOfFile ((BOOL(WINAPI*)(HANDLE))aSyscall[53].pCurrent) #if !SQLITE_OS_WINRT { "SetFilePointer", (SYSCALL)SetFilePointer, 0 }, #else { "SetFilePointer", (SYSCALL)0, 0 }, #endif #define osSetFilePointer ((DWORD(WINAPI*)(HANDLE,LONG,PLONG, \ - DWORD))aSyscall[53].pCurrent) + DWORD))aSyscall[54].pCurrent) #if !SQLITE_OS_WINRT { "Sleep", (SYSCALL)Sleep, 0 }, #else { "Sleep", (SYSCALL)0, 0 }, #endif -#define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[54].pCurrent) +#define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent) { "SystemTimeToFileTime", (SYSCALL)SystemTimeToFileTime, 0 }, #define osSystemTimeToFileTime ((BOOL(WINAPI*)(CONST SYSTEMTIME*, \ - LPFILETIME))aSyscall[55].pCurrent) + LPFILETIME))aSyscall[56].pCurrent) #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT { "UnlockFile", (SYSCALL)UnlockFile, 0 }, #else { "UnlockFile", (SYSCALL)0, 0 }, #endif #ifndef osUnlockFile #define osUnlockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ - DWORD))aSyscall[56].pCurrent) + DWORD))aSyscall[57].pCurrent) #endif #if !SQLITE_OS_WINCE { "UnlockFileEx", (SYSCALL)UnlockFileEx, 0 }, #else { "UnlockFileEx", (SYSCALL)0, 0 }, #endif #define osUnlockFileEx ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ - LPOVERLAPPED))aSyscall[57].pCurrent) + LPOVERLAPPED))aSyscall[58].pCurrent) -#if SQLITE_OS_WINCE || !defined(SQLITE_OMIT_WAL) +#if SQLITE_OS_WINCE || !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 { "UnmapViewOfFile", (SYSCALL)UnmapViewOfFile, 0 }, #else { "UnmapViewOfFile", (SYSCALL)0, 0 }, #endif -#define osUnmapViewOfFile ((BOOL(WINAPI*)(LPCVOID))aSyscall[58].pCurrent) +#define osUnmapViewOfFile ((BOOL(WINAPI*)(LPCVOID))aSyscall[59].pCurrent) { "WideCharToMultiByte", (SYSCALL)WideCharToMultiByte, 0 }, #define osWideCharToMultiByte ((int(WINAPI*)(UINT,DWORD,LPCWSTR,int,LPSTR,int, \ - LPCSTR,LPBOOL))aSyscall[59].pCurrent) + LPCSTR,LPBOOL))aSyscall[60].pCurrent) { "WriteFile", (SYSCALL)WriteFile, 0 }, #define osWriteFile ((BOOL(WINAPI*)(HANDLE,LPCVOID,DWORD,LPDWORD, \ - LPOVERLAPPED))aSyscall[60].pCurrent) + LPOVERLAPPED))aSyscall[61].pCurrent) #if SQLITE_OS_WINRT { "CreateEventExW", (SYSCALL)CreateEventExW, 0 }, #else { "CreateEventExW", (SYSCALL)0, 0 }, #endif #define osCreateEventExW ((HANDLE(WINAPI*)(LPSECURITY_ATTRIBUTES,LPCWSTR, \ - DWORD,DWORD))aSyscall[61].pCurrent) + DWORD,DWORD))aSyscall[62].pCurrent) #if !SQLITE_OS_WINRT { "WaitForSingleObject", (SYSCALL)WaitForSingleObject, 0 }, #else { "WaitForSingleObject", (SYSCALL)0, 0 }, #endif #define osWaitForSingleObject ((DWORD(WINAPI*)(HANDLE, \ - DWORD))aSyscall[62].pCurrent) + DWORD))aSyscall[63].pCurrent) -#if SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE { "WaitForSingleObjectEx", (SYSCALL)WaitForSingleObjectEx, 0 }, #else { "WaitForSingleObjectEx", (SYSCALL)0, 0 }, #endif #define osWaitForSingleObjectEx ((DWORD(WINAPI*)(HANDLE,DWORD, \ - BOOL))aSyscall[63].pCurrent) + BOOL))aSyscall[64].pCurrent) #if SQLITE_OS_WINRT { "SetFilePointerEx", (SYSCALL)SetFilePointerEx, 0 }, #else { "SetFilePointerEx", (SYSCALL)0, 0 }, #endif #define osSetFilePointerEx ((BOOL(WINAPI*)(HANDLE,LARGE_INTEGER, \ - PLARGE_INTEGER,DWORD))aSyscall[64].pCurrent) + PLARGE_INTEGER,DWORD))aSyscall[65].pCurrent) #if SQLITE_OS_WINRT { "GetFileInformationByHandleEx", (SYSCALL)GetFileInformationByHandleEx, 0 }, #else { "GetFileInformationByHandleEx", (SYSCALL)0, 0 }, #endif #define osGetFileInformationByHandleEx ((BOOL(WINAPI*)(HANDLE, \ - FILE_INFO_BY_HANDLE_CLASS,LPVOID,DWORD))aSyscall[65].pCurrent) + FILE_INFO_BY_HANDLE_CLASS,LPVOID,DWORD))aSyscall[66].pCurrent) -#if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_WAL) +#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) { "MapViewOfFileFromApp", (SYSCALL)MapViewOfFileFromApp, 0 }, #else { "MapViewOfFileFromApp", (SYSCALL)0, 0 }, #endif #define osMapViewOfFileFromApp ((LPVOID(WINAPI*)(HANDLE,ULONG,ULONG64, \ - SIZE_T))aSyscall[66].pCurrent) + SIZE_T))aSyscall[67].pCurrent) #if SQLITE_OS_WINRT { "CreateFile2", (SYSCALL)CreateFile2, 0 }, #else { "CreateFile2", (SYSCALL)0, 0 }, #endif #define osCreateFile2 ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD,DWORD, \ - LPCREATEFILE2_EXTENDED_PARAMETERS))aSyscall[67].pCurrent) + LPCREATEFILE2_EXTENDED_PARAMETERS))aSyscall[68].pCurrent) #if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_LOAD_EXTENSION) { "LoadPackagedLibrary", (SYSCALL)LoadPackagedLibrary, 0 }, #else { "LoadPackagedLibrary", (SYSCALL)0, 0 }, #endif #define osLoadPackagedLibrary ((HMODULE(WINAPI*)(LPCWSTR, \ - DWORD))aSyscall[68].pCurrent) + DWORD))aSyscall[69].pCurrent) #if SQLITE_OS_WINRT { "GetTickCount64", (SYSCALL)GetTickCount64, 0 }, #else { "GetTickCount64", (SYSCALL)0, 0 }, #endif -#define osGetTickCount64 ((ULONGLONG(WINAPI*)(VOID))aSyscall[69].pCurrent) +#define osGetTickCount64 ((ULONGLONG(WINAPI*)(VOID))aSyscall[70].pCurrent) #if SQLITE_OS_WINRT { "GetNativeSystemInfo", (SYSCALL)GetNativeSystemInfo, 0 }, #else { "GetNativeSystemInfo", (SYSCALL)0, 0 }, #endif #define osGetNativeSystemInfo ((VOID(WINAPI*)( \ - LPSYSTEM_INFO))aSyscall[70].pCurrent) + LPSYSTEM_INFO))aSyscall[71].pCurrent) #if defined(SQLITE_WIN32_HAS_ANSI) { "OutputDebugStringA", (SYSCALL)OutputDebugStringA, 0 }, #else { "OutputDebugStringA", (SYSCALL)0, 0 }, #endif -#define osOutputDebugStringA ((VOID(WINAPI*)(LPCSTR))aSyscall[71].pCurrent) +#define osOutputDebugStringA ((VOID(WINAPI*)(LPCSTR))aSyscall[72].pCurrent) #if defined(SQLITE_WIN32_HAS_WIDE) { "OutputDebugStringW", (SYSCALL)OutputDebugStringW, 0 }, #else { "OutputDebugStringW", (SYSCALL)0, 0 }, #endif -#define osOutputDebugStringW ((VOID(WINAPI*)(LPCWSTR))aSyscall[72].pCurrent) +#define osOutputDebugStringW ((VOID(WINAPI*)(LPCWSTR))aSyscall[73].pCurrent) { "GetProcessHeap", (SYSCALL)GetProcessHeap, 0 }, -#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[73].pCurrent) +#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[74].pCurrent) -#if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_WAL) +#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) { "CreateFileMappingFromApp", (SYSCALL)CreateFileMappingFromApp, 0 }, #else { "CreateFileMappingFromApp", (SYSCALL)0, 0 }, #endif #define osCreateFileMappingFromApp ((HANDLE(WINAPI*)(HANDLE, \ - LPSECURITY_ATTRIBUTES,ULONG,ULONG64,LPCWSTR))aSyscall[74].pCurrent) + LPSECURITY_ATTRIBUTES,ULONG,ULONG64,LPCWSTR))aSyscall[75].pCurrent) + +/* +** NOTE: On some sub-platforms, the InterlockedCompareExchange "function" +** is really just a macro that uses a compiler intrinsic (e.g. x64). +** So do not try to make this is into a redefinable interface. +*/ +#if defined(InterlockedCompareExchange) + { "InterlockedCompareExchange", (SYSCALL)0, 0 }, + +#define osInterlockedCompareExchange InterlockedCompareExchange +#else + { "InterlockedCompareExchange", (SYSCALL)InterlockedCompareExchange, 0 }, + +#define osInterlockedCompareExchange ((LONG(WINAPI*)(LONG \ + SQLITE_WIN32_VOLATILE*, LONG,LONG))aSyscall[76].pCurrent) +#endif /* defined(InterlockedCompareExchange) */ + +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID + { "UuidCreate", (SYSCALL)UuidCreate, 0 }, +#else + { "UuidCreate", (SYSCALL)0, 0 }, +#endif + +#define osUuidCreate ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[77].pCurrent) + +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID + { "UuidCreateSequential", (SYSCALL)UuidCreateSequential, 0 }, +#else + { "UuidCreateSequential", (SYSCALL)0, 0 }, +#endif + +#define osUuidCreateSequential \ + ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[78].pCurrent) }; /* End of the overrideable system calls */ /* ** This is the xSetSystemCall() method of sqlite3_vfs for all of the @@ -31774,16 +34294,104 @@ if( aSyscall[i].pCurrent!=0 ) return aSyscall[i].zName; } return 0; } +#ifdef SQLITE_WIN32_MALLOC +/* +** If a Win32 native heap has been configured, this function will attempt to +** compact it. Upon success, SQLITE_OK will be returned. Upon failure, one +** of SQLITE_NOMEM, SQLITE_ERROR, or SQLITE_NOTFOUND will be returned. The +** "pnLargest" argument, if non-zero, will be used to return the size of the +** largest committed free block in the heap, in bytes. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_win32_compact_heap(LPUINT pnLargest){ + int rc = SQLITE_OK; + UINT nLargest = 0; + HANDLE hHeap; + + winMemAssertMagic(); + hHeap = winMemGetHeap(); + assert( hHeap!=0 ); + assert( hHeap!=INVALID_HANDLE_VALUE ); +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) + assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); +#endif +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT + if( (nLargest=osHeapCompact(hHeap, SQLITE_WIN32_HEAP_FLAGS))==0 ){ + DWORD lastErrno = osGetLastError(); + if( lastErrno==NO_ERROR ){ + sqlite3_log(SQLITE_NOMEM, "failed to HeapCompact (no space), heap=%p", + (void*)hHeap); + rc = SQLITE_NOMEM; + }else{ + sqlite3_log(SQLITE_ERROR, "failed to HeapCompact (%lu), heap=%p", + osGetLastError(), (void*)hHeap); + rc = SQLITE_ERROR; + } + } +#else + sqlite3_log(SQLITE_NOTFOUND, "failed to HeapCompact, heap=%p", + (void*)hHeap); + rc = SQLITE_NOTFOUND; +#endif + if( pnLargest ) *pnLargest = nLargest; + return rc; +} + +/* +** If a Win32 native heap has been configured, this function will attempt to +** destroy and recreate it. If the Win32 native heap is not isolated and/or +** the sqlite3_memory_used() function does not return zero, SQLITE_BUSY will +** be returned and no changes will be made to the Win32 native heap. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_win32_reset_heap(){ + int rc; + MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */ + MUTEX_LOGIC( sqlite3_mutex *pMem; ) /* The memsys static mutex */ + MUTEX_LOGIC( pMaster = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); ) + MUTEX_LOGIC( pMem = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM); ) + sqlite3_mutex_enter(pMaster); + sqlite3_mutex_enter(pMem); + winMemAssertMagic(); + if( winMemGetHeap()!=NULL && winMemGetOwned() && sqlite3_memory_used()==0 ){ + /* + ** At this point, there should be no outstanding memory allocations on + ** the heap. Also, since both the master and memsys locks are currently + ** being held by us, no other function (i.e. from another thread) should + ** be able to even access the heap. Attempt to destroy and recreate our + ** isolated Win32 native heap now. + */ + assert( winMemGetHeap()!=NULL ); + assert( winMemGetOwned() ); + assert( sqlite3_memory_used()==0 ); + winMemShutdown(winMemGetDataPtr()); + assert( winMemGetHeap()==NULL ); + assert( !winMemGetOwned() ); + assert( sqlite3_memory_used()==0 ); + rc = winMemInit(winMemGetDataPtr()); + assert( rc!=SQLITE_OK || winMemGetHeap()!=NULL ); + assert( rc!=SQLITE_OK || winMemGetOwned() ); + assert( rc!=SQLITE_OK || sqlite3_memory_used()==0 ); + }else{ + /* + ** The Win32 native heap cannot be modified because it may be in use. + */ + rc = SQLITE_BUSY; + } + sqlite3_mutex_leave(pMem); + sqlite3_mutex_leave(pMaster); + return rc; +} +#endif /* SQLITE_WIN32_MALLOC */ + /* ** This function outputs the specified (ANSI) string to the Win32 debugger ** (if available). */ -SQLITE_API void sqlite3_win32_write_debug(const char *zBuf, int nBuf){ +SQLITE_API void SQLITE_STDCALL sqlite3_win32_write_debug(const char *zBuf, int nBuf){ char zDbgBuf[SQLITE_WIN32_DBG_BUF_SIZE]; int nMin = MIN(nBuf, (SQLITE_WIN32_DBG_BUF_SIZE - 1)); /* may be negative. */ if( nMin<-1 ) nMin = -1; /* all negative values become -1. */ assert( nMin==-1 || nMin==0 || nMin<SQLITE_WIN32_DBG_BUF_SIZE ); #if defined(SQLITE_WIN32_HAS_ANSI) @@ -31819,11 +34427,11 @@ */ #if SQLITE_OS_WINRT static HANDLE sleepObj = NULL; #endif -SQLITE_API void sqlite3_win32_sleep(DWORD milliseconds){ +SQLITE_API void SQLITE_STDCALL sqlite3_win32_sleep(DWORD milliseconds){ #if SQLITE_OS_WINRT if ( sleepObj==NULL ){ sleepObj = osCreateEventExW(NULL, NULL, CREATE_EVENT_MANUAL_RESET, SYNCHRONIZE); } @@ -31831,10 +34439,20 @@ osWaitForSingleObjectEx(sleepObj, milliseconds, FALSE); #else osSleep(milliseconds); #endif } + +#if SQLITE_MAX_WORKER_THREADS>0 && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ + SQLITE_THREADSAFE>0 +SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject){ + DWORD rc; + while( (rc = osWaitForSingleObjectEx(hObject, INFINITE, + TRUE))==WAIT_IO_COMPLETION ){} + return rc; +} +#endif /* ** Return true (non-zero) if we are running under WinNT, Win2K, WinXP, ** or WinCE. Return false (zero) for Win95, Win98, or WinME. ** @@ -31843,35 +34461,59 @@ ** API as long as we don't call it when running Win95/98/ME. A call to ** this routine is used to determine if the host is Win95/98/ME or ** WinNT/2K/XP so that we will know whether or not we can safely call ** the LockFileEx() API. */ -#ifndef NTDDI_WIN8 -# define NTDDI_WIN8 0x06020000 -#endif -#if SQLITE_OS_WINCE || SQLITE_OS_WINRT || !defined(SQLITE_WIN32_HAS_ANSI) +#if !defined(SQLITE_WIN32_GETVERSIONEX) || !SQLITE_WIN32_GETVERSIONEX +# define osIsNT() (1) +#elif SQLITE_OS_WINCE || SQLITE_OS_WINRT || !defined(SQLITE_WIN32_HAS_ANSI) # define osIsNT() (1) #elif !defined(SQLITE_WIN32_HAS_WIDE) # define osIsNT() (0) #else - static int osIsNT(void){ - if( sqlite3_os_type==0 ){ -#if defined(NTDDI_VERSION) && NTDDI_VERSION >= NTDDI_WIN8 - OSVERSIONINFOW sInfo; - sInfo.dwOSVersionInfoSize = sizeof(sInfo); - osGetVersionExW(&sInfo); +# define osIsNT() ((sqlite3_os_type==2) || sqlite3_win32_is_nt()) +#endif + +/* +** This function determines if the machine is running a version of Windows +** based on the NT kernel. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_win32_is_nt(void){ +#if SQLITE_OS_WINRT + /* + ** NOTE: The WinRT sub-platform is always assumed to be based on the NT + ** kernel. + */ + return 1; +#elif defined(SQLITE_WIN32_GETVERSIONEX) && SQLITE_WIN32_GETVERSIONEX + if( osInterlockedCompareExchange(&sqlite3_os_type, 0, 0)==0 ){ +#if defined(SQLITE_WIN32_HAS_ANSI) + OSVERSIONINFOA sInfo; + sInfo.dwOSVersionInfoSize = sizeof(sInfo); + osGetVersionExA(&sInfo); + osInterlockedCompareExchange(&sqlite3_os_type, + (sInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) ? 2 : 1, 0); +#elif defined(SQLITE_WIN32_HAS_WIDE) + OSVERSIONINFOW sInfo; + sInfo.dwOSVersionInfoSize = sizeof(sInfo); + osGetVersionExW(&sInfo); + osInterlockedCompareExchange(&sqlite3_os_type, + (sInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) ? 2 : 1, 0); +#endif + } + return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2; +#elif SQLITE_TEST + return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2; #else - OSVERSIONINFOA sInfo; - sInfo.dwOSVersionInfoSize = sizeof(sInfo); - osGetVersionExA(&sInfo); -#endif - sqlite3_os_type = sInfo.dwPlatformId==VER_PLATFORM_WIN32_NT ? 2 : 1; - } - return sqlite3_os_type==2; - } -#endif + /* + ** NOTE: All sub-platforms where the GetVersionEx[AW] functions are + ** deprecated are always assumed to be based on the NT kernel. + */ + return 1; +#endif +} #ifdef SQLITE_WIN32_MALLOC /* ** Allocate nBytes of memory. */ @@ -31882,11 +34524,11 @@ winMemAssertMagic(); hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); #if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) - assert ( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); + assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif assert( nBytes>=0 ); p = osHeapAlloc(hHeap, SQLITE_WIN32_HEAP_FLAGS, (SIZE_T)nBytes); if( !p ){ sqlite3_log(SQLITE_NOMEM, "failed to HeapAlloc %u bytes (%lu), heap=%p", @@ -31904,11 +34546,11 @@ winMemAssertMagic(); hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); #if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) - assert ( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); + assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); #endif if( !pPrior ) return; /* Passing NULL to HeapFree is undefined. */ if( !osHeapFree(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ){ sqlite3_log(SQLITE_NOMEM, "failed to HeapFree block %p (%lu), heap=%p", pPrior, osGetLastError(), (void*)hHeap); @@ -31925,11 +34567,11 @@ winMemAssertMagic(); hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); #if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) - assert ( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); + assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); #endif assert( nBytes>=0 ); if( !pPrior ){ p = osHeapAlloc(hHeap, SQLITE_WIN32_HEAP_FLAGS, (SIZE_T)nBytes); }else{ @@ -31953,11 +34595,11 @@ winMemAssertMagic(); hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); #if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) - assert ( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); + assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, p) ); #endif if( !p ) return 0; n = osHeapSize(hHeap, SQLITE_WIN32_HEAP_FLAGS, p); if( n==(SIZE_T)-1 ){ sqlite3_log(SQLITE_NOMEM, "failed to HeapSize block %p (%lu), heap=%p", @@ -31979,22 +34621,29 @@ */ static int winMemInit(void *pAppData){ winMemData *pWinMemData = (winMemData *)pAppData; if( !pWinMemData ) return SQLITE_ERROR; - assert( pWinMemData->magic==WINMEM_MAGIC ); + assert( pWinMemData->magic1==WINMEM_MAGIC1 ); + assert( pWinMemData->magic2==WINMEM_MAGIC2 ); #if !SQLITE_OS_WINRT && SQLITE_WIN32_HEAP_CREATE if( !pWinMemData->hHeap ){ + DWORD dwInitialSize = SQLITE_WIN32_HEAP_INIT_SIZE; + DWORD dwMaximumSize = (DWORD)sqlite3GlobalConfig.nHeap; + if( dwMaximumSize==0 ){ + dwMaximumSize = SQLITE_WIN32_HEAP_MAX_SIZE; + }else if( dwInitialSize>dwMaximumSize ){ + dwInitialSize = dwMaximumSize; + } pWinMemData->hHeap = osHeapCreate(SQLITE_WIN32_HEAP_FLAGS, - SQLITE_WIN32_HEAP_INIT_SIZE, - SQLITE_WIN32_HEAP_MAX_SIZE); + dwInitialSize, dwMaximumSize); if( !pWinMemData->hHeap ){ sqlite3_log(SQLITE_NOMEM, - "failed to HeapCreate (%lu), flags=%u, initSize=%u, maxSize=%u", - osGetLastError(), SQLITE_WIN32_HEAP_FLAGS, - SQLITE_WIN32_HEAP_INIT_SIZE, SQLITE_WIN32_HEAP_MAX_SIZE); + "failed to HeapCreate (%lu), flags=%u, initSize=%lu, maxSize=%lu", + osGetLastError(), SQLITE_WIN32_HEAP_FLAGS, dwInitialSize, + dwMaximumSize); return SQLITE_NOMEM; } pWinMemData->bOwned = TRUE; assert( pWinMemData->bOwned ); } @@ -32021,10 +34670,13 @@ */ static void winMemShutdown(void *pAppData){ winMemData *pWinMemData = (winMemData *)pAppData; if( !pWinMemData ) return; + assert( pWinMemData->magic1==WINMEM_MAGIC1 ); + assert( pWinMemData->magic2==WINMEM_MAGIC2 ); + if( pWinMemData->hHeap ){ assert( pWinMemData->hHeap!=INVALID_HANDLE_VALUE ); #if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(pWinMemData->hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif @@ -32065,11 +34717,11 @@ sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetWin32()); } #endif /* SQLITE_WIN32_MALLOC */ /* -** Convert a UTF-8 string to Microsoft Unicode (UTF-16?). +** Convert a UTF-8 string to Microsoft Unicode (UTF-16?). ** ** Space to hold the returned string is obtained from malloc. */ static LPWSTR winUtf8ToUnicode(const char *zFilename){ int nChar; @@ -32118,11 +34770,11 @@ } /* ** Convert an ANSI string to Microsoft Unicode, based on the ** current codepage settings for file apis. -** +** ** Space to hold the returned string is obtained ** from sqlite3_malloc. */ static LPWSTR winMbcsToUnicode(const char *zFilename){ int nByte; @@ -32178,11 +34830,11 @@ /* ** Convert multibyte character string to UTF-8. Space to hold the ** returned string is obtained from sqlite3_malloc(). */ -SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zFilename){ +SQLITE_API char *SQLITE_STDCALL sqlite3_win32_mbcs_to_utf8(const char *zFilename){ char *zFilenameUtf8; LPWSTR zTmpWide; zTmpWide = winMbcsToUnicode(zFilename); if( zTmpWide==0 ){ @@ -32192,14 +34844,14 @@ sqlite3_free(zTmpWide); return zFilenameUtf8; } /* -** Convert UTF-8 to multibyte character string. Space to hold the +** Convert UTF-8 to multibyte character string. Space to hold the ** returned string is obtained from sqlite3_malloc(). */ -SQLITE_API char *sqlite3_win32_utf8_to_mbcs(const char *zFilename){ +SQLITE_API char *SQLITE_STDCALL sqlite3_win32_utf8_to_mbcs(const char *zFilename){ char *zFilenameMbcs; LPWSTR zTmpWide; zTmpWide = winUtf8ToUnicode(zFilename); if( zTmpWide==0 ){ @@ -32215,11 +34867,11 @@ ** the provided arguments. The type argument must be 1 in order to set the ** data directory or 2 in order to set the temporary directory. The zValue ** argument is the name of the directory to use. The return value will be ** SQLITE_OK if successful. */ -SQLITE_API int sqlite3_win32_set_directory(DWORD type, LPCWSTR zValue){ +SQLITE_API int SQLITE_STDCALL sqlite3_win32_set_directory(DWORD type, LPCWSTR zValue){ char **ppDirectory = 0; #ifndef SQLITE_OMIT_AUTOINIT int rc = sqlite3_initialize(); if( rc ) return rc; #endif @@ -32332,15 +34984,15 @@ ** This function - winLogErrorAtLine() - is only ever called via the macro ** winLogError(). ** ** This routine is invoked after an error occurs in an OS function. ** It logs a message using sqlite3_log() containing the current value of -** error code and, if possible, the human-readable equivalent from +** error code and, if possible, the human-readable equivalent from ** FormatMessage. ** ** The first argument passed to the macro should be the error code that -** will be returned to SQLite (e.g. SQLITE_IOERR_DELETE, SQLITE_CANTOPEN). +** will be returned to SQLite (e.g. SQLITE_IOERR_DELETE, SQLITE_CANTOPEN). ** The two subsequent arguments should be the name of the OS function that ** failed and the associated file-system path, if any. */ #define winLogError(a,b,c,d) winLogErrorAtLine(a,b,c,d,__LINE__) static int winLogErrorAtLine( @@ -32367,11 +35019,11 @@ return errcode; } /* ** The number of times that a ReadFile(), WriteFile(), and DeleteFile() -** will be retried following a locking error - probably caused by +** will be retried following a locking error - probably caused by ** antivirus software. Also the initial delay before the first retry. ** The delay increases linearly with each retry. */ #ifndef SQLITE_WIN32_IOERR_RETRY # define SQLITE_WIN32_IOERR_RETRY 10 @@ -32380,10 +35032,36 @@ # define SQLITE_WIN32_IOERR_RETRY_DELAY 25 #endif static int winIoerrRetry = SQLITE_WIN32_IOERR_RETRY; static int winIoerrRetryDelay = SQLITE_WIN32_IOERR_RETRY_DELAY; +/* +** The "winIoerrCanRetry1" macro is used to determine if a particular I/O +** error code obtained via GetLastError() is eligible to be retried. It +** must accept the error code DWORD as its only argument and should return +** non-zero if the error code is transient in nature and the operation +** responsible for generating the original error might succeed upon being +** retried. The argument to this macro should be a variable. +** +** Additionally, a macro named "winIoerrCanRetry2" may be defined. If it +** is defined, it will be consulted only when the macro "winIoerrCanRetry1" +** returns zero. The "winIoerrCanRetry2" macro is completely optional and +** may be used to include additional error codes in the set that should +** result in the failing I/O operation being retried by the caller. If +** defined, the "winIoerrCanRetry2" macro must exhibit external semantics +** identical to those of the "winIoerrCanRetry1" macro. +*/ +#if !defined(winIoerrCanRetry1) +#define winIoerrCanRetry1(a) (((a)==ERROR_ACCESS_DENIED) || \ + ((a)==ERROR_SHARING_VIOLATION) || \ + ((a)==ERROR_LOCK_VIOLATION) || \ + ((a)==ERROR_DEV_NOT_EXIST) || \ + ((a)==ERROR_NETNAME_DELETED) || \ + ((a)==ERROR_SEM_TIMEOUT) || \ + ((a)==ERROR_NETWORK_UNREACHABLE)) +#endif + /* ** If a ReadFile() or WriteFile() error occurs, invoke this routine ** to see if it should be retried. Return TRUE to retry. Return FALSE ** to give up with an error. */ @@ -32393,31 +35071,36 @@ if( pError ){ *pError = e; } return 0; } - if( e==ERROR_ACCESS_DENIED || - e==ERROR_LOCK_VIOLATION || - e==ERROR_SHARING_VIOLATION ){ + if( winIoerrCanRetry1(e) ){ + sqlite3_win32_sleep(winIoerrRetryDelay*(1+*pnRetry)); + ++*pnRetry; + return 1; + } +#if defined(winIoerrCanRetry2) + else if( winIoerrCanRetry2(e) ){ sqlite3_win32_sleep(winIoerrRetryDelay*(1+*pnRetry)); ++*pnRetry; return 1; } +#endif if( pError ){ *pError = e; } return 0; } /* ** Log a I/O error retry episode. */ -static void winLogIoerr(int nRetry){ +static void winLogIoerr(int nRetry, int lineno){ if( nRetry ){ - sqlite3_log(SQLITE_IOERR, - "delayed %dms for lock/sharing conflict", - winIoerrRetryDelay*nRetry*(nRetry+1)/2 + sqlite3_log(SQLITE_IOERR, + "delayed %dms for lock/sharing conflict at line %d", + winIoerrRetryDelay*nRetry*(nRetry+1)/2, lineno ); } } #if SQLITE_OS_WINCE @@ -32505,21 +35188,21 @@ "winceCreateLock1", zFilename); } /* Acquire the mutex before continuing */ winceMutexAcquire(pFile->hMutex); - - /* Since the names of named mutexes, semaphores, file mappings etc are + + /* Since the names of named mutexes, semaphores, file mappings etc are ** case-sensitive, take advantage of that by uppercasing the mutex name ** and using that as the shared filemapping name. */ osCharUpperW(zName); pFile->hShared = osCreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(winceLock), - zName); + zName); - /* Set a flag that indicates we're the first to create the memory so it + /* Set a flag that indicates we're the first to create the memory so it ** must be zero-initialized */ lastErrno = osGetLastError(); if (lastErrno == ERROR_ALREADY_EXISTS){ bInit = FALSE; } @@ -32526,11 +35209,11 @@ sqlite3_free(zName); /* If we succeeded in making the shared memory handle, map it. */ if( pFile->hShared ){ - pFile->shared = (winceLock*)osMapViewOfFile(pFile->hShared, + pFile->shared = (winceLock*)osMapViewOfFile(pFile->hShared, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, sizeof(winceLock)); /* If mapping failed, close the shared memory handle and erase it */ if( !pFile->shared ){ pFile->lastErrno = osGetLastError(); winLogError(SQLITE_IOERR, pFile->lastErrno, @@ -32552,11 +35235,11 @@ winceMutexRelease(pFile->hMutex); osCloseHandle(pFile->hMutex); pFile->hMutex = NULL; return SQLITE_IOERR; } - + /* Initialize the shared memory if we're supposed to */ if( bInit ){ memset(pFile->shared, 0, sizeof(winceLock)); } @@ -32590,17 +35273,17 @@ /* De-reference and close our copy of the shared memory handle */ osUnmapViewOfFile(pFile->shared); osCloseHandle(pFile->hShared); /* Done with the mutex */ - winceMutexRelease(pFile->hMutex); + winceMutexRelease(pFile->hMutex); osCloseHandle(pFile->hMutex); pFile->hMutex = NULL; } } -/* +/* ** An implementation of the LockFile() API of Windows for CE */ static BOOL winceLockFile( LPHANDLE phFile, DWORD dwFileOffsetLow, @@ -32807,12 +35490,12 @@ #ifndef INVALID_SET_FILE_POINTER # define INVALID_SET_FILE_POINTER ((DWORD)-1) #endif /* -** Move the current position of the file handle passed as the first -** argument to offset iOffset within the file. If successful, return 0. +** Move the current position of the file handle passed as the first +** argument to offset iOffset within the file. If successful, return 0. ** Otherwise, set pFile->lastErrno and return non-zero. */ static int winSeekFile(winFile *pFile, sqlite3_int64 iOffset){ #if !SQLITE_OS_WINRT LONG upperBits; /* Most sig. 32 bits of new offset */ @@ -32823,15 +35506,15 @@ OSTRACE(("SEEK file=%p, offset=%lld\n", pFile->h, iOffset)); upperBits = (LONG)((iOffset>>32) & 0x7fffffff); lowerBits = (LONG)(iOffset & 0xffffffff); - /* API oddity: If successful, SetFilePointer() returns a dword + /* API oddity: If successful, SetFilePointer() returns a dword ** containing the lower 32-bits of the new file-offset. Or, if it fails, - ** it returns INVALID_SET_FILE_POINTER. However according to MSDN, - ** INVALID_SET_FILE_POINTER may also be a valid new offset. So to determine - ** whether an error has actually occurred, it is also necessary to call + ** it returns INVALID_SET_FILE_POINTER. However according to MSDN, + ** INVALID_SET_FILE_POINTER may also be a valid new offset. So to determine + ** whether an error has actually occurred, it is also necessary to call ** GetLastError(). */ dwRet = osSetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN); if( (dwRet==INVALID_SET_FILE_POINTER @@ -32893,15 +35576,15 @@ assert( id!=0 ); #ifndef SQLITE_OMIT_WAL assert( pFile->pShm==0 ); #endif assert( pFile->h!=NULL && pFile->h!=INVALID_HANDLE_VALUE ); - OSTRACE(("CLOSE file=%p\n", pFile->h)); + OSTRACE(("CLOSE pid=%lu, pFile=%p, file=%p\n", + osGetCurrentProcessId(), pFile, pFile->h)); #if SQLITE_MAX_MMAP_SIZE>0 - rc = winUnmapfile(pFile); - if( rc!=SQLITE_OK ) return rc; + winUnmapfile(pFile); #endif do{ rc = osCloseHandle(pFile->h); /* SimulateIOError( rc=0; cnt=MX_CLOSE_ATTEMPT; ); */ @@ -32911,11 +35594,11 @@ winceDestroyLock(pFile); if( pFile->zDeleteOnClose ){ int cnt = 0; while( osDeleteFileW(pFile->zDeleteOnClose)==0 - && osGetFileAttributesW(pFile->zDeleteOnClose)!=0xffffffff + && osGetFileAttributesW(pFile->zDeleteOnClose)!=0xffffffff && cnt++ < WINCE_DELETION_ATTEMPTS ){ sqlite3_win32_sleep(100); /* Wait a little before trying again */ } sqlite3_free(pFile->zDeleteOnClose); @@ -32923,11 +35606,12 @@ #endif if( rc ){ pFile->h = NULL; } OpenCounter(-1); - OSTRACE(("CLOSE file=%p, rc=%s\n", pFile->h, rc ? "ok" : "failed")); + OSTRACE(("CLOSE pid=%lu, pFile=%p, file=%p, rc=%s\n", + osGetCurrentProcessId(), pFile, pFile->h, rc ? "ok" : "failed")); return rc ? SQLITE_OK : winLogError(SQLITE_IOERR_CLOSE, osGetLastError(), "winClose", pFile->zPath); } @@ -32940,11 +35624,11 @@ sqlite3_file *id, /* File to read from */ void *pBuf, /* Write content into this buffer */ int amt, /* Number of bytes to read */ sqlite3_int64 offset /* Begin reading at this offset */ ){ -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) OVERLAPPED overlapped; /* The offset for ReadFile. */ #endif winFile *pFile = (winFile*)id; /* file handle */ DWORD nRead; /* Number of bytes actually read from file */ int nRetry = 0; /* Number of retrys */ @@ -32951,20 +35635,22 @@ assert( id!=0 ); assert( amt>0 ); assert( offset>=0 ); SimulateIOError(return SQLITE_IOERR_READ); - OSTRACE(("READ file=%p, buffer=%p, amount=%d, offset=%lld, lock=%d\n", + OSTRACE(("READ pid=%lu, pFile=%p, file=%p, buffer=%p, amount=%d, " + "offset=%lld, lock=%d\n", osGetCurrentProcessId(), pFile, pFile->h, pBuf, amt, offset, pFile->locktype)); #if SQLITE_MAX_MMAP_SIZE>0 /* Deal with as much of this read request as possible by transfering ** data from the memory mapping using memcpy(). */ if( offset<pFile->mmapSize ){ if( offset+amt <= pFile->mmapSize ){ memcpy(pBuf, &((u8 *)(pFile->pMapRegion))[offset], amt); - OSTRACE(("READ-MMAP file=%p, rc=SQLITE_OK\n", pFile->h)); + OSTRACE(("READ-MMAP pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); return SQLITE_OK; }else{ int nCopy = (int)(pFile->mmapSize - offset); memcpy(pBuf, &((u8 *)(pFile->pMapRegion))[offset], nCopy); pBuf = &((u8 *)pBuf)[nCopy]; @@ -32972,13 +35658,14 @@ offset += nCopy; } } #endif -#if SQLITE_OS_WINCE +#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED) if( winSeekFile(pFile, offset) ){ - OSTRACE(("READ file=%p, rc=SQLITE_FULL\n", pFile->h)); + OSTRACE(("READ pid=%lu, pFile=%p, file=%p, rc=SQLITE_FULL\n", + osGetCurrentProcessId(), pFile, pFile->h)); return SQLITE_FULL; } while( !osReadFile(pFile->h, pBuf, amt, &nRead, 0) ){ #else memset(&overlapped, 0, sizeof(OVERLAPPED)); @@ -32988,23 +35675,26 @@ osGetLastError()!=ERROR_HANDLE_EOF ){ #endif DWORD lastErrno; if( winRetryIoerr(&nRetry, &lastErrno) ) continue; pFile->lastErrno = lastErrno; - OSTRACE(("READ file=%p, rc=SQLITE_IOERR_READ\n", pFile->h)); + OSTRACE(("READ pid=%lu, pFile=%p, file=%p, rc=SQLITE_IOERR_READ\n", + osGetCurrentProcessId(), pFile, pFile->h)); return winLogError(SQLITE_IOERR_READ, pFile->lastErrno, "winRead", pFile->zPath); } - winLogIoerr(nRetry); + winLogIoerr(nRetry, __LINE__); if( nRead<(DWORD)amt ){ /* Unread parts of the buffer must be zero-filled */ memset(&((char*)pBuf)[nRead], 0, amt-nRead); - OSTRACE(("READ file=%p, rc=SQLITE_IOERR_SHORT_READ\n", pFile->h)); + OSTRACE(("READ pid=%lu, pFile=%p, file=%p, rc=SQLITE_IOERR_SHORT_READ\n", + osGetCurrentProcessId(), pFile, pFile->h)); return SQLITE_IOERR_SHORT_READ; } - OSTRACE(("READ file=%p, rc=SQLITE_OK\n", pFile->h)); + OSTRACE(("READ pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); return SQLITE_OK; } /* ** Write data from a buffer into a file. Return SQLITE_OK on success @@ -33023,20 +35713,22 @@ assert( amt>0 ); assert( pFile ); SimulateIOError(return SQLITE_IOERR_WRITE); SimulateDiskfullError(return SQLITE_FULL); - OSTRACE(("WRITE file=%p, buffer=%p, amount=%d, offset=%lld, lock=%d\n", + OSTRACE(("WRITE pid=%lu, pFile=%p, file=%p, buffer=%p, amount=%d, " + "offset=%lld, lock=%d\n", osGetCurrentProcessId(), pFile, pFile->h, pBuf, amt, offset, pFile->locktype)); #if SQLITE_MAX_MMAP_SIZE>0 /* Deal with as much of this write request as possible by transfering ** data from the memory mapping using memcpy(). */ if( offset<pFile->mmapSize ){ if( offset+amt <= pFile->mmapSize ){ memcpy(&((u8 *)(pFile->pMapRegion))[offset], pBuf, amt); - OSTRACE(("WRITE-MMAP file=%p, rc=SQLITE_OK\n", pFile->h)); + OSTRACE(("WRITE-MMAP pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); return SQLITE_OK; }else{ int nCopy = (int)(pFile->mmapSize - offset); memcpy(&((u8 *)(pFile->pMapRegion))[offset], pBuf, nCopy); pBuf = &((u8 *)pBuf)[nCopy]; @@ -33044,32 +35736,32 @@ offset += nCopy; } } #endif -#if SQLITE_OS_WINCE +#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED) rc = winSeekFile(pFile, offset); if( rc==0 ){ #else { #endif -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) OVERLAPPED overlapped; /* The offset for WriteFile. */ #endif u8 *aRem = (u8 *)pBuf; /* Data yet to be written */ int nRem = amt; /* Number of bytes yet to be written */ DWORD nWrite; /* Bytes written by each WriteFile() call */ DWORD lastErrno = NO_ERROR; /* Value returned by GetLastError() */ -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) memset(&overlapped, 0, sizeof(OVERLAPPED)); overlapped.Offset = (LONG)(offset & 0xffffffff); overlapped.OffsetHigh = (LONG)((offset>>32) & 0x7fffffff); #endif while( nRem>0 ){ -#if SQLITE_OS_WINCE +#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED) if( !osWriteFile(pFile->h, aRem, nRem, &nWrite, 0) ){ #else if( !osWriteFile(pFile->h, aRem, nRem, &nWrite, &overlapped) ){ #endif if( winRetryIoerr(&nRetry, &lastErrno) ) continue; @@ -33078,11 +35770,11 @@ assert( nWrite==0 || nWrite<=(DWORD)nRem ); if( nWrite==0 || nWrite>(DWORD)nRem ){ lastErrno = osGetLastError(); break; } -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) offset += nWrite; overlapped.Offset = (LONG)(offset & 0xffffffff); overlapped.OffsetHigh = (LONG)((offset>>32) & 0x7fffffff); #endif aRem += nWrite; @@ -33095,21 +35787,24 @@ } if( rc ){ if( ( pFile->lastErrno==ERROR_HANDLE_DISK_FULL ) || ( pFile->lastErrno==ERROR_DISK_FULL )){ - OSTRACE(("WRITE file=%p, rc=SQLITE_FULL\n", pFile->h)); + OSTRACE(("WRITE pid=%lu, pFile=%p, file=%p, rc=SQLITE_FULL\n", + osGetCurrentProcessId(), pFile, pFile->h)); return winLogError(SQLITE_FULL, pFile->lastErrno, "winWrite1", pFile->zPath); } - OSTRACE(("WRITE file=%p, rc=SQLITE_IOERR_WRITE\n", pFile->h)); + OSTRACE(("WRITE pid=%lu, pFile=%p, file=%p, rc=SQLITE_IOERR_WRITE\n", + osGetCurrentProcessId(), pFile, pFile->h)); return winLogError(SQLITE_IOERR_WRITE, pFile->lastErrno, "winWrite2", pFile->zPath); }else{ - winLogIoerr(nRetry); + winLogIoerr(nRetry, __LINE__); } - OSTRACE(("WRITE file=%p, rc=SQLITE_OK\n", pFile->h)); + OSTRACE(("WRITE pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); return SQLITE_OK; } /* ** Truncate an open file to a specified size @@ -33119,12 +35814,12 @@ int rc = SQLITE_OK; /* Return code for this function */ DWORD lastErrno; assert( pFile ); SimulateIOError(return SQLITE_IOERR_TRUNCATE); - OSTRACE(("TRUNCATE file=%p, size=%lld, lock=%d\n", - pFile->h, nByte, pFile->locktype)); + OSTRACE(("TRUNCATE pid=%lu, pFile=%p, file=%p, size=%lld, lock=%d\n", + osGetCurrentProcessId(), pFile, pFile->h, nByte, pFile->locktype)); /* If the user has configured a chunk-size for this file, truncate the ** file so that it consists of an integer number of chunks (i.e. the ** actual file size after the operation may be larger than the requested ** size). @@ -33152,11 +35847,12 @@ if( pFile->pMapRegion && nByte<pFile->mmapSize ){ pFile->mmapSize = nByte; } #endif - OSTRACE(("TRUNCATE file=%p, rc=%s\n", pFile->h, sqlite3ErrName(rc))); + OSTRACE(("TRUNCATE pid=%lu, pFile=%p, file=%p, rc=%s\n", + osGetCurrentProcessId(), pFile, pFile->h, sqlite3ErrName(rc))); return rc; } #ifdef SQLITE_TEST /* @@ -33197,12 +35893,13 @@ /* Unix cannot, but some systems may return SQLITE_FULL from here. This ** line is to test that doing so does not cause any problems. */ SimulateDiskfullError( return SQLITE_FULL ); - OSTRACE(("SYNC file=%p, flags=%x, lock=%d\n", - pFile->h, flags, pFile->locktype)); + OSTRACE(("SYNC pid=%lu, pFile=%p, file=%p, flags=%x, lock=%d\n", + osGetCurrentProcessId(), pFile, pFile->h, flags, + pFile->locktype)); #ifndef SQLITE_TEST UNUSED_PARAMETER(flags); #else if( (flags&0x0F)==SQLITE_SYNC_FULL ){ @@ -33213,21 +35910,24 @@ /* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a ** no-op */ #ifdef SQLITE_NO_SYNC - OSTRACE(("SYNC-NOP file=%p, rc=SQLITE_OK\n", pFile->h)); + OSTRACE(("SYNC-NOP pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); return SQLITE_OK; #else rc = osFlushFileBuffers(pFile->h); SimulateIOError( rc=FALSE ); if( rc ){ - OSTRACE(("SYNC file=%p, rc=SQLITE_OK\n", pFile->h)); + OSTRACE(("SYNC pid=%lu, pFile=%p, file=%p, rc=SQLITE_OK\n", + osGetCurrentProcessId(), pFile, pFile->h)); return SQLITE_OK; }else{ pFile->lastErrno = osGetLastError(); - OSTRACE(("SYNC file=%p, rc=SQLITE_IOERR_FSYNC\n", pFile->h)); + OSTRACE(("SYNC pid=%lu, pFile=%p, file=%p, rc=SQLITE_IOERR_FSYNC\n", + osGetCurrentProcessId(), pFile, pFile->h)); return winLogError(SQLITE_IOERR_FSYNC, pFile->lastErrno, "winSync", pFile->zPath); } #endif } @@ -33339,11 +36039,11 @@ #endif if( res == 0 ){ pFile->lastErrno = osGetLastError(); /* No need to log a failure to lock */ } - OSTRACE(("READ-LOCK file=%p, rc=%s\n", pFile->h, sqlite3ErrName(res))); + OSTRACE(("READ-LOCK file=%p, result=%d\n", pFile->h, res)); return res; } /* ** Undo a readlock @@ -33363,11 +36063,11 @@ if( res==0 && ((lastErrno = osGetLastError())!=ERROR_NOT_LOCKED) ){ pFile->lastErrno = lastErrno; winLogError(SQLITE_IOERR_UNLOCK, pFile->lastErrno, "winUnlockReadLock", pFile->zPath); } - OSTRACE(("READ-UNLOCK file=%p, rc=%s\n", pFile->h, sqlite3ErrName(res))); + OSTRACE(("READ-UNLOCK file=%p, result=%d\n", pFile->h, res)); return res; } /* ** Lock the file with the lock specified by parameter locktype - one @@ -33438,12 +36138,20 @@ ** around problems caused by indexing and/or anti-virus software on ** Windows systems. ** If you are using this code as a model for alternative VFSes, do not ** copy this retry logic. It is a hack intended for Windows only. */ - OSTRACE(("LOCK-PENDING-FAIL file=%p, count=%d, rc=%s\n", - pFile->h, cnt, sqlite3ErrName(res))); + lastErrno = osGetLastError(); + OSTRACE(("LOCK-PENDING-FAIL file=%p, count=%d, result=%d\n", + pFile->h, cnt, res)); + if( lastErrno==ERROR_INVALID_HANDLE ){ + pFile->lastErrno = lastErrno; + rc = SQLITE_IOERR_LOCK; + OSTRACE(("LOCK-FAIL file=%p, count=%d, rc=%s\n", + pFile->h, cnt, sqlite3ErrName(rc))); + return rc; + } if( cnt ) sqlite3_win32_sleep(1); } gotPendingLock = res; if( !res ){ lastErrno = osGetLastError(); @@ -33524,29 +36232,29 @@ ** This routine checks if there is a RESERVED lock held on the specified ** file by this or any other process. If such a lock is held, return ** non-zero, otherwise zero. */ static int winCheckReservedLock(sqlite3_file *id, int *pResOut){ - int rc; + int res; winFile *pFile = (winFile*)id; SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); OSTRACE(("TEST-WR-LOCK file=%p, pResOut=%p\n", pFile->h, pResOut)); assert( id!=0 ); if( pFile->locktype>=RESERVED_LOCK ){ - rc = 1; - OSTRACE(("TEST-WR-LOCK file=%p, rc=%d (local)\n", pFile->h, rc)); + res = 1; + OSTRACE(("TEST-WR-LOCK file=%p, result=%d (local)\n", pFile->h, res)); }else{ - rc = winLockFile(&pFile->h, SQLITE_LOCKFILEEX_FLAGS,RESERVED_BYTE, 0, 1, 0); - if( rc ){ + res = winLockFile(&pFile->h, SQLITE_LOCKFILEEX_FLAGS,RESERVED_BYTE, 0, 1, 0); + if( res ){ winUnlockFile(&pFile->h, RESERVED_BYTE, 0, 1, 0); } - rc = !rc; - OSTRACE(("TEST-WR-LOCK file=%p, rc=%d (remote)\n", pFile->h, rc)); + res = !res; + OSTRACE(("TEST-WR-LOCK file=%p, result=%d (remote)\n", pFile->h, res)); } - *pResOut = rc; + *pResOut = res; OSTRACE(("TEST-WR-LOCK file=%p, pResOut=%p, *pResOut=%d, rc=SQLITE_OK\n", pFile->h, pResOut, *pResOut)); return SQLITE_OK; } @@ -33593,11 +36301,11 @@ pFile->h, pFile->locktype, sqlite3ErrName(rc))); return rc; } /* -** If *pArg is inititially negative then this is a query. Set *pArg to +** If *pArg is initially negative then this is a query. Set *pArg to ** 1 or 0 depending on whether or not bit mask of pFile->ctrlFlags is set. ** ** If *pArg is 0 or 1, then clear or set the mask bit of pFile->ctrlFlags. */ static void winModeBit(winFile *pFile, unsigned char mask, int *pArg){ @@ -33664,11 +36372,11 @@ winModeBit(pFile, WINFILE_PSOW, (int*)pArg); OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); return SQLITE_OK; } case SQLITE_FCNTL_VFSNAME: { - *(char**)pArg = sqlite3_mprintf("win32"); + *(char**)pArg = sqlite3_mprintf("%s", pFile->pVfs->zName); OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); return SQLITE_OK; } case SQLITE_FCNTL_WIN32_AV_RETRY: { int *a = (int*)pArg; @@ -33683,10 +36391,21 @@ a[1] = winIoerrRetryDelay; } OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); return SQLITE_OK; } +#ifdef SQLITE_TEST + case SQLITE_FCNTL_WIN32_SET_HANDLE: { + LPHANDLE phFile = (LPHANDLE)pArg; + HANDLE hOldFile = pFile->h; + pFile->h = *phFile; + *phFile = hOldFile; + OSTRACE(("FCNTL oldFile=%p, newFile=%p, rc=SQLITE_OK\n", + hOldFile, pFile->h)); + return SQLITE_OK; + } +#endif case SQLITE_FCNTL_TEMPFILENAME: { char *zTFile = 0; int rc = winGetTempname(pFile->pVfs, &zTFile); if( rc==SQLITE_OK ){ *(char**)pArg = zTFile; @@ -33703,11 +36422,11 @@ } *(i64*)pArg = pFile->mmapSizeMax; if( newLimit>=0 && newLimit!=pFile->mmapSizeMax && pFile->nFetchOut==0 ){ pFile->mmapSizeMax = newLimit; if( pFile->mmapSize>0 ){ - (void)winUnmapfile(pFile); + winUnmapfile(pFile); rc = winMapfile(pFile, -1); } } OSTRACE(("FCNTL file=%p, rc=%s\n", pFile->h, sqlite3ErrName(rc))); return rc; @@ -33740,27 +36459,27 @@ winFile *p = (winFile*)id; return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN | ((p->ctrlFlags & WINFILE_PSOW)?SQLITE_IOCAP_POWERSAFE_OVERWRITE:0); } -/* +/* ** Windows will only let you create file view mappings ** on allocation size granularity boundaries. ** During sqlite3_os_init() we do a GetSystemInfo() ** to get the granularity size. */ -SYSTEM_INFO winSysInfo; +static SYSTEM_INFO winSysInfo; #ifndef SQLITE_OMIT_WAL /* ** Helper functions to obtain and relinquish the global mutex. The -** global mutex is used to protect the winLockInfo objects used by +** global mutex is used to protect the winLockInfo objects used by ** this file, all of which may be shared by multiple threads. ** -** Function winShmMutexHeld() is used to assert() that the global mutex -** is held when required. This function is only used as part of assert() +** Function winShmMutexHeld() is used to assert() that the global mutex +** is held when required. This function is only used as part of assert() ** statements. e.g. ** ** winShmEnterMutex() ** assert( winShmMutexHeld() ); ** winShmLeaveMutex() @@ -33769,11 +36488,11 @@ sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); } static void winShmLeaveMutex(void){ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); } -#ifdef SQLITE_DEBUG +#ifndef NDEBUG static int winShmMutexHeld(void) { return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); } #endif @@ -33786,14 +36505,14 @@ ** ** winShmMutexHeld() must be true when creating or destroying ** this object or while reading or writing the following fields: ** ** nRef -** pNext +** pNext ** ** The following fields are read-only after the object is created: -** +** ** fid ** zFilename ** ** Either winShmNode.mutex must be held or winShmNode.nRef==0 and ** winShmMutexHeld() is true when reading or writing any other field @@ -33885,11 +36604,11 @@ /* Initialize the locking parameters */ DWORD dwFlags = LOCKFILE_FAIL_IMMEDIATELY; if( lockType == _SHM_WRLCK ) dwFlags |= LOCKFILE_EXCLUSIVE_LOCK; rc = winLockFile(&pFile->hFile.h, dwFlags, ofst, 0, nByte, 0); } - + if( rc!= 0 ){ rc = SQLITE_OK; }else{ pFile->lastErrno = osGetLastError(); rc = SQLITE_BUSY; @@ -33920,11 +36639,11 @@ osGetCurrentProcessId(), deleteFlag)); pp = &winShmNodeList; while( (p = *pp)!=0 ){ if( p->nRef==0 ){ int i; - if( p->mutex ) sqlite3_mutex_free(p->mutex); + if( p->mutex ){ sqlite3_mutex_free(p->mutex); } for(i=0; i<p->nRegion; i++){ BOOL bRc = osUnmapViewOfFile(p->aRegion[i].pMap); OSTRACE(("SHM-PURGE-UNMAP pid=%lu, region=%d, rc=%s\n", osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); UNUSED_VARIABLE_VALUE(bRc); @@ -33981,11 +36700,11 @@ sqlite3_free(p); return SQLITE_IOERR_NOMEM; } pNew->zFilename = (char*)&pNew[1]; sqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath); - sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename); + sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename); /* Look to see if there is an existing winShmNode that can be used. ** If no matching winShmNode currently exists, create a new one. */ winShmEnterMutex(); @@ -34018,11 +36737,11 @@ if( SQLITE_OK!=rc ){ goto shm_open_err; } /* Check to see if another process is holding the dead-man switch. - ** If not, truncate the file to zero length. + ** If not, truncate the file to zero length. */ if( winShmSystemLock(pShmNode, _SHM_WRLCK, WIN_SHM_DMS, 1)==SQLITE_OK ){ rc = winTruncate((sqlite3_file *)&pShmNode->hFile, 0); if( rc!=SQLITE_OK ){ rc = winLogError(SQLITE_IOERR_SHMOPEN, osGetLastError(), @@ -34047,11 +36766,11 @@ /* The reference count on pShmNode has already been incremented under ** the cover of the winShmEnterMutex() mutex and the pointer from the ** new (struct winShm) object to the pShmNode has been set. All that is ** left to do is to link the new object into the linked list starting - ** at pShmNode->pFirst. This must be done while holding the pShmNode->mutex + ** at pShmNode->pFirst. This must be done while holding the pShmNode->mutex ** mutex. */ sqlite3_mutex_enter(pShmNode->mutex); p->pNext = pShmNode->pFirst; pShmNode->pFirst = p; @@ -34067,11 +36786,11 @@ winShmLeaveMutex(); return rc; } /* -** Close a connection to shared-memory. Delete the underlying +** Close a connection to shared-memory. Delete the underlying ** storage if deleteFlag is true. */ static int winShmUnmap( sqlite3_file *fd, /* Database holding shared memory */ int deleteFlag /* Delete after closing if true */ @@ -34156,11 +36875,11 @@ /* Undo the local locks */ if( rc==SQLITE_OK ){ p->exclMask &= ~mask; p->sharedMask &= ~mask; - } + } }else if( flags & SQLITE_SHM_SHARED ){ u16 allShared = 0; /* Union of locks held by connections other than "p" */ /* Find out which shared locks are already held by sibling connections. ** If any sibling already holds an exclusive lock, go ahead and return @@ -34195,11 +36914,11 @@ if( (pX->exclMask & mask)!=0 || (pX->sharedMask & mask)!=0 ){ rc = SQLITE_BUSY; break; } } - + /* Get the exclusive locks at the system level. Then if successful ** also mark the local connection as being locked. */ if( rc==SQLITE_OK ){ rc = winShmSystemLock(pShmNode, _SHM_WRLCK, ofst+WIN_SHM_BASE, n); @@ -34215,11 +36934,11 @@ sqlite3ErrName(rc))); return rc; } /* -** Implement a memory barrier or memory fence on shared memory. +** Implement a memory barrier or memory fence on shared memory. ** ** All loads and stores begun before the barrier must complete before ** any load or store begun after the barrier. */ static void winShmBarrier( @@ -34230,26 +36949,26 @@ winShmEnterMutex(); winShmLeaveMutex(); } /* -** This function is called to obtain a pointer to region iRegion of the -** shared-memory associated with the database file fd. Shared-memory regions -** are numbered starting from zero. Each shared-memory region is szRegion +** This function is called to obtain a pointer to region iRegion of the +** shared-memory associated with the database file fd. Shared-memory regions +** are numbered starting from zero. Each shared-memory region is szRegion ** bytes in size. ** ** If an error occurs, an error code is returned and *pp is set to NULL. ** ** Otherwise, if the isWrite parameter is 0 and the requested shared-memory ** region has not been allocated (by any client, including one running in a -** separate process), then *pp is set to NULL and SQLITE_OK returned. If -** isWrite is non-zero and the requested shared-memory region has not yet +** separate process), then *pp is set to NULL and SQLITE_OK returned. If +** isWrite is non-zero and the requested shared-memory region has not yet ** been allocated, it is allocated by this function. ** ** If the shared-memory region has already been allocated or is allocated by -** this call as described above, then it is mapped into this processes -** address space (if it is not already), *pp is set to point to the mapped +** this call as described above, then it is mapped into this processes +** address space (if it is not already), *pp is set to point to the mapped ** memory and SQLITE_OK returned. */ static int winShmMap( sqlite3_file *fd, /* Handle open on database file */ int iRegion, /* Region to retrieve */ @@ -34256,20 +36975,20 @@ int szRegion, /* Size of regions */ int isWrite, /* True to extend file if necessary */ void volatile **pp /* OUT: Mapped memory */ ){ winFile *pDbFd = (winFile*)fd; - winShm *p = pDbFd->pShm; + winShm *pShm = pDbFd->pShm; winShmNode *pShmNode; int rc = SQLITE_OK; - if( !p ){ + if( !pShm ){ rc = winOpenSharedMemory(pDbFd); if( rc!=SQLITE_OK ) return rc; - p = pDbFd->pShm; + pShm = pDbFd->pShm; } - pShmNode = p->pShmNode; + pShmNode = pShm->pShmNode; sqlite3_mutex_enter(pShmNode->mutex); assert( szRegion==pShmNode->szRegion || pShmNode->nRegion==0 ); if( pShmNode->nRegion<=iRegion ){ @@ -34317,21 +37036,21 @@ pShmNode->aRegion = apNew; while( pShmNode->nRegion<=iRegion ){ HANDLE hMap = NULL; /* file-mapping handle */ void *pMap = 0; /* Mapped memory region */ - + #if SQLITE_OS_WINRT hMap = osCreateFileMappingFromApp(pShmNode->hFile.h, NULL, PAGE_READWRITE, nByte, NULL ); #elif defined(SQLITE_WIN32_HAS_WIDE) - hMap = osCreateFileMappingW(pShmNode->hFile.h, + hMap = osCreateFileMappingW(pShmNode->hFile.h, NULL, PAGE_READWRITE, 0, nByte, NULL ); #elif defined(SQLITE_WIN32_HAS_ANSI) - hMap = osCreateFileMappingA(pShmNode->hFile.h, + hMap = osCreateFileMappingA(pShmNode->hFile.h, NULL, PAGE_READWRITE, 0, nByte, NULL ); #endif OSTRACE(("SHM-MAP-CREATE pid=%lu, region=%d, size=%d, rc=%s\n", osGetCurrentProcessId(), pShmNode->nRegion, nByte, @@ -34424,18 +37143,18 @@ return SQLITE_OK; } /* ** Memory map or remap the file opened by file-descriptor pFd (if the file -** is already mapped, the existing mapping is replaced by the new). Or, if -** there already exists a mapping for this file, and there are still +** is already mapped, the existing mapping is replaced by the new). Or, if +** there already exists a mapping for this file, and there are still ** outstanding xFetch() references to it, this function is a no-op. ** -** If parameter nByte is non-negative, then it is the requested size of -** the mapping to create. Otherwise, if nByte is less than zero, then the +** If parameter nByte is non-negative, then it is the requested size of +** the mapping to create. Otherwise, if nByte is less than zero, then the ** requested size is the size of the file on disk. The actual size of the -** created mapping is either the requested size or the value configured +** created mapping is either the requested size or the value configured ** using SQLITE_FCNTL_MMAP_SIZE, whichever is smaller. ** ** SQLITE_OK is returned if no error occurs (even if the mapping is not ** recreated as a result of outstanding references) or an SQLite error ** code otherwise. @@ -34460,11 +37179,11 @@ } if( nMap>pFd->mmapSizeMax ){ nMap = pFd->mmapSizeMax; } nMap &= ~(sqlite3_int64)(winSysInfo.dwPageSize - 1); - + if( nMap==0 && pFd->mmapSize>0 ){ winUnmapfile(pFd); } if( nMap!=pFd->mmapSize ){ void *pNew = 0; @@ -34532,11 +37251,11 @@ ** If such a pointer can be obtained, store it in *pp and return SQLITE_OK. ** Or, if one cannot but no error occurs, set *pp to 0 and return SQLITE_OK. ** Finally, if an error does occur, return an SQLite error code. The final ** value of *pp is undefined in this case. ** -** If this function does return a pointer, the caller must eventually +** If this function does return a pointer, the caller must eventually ** release the reference by calling winUnfetch(). */ static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ #if SQLITE_MAX_MMAP_SIZE>0 winFile *pFd = (winFile*)fd; /* The underlying database file */ @@ -34567,24 +37286,24 @@ osGetCurrentProcessId(), fd, pp, *pp)); return SQLITE_OK; } /* -** If the third argument is non-NULL, then this function releases a +** If the third argument is non-NULL, then this function releases a ** reference obtained by an earlier call to winFetch(). The second ** argument passed to this function must be the same as the corresponding -** argument that was passed to the winFetch() invocation. +** argument that was passed to the winFetch() invocation. ** -** Or, if the third argument is NULL, then this function is being called -** to inform the VFS layer that, according to POSIX, any existing mapping +** Or, if the third argument is NULL, then this function is being called +** to inform the VFS layer that, according to POSIX, any existing mapping ** may now be invalid and should be unmapped. */ static int winUnfetch(sqlite3_file *fd, i64 iOff, void *p){ #if SQLITE_MAX_MMAP_SIZE>0 winFile *pFd = (winFile*)fd; /* The underlying database file */ - /* If p==0 (unmap the entire file) then there must be no outstanding + /* If p==0 (unmap the entire file) then there must be no outstanding ** xFetch references. Or, if p!=0 (meaning it is an xFetch reference), ** then there must be at least one outstanding. */ assert( (p==0)==(pFd->nFetchOut==0) ); /* If p!=0, it must match the iOff value. */ @@ -34596,11 +37315,11 @@ if( p ){ pFd->nFetchOut--; }else{ /* FIXME: If Windows truly always prevents truncating or deleting a ** file while a mapping is held, then the following winUnmapfile() call - ** is unnecessary can can be omitted - potentially improving + ** is unnecessary can be omitted - potentially improving ** performance. */ winUnmapfile(pFd); } assert( pFd->nFetchOut>=0 ); @@ -34648,11 +37367,11 @@ ** ** This division contains the implementation of methods on the ** sqlite3_vfs object. */ -#if 0 +#if defined(__CYGWIN__) /* ** Convert a filename from whatever the underlying operating system ** supports for filenames into UTF-8. Space to hold the result is ** obtained from malloc and must be freed by the calling function. */ @@ -34691,16 +37410,25 @@ return zConverted; } /* ** This function returns non-zero if the specified UTF-8 string buffer -** ends with a directory separator character. +** ends with a directory separator character or one was successfully +** added to it. */ -static int winEndsInDirSep(char *zBuf){ +static int winMakeEndInDirSep(int nBuf, char *zBuf){ if( zBuf ){ int nLen = sqlite3Strlen30(zBuf); - return nLen>0 && winIsDirSep(zBuf[nLen-1]); + if( nLen>0 ){ + if( winIsDirSep(zBuf[nLen-1]) ){ + return 1; + }else if( nLen+1<nBuf ){ + zBuf[nLen] = winGetDirSep(); + zBuf[nLen+1] = '\0'; + return 1; + } + } } return 0; } /* @@ -34711,38 +37439,49 @@ static char zChars[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; size_t i, j; - int nBuf, nLen; + int nPre = sqlite3Strlen30(SQLITE_TEMP_FILE_PREFIX); + int nMax, nBuf, nDir, nLen; char *zBuf; /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this - ** function failing. + ** function failing. */ SimulateIOError( return SQLITE_IOERR ); /* Allocate a temporary buffer to store the fully qualified file ** name for the temporary file. If this fails, we cannot continue. */ - nBuf = pVfs->mxPathname; - zBuf = sqlite3MallocZero( nBuf+2 ); + nMax = pVfs->mxPathname; nBuf = nMax + 2; + zBuf = sqlite3MallocZero( nBuf ); if( !zBuf ){ OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); return SQLITE_IOERR_NOMEM; } /* Figure out the effective temporary directory. First, check if one ** has been explicitly set by the application; otherwise, use the one ** configured by the operating system. */ - assert( nBuf>30 ); + nDir = nMax - (nPre + 15); + assert( nDir>0 ); if( sqlite3_temp_directory ){ - sqlite3_snprintf(nBuf-30, zBuf, "%s%s", sqlite3_temp_directory, - winEndsInDirSep(sqlite3_temp_directory) ? "" : - winGetDirDep()); + int nDirLen = sqlite3Strlen30(sqlite3_temp_directory); + if( nDirLen>0 ){ + if( !winIsDirSep(sqlite3_temp_directory[nDirLen-1]) ){ + nDirLen++; + } + if( nDirLen>nDir ){ + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n")); + return winLogError(SQLITE_ERROR, 0, "winGetTempname1", 0); + } + sqlite3_snprintf(nMax, zBuf, "%s", sqlite3_temp_directory); + } } #if defined(__CYGWIN__) else{ static const char *azDirs[] = { 0, /* getenv("SQLITE_TMPDIR") */ @@ -34767,85 +37506,82 @@ for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); zDir=azDirs[i++]){ void *zConverted; if( zDir==0 ) continue; /* If the path starts with a drive letter followed by the colon ** character, assume it is already a native Win32 path; otherwise, - ** it must be converted to a native Win32 path prior via the Cygwin - ** API prior to using it. + ** it must be converted to a native Win32 path via the Cygwin API + ** prior to using it. */ if( winIsDriveLetterAndColon(zDir) ){ zConverted = winConvertFromUtf8Filename(zDir); if( !zConverted ){ + sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); return SQLITE_IOERR_NOMEM; } if( winIsDir(zConverted) ){ - sqlite3_snprintf(nBuf-30, zBuf, "%s", zDir); + sqlite3_snprintf(nMax, zBuf, "%s", zDir); sqlite3_free(zConverted); break; } sqlite3_free(zConverted); }else{ - zConverted = sqlite3MallocZero( nBuf+1 ); + zConverted = sqlite3MallocZero( nMax+1 ); if( !zConverted ){ + sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); return SQLITE_IOERR_NOMEM; } if( cygwin_conv_path( osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A, zDir, - zConverted, nBuf+1)<0 ){ + zConverted, nMax+1)<0 ){ sqlite3_free(zConverted); + sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_CONVPATH\n")); return winLogError(SQLITE_IOERR_CONVPATH, (DWORD)errno, - "winGetTempname1", zDir); + "winGetTempname2", zDir); } if( winIsDir(zConverted) ){ /* At this point, we know the candidate directory exists and should ** be used. However, we may need to convert the string containing ** its name into UTF-8 (i.e. if it is UTF-16 right now). */ - if( osIsNT() ){ - char *zUtf8 = winUnicodeToUtf8(zConverted); - if( !zUtf8 ){ - sqlite3_free(zConverted); - OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); - return SQLITE_IOERR_NOMEM; - } - sqlite3_snprintf(nBuf-30, zBuf, "%s", zUtf8); - sqlite3_free(zUtf8); - sqlite3_free(zConverted); - break; - }else{ - sqlite3_snprintf(nBuf-30, zBuf, "%s", zConverted); - sqlite3_free(zConverted); - break; - } + char *zUtf8 = winConvertToUtf8Filename(zConverted); + if( !zUtf8 ){ + sqlite3_free(zConverted); + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); + return SQLITE_IOERR_NOMEM; + } + sqlite3_snprintf(nMax, zBuf, "%s", zUtf8); + sqlite3_free(zUtf8); + sqlite3_free(zConverted); + break; } sqlite3_free(zConverted); } - break; } } #elif !SQLITE_OS_WINRT && !defined(__CYGWIN__) else if( osIsNT() ){ char *zMulti; - LPWSTR zWidePath = sqlite3MallocZero( nBuf*sizeof(WCHAR) ); + LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) ); if( !zWidePath ){ sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); return SQLITE_IOERR_NOMEM; } - if( osGetTempPathW(nBuf, zWidePath)==0 ){ + if( osGetTempPathW(nMax, zWidePath)==0 ){ sqlite3_free(zWidePath); sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_GETTEMPPATH\n")); return winLogError(SQLITE_IOERR_GETTEMPPATH, osGetLastError(), - "winGetTempname1", 0); + "winGetTempname2", 0); } zMulti = winUnicodeToUtf8(zWidePath); if( zMulti ){ - sqlite3_snprintf(nBuf-30, zBuf, "%s", zMulti); + sqlite3_snprintf(nMax, zBuf, "%s", zMulti); sqlite3_free(zMulti); sqlite3_free(zWidePath); }else{ sqlite3_free(zWidePath); sqlite3_free(zBuf); @@ -34854,25 +37590,25 @@ } } #ifdef SQLITE_WIN32_HAS_ANSI else{ char *zUtf8; - char *zMbcsPath = sqlite3MallocZero( nBuf ); + char *zMbcsPath = sqlite3MallocZero( nMax ); if( !zMbcsPath ){ sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); return SQLITE_IOERR_NOMEM; } - if( osGetTempPathA(nBuf, zMbcsPath)==0 ){ + if( osGetTempPathA(nMax, zMbcsPath)==0 ){ sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_GETTEMPPATH\n")); return winLogError(SQLITE_IOERR_GETTEMPPATH, osGetLastError(), - "winGetTempname2", 0); + "winGetTempname3", 0); } zUtf8 = sqlite3_win32_mbcs_to_utf8(zMbcsPath); if( zUtf8 ){ - sqlite3_snprintf(nBuf-30, zBuf, "%s", zUtf8); + sqlite3_snprintf(nMax, zBuf, "%s", zUtf8); sqlite3_free(zUtf8); }else{ sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); return SQLITE_IOERR_NOMEM; @@ -34879,22 +37615,40 @@ } } #endif /* SQLITE_WIN32_HAS_ANSI */ #endif /* !SQLITE_OS_WINRT */ - /* Check that the output buffer is large enough for the temporary file - ** name. If it is not, return SQLITE_ERROR. + /* + ** Check to make sure the temporary directory ends with an appropriate + ** separator. If it does not and there is not enough space left to add + ** one, fail. + */ + if( !winMakeEndInDirSep(nDir+1, zBuf) ){ + sqlite3_free(zBuf); + OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n")); + return winLogError(SQLITE_ERROR, 0, "winGetTempname4", 0); + } + + /* + ** Check that the output buffer is large enough for the temporary file + ** name in the following format: + ** + ** "<temporary_directory>/etilqs_XXXXXXXXXXXXXXX\0\0" + ** + ** If not, return SQLITE_ERROR. The number 17 is used here in order to + ** account for the space used by the 15 character random suffix and the + ** two trailing NUL characters. The final directory separator character + ** has already added if it was not already present. */ nLen = sqlite3Strlen30(zBuf); - - if( (nLen + sqlite3Strlen30(SQLITE_TEMP_FILE_PREFIX) + 18) >= nBuf ){ + if( (nLen + nPre + 17) > nBuf ){ sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n")); - return winLogError(SQLITE_ERROR, 0, "winGetTempname3", 0); + return winLogError(SQLITE_ERROR, 0, "winGetTempname5", 0); } - sqlite3_snprintf(nBuf-18-nLen, zBuf+nLen, SQLITE_TEMP_FILE_PREFIX); + sqlite3_snprintf(nBuf-16-nLen, zBuf+nLen, SQLITE_TEMP_FILE_PREFIX); j = sqlite3Strlen30(zBuf); sqlite3_randomness(15, &zBuf[j]); for(i=0; i<15; i++, j++){ zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; @@ -34945,11 +37699,11 @@ sqlite3_file *id, /* Write the SQLite file handle here */ int flags, /* Open mode flags */ int *pOutFlags /* Status return flags */ ){ HANDLE h; - DWORD lastErrno; + DWORD lastErrno = 0; DWORD dwDesiredAccess; DWORD dwShareMode; DWORD dwCreationDisposition; DWORD dwFlagsAndAttributes = 0; #if SQLITE_OS_WINCE @@ -34976,42 +37730,42 @@ int isReadonly = (flags & SQLITE_OPEN_READONLY); int isReadWrite = (flags & SQLITE_OPEN_READWRITE); #ifndef NDEBUG int isOpenJournal = (isCreate && ( - eType==SQLITE_OPEN_MASTER_JOURNAL - || eType==SQLITE_OPEN_MAIN_JOURNAL + eType==SQLITE_OPEN_MASTER_JOURNAL + || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_WAL )); #endif OSTRACE(("OPEN name=%s, pFile=%p, flags=%x, pOutFlags=%p\n", zUtf8Name, id, flags, pOutFlags)); - /* Check the following statements are true: + /* Check the following statements are true: ** - ** (a) Exactly one of the READWRITE and READONLY flags must be set, and + ** (a) Exactly one of the READWRITE and READONLY flags must be set, and ** (b) if CREATE is set, then READWRITE must also be set, and ** (c) if EXCLUSIVE is set, then CREATE must also be set. ** (d) if DELETEONCLOSE is set, then CREATE must also be set. */ assert((isReadonly==0 || isReadWrite==0) && (isReadWrite || isReadonly)); assert(isCreate==0 || isReadWrite); assert(isExclusive==0 || isCreate); assert(isDelete==0 || isCreate); - /* The main DB, main journal, WAL file and master journal are never + /* The main DB, main journal, WAL file and master journal are never ** automatically deleted. Nor are they ever temporary files. */ assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_DB ); assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_JOURNAL ); assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MASTER_JOURNAL ); assert( (!isDelete && zName) || eType!=SQLITE_OPEN_WAL ); /* Assert that the upper layer has set one of the "file-type" flags. */ - assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB - || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL - || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_MASTER_JOURNAL + assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB + || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL + || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_MASTER_JOURNAL || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL ); assert( pFile!=0 ); memset(pFile, 0, sizeof(winFile)); @@ -35022,12 +37776,12 @@ sqlite3_log(SQLITE_ERROR, "sqlite3_temp_directory variable should be set for WinRT"); } #endif - /* If the second argument to this function is NULL, generate a - ** temporary file name to use + /* If the second argument to this function is NULL, generate a + ** temporary file name to use */ if( !zUtf8Name ){ assert( isDelete && !isOpenJournal ); rc = winGetTempname(pVfs, &zTmpname); if( rc!=SQLITE_OK ){ @@ -35063,12 +37817,12 @@ dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; }else{ dwDesiredAccess = GENERIC_READ; } - /* SQLITE_OPEN_EXCLUSIVE is used to make sure that a new file is - ** created. SQLite doesn't use it to indicate "exclusive access" + /* SQLITE_OPEN_EXCLUSIVE is used to make sure that a new file is + ** created. SQLite doesn't use it to indicate "exclusive access" ** as it is usually understood. */ if( isExclusive ){ /* Creates a new file, only if it does not already exist. */ /* If the file exists, it fails. */ @@ -35142,11 +37896,11 @@ winRetryIoerr(&cnt, &lastErrno) ){ /* Noop */ } } #endif - winLogIoerr(cnt); + winLogIoerr(cnt, __LINE__); OSTRACE(("OPEN file=%p, name=%s, access=%lx, rc=%s\n", h, zUtf8Name, dwDesiredAccess, (h==INVALID_HANDLE_VALUE) ? "failed" : "ok")); if( h==INVALID_HANDLE_VALUE ){ @@ -35153,11 +37907,11 @@ pFile->lastErrno = lastErrno; winLogError(SQLITE_CANTOPEN, pFile->lastErrno, "winOpen", zUtf8Name); sqlite3_free(zConverted); sqlite3_free(zTmpname); if( isReadWrite && !isExclusive ){ - return winOpen(pVfs, zName, id, + return winOpen(pVfs, zName, id, ((flags|SQLITE_OPEN_READONLY) & ~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)), pOutFlags); }else{ return SQLITE_CANTOPEN_BKPT; @@ -35190,13 +37944,13 @@ pFile->zDeleteOnClose = zConverted; }else #endif { sqlite3_free(zConverted); - sqlite3_free(zTmpname); } + sqlite3_free(zTmpname); pFile->pMethod = &winIoMethod; pFile->pVfs = pVfs; pFile->h = h; if( isReadonly ){ pFile->ctrlFlags |= WINFILE_RDONLY; @@ -35236,11 +37990,11 @@ int syncDir /* Not used on win32 */ ){ int cnt = 0; int rc; DWORD attr; - DWORD lastErrno; + DWORD lastErrno = 0; void *zConverted; UNUSED_PARAMETER(pVfs); UNUSED_PARAMETER(syncDir); SimulateIOError(return SQLITE_IOERR_DELETE); @@ -35326,11 +38080,11 @@ } #endif if( rc && rc!=SQLITE_IOERR_DELETE_NOENT ){ rc = winLogError(SQLITE_IOERR_DELETE, lastErrno, "winDelete", zFilename); }else{ - winLogIoerr(cnt); + winLogIoerr(cnt, __LINE__); } sqlite3_free(zConverted); OSTRACE(("DELETE name=%s, rc=%s\n", zFilename, sqlite3ErrName(rc))); return rc; } @@ -35344,11 +38098,11 @@ int flags, /* Type of test to make on this file */ int *pResOut /* OUT: Result */ ){ DWORD attr; int rc = 0; - DWORD lastErrno; + DWORD lastErrno = 0; void *zConverted; UNUSED_PARAMETER(pVfs); SimulateIOError( return SQLITE_IOERR_ACCESS; ); OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n", @@ -35362,25 +38116,25 @@ if( osIsNT() ){ int cnt = 0; WIN32_FILE_ATTRIBUTE_DATA sAttrData; memset(&sAttrData, 0, sizeof(sAttrData)); while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted, - GetFileExInfoStandard, + GetFileExInfoStandard, &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){} if( rc ){ /* For an SQLITE_ACCESS_EXISTS query, treat a zero-length file ** as if it does not exist. */ if( flags==SQLITE_ACCESS_EXISTS - && sAttrData.nFileSizeHigh==0 + && sAttrData.nFileSizeHigh==0 && sAttrData.nFileSizeLow==0 ){ attr = INVALID_FILE_ATTRIBUTES; }else{ attr = sAttrData.dwFileAttributes; } }else{ - winLogIoerr(cnt); + winLogIoerr(cnt, __LINE__); if( lastErrno!=ERROR_FILE_NOT_FOUND && lastErrno!=ERROR_PATH_NOT_FOUND ){ sqlite3_free(zConverted); return winLogError(SQLITE_IOERR_ACCESS, lastErrno, "winAccess", zFilename); }else{ @@ -35468,11 +38222,11 @@ sqlite3_vfs *pVfs, /* Pointer to vfs object */ const char *zRelative, /* Possibly relative input path */ int nFull, /* Size of output buffer in bytes */ char *zFull /* Output buffer */ ){ - + #if defined(__CYGWIN__) SimulateIOError( return SQLITE_ERROR ); UNUSED_PARAMETER(nFull); assert( nFull>=pVfs->mxPathname ); if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){ @@ -35484,23 +38238,47 @@ */ char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); if( !zOut ){ return SQLITE_IOERR_NOMEM; } - if( cygwin_conv_path(CCP_POSIX_TO_WIN_A|CCP_RELATIVE, zRelative, zOut, - pVfs->mxPathname+1)<0 ){ + if( cygwin_conv_path( + (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A) | + CCP_RELATIVE, zRelative, zOut, pVfs->mxPathname+1)<0 ){ sqlite3_free(zOut); return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, "winFullPathname1", zRelative); + }else{ + char *zUtf8 = winConvertToUtf8Filename(zOut); + if( !zUtf8 ){ + sqlite3_free(zOut); + return SQLITE_IOERR_NOMEM; + } + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", + sqlite3_data_directory, winGetDirSep(), zUtf8); + sqlite3_free(zUtf8); + sqlite3_free(zOut); } - sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%s%s", - sqlite3_data_directory, winGetDirDep(), zOut); - sqlite3_free(zOut); }else{ - if( cygwin_conv_path(CCP_POSIX_TO_WIN_A, zRelative, zFull, nFull)<0 ){ + char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); + if( !zOut ){ + return SQLITE_IOERR_NOMEM; + } + if( cygwin_conv_path( + (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A), + zRelative, zOut, pVfs->mxPathname+1)<0 ){ + sqlite3_free(zOut); return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, "winFullPathname2", zRelative); + }else{ + char *zUtf8 = winConvertToUtf8Filename(zOut); + if( !zUtf8 ){ + sqlite3_free(zOut); + return SQLITE_IOERR_NOMEM; + } + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8); + sqlite3_free(zUtf8); + sqlite3_free(zOut); } } return SQLITE_OK; #endif @@ -35513,12 +38291,12 @@ ** NOTE: We are dealing with a relative path name and the data ** directory has been set. Therefore, use it as the basis ** for converting the relative path name to an absolute ** one by prepending the data directory and a backslash. */ - sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%s%s", - sqlite3_data_directory, winGetDirDep(), zRelative); + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", + sqlite3_data_directory, winGetDirSep(), zRelative); }else{ sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zRelative); } return SQLITE_OK; #endif @@ -35546,12 +38324,12 @@ ** NOTE: We are dealing with a relative path name and the data ** directory has been set. Therefore, use it as the basis ** for converting the relative path name to an absolute ** one by prepending the data directory and a backslash. */ - sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%s%s", - sqlite3_data_directory, winGetDirDep(), zRelative); + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", + sqlite3_data_directory, winGetDirSep(), zRelative); return SQLITE_OK; } zConverted = winConvertFromUtf8Filename(zRelative); if( zConverted==0 ){ return SQLITE_IOERR_NOMEM; @@ -35621,19 +38399,33 @@ #ifndef SQLITE_OMIT_LOAD_EXTENSION /* ** Interfaces for opening a shared library, finding entry points ** within the shared library, and closing the shared library. */ -/* -** Interfaces for opening a shared library, finding entry points -** within the shared library, and closing the shared library. -*/ static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){ HANDLE h; +#if defined(__CYGWIN__) + int nFull = pVfs->mxPathname+1; + char *zFull = sqlite3MallocZero( nFull ); + void *zConverted = 0; + if( zFull==0 ){ + OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); + return 0; + } + if( winFullPathname(pVfs, zFilename, nFull, zFull)!=SQLITE_OK ){ + sqlite3_free(zFull); + OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); + return 0; + } + zConverted = winConvertFromUtf8Filename(zFull); + sqlite3_free(zFull); +#else void *zConverted = winConvertFromUtf8Filename(zFilename); UNUSED_PARAMETER(pVfs); +#endif if( zConverted==0 ){ + OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); return 0; } if( osIsNT() ){ #if SQLITE_OS_WINRT h = osLoadPackagedLibrary((LPCWSTR)zConverted, 0); @@ -35644,24 +38436,30 @@ #ifdef SQLITE_WIN32_HAS_ANSI else{ h = osLoadLibraryA((char*)zConverted); } #endif + OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)h)); sqlite3_free(zConverted); return (void*)h; } static void winDlError(sqlite3_vfs *pVfs, int nBuf, char *zBufOut){ UNUSED_PARAMETER(pVfs); winGetLastErrorMsg(osGetLastError(), nBuf, zBufOut); } static void (*winDlSym(sqlite3_vfs *pVfs,void *pH,const char *zSym))(void){ + FARPROC proc; UNUSED_PARAMETER(pVfs); - return (void(*)(void))osGetProcAddressA((HANDLE)pH, zSym); + proc = osGetProcAddressA((HANDLE)pH, zSym); + OSTRACE(("DLSYM handle=%p, symbol=%s, address=%p\n", + (void*)pH, zSym, (void*)proc)); + return (void(*)(void))proc; } static void winDlClose(sqlite3_vfs *pVfs, void *pHandle){ UNUSED_PARAMETER(pVfs); osFreeLibrary((HANDLE)pHandle); + OSTRACE(("DLCLOSE handle=%p\n", (void*)pHandle)); } #else /* if SQLITE_OMIT_LOAD_EXTENSION is defined: */ #define winDlOpen 0 #define winDlError 0 #define winDlSym 0 @@ -35707,10 +38505,26 @@ LARGE_INTEGER i; osQueryPerformanceCounter(&i); memcpy(&zBuf[n], &i, sizeof(i)); n += sizeof(i); } +#endif +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID + if( sizeof(UUID)<=nBuf-n ){ + UUID id; + memset(&id, 0, sizeof(UUID)); + osUuidCreate(&id); + memcpy(zBuf, &id, sizeof(UUID)); + n += sizeof(UUID); + } + if( sizeof(UUID)<=nBuf-n ){ + UUID id; + memset(&id, 0, sizeof(UUID)); + osUuidCreateSequential(&id); + memcpy(zBuf, &id, sizeof(UUID)); + n += sizeof(UUID); + } #endif return n; } @@ -35737,24 +38551,24 @@ ** the current time and date as a Julian Day number times 86_400_000. In ** other words, write into *piNow the number of milliseconds since the Julian ** epoch of noon in Greenwich on November 24, 4714 B.C according to the ** proleptic Gregorian calendar. ** -** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date ** cannot be found. */ static int winCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *piNow){ - /* FILETIME structure is a 64-bit value representing the number of - 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5). + /* FILETIME structure is a 64-bit value representing the number of + 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5). */ FILETIME ft; static const sqlite3_int64 winFiletimeEpoch = 23058135*(sqlite3_int64)8640000; #ifdef SQLITE_TEST static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000; #endif /* 2^32 - to avoid use of LL and warnings in gcc */ - static const sqlite3_int64 max32BitValue = + static const sqlite3_int64 max32BitValue = (sqlite3_int64)2000000000 + (sqlite3_int64)2000000000 + (sqlite3_int64)294967296; #if SQLITE_OS_WINCE SYSTEMTIME time; @@ -35766,11 +38580,11 @@ #else osGetSystemTimeAsFileTime( &ft ); #endif *piNow = winFiletimeEpoch + - ((((sqlite3_int64)ft.dwHighDateTime)*max32BitValue) + + ((((sqlite3_int64)ft.dwHighDateTime)*max32BitValue) + (sqlite3_int64)ft.dwLowDateTime)/(sqlite3_int64)10000; #ifdef SQLITE_TEST if( sqlite3_current_time ){ *piNow = 1000*(sqlite3_int64)sqlite3_current_time + unixEpoch; @@ -35831,11 +38645,11 @@ } /* ** Initialize and deinitialize the operating system interface. */ -SQLITE_API int sqlite3_os_init(void){ +SQLITE_API int SQLITE_STDCALL sqlite3_os_init(void){ static sqlite3_vfs winVfs = { 3, /* iVersion */ sizeof(winFile), /* szOsFile */ SQLITE_WIN32_MAX_PATH_BYTES, /* mxPathname */ 0, /* pNext */ @@ -35885,11 +38699,11 @@ }; #endif /* Double-check that the aSyscall[] array has been constructed ** correctly. See ticket [bb3a86e890c8e96ab] */ - assert( ArraySize(aSyscall)==75 ); + assert( ArraySize(aSyscall)==79 ); /* get memory map allocation granularity */ memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); #if SQLITE_OS_WINRT osGetNativeSystemInfo(&winSysInfo); @@ -35903,14 +38717,14 @@ #if defined(SQLITE_WIN32_HAS_WIDE) sqlite3_vfs_register(&winLongPathVfs, 0); #endif - return SQLITE_OK; + return SQLITE_OK; } -SQLITE_API int sqlite3_os_end(void){ +SQLITE_API int SQLITE_STDCALL sqlite3_os_end(void){ #if SQLITE_OS_WINRT if( sleepObj!=NULL ){ osCloseHandle(sleepObj); sleepObj = NULL; } @@ -36353,119 +39167,116 @@ PgHdr *pSynced; /* Last synced page in dirty page list */ int nRef; /* Number of referenced pages */ int szCache; /* Configured cache size */ int szPage; /* Size of every page in this cache */ int szExtra; /* Size of extra space for each page */ - int bPurgeable; /* True if pages are on backing store */ + u8 bPurgeable; /* True if pages are on backing store */ + u8 eCreate; /* eCreate value for for xFetch() */ int (*xStress)(void*,PgHdr*); /* Call to try make a page clean */ void *pStress; /* Argument to xStress */ sqlite3_pcache *pCache; /* Pluggable cache module */ PgHdr *pPage1; /* Reference to page 1 */ }; -/* -** Some of the assert() macros in this code are too expensive to run -** even during normal debugging. Use them only rarely on long-running -** tests. Enable the expensive asserts using the -** -DSQLITE_ENABLE_EXPENSIVE_ASSERT=1 compile-time option. -*/ -#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT -# define expensive_assert(X) assert(X) -#else -# define expensive_assert(X) -#endif - -/********************************** Linked List Management ********************/ - -#if !defined(NDEBUG) && defined(SQLITE_ENABLE_EXPENSIVE_ASSERT) -/* -** Check that the pCache->pSynced variable is set correctly. If it -** is not, either fail an assert or return zero. Otherwise, return -** non-zero. This is only used in debugging builds, as follows: -** -** expensive_assert( pcacheCheckSynced(pCache) ); -*/ -static int pcacheCheckSynced(PCache *pCache){ - PgHdr *p; - for(p=pCache->pDirtyTail; p!=pCache->pSynced; p=p->pDirtyPrev){ - assert( p->nRef || (p->flags&PGHDR_NEED_SYNC) ); - } - return (p==0 || p->nRef || (p->flags&PGHDR_NEED_SYNC)==0); -} -#endif /* !NDEBUG && SQLITE_ENABLE_EXPENSIVE_ASSERT */ - -/* -** Remove page pPage from the list of dirty pages. -*/ -static void pcacheRemoveFromDirtyList(PgHdr *pPage){ - PCache *p = pPage->pCache; - - assert( pPage->pDirtyNext || pPage==p->pDirtyTail ); - assert( pPage->pDirtyPrev || pPage==p->pDirty ); - - /* Update the PCache1.pSynced variable if necessary. */ - if( p->pSynced==pPage ){ - PgHdr *pSynced = pPage->pDirtyPrev; - while( pSynced && (pSynced->flags&PGHDR_NEED_SYNC) ){ - pSynced = pSynced->pDirtyPrev; - } - p->pSynced = pSynced; - } - - if( pPage->pDirtyNext ){ - pPage->pDirtyNext->pDirtyPrev = pPage->pDirtyPrev; - }else{ - assert( pPage==p->pDirtyTail ); - p->pDirtyTail = pPage->pDirtyPrev; - } - if( pPage->pDirtyPrev ){ - pPage->pDirtyPrev->pDirtyNext = pPage->pDirtyNext; - }else{ - assert( pPage==p->pDirty ); - p->pDirty = pPage->pDirtyNext; - } - pPage->pDirtyNext = 0; - pPage->pDirtyPrev = 0; - - expensive_assert( pcacheCheckSynced(p) ); -} - -/* -** Add page pPage to the head of the dirty list (PCache1.pDirty is set to -** pPage). -*/ -static void pcacheAddToDirtyList(PgHdr *pPage){ - PCache *p = pPage->pCache; - - assert( pPage->pDirtyNext==0 && pPage->pDirtyPrev==0 && p->pDirty!=pPage ); - - pPage->pDirtyNext = p->pDirty; - if( pPage->pDirtyNext ){ - assert( pPage->pDirtyNext->pDirtyPrev==0 ); - pPage->pDirtyNext->pDirtyPrev = pPage; - } - p->pDirty = pPage; - if( !p->pDirtyTail ){ - p->pDirtyTail = pPage; - } - if( !p->pSynced && 0==(pPage->flags&PGHDR_NEED_SYNC) ){ - p->pSynced = pPage; - } - expensive_assert( pcacheCheckSynced(p) ); +/********************************** Linked List Management ********************/ + +/* Allowed values for second argument to pcacheManageDirtyList() */ +#define PCACHE_DIRTYLIST_REMOVE 1 /* Remove pPage from dirty list */ +#define PCACHE_DIRTYLIST_ADD 2 /* Add pPage to the dirty list */ +#define PCACHE_DIRTYLIST_FRONT 3 /* Move pPage to the front of the list */ + +/* +** Manage pPage's participation on the dirty list. Bits of the addRemove +** argument determines what operation to do. The 0x01 bit means first +** remove pPage from the dirty list. The 0x02 means add pPage back to +** the dirty list. Doing both moves pPage to the front of the dirty list. +*/ +static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){ + PCache *p = pPage->pCache; + + if( addRemove & PCACHE_DIRTYLIST_REMOVE ){ + assert( pPage->pDirtyNext || pPage==p->pDirtyTail ); + assert( pPage->pDirtyPrev || pPage==p->pDirty ); + + /* Update the PCache1.pSynced variable if necessary. */ + if( p->pSynced==pPage ){ + PgHdr *pSynced = pPage->pDirtyPrev; + while( pSynced && (pSynced->flags&PGHDR_NEED_SYNC) ){ + pSynced = pSynced->pDirtyPrev; + } + p->pSynced = pSynced; + } + + if( pPage->pDirtyNext ){ + pPage->pDirtyNext->pDirtyPrev = pPage->pDirtyPrev; + }else{ + assert( pPage==p->pDirtyTail ); + p->pDirtyTail = pPage->pDirtyPrev; + } + if( pPage->pDirtyPrev ){ + pPage->pDirtyPrev->pDirtyNext = pPage->pDirtyNext; + }else{ + assert( pPage==p->pDirty ); + p->pDirty = pPage->pDirtyNext; + if( p->pDirty==0 && p->bPurgeable ){ + assert( p->eCreate==1 ); + p->eCreate = 2; + } + } + pPage->pDirtyNext = 0; + pPage->pDirtyPrev = 0; + } + if( addRemove & PCACHE_DIRTYLIST_ADD ){ + assert( pPage->pDirtyNext==0 && pPage->pDirtyPrev==0 && p->pDirty!=pPage ); + + pPage->pDirtyNext = p->pDirty; + if( pPage->pDirtyNext ){ + assert( pPage->pDirtyNext->pDirtyPrev==0 ); + pPage->pDirtyNext->pDirtyPrev = pPage; + }else{ + p->pDirtyTail = pPage; + if( p->bPurgeable ){ + assert( p->eCreate==2 ); + p->eCreate = 1; + } + } + p->pDirty = pPage; + if( !p->pSynced && 0==(pPage->flags&PGHDR_NEED_SYNC) ){ + p->pSynced = pPage; + } + } } /* ** Wrapper around the pluggable caches xUnpin method. If the cache is ** being used for an in-memory database, this function is a no-op. */ static void pcacheUnpin(PgHdr *p){ - PCache *pCache = p->pCache; - if( pCache->bPurgeable ){ + if( p->pCache->bPurgeable ){ if( p->pgno==1 ){ - pCache->pPage1 = 0; + p->pCache->pPage1 = 0; } - sqlite3GlobalConfig.pcache2.xUnpin(pCache->pCache, p->pPage, 0); + sqlite3GlobalConfig.pcache2.xUnpin(p->pCache->pCache, p->pPage, 0); + } +} + +/* +** Compute the number of pages of cache requested. p->szCache is the +** cache size requested by the "PRAGMA cache_size" statement. +** +** +*/ +static int numberOfCachePages(PCache *p){ + if( p->szCache>=0 ){ + /* IMPLEMENTATION-OF: R-42059-47211 If the argument N is positive then the + ** suggested cache size is set to N. */ + return p->szCache; + }else{ + /* IMPLEMENTATION-OF: R-61436-13639 If the argument N is negative, then + ** the number of cache pages is adjusted to use approximately abs(N*1024) + ** bytes of memory. */ + return (int)((-1024*(i64)p->szCache)/(p->szPage+p->szExtra)); } } /*************************************************** General Interfaces ****** ** @@ -36497,169 +39308,225 @@ ** Create a new PCache object. Storage space to hold the object ** has already been allocated and is passed in as the p pointer. ** The caller discovers how much space needs to be allocated by ** calling sqlite3PcacheSize(). */ -SQLITE_PRIVATE void sqlite3PcacheOpen( +SQLITE_PRIVATE int sqlite3PcacheOpen( int szPage, /* Size of every page */ int szExtra, /* Extra space associated with each page */ int bPurgeable, /* True if pages are on backing store */ int (*xStress)(void*,PgHdr*),/* Call to try to make pages clean */ void *pStress, /* Argument to xStress */ PCache *p /* Preallocated space for the PCache */ ){ memset(p, 0, sizeof(PCache)); - p->szPage = szPage; + p->szPage = 1; p->szExtra = szExtra; p->bPurgeable = bPurgeable; + p->eCreate = 2; p->xStress = xStress; p->pStress = pStress; p->szCache = 100; + return sqlite3PcacheSetPageSize(p, szPage); } /* ** Change the page size for PCache object. The caller must ensure that there ** are no outstanding page references when this function is called. */ -SQLITE_PRIVATE void sqlite3PcacheSetPageSize(PCache *pCache, int szPage){ +SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *pCache, int szPage){ assert( pCache->nRef==0 && pCache->pDirty==0 ); - if( pCache->pCache ){ - sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache); - pCache->pCache = 0; + if( pCache->szPage ){ + sqlite3_pcache *pNew; + pNew = sqlite3GlobalConfig.pcache2.xCreate( + szPage, pCache->szExtra + ROUND8(sizeof(PgHdr)), + pCache->bPurgeable + ); + if( pNew==0 ) return SQLITE_NOMEM; + sqlite3GlobalConfig.pcache2.xCachesize(pNew, numberOfCachePages(pCache)); + if( pCache->pCache ){ + sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache); + } + pCache->pCache = pNew; pCache->pPage1 = 0; - } - pCache->szPage = szPage; -} - -/* -** Compute the number of pages of cache requested. -*/ -static int numberOfCachePages(PCache *p){ - if( p->szCache>=0 ){ - return p->szCache; - }else{ - return (int)((-1024*(i64)p->szCache)/(p->szPage+p->szExtra)); - } + pCache->szPage = szPage; + } + return SQLITE_OK; } /* ** Try to obtain a page from the cache. +** +** This routine returns a pointer to an sqlite3_pcache_page object if +** such an object is already in cache, or if a new one is created. +** This routine returns a NULL pointer if the object was not in cache +** and could not be created. +** +** The createFlags should be 0 to check for existing pages and should +** be 3 (not 1, but 3) to try to create a new page. +** +** If the createFlag is 0, then NULL is always returned if the page +** is not already in the cache. If createFlag is 1, then a new page +** is created only if that can be done without spilling dirty pages +** and without exceeding the cache size limit. +** +** The caller needs to invoke sqlite3PcacheFetchFinish() to properly +** initialize the sqlite3_pcache_page object and convert it into a +** PgHdr object. The sqlite3PcacheFetch() and sqlite3PcacheFetchFinish() +** routines are split this way for performance reasons. When separated +** they can both (usually) operate without having to push values to +** the stack on entry and pop them back off on exit, which saves a +** lot of pushing and popping. */ -SQLITE_PRIVATE int sqlite3PcacheFetch( +SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch( PCache *pCache, /* Obtain the page from this cache */ Pgno pgno, /* Page number to obtain */ - int createFlag, /* If true, create page if it does not exist already */ - PgHdr **ppPage /* Write the page here */ + int createFlag /* If true, create page if it does not exist already */ ){ - sqlite3_pcache_page *pPage = 0; - PgHdr *pPgHdr = 0; int eCreate; assert( pCache!=0 ); - assert( createFlag==1 || createFlag==0 ); + assert( pCache->pCache!=0 ); + assert( createFlag==3 || createFlag==0 ); assert( pgno>0 ); - /* If the pluggable cache (sqlite3_pcache*) has not been allocated, - ** allocate it now. - */ - if( !pCache->pCache && createFlag ){ - sqlite3_pcache *p; - p = sqlite3GlobalConfig.pcache2.xCreate( - pCache->szPage, pCache->szExtra + sizeof(PgHdr), pCache->bPurgeable - ); - if( !p ){ - return SQLITE_NOMEM; - } - sqlite3GlobalConfig.pcache2.xCachesize(p, numberOfCachePages(pCache)); - pCache->pCache = p; - } - - eCreate = createFlag * (1 + (!pCache->bPurgeable || !pCache->pDirty)); - if( pCache->pCache ){ - pPage = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate); - } - - if( !pPage && eCreate==1 ){ - PgHdr *pPg; - - /* Find a dirty page to write-out and recycle. First try to find a - ** page that does not require a journal-sync (one with PGHDR_NEED_SYNC - ** cleared), but if that is not possible settle for any other - ** unreferenced dirty page. - */ - expensive_assert( pcacheCheckSynced(pCache) ); - for(pPg=pCache->pSynced; - pPg && (pPg->nRef || (pPg->flags&PGHDR_NEED_SYNC)); - pPg=pPg->pDirtyPrev - ); - pCache->pSynced = pPg; - if( !pPg ){ - for(pPg=pCache->pDirtyTail; pPg && pPg->nRef; pPg=pPg->pDirtyPrev); - } - if( pPg ){ - int rc; + /* eCreate defines what to do if the page does not exist. + ** 0 Do not allocate a new page. (createFlag==0) + ** 1 Allocate a new page if doing so is inexpensive. + ** (createFlag==1 AND bPurgeable AND pDirty) + ** 2 Allocate a new page even it doing so is difficult. + ** (createFlag==1 AND !(bPurgeable AND pDirty) + */ + eCreate = createFlag & pCache->eCreate; + assert( eCreate==0 || eCreate==1 || eCreate==2 ); + assert( createFlag==0 || pCache->eCreate==eCreate ); + assert( createFlag==0 || eCreate==1+(!pCache->bPurgeable||!pCache->pDirty) ); + return sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate); +} + +/* +** If the sqlite3PcacheFetch() routine is unable to allocate a new +** page because new clean pages are available for reuse and the cache +** size limit has been reached, then this routine can be invoked to +** try harder to allocate a page. This routine might invoke the stress +** callback to spill dirty pages to the journal. It will then try to +** allocate the new page and will only fail to allocate a new page on +** an OOM error. +** +** This routine should be invoked only after sqlite3PcacheFetch() fails. +*/ +SQLITE_PRIVATE int sqlite3PcacheFetchStress( + PCache *pCache, /* Obtain the page from this cache */ + Pgno pgno, /* Page number to obtain */ + sqlite3_pcache_page **ppPage /* Write result here */ +){ + PgHdr *pPg; + if( pCache->eCreate==2 ) return 0; + + + /* Find a dirty page to write-out and recycle. First try to find a + ** page that does not require a journal-sync (one with PGHDR_NEED_SYNC + ** cleared), but if that is not possible settle for any other + ** unreferenced dirty page. + */ + for(pPg=pCache->pSynced; + pPg && (pPg->nRef || (pPg->flags&PGHDR_NEED_SYNC)); + pPg=pPg->pDirtyPrev + ); + pCache->pSynced = pPg; + if( !pPg ){ + for(pPg=pCache->pDirtyTail; pPg && pPg->nRef; pPg=pPg->pDirtyPrev); + } + if( pPg ){ + int rc; #ifdef SQLITE_LOG_CACHE_SPILL - sqlite3_log(SQLITE_FULL, - "spill page %d making room for %d - cache used: %d/%d", - pPg->pgno, pgno, - sqlite3GlobalConfig.pcache.xPagecount(pCache->pCache), - numberOfCachePages(pCache)); -#endif - rc = pCache->xStress(pCache->pStress, pPg); - if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){ - return rc; - } - } - - pPage = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, 2); - } - - if( pPage ){ - pPgHdr = (PgHdr *)pPage->pExtra; - - if( !pPgHdr->pPage ){ - memset(pPgHdr, 0, sizeof(PgHdr)); - pPgHdr->pPage = pPage; - pPgHdr->pData = pPage->pBuf; - pPgHdr->pExtra = (void *)&pPgHdr[1]; - memset(pPgHdr->pExtra, 0, pCache->szExtra); - pPgHdr->pCache = pCache; - pPgHdr->pgno = pgno; - } - assert( pPgHdr->pCache==pCache ); - assert( pPgHdr->pgno==pgno ); - assert( pPgHdr->pData==pPage->pBuf ); - assert( pPgHdr->pExtra==(void *)&pPgHdr[1] ); - - if( 0==pPgHdr->nRef ){ - pCache->nRef++; - } - pPgHdr->nRef++; - if( pgno==1 ){ - pCache->pPage1 = pPgHdr; - } - } - *ppPage = pPgHdr; - return (pPgHdr==0 && eCreate) ? SQLITE_NOMEM : SQLITE_OK; + sqlite3_log(SQLITE_FULL, + "spill page %d making room for %d - cache used: %d/%d", + pPg->pgno, pgno, + sqlite3GlobalConfig.pcache.xPagecount(pCache->pCache), + numberOfCachePages(pCache)); +#endif + rc = pCache->xStress(pCache->pStress, pPg); + if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){ + return rc; + } + } + *ppPage = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, 2); + return *ppPage==0 ? SQLITE_NOMEM : SQLITE_OK; +} + +/* +** This is a helper routine for sqlite3PcacheFetchFinish() +** +** In the uncommon case where the page being fetched has not been +** initialized, this routine is invoked to do the initialization. +** This routine is broken out into a separate function since it +** requires extra stack manipulation that can be avoided in the common +** case. +*/ +static SQLITE_NOINLINE PgHdr *pcacheFetchFinishWithInit( + PCache *pCache, /* Obtain the page from this cache */ + Pgno pgno, /* Page number obtained */ + sqlite3_pcache_page *pPage /* Page obtained by prior PcacheFetch() call */ +){ + PgHdr *pPgHdr; + assert( pPage!=0 ); + pPgHdr = (PgHdr*)pPage->pExtra; + assert( pPgHdr->pPage==0 ); + memset(pPgHdr, 0, sizeof(PgHdr)); + pPgHdr->pPage = pPage; + pPgHdr->pData = pPage->pBuf; + pPgHdr->pExtra = (void *)&pPgHdr[1]; + memset(pPgHdr->pExtra, 0, pCache->szExtra); + pPgHdr->pCache = pCache; + pPgHdr->pgno = pgno; + return sqlite3PcacheFetchFinish(pCache,pgno,pPage); +} + +/* +** This routine converts the sqlite3_pcache_page object returned by +** sqlite3PcacheFetch() into an initialized PgHdr object. This routine +** must be called after sqlite3PcacheFetch() in order to get a usable +** result. +*/ +SQLITE_PRIVATE PgHdr *sqlite3PcacheFetchFinish( + PCache *pCache, /* Obtain the page from this cache */ + Pgno pgno, /* Page number obtained */ + sqlite3_pcache_page *pPage /* Page obtained by prior PcacheFetch() call */ +){ + PgHdr *pPgHdr; + + if( pPage==0 ) return 0; + pPgHdr = (PgHdr *)pPage->pExtra; + + if( !pPgHdr->pPage ){ + return pcacheFetchFinishWithInit(pCache, pgno, pPage); + } + if( 0==pPgHdr->nRef ){ + pCache->nRef++; + } + pPgHdr->nRef++; + if( pgno==1 ){ + pCache->pPage1 = pPgHdr; + } + return pPgHdr; } /* ** Decrement the reference count on a page. If the page is clean and the -** reference count drops to 0, then it is made elible for recycling. +** reference count drops to 0, then it is made eligible for recycling. */ -SQLITE_PRIVATE void sqlite3PcacheRelease(PgHdr *p){ +SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){ assert( p->nRef>0 ); p->nRef--; if( p->nRef==0 ){ - PCache *pCache = p->pCache; - pCache->nRef--; + p->pCache->nRef--; if( (p->flags&PGHDR_DIRTY)==0 ){ pcacheUnpin(p); - }else{ + }else if( p->pDirtyPrev!=0 ){ /* Move the page to the head of the dirty list. */ - pcacheRemoveFromDirtyList(p); - pcacheAddToDirtyList(p); + pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT); } } } /* @@ -36674,21 +39541,19 @@ ** Drop a page from the cache. There must be exactly one reference to the ** page. This function deletes that reference, so after it returns the ** page pointed to by p is invalid. */ SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr *p){ - PCache *pCache; assert( p->nRef==1 ); if( p->flags&PGHDR_DIRTY ){ - pcacheRemoveFromDirtyList(p); + pcacheManageDirtyList(p, PCACHE_DIRTYLIST_REMOVE); } - pCache = p->pCache; - pCache->nRef--; + p->pCache->nRef--; if( p->pgno==1 ){ - pCache->pPage1 = 0; + p->pCache->pPage1 = 0; } - sqlite3GlobalConfig.pcache2.xUnpin(pCache->pCache, p->pPage, 1); + sqlite3GlobalConfig.pcache2.xUnpin(p->pCache->pCache, p->pPage, 1); } /* ** Make sure the page is marked as dirty. If it isn't dirty already, ** make it so. @@ -36696,21 +39561,21 @@ SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr *p){ p->flags &= ~PGHDR_DONT_WRITE; assert( p->nRef>0 ); if( 0==(p->flags & PGHDR_DIRTY) ){ p->flags |= PGHDR_DIRTY; - pcacheAddToDirtyList( p); + pcacheManageDirtyList(p, PCACHE_DIRTYLIST_ADD); } } /* ** Make sure the page is marked as clean. If it isn't clean already, ** make it so. */ SQLITE_PRIVATE void sqlite3PcacheMakeClean(PgHdr *p){ if( (p->flags & PGHDR_DIRTY) ){ - pcacheRemoveFromDirtyList(p); + pcacheManageDirtyList(p, PCACHE_DIRTYLIST_REMOVE); p->flags &= ~(PGHDR_DIRTY|PGHDR_NEED_SYNC); if( p->nRef==0 ){ pcacheUnpin(p); } } @@ -36745,12 +39610,11 @@ assert( p->nRef>0 ); assert( newPgno>0 ); sqlite3GlobalConfig.pcache2.xRekey(pCache->pCache, p->pPage, p->pgno,newPgno); p->pgno = newPgno; if( (p->flags&PGHDR_DIRTY) && (p->flags&PGHDR_NEED_SYNC) ){ - pcacheRemoveFromDirtyList(p); - pcacheAddToDirtyList(p); + pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT); } } /* ** Drop every cache entry whose page number is greater than "pgno". The @@ -36787,13 +39651,12 @@ /* ** Close a cache. */ SQLITE_PRIVATE void sqlite3PcacheClose(PCache *pCache){ - if( pCache->pCache ){ - sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache); - } + assert( pCache->pCache!=0 ); + sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache); } /* ** Discard the contents of the cache. */ @@ -36898,15 +39761,12 @@ /* ** Return the total number of pages in the cache. */ SQLITE_PRIVATE int sqlite3PcachePagecount(PCache *pCache){ - int nPage = 0; - if( pCache->pCache ){ - nPage = sqlite3GlobalConfig.pcache2.xPagecount(pCache->pCache); - } - return nPage; + assert( pCache->pCache!=0 ); + return sqlite3GlobalConfig.pcache2.xPagecount(pCache->pCache); } #ifdef SQLITE_TEST /* ** Get the suggested cache-size value. @@ -36918,25 +39778,30 @@ /* ** Set the suggested cache-size value. */ SQLITE_PRIVATE void sqlite3PcacheSetCachesize(PCache *pCache, int mxPage){ + assert( pCache->pCache!=0 ); pCache->szCache = mxPage; - if( pCache->pCache ){ - sqlite3GlobalConfig.pcache2.xCachesize(pCache->pCache, - numberOfCachePages(pCache)); - } + sqlite3GlobalConfig.pcache2.xCachesize(pCache->pCache, + numberOfCachePages(pCache)); } /* ** Free up as much memory as possible from the page cache. */ SQLITE_PRIVATE void sqlite3PcacheShrink(PCache *pCache){ - if( pCache->pCache ){ - sqlite3GlobalConfig.pcache2.xShrink(pCache->pCache); - } + assert( pCache->pCache!=0 ); + sqlite3GlobalConfig.pcache2.xShrink(pCache->pCache); } + +/* +** Return the size of the header added by this middleware layer +** in the page-cache hierarchy. +*/ +SQLITE_PRIVATE int sqlite3HeaderSizePcache(void){ return ROUND8(sizeof(PgHdr)); } + #if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG) /* ** For all dirty pages currently in the cache, invoke the specified ** callback. This is only used if the SQLITE_CHECK_PAGES macro is @@ -36965,11 +39830,11 @@ ************************************************************************* ** ** This file implements the default page cache implementation (the ** sqlite3_pcache interface). It also contains part of the implementation ** of the SQLITE_CONFIG_PAGECACHE and sqlite3_release_memory() features. -** If the default page cache implementation is overriden, then neither of +** If the default page cache implementation is overridden, then neither of ** these two features are available. */ typedef struct PCache1 PCache1; @@ -36976,11 +39841,11 @@ typedef struct PgHdr1 PgHdr1; typedef struct PgFreeslot PgFreeslot; typedef struct PGroup PGroup; /* Each page cache (or PCache) belongs to a PGroup. A PGroup is a set -** of one or more PCaches that are able to recycle each others unpinned +** of one or more PCaches that are able to recycle each other's unpinned ** pages when they are under memory pressure. A PGroup is an instance of ** the following object. ** ** This page cache implementation works in one of two modes: ** @@ -37047,10 +39912,11 @@ ** in memory. */ struct PgHdr1 { sqlite3_pcache_page page; unsigned int iKey; /* Key value (page number) */ + u8 isPinned; /* Page in use, not on the LRU list */ PgHdr1 *pNext; /* Next in hash table chain */ PCache1 *pCache; /* Cache that currently owns this page */ PgHdr1 *pLruNext; /* Next in LRU list of unpinned pages */ PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */ }; @@ -37145,20 +40011,20 @@ ** in pcache1 need to be protected via mutex. */ static void *pcache1Alloc(int nByte){ void *p = 0; assert( sqlite3_mutex_notheld(pcache1.grp.mutex) ); - sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, nByte); if( nByte<=pcache1.szSlot ){ sqlite3_mutex_enter(pcache1.mutex); p = (PgHdr1 *)pcache1.pFree; if( p ){ pcache1.pFree = pcache1.pFree->pNext; pcache1.nFreeSlot--; pcache1.bUnderPressure = pcache1.nFreeSlot<pcache1.nReserve; assert( pcache1.nFreeSlot>=0 ); - sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, 1); + sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, nByte); + sqlite3StatusUp(SQLITE_STATUS_PAGECACHE_USED, 1); } sqlite3_mutex_leave(pcache1.mutex); } if( p==0 ){ /* Memory is not available in the SQLITE_CONFIG_PAGECACHE pool. Get @@ -37167,11 +40033,12 @@ p = sqlite3Malloc(nByte); #ifndef SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS if( p ){ int sz = sqlite3MallocSize(p); sqlite3_mutex_enter(pcache1.mutex); - sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz); + sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, nByte); + sqlite3StatusUp(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz); sqlite3_mutex_leave(pcache1.mutex); } #endif sqlite3MemdebugSetType(p, MEMTYPE_PCACHE); } @@ -37185,11 +40052,11 @@ int nFreed = 0; if( p==0 ) return 0; if( p>=pcache1.pStart && p<pcache1.pEnd ){ PgFreeslot *pSlot; sqlite3_mutex_enter(pcache1.mutex); - sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, -1); + sqlite3StatusDown(SQLITE_STATUS_PAGECACHE_USED, 1); pSlot = (PgFreeslot*)p; pSlot->pNext = pcache1.pFree; pcache1.pFree = pSlot; pcache1.nFreeSlot++; pcache1.bUnderPressure = pcache1.nFreeSlot<pcache1.nReserve; @@ -37199,11 +40066,11 @@ assert( sqlite3MemdebugHasType(p, MEMTYPE_PCACHE) ); sqlite3MemdebugSetType(p, MEMTYPE_HEAP); nFreed = sqlite3MallocSize(p); #ifndef SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS sqlite3_mutex_enter(pcache1.mutex); - sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, -nFreed); + sqlite3StatusDown(SQLITE_STATUS_PAGECACHE_OVERFLOW, nFreed); sqlite3_mutex_leave(pcache1.mutex); #endif sqlite3_free(p); } return nFreed; @@ -37246,11 +40113,11 @@ pcache1Free(pPg); sqlite3_free(p); pPg = 0; } #else - pPg = pcache1Alloc(sizeof(PgHdr1) + pCache->szPage + pCache->szExtra); + pPg = pcache1Alloc(ROUND8(sizeof(PgHdr1)) + pCache->szPage + pCache->szExtra); p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage]; #endif pcache1EnterMutex(pCache->pGroup); if( pPg ){ @@ -37333,11 +40200,11 @@ ** This function is used to resize the hash table used by the cache passed ** as the first argument. ** ** The PCache mutex must be held when this function is called. */ -static int pcache1ResizeHash(PCache1 *p){ +static void pcache1ResizeHash(PCache1 *p){ PgHdr1 **apNew; unsigned int nNew; unsigned int i; assert( sqlite3_mutex_held(p->pGroup->mutex) ); @@ -37365,48 +40232,44 @@ } sqlite3_free(p->apHash); p->apHash = apNew; p->nHash = nNew; } - - return (p->apHash ? SQLITE_OK : SQLITE_NOMEM); } /* ** This function is used internally to remove the page pPage from the ** PGroup LRU list, if is part of it. If pPage is not part of the PGroup ** LRU list, then this function is a no-op. ** ** The PGroup mutex must be held when this function is called. -** -** If pPage is NULL then this routine is a no-op. */ static void pcache1PinPage(PgHdr1 *pPage){ PCache1 *pCache; PGroup *pGroup; - if( pPage==0 ) return; + assert( pPage!=0 ); + assert( pPage->isPinned==0 ); pCache = pPage->pCache; pGroup = pCache->pGroup; + assert( pPage->pLruNext || pPage==pGroup->pLruTail ); + assert( pPage->pLruPrev || pPage==pGroup->pLruHead ); assert( sqlite3_mutex_held(pGroup->mutex) ); - if( pPage->pLruNext || pPage==pGroup->pLruTail ){ - if( pPage->pLruPrev ){ - pPage->pLruPrev->pLruNext = pPage->pLruNext; - } - if( pPage->pLruNext ){ - pPage->pLruNext->pLruPrev = pPage->pLruPrev; - } - if( pGroup->pLruHead==pPage ){ - pGroup->pLruHead = pPage->pLruNext; - } - if( pGroup->pLruTail==pPage ){ - pGroup->pLruTail = pPage->pLruPrev; - } - pPage->pLruNext = 0; - pPage->pLruPrev = 0; - pPage->pCache->nRecyclable--; - } + if( pPage->pLruPrev ){ + pPage->pLruPrev->pLruNext = pPage->pLruNext; + }else{ + pGroup->pLruHead = pPage->pLruNext; + } + if( pPage->pLruNext ){ + pPage->pLruNext->pLruPrev = pPage->pLruPrev; + }else{ + pGroup->pLruTail = pPage->pLruPrev; + } + pPage->pLruNext = 0; + pPage->pLruPrev = 0; + pPage->isPinned = 1; + pCache->nRecyclable--; } /* ** Remove the page supplied as an argument from the hash table @@ -37434,10 +40297,11 @@ static void pcache1EnforceMaxPage(PGroup *pGroup){ assert( sqlite3_mutex_held(pGroup->mutex) ); while( pGroup->nCurrentPage>pGroup->nMaxPage && pGroup->pLruTail ){ PgHdr1 *p = pGroup->pLruTail; assert( p->pCache->pGroup==pGroup ); + assert( p->isPinned==0 ); pcache1PinPage(p); pcache1RemoveFromHash(p); pcache1FreePage(p); } } @@ -37461,11 +40325,11 @@ PgHdr1 *pPage; while( (pPage = *pp)!=0 ){ if( pPage->iKey>=iLimit ){ pCache->nPage--; *pp = pPage->pNext; - pcache1PinPage(pPage); + if( !pPage->isPinned ) pcache1PinPage(pPage); pcache1FreePage(pPage); }else{ pp = &pPage->pNext; TESTONLY( nPage++; ) } @@ -37502,10 +40366,13 @@ UNUSED_PARAMETER(NotUsed); assert( pcache1.isInit!=0 ); memset(&pcache1, 0, sizeof(pcache1)); } +/* forward declaration */ +static void pcache1Destroy(sqlite3_pcache *p); + /* ** Implementation of the sqlite3_pcache.xCreate method. ** ** Allocate a new cache. */ @@ -37546,16 +40413,21 @@ } pCache->pGroup = pGroup; pCache->szPage = szPage; pCache->szExtra = szExtra; pCache->bPurgeable = (bPurgeable ? 1 : 0); + pcache1EnterMutex(pGroup); + pcache1ResizeHash(pCache); if( bPurgeable ){ pCache->nMin = 10; - pcache1EnterMutex(pGroup); pGroup->nMinPage += pCache->nMin; pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; - pcache1LeaveMutex(pGroup); + } + pcache1LeaveMutex(pGroup); + if( pCache->nHash==0 ){ + pcache1Destroy((sqlite3_pcache*)pCache); + pCache = 0; } } return (sqlite3_pcache *)pCache; } @@ -37607,10 +40479,99 @@ n = pCache->nPage; pcache1LeaveMutex(pCache->pGroup); return n; } + +/* +** Implement steps 3, 4, and 5 of the pcache1Fetch() algorithm described +** in the header of the pcache1Fetch() procedure. +** +** This steps are broken out into a separate procedure because they are +** usually not needed, and by avoiding the stack initialization required +** for these steps, the main pcache1Fetch() procedure can run faster. +*/ +static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( + PCache1 *pCache, + unsigned int iKey, + int createFlag +){ + unsigned int nPinned; + PGroup *pGroup = pCache->pGroup; + PgHdr1 *pPage = 0; + + /* Step 3: Abort if createFlag is 1 but the cache is nearly full */ + assert( pCache->nPage >= pCache->nRecyclable ); + nPinned = pCache->nPage - pCache->nRecyclable; + assert( pGroup->mxPinned == pGroup->nMaxPage + 10 - pGroup->nMinPage ); + assert( pCache->n90pct == pCache->nMax*9/10 ); + if( createFlag==1 && ( + nPinned>=pGroup->mxPinned + || nPinned>=pCache->n90pct + || (pcache1UnderMemoryPressure(pCache) && pCache->nRecyclable<nPinned) + )){ + return 0; + } + + if( pCache->nPage>=pCache->nHash ) pcache1ResizeHash(pCache); + assert( pCache->nHash>0 && pCache->apHash ); + + /* Step 4. Try to recycle a page. */ + if( pCache->bPurgeable && pGroup->pLruTail && ( + (pCache->nPage+1>=pCache->nMax) + || pGroup->nCurrentPage>=pGroup->nMaxPage + || pcache1UnderMemoryPressure(pCache) + )){ + PCache1 *pOther; + pPage = pGroup->pLruTail; + assert( pPage->isPinned==0 ); + pcache1RemoveFromHash(pPage); + pcache1PinPage(pPage); + pOther = pPage->pCache; + + /* We want to verify that szPage and szExtra are the same for pOther + ** and pCache. Assert that we can verify this by comparing sums. */ + assert( (pCache->szPage & (pCache->szPage-1))==0 && pCache->szPage>=512 ); + assert( pCache->szExtra<512 ); + assert( (pOther->szPage & (pOther->szPage-1))==0 && pOther->szPage>=512 ); + assert( pOther->szExtra<512 ); + + if( pOther->szPage+pOther->szExtra != pCache->szPage+pCache->szExtra ){ + pcache1FreePage(pPage); + pPage = 0; + }else{ + pGroup->nCurrentPage -= (pOther->bPurgeable - pCache->bPurgeable); + } + } + + /* Step 5. If a usable page buffer has still not been found, + ** attempt to allocate a new one. + */ + if( !pPage ){ + if( createFlag==1 ) sqlite3BeginBenignMalloc(); + pPage = pcache1AllocPage(pCache); + if( createFlag==1 ) sqlite3EndBenignMalloc(); + } + + if( pPage ){ + unsigned int h = iKey % pCache->nHash; + pCache->nPage++; + pPage->iKey = iKey; + pPage->pNext = pCache->apHash[h]; + pPage->pCache = pCache; + pPage->pLruPrev = 0; + pPage->pLruNext = 0; + pPage->isPinned = 1; + *(void **)pPage->page.pExtra = 0; + pCache->apHash[h] = pPage; + if( iKey>pCache->iMaxKey ){ + pCache->iMaxKey = iKey; + } + } + return pPage; +} + /* ** Implementation of the sqlite3_pcache.xFetch method. ** ** Fetch a page by key value. ** @@ -37666,116 +40627,35 @@ static sqlite3_pcache_page *pcache1Fetch( sqlite3_pcache *p, unsigned int iKey, int createFlag ){ - unsigned int nPinned; PCache1 *pCache = (PCache1 *)p; - PGroup *pGroup; PgHdr1 *pPage = 0; + assert( offsetof(PgHdr1,page)==0 ); assert( pCache->bPurgeable || createFlag!=1 ); assert( pCache->bPurgeable || pCache->nMin==0 ); assert( pCache->bPurgeable==0 || pCache->nMin==10 ); assert( pCache->nMin==0 || pCache->bPurgeable ); - pcache1EnterMutex(pGroup = pCache->pGroup); + assert( pCache->nHash>0 ); + pcache1EnterMutex(pCache->pGroup); /* Step 1: Search the hash table for an existing entry. */ - if( pCache->nHash>0 ){ - unsigned int h = iKey % pCache->nHash; - for(pPage=pCache->apHash[h]; pPage&&pPage->iKey!=iKey; pPage=pPage->pNext); - } + pPage = pCache->apHash[iKey % pCache->nHash]; + while( pPage && pPage->iKey!=iKey ){ pPage = pPage->pNext; } /* Step 2: Abort if no existing page is found and createFlag is 0 */ - if( pPage || createFlag==0 ){ - pcache1PinPage(pPage); - goto fetch_out; - } - - /* The pGroup local variable will normally be initialized by the - ** pcache1EnterMutex() macro above. But if SQLITE_MUTEX_OMIT is defined, - ** then pcache1EnterMutex() is a no-op, so we have to initialize the - ** local variable here. Delaying the initialization of pGroup is an - ** optimization: The common case is to exit the module before reaching - ** this point. - */ -#ifdef SQLITE_MUTEX_OMIT - pGroup = pCache->pGroup; -#endif - - /* Step 3: Abort if createFlag is 1 but the cache is nearly full */ - assert( pCache->nPage >= pCache->nRecyclable ); - nPinned = pCache->nPage - pCache->nRecyclable; - assert( pGroup->mxPinned == pGroup->nMaxPage + 10 - pGroup->nMinPage ); - assert( pCache->n90pct == pCache->nMax*9/10 ); - if( createFlag==1 && ( - nPinned>=pGroup->mxPinned - || nPinned>=pCache->n90pct - || pcache1UnderMemoryPressure(pCache) - )){ - goto fetch_out; - } - - if( pCache->nPage>=pCache->nHash && pcache1ResizeHash(pCache) ){ - goto fetch_out; - } - assert( pCache->nHash>0 && pCache->apHash ); - - /* Step 4. Try to recycle a page. */ - if( pCache->bPurgeable && pGroup->pLruTail && ( - (pCache->nPage+1>=pCache->nMax) - || pGroup->nCurrentPage>=pGroup->nMaxPage - || pcache1UnderMemoryPressure(pCache) - )){ - PCache1 *pOther; - pPage = pGroup->pLruTail; - pcache1RemoveFromHash(pPage); - pcache1PinPage(pPage); - pOther = pPage->pCache; - - /* We want to verify that szPage and szExtra are the same for pOther - ** and pCache. Assert that we can verify this by comparing sums. */ - assert( (pCache->szPage & (pCache->szPage-1))==0 && pCache->szPage>=512 ); - assert( pCache->szExtra<512 ); - assert( (pOther->szPage & (pOther->szPage-1))==0 && pOther->szPage>=512 ); - assert( pOther->szExtra<512 ); - - if( pOther->szPage+pOther->szExtra != pCache->szPage+pCache->szExtra ){ - pcache1FreePage(pPage); - pPage = 0; - }else{ - pGroup->nCurrentPage -= (pOther->bPurgeable - pCache->bPurgeable); - } - } - - /* Step 5. If a usable page buffer has still not been found, - ** attempt to allocate a new one. - */ - if( !pPage ){ - if( createFlag==1 ) sqlite3BeginBenignMalloc(); - pPage = pcache1AllocPage(pCache); - if( createFlag==1 ) sqlite3EndBenignMalloc(); - } - if( pPage ){ - unsigned int h = iKey % pCache->nHash; - pCache->nPage++; - pPage->iKey = iKey; - pPage->pNext = pCache->apHash[h]; - pPage->pCache = pCache; - pPage->pLruPrev = 0; - pPage->pLruNext = 0; - *(void **)pPage->page.pExtra = 0; - pCache->apHash[h] = pPage; - } - -fetch_out: - if( pPage && iKey>pCache->iMaxKey ){ - pCache->iMaxKey = iKey; - } - pcache1LeaveMutex(pGroup); - return &pPage->page; + if( !pPage->isPinned ) pcache1PinPage(pPage); + }else if( createFlag ){ + /* Steps 3, 4, and 5 implemented by this subroutine */ + pPage = pcache1FetchStage2(pCache, iKey, createFlag); + } + assert( pPage==0 || pCache->iMaxKey>=iKey ); + pcache1LeaveMutex(pCache->pGroup); + return (sqlite3_pcache_page*)pPage; } /* ** Implementation of the sqlite3_pcache.xUnpin method. @@ -37797,10 +40677,11 @@ /* It is an error to call this function if the page is already ** part of the PGroup LRU list. */ assert( pPage->pLruPrev==0 && pPage->pLruNext==0 ); assert( pGroup->pLruHead!=pPage && pGroup->pLruTail!=pPage ); + assert( pPage->isPinned==1 ); if( reuseUnlikely || pGroup->nCurrentPage>pGroup->nMaxPage ){ pcache1RemoveFromHash(pPage); pcache1FreePage(pPage); }else{ @@ -37812,10 +40693,11 @@ }else{ pGroup->pLruTail = pPage; pGroup->pLruHead = pPage; } pCache->nRecyclable++; + pPage->isPinned = 0; } pcache1LeaveMutex(pCache->pGroup); } @@ -37915,10 +40797,23 @@ pcache1Destroy, /* xDestroy */ pcache1Shrink /* xShrink */ }; sqlite3_config(SQLITE_CONFIG_PCACHE2, &defaultMethods); } + +/* +** Return the size of the header on each page of this PCACHE implementation. +*/ +SQLITE_PRIVATE int sqlite3HeaderSizePcache1(void){ return ROUND8(sizeof(PgHdr1)); } + +/* +** Return the global mutex used by this PCACHE implementation. The +** sqlite3_status() routine needs access to this mutex. +*/ +SQLITE_PRIVATE sqlite3_mutex *sqlite3Pcache1Mutex(void){ + return pcache1.mutex; +} #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT /* ** This function is called to free superfluous dynamically allocated memory ** held by the pager system. Memory in use by any SQLite pager allocated @@ -37938,10 +40833,11 @@ while( (nReq<0 || nFree<nReq) && ((p=pcache1.grp.pLruTail)!=0) ){ nFree += pcache1MemSize(p->page.pBuf); #ifdef SQLITE_PCACHE_SEPARATE_HEADER nFree += sqlite3MemSize(p); #endif + assert( p->isPinned==0 ); pcache1PinPage(p); pcache1RemoveFromHash(p); pcache1FreePage(p); } pcache1LeaveMutex(&pcache1.grp); @@ -37962,10 +40858,11 @@ int *pnRecyclable /* OUT: Total number of pages available for recycling */ ){ PgHdr1 *p; int nRecyclable = 0; for(p=pcache1.grp.pLruHead; p; p=p->pLruNext){ + assert( p->isPinned==0 ); nRecyclable++; } *pnCurrent = pcache1.grp.nCurrentPage; *pnMax = (int)pcache1.grp.nMaxPage; *pnMin = (int)pcache1.grp.nMinPage; @@ -38025,11 +40922,11 @@ ** a non-zero batch number, it will see all prior INSERTs. ** ** No INSERTs may occurs after a SMALLEST. An assertion will fail if ** that is attempted. ** -** The cost of an INSERT is roughly constant. (Sometime new memory +** The cost of an INSERT is roughly constant. (Sometimes new memory ** has to be allocated on an INSERT.) The cost of a TEST with a new ** batch number is O(NlogN) where N is the number of elements in the RowSet. ** The cost of a TEST using the same batch number is O(logN). The cost ** of the first SMALLEST is O(NlogN). Second and subsequent SMALLEST ** primitives are constant time. The cost of DESTROY is O(N). @@ -38086,12 +40983,12 @@ struct RowSetEntry *pEntry; /* List of entries using pRight */ struct RowSetEntry *pLast; /* Last entry on the pEntry list */ struct RowSetEntry *pFresh; /* Source of new entry objects */ struct RowSetEntry *pForest; /* List of binary trees of entries */ u16 nFresh; /* Number of objects on pFresh */ - u8 rsFlags; /* Various flags */ - u8 iBatch; /* Current insert batch */ + u16 rsFlags; /* Various flags */ + int iBatch; /* Current insert batch */ }; /* ** Allowed values for RowSet.rsFlags */ @@ -38417,15 +41314,15 @@ /* ** Check to see if element iRowid was inserted into the rowset as ** part of any insert batch prior to iBatch. Return 1 or 0. ** -** If this is the first test of a new batch and if there exist entires -** on pRowSet->pEntry, then sort those entires into the forest at +** If this is the first test of a new batch and if there exist entries +** on pRowSet->pEntry, then sort those entries into the forest at ** pRowSet->pForest so that they can be tested. */ -SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, u8 iBatch, sqlite3_int64 iRowid){ +SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 iRowid){ struct RowSetEntry *p, *pTree; /* This routine is never called after sqlite3RowSetNext() */ assert( pRowSet!=0 && (pRowSet->rsFlags & ROWSET_NEXT)==0 ); @@ -38700,16 +41597,16 @@ ** are synced prior to the master journal being deleted. ** ** Definition: Two databases (or the same database at two points it time) ** are said to be "logically equivalent" if they give the same answer to ** all queries. Note in particular the content of freelist leaf -** pages can be changed arbitarily without effecting the logical equivalence +** pages can be changed arbitrarily without affecting the logical equivalence ** of the database. ** ** (7) At any time, if any subset, including the empty set and the total set, ** of the unsynced changes to a rollback journal are removed and the -** journal is rolled back, the resulting database file will be logical +** journal is rolled back, the resulting database file will be logically ** equivalent to the database file at the beginning of the transaction. ** ** (8) When a transaction is rolled back, the xTruncate method of the VFS ** is called to restore the database file to the same size it was at ** the beginning of the transaction. (In some VFSes, the xTruncate @@ -39002,11 +41899,11 @@ ** be a few redundant xLock() calls or a lock may be held for longer than ** required, but nothing really goes wrong. ** ** The exception is when the database file is unlocked as the pager moves ** from ERROR to OPEN state. At this point there may be a hot-journal file -** in the file-system that needs to be rolled back (as part of a OPEN->SHARED +** in the file-system that needs to be rolled back (as part of an OPEN->SHARED ** transition, by the same pager or any other). If the call to xUnlock() ** fails at this point and the pager is left holding an EXCLUSIVE lock, this ** can confuse the call to xCheckReservedLock() call made later as part ** of hot-journal detection. ** @@ -39085,11 +41982,11 @@ #define SPILLFLAG_OFF 0x01 /* Never spill cache. Set via pragma */ #define SPILLFLAG_ROLLBACK 0x02 /* Current rolling back, so do not spill */ #define SPILLFLAG_NOSYNC 0x04 /* Spill is ok, but do not sync */ /* -** A open page cache is an instance of struct Pager. A description of +** An open page cache is an instance of struct Pager. A description of ** some of the more important member variables follows: ** ** eState ** ** The current 'state' of the pager object. See the comment and state @@ -39250,17 +42147,18 @@ u8 noSync; /* Do not sync the journal if true */ u8 fullSync; /* Do extra syncs of the journal for robustness */ u8 ckptSyncFlags; /* SYNC_NORMAL or SYNC_FULL for checkpoint */ u8 walSyncFlags; /* SYNC_NORMAL or SYNC_FULL for wal writes */ u8 syncFlags; /* SYNC_NORMAL or SYNC_FULL otherwise */ - u8 tempFile; /* zFilename is a temporary file */ + u8 tempFile; /* zFilename is a temporary or immutable file */ + u8 noLock; /* Do not lock (except in WAL mode) */ u8 readOnly; /* True for a read-only database */ u8 memDb; /* True to inhibit all file I/O */ /************************************************************************** ** The following block contains those class members that change during - ** routine opertion. Class members not in this block are either fixed + ** routine operation. Class members not in this block are either fixed ** when the pager is first created or else only change when there is a ** significant mode change (such as changing the page_size, locking_mode, ** or the journal_mode). From another view, these class members describe ** the "state" of the pager, while other class members describe the ** "configuration" of the pager. @@ -39269,10 +42167,12 @@ u8 eLock; /* Current lock held on database file */ u8 changeCountDone; /* Set after incrementing the change-counter */ u8 setMaster; /* True if a m-j name has been written to jrnl */ u8 doNotSpill; /* Do not spill the cache when non-zero */ u8 subjInMemory; /* True to use in-memory sub-journals */ + u8 bUseFetch; /* True to use xFetch() */ + u8 hasBeenUsed; /* True if any content previously read from this pager*/ Pgno dbSize; /* Number of pages in the database */ Pgno dbOrigSize; /* dbSize before the current transaction */ Pgno dbFileSize; /* Number of pages in the database file */ Pgno dbHintSize; /* Value passed to FCNTL_SIZE_HINT call */ int errCode; /* One of several kinds of errors */ @@ -39286,13 +42186,13 @@ i64 journalOff; /* Current write offset in the journal file */ i64 journalHdr; /* Byte offset to previous journal header */ sqlite3_backup *pBackup; /* Pointer to list of ongoing backup processes */ PagerSavepoint *aSavepoint; /* Array of active savepoints */ int nSavepoint; /* Number of elements in aSavepoint[] */ + u32 iDataVersion; /* Changes whenever database content changes */ char dbFileVers[16]; /* Changes whenever database file changes */ - u8 bUseFetch; /* True to use xFetch() */ int nMmapOut; /* Number of mmap pages currently outstanding */ sqlite3_int64 szMmap; /* Desired maximum mmap size */ PgHdr *pMmapFreelist; /* List of free mmap page headers (pDirty) */ /* ** End of the routinely-changing class members @@ -39648,29 +42548,26 @@ ** PagerSavepoint.pInSavepoint. */ static int subjRequiresPage(PgHdr *pPg){ Pager *pPager = pPg->pPager; PagerSavepoint *p; - Pgno pgno; + Pgno pgno = pPg->pgno; int i; - if( pPager->nSavepoint ){ - pgno = pPg->pgno; - for(i=0; i<pPager->nSavepoint; i++){ - p = &pPager->aSavepoint[i]; - if( p->nOrig>=pgno && 0==sqlite3BitvecTest(p->pInSavepoint, pgno) ){ - return 1; - } + for(i=0; i<pPager->nSavepoint; i++){ + p = &pPager->aSavepoint[i]; + if( p->nOrig>=pgno && 0==sqlite3BitvecTest(p->pInSavepoint, pgno) ){ + return 1; } } return 0; } /* ** Return true if the page is already in the journal file. */ -static int pageInJournal(PgHdr *pPg){ - return sqlite3BitvecTest(pPg->pPager->pInJournal, pPg->pgno); +static int pageInJournal(Pager *pPager, PgHdr *pPg){ + return sqlite3BitvecTest(pPager->pInJournal, pPg->pgno); } /* ** Read a 32-bit integer from the given file descriptor. Store the integer ** that is read in *pRes. Return SQLITE_OK if everything worked, or an @@ -39718,11 +42615,11 @@ assert( !pPager->exclusiveMode || pPager->eLock==eLock ); assert( eLock==NO_LOCK || eLock==SHARED_LOCK ); assert( eLock!=NO_LOCK || pagerUseWal(pPager)==0 ); if( isOpen(pPager->fd) ){ assert( pPager->eLock>=eLock ); - rc = sqlite3OsUnlock(pPager->fd, eLock); + rc = pPager->noLock ? SQLITE_OK : sqlite3OsUnlock(pPager->fd, eLock); if( pPager->eLock!=UNKNOWN_LOCK ){ pPager->eLock = (u8)eLock; } IOTRACE(("UNLOCK %p %d\n", pPager, eLock)) } @@ -39742,11 +42639,11 @@ static int pagerLockDb(Pager *pPager, int eLock){ int rc = SQLITE_OK; assert( eLock==SHARED_LOCK || eLock==RESERVED_LOCK || eLock==EXCLUSIVE_LOCK ); if( pPager->eLock<eLock || pPager->eLock==UNKNOWN_LOCK ){ - rc = sqlite3OsLock(pPager->fd, eLock); + rc = pPager->noLock ? SQLITE_OK : sqlite3OsLock(pPager->fd, eLock); if( rc==SQLITE_OK && (pPager->eLock!=UNKNOWN_LOCK||eLock==EXCLUSIVE_LOCK) ){ pPager->eLock = (u8)eLock; IOTRACE(("LOCK %p %d\n", pPager, eLock)) } } @@ -39873,10 +42770,11 @@ if( SQLITE_OK!=(rc = sqlite3OsFileSize(pJrnl, &szJ)) || szJ<16 || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-16, &len)) || len>=nMaster + || len==0 || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-12, &cksum)) || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, aMagic, 8, szJ-8)) || memcmp(aMagic, aJournalMagic, 8) || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, zMaster, len, szJ-16-len)) ){ @@ -40250,16 +43148,15 @@ assert( pPager->setMaster==0 ); assert( !pagerUseWal(pPager) ); if( !zMaster || pPager->journalMode==PAGER_JOURNALMODE_MEMORY - || pPager->journalMode==PAGER_JOURNALMODE_OFF + || !isOpen(pPager->jfd) ){ return SQLITE_OK; } pPager->setMaster = 1; - assert( isOpen(pPager->jfd) ); assert( pPager->journalHdr <= pPager->journalOff ); /* Calculate the length in bytes and the checksum of zMaster */ for(nMaster=0; zMaster[nMaster]; nMaster++){ cksum += zMaster[nMaster]; @@ -40303,32 +43200,26 @@ rc = sqlite3OsTruncate(pPager->jfd, pPager->journalOff); } return rc; } -/* -** Find a page in the hash table given its page number. Return -** a pointer to the page or NULL if the requested page is not -** already in memory. -*/ -static PgHdr *pager_lookup(Pager *pPager, Pgno pgno){ - PgHdr *p; /* Return value */ - - /* It is not possible for a call to PcacheFetch() with createFlag==0 to - ** fail, since no attempt to allocate dynamic memory will be made. - */ - (void)sqlite3PcacheFetch(pPager->pPCache, pgno, 0, &p); - return p; -} - /* ** Discard the entire contents of the in-memory page-cache. */ static void pager_reset(Pager *pPager){ + pPager->iDataVersion++; sqlite3BackupRestart(pPager->pBackup); sqlite3PcacheClear(pPager->pPCache); } + +/* +** Return the pPager->iDataVersion value +*/ +SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager *pPager){ + assert( pPager->eState>PAGER_OPEN ); + return pPager->iDataVersion; +} /* ** Free all structures in the Pager.aSavepoint[] array and set both ** Pager.aSavepoint and Pager.nSavepoint to zero. Close the sub-journal ** if it is open and the pager is not in exclusive mode. @@ -40582,10 +43473,18 @@ }else if( pPager->journalMode==PAGER_JOURNALMODE_TRUNCATE ){ if( pPager->journalOff==0 ){ rc = SQLITE_OK; }else{ rc = sqlite3OsTruncate(pPager->jfd, 0); + if( rc==SQLITE_OK && pPager->fullSync ){ + /* Make sure the new file size is written into the inode right away. + ** Otherwise the journal might resurrect following a power loss and + ** cause the last transaction to roll back. See + ** https://bugzilla.mozilla.org/show_bug.cgi?id=1072773 + */ + rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags); + } } pPager->journalOff = 0; }else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST || (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL) ){ @@ -40610,14 +43509,14 @@ } #ifdef SQLITE_CHECK_PAGES sqlite3PcacheIterateDirty(pPager->pPCache, pager_set_pagehash); if( pPager->dbSize==0 && sqlite3PcacheRefCount(pPager->pPCache)>0 ){ - PgHdr *p = pager_lookup(pPager, 1); + PgHdr *p = sqlite3PagerLookup(pPager, 1); if( p ){ p->pageHash = 0; - sqlite3PagerUnref(p); + sqlite3PagerUnrefNotNull(p); } } #endif sqlite3BitvecDestroy(pPager->pInJournal); @@ -40641,10 +43540,15 @@ ** file. So it is safe to truncate the database file to its minimum ** required size. */ assert( pPager->eLock==EXCLUSIVE_LOCK ); rc = pager_truncate(pPager, pPager->dbSize); } + + if( rc==SQLITE_OK && bCommit && isOpen(pPager->fd) ){ + rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_COMMIT_PHASETWO, 0); + if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK; + } if( !pPager->exclusiveMode && (!pagerUseWal(pPager) || sqlite3WalExclusiveMode(pPager->pWal, 0)) ){ rc2 = pagerUnlockDb(pPager, SHARED_LOCK); @@ -40884,11 +43788,11 @@ ** Do not attempt to write if database file has never been opened. */ if( pagerUseWal(pPager) ){ pPg = 0; }else{ - pPg = pager_lookup(pPager, pgno); + pPg = sqlite3PagerLookup(pPager, pgno); } assert( pPg || !MEMDB ); assert( pPager->eState!=PAGER_OPEN || pPg==0 ); PAGERTRACE(("PLAYBACK %d page %d hash(%08x) %s\n", PAGERID(pPager), pgno, pager_datahash(pPager->pageSize, (u8*)aData), @@ -41064,11 +43968,11 @@ ** journal files extracted from regular rollback-journals. */ rc = sqlite3OsFileSize(pMaster, &nMasterJournal); if( rc!=SQLITE_OK ) goto delmaster_out; nMasterPtr = pVfs->mxPathname+1; - zMasterJournal = sqlite3Malloc((int)nMasterJournal + nMasterPtr + 1); + zMasterJournal = sqlite3Malloc(nMasterJournal + nMasterPtr + 1); if( !zMasterJournal ){ rc = SQLITE_NOMEM; goto delmaster_out; } zMasterPtr = &zMasterJournal[nMasterJournal+1]; @@ -41133,11 +44037,11 @@ ** DBMOD or OPEN state, this function is a no-op. Otherwise, the size ** of the file is changed to nPage pages (nPage*pPager->pageSize bytes). ** If the file on disk is currently larger than nPage pages, then use the VFS ** xTruncate() method to truncate it. ** -** Or, it might might be the case that the file on disk is smaller than +** Or, it might be the case that the file on disk is smaller than ** nPage pages. Some operating system implementations can get confused if ** you try to truncate a file to some size that is larger than it ** currently is, so detect this case and write a single zero byte to ** the end of the new file instead. ** @@ -41192,11 +44096,11 @@ } /* ** Set the value of the Pager.sectorSize variable for the given ** pager based on the value returned by the xSectorSize method -** of the open database file. The sector size will be used used +** of the open database file. The sector size will be used ** to determine the size and alignment of journal header and ** master journal pointers within created journal files. ** ** For temporary files the effective sector size is always 512 bytes. ** @@ -41455,11 +44359,11 @@ testcase( rc!=SQLITE_OK ); } if( rc==SQLITE_OK && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) ){ - rc = sqlite3PagerSync(pPager); + rc = sqlite3PagerSync(pPager, 0); } if( rc==SQLITE_OK ){ rc = pager_end_transaction(pPager, zMaster[0]!='\0', 0); testcase( rc!=SQLITE_OK ); } @@ -41527,11 +44431,11 @@ ** should be page numbers which are never 0xffffffff. So filling ** pPager->dbFileVers[] with all 0xff bytes should suffice. ** ** For an encrypted database, the situation is more complex: bytes ** 24..39 of the database are white noise. But the probability of - ** white noising equaling 16 bytes of 0xff is vanishingly small so + ** white noise equaling 16 bytes of 0xff is vanishingly small so ** we should still be ok. */ memset(pPager->dbFileVers, 0xff, sizeof(pPager->dbFileVers)); }else{ u8 *dbFileVers = &((u8*)pPg->pData)[24]; @@ -41601,11 +44505,11 @@ rc = readDbPage(pPg, iFrame); } if( rc==SQLITE_OK ){ pPager->xReiniter(pPg); } - sqlite3PagerUnref(pPg); + sqlite3PagerUnrefNotNull(pPg); } } /* Normally, if a transaction is rolled back, any backup processes are ** updated as data is copied out of the rollback journal and into the @@ -42254,15 +45158,19 @@ if( !pNew ) rc = SQLITE_NOMEM; } if( rc==SQLITE_OK ){ pager_reset(pPager); - pPager->dbSize = (Pgno)((nByte+pageSize-1)/pageSize); - pPager->pageSize = pageSize; + rc = sqlite3PcacheSetPageSize(pPager->pPCache, pageSize); + } + if( rc==SQLITE_OK ){ sqlite3PageFree(pPager->pTmpSpace); pPager->pTmpSpace = pNew; - sqlite3PcacheSetPageSize(pPager->pPCache, pageSize); + pPager->dbSize = (Pgno)((nByte+pageSize-1)/pageSize); + pPager->pageSize = pageSize; + }else{ + sqlite3PageFree(pNew); } } *pPageSize = pPager->pageSize; if( rc==SQLITE_OK ){ @@ -42392,11 +45300,11 @@ */ static int pager_wait_on_lock(Pager *pPager, int locktype){ int rc; /* Return code */ /* Check that this is either a no-op (because the requested lock is - ** already held, or one of the transistions that the busy-handler + ** already held), or one of the transitions that the busy-handler ** may be invoked during, according to the comment above ** sqlite3PagerSetBusyhandler(). */ assert( (pPager->eLock>=locktype) || (pPager->eLock==NO_LOCK && locktype==SHARED_LOCK) @@ -42511,11 +45419,11 @@ Pgno pgno, /* Page number */ void *pData, /* xFetch()'d data for this page */ PgHdr **ppPage /* OUT: Acquired page object */ ){ PgHdr *p; /* Memory mapped page to return */ - + if( pPager->pMmapFreelist ){ *ppPage = p = pPager->pMmapFreelist; pPager->pMmapFreelist = p->pDirty; p->pDirty = 0; memset(p->pExtra, 0, pPager->nExtra); @@ -42956,11 +45864,11 @@ /* Open the sub-journal, if it has not already been opened */ assert( pPager->useJournal ); assert( isOpen(pPager->jfd) || pagerUseWal(pPager) ); assert( isOpen(pPager->sjfd) || pPager->nSubRec==0 ); assert( pagerUseWal(pPager) - || pageInJournal(pPg) + || pageInJournal(pPager, pPg) || pPg->pgno>pPager->dbOrigSize ); rc = openSubJournal(pPager); /* If the sub-journal was opened successfully (or was already open), @@ -43020,12 +45928,12 @@ ** The doNotSpill ROLLBACK and OFF bits inhibits all cache spilling ** regardless of whether or not a sync is required. This is set during ** a rollback or by user request, respectively. ** ** Spilling is also prohibited when in an error state since that could - ** lead to database corruption. In the current implementaton it - ** is impossible for sqlite3PcacheFetch() to be called with createFlag==1 + ** lead to database corruption. In the current implementation it + ** is impossible for sqlite3PcacheFetch() to be called with createFlag==3 ** while in the error state, hence it is impossible for this routine to ** be called in the error state. Nevertheless, we include a NEVER() ** test for the error state as a safeguard against future changes. */ if( NEVER(pPager->errCode) ) return SQLITE_OK; @@ -43296,47 +46204,59 @@ ** ** + SQLITE_DEFAULT_PAGE_SIZE, ** + The value returned by sqlite3OsSectorSize() ** + The largest page size that can be written atomically. */ - if( rc==SQLITE_OK && !readOnly ){ - setSectorSize(pPager); - assert(SQLITE_DEFAULT_PAGE_SIZE<=SQLITE_MAX_DEFAULT_PAGE_SIZE); - if( szPageDflt<pPager->sectorSize ){ - if( pPager->sectorSize>SQLITE_MAX_DEFAULT_PAGE_SIZE ){ - szPageDflt = SQLITE_MAX_DEFAULT_PAGE_SIZE; - }else{ - szPageDflt = (u32)pPager->sectorSize; - } - } + if( rc==SQLITE_OK ){ + int iDc = sqlite3OsDeviceCharacteristics(pPager->fd); + if( !readOnly ){ + setSectorSize(pPager); + assert(SQLITE_DEFAULT_PAGE_SIZE<=SQLITE_MAX_DEFAULT_PAGE_SIZE); + if( szPageDflt<pPager->sectorSize ){ + if( pPager->sectorSize>SQLITE_MAX_DEFAULT_PAGE_SIZE ){ + szPageDflt = SQLITE_MAX_DEFAULT_PAGE_SIZE; + }else{ + szPageDflt = (u32)pPager->sectorSize; + } + } #ifdef SQLITE_ENABLE_ATOMIC_WRITE - { - int iDc = sqlite3OsDeviceCharacteristics(pPager->fd); - int ii; - assert(SQLITE_IOCAP_ATOMIC512==(512>>8)); - assert(SQLITE_IOCAP_ATOMIC64K==(65536>>8)); - assert(SQLITE_MAX_DEFAULT_PAGE_SIZE<=65536); - for(ii=szPageDflt; ii<=SQLITE_MAX_DEFAULT_PAGE_SIZE; ii=ii*2){ - if( iDc&(SQLITE_IOCAP_ATOMIC|(ii>>8)) ){ - szPageDflt = ii; + { + int ii; + assert(SQLITE_IOCAP_ATOMIC512==(512>>8)); + assert(SQLITE_IOCAP_ATOMIC64K==(65536>>8)); + assert(SQLITE_MAX_DEFAULT_PAGE_SIZE<=65536); + for(ii=szPageDflt; ii<=SQLITE_MAX_DEFAULT_PAGE_SIZE; ii=ii*2){ + if( iDc&(SQLITE_IOCAP_ATOMIC|(ii>>8)) ){ + szPageDflt = ii; + } } } +#endif } -#endif + pPager->noLock = sqlite3_uri_boolean(zFilename, "nolock", 0); + if( (iDc & SQLITE_IOCAP_IMMUTABLE)!=0 + || sqlite3_uri_boolean(zFilename, "immutable", 0) ){ + vfsFlags |= SQLITE_OPEN_READONLY; + goto act_like_temp_file; + } } }else{ /* If a temporary file is requested, it is not opened immediately. ** In this case we accept the default page size and delay actually ** opening the file until the first call to OsWrite(). ** ** This branch is also run for an in-memory database. An in-memory ** database is the same as a temp-file that is never written out to ** disk and uses an in-memory rollback journal. + ** + ** This branch also runs for files marked as immutable. */ +act_like_temp_file: tempFile = 1; - pPager->eState = PAGER_READER; - pPager->eLock = EXCLUSIVE_LOCK; + pPager->eState = PAGER_READER; /* Pretend we already have a lock */ + pPager->eLock = EXCLUSIVE_LOCK; /* Pretend we are in EXCLUSIVE locking mode */ + pPager->noLock = 1; /* Do no locking */ readOnly = (vfsFlags&SQLITE_OPEN_READONLY); } /* The following call to PagerSetPagesize() serves to set the value of ** Pager.pageSize and to allocate the Pager.pTmpSpace buffer. @@ -43345,26 +46265,27 @@ assert( pPager->memDb==0 ); rc = sqlite3PagerSetPagesize(pPager, &szPageDflt, -1); testcase( rc!=SQLITE_OK ); } - /* If an error occurred in either of the blocks above, free the - ** Pager structure and close the file. + /* Initialize the PCache object. */ + if( rc==SQLITE_OK ){ + assert( nExtra<1000 ); + nExtra = ROUND8(nExtra); + rc = sqlite3PcacheOpen(szPageDflt, nExtra, !memDb, + !memDb?pagerStress:0, (void *)pPager, pPager->pPCache); + } + + /* If an error occurred above, free the Pager structure and close the file. */ if( rc!=SQLITE_OK ){ - assert( !pPager->pTmpSpace ); sqlite3OsClose(pPager->fd); + sqlite3PageFree(pPager->pTmpSpace); sqlite3_free(pPager); return rc; } - /* Initialize the PCache object. */ - assert( nExtra<1000 ); - nExtra = ROUND8(nExtra); - sqlite3PcacheOpen(szPageDflt, nExtra, !memDb, - !memDb?pagerStress:0, (void *)pPager, pPager->pPCache); - PAGERTRACE(("OPEN %d %s\n", FILEHANDLEID(pPager->fd), pPager->zFilename)); IOTRACE(("OPEN %p %s\n", pPager, pPager->zFilename)) pPager->useJournal = (u8)useJournal; /* pPager->stmtOpen = 0; */ @@ -43373,13 +46294,10 @@ /* pPager->stmtSize = 0; */ /* pPager->stmtJSize = 0; */ /* pPager->nPage = 0; */ pPager->mxPgno = SQLITE_MAX_PAGE_COUNT; /* pPager->state = PAGER_UNLOCK; */ -#if 0 - assert( pPager->state == (tempFile ? PAGER_EXCLUSIVE : PAGER_UNLOCK) ); -#endif /* pPager->errMask = 0; */ pPager->tempFile = (u8)tempFile; assert( tempFile==PAGER_LOCKINGMODE_NORMAL || tempFile==PAGER_LOCKINGMODE_EXCLUSIVE ); assert( PAGER_LOCKINGMODE_EXCLUSIVE==1 ); @@ -43420,10 +46338,34 @@ *ppPager = pPager; return SQLITE_OK; } + +/* Verify that the database file has not be deleted or renamed out from +** under the pager. Return SQLITE_OK if the database is still were it ought +** to be on disk. Return non-zero (SQLITE_READONLY_DBMOVED or some other error +** code from sqlite3OsAccess()) if the database has gone missing. +*/ +static int databaseIsUnmoved(Pager *pPager){ + int bHasMoved = 0; + int rc; + + if( pPager->tempFile ) return SQLITE_OK; + if( pPager->dbSize==0 ) return SQLITE_OK; + assert( pPager->zFilename && pPager->zFilename[0] ); + rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_HAS_MOVED, &bHasMoved); + if( rc==SQLITE_NOTFOUND ){ + /* If the HAS_MOVED file-control is unimplemented, assume that the file + ** has not been moved. That is the historical behavior of SQLite: prior to + ** version 3.8.3, it never checked */ + rc = SQLITE_OK; + }else if( rc==SQLITE_OK && bHasMoved ){ + rc = SQLITE_READONLY_DBMOVED; + } + return rc; +} /* ** This function is called after transitioning from PAGER_UNLOCK to ** PAGER_SHARED state. It tests if there is a hot journal present in @@ -43486,19 +46428,21 @@ */ rc = sqlite3OsCheckReservedLock(pPager->fd, &locked); if( rc==SQLITE_OK && !locked ){ Pgno nPage; /* Number of pages in database file */ - /* Check the size of the database file. If it consists of 0 pages, - ** then delete the journal file. See the header comment above for - ** the reasoning here. Delete the obsolete journal file under - ** a RESERVED lock to avoid race conditions and to avoid violating - ** [H33020]. - */ rc = pagerPagecount(pPager, &nPage); if( rc==SQLITE_OK ){ - if( nPage==0 ){ + /* If the database is zero pages in size, that means that either (1) the + ** journal is a remnant from a prior database with the same name where + ** the database file but not the journal was deleted, or (2) the initial + ** transaction that populates a new database is being rolled back. + ** In either case, the journal file can be deleted. However, take care + ** not to delete the journal file if it is already open due to + ** journal_mode=PERSIST. + */ + if( nPage==0 && !jrnlOpen ){ sqlite3BeginBenignMalloc(); if( pagerLockDb(pPager, RESERVED_LOCK)==SQLITE_OK ){ sqlite3OsDelete(pVfs, pPager->zJournal, 0); if( !pPager->exclusiveMode ) pagerUnlockDb(pPager, SHARED_LOCK); } @@ -43524,11 +46468,11 @@ sqlite3OsClose(pPager->jfd); } *pExists = (first!=0); }else if( rc==SQLITE_CANTOPEN ){ /* If we cannot open the rollback journal file in order to see if - ** its has a zero header, that might be due to an I/O error, or + ** it has a zero header, that might be due to an I/O error, or ** it might be due to the race condition described above and in ** ticket #3883. Either way, assume that the journal is hot. ** This might be a false positive. But if it is, then the ** automatic journal playback and recovery mechanism will deal ** with it under an EXCLUSIVE lock where we do not need to @@ -43706,20 +46650,16 @@ assert( (pPager->eLock==SHARED_LOCK) || (pPager->exclusiveMode && pPager->eLock>SHARED_LOCK) ); } - if( !pPager->tempFile && ( - pPager->pBackup - || sqlite3PcachePagecount(pPager->pPCache)>0 - || USEFETCH(pPager) - )){ - /* The shared-lock has just been acquired on the database file - ** and there are already pages in the cache (from a previous - ** read or write transaction). Check to see if the database - ** has been modified. If the database has changed, flush the - ** cache. + if( !pPager->tempFile && pPager->hasBeenUsed ){ + /* The shared-lock has just been acquired then check to + ** see if the database has been modified. If the database has changed, + ** flush the cache. The pPager->hasBeenUsed flag prevents this from + ** occurring on the very first access to a file, in order to save a + ** single unnecessary sqlite3OsRead() call at the start-up. ** ** Database changes is detected by looking at 15 bytes beginning ** at offset 24 into the file. The first 4 of these 16 bytes are ** a 32-bit counter that is incremented with each change. The ** other bytes change randomly with each file change when @@ -43880,32 +46820,32 @@ assert( noContent==0 || bMmapOk==0 ); if( pgno==0 ){ return SQLITE_CORRUPT_BKPT; } + pPager->hasBeenUsed = 1; /* If the pager is in the error state, return an error immediately. ** Otherwise, request the page from the PCache layer. */ if( pPager->errCode!=SQLITE_OK ){ rc = pPager->errCode; }else{ - if( bMmapOk && pagerUseWal(pPager) ){ rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame); if( rc!=SQLITE_OK ) goto pager_acquire_err; } - if( iFrame==0 && bMmapOk ){ + if( bMmapOk && iFrame==0 ){ void *pData = 0; rc = sqlite3OsFetch(pPager->fd, (i64)(pgno-1) * pPager->pageSize, pPager->pageSize, &pData ); if( rc==SQLITE_OK && pData ){ if( pPager->eState>PAGER_READER ){ - (void)sqlite3PcacheFetch(pPager->pPCache, pgno, 0, &pPg); + pPg = sqlite3PagerLookup(pPager, pgno); } if( pPg==0 ){ rc = pagerAcquireMapPage(pPager, pgno, pData, &pPg); }else{ sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1)*pPager->pageSize, pData); @@ -43919,11 +46859,20 @@ if( rc!=SQLITE_OK ){ goto pager_acquire_err; } } - rc = sqlite3PcacheFetch(pPager->pPCache, pgno, 1, ppPage); + { + sqlite3_pcache_page *pBase; + pBase = sqlite3PcacheFetch(pPager->pPCache, pgno, 3); + if( pBase==0 ){ + rc = sqlite3PcacheFetchStress(pPager->pPCache, pgno, &pBase); + if( rc!=SQLITE_OK ) goto pager_acquire_err; + } + pPg = *ppPage = sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pBase); + if( pPg==0 ) rc = SQLITE_NOMEM; + } } if( rc!=SQLITE_OK ){ /* Either the call to sqlite3PcacheFetch() returned an error or the ** pager was already in the error-state when this function was called. @@ -44016,17 +46965,17 @@ ** in the page if the page is not already in cache. This routine ** returns NULL if the page is not in cache or if a disk I/O error ** has ever happened. */ SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){ - PgHdr *pPg = 0; + sqlite3_pcache_page *pPage; assert( pPager!=0 ); assert( pgno!=0 ); assert( pPager->pPCache!=0 ); - assert( pPager->eState>=PAGER_READER && pPager->eState!=PAGER_ERROR ); - sqlite3PcacheFetch(pPager->pPCache, pgno, 0, &pPg); - return pPg; + pPage = sqlite3PcacheFetch(pPager->pPCache, pgno, 0); + assert( pPage==0 || pPager->hasBeenUsed ); + return sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pPage); } /* ** Release a page reference. ** @@ -44033,20 +46982,23 @@ ** If the number of references to the page drop to zero, then the ** page is added to the LRU list. When all references to all pages ** are released, a rollback occurs and the lock on the database is ** removed. */ +SQLITE_PRIVATE void sqlite3PagerUnrefNotNull(DbPage *pPg){ + Pager *pPager; + assert( pPg!=0 ); + pPager = pPg->pPager; + if( pPg->flags & PGHDR_MMAP ){ + pagerReleaseMapPage(pPg); + }else{ + sqlite3PcacheRelease(pPg); + } + pagerUnlockIfUnused(pPager); +} SQLITE_PRIVATE void sqlite3PagerUnref(DbPage *pPg){ - if( pPg ){ - Pager *pPager = pPg->pPager; - if( pPg->flags & PGHDR_MMAP ){ - pagerReleaseMapPage(pPg); - }else{ - sqlite3PcacheRelease(pPg); - } - pagerUnlockIfUnused(pPager); - } + if( pPg ) sqlite3PagerUnrefNotNull(pPg); } /* ** This function is called at the start of every write transaction. ** There must already be a RESERVED or EXCLUSIVE lock on the database @@ -44097,17 +47049,23 @@ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE| (pPager->tempFile ? (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL): (SQLITE_OPEN_MAIN_JOURNAL) ); - #ifdef SQLITE_ENABLE_ATOMIC_WRITE - rc = sqlite3JournalOpen( - pVfs, pPager->zJournal, pPager->jfd, flags, jrnlBufferSize(pPager) - ); - #else - rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, flags, 0); - #endif + + /* Verify that the database still has the same name as it did when + ** it was originally opened. */ + rc = databaseIsUnmoved(pPager); + if( rc==SQLITE_OK ){ +#ifdef SQLITE_ENABLE_ATOMIC_WRITE + rc = sqlite3JournalOpen( + pVfs, pPager->zJournal, pPager->jfd, flags, jrnlBufferSize(pPager) + ); +#else + rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, flags, 0); +#endif + } } assert( rc!=SQLITE_OK || isOpen(pPager->jfd) ); } @@ -44224,13 +47182,13 @@ ** one of the journals, the corresponding bit is set in the ** Pager.pInJournal bitvec and the PagerSavepoint.pInSavepoint bitvecs ** of any open savepoints as appropriate. */ static int pager_write(PgHdr *pPg){ - void *pData = pPg->pData; Pager *pPager = pPg->pPager; int rc = SQLITE_OK; + int inJournal; /* This routine is not called unless a write-transaction has already ** been started. The journal file may or may not be open at this point. ** It is never called in the ERROR state. */ @@ -44237,18 +47195,12 @@ assert( pPager->eState==PAGER_WRITER_LOCKED || pPager->eState==PAGER_WRITER_CACHEMOD || pPager->eState==PAGER_WRITER_DBMOD ); assert( assert_pager_state(pPager) ); - - /* If an error has been previously detected, report the same error - ** again. This should not happen, but the check provides robustness. */ - if( NEVER(pPager->errCode) ) return pPager->errCode; - - /* Higher-level routines never call this function if database is not - ** writable. But check anyway, just for robustness. */ - if( NEVER(pPager->readOnly) ) return SQLITE_PERM; + assert( pPager->errCode==0 ); + assert( pPager->readOnly==0 ); CHECK_PAGE(pPg); /* The journal file needs to be opened. Higher level routines have already ** obtained the necessary locks to begin the write-transaction, but the @@ -44268,19 +47220,20 @@ /* Mark the page as dirty. If the page has already been written ** to the journal then we can return right away. */ sqlite3PcacheMakeDirty(pPg); - if( pageInJournal(pPg) && !subjRequiresPage(pPg) ){ + inJournal = pageInJournal(pPager, pPg); + if( inJournal && (pPager->nSavepoint==0 || !subjRequiresPage(pPg)) ){ assert( !pagerUseWal(pPager) ); }else{ /* The transaction journal now exists and we have a RESERVED or an ** EXCLUSIVE lock on the main database file. Write the current page to ** the transaction journal if it is not there already. */ - if( !pageInJournal(pPg) && !pagerUseWal(pPager) ){ + if( !inJournal && !pagerUseWal(pPager) ){ assert( pagerUseWal(pPager)==0 ); if( pPg->pgno<=pPager->dbOrigSize && isOpen(pPager->jfd) ){ u32 cksum; char *pData2; i64 iOff = pPager->journalOff; @@ -44289,11 +47242,11 @@ ** contains the database locks. The following assert verifies ** that we do not. */ assert( pPg->pgno!=PAGER_MJ_PGNO(pPager) ); assert( pPager->journalHdr<=pPager->journalOff ); - CODEC2(pPager, pData, pPg->pgno, 7, return SQLITE_NOMEM, pData2); + CODEC2(pPager, pPg->pData, pPg->pgno, 7, return SQLITE_NOMEM, pData2); cksum = pager_cksum(pPager, (u8*)pData2); /* Even if an IO or diskfull error occurs while journalling the ** page in the block above, set the need-sync flag for the page. ** Otherwise, when the transaction is rolled back, the logic in @@ -44341,11 +47294,11 @@ /* If the statement journal is open and the page is not in it, ** then write the current page to the statement journal. Note that ** the statement journal format differs from the standard journal format ** in that it omits the checksums and the header. */ - if( subjRequiresPage(pPg) ){ + if( pPager->nSavepoint>0 && subjRequiresPage(pPg) ){ rc = subjournalPage(pPg); } } /* Update the database size and return. @@ -44353,10 +47306,101 @@ if( pPager->dbSize<pPg->pgno ){ pPager->dbSize = pPg->pgno; } return rc; } + +/* +** This is a variant of sqlite3PagerWrite() that runs when the sector size +** is larger than the page size. SQLite makes the (reasonable) assumption that +** all bytes of a sector are written together by hardware. Hence, all bytes of +** a sector need to be journalled in case of a power loss in the middle of +** a write. +** +** Usually, the sector size is less than or equal to the page size, in which +** case pages can be individually written. This routine only runs in the exceptional +** case where the page size is smaller than the sector size. +*/ +static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){ + int rc = SQLITE_OK; /* Return code */ + Pgno nPageCount; /* Total number of pages in database file */ + Pgno pg1; /* First page of the sector pPg is located on. */ + int nPage = 0; /* Number of pages starting at pg1 to journal */ + int ii; /* Loop counter */ + int needSync = 0; /* True if any page has PGHDR_NEED_SYNC */ + Pager *pPager = pPg->pPager; /* The pager that owns pPg */ + Pgno nPagePerSector = (pPager->sectorSize/pPager->pageSize); + + /* Set the doNotSpill NOSYNC bit to 1. This is because we cannot allow + ** a journal header to be written between the pages journaled by + ** this function. + */ + assert( !MEMDB ); + assert( (pPager->doNotSpill & SPILLFLAG_NOSYNC)==0 ); + pPager->doNotSpill |= SPILLFLAG_NOSYNC; + + /* This trick assumes that both the page-size and sector-size are + ** an integer power of 2. It sets variable pg1 to the identifier + ** of the first page of the sector pPg is located on. + */ + pg1 = ((pPg->pgno-1) & ~(nPagePerSector-1)) + 1; + + nPageCount = pPager->dbSize; + if( pPg->pgno>nPageCount ){ + nPage = (pPg->pgno - pg1)+1; + }else if( (pg1+nPagePerSector-1)>nPageCount ){ + nPage = nPageCount+1-pg1; + }else{ + nPage = nPagePerSector; + } + assert(nPage>0); + assert(pg1<=pPg->pgno); + assert((pg1+nPage)>pPg->pgno); + + for(ii=0; ii<nPage && rc==SQLITE_OK; ii++){ + Pgno pg = pg1+ii; + PgHdr *pPage; + if( pg==pPg->pgno || !sqlite3BitvecTest(pPager->pInJournal, pg) ){ + if( pg!=PAGER_MJ_PGNO(pPager) ){ + rc = sqlite3PagerGet(pPager, pg, &pPage); + if( rc==SQLITE_OK ){ + rc = pager_write(pPage); + if( pPage->flags&PGHDR_NEED_SYNC ){ + needSync = 1; + } + sqlite3PagerUnrefNotNull(pPage); + } + } + }else if( (pPage = sqlite3PagerLookup(pPager, pg))!=0 ){ + if( pPage->flags&PGHDR_NEED_SYNC ){ + needSync = 1; + } + sqlite3PagerUnrefNotNull(pPage); + } + } + + /* If the PGHDR_NEED_SYNC flag is set for any of the nPage pages + ** starting at pg1, then it needs to be set for all of them. Because + ** writing to any of these nPage pages may damage the others, the + ** journal file must contain sync()ed copies of all of them + ** before any of them can be written out to the database file. + */ + if( rc==SQLITE_OK && needSync ){ + assert( !MEMDB ); + for(ii=0; ii<nPage; ii++){ + PgHdr *pPage = sqlite3PagerLookup(pPager, pg1+ii); + if( pPage ){ + pPage->flags |= PGHDR_NEED_SYNC; + sqlite3PagerUnrefNotNull(pPage); + } + } + } + + assert( (pPager->doNotSpill & SPILLFLAG_NOSYNC)!=0 ); + pPager->doNotSpill &= ~SPILLFLAG_NOSYNC; + return rc; +} /* ** Mark a data page as writeable. This routine must be called before ** making changes to a page. The caller must check the return value ** of this function and be careful not to change any page data unless @@ -44368,100 +47412,20 @@ ** must have been written to the journal file before returning. ** ** If an error occurs, SQLITE_NOMEM or an IO error code is returned ** as appropriate. Otherwise, SQLITE_OK. */ -SQLITE_PRIVATE int sqlite3PagerWrite(DbPage *pDbPage){ - int rc = SQLITE_OK; - - PgHdr *pPg = pDbPage; - Pager *pPager = pPg->pPager; - Pgno nPagePerSector = (pPager->sectorSize/pPager->pageSize); - +SQLITE_PRIVATE int sqlite3PagerWrite(PgHdr *pPg){ assert( (pPg->flags & PGHDR_MMAP)==0 ); - assert( pPager->eState>=PAGER_WRITER_LOCKED ); - assert( pPager->eState!=PAGER_ERROR ); - assert( assert_pager_state(pPager) ); - - if( nPagePerSector>1 ){ - Pgno nPageCount; /* Total number of pages in database file */ - Pgno pg1; /* First page of the sector pPg is located on. */ - int nPage = 0; /* Number of pages starting at pg1 to journal */ - int ii; /* Loop counter */ - int needSync = 0; /* True if any page has PGHDR_NEED_SYNC */ - - /* Set the doNotSpill NOSYNC bit to 1. This is because we cannot allow - ** a journal header to be written between the pages journaled by - ** this function. - */ - assert( !MEMDB ); - assert( (pPager->doNotSpill & SPILLFLAG_NOSYNC)==0 ); - pPager->doNotSpill |= SPILLFLAG_NOSYNC; - - /* This trick assumes that both the page-size and sector-size are - ** an integer power of 2. It sets variable pg1 to the identifier - ** of the first page of the sector pPg is located on. - */ - pg1 = ((pPg->pgno-1) & ~(nPagePerSector-1)) + 1; - - nPageCount = pPager->dbSize; - if( pPg->pgno>nPageCount ){ - nPage = (pPg->pgno - pg1)+1; - }else if( (pg1+nPagePerSector-1)>nPageCount ){ - nPage = nPageCount+1-pg1; - }else{ - nPage = nPagePerSector; - } - assert(nPage>0); - assert(pg1<=pPg->pgno); - assert((pg1+nPage)>pPg->pgno); - - for(ii=0; ii<nPage && rc==SQLITE_OK; ii++){ - Pgno pg = pg1+ii; - PgHdr *pPage; - if( pg==pPg->pgno || !sqlite3BitvecTest(pPager->pInJournal, pg) ){ - if( pg!=PAGER_MJ_PGNO(pPager) ){ - rc = sqlite3PagerGet(pPager, pg, &pPage); - if( rc==SQLITE_OK ){ - rc = pager_write(pPage); - if( pPage->flags&PGHDR_NEED_SYNC ){ - needSync = 1; - } - sqlite3PagerUnref(pPage); - } - } - }else if( (pPage = pager_lookup(pPager, pg))!=0 ){ - if( pPage->flags&PGHDR_NEED_SYNC ){ - needSync = 1; - } - sqlite3PagerUnref(pPage); - } - } - - /* If the PGHDR_NEED_SYNC flag is set for any of the nPage pages - ** starting at pg1, then it needs to be set for all of them. Because - ** writing to any of these nPage pages may damage the others, the - ** journal file must contain sync()ed copies of all of them - ** before any of them can be written out to the database file. - */ - if( rc==SQLITE_OK && needSync ){ - assert( !MEMDB ); - for(ii=0; ii<nPage; ii++){ - PgHdr *pPage = pager_lookup(pPager, pg1+ii); - if( pPage ){ - pPage->flags |= PGHDR_NEED_SYNC; - sqlite3PagerUnref(pPage); - } - } - } - - assert( (pPager->doNotSpill & SPILLFLAG_NOSYNC)!=0 ); - pPager->doNotSpill &= ~SPILLFLAG_NOSYNC; - }else{ - rc = pager_write(pDbPage); - } - return rc; + assert( pPg->pPager->eState>=PAGER_WRITER_LOCKED ); + assert( pPg->pPager->eState!=PAGER_ERROR ); + assert( assert_pager_state(pPg->pPager) ); + if( pPg->pPager->sectorSize > (u32)pPg->pPager->pageSize ){ + return pagerWriteLargeSector(pPg); + }else{ + return pager_write(pPg); + } } /* ** Return TRUE if the page given in the argument was previously passed ** to sqlite3PagerWrite(). In other words, return TRUE if it is ok @@ -44600,21 +47564,21 @@ ** or pages with the Pager.noSync flag set. ** ** If successful, or if called on a pager for which it is a no-op, this ** function returns SQLITE_OK. Otherwise, an IO error code is returned. */ -SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager){ +SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zMaster){ int rc = SQLITE_OK; - if( !pPager->noSync ){ + + if( isOpen(pPager->fd) ){ + void *pArg = (void*)zMaster; + rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SYNC, pArg); + if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK; + } + if( rc==SQLITE_OK && !pPager->noSync ){ assert( !MEMDB ); rc = sqlite3OsSync(pPager->fd, pPager->syncFlags); - }else if( isOpen(pPager->fd) ){ - assert( !MEMDB ); - rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SYNC_OMITTED, 0); - if( rc==SQLITE_NOTFOUND ){ - rc = SQLITE_OK; - } } return rc; } /* @@ -44809,11 +47773,11 @@ if( rc!=SQLITE_OK ) goto commit_phase_one_exit; } /* Finally, sync the database file. */ if( !noSync ){ - rc = sqlite3PagerSync(pPager); + rc = sqlite3PagerSync(pPager, zMaster); } IOTRACE(("DBSYNC %p\n", pPager)) } } @@ -44873,10 +47837,11 @@ pPager->eState = PAGER_READER; return SQLITE_OK; } PAGERTRACE(("COMMIT %d\n", PAGERID(pPager))); + pPager->iDataVersion++; rc = pager_end_transaction(pPager, pPager->setMaster, 1); return pager_error(pPager, rc); } /* @@ -44938,11 +47903,13 @@ rc = pager_playback(pPager, 0); } assert( pPager->eState==PAGER_READER || rc!=SQLITE_OK ); assert( rc==SQLITE_OK || rc==SQLITE_FULL || rc==SQLITE_CORRUPT - || rc==SQLITE_NOMEM || (rc&0xFF)==SQLITE_IOERR ); + || rc==SQLITE_NOMEM || (rc&0xFF)==SQLITE_IOERR + || rc==SQLITE_CANTOPEN + ); /* If an error occurs during a ROLLBACK, we can no longer trust the pager ** cache. So call pager_error() on the way out to make any error persistent. */ return pager_error(pPager, rc); @@ -45341,21 +48308,21 @@ ** can be written to. The caller has already promised not to write to it. */ if( (pPg->flags&PGHDR_NEED_SYNC) && !isCommit ){ needSyncPgno = pPg->pgno; assert( pPager->journalMode==PAGER_JOURNALMODE_OFF || - pageInJournal(pPg) || pPg->pgno>pPager->dbOrigSize ); + pageInJournal(pPager, pPg) || pPg->pgno>pPager->dbOrigSize ); assert( pPg->flags&PGHDR_DIRTY ); } /* If the cache contains a page with page-number pgno, remove it ** from its hash chain. Also, if the PGHDR_NEED_SYNC flag was set for ** page pgno before the 'move' operation, it needs to be retained ** for the page moved there. */ pPg->flags &= ~PGHDR_NEED_SYNC; - pPgOld = pager_lookup(pPager, pgno); + pPgOld = sqlite3PagerLookup(pPager, pgno); assert( !pPgOld || pPgOld->nRef==1 ); if( pPgOld ){ pPg->flags |= (pPgOld->flags&PGHDR_NEED_SYNC); if( MEMDB ){ /* Do not discard pages from an in-memory database since we might @@ -45375,11 +48342,11 @@ ** as the original page since it has already been allocated. */ if( MEMDB ){ assert( pPgOld ); sqlite3PcacheMove(pPgOld, origPgno); - sqlite3PagerUnref(pPgOld); + sqlite3PagerUnrefNotNull(pPgOld); } if( needSyncPgno ){ /* If needSyncPgno is non-zero, then the journal file needs to be ** sync()ed before any data is written to database file page needSyncPgno. @@ -45404,16 +48371,28 @@ } return rc; } pPgHdr->flags |= PGHDR_NEED_SYNC; sqlite3PcacheMakeDirty(pPgHdr); - sqlite3PagerUnref(pPgHdr); + sqlite3PagerUnrefNotNull(pPgHdr); } return SQLITE_OK; } #endif + +/* +** The page handle passed as the first argument refers to a dirty page +** with a page number other than iNew. This function changes the page's +** page number to iNew and sets the value of the PgHdr.flags field to +** the value passed as the third parameter. +*/ +SQLITE_PRIVATE void sqlite3PagerRekey(DbPage *pPg, Pgno iNew, u16 flags){ + assert( pPg->pgno!=iNew ); + pPg->flags = flags; + sqlite3PcacheMove(pPg, iNew); +} /* ** Return a pointer to the data for the specified page. */ SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *pPg){ @@ -45627,11 +48606,12 @@ */ SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, int eMode, int *pnLog, int *pnCkpt){ int rc = SQLITE_OK; if( pPager->pWal ){ rc = sqlite3WalCheckpoint(pPager->pWal, eMode, - pPager->xBusyHandler, pPager->pBusyHandlerArg, + (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), + pPager->pBusyHandlerArg, pPager->ckptSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace, pnLog, pnCkpt ); } return rc; @@ -45804,14 +48784,15 @@ ** frames, return the size in bytes of the page images stored within the ** WAL frames. Otherwise, if this is not a WAL database or the WAL file ** is empty, return 0. */ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ - assert( pPager->eState==PAGER_READER ); + assert( pPager->eState>=PAGER_READER ); return sqlite3WalFramesize(pPager->pWal); } #endif + #endif /* SQLITE_OMIT_DISKIO */ /************** End of pager.c ***********************************************/ /************** Begin file wal.c *********************************************/ @@ -46388,11 +49369,11 @@ /* ** The argument to this macro must be of type u32. On a little-endian ** architecture, it returns the u32 value that results from interpreting ** the 4 bytes as a big-endian value. On a big-endian architecture, it -** returns the value that would be produced by intepreting the 4 bytes +** returns the value that would be produced by interpreting the 4 bytes ** of the input value as a little-endian integer. */ #define BYTESWAP32(x) ( \ (((x)&0x000000FF)<<24) + (((x)&0x0000FF00)<<8) \ + (((x)&0x00FF0000)>>8) + (((x)&0xFF000000)>>24) \ @@ -46602,13 +49583,14 @@ if( pWal->exclusiveMode ) return; (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, 1, SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED); WALTRACE(("WAL%p: release SHARED-%s\n", pWal, walLockName(lockIdx))); } -static int walLockExclusive(Wal *pWal, int lockIdx, int n){ +static int walLockExclusive(Wal *pWal, int lockIdx, int n, int fBlock){ int rc; if( pWal->exclusiveMode ) return SQLITE_OK; + if( fBlock ) sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_WAL_BLOCK, 0); rc = sqlite3OsShmLock(pWal->pDbFd, lockIdx, n, SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE); WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal, walLockName(lockIdx), n, rc ? "failed" : "ok")); VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && rc!=SQLITE_BUSY); ) @@ -46802,11 +49784,11 @@ idx = iFrame - iZero; assert( idx <= HASHTABLE_NSLOT/2 + 1 ); /* If this is the first entry to be added to this hash-table, zero the - ** entire hash table and aPgno[] array before proceding. + ** entire hash table and aPgno[] array before proceeding. */ if( idx==1 ){ int nByte = (int)((u8 *)&aHash[HASHTABLE_NSLOT] - (u8 *)&aPgno[1]); memset((void*)&aPgno[1], 0, nByte); } @@ -46890,11 +49872,11 @@ assert( WAL_ALL_BUT_WRITE==WAL_WRITE_LOCK+1 ); assert( WAL_CKPT_LOCK==WAL_ALL_BUT_WRITE ); assert( pWal->writeLock ); iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock; nLock = SQLITE_SHM_NLOCK - iLock; - rc = walLockExclusive(pWal, iLock, nLock); + rc = walLockExclusive(pWal, iLock, nLock, 0); if( rc ){ return rc; } WALTRACE(("WAL%p: recovery begin...\n", pWal)); @@ -47120,11 +50102,11 @@ if( rc!=SQLITE_OK ){ walIndexClose(pRet, 0); sqlite3OsClose(pRet->pWalFd); sqlite3_free(pRet); }else{ - int iDC = sqlite3OsDeviceCharacteristics(pRet->pWalFd); + int iDC = sqlite3OsDeviceCharacteristics(pDbFd); if( iDC & SQLITE_IOCAP_SEQUENTIAL ){ pRet->syncHeader = 0; } if( iDC & SQLITE_IOCAP_POWERSAFE_OVERWRITE ){ pRet->padToSectorBoundary = 0; } *ppWal = pRet; @@ -47318,11 +50300,11 @@ /* ** Free an iterator allocated by walIteratorInit(). */ static void walIteratorFree(WalIterator *p){ - sqlite3ScratchFree(p); + sqlite3_free(p); } /* ** Construct a WalInterator object that can be used to loop over all ** pages in the WAL in ascending order. The caller must hold the checkpoint @@ -47353,21 +50335,21 @@ /* Allocate space for the WalIterator object. */ nSegment = walFramePage(iLast) + 1; nByte = sizeof(WalIterator) + (nSegment-1)*sizeof(struct WalSegment) + iLast*sizeof(ht_slot); - p = (WalIterator *)sqlite3ScratchMalloc(nByte); + p = (WalIterator *)sqlite3_malloc(nByte); if( !p ){ return SQLITE_NOMEM; } memset(p, 0, nByte); p->nSegment = nSegment; /* Allocate temporary space used by the merge-sort routine. This block ** of memory will be freed before this function returns. */ - aTmp = (ht_slot *)sqlite3ScratchMalloc( + aTmp = (ht_slot *)sqlite3_malloc( sizeof(ht_slot) * (iLast>HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast) ); if( !aTmp ){ rc = SQLITE_NOMEM; } @@ -47400,11 +50382,11 @@ p->aSegment[i].nEntry = nEntry; p->aSegment[i].aIndex = aIndex; p->aSegment[i].aPgno = (u32 *)aPgno; } } - sqlite3ScratchFree(aTmp); + sqlite3_free(aTmp); if( rc!=SQLITE_OK ){ walIteratorFree(p); } *pp = p; @@ -47424,11 +50406,11 @@ int lockIdx, /* Offset of first byte to lock */ int n /* Number of bytes to lock */ ){ int rc; do { - rc = walLockExclusive(pWal, lockIdx, n); + rc = walLockExclusive(pWal, lockIdx, n, 0); }while( xBusy && rc==SQLITE_BUSY && xBusy(pBusyArg) ); return rc; } /* @@ -47436,10 +50418,42 @@ ** Return the page-size in bytes used by the database. */ static int walPagesize(Wal *pWal){ return (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16); } + +/* +** The following is guaranteed when this function is called: +** +** a) the WRITER lock is held, +** b) the entire log file has been checkpointed, and +** c) any existing readers are reading exclusively from the database +** file - there are no readers that may attempt to read a frame from +** the log file. +** +** This function updates the shared-memory structures so that the next +** client to write to the database (which may be this one) does so by +** writing frames into the start of the log file. +** +** The value of parameter salt1 is used as the aSalt[1] value in the +** new wal-index header. It should be passed a pseudo-random value (i.e. +** one obtained from sqlite3_randomness()). +*/ +static void walRestartHdr(Wal *pWal, u32 salt1){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + int i; /* Loop counter */ + u32 *aSalt = pWal->hdr.aSalt; /* Big-endian salt values */ + pWal->nCkpt++; + pWal->hdr.mxFrame = 0; + sqlite3Put4byte((u8*)&aSalt[0], 1 + sqlite3Get4byte((u8*)&aSalt[0])); + memcpy(&pWal->hdr.aSalt[1], &salt1, 4); + walIndexWriteHdr(pWal); + pInfo->nBackfill = 0; + pInfo->aReadMark[1] = 0; + for(i=2; i<WAL_NREADER; i++) pInfo->aReadMark[i] = READMARK_NOT_USED; + assert( pInfo->aReadMark[0]==0 ); +} /* ** Copy as much content as we can from the WAL back into the database file ** in response to an sqlite3_wal_checkpoint() request or the equivalent. ** @@ -47460,11 +50474,11 @@ ** WAL content is copied into the database file. This second fsync makes ** it safe to delete the WAL since the new content will persist in the ** database file. ** ** This routine uses and updates the nBackfill field of the wal-index header. -** This is the only routine tha will increase the value of nBackfill. +** This is the only routine that will increase the value of nBackfill. ** (A WAL reset or recovery will revert nBackfill to zero, but not increase ** its value.) ** ** The caller must be holding sufficient locks to ensure that no other ** checkpoint is running (in any other thread or process) at the same @@ -47471,141 +50485,164 @@ ** time. */ static int walCheckpoint( Wal *pWal, /* Wal connection */ int eMode, /* One of PASSIVE, FULL or RESTART */ - int (*xBusyCall)(void*), /* Function to call when busy */ + int (*xBusy)(void*), /* Function to call when busy */ void *pBusyArg, /* Context argument for xBusyHandler */ int sync_flags, /* Flags for OsSync() (or 0) */ u8 *zBuf /* Temporary buffer to use */ ){ - int rc; /* Return code */ + int rc = SQLITE_OK; /* Return code */ int szPage; /* Database page-size */ WalIterator *pIter = 0; /* Wal iterator context */ u32 iDbpage = 0; /* Next database page to write */ u32 iFrame = 0; /* Wal frame containing data for iDbpage */ u32 mxSafeFrame; /* Max frame that can be backfilled */ u32 mxPage; /* Max database page to write */ int i; /* Loop counter */ volatile WalCkptInfo *pInfo; /* The checkpoint status information */ - int (*xBusy)(void*) = 0; /* Function to call when waiting for locks */ szPage = walPagesize(pWal); testcase( szPage<=32768 ); testcase( szPage>=65536 ); pInfo = walCkptInfo(pWal); - if( pInfo->nBackfill>=pWal->hdr.mxFrame ) return SQLITE_OK; - - /* Allocate the iterator */ - rc = walIteratorInit(pWal, &pIter); - if( rc!=SQLITE_OK ){ - return rc; - } - assert( pIter ); - - if( eMode!=SQLITE_CHECKPOINT_PASSIVE ) xBusy = xBusyCall; - - /* Compute in mxSafeFrame the index of the last frame of the WAL that is - ** safe to write into the database. Frames beyond mxSafeFrame might - ** overwrite database pages that are in use by active readers and thus - ** cannot be backfilled from the WAL. - */ - mxSafeFrame = pWal->hdr.mxFrame; - mxPage = pWal->hdr.nPage; - for(i=1; i<WAL_NREADER; i++){ - u32 y = pInfo->aReadMark[i]; - if( mxSafeFrame>y ){ - assert( y<=pWal->hdr.mxFrame ); - rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1); - if( rc==SQLITE_OK ){ - pInfo->aReadMark[i] = (i==1 ? mxSafeFrame : READMARK_NOT_USED); - walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); - }else if( rc==SQLITE_BUSY ){ - mxSafeFrame = y; - xBusy = 0; - }else{ - goto walcheckpoint_out; - } - } - } - - if( pInfo->nBackfill<mxSafeFrame - && (rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(0), 1))==SQLITE_OK - ){ - i64 nSize; /* Current size of database file */ - u32 nBackfill = pInfo->nBackfill; - - /* Sync the WAL to disk */ - if( sync_flags ){ - rc = sqlite3OsSync(pWal->pWalFd, sync_flags); - } - - /* If the database may grow as a result of this checkpoint, hint - ** about the eventual size of the db file to the VFS layer. - */ - if( rc==SQLITE_OK ){ - i64 nReq = ((i64)mxPage * szPage); - rc = sqlite3OsFileSize(pWal->pDbFd, &nSize); - if( rc==SQLITE_OK && nSize<nReq ){ - sqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq); - } - } - - - /* Iterate through the contents of the WAL, copying data to the db file. */ - while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ - i64 iOffset; - assert( walFramePgno(pWal, iFrame)==iDbpage ); - if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ) continue; - iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE; - /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */ - rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset); - if( rc!=SQLITE_OK ) break; - iOffset = (iDbpage-1)*(i64)szPage; - testcase( IS_BIG_INT(iOffset) ); - rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset); - if( rc!=SQLITE_OK ) break; - } - - /* If work was actually accomplished... */ - if( rc==SQLITE_OK ){ - if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){ - i64 szDb = pWal->hdr.nPage*(i64)szPage; - testcase( IS_BIG_INT(szDb) ); - rc = sqlite3OsTruncate(pWal->pDbFd, szDb); - if( rc==SQLITE_OK && sync_flags ){ - rc = sqlite3OsSync(pWal->pDbFd, sync_flags); - } - } - if( rc==SQLITE_OK ){ - pInfo->nBackfill = mxSafeFrame; - } - } - - /* Release the reader lock held while backfilling */ - walUnlockExclusive(pWal, WAL_READ_LOCK(0), 1); - } - - if( rc==SQLITE_BUSY ){ - /* Reset the return code so as not to report a checkpoint failure - ** just because there are active readers. */ - rc = SQLITE_OK; - } - - /* If this is an SQLITE_CHECKPOINT_RESTART operation, and the entire wal - ** file has been copied into the database file, then block until all - ** readers have finished using the wal file. This ensures that the next - ** process to write to the database restarts the wal file. + if( pInfo->nBackfill<pWal->hdr.mxFrame ){ + + /* Allocate the iterator */ + rc = walIteratorInit(pWal, &pIter); + if( rc!=SQLITE_OK ){ + return rc; + } + assert( pIter ); + + /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked + ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ + assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); + + /* Compute in mxSafeFrame the index of the last frame of the WAL that is + ** safe to write into the database. Frames beyond mxSafeFrame might + ** overwrite database pages that are in use by active readers and thus + ** cannot be backfilled from the WAL. + */ + mxSafeFrame = pWal->hdr.mxFrame; + mxPage = pWal->hdr.nPage; + for(i=1; i<WAL_NREADER; i++){ + u32 y = pInfo->aReadMark[i]; + if( mxSafeFrame>y ){ + assert( y<=pWal->hdr.mxFrame ); + rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1); + if( rc==SQLITE_OK ){ + pInfo->aReadMark[i] = (i==1 ? mxSafeFrame : READMARK_NOT_USED); + walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); + }else if( rc==SQLITE_BUSY ){ + mxSafeFrame = y; + xBusy = 0; + }else{ + goto walcheckpoint_out; + } + } + } + + if( pInfo->nBackfill<mxSafeFrame + && (rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(0),1))==SQLITE_OK + ){ + i64 nSize; /* Current size of database file */ + u32 nBackfill = pInfo->nBackfill; + + /* Sync the WAL to disk */ + if( sync_flags ){ + rc = sqlite3OsSync(pWal->pWalFd, sync_flags); + } + + /* If the database may grow as a result of this checkpoint, hint + ** about the eventual size of the db file to the VFS layer. + */ + if( rc==SQLITE_OK ){ + i64 nReq = ((i64)mxPage * szPage); + rc = sqlite3OsFileSize(pWal->pDbFd, &nSize); + if( rc==SQLITE_OK && nSize<nReq ){ + sqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq); + } + } + + + /* Iterate through the contents of the WAL, copying data to the db file */ + while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ + i64 iOffset; + assert( walFramePgno(pWal, iFrame)==iDbpage ); + if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){ + continue; + } + iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE; + /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */ + rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset); + if( rc!=SQLITE_OK ) break; + iOffset = (iDbpage-1)*(i64)szPage; + testcase( IS_BIG_INT(iOffset) ); + rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset); + if( rc!=SQLITE_OK ) break; + } + + /* If work was actually accomplished... */ + if( rc==SQLITE_OK ){ + if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){ + i64 szDb = pWal->hdr.nPage*(i64)szPage; + testcase( IS_BIG_INT(szDb) ); + rc = sqlite3OsTruncate(pWal->pDbFd, szDb); + if( rc==SQLITE_OK && sync_flags ){ + rc = sqlite3OsSync(pWal->pDbFd, sync_flags); + } + } + if( rc==SQLITE_OK ){ + pInfo->nBackfill = mxSafeFrame; + } + } + + /* Release the reader lock held while backfilling */ + walUnlockExclusive(pWal, WAL_READ_LOCK(0), 1); + } + + if( rc==SQLITE_BUSY ){ + /* Reset the return code so as not to report a checkpoint failure + ** just because there are active readers. */ + rc = SQLITE_OK; + } + } + + /* If this is an SQLITE_CHECKPOINT_RESTART or TRUNCATE operation, and the + ** entire wal file has been copied into the database file, then block + ** until all readers have finished using the wal file. This ensures that + ** the next process to write to the database restarts the wal file. */ if( rc==SQLITE_OK && eMode!=SQLITE_CHECKPOINT_PASSIVE ){ assert( pWal->writeLock ); if( pInfo->nBackfill<pWal->hdr.mxFrame ){ rc = SQLITE_BUSY; - }else if( eMode==SQLITE_CHECKPOINT_RESTART ){ - assert( mxSafeFrame==pWal->hdr.mxFrame ); + }else if( eMode>=SQLITE_CHECKPOINT_RESTART ){ + u32 salt1; + sqlite3_randomness(4, &salt1); + assert( pInfo->nBackfill==pWal->hdr.mxFrame ); rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1); if( rc==SQLITE_OK ){ + if( eMode==SQLITE_CHECKPOINT_TRUNCATE ){ + /* IMPLEMENTATION-OF: R-44699-57140 This mode works the same way as + ** SQLITE_CHECKPOINT_RESTART with the addition that it also + ** truncates the log file to zero bytes just prior to a + ** successful return. + ** + ** In theory, it might be safe to do this without updating the + ** wal-index header in shared memory, as all subsequent reader or + ** writer clients should see that the entire log file has been + ** checkpointed and behave accordingly. This seems unsafe though, + ** as it would leave the system in a state where the contents of + ** the wal-index header do not match the contents of the + ** file-system. To avoid this, update the wal-index header to + ** indicate that the log file contains zero valid frames. */ + walRestartHdr(pWal, salt1); + rc = sqlite3OsTruncate(pWal->pWalFd, 0); + } walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); } } } @@ -47764,11 +50801,11 @@ ** Read the wal-index header from the wal-index and into pWal->hdr. ** If the wal-header appears to be corrupt, try to reconstruct the ** wal-index from the WAL before returning. ** ** Set *pChanged to 1 if the wal-index header value in pWal->hdr is -** changed by this opertion. If pWal->hdr is unchanged, set *pChanged +** changed by this operation. If pWal->hdr is unchanged, set *pChanged ** to 0. ** ** If the wal-index header is successfully read, return SQLITE_OK. ** Otherwise an SQLite error code. */ @@ -47802,11 +50839,11 @@ if( pWal->readOnly & WAL_SHM_RDONLY ){ if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){ walUnlockShared(pWal, WAL_WRITE_LOCK); rc = SQLITE_READONLY_RECOVERY; } - }else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){ + }else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1, 1)) ){ pWal->writeLock = 1; if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){ badHdr = walIndexTryHdr(pWal, pChanged); if( badHdr ){ /* If the wal-index header is still malformed even while holding @@ -47910,20 +50947,20 @@ ** ** After 5 RETRYs, we begin calling sqlite3OsSleep(). The first few ** calls to sqlite3OsSleep() have a delay of 1 microsecond. Really this ** is more of a scheduler yield than an actual delay. But on the 10th ** an subsequent retries, the delays start becoming longer and longer, - ** so that on the 100th (and last) RETRY we delay for 21 milliseconds. - ** The total delay time before giving up is less than 1 second. + ** so that on the 100th (and last) RETRY we delay for 323 milliseconds. + ** The total delay time before giving up is less than 10 seconds. */ if( cnt>5 ){ int nDelay = 1; /* Pause time in microseconds */ if( cnt>100 ){ VVA_ONLY( pWal->lockError = 1; ) return SQLITE_PROTOCOL; } - if( cnt>=10 ) nDelay = (cnt-9)*238; /* Max delay 21ms. Total delay 996ms */ + if( cnt>=10 ) nDelay = (cnt-9)*(cnt-9)*39; sqlite3OsSleep(pWal->pVfs, nDelay); } if( !useWal ){ rc = walIndexReadHdr(pWal, pChanged); @@ -47968,11 +51005,11 @@ if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){ /* It is not safe to allow the reader to continue here if frames ** may have been appended to the log before READ_LOCK(0) was obtained. ** When holding READ_LOCK(0), the reader ignores the entire log file, ** which implies that the database file contains a trustworthy - ** snapshoT. Since holding READ_LOCK(0) prevents a checkpoint from + ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from ** happening, this is usually correct. ** ** However, if frames have been appended to the log (or if the log ** is wrapped and written for that matter) before the READ_LOCK(0) ** is obtained, that is not necessarily true. A checkpointer may @@ -48008,11 +51045,11 @@ { if( (pWal->readOnly & WAL_SHM_RDONLY)==0 && (mxReadMark<pWal->hdr.mxFrame || mxI==0) ){ for(i=1; i<WAL_NREADER; i++){ - rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1); + rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1, 0); if( rc==SQLITE_OK ){ mxReadMark = pInfo->aReadMark[i] = pWal->hdr.mxFrame; mxI = i; walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); break; @@ -48174,11 +51211,11 @@ } nCollide = HASHTABLE_NSLOT; for(iKey=walHash(pgno); aHash[iKey]; iKey=walNextHash(iKey)){ u32 iFrame = aHash[iKey] + iZero; if( iFrame<=iLast && aPgno[aHash[iKey]]==pgno ){ - /* assert( iFrame>iRead ); -- not true if there is corruption */ + assert( iFrame>iRead || CORRUPT_DB ); iRead = iFrame; } if( (nCollide--)==0 ){ return SQLITE_CORRUPT_BKPT; } @@ -48264,11 +51301,11 @@ } /* Only one writer allowed at a time. Get the write lock. Return ** SQLITE_BUSY if unable. */ - rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); + rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1, 0); if( rc ){ return rc; } pWal->writeLock = 1; @@ -48339,11 +51376,10 @@ assert( walFramePgno(pWal, iFrame)!=1 ); rc = xUndo(pUndoCtx, walFramePgno(pWal, iFrame)); } if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal); } - assert( rc==SQLITE_OK ); return rc; } /* ** Argument aWalData must point to an array of WAL_SAVEPOINT_NDATA u32 @@ -48388,11 +51424,10 @@ } return rc; } - /* ** This function is called just before writing a set of frames to the log ** file (see sqlite3WalFrames()). It checks to see if, instead of appending ** to the current log file, it is possible to overwrite the start of the ** existing log file with the new frames (i.e. "reset" the log). If so, @@ -48411,34 +51446,22 @@ volatile WalCkptInfo *pInfo = walCkptInfo(pWal); assert( pInfo->nBackfill==pWal->hdr.mxFrame ); if( pInfo->nBackfill>0 ){ u32 salt1; sqlite3_randomness(4, &salt1); - rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); + rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1, 0); if( rc==SQLITE_OK ){ /* If all readers are using WAL_READ_LOCK(0) (in other words if no ** readers are currently using the WAL), then the transactions ** frames will overwrite the start of the existing log. Update the ** wal-index header to reflect this. ** ** In theory it would be Ok to update the cache of the header only ** at this point. But updating the actual wal-index header is also ** safe and means there is no special case for sqlite3WalUndo() - ** to handle if this transaction is rolled back. - */ - int i; /* Loop counter */ - u32 *aSalt = pWal->hdr.aSalt; /* Big-endian salt values */ - - pWal->nCkpt++; - pWal->hdr.mxFrame = 0; - sqlite3Put4byte((u8*)&aSalt[0], 1 + sqlite3Get4byte((u8*)&aSalt[0])); - aSalt[1] = salt1; - walIndexWriteHdr(pWal); - pInfo->nBackfill = 0; - pInfo->aReadMark[1] = 0; - for(i=2; i<WAL_NREADER; i++) pInfo->aReadMark[i] = READMARK_NOT_USED; - assert( pInfo->aReadMark[0]==0 ); + ** to handle if this transaction is rolled back. */ + walRestartHdr(pWal, salt1); walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); }else if( rc!=SQLITE_BUSY ){ return rc; } } @@ -48491,11 +51514,11 @@ if( rc ) return rc; iOffset += iFirstAmt; iAmt -= iFirstAmt; pContent = (void*)(iFirstAmt + (char*)pContent); assert( p->syncFlags & (SQLITE_SYNC_NORMAL|SQLITE_SYNC_FULL) ); - rc = sqlite3OsSync(p->pFd, p->syncFlags); + rc = sqlite3OsSync(p->pFd, p->syncFlags & SQLITE_SYNC_MASK); if( iAmt==0 || rc ) return rc; } rc = sqlite3OsWrite(p->pFd, pContent, iAmt, iOffset); return rc; } @@ -48636,11 +51659,11 @@ /* If this is the end of a transaction, then we might need to pad ** the transaction and/or sync the WAL file. ** ** Padding and syncing only occur if this set of frames complete a ** transaction and if PRAGMA synchronous=FULL. If synchronous==NORMAL - ** or synchonous==OFF, then no padding or syncing are needed. + ** or synchronous==OFF, then no padding or syncing are needed. ** ** If SQLITE_IOCAP_POWERSAFE_OVERWRITE is defined, then padding is not ** needed and only the sync is done. If padding is needed, then the ** final frame is repeated (with its commit mark) until the next sector ** boundary is crossed. Only the part of the WAL prior to the last @@ -48722,11 +51745,11 @@ ** If parameter xBusy is not NULL, it is a pointer to a busy-handler ** callback. In this case this function runs a blocking checkpoint. */ SQLITE_PRIVATE int sqlite3WalCheckpoint( Wal *pWal, /* Wal connection */ - int eMode, /* PASSIVE, FULL or RESTART */ + int eMode, /* PASSIVE, FULL, RESTART, or TRUNCATE */ int (*xBusy)(void*), /* Function to call when busy */ void *pBusyArg, /* Context argument for xBusyHandler */ int sync_flags, /* Flags to sync db file with (or 0) */ int nBuf, /* Size of temporary buffer */ u8 *zBuf, /* Temporary buffer to use */ @@ -48734,40 +51757,54 @@ int *pnCkpt /* OUT: Number of backfilled frames in WAL */ ){ int rc; /* Return code */ int isChanged = 0; /* True if a new wal-index header is loaded */ int eMode2 = eMode; /* Mode to pass to walCheckpoint() */ + int (*xBusy2)(void*) = xBusy; /* Busy handler for eMode2 */ assert( pWal->ckptLock==0 ); assert( pWal->writeLock==0 ); + /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked + ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ + assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); + if( pWal->readOnly ) return SQLITE_READONLY; WALTRACE(("WAL%p: checkpoint begins\n", pWal)); - rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); + + /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive + ** "checkpoint" lock on the database file. */ + rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1, 0); if( rc ){ - /* Usually this is SQLITE_BUSY meaning that another thread or process - ** is already running a checkpoint, or maybe a recovery. But it might - ** also be SQLITE_IOERR. */ + /* EVIDENCE-OF: R-10421-19736 If any other process is running a + ** checkpoint operation at the same time, the lock cannot be obtained and + ** SQLITE_BUSY is returned. + ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured, + ** it will not be invoked in this case. + */ + testcase( rc==SQLITE_BUSY ); + testcase( xBusy!=0 ); return rc; } pWal->ckptLock = 1; - /* If this is a blocking-checkpoint, then obtain the write-lock as well - ** to prevent any writers from running while the checkpoint is underway. - ** This has to be done before the call to walIndexReadHdr() below. + /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and + ** TRUNCATE modes also obtain the exclusive "writer" lock on the database + ** file. ** - ** If the writer lock cannot be obtained, then a passive checkpoint is - ** run instead. Since the checkpointer is not holding the writer lock, - ** there is no point in blocking waiting for any readers. Assuming no - ** other error occurs, this function will return SQLITE_BUSY to the caller. + ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained + ** immediately, and a busy-handler is configured, it is invoked and the + ** writer lock retried until either the busy-handler returns 0 or the + ** lock is successfully obtained. */ if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_WRITE_LOCK, 1); if( rc==SQLITE_OK ){ pWal->writeLock = 1; }else if( rc==SQLITE_BUSY ){ eMode2 = SQLITE_CHECKPOINT_PASSIVE; + xBusy2 = 0; rc = SQLITE_OK; } } /* Read the wal-index header. */ @@ -48781,11 +51818,11 @@ /* Copy data from the log to the database file. */ if( rc==SQLITE_OK ){ if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ rc = SQLITE_CORRUPT_BKPT; }else{ - rc = walCheckpoint(pWal, eMode2, xBusy, pBusyArg, sync_flags, zBuf); + rc = walCheckpoint(pWal, eMode2, xBusy2, pBusyArg, sync_flags, zBuf); } /* If no error occurred, set the output variables. */ if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame; @@ -48939,11 +51976,11 @@ ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file implements a external (disk-based) database using BTrees. +** This file implements an external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to ** ** Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3: ** "Sorting And Searching", pages 473-480. Addison-Wesley ** Publishing Company, Reading, Massachusetts. @@ -48986,17 +52023,17 @@ ** page contain a special header (the "file header") that describes the file. ** The format of the file header is as follows: ** ** OFFSET SIZE DESCRIPTION ** 0 16 Header string: "SQLite format 3\000" -** 16 2 Page size in bytes. +** 16 2 Page size in bytes. (1 means 65536) ** 18 1 File format write version ** 19 1 File format read version ** 20 1 Bytes of unused space at the end of each page -** 21 1 Max embedded payload fraction -** 22 1 Min embedded payload fraction -** 23 1 Min leaf payload fraction +** 21 1 Max embedded payload fraction (must be 64) +** 22 1 Min embedded payload fraction (must be 32) +** 23 1 Min leaf payload fraction (must be 32) ** 24 4 File change counter ** 28 4 Reserved for future use ** 32 4 First freelist page ** 36 4 Number of freelist pages in the file ** 40 60 15 4-byte meta values passed to higher layers @@ -49006,13 +52043,14 @@ ** 48 4 Size of page cache ** 52 4 Largest root-page (auto/incr_vacuum) ** 56 4 1=UTF-8 2=UTF16le 3=UTF16be ** 60 4 User version ** 64 4 Incremental vacuum mode -** 68 4 unused -** 72 4 unused -** 76 4 unused +** 68 4 Application-ID +** 72 20 unused +** 92 4 The version-valid-for number +** 96 4 SQLITE_VERSION_NUMBER ** ** All of the integer values are big-endian (most significant byte first). ** ** The file change counter is incremented when the database is changed ** This counter allows other processes to know when the file has changed @@ -49064,11 +52102,11 @@ ** 7 1 number of fragmented free bytes ** 8 4 Right child (the Ptr(N) value). Omitted on leaves. ** ** The flags define the format of this btree page. The leaf flag means that ** this page has no children. The zerodata flag means that this page carries -** only keys and no data. The intkey flag means that the key is a integer +** only keys and no data. The intkey flag means that the key is an integer ** which is stored in the key size entry of the cell header rather than in ** the payload area. ** ** The cell pointer array begins on the first byte after the page header. ** The cell pointer array contains zero or more 2-byte numbers which are @@ -49201,13 +52239,14 @@ ** stored in MemPage.pBt->mutex. */ struct MemPage { u8 isInit; /* True if previously initialized. MUST BE FIRST! */ u8 nOverflow; /* Number of overflow cell bodies in aCell[] */ - u8 intKey; /* True if intkey flag is set */ - u8 leaf; /* True if leaf flag is set */ - u8 hasData; /* True if this page stores data */ + u8 intKey; /* True if table b-trees. False for index b-trees */ + u8 intKeyLeaf; /* True if the leaf of an intKey table */ + u8 noPayload; /* True if internal intKey page (thus w/o data) */ + u8 leaf; /* True if a leaf page */ u8 hdrOffset; /* 100 for page 1. 0 otherwise */ u8 childPtrSize; /* 0 if leaf==1. 4 if leaf==0 */ u8 max1bytePayload; /* min(maxLocal,127) */ u16 maxLocal; /* Copy of BtShared.maxLocal or BtShared.maxLeaf */ u16 minLocal; /* Copy of BtShared.minLocal or BtShared.minLeaf */ @@ -49278,10 +52317,11 @@ u8 inTrans; /* TRANS_NONE, TRANS_READ or TRANS_WRITE */ u8 sharable; /* True if we can share pBt with another db */ u8 locked; /* True if db currently has pBt locked */ int wantToLock; /* Number of nested calls to sqlite3BtreeEnter() */ int nBackup; /* Number of backup operations reading this btree */ + u32 iDataVersion; /* Combines with pBt->pPager->iDataVersion */ Btree *pNext; /* List of other sharable Btrees from the same db */ Btree *pPrev; /* Back pointer of the same list */ #ifndef SQLITE_OMIT_SHARED_CACHE BtLock lock; /* Object used to lock page 1 */ #endif @@ -49344,10 +52384,13 @@ u8 incrVacuum; /* True if incr-vacuum is enabled */ u8 bDoTruncate; /* True to truncate db on commit */ #endif u8 inTransaction; /* Transaction state */ u8 max1bytePayload; /* Maximum first byte of cell for a 1-byte payload */ +#ifdef SQLITE_HAS_CODEC + u8 optimalReserve; /* Desired amount of reserved space per page */ +#endif u16 btsFlags; /* Boolean parameters. See BTS_* macros below */ u16 maxLocal; /* Maximum local payload in non-LEAFDATA tables */ u16 minLocal; /* Minimum local payload in non-LEAFDATA tables */ u16 maxLeaf; /* Maximum local payload in a LEAFDATA table */ u16 minLeaf; /* Minimum local payload in a LEAFDATA table */ @@ -49363,11 +52406,11 @@ int nRef; /* Number of references to this structure */ BtShared *pNext; /* Next on a list of sharable BtShared structs */ BtLock *pLock; /* List of locks held on this shared-btree struct */ Btree *pWriter; /* Btree with currently open write transaction */ #endif - u8 *pTmpSpace; /* BtShared.pageSize bytes of space for tmp use */ + u8 *pTmpSpace; /* Temp space sufficient to hold a single cell */ }; /* ** Allowed values for BtShared.btsFlags */ @@ -49384,16 +52427,14 @@ ** about a cell. The parseCellPtr() function fills in this structure ** based on information extract from the raw disk page. */ typedef struct CellInfo CellInfo; struct CellInfo { - i64 nKey; /* The key for INTKEY tables, or number of bytes in key */ - u8 *pCell; /* Pointer to the start of cell content */ - u32 nData; /* Number of bytes of data */ - u32 nPayload; /* Total amount of payload */ - u16 nHeader; /* Size of the cell content header in bytes */ - u16 nLocal; /* Amount of payload held locally */ + i64 nKey; /* The key for INTKEY tables, or nPayload otherwise */ + u8 *pPayload; /* Pointer to the start of payload */ + u32 nPayload; /* Bytes of payload */ + u16 nLocal; /* Amount of payload held locally, not on overflow */ u16 iOverflow; /* Offset to overflow page number. Zero if no overflow */ u16 nSize; /* Size of the cell content on the main b-tree page */ }; /* @@ -49418,38 +52459,46 @@ ** but cursors cannot be shared. Each cursor is associated with a ** particular database connection identified BtCursor.pBtree.db. ** ** Fields in this structure are accessed under the BtShared.mutex ** found at self->pBt->mutex. +** +** skipNext meaning: +** eState==SKIPNEXT && skipNext>0: Next sqlite3BtreeNext() is no-op. +** eState==SKIPNEXT && skipNext<0: Next sqlite3BtreePrevious() is no-op. +** eState==FAULT: Cursor fault with skipNext as error code. */ struct BtCursor { Btree *pBtree; /* The Btree to which this cursor belongs */ BtShared *pBt; /* The BtShared this cursor points to */ BtCursor *pNext, *pPrev; /* Forms a linked list of all cursors */ struct KeyInfo *pKeyInfo; /* Argument passed to comparison function */ -#ifndef SQLITE_OMIT_INCRBLOB Pgno *aOverflow; /* Cache of overflow page locations */ -#endif + CellInfo info; /* A parse of the cell we are pointing at */ + i64 nKey; /* Size of pKey, or last integer key */ + void *pKey; /* Saved key that was cursor last known position */ Pgno pgnoRoot; /* The root page of this tree */ - sqlite3_int64 cachedRowid; /* Next rowid cache. 0 means not valid */ - CellInfo info; /* A parse of the cell we are pointing at */ - i64 nKey; /* Size of pKey, or last integer key */ - void *pKey; /* Saved key that was cursor's last known position */ - int skipNext; /* Prev() is noop if negative. Next() is noop if positive */ - u8 wrFlag; /* True if writable */ - u8 atLast; /* Cursor pointing to the last entry */ - u8 validNKey; /* True if info.nKey is valid */ + int nOvflAlloc; /* Allocated size of aOverflow[] array */ + int skipNext; /* Prev() is noop if negative. Next() is noop if positive. + ** Error code if eState==CURSOR_FAULT */ + u8 curFlags; /* zero or more BTCF_* flags defined below */ u8 eState; /* One of the CURSOR_XXX constants (see below) */ -#ifndef SQLITE_OMIT_INCRBLOB - u8 isIncrblobHandle; /* True if this cursor is an incr. io handle */ -#endif u8 hints; /* As configured by CursorSetHints() */ i16 iPage; /* Index of current page in apPage */ u16 aiIdx[BTCURSOR_MAX_DEPTH]; /* Current index in apPage[i] */ MemPage *apPage[BTCURSOR_MAX_DEPTH]; /* Pages from root to current page */ }; +/* +** Legal values for BtCursor.curFlags +*/ +#define BTCF_WriteFlag 0x01 /* True if a write cursor */ +#define BTCF_ValidNKey 0x02 /* True if info.nKey is valid */ +#define BTCF_ValidOvfl 0x04 /* True if aOverflow is valid */ +#define BTCF_AtLast 0x08 /* Cursor is pointing ot the last entry */ +#define BTCF_Incrblob 0x10 /* True if an incremental I/O handle */ + /* ** Potential values for BtCursor.eState. ** ** CURSOR_INVALID: ** Cursor does not point to a valid entry. This can happen (for example) @@ -49470,15 +52519,15 @@ ** in variables BtCursor.pKey and BtCursor.nKey. When a cursor is in ** this state, restoreCursorPosition() can be called to attempt to ** seek the cursor to the saved position. ** ** CURSOR_FAULT: -** A unrecoverable error (an I/O error or a malloc failure) has occurred +** An unrecoverable error (an I/O error or a malloc failure) has occurred ** on a different connection that shares the BtShared cache with this ** cursor. The error has left the cache in an inconsistent state. ** Do nothing else with this cursor. Any attempt to use the cursor -** should return the error code stored in BtCursor.skip +** should return the error code stored in BtCursor.skipNext */ #define CURSOR_INVALID 0 #define CURSOR_VALID 1 #define CURSOR_SKIPNEXT 2 #define CURSOR_REQUIRESEEK 3 @@ -49584,10 +52633,12 @@ u8 *aPgRef; /* 1 bit per page in the db (see above) */ Pgno nPage; /* Number of pages in the database */ int mxErr; /* Stop accumulating errors when this reaches zero */ int nErr; /* Number of messages written to zErrMsg so far */ int mallocFailed; /* A memory allocation error has occurred */ + const char *zPfx; /* Error message prefix */ + int v1, v2; /* Values for up to two %d fields in zPfx */ StrAccum errMsg; /* Accumulate the error message text here */ }; /* ** Routines to read or write a two- and four-byte big-endian integer values. @@ -49619,11 +52670,11 @@ /* ** Release the BtShared mutex associated with B-Tree handle p and ** clear the p->locked boolean. */ -static void unlockBtreeMutex(Btree *p){ +static void SQLITE_NOINLINE unlockBtreeMutex(Btree *p){ BtShared *pBt = p->pBt; assert( p->locked==1 ); assert( sqlite3_mutex_held(pBt->mutex) ); assert( sqlite3_mutex_held(p->db->mutex) ); assert( p->db==pBt->db ); @@ -49630,10 +52681,13 @@ sqlite3_mutex_leave(pBt->mutex); p->locked = 0; } +/* Forward reference */ +static void SQLITE_NOINLINE btreeLockCarefully(Btree *p); + /* ** Enter a mutex on the given BTree object. ** ** If the object is not sharable, then no mutex is ever required ** and this routine is a no-op. The underlying mutex is non-recursive. @@ -49647,12 +52701,10 @@ ** p, then first unlock all of the others on p->pNext, then wait ** for the lock to become available on p, then relock all of the ** subsequent Btrees that desire a lock. */ SQLITE_PRIVATE void sqlite3BtreeEnter(Btree *p){ - Btree *pLater; - /* Some basic sanity checking on the Btree. The list of Btrees ** connected by pNext and pPrev should be in sorted order by ** Btree.pBt value. All elements of the list should belong to ** the same connection. Only shared Btrees are on the list. */ assert( p->pNext==0 || p->pNext->pBt>p->pBt ); @@ -49673,13 +52725,24 @@ assert( (p->locked==0 && p->sharable) || p->pBt->db==p->db ); if( !p->sharable ) return; p->wantToLock++; if( p->locked ) return; + btreeLockCarefully(p); +} + +/* This is a helper function for sqlite3BtreeLock(). By moving +** complex, but seldom used logic, out of sqlite3BtreeLock() and +** into this routine, we avoid unnecessary stack pointer changes +** and thus help the sqlite3BtreeLock() routine to run much faster +** in the common case. +*/ +static void SQLITE_NOINLINE btreeLockCarefully(Btree *p){ + Btree *pLater; /* In most cases, we should be able to acquire the lock we - ** want without having to go throught the ascending lock + ** want without having to go through the ascending lock ** procedure that follows. Just be sure not to block. */ if( sqlite3_mutex_try(p->pBt->mutex)==SQLITE_OK ){ p->pBt->db = p->db; p->locked = 1; @@ -49704,15 +52767,17 @@ if( pLater->wantToLock ){ lockBtreeMutex(pLater); } } } + /* ** Exit the recursive mutex on a Btree. */ SQLITE_PRIVATE void sqlite3BtreeLeave(Btree *p){ + assert( sqlite3_mutex_held(p->db->mutex) ); if( p->sharable ){ assert( p->wantToLock>0 ); p->wantToLock--; if( p->wantToLock==0 ){ unlockBtreeMutex(p); @@ -49880,11 +52945,11 @@ ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file implements a external (disk-based) database using BTrees. +** This file implements an external (disk-based) database using BTrees. ** See the header comment on "btreeInt.h" for additional information. ** Including a description of file format and an overview of operation. */ /* @@ -49956,11 +53021,11 @@ ** ** This routine has no effect on existing database connections. ** The shared cache setting effects only future calls to ** sqlite3_open(), sqlite3_open16(), or sqlite3_open_v2(). */ -SQLITE_API int sqlite3_enable_shared_cache(int enable){ +SQLITE_API int SQLITE_STDCALL sqlite3_enable_shared_cache(int enable){ sqlite3GlobalConfig.sharedCacheEnabled = enable; return SQLITE_OK; } #endif @@ -50032,11 +53097,11 @@ /* If the client is reading or writing an index and the schema is ** not loaded, then it is too difficult to actually check to see if ** the correct locks are held. So do not bother - just return true. ** This case does not come up very often anyhow. */ - if( isIndex && (!pSchema || (pSchema->flags&DB_SchemaLoaded)==0) ){ + if( isIndex && (!pSchema || (pSchema->schemaFlags&DB_SchemaLoaded)==0) ){ return 1; } /* Figure out the root-page that the lock should be held on. For table ** b-trees, this is just the root page of the b-tree being read or @@ -50045,10 +53110,16 @@ if( isIndex ){ HashElem *p; for(p=sqliteHashFirst(&pSchema->idxHash); p; p=sqliteHashNext(p)){ Index *pIdx = (Index *)sqliteHashData(p); if( pIdx->tnum==(int)iRoot ){ + if( iTab ){ + /* Two or more indexes share the same root page. There must + ** be imposter tables. So just return true. The assert is not + ** useful in that case. */ + return 1; + } iTab = pIdx->pTable->tnum; } } }else{ iTab = iRoot; @@ -50316,20 +53387,15 @@ static int cursorHoldsMutex(BtCursor *p){ return sqlite3_mutex_held(p->pBt->mutex); } #endif - -#ifndef SQLITE_OMIT_INCRBLOB /* -** Invalidate the overflow page-list cache for cursor pCur, if any. +** Invalidate the overflow cache of the cursor passed as the first argument. +** on the shared btree structure pBt. */ -static void invalidateOverflowCache(BtCursor *pCur){ - assert( cursorHoldsMutex(pCur) ); - sqlite3_free(pCur->aOverflow); - pCur->aOverflow = 0; -} +#define invalidateOverflowCache(pCur) (pCur->curFlags &= ~BTCF_ValidOvfl) /* ** Invalidate the overflow page-list cache for all cursors opened ** on the shared btree structure pBt. */ @@ -50339,10 +53405,11 @@ for(p=pBt->pCursor; p; p=p->pNext){ invalidateOverflowCache(p); } } +#ifndef SQLITE_OMIT_INCRBLOB /* ** This function is called before modifying the contents of a table ** to invalidate any incrblob cursors that are open on the ** row or one of the rows being modified. ** @@ -50361,20 +53428,20 @@ ){ BtCursor *p; BtShared *pBt = pBtree->pBt; assert( sqlite3BtreeHoldsMutex(pBtree) ); for(p=pBt->pCursor; p; p=p->pNext){ - if( p->isIncrblobHandle && (isClearTable || p->info.nKey==iRow) ){ + if( (p->curFlags & BTCF_Incrblob)!=0 + && (isClearTable || p->info.nKey==iRow) + ){ p->eState = CURSOR_INVALID; } } } #else - /* Stub functions when INCRBLOB is omitted */ - #define invalidateOverflowCache(x) - #define invalidateAllOverflowCache(x) + /* Stub function when INCRBLOB is omitted */ #define invalidateIncrblobCursors(x,y,z) #endif /* SQLITE_OMIT_INCRBLOB */ /* ** Set bit pgno of the BtShared.pHasContent bitvec. This is called @@ -50468,14 +53535,19 @@ ** prior to calling this routine. */ static int saveCursorPosition(BtCursor *pCur){ int rc; - assert( CURSOR_VALID==pCur->eState ); + assert( CURSOR_VALID==pCur->eState || CURSOR_SKIPNEXT==pCur->eState ); assert( 0==pCur->pKey ); assert( cursorHoldsMutex(pCur) ); + if( pCur->eState==CURSOR_SKIPNEXT ){ + pCur->eState = CURSOR_VALID; + }else{ + pCur->skipNext = 0; + } rc = sqlite3BtreeKeySize(pCur, &pCur->nKey); assert( rc==SQLITE_OK ); /* KeySize() cannot fail */ /* If this is an intKey table, then the above call to BtreeKeySize() ** stores the integer key in pCur->nKey. In this case this value is @@ -50482,11 +53554,11 @@ ** all that is required. Otherwise, if pCur is not open on an intKey ** table, then malloc space for and store the pCur->nKey bytes of key ** data. */ if( 0==pCur->apPage[0]->intKey ){ - void *pKey = sqlite3Malloc( (int)pCur->nKey ); + void *pKey = sqlite3Malloc( pCur->nKey ); if( pKey ){ rc = sqlite3BtreeKey(pCur, 0, (int)pCur->nKey, pKey); if( rc==SQLITE_OK ){ pCur->pKey = pKey; }else{ @@ -50505,32 +53577,59 @@ invalidateOverflowCache(pCur); return rc; } +/* Forward reference */ +static int SQLITE_NOINLINE saveCursorsOnList(BtCursor*,Pgno,BtCursor*); + /* ** Save the positions of all cursors (except pExcept) that are open on -** the table with root-page iRoot. Usually, this is called just before cursor -** pExcept is used to modify the table (BtreeDelete() or BtreeInsert()). +** the table with root-page iRoot. "Saving the cursor position" means that +** the location in the btree is remembered in such a way that it can be +** moved back to the same spot after the btree has been modified. This +** routine is called just before cursor pExcept is used to modify the +** table, for example in BtreeDelete() or BtreeInsert(). +** +** Implementation note: This routine merely checks to see if any cursors +** need to be saved. It calls out to saveCursorsOnList() in the (unusual) +** event that cursors are in need to being saved. */ static int saveAllCursors(BtShared *pBt, Pgno iRoot, BtCursor *pExcept){ BtCursor *p; assert( sqlite3_mutex_held(pBt->mutex) ); assert( pExcept==0 || pExcept->pBt==pBt ); for(p=pBt->pCursor; p; p=p->pNext){ + if( p!=pExcept && (0==iRoot || p->pgnoRoot==iRoot) ) break; + } + return p ? saveCursorsOnList(p, iRoot, pExcept) : SQLITE_OK; +} + +/* This helper routine to saveAllCursors does the actual work of saving +** the cursors if and when a cursor is found that actually requires saving. +** The common case is that no cursors need to be saved, so this routine is +** broken out from its caller to avoid unnecessary stack pointer movement. +*/ +static int SQLITE_NOINLINE saveCursorsOnList( + BtCursor *p, /* The first cursor that needs saving */ + Pgno iRoot, /* Only save cursor with this iRoot. Save all if zero */ + BtCursor *pExcept /* Do not save this cursor */ +){ + do{ if( p!=pExcept && (0==iRoot || p->pgnoRoot==iRoot) ){ - if( p->eState==CURSOR_VALID ){ + if( p->eState==CURSOR_VALID || p->eState==CURSOR_SKIPNEXT ){ int rc = saveCursorPosition(p); if( SQLITE_OK!=rc ){ return rc; } }else{ testcase( p->iPage>0 ); btreeReleaseAllCursorPages(p); } } - } + p = p->pNext; + }while( p ); return SQLITE_OK; } /* ** Clear the current cursor position. @@ -50554,20 +53653,24 @@ int bias, /* Bias search to the high end */ int *pRes /* Write search results here */ ){ int rc; /* Status code */ UnpackedRecord *pIdxKey; /* Unpacked index key */ - char aSpace[150]; /* Temp space for pIdxKey - to avoid a malloc */ + char aSpace[200]; /* Temp space for pIdxKey - to avoid a malloc */ char *pFree = 0; if( pKey ){ assert( nKey==(i64)(int)nKey ); pIdxKey = sqlite3VdbeAllocUnpackedRecord( pCur->pKeyInfo, aSpace, sizeof(aSpace), &pFree ); if( pIdxKey==0 ) return SQLITE_NOMEM; sqlite3VdbeRecordUnpack(pCur->pKeyInfo, (int)nKey, pKey, pIdxKey); + if( pIdxKey->nField==0 ){ + sqlite3DbFree(pCur->pKeyInfo->db, pFree); + return SQLITE_CORRUPT_BKPT; + } }else{ pIdxKey = 0; } rc = sqlite3BtreeMovetoUnpacked(pCur, pIdxKey, nKey, bias, pRes); if( pFree ){ @@ -50583,21 +53686,23 @@ ** at most one effective restoreCursorPosition() call after each ** saveCursorPosition(). */ static int btreeRestoreCursorPosition(BtCursor *pCur){ int rc; + int skipNext; assert( cursorHoldsMutex(pCur) ); assert( pCur->eState>=CURSOR_REQUIRESEEK ); if( pCur->eState==CURSOR_FAULT ){ return pCur->skipNext; } pCur->eState = CURSOR_INVALID; - rc = btreeMoveto(pCur, pCur->pKey, pCur->nKey, 0, &pCur->skipNext); + rc = btreeMoveto(pCur, pCur->pKey, pCur->nKey, 0, &skipNext); if( rc==SQLITE_OK ){ sqlite3_free(pCur->pKey); pCur->pKey = 0; assert( pCur->eState==CURSOR_VALID || pCur->eState==CURSOR_INVALID ); + pCur->skipNext |= skipNext; if( pCur->skipNext && pCur->eState==CURSOR_VALID ){ pCur->eState = CURSOR_SKIPNEXT; } } return rc; @@ -50607,29 +53712,53 @@ (p->eState>=CURSOR_REQUIRESEEK ? \ btreeRestoreCursorPosition(p) : \ SQLITE_OK) /* -** Determine whether or not a cursor has moved from the position it -** was last placed at. Cursors can move when the row they are pointing -** at is deleted out from under them. +** Determine whether or not a cursor has moved from the position where +** it was last placed, or has been invalidated for any other reason. +** Cursors can move when the row they are pointing at is deleted out +** from under them, for example. Cursor might also move if a btree +** is rebalanced. ** -** This routine returns an error code if something goes wrong. The -** integer *pHasMoved is set to one if the cursor has moved and 0 if not. +** Calling this routine with a NULL cursor pointer returns false. +** +** Use the separate sqlite3BtreeCursorRestore() routine to restore a cursor +** back to where it ought to be if this routine returns true. */ -SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor *pCur, int *pHasMoved){ +SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor *pCur){ + return pCur->eState!=CURSOR_VALID; +} + +/* +** This routine restores a cursor back to its original position after it +** has been moved by some outside activity (such as a btree rebalance or +** a row having been deleted out from under the cursor). +** +** On success, the *pDifferentRow parameter is false if the cursor is left +** pointing at exactly the same row. *pDifferntRow is the row the cursor +** was pointing to has been deleted, forcing the cursor to point to some +** nearby row. +** +** This routine should only be called for a cursor that just returned +** TRUE from sqlite3BtreeCursorHasMoved(). +*/ +SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor *pCur, int *pDifferentRow){ int rc; + assert( pCur!=0 ); + assert( pCur->eState!=CURSOR_VALID ); rc = restoreCursorPosition(pCur); if( rc ){ - *pHasMoved = 1; + *pDifferentRow = 1; return rc; } - if( pCur->eState!=CURSOR_VALID || NEVER(pCur->skipNext!=0) ){ - *pHasMoved = 1; + if( pCur->eState!=CURSOR_VALID ){ + *pDifferentRow = 1; }else{ - *pHasMoved = 0; + assert( pCur->skipNext==0 ); + *pDifferentRow = 0; } return SQLITE_OK; } #ifndef SQLITE_OMIT_AUTOVACUUM @@ -50790,51 +53919,48 @@ /* ** Parse a cell content block and fill in the CellInfo structure. There ** are two versions of this function. btreeParseCell() takes a ** cell index as the second argument and btreeParseCellPtr() ** takes a pointer to the body of the cell as its second argument. -** -** Within this file, the parseCell() macro can be called instead of -** btreeParseCellPtr(). Using some compilers, this will be faster. */ static void btreeParseCellPtr( MemPage *pPage, /* Page containing the cell */ u8 *pCell, /* Pointer to the cell text. */ CellInfo *pInfo /* Fill in this structure */ ){ - u16 n; /* Number bytes in cell content header */ + u8 *pIter; /* For scanning through pCell */ u32 nPayload; /* Number of bytes of cell payload */ assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - - pInfo->pCell = pCell; assert( pPage->leaf==0 || pPage->leaf==1 ); - n = pPage->childPtrSize; - assert( n==4-4*pPage->leaf ); - if( pPage->intKey ){ - if( pPage->hasData ){ - assert( n==0 ); - n = getVarint32(pCell, nPayload); - }else{ - nPayload = 0; - } - n += getVarint(&pCell[n], (u64*)&pInfo->nKey); - pInfo->nData = nPayload; - }else{ - pInfo->nData = 0; - n += getVarint32(&pCell[n], nPayload); + if( pPage->intKeyLeaf ){ + assert( pPage->childPtrSize==0 ); + pIter = pCell + getVarint32(pCell, nPayload); + pIter += getVarint(pIter, (u64*)&pInfo->nKey); + }else if( pPage->noPayload ){ + assert( pPage->childPtrSize==4 ); + pInfo->nSize = 4 + getVarint(&pCell[4], (u64*)&pInfo->nKey); + pInfo->nPayload = 0; + pInfo->nLocal = 0; + pInfo->iOverflow = 0; + pInfo->pPayload = 0; + return; + }else{ + pIter = pCell + pPage->childPtrSize; + pIter += getVarint32(pIter, nPayload); pInfo->nKey = nPayload; } pInfo->nPayload = nPayload; - pInfo->nHeader = n; + pInfo->pPayload = pIter; testcase( nPayload==pPage->maxLocal ); testcase( nPayload==pPage->maxLocal+1 ); - if( likely(nPayload<=pPage->maxLocal) ){ + if( nPayload<=pPage->maxLocal ){ /* This is the (easy) common case where the entire payload fits ** on the local page. No overflow is required. */ - if( (pInfo->nSize = (u16)(n+nPayload))<4 ) pInfo->nSize = 4; + pInfo->nSize = nPayload + (u16)(pIter - pCell); + if( pInfo->nSize<4 ) pInfo->nSize = 4; pInfo->nLocal = (u16)nPayload; pInfo->iOverflow = 0; }else{ /* If the payload will not fit completely on the local page, we have ** to decide how much to store locally and how much to spill onto @@ -50857,33 +53983,32 @@ if( surplus <= maxLocal ){ pInfo->nLocal = (u16)surplus; }else{ pInfo->nLocal = (u16)minLocal; } - pInfo->iOverflow = (u16)(pInfo->nLocal + n); + pInfo->iOverflow = (u16)(&pInfo->pPayload[pInfo->nLocal] - pCell); pInfo->nSize = pInfo->iOverflow + 4; } } -#define parseCell(pPage, iCell, pInfo) \ - btreeParseCellPtr((pPage), findCell((pPage), (iCell)), (pInfo)) static void btreeParseCell( MemPage *pPage, /* Page containing the cell */ int iCell, /* The cell index. First cell is 0 */ CellInfo *pInfo /* Fill in this structure */ ){ - parseCell(pPage, iCell, pInfo); + btreeParseCellPtr(pPage, findCell(pPage, iCell), pInfo); } /* ** Compute the total number of bytes that a Cell needs in the cell ** data area of the btree-page. The return number includes the cell ** data header and the local payload, but not any overflow page or ** the space used by the cell pointer. */ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ - u8 *pIter = &pCell[pPage->childPtrSize]; - u32 nSize; + u8 *pIter = pCell + pPage->childPtrSize; /* For looping over bytes of pCell */ + u8 *pEnd; /* End mark for a varint */ + u32 nSize; /* Size value to return */ #ifdef SQLITE_DEBUG /* The value returned by this function should always be the same as ** the (CellInfo.nSize) value found by doing a full parse of the ** cell. If SQLITE_DEBUG is defined, an assert() at the bottom of @@ -50890,47 +54015,48 @@ ** this function verifies that this invariant is not violated. */ CellInfo debuginfo; btreeParseCellPtr(pPage, pCell, &debuginfo); #endif + if( pPage->noPayload ){ + pEnd = &pIter[9]; + while( (*pIter++)&0x80 && pIter<pEnd ); + assert( pPage->childPtrSize==4 ); + return (u16)(pIter - pCell); + } + nSize = *pIter; + if( nSize>=0x80 ){ + pEnd = &pIter[9]; + nSize &= 0x7f; + do{ + nSize = (nSize<<7) | (*++pIter & 0x7f); + }while( *(pIter)>=0x80 && pIter<pEnd ); + } + pIter++; if( pPage->intKey ){ - u8 *pEnd; - if( pPage->hasData ){ - pIter += getVarint32(pIter, nSize); - }else{ - nSize = 0; - } - /* pIter now points at the 64-bit integer key value, a variable length ** integer. The following block moves pIter to point at the first byte ** past the end of the key value. */ pEnd = &pIter[9]; while( (*pIter++)&0x80 && pIter<pEnd ); - }else{ - pIter += getVarint32(pIter, nSize); } - testcase( nSize==pPage->maxLocal ); testcase( nSize==pPage->maxLocal+1 ); - if( nSize>pPage->maxLocal ){ + if( nSize<=pPage->maxLocal ){ + nSize += (u32)(pIter - pCell); + if( nSize<4 ) nSize = 4; + }else{ int minLocal = pPage->minLocal; nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); testcase( nSize==pPage->maxLocal ); testcase( nSize==pPage->maxLocal+1 ); if( nSize>pPage->maxLocal ){ nSize = minLocal; } - nSize += 4; - } - nSize += (u32)(pIter - pCell); - - /* The minimum size of any cell is 4 bytes. */ - if( nSize<4 ){ - nSize = 4; - } - - assert( nSize==debuginfo.nSize ); + nSize += 4 + (u16)(pIter - pCell); + } + assert( nSize==debuginfo.nSize || CORRUPT_DB ); return (u16)nSize; } #ifdef SQLITE_DEBUG /* This variation on cellSizePtr() is used inside of assert() statements @@ -50949,11 +54075,10 @@ static void ptrmapPutOvflPtr(MemPage *pPage, u8 *pCell, int *pRC){ CellInfo info; if( *pRC ) return; assert( pCell!=0 ); btreeParseCellPtr(pPage, pCell, &info); - assert( (info.nData+(pPage->intKey?0:info.nKey))==info.nPayload ); if( info.iOverflow ){ Pgno ovfl = get4byte(&pCell[info.iOverflow]); ptrmapPut(pPage->pBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno, pRC); } } @@ -50963,40 +54088,44 @@ /* ** Defragment the page given. All Cells are moved to the ** end of the page and all free space is collected into one ** big FreeBlk that occurs in between the header and cell ** pointer array and the cell content area. +** +** EVIDENCE-OF: R-44582-60138 SQLite may from time to time reorganize a +** b-tree page so that there are no freeblocks or fragment bytes, all +** unused bytes are contained in the unallocated space region, and all +** cells are packed tightly at the end of the page. */ static int defragmentPage(MemPage *pPage){ int i; /* Loop counter */ - int pc; /* Address of a i-th cell */ + int pc; /* Address of the i-th cell */ int hdr; /* Offset to the page header */ int size; /* Size of a cell */ int usableSize; /* Number of usable bytes on a page */ int cellOffset; /* Offset to the cell pointer array */ int cbrk; /* Offset to the cell content area */ int nCell; /* Number of cells on the page */ unsigned char *data; /* The page data */ unsigned char *temp; /* Temp area for cell content */ + unsigned char *src; /* Source of content */ int iCellFirst; /* First allowable cell index */ int iCellLast; /* Last possible cell index */ assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( pPage->pBt!=0 ); assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE ); assert( pPage->nOverflow==0 ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - temp = sqlite3PagerTempSpace(pPage->pBt->pPager); - data = pPage->aData; + temp = 0; + src = data = pPage->aData; hdr = pPage->hdrOffset; cellOffset = pPage->cellOffset; nCell = pPage->nCell; assert( nCell==get2byte(&data[hdr+3]) ); usableSize = pPage->pBt->usableSize; - cbrk = get2byte(&data[hdr+5]); - memcpy(&temp[cbrk], &data[cbrk], usableSize - cbrk); cbrk = usableSize; iCellFirst = cellOffset + 2*nCell; iCellLast = usableSize - 4; for(i=0; i<nCell; i++){ u8 *pAddr; /* The i-th cell pointer */ @@ -51011,11 +54140,11 @@ if( pc<iCellFirst || pc>iCellLast ){ return SQLITE_CORRUPT_BKPT; } #endif assert( pc>=iCellFirst && pc<=iCellLast ); - size = cellSizePtr(pPage, &temp[pc]); + size = cellSizePtr(pPage, &src[pc]); cbrk -= size; #if defined(SQLITE_ENABLE_OVERSIZE_CELL_CHECK) if( cbrk<iCellFirst ){ return SQLITE_CORRUPT_BKPT; } @@ -51025,12 +54154,20 @@ } #endif assert( cbrk+size<=usableSize && cbrk>=iCellFirst ); testcase( cbrk+size==usableSize ); testcase( pc+size==usableSize ); - memcpy(&data[cbrk], &temp[pc], size); put2byte(pAddr, cbrk); + if( temp==0 ){ + int x; + if( cbrk==pc ) continue; + temp = sqlite3PagerTempSpace(pPage->pBt->pPager); + x = get2byte(&data[hdr+5]); + memcpy(&temp[x], &data[x], (cbrk+size) - x); + src = temp; + } + memcpy(&data[cbrk], &src[pc], size); } assert( cbrk>=iCellFirst ); put2byte(&data[hdr+5], cbrk); data[hdr+1] = 0; data[hdr+2] = 0; @@ -51040,10 +54177,73 @@ if( cbrk-iCellFirst!=pPage->nFree ){ return SQLITE_CORRUPT_BKPT; } return SQLITE_OK; } + +/* +** Search the free-list on page pPg for space to store a cell nByte bytes in +** size. If one can be found, return a pointer to the space and remove it +** from the free-list. +** +** If no suitable space can be found on the free-list, return NULL. +** +** This function may detect corruption within pPg. If corruption is +** detected then *pRc is set to SQLITE_CORRUPT and NULL is returned. +** +** If a slot of at least nByte bytes is found but cannot be used because +** there are already at least 60 fragmented bytes on the page, return NULL. +** In this case, if pbDefrag parameter is not NULL, set *pbDefrag to true. +*/ +static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc, int *pbDefrag){ + const int hdr = pPg->hdrOffset; + u8 * const aData = pPg->aData; + int iAddr; + int pc; + int usableSize = pPg->pBt->usableSize; + + for(iAddr=hdr+1; (pc = get2byte(&aData[iAddr]))>0; iAddr=pc){ + int size; /* Size of the free slot */ + /* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of + ** increasing offset. */ + if( pc>usableSize-4 || pc<iAddr+4 ){ + *pRc = SQLITE_CORRUPT_BKPT; + return 0; + } + /* EVIDENCE-OF: R-22710-53328 The third and fourth bytes of each + ** freeblock form a big-endian integer which is the size of the freeblock + ** in bytes, including the 4-byte header. */ + size = get2byte(&aData[pc+2]); + if( size>=nByte ){ + int x = size - nByte; + testcase( x==4 ); + testcase( x==3 ); + if( x<4 ){ + /* EVIDENCE-OF: R-11498-58022 In a well-formed b-tree page, the total + ** number of bytes in fragments may not exceed 60. */ + if( aData[hdr+7]>=60 ){ + if( pbDefrag ) *pbDefrag = 1; + return 0; + } + /* Remove the slot from the free-list. Update the number of + ** fragmented bytes within the page. */ + memcpy(&aData[iAddr], &aData[pc], 2); + aData[hdr+7] += (u8)x; + }else if( size+pc > usableSize ){ + *pRc = SQLITE_CORRUPT_BKPT; + return 0; + }else{ + /* The slot remains on the free-list. Reduce its size to account + ** for the portion used by the new allocation. */ + put2byte(&aData[pc+2], x); + } + return &aData[pc + x]; + } + } + + return 0; +} /* ** Allocate nByte bytes of space from within the B-Tree page passed ** as the first argument. Write into *pIdx the index into pPage->aData[] ** of the first byte of allocated space. Return either SQLITE_OK or @@ -51057,78 +54257,59 @@ ** also end up needing a new cell pointer. */ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ const int hdr = pPage->hdrOffset; /* Local cache of pPage->hdrOffset */ u8 * const data = pPage->aData; /* Local cache of pPage->aData */ - int nFrag; /* Number of fragmented bytes on pPage */ int top; /* First byte of cell content area */ + int rc = SQLITE_OK; /* Integer return code */ int gap; /* First byte of gap between cell pointers and cell content */ - int rc; /* Integer return code */ - int usableSize; /* Usable size of the page */ assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( pPage->pBt ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( nByte>=0 ); /* Minimum cell size is 4 */ assert( pPage->nFree>=nByte ); assert( pPage->nOverflow==0 ); - usableSize = pPage->pBt->usableSize; - assert( nByte < usableSize-8 ); + assert( nByte < (int)(pPage->pBt->usableSize-8) ); - nFrag = data[hdr+7]; assert( pPage->cellOffset == hdr + 12 - 4*pPage->leaf ); gap = pPage->cellOffset + 2*pPage->nCell; + assert( gap<=65536 ); + /* EVIDENCE-OF: R-29356-02391 If the database uses a 65536-byte page size + ** and the reserved space is zero (the usual value for reserved space) + ** then the cell content offset of an empty page wants to be 65536. + ** However, that integer is too large to be stored in a 2-byte unsigned + ** integer, so a value of 0 is used in its place. */ top = get2byteNotZero(&data[hdr+5]); if( gap>top ) return SQLITE_CORRUPT_BKPT; + + /* If there is enough space between gap and top for one more cell pointer + ** array entry offset, and if the freelist is not empty, then search the + ** freelist looking for a free slot big enough to satisfy the request. + */ testcase( gap+2==top ); testcase( gap+1==top ); testcase( gap==top ); - - if( nFrag>=60 ){ - /* Always defragment highly fragmented pages */ - rc = defragmentPage(pPage); + if( gap+2<=top && (data[hdr+1] || data[hdr+2]) ){ + int bDefrag = 0; + u8 *pSpace = pageFindSlot(pPage, nByte, &rc, &bDefrag); if( rc ) return rc; - top = get2byteNotZero(&data[hdr+5]); - }else if( gap+2<=top ){ - /* Search the freelist looking for a free slot big enough to satisfy - ** the request. The allocation is made from the first free slot in - ** the list that is large enough to accommodate it. - */ - int pc, addr; - for(addr=hdr+1; (pc = get2byte(&data[addr]))>0; addr=pc){ - int size; /* Size of the free slot */ - if( pc>usableSize-4 || pc<addr+4 ){ - return SQLITE_CORRUPT_BKPT; - } - size = get2byte(&data[pc+2]); - if( size>=nByte ){ - int x = size - nByte; - testcase( x==4 ); - testcase( x==3 ); - if( x<4 ){ - /* Remove the slot from the free-list. Update the number of - ** fragmented bytes within the page. */ - memcpy(&data[addr], &data[pc], 2); - data[hdr+7] = (u8)(nFrag + x); - }else if( size+pc > usableSize ){ - return SQLITE_CORRUPT_BKPT; - }else{ - /* The slot remains on the free-list. Reduce its size to account - ** for the portion used by the new allocation. */ - put2byte(&data[pc+2], x); - } - *pIdx = pc + x; - return SQLITE_OK; - } - } - } - - /* Check to make sure there is enough space in the gap to satisfy - ** the allocation. If not, defragment. + if( bDefrag ) goto defragment_page; + if( pSpace ){ + assert( pSpace>=data && (pSpace - data)<65536 ); + *pIdx = (int)(pSpace - data); + return SQLITE_OK; + } + } + + /* The request could not be fulfilled using a freelist slot. Check + ** to see if defragmentation is necessary. */ testcase( gap+2+nByte==top ); if( gap+2+nByte>top ){ + defragment_page: + assert( pPage->nCell>0 || CORRUPT_DB ); rc = defragmentPage(pPage); if( rc ) return rc; top = get2byteNotZero(&data[hdr+5]); assert( gap+nByte<=top ); } @@ -51147,94 +54328,104 @@ return SQLITE_OK; } /* ** Return a section of the pPage->aData to the freelist. -** The first byte of the new free block is pPage->aDisk[start] -** and the size of the block is "size" bytes. +** The first byte of the new free block is pPage->aData[iStart] +** and the size of the block is iSize bytes. ** -** Most of the effort here is involved in coalesing adjacent -** free blocks into a single big free block. +** Adjacent freeblocks are coalesced. +** +** Note that even though the freeblock list was checked by btreeInitPage(), +** that routine will not detect overlap between cells or freeblocks. Nor +** does it detect cells or freeblocks that encrouch into the reserved bytes +** at the end of the page. So do additional corruption checks inside this +** routine and return SQLITE_CORRUPT if any problems are found. */ -static int freeSpace(MemPage *pPage, int start, int size){ - int addr, pbegin, hdr; - int iLast; /* Largest possible freeblock offset */ - unsigned char *data = pPage->aData; +static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ + u16 iPtr; /* Address of ptr to next freeblock */ + u16 iFreeBlk; /* Address of the next freeblock */ + u8 hdr; /* Page header size. 0 or 100 */ + u8 nFrag = 0; /* Reduction in fragmentation */ + u16 iOrigSize = iSize; /* Original value of iSize */ + u32 iLast = pPage->pBt->usableSize-4; /* Largest possible freeblock offset */ + u32 iEnd = iStart + iSize; /* First byte past the iStart buffer */ + unsigned char *data = pPage->aData; /* Page content */ assert( pPage->pBt!=0 ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); - assert( start>=pPage->hdrOffset+6+pPage->childPtrSize ); - assert( (start + size) <= (int)pPage->pBt->usableSize ); + assert( iStart>=pPage->hdrOffset+6+pPage->childPtrSize ); + assert( CORRUPT_DB || iEnd <= pPage->pBt->usableSize ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - assert( size>=0 ); /* Minimum cell size is 4 */ + assert( iSize>=4 ); /* Minimum cell size is 4 */ + assert( iStart<=iLast ); + /* Overwrite deleted information with zeros when the secure_delete + ** option is enabled */ if( pPage->pBt->btsFlags & BTS_SECURE_DELETE ){ - /* Overwrite deleted information with zeros when the secure_delete - ** option is enabled */ - memset(&data[start], 0, size); + memset(&data[iStart], 0, iSize); } - /* Add the space back into the linked list of freeblocks. Note that - ** even though the freeblock list was checked by btreeInitPage(), - ** btreeInitPage() did not detect overlapping cells or - ** freeblocks that overlapped cells. Nor does it detect when the - ** cell content area exceeds the value in the page header. If these - ** situations arise, then subsequent insert operations might corrupt - ** the freelist. So we do need to check for corruption while scanning - ** the freelist. + /* The list of freeblocks must be in ascending order. Find the + ** spot on the list where iStart should be inserted. */ hdr = pPage->hdrOffset; - addr = hdr + 1; - iLast = pPage->pBt->usableSize - 4; - assert( start<=iLast ); - while( (pbegin = get2byte(&data[addr]))<start && pbegin>0 ){ - if( pbegin<addr+4 ){ - return SQLITE_CORRUPT_BKPT; - } - addr = pbegin; - } - if( pbegin>iLast ){ - return SQLITE_CORRUPT_BKPT; - } - assert( pbegin>addr || pbegin==0 ); - put2byte(&data[addr], start); - put2byte(&data[start], pbegin); - put2byte(&data[start+2], size); - pPage->nFree = pPage->nFree + (u16)size; - - /* Coalesce adjacent free blocks */ - addr = hdr + 1; - while( (pbegin = get2byte(&data[addr]))>0 ){ - int pnext, psize, x; - assert( pbegin>addr ); - assert( pbegin <= (int)pPage->pBt->usableSize-4 ); - pnext = get2byte(&data[pbegin]); - psize = get2byte(&data[pbegin+2]); - if( pbegin + psize + 3 >= pnext && pnext>0 ){ - int frag = pnext - (pbegin+psize); - if( (frag<0) || (frag>(int)data[hdr+7]) ){ - return SQLITE_CORRUPT_BKPT; - } - data[hdr+7] -= (u8)frag; - x = get2byte(&data[pnext]); - put2byte(&data[pbegin], x); - x = pnext + get2byte(&data[pnext+2]) - pbegin; - put2byte(&data[pbegin+2], x); - }else{ - addr = pbegin; - } - } - - /* If the cell content area begins with a freeblock, remove it. */ - if( data[hdr+1]==data[hdr+5] && data[hdr+2]==data[hdr+6] ){ - int top; - pbegin = get2byte(&data[hdr+1]); - memcpy(&data[hdr+1], &data[pbegin], 2); - top = get2byte(&data[hdr+5]) + get2byte(&data[pbegin+2]); - put2byte(&data[hdr+5], top); - } - assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + iPtr = hdr + 1; + if( data[iPtr+1]==0 && data[iPtr]==0 ){ + iFreeBlk = 0; /* Shortcut for the case when the freelist is empty */ + }else{ + while( (iFreeBlk = get2byte(&data[iPtr]))>0 && iFreeBlk<iStart ){ + if( iFreeBlk<iPtr+4 ) return SQLITE_CORRUPT_BKPT; + iPtr = iFreeBlk; + } + if( iFreeBlk>iLast ) return SQLITE_CORRUPT_BKPT; + assert( iFreeBlk>iPtr || iFreeBlk==0 ); + + /* At this point: + ** iFreeBlk: First freeblock after iStart, or zero if none + ** iPtr: The address of a pointer iFreeBlk + ** + ** Check to see if iFreeBlk should be coalesced onto the end of iStart. + */ + if( iFreeBlk && iEnd+3>=iFreeBlk ){ + nFrag = iFreeBlk - iEnd; + if( iEnd>iFreeBlk ) return SQLITE_CORRUPT_BKPT; + iEnd = iFreeBlk + get2byte(&data[iFreeBlk+2]); + iSize = iEnd - iStart; + iFreeBlk = get2byte(&data[iFreeBlk]); + } + + /* If iPtr is another freeblock (that is, if iPtr is not the freelist + ** pointer in the page header) then check to see if iStart should be + ** coalesced onto the end of iPtr. + */ + if( iPtr>hdr+1 ){ + int iPtrEnd = iPtr + get2byte(&data[iPtr+2]); + if( iPtrEnd+3>=iStart ){ + if( iPtrEnd>iStart ) return SQLITE_CORRUPT_BKPT; + nFrag += iStart - iPtrEnd; + iSize = iEnd - iPtr; + iStart = iPtr; + } + } + if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_BKPT; + data[hdr+7] -= nFrag; + } + if( iStart==get2byte(&data[hdr+5]) ){ + /* The new freeblock is at the beginning of the cell content area, + ** so just extend the cell content area rather than create another + ** freelist entry */ + if( iPtr!=hdr+1 ) return SQLITE_CORRUPT_BKPT; + put2byte(&data[hdr+1], iFreeBlk); + put2byte(&data[hdr+5], iEnd); + }else{ + /* Insert the new freeblock into the freelist */ + put2byte(&data[iPtr], iStart); + put2byte(&data[iStart], iFreeBlk); + put2byte(&data[iStart+2], iSize); + } + pPage->nFree += iOrigSize; return SQLITE_OK; } /* ** Decode the flags byte (the first byte of the header) for a page @@ -51256,20 +54447,36 @@ pPage->leaf = (u8)(flagByte>>3); assert( PTF_LEAF == 1<<3 ); flagByte &= ~PTF_LEAF; pPage->childPtrSize = 4-4*pPage->leaf; pBt = pPage->pBt; if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){ + /* EVIDENCE-OF: R-03640-13415 A value of 5 means the page is an interior + ** table b-tree page. */ + assert( (PTF_LEAFDATA|PTF_INTKEY)==5 ); + /* EVIDENCE-OF: R-20501-61796 A value of 13 means the page is a leaf + ** table b-tree page. */ + assert( (PTF_LEAFDATA|PTF_INTKEY|PTF_LEAF)==13 ); pPage->intKey = 1; - pPage->hasData = pPage->leaf; + pPage->intKeyLeaf = pPage->leaf; + pPage->noPayload = !pPage->leaf; pPage->maxLocal = pBt->maxLeaf; pPage->minLocal = pBt->minLeaf; }else if( flagByte==PTF_ZERODATA ){ + /* EVIDENCE-OF: R-27225-53936 A value of 2 means the page is an interior + ** index b-tree page. */ + assert( (PTF_ZERODATA)==2 ); + /* EVIDENCE-OF: R-16571-11615 A value of 10 means the page is a leaf + ** index b-tree page. */ + assert( (PTF_ZERODATA|PTF_LEAF)==10 ); pPage->intKey = 0; - pPage->hasData = 0; + pPage->intKeyLeaf = 0; + pPage->noPayload = 0; pPage->maxLocal = pBt->maxLocal; pPage->minLocal = pBt->minLocal; }else{ + /* EVIDENCE-OF: R-47608-56469 Any other value for the b-tree page type is + ** an error. */ return SQLITE_CORRUPT_BKPT; } pPage->max1bytePayload = pBt->max1bytePayload; return SQLITE_OK; } @@ -51305,25 +54512,37 @@ pBt = pPage->pBt; hdr = pPage->hdrOffset; data = pPage->aData; + /* EVIDENCE-OF: R-28594-02890 The one-byte flag at offset 0 indicating + ** the b-tree page type. */ if( decodeFlags(pPage, data[hdr]) ) return SQLITE_CORRUPT_BKPT; assert( pBt->pageSize>=512 && pBt->pageSize<=65536 ); pPage->maskPage = (u16)(pBt->pageSize - 1); pPage->nOverflow = 0; usableSize = pBt->usableSize; - pPage->cellOffset = cellOffset = hdr + 12 - 4*pPage->leaf; + pPage->cellOffset = cellOffset = hdr + 8 + pPage->childPtrSize; pPage->aDataEnd = &data[usableSize]; pPage->aCellIdx = &data[cellOffset]; + /* EVIDENCE-OF: R-58015-48175 The two-byte integer at offset 5 designates + ** the start of the cell content area. A zero value for this integer is + ** interpreted as 65536. */ top = get2byteNotZero(&data[hdr+5]); + /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the + ** number of cells on the page. */ pPage->nCell = get2byte(&data[hdr+3]); if( pPage->nCell>MX_CELL(pBt) ){ /* To many cells for a single page. The page must be corrupt */ return SQLITE_CORRUPT_BKPT; } testcase( pPage->nCell==MX_CELL(pBt) ); + /* EVIDENCE-OF: R-24089-57979 If a page contains no cells (which is only + ** possible for a root page of a table that contains no rows) then the + ** offset to the cell content area will equal the page size minus the + ** bytes of reserved space. */ + assert( pPage->nCell>0 || top==usableSize || CORRUPT_DB ); /* A malformed database page might cause us to read past the end ** of page when parsing a cell. ** ** The following block of code checks early to see if a cell extends @@ -51353,17 +54572,24 @@ } if( !pPage->leaf ) iCellLast++; } #endif - /* Compute the total free space on the page */ + /* Compute the total free space on the page + ** EVIDENCE-OF: R-23588-34450 The two-byte integer at offset 1 gives the + ** start of the first freeblock on the page, or is zero if there are no + ** freeblocks. */ pc = get2byte(&data[hdr+1]); - nFree = data[hdr+7] + top; + nFree = data[hdr+7] + top; /* Init nFree to non-freeblock free space */ while( pc>0 ){ u16 next, size; if( pc<iCellFirst || pc>iCellLast ){ - /* Start of free block is off the page */ + /* EVIDENCE-OF: R-55530-52930 In a well-formed b-tree page, there will + ** always be at least one cell before the first freeblock. + ** + ** Or, the freeblock is off the end of the page + */ return SQLITE_CORRUPT_BKPT; } next = get2byte(&data[pc]); size = get2byte(&data[pc+2]); if( (next>0 && next<=pc+size+3) || pc+size>usableSize ){ @@ -51408,17 +54634,16 @@ assert( sqlite3_mutex_held(pBt->mutex) ); if( pBt->btsFlags & BTS_SECURE_DELETE ){ memset(&data[hdr], 0, pBt->usableSize - hdr); } data[hdr] = (char)flags; - first = hdr + 8 + 4*((flags&PTF_LEAF)==0 ?1:0); + first = hdr + ((flags&PTF_LEAF)==0 ? 12 : 8); memset(&data[hdr+1], 0, 4); data[hdr+7] = 0; put2byte(&data[hdr+5], pBt->usableSize); pPage->nFree = (u16)(pBt->usableSize - first); decodeFlags(pPage, flags); - pPage->hdrOffset = hdr; pPage->cellOffset = first; pPage->aDataEnd = &data[pBt->usableSize]; pPage->aCellIdx = &data[first]; pPage->nOverflow = 0; assert( pBt->pageSize>=512 && pBt->pageSize<=65536 ); @@ -51493,11 +54718,11 @@ return pBt->nPage; } SQLITE_PRIVATE u32 sqlite3BtreeLastPage(Btree *p){ assert( sqlite3BtreeHoldsMutex(p) ); assert( ((p->pBt->nPage)&0x8000000)==0 ); - return (int)btreePagecount(p->pBt); + return btreePagecount(p->pBt); } /* ** Get a page from the pager and initialize it. This routine is just a ** convenience wrapper around separate calls to btreeGetPage() and @@ -51518,11 +54743,11 @@ if( pgno>btreePagecount(pBt) ){ rc = SQLITE_CORRUPT_BKPT; }else{ rc = btreeGetPage(pBt, pgno, ppPage, bReadonly); - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && (*ppPage)->isInit==0 ){ rc = btreeInitPage(*ppPage); if( rc!=SQLITE_OK ){ releasePage(*ppPage); } } @@ -51539,14 +54764,15 @@ */ static void releasePage(MemPage *pPage){ if( pPage ){ assert( pPage->aData ); assert( pPage->pBt ); + assert( pPage->pDbPage!=0 ); assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage ); assert( sqlite3PagerGetData(pPage->pDbPage)==pPage->aData ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - sqlite3PagerUnref(pPage->pDbPage); + sqlite3PagerUnrefNotNull(pPage->pDbPage); } } /* ** During a rollback, when the pager reloads information into the cache @@ -51734,12 +54960,12 @@ /* ** The following asserts make sure that structures used by the btree are ** the right size. This is to guard against size changes that result ** when compiling on a different architecture. */ - assert( sizeof(i64)==8 || sizeof(i64)==4 ); - assert( sizeof(u64)==8 || sizeof(u64)==4 ); + assert( sizeof(i64)==8 ); + assert( sizeof(u64)==8 ); assert( sizeof(u32)==4 ); assert( sizeof(u16)==2 ); assert( sizeof(Pgno)==4 ); pBt = sqlite3MallocZero( sizeof(*pBt) ); @@ -51765,10 +54991,13 @@ pBt->pPage1 = 0; if( sqlite3PagerIsreadonly(pBt->pPager) ) pBt->btsFlags |= BTS_READ_ONLY; #ifdef SQLITE_SECURE_DELETE pBt->btsFlags |= BTS_SECURE_DELETE; #endif + /* EVIDENCE-OF: R-51873-39618 The page size for a database file is + ** determined by the 2-byte integer located at an offset of 16 bytes from + ** the beginning of the database file. */ pBt->pageSize = (zDbHeader[16]<<8) | (zDbHeader[17]<<16); if( pBt->pageSize<512 || pBt->pageSize>SQLITE_MAX_PAGE_SIZE || ((pBt->pageSize-1)&pBt->pageSize)!=0 ){ pBt->pageSize = 0; #ifndef SQLITE_OMIT_AUTOVACUUM @@ -51783,10 +55012,13 @@ pBt->incrVacuum = (SQLITE_DEFAULT_AUTOVACUUM==2 ? 1 : 0); } #endif nReserve = 0; }else{ + /* EVIDENCE-OF: R-37497-42412 The size of the reserved region is + ** determined by the one-byte unsigned integer found at an offset of 20 + ** into the database file header. */ nReserve = zDbHeader[20]; pBt->btsFlags |= BTS_PAGESIZE_FIXED; #ifndef SQLITE_OMIT_AUTOVACUUM pBt->autoVacuum = (get4byte(&zDbHeader[36 + 4*4])?1:0); pBt->incrVacuum = (get4byte(&zDbHeader[36 + 7*4])?1:0); @@ -51917,24 +55149,48 @@ #endif } /* ** Make sure pBt->pTmpSpace points to an allocation of -** MX_CELL_SIZE(pBt) bytes. +** MX_CELL_SIZE(pBt) bytes with a 4-byte prefix for a left-child +** pointer. */ static void allocateTempSpace(BtShared *pBt){ if( !pBt->pTmpSpace ){ pBt->pTmpSpace = sqlite3PageMalloc( pBt->pageSize ); + + /* One of the uses of pBt->pTmpSpace is to format cells before + ** inserting them into a leaf page (function fillInCell()). If + ** a cell is less than 4 bytes in size, it is rounded up to 4 bytes + ** by the various routines that manipulate binary cells. Which + ** can mean that fillInCell() only initializes the first 2 or 3 + ** bytes of pTmpSpace, but that the first 4 bytes are copied from + ** it into a database page. This is not actually a problem, but it + ** does cause a valgrind error when the 1 or 2 bytes of unitialized + ** data is passed to system call write(). So to avoid this error, + ** zero the first 4 bytes of temp space here. + ** + ** Also: Provide four bytes of initialized space before the + ** beginning of pTmpSpace as an area available to prepend the + ** left-child pointer to the beginning of a cell. + */ + if( pBt->pTmpSpace ){ + memset(pBt->pTmpSpace, 0, 8); + pBt->pTmpSpace += 4; + } } } /* ** Free the pBt->pTmpSpace allocation */ static void freeTempSpace(BtShared *pBt){ - sqlite3PageFree( pBt->pTmpSpace); - pBt->pTmpSpace = 0; + if( pBt->pTmpSpace ){ + pBt->pTmpSpace -= 4; + sqlite3PageFree(pBt->pTmpSpace); + pBt->pTmpSpace = 0; + } } /* ** Close an open database and invalidate all cursors. */ @@ -51956,11 +55212,11 @@ /* Rollback any active transaction and free the handle structure. ** The call to sqlite3BtreeRollback() drops any table-locks held by ** this handle. */ - sqlite3BtreeRollback(p, SQLITE_OK); + sqlite3BtreeRollback(p, SQLITE_OK, 0); sqlite3BtreeLeave(p); /* If there are still other outstanding references to the shared-btree ** structure, return now. The remainder of this procedure cleans ** up the shared-btree. @@ -52015,10 +55271,11 @@ sqlite3PagerSetCachesize(pBt->pPager, mxPage); sqlite3BtreeLeave(p); return SQLITE_OK; } +#if SQLITE_MAX_MMAP_SIZE>0 /* ** Change the limit on the amount of the database file that may be ** memory mapped. */ SQLITE_PRIVATE int sqlite3BtreeSetMmapLimit(Btree *p, sqlite3_int64 szMmap){ @@ -52027,10 +55284,11 @@ sqlite3BtreeEnter(p); sqlite3PagerSetMmapLimit(pBt->pPager, szMmap); sqlite3BtreeLeave(p); return SQLITE_OK; } +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ /* ** Change the way data is synced to disk in order to increase or decrease ** how well the database resists damage due to OS crashes and power ** failures. Level 1 is the same as asynchronous (no syncs() occur and @@ -52090,10 +55348,13 @@ SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve, int iFix){ int rc = SQLITE_OK; BtShared *pBt = p->pBt; assert( nReserve>=-1 && nReserve<=255 ); sqlite3BtreeEnter(p); +#if SQLITE_HAS_CODEC + if( nReserve>pBt->optimalReserve ) pBt->optimalReserve = (u8)nReserve; +#endif if( pBt->btsFlags & BTS_PAGESIZE_FIXED ){ sqlite3BtreeLeave(p); return SQLITE_READONLY; } if( nReserve<0 ){ @@ -52119,11 +55380,10 @@ */ SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree *p){ return p->pBt->pageSize; } -#if defined(SQLITE_HAS_CODEC) || defined(SQLITE_DEBUG) /* ** This function is similar to sqlite3BtreeGetReserve(), except that it ** may only be called if it is guaranteed that the b-tree mutex is already ** held. ** @@ -52132,28 +55392,36 @@ ** database handle that owns *p is not. In this case if sqlite3BtreeEnter() ** were to be called, it might collide with some other operation on the ** database handle that owns *p, causing undefined behavior. */ SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p){ + int n; assert( sqlite3_mutex_held(p->pBt->mutex) ); - return p->pBt->pageSize - p->pBt->usableSize; + n = p->pBt->pageSize - p->pBt->usableSize; + return n; } -#endif /* SQLITE_HAS_CODEC || SQLITE_DEBUG */ -#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) || !defined(SQLITE_OMIT_VACUUM) /* ** Return the number of bytes of space at the end of every page that ** are intentually left unused. This is the "reserved" space that is ** sometimes used by extensions. +** +** If SQLITE_HAS_MUTEX is defined then the number returned is the +** greater of the current reserved space and the maximum requested +** reserve space. */ -SQLITE_PRIVATE int sqlite3BtreeGetReserve(Btree *p){ +SQLITE_PRIVATE int sqlite3BtreeGetOptimalReserve(Btree *p){ int n; sqlite3BtreeEnter(p); - n = p->pBt->pageSize - p->pBt->usableSize; + n = sqlite3BtreeGetReserveNoMutex(p); +#ifdef SQLITE_HAS_CODEC + if( n<p->pBt->optimalReserve ) n = p->pBt->optimalReserve; +#endif sqlite3BtreeLeave(p); return n; } + /* ** Set the maximum page count for a database if mxPage is positive. ** No changes are made if mxPage is 0 or negative. ** Regardless of the value of mxPage, return the maximum page count. @@ -52181,11 +55449,10 @@ } b = (p->pBt->btsFlags & BTS_SECURE_DELETE)!=0; sqlite3BtreeLeave(p); return b; } -#endif /* !defined(SQLITE_OMIT_PAGER_PRAGMAS) || !defined(SQLITE_OMIT_VACUUM) */ /* ** Change the 'auto-vacuum' property of the database. If the 'autoVacuum' ** parameter is non-zero, then auto-vacuum mode is enabled. If zero, it ** is disabled. The default value for the auto-vacuum property is @@ -52266,10 +55533,13 @@ if( nPage>0 ){ u32 pageSize; u32 usableSize; u8 *page1 = pPage1->aData; rc = SQLITE_NOTADB; + /* EVIDENCE-OF: R-43737-39999 Every valid SQLite database file begins + ** with the following 16 bytes (in hex): 53 51 4c 69 74 65 20 66 6f 72 6d + ** 61 74 20 33 00. */ if( memcmp(page1, zMagicHeader, 16)!=0 ){ goto page1_init_failed; } #ifdef SQLITE_OMIT_WAL @@ -52306,26 +55576,39 @@ } rc = SQLITE_NOTADB; } #endif - /* The maximum embedded fraction must be exactly 25%. And the minimum - ** embedded fraction must be 12.5% for both leaf-data and non-leaf-data. + /* EVIDENCE-OF: R-15465-20813 The maximum and minimum embedded payload + ** fractions and the leaf payload fraction values must be 64, 32, and 32. + ** ** The original design allowed these amounts to vary, but as of ** version 3.6.0, we require them to be fixed. */ if( memcmp(&page1[21], "\100\040\040",3)!=0 ){ goto page1_init_failed; } + /* EVIDENCE-OF: R-51873-39618 The page size for a database file is + ** determined by the 2-byte integer located at an offset of 16 bytes from + ** the beginning of the database file. */ pageSize = (page1[16]<<8) | (page1[17]<<16); + /* EVIDENCE-OF: R-25008-21688 The size of a page is a power of two + ** between 512 and 65536 inclusive. */ if( ((pageSize-1)&pageSize)!=0 || pageSize>SQLITE_MAX_PAGE_SIZE || pageSize<=256 ){ goto page1_init_failed; } assert( (pageSize & 7)==0 ); + /* EVIDENCE-OF: R-59310-51205 The "reserved space" size in the 1-byte + ** integer at offset 20 is the number of bytes of space at the end of + ** each page to reserve for extensions. + ** + ** EVIDENCE-OF: R-37497-42412 The size of the reserved region is + ** determined by the one-byte unsigned integer found at an offset of 20 + ** into the database file header. */ usableSize = pageSize - page1[20]; if( (u32)pageSize!=pBt->pageSize ){ /* After reading the first page of the database assuming a page size ** of BtShared.pageSize, we have discovered that the page-size is ** actually pageSize. Unlock the database, leave pBt->pPage1 at @@ -52342,10 +55625,13 @@ } if( (pBt->db->flags & SQLITE_RecoveryMode)==0 && nPage>nPageFile ){ rc = SQLITE_CORRUPT_BKPT; goto page1_init_failed; } + /* EVIDENCE-OF: R-28312-64704 However, the usable size is not allowed to + ** be less than 480. In other words, if the page size is 512, then the + ** reserved space size cannot exceed 32. */ if( usableSize<480 ){ goto page1_init_failed; } pBt->pageSize = pageSize; pBt->usableSize = usableSize; @@ -52378,11 +55664,10 @@ pBt->max1bytePayload = (u8)pBt->maxLocal; } assert( pBt->maxLeaf + 23 <= MX_CELL_SIZE(pBt) ); pBt->pPage1 = pPage1; pBt->nPage = nPage; -assert( pPage1->leaf==0 || pPage1->leaf==1 ); return SQLITE_OK; page1_init_failed: releasePage(pPage1); pBt->pPage1 = 0; @@ -52397,18 +55682,19 @@ ** ** Only write cursors are counted if wrOnly is true. If wrOnly is ** false then all cursors are counted. ** ** For the purposes of this routine, a cursor is any cursor that -** is capable of reading or writing to the databse. Cursors that +** is capable of reading or writing to the database. Cursors that ** have been tripped into the CURSOR_FAULT state are not counted. */ static int countValidCursors(BtShared *pBt, int wrOnly){ BtCursor *pCur; int r = 0; for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ - if( (wrOnly==0 || pCur->wrFlag) && pCur->eState!=CURSOR_FAULT ) r++; + if( (wrOnly==0 || (pCur->curFlags & BTCF_WriteFlag)!=0) + && pCur->eState!=CURSOR_FAULT ) r++; } return r; } #endif @@ -52422,15 +55708,15 @@ */ static void unlockBtreeIfUnused(BtShared *pBt){ assert( sqlite3_mutex_held(pBt->mutex) ); assert( countValidCursors(pBt,0)==0 || pBt->inTransaction>TRANS_NONE ); if( pBt->inTransaction==TRANS_NONE && pBt->pPage1!=0 ){ - assert( pBt->pPage1->aData ); + MemPage *pPage1 = pBt->pPage1; + assert( pPage1->aData ); assert( sqlite3PagerRefcount(pBt->pPager)==1 ); - assert( pBt->pPage1->aData ); - releasePage(pBt->pPage1); pBt->pPage1 = 0; + releasePage(pPage1); } } /* ** If pBt points to an empty file then convert that empty file @@ -52538,11 +55824,11 @@ ** is requested, this is a no-op. */ if( p->inTrans==TRANS_WRITE || (p->inTrans==TRANS_READ && !wrflag) ){ goto trans_begun; } - assert( IfNotOmitAV(pBt->bDoTruncate)==0 ); + assert( pBt->inTransaction==TRANS_WRITE || IfNotOmitAV(pBt->bDoTruncate)==0 ); /* Write transactions are not possible on a read-only database */ if( (pBt->btsFlags & BTS_READ_ONLY)!=0 && wrflag ){ rc = SQLITE_READONLY; goto trans_begun; @@ -52860,19 +56146,19 @@ ** Perform a single step of an incremental-vacuum. If successful, return ** SQLITE_OK. If there is no work to do (and therefore no point in ** calling this function again), return SQLITE_DONE. Or, if an error ** occurs, return some other error code. ** -** More specificly, this function attempts to re-organize the database so +** More specifically, this function attempts to re-organize the database so ** that the last page of the file currently in use is no longer in use. ** ** Parameter nFin is the number of pages that this database would contain ** were this function called until it returns SQLITE_DONE. ** ** If the bCommit parameter is non-zero, this function assumes that the ** caller will keep calling incrVacuumStep() until it returns SQLITE_DONE -** or an error. bCommit is passed true for an auto-vacuum-on-commmit +** or an error. bCommit is passed true for an auto-vacuum-on-commit ** operation, or false for an incremental vacuum. */ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){ Pgno nFreeList; /* Number of pages still on the free-list */ int rc; @@ -53222,10 +56508,11 @@ rc = sqlite3PagerCommitPhaseTwo(pBt->pPager); if( rc!=SQLITE_OK && bCleanup==0 ){ sqlite3BtreeLeave(p); return rc; } + p->iDataVersion--; /* Compensate for pPager->iDataVersion++; */ pBt->inTransaction = TRANS_READ; btreeClearHasContent(pBt); } btreeEndTransaction(p); @@ -53247,64 +56534,95 @@ return rc; } /* ** This routine sets the state to CURSOR_FAULT and the error -** code to errCode for every cursor on BtShared that pBtree -** references. -** -** Every cursor is tripped, including cursors that belong -** to other database connections that happen to be sharing -** the cache with pBtree. -** -** This routine gets called when a rollback occurs. -** All cursors using the same cache must be tripped -** to prevent them from trying to use the btree after -** the rollback. The rollback may have deleted tables -** or moved root pages, so it is not sufficient to -** save the state of the cursor. The cursor must be -** invalidated. -*/ -SQLITE_PRIVATE void sqlite3BtreeTripAllCursors(Btree *pBtree, int errCode){ +** code to errCode for every cursor on any BtShared that pBtree +** references. Or if the writeOnly flag is set to 1, then only +** trip write cursors and leave read cursors unchanged. +** +** Every cursor is a candidate to be tripped, including cursors +** that belong to other database connections that happen to be +** sharing the cache with pBtree. +** +** This routine gets called when a rollback occurs. If the writeOnly +** flag is true, then only write-cursors need be tripped - read-only +** cursors save their current positions so that they may continue +** following the rollback. Or, if writeOnly is false, all cursors are +** tripped. In general, writeOnly is false if the transaction being +** rolled back modified the database schema. In this case b-tree root +** pages may be moved or deleted from the database altogether, making +** it unsafe for read cursors to continue. +** +** If the writeOnly flag is true and an error is encountered while +** saving the current position of a read-only cursor, all cursors, +** including all read-cursors are tripped. +** +** SQLITE_OK is returned if successful, or if an error occurs while +** saving a cursor position, an SQLite error code. +*/ +SQLITE_PRIVATE int sqlite3BtreeTripAllCursors(Btree *pBtree, int errCode, int writeOnly){ BtCursor *p; - if( pBtree==0 ) return; - sqlite3BtreeEnter(pBtree); - for(p=pBtree->pBt->pCursor; p; p=p->pNext){ - int i; - sqlite3BtreeClearCursor(p); - p->eState = CURSOR_FAULT; - p->skipNext = errCode; - for(i=0; i<=p->iPage; i++){ - releasePage(p->apPage[i]); - p->apPage[i] = 0; - } - } - sqlite3BtreeLeave(pBtree); + int rc = SQLITE_OK; + + assert( (writeOnly==0 || writeOnly==1) && BTCF_WriteFlag==1 ); + if( pBtree ){ + sqlite3BtreeEnter(pBtree); + for(p=pBtree->pBt->pCursor; p; p=p->pNext){ + int i; + if( writeOnly && (p->curFlags & BTCF_WriteFlag)==0 ){ + if( p->eState==CURSOR_VALID || p->eState==CURSOR_SKIPNEXT ){ + rc = saveCursorPosition(p); + if( rc!=SQLITE_OK ){ + (void)sqlite3BtreeTripAllCursors(pBtree, rc, 0); + break; + } + } + }else{ + sqlite3BtreeClearCursor(p); + p->eState = CURSOR_FAULT; + p->skipNext = errCode; + } + for(i=0; i<=p->iPage; i++){ + releasePage(p->apPage[i]); + p->apPage[i] = 0; + } + } + sqlite3BtreeLeave(pBtree); + } + return rc; } /* -** Rollback the transaction in progress. All cursors will be -** invalided by this operation. Any attempt to use a cursor -** that was open at the beginning of this operation will result -** in an error. +** Rollback the transaction in progress. +** +** If tripCode is not SQLITE_OK then cursors will be invalidated (tripped). +** Only write cursors are tripped if writeOnly is true but all cursors are +** tripped if writeOnly is false. Any attempt to use +** a tripped cursor will result in an error. ** ** This will release the write lock on the database file. If there ** are no active cursors, it also releases the read lock. */ -SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p, int tripCode){ +SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){ int rc; BtShared *pBt = p->pBt; MemPage *pPage1; + assert( writeOnly==1 || writeOnly==0 ); + assert( tripCode==SQLITE_ABORT_ROLLBACK || tripCode==SQLITE_OK ); sqlite3BtreeEnter(p); if( tripCode==SQLITE_OK ){ rc = tripCode = saveAllCursors(pBt, 0, 0); + if( rc ) writeOnly = 0; }else{ rc = SQLITE_OK; } if( tripCode ){ - sqlite3BtreeTripAllCursors(p, tripCode); + int rc2 = sqlite3BtreeTripAllCursors(p, tripCode, writeOnly); + assert( rc==SQLITE_OK || (writeOnly==0 && rc2==SQLITE_OK) ); + if( rc2!=SQLITE_OK ) rc = rc2; } btreeIntegrity(p); if( p->inTrans==TRANS_WRITE ){ int rc2; @@ -53335,11 +56653,11 @@ sqlite3BtreeLeave(p); return rc; } /* -** Start a statement subtransaction. The subtransaction can can be rolled +** Start a statement subtransaction. The subtransaction can be rolled ** back independently of the main transaction. You must start a transaction ** before starting a subtransaction. The subtransaction is ended automatically ** if the main transaction commits or rolls back. ** ** Statement subtransactions are used around individual SQL statements @@ -53467,10 +56785,14 @@ assert( pBt->pPage1 && pBt->pPage1->aData ); if( NEVER(wrFlag && (pBt->btsFlags & BTS_READ_ONLY)!=0) ){ return SQLITE_READONLY; } + if( wrFlag ){ + allocateTempSpace(pBt); + if( pBt->pTmpSpace==0 ) return SQLITE_NOMEM; + } if( iTable==1 && btreePagecount(pBt)==0 ){ assert( wrFlag==0 ); iTable = 0; } @@ -53479,18 +56801,18 @@ pCur->pgnoRoot = (Pgno)iTable; pCur->iPage = -1; pCur->pKeyInfo = pKeyInfo; pCur->pBtree = p; pCur->pBt = pBt; - pCur->wrFlag = (u8)wrFlag; + assert( wrFlag==0 || wrFlag==BTCF_WriteFlag ); + pCur->curFlags = wrFlag; pCur->pNext = pBt->pCursor; if( pCur->pNext ){ pCur->pNext->pPrev = pCur; } pBt->pCursor = pCur; pCur->eState = CURSOR_INVALID; - pCur->cachedRowid = 0; return SQLITE_OK; } SQLITE_PRIVATE int sqlite3BtreeCursor( Btree *p, /* The btree */ int iTable, /* Root page of table to open */ @@ -53527,40 +56849,10 @@ */ SQLITE_PRIVATE void sqlite3BtreeCursorZero(BtCursor *p){ memset(p, 0, offsetof(BtCursor, iPage)); } -/* -** Set the cached rowid value of every cursor in the same database file -** as pCur and having the same root page number as pCur. The value is -** set to iRowid. -** -** Only positive rowid values are considered valid for this cache. -** The cache is initialized to zero, indicating an invalid cache. -** A btree will work fine with zero or negative rowids. We just cannot -** cache zero or negative rowids, which means tables that use zero or -** negative rowids might run a little slower. But in practice, zero -** or negative rowids are very uncommon so this should not be a problem. -*/ -SQLITE_PRIVATE void sqlite3BtreeSetCachedRowid(BtCursor *pCur, sqlite3_int64 iRowid){ - BtCursor *p; - for(p=pCur->pBt->pCursor; p; p=p->pNext){ - if( p->pgnoRoot==pCur->pgnoRoot ) p->cachedRowid = iRowid; - } - assert( pCur->cachedRowid==iRowid ); -} - -/* -** Return the cached rowid for the given cursor. A negative or zero -** return value indicates that the rowid cache is invalid and should be -** ignored. If the rowid cache has never before been set, then a -** zero is returned. -*/ -SQLITE_PRIVATE sqlite3_int64 sqlite3BtreeGetCachedRowid(BtCursor *pCur){ - return pCur->cachedRowid; -} - /* ** Close a cursor. The read lock on the database file is released ** when the last cursor is closed. */ SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){ @@ -53580,11 +56872,11 @@ } for(i=0; i<=pCur->iPage; i++){ releasePage(pCur->apPage[i]); } unlockBtreeIfUnused(pBt); - invalidateOverflowCache(pCur); + sqlite3_free(pCur->aOverflow); /* sqlite3_free(pCur); */ sqlite3BtreeLeave(pBtree); } return SQLITE_OK; } @@ -53599,20 +56891,20 @@ ** ** 2007-06-25: There is a bug in some versions of MSVC that cause the ** compiler to crash when getCellInfo() is implemented as a macro. ** But there is a measureable speed advantage to using the macro on gcc ** (when less compiler optimizations like -Os or -O0 are used and the -** compiler is not doing agressive inlining.) So we use a real function +** compiler is not doing aggressive inlining.) So we use a real function ** for MSVC and a macro for everything else. Ticket #2457. */ #ifndef NDEBUG static void assertCellInfo(BtCursor *pCur){ CellInfo info; int iPage = pCur->iPage; memset(&info, 0, sizeof(info)); btreeParseCell(pCur->apPage[iPage], pCur->aiIdx[iPage], &info); - assert( memcmp(&info, &pCur->info, sizeof(info))==0 ); + assert( CORRUPT_DB || memcmp(&info, &pCur->info, sizeof(info))==0 ); } #else #define assertCellInfo(x) #endif #ifdef _MSC_VER @@ -53619,22 +56911,22 @@ /* Use a real function in MSVC to work around bugs in that compiler. */ static void getCellInfo(BtCursor *pCur){ if( pCur->info.nSize==0 ){ int iPage = pCur->iPage; btreeParseCell(pCur->apPage[iPage],pCur->aiIdx[iPage],&pCur->info); - pCur->validNKey = 1; + pCur->curFlags |= BTCF_ValidNKey; }else{ assertCellInfo(pCur); } } #else /* if not _MSC_VER */ /* Use a macro in all other compilers so that the function is inlined */ #define getCellInfo(pCur) \ if( pCur->info.nSize==0 ){ \ int iPage = pCur->iPage; \ - btreeParseCell(pCur->apPage[iPage],pCur->aiIdx[iPage],&pCur->info); \ - pCur->validNKey = 1; \ + btreeParseCell(pCur->apPage[iPage],pCur->aiIdx[iPage],&pCur->info); \ + pCur->curFlags |= BTCF_ValidNKey; \ }else{ \ assertCellInfo(pCur); \ } #endif /* _MSC_VER */ @@ -53661,17 +56953,13 @@ ** ** This routine cannot fail. It always returns SQLITE_OK. */ SQLITE_PRIVATE int sqlite3BtreeKeySize(BtCursor *pCur, i64 *pSize){ assert( cursorHoldsMutex(pCur) ); - assert( pCur->eState==CURSOR_INVALID || pCur->eState==CURSOR_VALID ); - if( pCur->eState!=CURSOR_VALID ){ - *pSize = 0; - }else{ - getCellInfo(pCur); - *pSize = pCur->info.nKey; - } + assert( pCur->eState==CURSOR_VALID ); + getCellInfo(pCur); + *pSize = pCur->info.nKey; return SQLITE_OK; } /* ** Set *pSize to the number of bytes of data in the entry the @@ -53686,12 +56974,15 @@ ** to return an integer result code for historical reasons. */ SQLITE_PRIVATE int sqlite3BtreeDataSize(BtCursor *pCur, u32 *pSize){ assert( cursorHoldsMutex(pCur) ); assert( pCur->eState==CURSOR_VALID ); + assert( pCur->iPage>=0 ); + assert( pCur->iPage<BTCURSOR_MAX_DEPTH ); + assert( pCur->apPage[pCur->iPage]->intKeyLeaf==1 ); getCellInfo(pCur); - *pSize = pCur->info.nData; + *pSize = pCur->info.nPayload; return SQLITE_OK; } /* ** Given the page number of an overflow page in the database (parameter @@ -53801,26 +57092,28 @@ return SQLITE_OK; } /* ** This function is used to read or overwrite payload information -** for the entry that the pCur cursor is pointing to. If the eOp -** parameter is 0, this is a read operation (data copied into -** buffer pBuf). If it is non-zero, a write (data copied from -** buffer pBuf). +** for the entry that the pCur cursor is pointing to. The eOp +** argument is interpreted as follows: +** +** 0: The operation is a read. Populate the overflow cache. +** 1: The operation is a write. Populate the overflow cache. +** 2: The operation is a read. Do not populate the overflow cache. ** ** A total of "amt" bytes are read or written beginning at "offset". ** Data is read to or from the buffer pBuf. ** ** The content being read or written might appear on the main page ** or be scattered out on multiple overflow pages. ** -** If the BtCursor.isIncrblobHandle flag is set, and the current -** cursor entry uses one or more overflow pages, this function -** allocates space for and lazily popluates the overflow page-list -** cache array (BtCursor.aOverflow). Subsequent calls use this -** cache to make seeking to the supplied offset more efficient. +** If the current cursor entry uses one or more overflow pages and the +** eOp argument is not 2, this function may allocate space for and lazily +** populates the overflow page-list cache array (BtCursor.aOverflow). +** Subsequent calls use this cache to make seeking to the supplied offset +** more efficient. ** ** Once an overflow page-list cache has been allocated, it may be ** invalidated if some other cursor writes to the same table, or if ** the cursor is moved to a different row. Additionally, in auto-vacuum ** mode, the following events may invalidate an overflow page-list cache. @@ -53836,27 +57129,32 @@ unsigned char *pBuf, /* Write the bytes into this buffer */ int eOp /* zero to read. non-zero to write. */ ){ unsigned char *aPayload; int rc = SQLITE_OK; - u32 nKey; int iIdx = 0; MemPage *pPage = pCur->apPage[pCur->iPage]; /* Btree page of current entry */ BtShared *pBt = pCur->pBt; /* Btree this cursor belongs to */ +#ifdef SQLITE_DIRECT_OVERFLOW_READ + unsigned char * const pBufStart = pBuf; + int bEnd; /* True if reading to end of data */ +#endif assert( pPage ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->aiIdx[pCur->iPage]<pPage->nCell ); assert( cursorHoldsMutex(pCur) ); + assert( eOp!=2 || offset==0 ); /* Always start from beginning for eOp==2 */ getCellInfo(pCur); - aPayload = pCur->info.pCell + pCur->info.nHeader; - nKey = (pPage->intKey ? 0 : (int)pCur->info.nKey); + aPayload = pCur->info.pPayload; +#ifdef SQLITE_DIRECT_OVERFLOW_READ + bEnd = offset+amt==pCur->info.nPayload; +#endif + assert( offset+amt <= pCur->info.nPayload ); - if( NEVER(offset+amt > nKey+pCur->info.nData) - || &aPayload[pCur->info.nLocal] > &pPage->aData[pBt->usableSize] - ){ + if( &aPayload[pCur->info.nLocal] > &pPage->aData[pBt->usableSize] ){ /* Trying to read or write past the end of the data is an error */ return SQLITE_CORRUPT_BKPT; } /* Check if data must be read/written to/from the btree page itself. */ @@ -53863,76 +57161,90 @@ if( offset<pCur->info.nLocal ){ int a = amt; if( a+offset>pCur->info.nLocal ){ a = pCur->info.nLocal - offset; } - rc = copyPayload(&aPayload[offset], pBuf, a, eOp, pPage->pDbPage); + rc = copyPayload(&aPayload[offset], pBuf, a, (eOp & 0x01), pPage->pDbPage); offset = 0; pBuf += a; amt -= a; }else{ offset -= pCur->info.nLocal; } + if( rc==SQLITE_OK && amt>0 ){ const u32 ovflSize = pBt->usableSize - 4; /* Bytes content per ovfl page */ Pgno nextPage; nextPage = get4byte(&aPayload[pCur->info.nLocal]); -#ifndef SQLITE_OMIT_INCRBLOB - /* If the isIncrblobHandle flag is set and the BtCursor.aOverflow[] - ** has not been allocated, allocate it now. The array is sized at - ** one entry for each overflow page in the overflow chain. The - ** page number of the first overflow page is stored in aOverflow[0], - ** etc. A value of 0 in the aOverflow[] array means "not yet known" - ** (the cache is lazily populated). + /* If the BtCursor.aOverflow[] has not been allocated, allocate it now. + ** Except, do not allocate aOverflow[] for eOp==2. + ** + ** The aOverflow[] array is sized at one entry for each overflow page + ** in the overflow chain. The page number of the first overflow page is + ** stored in aOverflow[0], etc. A value of 0 in the aOverflow[] array + ** means "not yet known" (the cache is lazily populated). */ - if( pCur->isIncrblobHandle && !pCur->aOverflow ){ + if( eOp!=2 && (pCur->curFlags & BTCF_ValidOvfl)==0 ){ int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize; - pCur->aOverflow = (Pgno *)sqlite3MallocZero(sizeof(Pgno)*nOvfl); - /* nOvfl is always positive. If it were zero, fetchPayload would have - ** been used instead of this routine. */ - if( ALWAYS(nOvfl) && !pCur->aOverflow ){ - rc = SQLITE_NOMEM; + if( nOvfl>pCur->nOvflAlloc ){ + Pgno *aNew = (Pgno*)sqlite3Realloc( + pCur->aOverflow, nOvfl*2*sizeof(Pgno) + ); + if( aNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + pCur->nOvflAlloc = nOvfl*2; + pCur->aOverflow = aNew; + } + } + if( rc==SQLITE_OK ){ + memset(pCur->aOverflow, 0, nOvfl*sizeof(Pgno)); + pCur->curFlags |= BTCF_ValidOvfl; } } /* If the overflow page-list cache has been allocated and the ** entry for the first required overflow page is valid, skip ** directly to it. */ - if( pCur->aOverflow && pCur->aOverflow[offset/ovflSize] ){ + if( (pCur->curFlags & BTCF_ValidOvfl)!=0 + && pCur->aOverflow[offset/ovflSize] + ){ iIdx = (offset/ovflSize); nextPage = pCur->aOverflow[iIdx]; offset = (offset%ovflSize); } -#endif for( ; rc==SQLITE_OK && amt>0 && nextPage; iIdx++){ -#ifndef SQLITE_OMIT_INCRBLOB /* If required, populate the overflow page-list cache. */ - if( pCur->aOverflow ){ + if( (pCur->curFlags & BTCF_ValidOvfl)!=0 ){ assert(!pCur->aOverflow[iIdx] || pCur->aOverflow[iIdx]==nextPage); pCur->aOverflow[iIdx] = nextPage; } -#endif if( offset>=ovflSize ){ /* The only reason to read this page is to obtain the page ** number for the next page in the overflow chain. The page ** data is not required. So first try to lookup the overflow ** page-list cache, if any, then fall back to the getOverflowPage() ** function. + ** + ** Note that the aOverflow[] array must be allocated because eOp!=2 + ** here. If eOp==2, then offset==0 and this branch is never taken. */ -#ifndef SQLITE_OMIT_INCRBLOB - if( pCur->aOverflow && pCur->aOverflow[iIdx+1] ){ + assert( eOp!=2 ); + assert( pCur->curFlags & BTCF_ValidOvfl ); + assert( pCur->pBtree->db==pBt->db ); + if( pCur->aOverflow[iIdx+1] ){ nextPage = pCur->aOverflow[iIdx+1]; - } else -#endif + }else{ rc = getOverflowPage(pBt, nextPage, 0, &nextPage); + } offset -= ovflSize; }else{ /* Need to read this page properly. It contains some of the ** range of data that is being read (eOp==0) or written (eOp!=0). */ @@ -53950,23 +57262,28 @@ ** 1) this is a read operation, and ** 2) data is required from the start of this overflow page, and ** 3) the database is file-backed, and ** 4) there is no open write-transaction, and ** 5) the database is not a WAL database, + ** 6) all data from the page is being read. + ** 7) at least 4 bytes have already been read into the output buffer ** ** then data can be read directly from the database file into the ** output buffer, bypassing the page-cache altogether. This speeds ** up loading large records that span many overflow pages. */ - if( eOp==0 /* (1) */ + if( (eOp&0x01)==0 /* (1) */ && offset==0 /* (2) */ + && (bEnd || a==ovflSize) /* (6) */ && pBt->inTransaction==TRANS_READ /* (4) */ && (fd = sqlite3PagerFile(pBt->pPager))->pMethods /* (3) */ && pBt->pPage1->aData[19]==0x01 /* (5) */ + && &pBuf[-4]>=pBufStart /* (7) */ ){ u8 aSave[4]; u8 *aWrite = &pBuf[-4]; + assert( aWrite>=pBufStart ); /* hence (7) */ memcpy(aSave, aWrite, 4); rc = sqlite3OsRead(fd, aWrite, a+4, (i64)pBt->pageSize*(nextPage-1)); nextPage = get4byte(aWrite); memcpy(aWrite, aSave, 4); }else @@ -53973,16 +57290,16 @@ #endif { DbPage *pDbPage; rc = sqlite3PagerAcquire(pBt->pPager, nextPage, &pDbPage, - (eOp==0 ? PAGER_GET_READONLY : 0) + ((eOp&0x01)==0 ? PAGER_GET_READONLY : 0) ); if( rc==SQLITE_OK ){ aPayload = sqlite3PagerGetData(pDbPage); nextPage = get4byte(aPayload); - rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage); + rc = copyPayload(&aPayload[offset+4], pBuf, a, (eOp&0x01), pDbPage); sqlite3PagerUnref(pDbPage); offset = 0; } } amt -= a; @@ -53997,11 +57314,11 @@ return rc; } /* ** Read part of the key associated with cursor pCur. Exactly -** "amt" bytes will be transfered into pBuf[]. The transfer +** "amt" bytes will be transferred into pBuf[]. The transfer ** begins at "offset". ** ** The caller must ensure that pCur is pointing to a valid row ** in the table. ** @@ -54047,14 +57364,14 @@ } /* ** Return a pointer to payload information from the entry that the ** pCur cursor is pointing to. The pointer is to the beginning of -** the key if skipKey==0 and it points to the beginning of data if -** skipKey==1. The number of bytes of available key/data is written -** into *pAmt. If *pAmt==0, then the value returned will not be -** a valid pointer. +** the key if index btrees (pPage->intKey==0) and is the data for +** table btrees (pPage->intKey==1). The number of bytes of available +** key/data is written into *pAmt. If *pAmt==0, then the value +** returned will not be a valid pointer. ** ** This routine is an optimization. It is common for the entire key ** and data to fit on the local page and for there to be no overflow ** pages. When that is so, this routine can be used to access the ** key and data without making a copy. If the key and/or data spills @@ -54063,45 +57380,22 @@ ** ** The pointer returned by this routine looks directly into the cached ** page of the database. The data might change or move the next time ** any btree routine is called. */ -static const unsigned char *fetchPayload( +static const void *fetchPayload( BtCursor *pCur, /* Cursor pointing to entry to read from */ - int *pAmt, /* Write the number of available bytes here */ - int skipKey /* read beginning at data if this is true */ + u32 *pAmt /* Write the number of available bytes here */ ){ - unsigned char *aPayload; - MemPage *pPage; - u32 nKey; - u32 nLocal; - assert( pCur!=0 && pCur->iPage>=0 && pCur->apPage[pCur->iPage]); assert( pCur->eState==CURSOR_VALID ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); assert( cursorHoldsMutex(pCur) ); - pPage = pCur->apPage[pCur->iPage]; - assert( pCur->aiIdx[pCur->iPage]<pPage->nCell ); - if( NEVER(pCur->info.nSize==0) ){ - btreeParseCell(pCur->apPage[pCur->iPage], pCur->aiIdx[pCur->iPage], - &pCur->info); - } - aPayload = pCur->info.pCell; - aPayload += pCur->info.nHeader; - if( pPage->intKey ){ - nKey = 0; - }else{ - nKey = (int)pCur->info.nKey; - } - if( skipKey ){ - aPayload += nKey; - nLocal = pCur->info.nLocal - nKey; - }else{ - nLocal = pCur->info.nLocal; - assert( nLocal<=nKey ); - } - *pAmt = nLocal; - return aPayload; + assert( pCur->aiIdx[pCur->iPage]<pCur->apPage[pCur->iPage]->nCell ); + assert( pCur->info.nSize>0 ); + *pAmt = pCur->info.nLocal; + return (void*)pCur->info.pPayload; } /* ** For the entry that cursor pCur is point to, return as @@ -54115,27 +57409,15 @@ ** this routine. ** ** These routines is used to get quick access to key and data ** in the common case where no overflow pages are used. */ -SQLITE_PRIVATE const void *sqlite3BtreeKeyFetch(BtCursor *pCur, int *pAmt){ - const void *p = 0; - assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); - assert( cursorHoldsMutex(pCur) ); - if( ALWAYS(pCur->eState==CURSOR_VALID) ){ - p = (const void*)fetchPayload(pCur, pAmt, 0); - } - return p; -} -SQLITE_PRIVATE const void *sqlite3BtreeDataFetch(BtCursor *pCur, int *pAmt){ - const void *p = 0; - assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); - assert( cursorHoldsMutex(pCur) ); - if( ALWAYS(pCur->eState==CURSOR_VALID) ){ - p = (const void*)fetchPayload(pCur, pAmt, 1); - } - return p; +SQLITE_PRIVATE const void *sqlite3BtreeKeyFetch(BtCursor *pCur, u32 *pAmt){ + return fetchPayload(pCur, pAmt); +} +SQLITE_PRIVATE const void *sqlite3BtreeDataFetch(BtCursor *pCur, u32 *pAmt){ + return fetchPayload(pCur, pAmt); } /* ** Move the cursor down to a new child page. The newPgno argument is the @@ -54158,33 +57440,35 @@ assert( pCur->iPage>=0 ); if( pCur->iPage>=(BTCURSOR_MAX_DEPTH-1) ){ return SQLITE_CORRUPT_BKPT; } rc = getAndInitPage(pBt, newPgno, &pNewPage, - pCur->wrFlag==0 ? PAGER_GET_READONLY : 0); + (pCur->curFlags & BTCF_WriteFlag)==0 ? PAGER_GET_READONLY : 0); if( rc ) return rc; pCur->apPage[i+1] = pNewPage; pCur->aiIdx[i+1] = 0; pCur->iPage++; pCur->info.nSize = 0; - pCur->validNKey = 0; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); if( pNewPage->nCell<1 || pNewPage->intKey!=pCur->apPage[i]->intKey ){ return SQLITE_CORRUPT_BKPT; } return SQLITE_OK; } -#if 0 +#if SQLITE_DEBUG /* ** Page pParent is an internal (non-leaf) tree page. This function ** asserts that page number iChild is the left-child if the iIdx'th ** cell in page pParent. Or, if iIdx is equal to the total number of ** cells in pParent, that page number iChild is the right-child of ** the page. */ static void assertParentIndex(MemPage *pParent, int iIdx, Pgno iChild){ + if( CORRUPT_DB ) return; /* The conditions tested below might not be true + ** in a corrupt database */ assert( iIdx<=pParent->nCell ); if( iIdx==pParent->nCell ){ assert( get4byte(&pParent->aData[pParent->hdrOffset+8])==iChild ); }else{ assert( get4byte(findCell(pParent, iIdx))==iChild ); @@ -54205,29 +57489,21 @@ static void moveToParent(BtCursor *pCur){ assert( cursorHoldsMutex(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPage>0 ); assert( pCur->apPage[pCur->iPage] ); - - /* UPDATE: It is actually possible for the condition tested by the assert - ** below to be untrue if the database file is corrupt. This can occur if - ** one cursor has modified page pParent while a reference to it is held - ** by a second cursor. Which can only happen if a single page is linked - ** into more than one b-tree structure in a corrupt database. */ -#if 0 assertParentIndex( pCur->apPage[pCur->iPage-1], pCur->aiIdx[pCur->iPage-1], pCur->apPage[pCur->iPage]->pgno ); -#endif testcase( pCur->aiIdx[pCur->iPage-1] > pCur->apPage[pCur->iPage-1]->nCell ); releasePage(pCur->apPage[pCur->iPage]); pCur->iPage--; pCur->info.nSize = 0; - pCur->validNKey = 0; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); } /* ** Move the cursor to point to the root page of its b-tree structure. ** @@ -54250,12 +57526,10 @@ ** b-tree). */ static int moveToRoot(BtCursor *pCur){ MemPage *pRoot; int rc = SQLITE_OK; - Btree *p = pCur->pBtree; - BtShared *pBt = p->pBt; assert( cursorHoldsMutex(pCur) ); assert( CURSOR_INVALID < CURSOR_REQUIRESEEK ); assert( CURSOR_VALID < CURSOR_REQUIRESEEK ); assert( CURSOR_FAULT > CURSOR_REQUIRESEEK ); @@ -54266,60 +57540,55 @@ } sqlite3BtreeClearCursor(pCur); } if( pCur->iPage>=0 ){ - int i; - for(i=1; i<=pCur->iPage; i++){ - releasePage(pCur->apPage[i]); - } - pCur->iPage = 0; + while( pCur->iPage ) releasePage(pCur->apPage[pCur->iPage--]); }else if( pCur->pgnoRoot==0 ){ pCur->eState = CURSOR_INVALID; return SQLITE_OK; }else{ - rc = getAndInitPage(pBt, pCur->pgnoRoot, &pCur->apPage[0], - pCur->wrFlag==0 ? PAGER_GET_READONLY : 0); + rc = getAndInitPage(pCur->pBtree->pBt, pCur->pgnoRoot, &pCur->apPage[0], + (pCur->curFlags & BTCF_WriteFlag)==0 ? PAGER_GET_READONLY : 0); if( rc!=SQLITE_OK ){ pCur->eState = CURSOR_INVALID; return rc; } pCur->iPage = 0; - - /* If pCur->pKeyInfo is not NULL, then the caller that opened this cursor - ** expected to open it on an index b-tree. Otherwise, if pKeyInfo is - ** NULL, the caller expects a table b-tree. If this is not the case, - ** return an SQLITE_CORRUPT error. */ - assert( pCur->apPage[0]->intKey==1 || pCur->apPage[0]->intKey==0 ); - if( (pCur->pKeyInfo==0)!=pCur->apPage[0]->intKey ){ - return SQLITE_CORRUPT_BKPT; - } - } - - /* Assert that the root page is of the correct type. This must be the - ** case as the call to this function that loaded the root-page (either - ** this call or a previous invocation) would have detected corruption - ** if the assumption were not true, and it is not possible for the flags - ** byte to have been modified while this cursor is holding a reference - ** to the page. */ + } pRoot = pCur->apPage[0]; assert( pRoot->pgno==pCur->pgnoRoot ); - assert( pRoot->isInit && (pCur->pKeyInfo==0)==pRoot->intKey ); + + /* If pCur->pKeyInfo is not NULL, then the caller that opened this cursor + ** expected to open it on an index b-tree. Otherwise, if pKeyInfo is + ** NULL, the caller expects a table b-tree. If this is not the case, + ** return an SQLITE_CORRUPT error. + ** + ** Earlier versions of SQLite assumed that this test could not fail + ** if the root page was already loaded when this function was called (i.e. + ** if pCur->iPage>=0). But this is not so if the database is corrupted + ** in such a way that page pRoot is linked into a second b-tree table + ** (or the freelist). */ + assert( pRoot->intKey==1 || pRoot->intKey==0 ); + if( pRoot->isInit==0 || (pCur->pKeyInfo==0)!=pRoot->intKey ){ + return SQLITE_CORRUPT_BKPT; + } pCur->aiIdx[0] = 0; pCur->info.nSize = 0; - pCur->atLast = 0; - pCur->validNKey = 0; + pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidNKey|BTCF_ValidOvfl); - if( pRoot->nCell==0 && !pRoot->leaf ){ + if( pRoot->nCell>0 ){ + pCur->eState = CURSOR_VALID; + }else if( !pRoot->leaf ){ Pgno subpage; if( pRoot->pgno!=1 ) return SQLITE_CORRUPT_BKPT; subpage = get4byte(&pRoot->aData[pRoot->hdrOffset+8]); pCur->eState = CURSOR_VALID; rc = moveToChild(pCur, subpage); }else{ - pCur->eState = ((pRoot->nCell>0)?CURSOR_VALID:CURSOR_INVALID); + pCur->eState = CURSOR_INVALID; } return rc; } /* @@ -54359,21 +57628,20 @@ int rc = SQLITE_OK; MemPage *pPage = 0; assert( cursorHoldsMutex(pCur) ); assert( pCur->eState==CURSOR_VALID ); - while( rc==SQLITE_OK && !(pPage = pCur->apPage[pCur->iPage])->leaf ){ + while( !(pPage = pCur->apPage[pCur->iPage])->leaf ){ pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]); pCur->aiIdx[pCur->iPage] = pPage->nCell; rc = moveToChild(pCur, pgno); + if( rc ) return rc; } - if( rc==SQLITE_OK ){ - pCur->aiIdx[pCur->iPage] = pPage->nCell-1; - pCur->info.nSize = 0; - pCur->validNKey = 0; - } - return rc; + pCur->aiIdx[pCur->iPage] = pPage->nCell-1; + assert( pCur->info.nSize==0 ); + assert( (pCur->curFlags & BTCF_ValidNKey)==0 ); + return SQLITE_OK; } /* Move the cursor to the first entry in the table. Return SQLITE_OK ** on success. Set *pRes to 0 if the cursor actually points to something ** or set *pRes to 1 if the table is empty. @@ -54406,11 +57674,11 @@ assert( cursorHoldsMutex(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); /* If the cursor already points to the last entry, this is a no-op. */ - if( CURSOR_VALID==pCur->eState && pCur->atLast ){ + if( CURSOR_VALID==pCur->eState && (pCur->curFlags & BTCF_AtLast)!=0 ){ #ifdef SQLITE_DEBUG /* This block serves to assert() that the cursor really does point ** to the last entry in the b-tree. */ int ii; for(ii=0; ii<pCur->iPage; ii++){ @@ -54429,11 +57697,16 @@ *pRes = 1; }else{ assert( pCur->eState==CURSOR_VALID ); *pRes = 0; rc = moveToRightmost(pCur); - pCur->atLast = rc==SQLITE_OK ?1:0; + if( rc==SQLITE_OK ){ + pCur->curFlags |= BTCF_AtLast; + }else{ + pCur->curFlags &= ~BTCF_AtLast; + } + } } return rc; } @@ -54471,30 +57744,42 @@ i64 intKey, /* The table key */ int biasRight, /* If true, bias the search to the high end */ int *pRes /* Write search results here */ ){ int rc; + RecordCompare xRecordCompare; assert( cursorHoldsMutex(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); assert( pRes ); assert( (pIdxKey==0)==(pCur->pKeyInfo==0) ); /* If the cursor is already positioned at the point we are trying ** to move to, then just return without doing any work */ - if( pCur->eState==CURSOR_VALID && pCur->validNKey + if( pCur->eState==CURSOR_VALID && (pCur->curFlags & BTCF_ValidNKey)!=0 && pCur->apPage[0]->intKey ){ if( pCur->info.nKey==intKey ){ *pRes = 0; return SQLITE_OK; } - if( pCur->atLast && pCur->info.nKey<intKey ){ + if( (pCur->curFlags & BTCF_AtLast)!=0 && pCur->info.nKey<intKey ){ *pRes = -1; return SQLITE_OK; } } + + if( pIdxKey ){ + xRecordCompare = sqlite3VdbeFindCompare(pIdxKey); + pIdxKey->errCode = 0; + assert( pIdxKey->default_rc==1 + || pIdxKey->default_rc==0 + || pIdxKey->default_rc==-1 + ); + }else{ + xRecordCompare = 0; /* All keys are integers */ + } rc = moveToRoot(pCur); if( rc ){ return rc; } @@ -54506,14 +57791,14 @@ assert( pCur->pgnoRoot==0 || pCur->apPage[pCur->iPage]->nCell==0 ); return SQLITE_OK; } assert( pCur->apPage[0]->intKey || pIdxKey ); for(;;){ - int lwr, upr, idx; + int lwr, upr, idx, c; Pgno chldPg; MemPage *pPage = pCur->apPage[pCur->iPage]; - int c; + u8 *pCell; /* Pointer to current cell in pPage */ /* pPage->nCell must be greater than zero. If this is the root-page ** the cursor would have been INVALID above and this for(;;) loop ** not run. If this is not the root-page, then the moveToChild() routine ** would have already detected db corruption. Similarly, pPage must @@ -54521,64 +57806,73 @@ ** a moveToChild() or moveToRoot() call would have detected corruption. */ assert( pPage->nCell>0 ); assert( pPage->intKey==(pIdxKey==0) ); lwr = 0; upr = pPage->nCell-1; - if( biasRight ){ - pCur->aiIdx[pCur->iPage] = (u16)(idx = upr); - }else{ - pCur->aiIdx[pCur->iPage] = (u16)(idx = (upr+lwr)/2); - } - for(;;){ - u8 *pCell; /* Pointer to current cell in pPage */ - - assert( idx==pCur->aiIdx[pCur->iPage] ); - pCur->info.nSize = 0; - pCell = findCell(pPage, idx) + pPage->childPtrSize; - if( pPage->intKey ){ + assert( biasRight==0 || biasRight==1 ); + idx = upr>>(1-biasRight); /* idx = biasRight ? upr : (lwr+upr)/2; */ + pCur->aiIdx[pCur->iPage] = (u16)idx; + if( xRecordCompare==0 ){ + for(;;){ i64 nCellKey; - if( pPage->hasData ){ - u32 dummy; - pCell += getVarint32(pCell, dummy); + pCell = findCell(pPage, idx) + pPage->childPtrSize; + if( pPage->intKeyLeaf ){ + while( 0x80 <= *(pCell++) ){ + if( pCell>=pPage->aDataEnd ) return SQLITE_CORRUPT_BKPT; + } } getVarint(pCell, (u64*)&nCellKey); - if( nCellKey==intKey ){ - c = 0; - }else if( nCellKey<intKey ){ - c = -1; - }else{ - assert( nCellKey>intKey ); - c = +1; - } - pCur->validNKey = 1; - pCur->info.nKey = nCellKey; - }else{ + if( nCellKey<intKey ){ + lwr = idx+1; + if( lwr>upr ){ c = -1; break; } + }else if( nCellKey>intKey ){ + upr = idx-1; + if( lwr>upr ){ c = +1; break; } + }else{ + assert( nCellKey==intKey ); + pCur->curFlags |= BTCF_ValidNKey; + pCur->info.nKey = nCellKey; + pCur->aiIdx[pCur->iPage] = (u16)idx; + if( !pPage->leaf ){ + lwr = idx; + goto moveto_next_layer; + }else{ + *pRes = 0; + rc = SQLITE_OK; + goto moveto_finish; + } + } + assert( lwr+upr>=0 ); + idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2; */ + } + }else{ + for(;;){ + int nCell; + pCell = findCell(pPage, idx) + pPage->childPtrSize; + /* The maximum supported page-size is 65536 bytes. This means that ** the maximum number of record bytes stored on an index B-Tree ** page is less than 16384 bytes and may be stored as a 2-byte ** varint. This information is used to attempt to avoid parsing ** the entire cell by checking for the cases where the record is ** stored entirely within the b-tree page by inspecting the first ** 2 bytes of the cell. */ - int nCell = pCell[0]; - if( nCell<=pPage->max1bytePayload - /* && (pCell+nCell)<pPage->aDataEnd */ - ){ + nCell = pCell[0]; + if( nCell<=pPage->max1bytePayload ){ /* This branch runs if the record-size field of the cell is a ** single byte varint and the record fits entirely on the main ** b-tree page. */ testcase( pCell+nCell+1==pPage->aDataEnd ); - c = sqlite3VdbeRecordCompare(nCell, (void*)&pCell[1], pIdxKey); + c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); }else if( !(pCell[1] & 0x80) && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal - /* && (pCell+nCell+2)<=pPage->aDataEnd */ ){ /* The record-size field is a 2 byte varint and the record ** fits entirely on the main b-tree page. */ testcase( pCell+nCell+2==pPage->aDataEnd ); - c = sqlite3VdbeRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); }else{ /* The record flows over onto one or more overflow pages. In ** this case the whole cell needs to be parsed, a buffer allocated ** and accessPayload() used to retrieve the record into the ** buffer before VdbeRecordCompare() can be called. */ @@ -54589,61 +57883,62 @@ pCellKey = sqlite3Malloc( nCell ); if( pCellKey==0 ){ rc = SQLITE_NOMEM; goto moveto_finish; } - rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 0); + pCur->aiIdx[pCur->iPage] = (u16)idx; + rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 2); if( rc ){ sqlite3_free(pCellKey); goto moveto_finish; } - c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey); + c = xRecordCompare(nCell, pCellKey, pIdxKey); sqlite3_free(pCellKey); } - } - if( c==0 ){ - if( pPage->intKey && !pPage->leaf ){ - lwr = idx; - break; + assert( + (pIdxKey->errCode!=SQLITE_CORRUPT || c==0) + && (pIdxKey->errCode!=SQLITE_NOMEM || pCur->pBtree->db->mallocFailed) + ); + if( c<0 ){ + lwr = idx+1; + }else if( c>0 ){ + upr = idx-1; }else{ + assert( c==0 ); *pRes = 0; rc = SQLITE_OK; + pCur->aiIdx[pCur->iPage] = (u16)idx; + if( pIdxKey->errCode ) rc = SQLITE_CORRUPT; goto moveto_finish; } - } - if( c<0 ){ - lwr = idx+1; - }else{ - upr = idx-1; - } - if( lwr>upr ){ - break; - } - pCur->aiIdx[pCur->iPage] = (u16)(idx = (lwr+upr)/2); + if( lwr>upr ) break; + assert( lwr+upr>=0 ); + idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2 */ + } } assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); assert( pPage->isInit ); if( pPage->leaf ){ - chldPg = 0; - }else if( lwr>=pPage->nCell ){ - chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); - }else{ - chldPg = get4byte(findCell(pPage, lwr)); - } - if( chldPg==0 ){ assert( pCur->aiIdx[pCur->iPage]<pCur->apPage[pCur->iPage]->nCell ); + pCur->aiIdx[pCur->iPage] = (u16)idx; *pRes = c; rc = SQLITE_OK; goto moveto_finish; } +moveto_next_layer: + if( lwr>=pPage->nCell ){ + chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); + }else{ + chldPg = get4byte(findCell(pPage, lwr)); + } pCur->aiIdx[pCur->iPage] = (u16)lwr; - pCur->info.nSize = 0; - pCur->validNKey = 0; rc = moveToChild(pCur, chldPg); - if( rc ) goto moveto_finish; + if( rc ) break; } moveto_finish: + pCur->info.nSize = 0; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); return rc; } /* @@ -54664,23 +57959,38 @@ /* ** Advance the cursor to the next entry in the database. If ** successful then set *pRes=0. If the cursor ** was already pointing to the last entry in the database before ** this routine was called, then set *pRes=1. +** +** The main entry point is sqlite3BtreeNext(). That routine is optimized +** for the common case of merely incrementing the cell counter BtCursor.aiIdx +** to the next cell on the current page. The (slower) btreeNext() helper +** routine is called when it is necessary to move to a different page or +** to restore the cursor. +** +** The calling function will set *pRes to 0 or 1. The initial *pRes value +** will be 1 if the cursor being stepped corresponds to an SQL index and +** if this routine could have been skipped if that SQL index had been +** a unique index. Otherwise the caller will have set *pRes to zero. +** Zero is the common case. The btree implementation is free to use the +** initial *pRes value as a hint to improve performance, but the current +** SQLite btree implementation does not. (Note that the comdb2 btree +** implementation does use this hint, however.) */ -SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int *pRes){ +static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){ int rc; int idx; MemPage *pPage; assert( cursorHoldsMutex(pCur) ); - assert( pRes!=0 ); assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); + assert( *pRes==0 ); if( pCur->eState!=CURSOR_VALID ){ + assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); rc = restoreCursorPosition(pCur); if( rc!=SQLITE_OK ){ - *pRes = 0; return rc; } if( CURSOR_INVALID==pCur->eState ){ *pRes = 1; return SQLITE_OK; @@ -54688,11 +57998,10 @@ if( pCur->skipNext ){ assert( pCur->eState==CURSOR_VALID || pCur->eState==CURSOR_SKIPNEXT ); pCur->eState = CURSOR_VALID; if( pCur->skipNext>0 ){ pCur->skipNext = 0; - *pRes = 0; return SQLITE_OK; } pCur->skipNext = 0; } } @@ -54706,22 +58015,15 @@ ** the page while cursor pCur is holding a reference to it. Which can ** only happen if the database is corrupt in such a way as to link the ** page into more than one b-tree structure. */ testcase( idx>pPage->nCell ); - pCur->info.nSize = 0; - pCur->validNKey = 0; if( idx>=pPage->nCell ){ if( !pPage->leaf ){ rc = moveToChild(pCur, get4byte(&pPage->aData[pPage->hdrOffset+8])); - if( rc ){ - *pRes = 0; - return rc; - } - rc = moveToLeftmost(pCur); - *pRes = 0; - return rc; + if( rc ) return rc; + return moveToLeftmost(pCur); } do{ if( pCur->iPage==0 ){ *pRes = 1; pCur->eState = CURSOR_INVALID; @@ -54728,48 +58030,79 @@ return SQLITE_OK; } moveToParent(pCur); pPage = pCur->apPage[pCur->iPage]; }while( pCur->aiIdx[pCur->iPage]>=pPage->nCell ); - *pRes = 0; if( pPage->intKey ){ - rc = sqlite3BtreeNext(pCur, pRes); + return sqlite3BtreeNext(pCur, pRes); }else{ - rc = SQLITE_OK; + return SQLITE_OK; } - return rc; } + if( pPage->leaf ){ + return SQLITE_OK; + }else{ + return moveToLeftmost(pCur); + } +} +SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int *pRes){ + MemPage *pPage; + assert( cursorHoldsMutex(pCur) ); + assert( pRes!=0 ); + assert( *pRes==0 || *pRes==1 ); + assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); + pCur->info.nSize = 0; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); *pRes = 0; + if( pCur->eState!=CURSOR_VALID ) return btreeNext(pCur, pRes); + pPage = pCur->apPage[pCur->iPage]; + if( (++pCur->aiIdx[pCur->iPage])>=pPage->nCell ){ + pCur->aiIdx[pCur->iPage]--; + return btreeNext(pCur, pRes); + } if( pPage->leaf ){ return SQLITE_OK; + }else{ + return moveToLeftmost(pCur); } - rc = moveToLeftmost(pCur); - return rc; } - /* ** Step the cursor to the back to the previous entry in the database. If ** successful then set *pRes=0. If the cursor ** was already pointing to the first entry in the database before ** this routine was called, then set *pRes=1. +** +** The main entry point is sqlite3BtreePrevious(). That routine is optimized +** for the common case of merely decrementing the cell counter BtCursor.aiIdx +** to the previous cell on the current page. The (slower) btreePrevious() +** helper routine is called when it is necessary to move to a different page +** or to restore the cursor. +** +** The calling function will set *pRes to 0 or 1. The initial *pRes value +** will be 1 if the cursor being stepped corresponds to an SQL index and +** if this routine could have been skipped if that SQL index had been +** a unique index. Otherwise the caller will have set *pRes to zero. +** Zero is the common case. The btree implementation is free to use the +** initial *pRes value as a hint to improve performance, but the current +** SQLite btree implementation does not. (Note that the comdb2 btree +** implementation does use this hint, however.) */ -SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){ +static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur, int *pRes){ int rc; MemPage *pPage; assert( cursorHoldsMutex(pCur) ); assert( pRes!=0 ); + assert( *pRes==0 ); assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); - pCur->atLast = 0; + assert( (pCur->curFlags & (BTCF_AtLast|BTCF_ValidOvfl|BTCF_ValidNKey))==0 ); + assert( pCur->info.nSize==0 ); if( pCur->eState!=CURSOR_VALID ){ - if( ALWAYS(pCur->eState>=CURSOR_REQUIRESEEK) ){ - rc = btreeRestoreCursorPosition(pCur); - if( rc!=SQLITE_OK ){ - *pRes = 0; - return rc; - } + rc = restoreCursorPosition(pCur); + if( rc!=SQLITE_OK ){ + return rc; } if( CURSOR_INVALID==pCur->eState ){ *pRes = 1; return SQLITE_OK; } @@ -54776,11 +58109,10 @@ if( pCur->skipNext ){ assert( pCur->eState==CURSOR_VALID || pCur->eState==CURSOR_SKIPNEXT ); pCur->eState = CURSOR_VALID; if( pCur->skipNext<0 ){ pCur->skipNext = 0; - *pRes = 0; return SQLITE_OK; } pCur->skipNext = 0; } } @@ -54788,14 +58120,11 @@ pPage = pCur->apPage[pCur->iPage]; assert( pPage->isInit ); if( !pPage->leaf ){ int idx = pCur->aiIdx[pCur->iPage]; rc = moveToChild(pCur, get4byte(findCell(pPage, idx))); - if( rc ){ - *pRes = 0; - return rc; - } + if( rc ) return rc; rc = moveToRightmost(pCur); }else{ while( pCur->aiIdx[pCur->iPage]==0 ){ if( pCur->iPage==0 ){ pCur->eState = CURSOR_INVALID; @@ -54802,23 +58131,39 @@ *pRes = 1; return SQLITE_OK; } moveToParent(pCur); } - pCur->info.nSize = 0; - pCur->validNKey = 0; + assert( pCur->info.nSize==0 ); + assert( (pCur->curFlags & (BTCF_ValidNKey|BTCF_ValidOvfl))==0 ); pCur->aiIdx[pCur->iPage]--; pPage = pCur->apPage[pCur->iPage]; if( pPage->intKey && !pPage->leaf ){ rc = sqlite3BtreePrevious(pCur, pRes); }else{ rc = SQLITE_OK; } } + return rc; +} +SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){ + assert( cursorHoldsMutex(pCur) ); + assert( pRes!=0 ); + assert( *pRes==0 || *pRes==1 ); + assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID ); *pRes = 0; - return rc; + pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidOvfl|BTCF_ValidNKey); + pCur->info.nSize = 0; + if( pCur->eState!=CURSOR_VALID + || pCur->aiIdx[pCur->iPage]==0 + || pCur->apPage[pCur->iPage]->leaf==0 + ){ + return btreePrevious(pCur, pRes); + } + pCur->aiIdx[pCur->iPage]--; + return SQLITE_OK; } /* ** Allocate a new page from the database file. ** @@ -54859,10 +58204,12 @@ assert( sqlite3_mutex_held(pBt->mutex) ); assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) ); pPage1 = pBt->pPage1; mxPage = btreePagecount(pBt); + /* EVIDENCE-OF: R-05119-02637 The 4-byte big-endian integer at offset 36 + ** stores stores the total number of pages on the freelist. */ n = get4byte(&pPage1->aData[36]); testcase( n==mxPage-1 ); if( n>=mxPage ){ return SQLITE_CORRUPT_BKPT; } @@ -54905,12 +58252,18 @@ ** or until a page less than 'nearby' is located (eMode==BTALLOC_LT) */ do { pPrevTrunk = pTrunk; if( pPrevTrunk ){ + /* EVIDENCE-OF: R-01506-11053 The first integer on a freelist trunk page + ** is the page number of the next freelist trunk page in the list or + ** zero if this is the last freelist trunk page. */ iTrunk = get4byte(&pPrevTrunk->aData[0]); }else{ + /* EVIDENCE-OF: R-59841-13798 The 4-byte big-endian integer at offset 32 + ** stores the page number of the first page of the freelist, or zero if + ** the freelist is empty. */ iTrunk = get4byte(&pPage1->aData[32]); } testcase( iTrunk==mxPage ); if( iTrunk>mxPage ){ rc = SQLITE_CORRUPT_BKPT; @@ -54921,12 +58274,13 @@ pTrunk = 0; goto end_allocate_page; } assert( pTrunk!=0 ); assert( pTrunk->aData!=0 ); - - k = get4byte(&pTrunk->aData[4]); /* # of leaves on this trunk page */ + /* EVIDENCE-OF: R-13523-04394 The second integer on a freelist trunk page + ** is the number of leaf page pointers to follow. */ + k = get4byte(&pTrunk->aData[4]); if( k==0 && !searchList ){ /* The trunk has no leaves and the list is not being searched. ** So extract the trunk page itself and use it as the newly ** allocated page */ assert( pPrevTrunk==0 ); @@ -55056,11 +58410,11 @@ if( rc ) goto end_allocate_page; if( closest<k-1 ){ memcpy(&aData[8+closest*4], &aData[4+k*4], 4); } put4byte(&aData[4], k-1); - noContent = !btreeGetHasContent(pBt, *pPgno) ? PAGER_GET_NOCONTENT : 0; + noContent = !btreeGetHasContent(pBt, *pPgno)? PAGER_GET_NOCONTENT : 0; rc = btreeGetPage(pBt, *pPgno, ppPage, noContent); if( rc==SQLITE_OK ){ rc = sqlite3PagerWrite((*ppPage)->pDbPage); if( rc!=SQLITE_OK ){ releasePage(*ppPage); @@ -55089,11 +58443,11 @@ ** content for any page that really does lie past the end of the database ** file on disk. So the effects of disabling the no-content optimization ** here are confined to those pages that lie between the end of the ** database image and the end of the database file. */ - int bNoContent = (0==IfNotOmitAV(pBt->bDoTruncate)) ? PAGER_GET_NOCONTENT : 0; + int bNoContent = (0==IfNotOmitAV(pBt->bDoTruncate))? PAGER_GET_NOCONTENT:0; rc = sqlite3PagerWrite(pBt->pPage1->pDbPage); if( rc ) return rc; pBt->nPage++; if( pBt->nPage==PENDING_BYTE_PAGE(pBt) ) pBt->nPage++; @@ -55136,10 +58490,11 @@ releasePage(pTrunk); releasePage(pPrevTrunk); if( rc==SQLITE_OK ){ if( sqlite3PagerPageRefcount((*ppPage)->pDbPage)>1 ){ releasePage(*ppPage); + *ppPage = 0; return SQLITE_CORRUPT_BKPT; } (*ppPage)->isInit = 0; }else{ *ppPage = 0; @@ -55239,10 +58594,15 @@ ** to maintain backwards compatibility with older versions of SQLite, ** we will continue to restrict the number of entries to usableSize/4 - 8 ** for now. At some point in the future (once everyone has upgraded ** to 3.6.0 or later) we should consider fixing the conditional above ** to read "usableSize/4-2" instead of "usableSize/4-8". + ** + ** EVIDENCE-OF: R-19920-11576 However, newer versions of SQLite still + ** avoid using the last six entries in the freelist trunk page array in + ** order that database files created by newer versions of SQLite can be + ** read by older versions of SQLite. */ rc = sqlite3PagerWrite(pTrunk->pDbPage); if( rc==SQLITE_OK ){ put4byte(&pTrunk->aData[4], nLeaf+1); put4byte(&pTrunk->aData[8+nLeaf*4], iPage); @@ -55287,22 +58647,29 @@ *pRC = freePage2(pPage->pBt, pPage, pPage->pgno); } } /* -** Free any overflow pages associated with the given Cell. +** Free any overflow pages associated with the given Cell. Write the +** local Cell size (the number of bytes on the original page, omitting +** overflow) into *pnSize. */ -static int clearCell(MemPage *pPage, unsigned char *pCell){ +static int clearCell( + MemPage *pPage, /* The page that contains the Cell */ + unsigned char *pCell, /* First byte of the Cell */ + u16 *pnSize /* Write the size of the Cell here */ +){ BtShared *pBt = pPage->pBt; CellInfo info; Pgno ovflPgno; int rc; int nOvfl; u32 ovflPageSize; assert( sqlite3_mutex_held(pPage->pBt->mutex) ); btreeParseCellPtr(pPage, pCell, &info); + *pnSize = info.nSize; if( info.iOverflow==0 ){ return SQLITE_OK; /* No overflow pages. Return without doing anything */ } if( pCell+info.iOverflow+3 > pPage->aData+pPage->maskPage ){ return SQLITE_CORRUPT_BKPT; /* Cell extends past end of page */ @@ -55382,54 +58749,87 @@ unsigned char *pPrior; unsigned char *pPayload; BtShared *pBt = pPage->pBt; Pgno pgnoOvfl = 0; int nHeader; - CellInfo info; assert( sqlite3_mutex_held(pPage->pBt->mutex) ); /* pPage is not necessarily writeable since pCell might be auxiliary ** buffer space that is separate from the pPage buffer area */ assert( pCell<pPage->aData || pCell>=&pPage->aData[pBt->pageSize] || sqlite3PagerIswriteable(pPage->pDbPage) ); /* Fill in the header. */ - nHeader = 0; - if( !pPage->leaf ){ - nHeader += 4; - } - if( pPage->hasData ){ - nHeader += putVarint(&pCell[nHeader], nData+nZero); + nHeader = pPage->childPtrSize; + nPayload = nData + nZero; + if( pPage->intKeyLeaf ){ + nHeader += putVarint32(&pCell[nHeader], nPayload); }else{ - nData = nZero = 0; + assert( nData==0 ); + assert( nZero==0 ); } nHeader += putVarint(&pCell[nHeader], *(u64*)&nKey); - btreeParseCellPtr(pPage, pCell, &info); - assert( info.nHeader==nHeader ); - assert( info.nKey==nKey ); - assert( info.nData==(u32)(nData+nZero) ); - /* Fill in the payload */ - nPayload = nData + nZero; + /* Fill in the payload size */ if( pPage->intKey ){ pSrc = pData; nSrc = nData; nData = 0; }else{ if( NEVER(nKey>0x7fffffff || pKey==0) ){ return SQLITE_CORRUPT_BKPT; } - nPayload += (int)nKey; + nPayload = (int)nKey; pSrc = pKey; nSrc = (int)nKey; } - *pnSize = info.nSize; - spaceLeft = info.nLocal; + if( nPayload<=pPage->maxLocal ){ + n = nHeader + nPayload; + testcase( n==3 ); + testcase( n==4 ); + if( n<4 ) n = 4; + *pnSize = n; + spaceLeft = nPayload; + pPrior = pCell; + }else{ + int mn = pPage->minLocal; + n = mn + (nPayload - mn) % (pPage->pBt->usableSize - 4); + testcase( n==pPage->maxLocal ); + testcase( n==pPage->maxLocal+1 ); + if( n > pPage->maxLocal ) n = mn; + spaceLeft = n; + *pnSize = n + nHeader + 4; + pPrior = &pCell[nHeader+n]; + } pPayload = &pCell[nHeader]; - pPrior = &pCell[info.iOverflow]; + /* At this point variables should be set as follows: + ** + ** nPayload Total payload size in bytes + ** pPayload Begin writing payload here + ** spaceLeft Space available at pPayload. If nPayload>spaceLeft, + ** that means content must spill into overflow pages. + ** *pnSize Size of the local cell (not counting overflow pages) + ** pPrior Where to write the pgno of the first overflow page + ** + ** Use a call to btreeParseCellPtr() to verify that the values above + ** were computed correctly. + */ +#if SQLITE_DEBUG + { + CellInfo info; + btreeParseCellPtr(pPage, pCell, &info); + assert( nHeader=(int)(info.pPayload - pCell) ); + assert( info.nKey==nKey ); + assert( *pnSize == info.nSize ); + assert( spaceLeft == info.nLocal ); + assert( pPrior == &pCell[info.iOverflow] ); + } +#endif + + /* Write the payload into the local Cell and any extra into overflow pages */ while( nPayload>0 ){ if( spaceLeft==0 ){ #ifndef SQLITE_OMIT_AUTOVACUUM Pgno pgnoPtrmap = pgnoOvfl; /* Overflow page pointer-map entry page */ if( pBt->autoVacuum ){ @@ -55525,11 +58925,10 @@ */ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ u32 pc; /* Offset to cell content of cell being deleted */ u8 *data; /* pPage->aData */ u8 *ptr; /* Used to move bytes around within data[] */ - u8 *endPtr; /* End of loop */ int rc; /* The return code */ int hdr; /* Beginning of the header. 0 most pages. 100 page 1 */ if( *pRC ) return; @@ -55550,19 +58949,22 @@ rc = freeSpace(pPage, pc, sz); if( rc ){ *pRC = rc; return; } - endPtr = &pPage->aCellIdx[2*pPage->nCell - 2]; - assert( (SQLITE_PTR_TO_INT(ptr)&1)==0 ); /* ptr is always 2-byte aligned */ - while( ptr<endPtr ){ - *(u16*)ptr = *(u16*)&ptr[2]; - ptr += 2; - } pPage->nCell--; - put2byte(&data[hdr+3], pPage->nCell); - pPage->nFree += 2; + if( pPage->nCell==0 ){ + memset(&data[hdr+1], 0, 4); + data[hdr+7] = 0; + put2byte(&data[hdr+5], pPage->pBt->usableSize); + pPage->nFree = pPage->pBt->usableSize - pPage->hdrOffset + - pPage->childPtrSize - 8; + }else{ + memmove(ptr, ptr+2, 2*(pPage->nCell - idx)); + put2byte(&data[hdr+3], pPage->nCell); + pPage->nFree += 2; + } } /* ** Insert a new cell on pPage at cell index "i". pCell points to the ** content of the cell. @@ -55572,15 +58974,10 @@ ** pTemp is not null. Regardless of pTemp, allocate a new entry ** in pPage->apOvfl[] and make it point to the cell content (either ** in pTemp or the original pCell) and also record its index. ** Allocating a new entry in pPage->aCell[] implies that ** pPage->nOverflow is incremented. -** -** If nSkip is non-zero, then do not copy the first nSkip bytes of the -** cell. The caller will overwrite them after this function returns. If -** nSkip is non-zero, then pCell may not point to an invalid memory location -** (but pCell+nSkip is always valid). */ static void insertCell( MemPage *pPage, /* Page into which we are copying */ int i, /* New cell becomes the i-th cell of the page */ u8 *pCell, /* Content of the new cell */ @@ -55593,19 +58990,16 @@ int j; /* Loop counter */ int end; /* First byte past the last cell pointer in data[] */ int ins; /* Index in data[] where new cell pointer is inserted */ int cellOffset; /* Address of first cell pointer in data[] */ u8 *data; /* The content of the whole page */ - u8 *ptr; /* Used for moving information around in data[] */ - u8 *endPtr; /* End of the loop */ - - int nSkip = (iChild ? 4 : 0); if( *pRC ) return; assert( i>=0 && i<=pPage->nCell+pPage->nOverflow ); - assert( pPage->nCell<=MX_CELL(pPage->pBt) && MX_CELL(pPage->pBt)<=10921 ); + assert( MX_CELL(pPage->pBt)<=10921 ); + assert( pPage->nCell<=MX_CELL(pPage->pBt) || CORRUPT_DB ); assert( pPage->nOverflow<=ArraySize(pPage->apOvfl) ); assert( ArraySize(pPage->apOvfl)==ArraySize(pPage->aiOvfl) ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); /* The cell should normally be sized correctly. However, when moving a ** malformed cell from a leaf page to an interior page, if the cell size @@ -55613,11 +59007,11 @@ ** might be less than 8 (leaf-size + pointer) on the interior node. Hence ** the term after the || in the following assert(). */ assert( sz==cellSizePtr(pPage, pCell) || (sz==8 && iChild>0) ); if( pPage->nOverflow || sz+2>pPage->nFree ){ if( pTemp ){ - memcpy(pTemp+nSkip, pCell+nSkip, sz-nSkip); + memcpy(pTemp, pCell, sz); pCell = pTemp; } if( iChild ){ put4byte(pCell, iChild); } @@ -55642,21 +59036,15 @@ ** if it returns success */ assert( idx >= end+2 ); assert( idx+sz <= (int)pPage->pBt->usableSize ); pPage->nCell++; pPage->nFree -= (u16)(2 + sz); - memcpy(&data[idx+nSkip], pCell+nSkip, sz-nSkip); + memcpy(&data[idx], pCell, sz); if( iChild ){ put4byte(&data[idx], iChild); } - ptr = &data[end]; - endPtr = &data[ins]; - assert( (SQLITE_PTR_TO_INT(ptr)&1)==0 ); /* ptr is always 2-byte aligned */ - while( ptr>endPtr ){ - *(u16*)ptr = *(u16*)&ptr[-2]; - ptr -= 2; - } + memmove(&data[ins+2], &data[ins], end-ins); put2byte(&data[ins], idx); put2byte(&data[pPage->hdrOffset+3], pPage->nCell); #ifndef SQLITE_OMIT_AUTOVACUUM if( pPage->pBt->autoVacuum ){ /* The cell may contain a pointer to an overflow page. If so, write @@ -55667,49 +59055,275 @@ #endif } } /* -** Add a list of cells to a page. The page should be initially empty. -** The cells are guaranteed to fit on the page. -*/ -static void assemblePage( - MemPage *pPage, /* The page to be assemblied */ - int nCell, /* The number of cells to add to this page */ - u8 **apCell, /* Pointers to cell bodies */ - u16 *aSize /* Sizes of the cells */ -){ - int i; /* Loop counter */ - u8 *pCellptr; /* Address of next cell pointer */ - int cellbody; /* Address of next cell body */ - u8 * const data = pPage->aData; /* Pointer to data for pPage */ - const int hdr = pPage->hdrOffset; /* Offset of header on pPage */ - const int nUsable = pPage->pBt->usableSize; /* Usable size of page */ - - assert( pPage->nOverflow==0 ); - assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - assert( nCell>=0 && nCell<=(int)MX_CELL(pPage->pBt) - && (int)MX_CELL(pPage->pBt)<=10921); - assert( sqlite3PagerIswriteable(pPage->pDbPage) ); - - /* Check that the page has just been zeroed by zeroPage() */ - assert( pPage->nCell==0 ); - assert( get2byteNotZero(&data[hdr+5])==nUsable ); - - pCellptr = &pPage->aCellIdx[nCell*2]; - cellbody = nUsable; - for(i=nCell-1; i>=0; i--){ - u16 sz = aSize[i]; - pCellptr -= 2; - cellbody -= sz; - put2byte(pCellptr, cellbody); - memcpy(&data[cellbody], apCell[i], sz); - } - put2byte(&data[hdr+3], nCell); - put2byte(&data[hdr+5], cellbody); - pPage->nFree -= (nCell*2 + nUsable - cellbody); - pPage->nCell = (u16)nCell; +** Array apCell[] contains pointers to nCell b-tree page cells. The +** szCell[] array contains the size in bytes of each cell. This function +** replaces the current contents of page pPg with the contents of the cell +** array. +** +** Some of the cells in apCell[] may currently be stored in pPg. This +** function works around problems caused by this by making a copy of any +** such cells before overwriting the page data. +** +** The MemPage.nFree field is invalidated by this function. It is the +** responsibility of the caller to set it correctly. +*/ +static void rebuildPage( + MemPage *pPg, /* Edit this page */ + int nCell, /* Final number of cells on page */ + u8 **apCell, /* Array of cells */ + u16 *szCell /* Array of cell sizes */ +){ + const int hdr = pPg->hdrOffset; /* Offset of header on pPg */ + u8 * const aData = pPg->aData; /* Pointer to data for pPg */ + const int usableSize = pPg->pBt->usableSize; + u8 * const pEnd = &aData[usableSize]; + int i; + u8 *pCellptr = pPg->aCellIdx; + u8 *pTmp = sqlite3PagerTempSpace(pPg->pBt->pPager); + u8 *pData; + + i = get2byte(&aData[hdr+5]); + memcpy(&pTmp[i], &aData[i], usableSize - i); + + pData = pEnd; + for(i=0; i<nCell; i++){ + u8 *pCell = apCell[i]; + if( pCell>aData && pCell<pEnd ){ + pCell = &pTmp[pCell - aData]; + } + pData -= szCell[i]; + memcpy(pData, pCell, szCell[i]); + put2byte(pCellptr, (pData - aData)); + pCellptr += 2; + assert( szCell[i]==cellSizePtr(pPg, pCell) ); + } + + /* The pPg->nFree field is now set incorrectly. The caller will fix it. */ + pPg->nCell = nCell; + pPg->nOverflow = 0; + + put2byte(&aData[hdr+1], 0); + put2byte(&aData[hdr+3], pPg->nCell); + put2byte(&aData[hdr+5], pData - aData); + aData[hdr+7] = 0x00; +} + +/* +** Array apCell[] contains nCell pointers to b-tree cells. Array szCell +** contains the size in bytes of each such cell. This function attempts to +** add the cells stored in the array to page pPg. If it cannot (because +** the page needs to be defragmented before the cells will fit), non-zero +** is returned. Otherwise, if the cells are added successfully, zero is +** returned. +** +** Argument pCellptr points to the first entry in the cell-pointer array +** (part of page pPg) to populate. After cell apCell[0] is written to the +** page body, a 16-bit offset is written to pCellptr. And so on, for each +** cell in the array. It is the responsibility of the caller to ensure +** that it is safe to overwrite this part of the cell-pointer array. +** +** When this function is called, *ppData points to the start of the +** content area on page pPg. If the size of the content area is extended, +** *ppData is updated to point to the new start of the content area +** before returning. +** +** Finally, argument pBegin points to the byte immediately following the +** end of the space required by this page for the cell-pointer area (for +** all cells - not just those inserted by the current call). If the content +** area must be extended to before this point in order to accomodate all +** cells in apCell[], then the cells do not fit and non-zero is returned. +*/ +static int pageInsertArray( + MemPage *pPg, /* Page to add cells to */ + u8 *pBegin, /* End of cell-pointer array */ + u8 **ppData, /* IN/OUT: Page content -area pointer */ + u8 *pCellptr, /* Pointer to cell-pointer area */ + int nCell, /* Number of cells to add to pPg */ + u8 **apCell, /* Array of cells */ + u16 *szCell /* Array of cell sizes */ +){ + int i; + u8 *aData = pPg->aData; + u8 *pData = *ppData; + const int bFreelist = aData[1] || aData[2]; + assert( CORRUPT_DB || pPg->hdrOffset==0 ); /* Never called on page 1 */ + for(i=0; i<nCell; i++){ + int sz = szCell[i]; + int rc; + u8 *pSlot; + if( bFreelist==0 || (pSlot = pageFindSlot(pPg, sz, &rc, 0))==0 ){ + pData -= sz; + if( pData<pBegin ) return 1; + pSlot = pData; + } + memcpy(pSlot, apCell[i], sz); + put2byte(pCellptr, (pSlot - aData)); + pCellptr += 2; + } + *ppData = pData; + return 0; +} + +/* +** Array apCell[] contains nCell pointers to b-tree cells. Array szCell +** contains the size in bytes of each such cell. This function adds the +** space associated with each cell in the array that is currently stored +** within the body of pPg to the pPg free-list. The cell-pointers and other +** fields of the page are not updated. +** +** This function returns the total number of cells added to the free-list. +*/ +static int pageFreeArray( + MemPage *pPg, /* Page to edit */ + int nCell, /* Cells to delete */ + u8 **apCell, /* Array of cells */ + u16 *szCell /* Array of cell sizes */ +){ + u8 * const aData = pPg->aData; + u8 * const pEnd = &aData[pPg->pBt->usableSize]; + u8 * const pStart = &aData[pPg->hdrOffset + 8 + pPg->childPtrSize]; + int nRet = 0; + int i; + u8 *pFree = 0; + int szFree = 0; + + for(i=0; i<nCell; i++){ + u8 *pCell = apCell[i]; + if( pCell>=pStart && pCell<pEnd ){ + int sz = szCell[i]; + if( pFree!=(pCell + sz) ){ + if( pFree ){ + assert( pFree>aData && (pFree - aData)<65536 ); + freeSpace(pPg, (u16)(pFree - aData), szFree); + } + pFree = pCell; + szFree = sz; + if( pFree+sz>pEnd ) return 0; + }else{ + pFree = pCell; + szFree += sz; + } + nRet++; + } + } + if( pFree ){ + assert( pFree>aData && (pFree - aData)<65536 ); + freeSpace(pPg, (u16)(pFree - aData), szFree); + } + return nRet; +} + +/* +** apCell[] and szCell[] contains pointers to and sizes of all cells in the +** pages being balanced. The current page, pPg, has pPg->nCell cells starting +** with apCell[iOld]. After balancing, this page should hold nNew cells +** starting at apCell[iNew]. +** +** This routine makes the necessary adjustments to pPg so that it contains +** the correct cells after being balanced. +** +** The pPg->nFree field is invalid when this function returns. It is the +** responsibility of the caller to set it correctly. +*/ +static void editPage( + MemPage *pPg, /* Edit this page */ + int iOld, /* Index of first cell currently on page */ + int iNew, /* Index of new first cell on page */ + int nNew, /* Final number of cells on page */ + u8 **apCell, /* Array of cells */ + u16 *szCell /* Array of cell sizes */ +){ + u8 * const aData = pPg->aData; + const int hdr = pPg->hdrOffset; + u8 *pBegin = &pPg->aCellIdx[nNew * 2]; + int nCell = pPg->nCell; /* Cells stored on pPg */ + u8 *pData; + u8 *pCellptr; + int i; + int iOldEnd = iOld + pPg->nCell + pPg->nOverflow; + int iNewEnd = iNew + nNew; + +#ifdef SQLITE_DEBUG + u8 *pTmp = sqlite3PagerTempSpace(pPg->pBt->pPager); + memcpy(pTmp, aData, pPg->pBt->usableSize); +#endif + + /* Remove cells from the start and end of the page */ + if( iOld<iNew ){ + int nShift = pageFreeArray( + pPg, iNew-iOld, &apCell[iOld], &szCell[iOld] + ); + memmove(pPg->aCellIdx, &pPg->aCellIdx[nShift*2], nCell*2); + nCell -= nShift; + } + if( iNewEnd < iOldEnd ){ + nCell -= pageFreeArray( + pPg, iOldEnd-iNewEnd, &apCell[iNewEnd], &szCell[iNewEnd] + ); + } + + pData = &aData[get2byteNotZero(&aData[hdr+5])]; + if( pData<pBegin ) goto editpage_fail; + + /* Add cells to the start of the page */ + if( iNew<iOld ){ + int nAdd = MIN(nNew,iOld-iNew); + assert( (iOld-iNew)<nNew || nCell==0 || CORRUPT_DB ); + pCellptr = pPg->aCellIdx; + memmove(&pCellptr[nAdd*2], pCellptr, nCell*2); + if( pageInsertArray( + pPg, pBegin, &pData, pCellptr, + nAdd, &apCell[iNew], &szCell[iNew] + ) ) goto editpage_fail; + nCell += nAdd; + } + + /* Add any overflow cells */ + for(i=0; i<pPg->nOverflow; i++){ + int iCell = (iOld + pPg->aiOvfl[i]) - iNew; + if( iCell>=0 && iCell<nNew ){ + pCellptr = &pPg->aCellIdx[iCell * 2]; + memmove(&pCellptr[2], pCellptr, (nCell - iCell) * 2); + nCell++; + if( pageInsertArray( + pPg, pBegin, &pData, pCellptr, + 1, &apCell[iCell + iNew], &szCell[iCell + iNew] + ) ) goto editpage_fail; + } + } + + /* Append cells to the end of the page */ + pCellptr = &pPg->aCellIdx[nCell*2]; + if( pageInsertArray( + pPg, pBegin, &pData, pCellptr, + nNew-nCell, &apCell[iNew+nCell], &szCell[iNew+nCell] + ) ) goto editpage_fail; + + pPg->nCell = nNew; + pPg->nOverflow = 0; + + put2byte(&aData[hdr+3], pPg->nCell); + put2byte(&aData[hdr+5], pData - aData); + +#ifdef SQLITE_DEBUG + for(i=0; i<nNew && !CORRUPT_DB; i++){ + u8 *pCell = apCell[i+iNew]; + int iOff = get2byte(&pPg->aCellIdx[i*2]); + if( pCell>=aData && pCell<&aData[pPg->pBt->usableSize] ){ + pCell = &pTmp[pCell - aData]; + } + assert( 0==memcmp(pCell, &aData[iOff], szCell[i+iNew]) ); + } +#endif + + return; + editpage_fail: + /* Unable to edit this page. Rebuild it from scratch instead. */ + rebuildPage(pPg, nNew, &apCell[iNew], &szCell[iNew]); } /* ** The following parameters determine how many adjacent pages get involved ** in a balancing operation. NN is the number of neighbors on either side @@ -55759,11 +59373,11 @@ assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( sqlite3PagerIswriteable(pParent->pDbPage) ); assert( pPage->nOverflow==1 ); /* This error condition is now caught prior to reaching this function */ - if( pPage->nCell==0 ) return SQLITE_CORRUPT_BKPT; + if( NEVER(pPage->nCell==0) ) return SQLITE_CORRUPT_BKPT; /* Allocate a new page. This page will become the right-sibling of ** pPage. Make the parent page writable, so that the new divider cell ** may be inserted. If both these operations are successful, proceed. */ @@ -55777,11 +59391,12 @@ u8 *pStop; assert( sqlite3PagerIswriteable(pNew->pDbPage) ); assert( pPage->aData[0]==(PTF_INTKEY|PTF_LEAFDATA|PTF_LEAF) ); zeroPage(pNew, PTF_INTKEY|PTF_LEAFDATA|PTF_LEAF); - assemblePage(pNew, 1, &pCell, &szCell); + rebuildPage(pNew, 1, &pCell, &szCell); + pNew->nFree = pBt->usableSize - pNew->cellOffset - 2 - szCell; /* If this is an auto-vacuum database, update the pointer map ** with entries for the new page, and any pointer from the ** cell on the page to an overflow page. If either of these ** operations fails, the return code is set, but the contents @@ -55996,21 +59611,26 @@ int subtotal; /* Subtotal of bytes in cells on one page */ int iSpace1 = 0; /* First unused byte of aSpace1[] */ int iOvflSpace = 0; /* First unused byte of aOvflSpace[] */ int szScratch; /* Size of scratch memory requested */ MemPage *apOld[NB]; /* pPage and up to two siblings */ - MemPage *apCopy[NB]; /* Private copies of apOld[] pages */ MemPage *apNew[NB+2]; /* pPage and up to NB siblings after balancing */ u8 *pRight; /* Location in parent of right-sibling pointer */ u8 *apDiv[NB-1]; /* Divider cells in pParent */ int cntNew[NB+2]; /* Index in aCell[] of cell after i-th page */ - int szNew[NB+2]; /* Combined size of cells place on i-th page */ + int cntOld[NB+2]; /* Old index in aCell[] after i-th page */ + int szNew[NB+2]; /* Combined size of cells placed on i-th page */ u8 **apCell = 0; /* All cells begin balanced */ u16 *szCell; /* Local size of all cells in apCell[] */ u8 *aSpace1; /* Space for copies of dividers cells */ Pgno pgno; /* Temp var to store a page number in */ + u8 abDone[NB+2]; /* True after i'th new page is populated */ + Pgno aPgno[NB+2]; /* Page numbers of new pages before shuffling */ + Pgno aPgOrder[NB+2]; /* Copy of aPgno[] used for sorting pages */ + u16 aPgFlags[NB+2]; /* flags field of new pages before shuffling */ + memset(abDone, 0, sizeof(abDone)); pBt = pParent->pBt; assert( sqlite3_mutex_held(pBt->mutex) ); assert( sqlite3PagerIswriteable(pParent->pDbPage) ); #if 0 @@ -56115,16 +59735,18 @@ nMaxCells = (nMaxCells + 3)&~3; /* ** Allocate space for memory structures */ - k = pBt->pageSize + ROUND8(sizeof(MemPage)); szScratch = nMaxCells*sizeof(u8*) /* apCell */ + nMaxCells*sizeof(u16) /* szCell */ - + pBt->pageSize /* aSpace1 */ - + k*nOld; /* Page copies (apCopy) */ + + pBt->pageSize; /* aSpace1 */ + + /* EVIDENCE-OF: R-28375-38319 SQLite will never request a scratch buffer + ** that is more than 6 times the database page size. */ + assert( szScratch<=6*(int)pBt->pageSize ); apCell = sqlite3ScratchMalloc( szScratch ); if( apCell==0 ){ rc = SQLITE_NOMEM; goto balance_cleanup; } @@ -56133,12 +59755,12 @@ assert( EIGHT_BYTE_ALIGNMENT(aSpace1) ); /* ** Load pointers to all cells on sibling pages and the divider cells ** into the local apCell[] array. Make copies of the divider cells - ** into space obtained from aSpace1[] and remove the divider cells - ** from pParent. + ** into space obtained from aSpace1[]. The divider cells have already + ** been removed from pParent. ** ** If the siblings are on leaf pages, then the child pointers of the ** divider cells are stripped from the cells before they are copied ** into aSpace1[]. In this way, all cells in apCell[] are without ** child pointers. If siblings are not leaves, then all cell in @@ -56147,22 +59769,14 @@ ** ** leafCorrection: 4 if pPage is a leaf. 0 if pPage is not a leaf. ** leafData: 1 if pPage holds key+data and pParent holds only keys. */ leafCorrection = apOld[0]->leaf*4; - leafData = apOld[0]->hasData; + leafData = apOld[0]->intKeyLeaf; for(i=0; i<nOld; i++){ int limit; - - /* Before doing anything else, take a copy of the i'th original sibling - ** The rest of this function will use data from the copies rather - ** that the original pages since the original pages will be in the - ** process of being overwritten. */ - MemPage *pOld = apCopy[i] = (MemPage*)&aSpace1[pBt->pageSize + k*i]; - memcpy(pOld, apOld[i], sizeof(MemPage)); - pOld->aData = (void*)&pOld[1]; - memcpy(pOld->aData, apOld[i]->aData, pBt->pageSize); + MemPage *pOld = apOld[i]; limit = pOld->nCell+pOld->nOverflow; if( pOld->nOverflow>0 ){ for(j=0; j<limit; j++){ assert( nCell<nMaxCells ); @@ -56179,10 +59793,11 @@ apCell[nCell] = findCellv2(aData, maskPage, cellOffset, j); szCell[nCell] = cellSizePtr(pOld, apCell[nCell]); nCell++; } } + cntOld[i] = nCell; if( i<nOld-1 && !leafData){ u16 sz = (u16)szNew[i]; u8 *pTemp; assert( nCell<nMaxCells ); szCell[nCell] = sz; @@ -56201,11 +59816,15 @@ ** pointer of the divider cell */ memcpy(apCell[nCell], &pOld->aData[8], 4); }else{ assert( leafCorrection==4 ); if( szCell[nCell]<4 ){ - /* Do not allow any cells smaller than 4 bytes. */ + /* Do not allow any cells smaller than 4 bytes. If a smaller cell + ** does exist, pad it with 0x00 bytes. */ + assert( szCell[nCell]==3 ); + assert( apCell[nCell]==&aSpace1[iSpace1-3] ); + aSpace1[iSpace1++] = 0x00; szCell[nCell] = 4; } } nCell++; } @@ -56230,11 +59849,11 @@ usableSpace = pBt->usableSize - 12 + leafCorrection; for(subtotal=k=i=0; i<nCell; i++){ assert( i<nMaxCells ); subtotal += szCell[i] + 2; if( subtotal > usableSpace ){ - szNew[k] = subtotal - szCell[i]; + szNew[k] = subtotal - szCell[i] - 2; cntNew[k] = i; if( leafData ){ i--; } subtotal = 0; k++; if( k>NB+1 ){ rc = SQLITE_CORRUPT_BKPT; goto balance_cleanup; } @@ -56244,13 +59863,14 @@ cntNew[k] = nCell; k++; /* ** The packing computed by the previous block is biased toward the siblings - ** on the left side. The left siblings are always nearly full, while the - ** right-most sibling might be nearly empty. This block of code attempts - ** to adjust the packing of siblings to get a better balance. + ** on the left side (siblings with smaller keys). The left siblings are + ** always nearly full, while the right-most sibling might be nearly empty. + ** The next block of code attempts to adjust the packing of siblings to + ** get a better balance. ** ** This adjustment is more than an optimization. The packing above might ** be so out of balance as to be illegal. For example, the right-most ** sibling might be completely empty. This adjustment is not optional. */ @@ -56275,26 +59895,22 @@ } szNew[i] = szRight; szNew[i-1] = szLeft; } - /* Either we found one or more cells (cntnew[0])>0) or pPage is - ** a virtual root page. A virtual root page is when the real root - ** page is page 1 and we are the only child of that page. - ** - ** UPDATE: The assert() below is not necessarily true if the database - ** file is corrupt. The corruption will be detected and reported later - ** in this procedure so there is no need to act upon it now. + /* Sanity check: For a non-corrupt database file one of the follwing + ** must be true: + ** (1) We found one or more cells (cntNew[0])>0), or + ** (2) pPage is a virtual root page. A virtual root page is when + ** the real root page is page 1 and we are the only child of + ** that page. */ -#if 0 - assert( cntNew[0]>0 || (pParent->pgno==1 && pParent->nCell==0) ); -#endif - - TRACE(("BALANCE: old: %d %d %d ", - apOld[0]->pgno, - nOld>=2 ? apOld[1]->pgno : 0, - nOld>=3 ? apOld[2]->pgno : 0 + assert( cntNew[0]>0 || (pParent->pgno==1 && pParent->nCell==0) || CORRUPT_DB); + TRACE(("BALANCE: old: %d(nc=%d) %d(nc=%d) %d(nc=%d)\n", + apOld[0]->pgno, apOld[0]->nCell, + nOld>=2 ? apOld[1]->pgno : 0, nOld>=2 ? apOld[1]->nCell : 0, + nOld>=3 ? apOld[2]->pgno : 0, nOld>=3 ? apOld[2]->nCell : 0 )); /* ** Allocate k new pages. Reuse old pages where possible. */ @@ -56313,12 +59929,14 @@ if( rc ) goto balance_cleanup; }else{ assert( i>0 ); rc = allocateBtreePage(pBt, &pNew, &pgno, (bBulk ? 1 : pgno), 0); if( rc ) goto balance_cleanup; + zeroPage(pNew, pageFlags); apNew[i] = pNew; nNew++; + cntOld[i] = nCell; /* Set the pointer-map entry for the new sibling page. */ if( ISAUTOVACUUM ){ ptrmapPut(pBt, pNew->pgno, PTRMAP_BTREE, pParent->pgno, &rc); if( rc!=SQLITE_OK ){ @@ -56326,139 +59944,251 @@ } } } } - /* Free any old pages that were not reused as new pages. - */ - while( i<nOld ){ - freePage(apOld[i], &rc); - if( rc ) goto balance_cleanup; - releasePage(apOld[i]); - apOld[i] = 0; - i++; - } - /* - ** Put the new pages in accending order. This helps to - ** keep entries in the disk file in order so that a scan - ** of the table is a linear scan through the file. That - ** in turn helps the operating system to deliver pages - ** from the disk more rapidly. - ** - ** An O(n^2) insertion sort algorithm is used, but since - ** n is never more than NB (a small constant), that should - ** not be a problem. - ** - ** When NB==3, this one optimization makes the database - ** about 25% faster for large insertions and deletions. - */ - for(i=0; i<k-1; i++){ - int minV = apNew[i]->pgno; - int minI = i; - for(j=i+1; j<k; j++){ - if( apNew[j]->pgno<(unsigned)minV ){ - minI = j; - minV = apNew[j]->pgno; - } - } - if( minI>i ){ - MemPage *pT; - pT = apNew[i]; - apNew[i] = apNew[minI]; - apNew[minI] = pT; - } - } - TRACE(("new: %d(%d) %d(%d) %d(%d) %d(%d) %d(%d)\n", - apNew[0]->pgno, szNew[0], + ** Reassign page numbers so that the new pages are in ascending order. + ** This helps to keep entries in the disk file in order so that a scan + ** of the table is closer to a linear scan through the file. That in turn + ** helps the operating system to deliver pages from the disk more rapidly. + ** + ** An O(n^2) insertion sort algorithm is used, but since n is never more + ** than (NB+2) (a small constant), that should not be a problem. + ** + ** When NB==3, this one optimization makes the database about 25% faster + ** for large insertions and deletions. + */ + for(i=0; i<nNew; i++){ + aPgOrder[i] = aPgno[i] = apNew[i]->pgno; + aPgFlags[i] = apNew[i]->pDbPage->flags; + for(j=0; j<i; j++){ + if( aPgno[j]==aPgno[i] ){ + /* This branch is taken if the set of sibling pages somehow contains + ** duplicate entries. This can happen if the database is corrupt. + ** It would be simpler to detect this as part of the loop below, but + ** we do the detection here in order to avoid populating the pager + ** cache with two separate objects associated with the same + ** page number. */ + assert( CORRUPT_DB ); + rc = SQLITE_CORRUPT_BKPT; + goto balance_cleanup; + } + } + } + for(i=0; i<nNew; i++){ + int iBest = 0; /* aPgno[] index of page number to use */ + for(j=1; j<nNew; j++){ + if( aPgOrder[j]<aPgOrder[iBest] ) iBest = j; + } + pgno = aPgOrder[iBest]; + aPgOrder[iBest] = 0xffffffff; + if( iBest!=i ){ + if( iBest>i ){ + sqlite3PagerRekey(apNew[iBest]->pDbPage, pBt->nPage+iBest+1, 0); + } + sqlite3PagerRekey(apNew[i]->pDbPage, pgno, aPgFlags[iBest]); + apNew[i]->pgno = pgno; + } + } + + TRACE(("BALANCE: new: %d(%d nc=%d) %d(%d nc=%d) %d(%d nc=%d) " + "%d(%d nc=%d) %d(%d nc=%d)\n", + apNew[0]->pgno, szNew[0], cntNew[0], nNew>=2 ? apNew[1]->pgno : 0, nNew>=2 ? szNew[1] : 0, + nNew>=2 ? cntNew[1] - cntNew[0] - !leafData : 0, nNew>=3 ? apNew[2]->pgno : 0, nNew>=3 ? szNew[2] : 0, + nNew>=3 ? cntNew[2] - cntNew[1] - !leafData : 0, nNew>=4 ? apNew[3]->pgno : 0, nNew>=4 ? szNew[3] : 0, - nNew>=5 ? apNew[4]->pgno : 0, nNew>=5 ? szNew[4] : 0)); + nNew>=4 ? cntNew[3] - cntNew[2] - !leafData : 0, + nNew>=5 ? apNew[4]->pgno : 0, nNew>=5 ? szNew[4] : 0, + nNew>=5 ? cntNew[4] - cntNew[3] - !leafData : 0 + )); assert( sqlite3PagerIswriteable(pParent->pDbPage) ); put4byte(pRight, apNew[nNew-1]->pgno); - /* - ** Evenly distribute the data in apCell[] across the new pages. - ** Insert divider cells into pParent as necessary. + /* If the sibling pages are not leaves, ensure that the right-child pointer + ** of the right-most new sibling page is set to the value that was + ** originally in the same field of the right-most old sibling page. */ + if( (pageFlags & PTF_LEAF)==0 && nOld!=nNew ){ + MemPage *pOld = (nNew>nOld ? apNew : apOld)[nOld-1]; + memcpy(&apNew[nNew-1]->aData[8], &pOld->aData[8], 4); + } + + /* Make any required updates to pointer map entries associated with + ** cells stored on sibling pages following the balance operation. Pointer + ** map entries associated with divider cells are set by the insertCell() + ** routine. The associated pointer map entries are: + ** + ** a) if the cell contains a reference to an overflow chain, the + ** entry associated with the first page in the overflow chain, and + ** + ** b) if the sibling pages are not leaves, the child page associated + ** with the cell. + ** + ** If the sibling pages are not leaves, then the pointer map entry + ** associated with the right-child of each sibling may also need to be + ** updated. This happens below, after the sibling pages have been + ** populated, not here. */ - j = 0; - for(i=0; i<nNew; i++){ - /* Assemble the new sibling page. */ + if( ISAUTOVACUUM ){ + MemPage *pNew = apNew[0]; + u8 *aOld = pNew->aData; + int cntOldNext = pNew->nCell + pNew->nOverflow; + int usableSize = pBt->usableSize; + int iNew = 0; + int iOld = 0; + + for(i=0; i<nCell; i++){ + u8 *pCell = apCell[i]; + if( i==cntOldNext ){ + MemPage *pOld = (++iOld)<nNew ? apNew[iOld] : apOld[iOld]; + cntOldNext += pOld->nCell + pOld->nOverflow + !leafData; + aOld = pOld->aData; + } + if( i==cntNew[iNew] ){ + pNew = apNew[++iNew]; + if( !leafData ) continue; + } + + /* Cell pCell is destined for new sibling page pNew. Originally, it + ** was either part of sibling page iOld (possibly an overflow cell), + ** or else the divider cell to the left of sibling page iOld. So, + ** if sibling page iOld had the same page number as pNew, and if + ** pCell really was a part of sibling page iOld (not a divider or + ** overflow cell), we can skip updating the pointer map entries. */ + if( iOld>=nNew + || pNew->pgno!=aPgno[iOld] + || pCell<aOld + || pCell>=&aOld[usableSize] + ){ + if( !leafCorrection ){ + ptrmapPut(pBt, get4byte(pCell), PTRMAP_BTREE, pNew->pgno, &rc); + } + if( szCell[i]>pNew->minLocal ){ + ptrmapPutOvflPtr(pNew, pCell, &rc); + } + } + } + } + + /* Insert new divider cells into pParent. */ + for(i=0; i<nNew-1; i++){ + u8 *pCell; + u8 *pTemp; + int sz; MemPage *pNew = apNew[i]; - assert( j<nMaxCells ); - zeroPage(pNew, pageFlags); - assemblePage(pNew, cntNew[i]-j, &apCell[j], &szCell[j]); - assert( pNew->nCell>0 || (nNew==1 && cntNew[0]==0) ); - assert( pNew->nOverflow==0 ); - j = cntNew[i]; - /* If the sibling page assembled above was not the right-most sibling, - ** insert a divider cell into the parent page. - */ - assert( i<nNew-1 || j==nCell ); - if( j<nCell ){ - u8 *pCell; - u8 *pTemp; - int sz; - - assert( j<nMaxCells ); - pCell = apCell[j]; - sz = szCell[j] + leafCorrection; - pTemp = &aOvflSpace[iOvflSpace]; - if( !pNew->leaf ){ - memcpy(&pNew->aData[8], pCell, 4); - }else if( leafData ){ - /* If the tree is a leaf-data tree, and the siblings are leaves, - ** then there is no divider cell in apCell[]. Instead, the divider - ** cell consists of the integer key for the right-most cell of - ** the sibling-page assembled above only. - */ - CellInfo info; - j--; - btreeParseCellPtr(pNew, apCell[j], &info); - pCell = pTemp; - sz = 4 + putVarint(&pCell[4], info.nKey); - pTemp = 0; - }else{ - pCell -= 4; - /* Obscure case for non-leaf-data trees: If the cell at pCell was - ** previously stored on a leaf node, and its reported size was 4 - ** bytes, then it may actually be smaller than this - ** (see btreeParseCellPtr(), 4 bytes is the minimum size of - ** any cell). But it is important to pass the correct size to - ** insertCell(), so reparse the cell now. - ** - ** Note that this can never happen in an SQLite data file, as all - ** cells are at least 4 bytes. It only happens in b-trees used - ** to evaluate "IN (SELECT ...)" and similar clauses. - */ - if( szCell[j]==4 ){ - assert(leafCorrection==4); - sz = cellSizePtr(pParent, pCell); - } - } - iOvflSpace += sz; - assert( sz<=pBt->maxLocal+23 ); - assert( iOvflSpace <= (int)pBt->pageSize ); - insertCell(pParent, nxDiv, pCell, sz, pTemp, pNew->pgno, &rc); - if( rc!=SQLITE_OK ) goto balance_cleanup; - assert( sqlite3PagerIswriteable(pParent->pDbPage) ); - - j++; - nxDiv++; - } - } - assert( j==nCell ); + assert( j<nMaxCells ); + pCell = apCell[j]; + sz = szCell[j] + leafCorrection; + pTemp = &aOvflSpace[iOvflSpace]; + if( !pNew->leaf ){ + memcpy(&pNew->aData[8], pCell, 4); + }else if( leafData ){ + /* If the tree is a leaf-data tree, and the siblings are leaves, + ** then there is no divider cell in apCell[]. Instead, the divider + ** cell consists of the integer key for the right-most cell of + ** the sibling-page assembled above only. + */ + CellInfo info; + j--; + btreeParseCellPtr(pNew, apCell[j], &info); + pCell = pTemp; + sz = 4 + putVarint(&pCell[4], info.nKey); + pTemp = 0; + }else{ + pCell -= 4; + /* Obscure case for non-leaf-data trees: If the cell at pCell was + ** previously stored on a leaf node, and its reported size was 4 + ** bytes, then it may actually be smaller than this + ** (see btreeParseCellPtr(), 4 bytes is the minimum size of + ** any cell). But it is important to pass the correct size to + ** insertCell(), so reparse the cell now. + ** + ** Note that this can never happen in an SQLite data file, as all + ** cells are at least 4 bytes. It only happens in b-trees used + ** to evaluate "IN (SELECT ...)" and similar clauses. + */ + if( szCell[j]==4 ){ + assert(leafCorrection==4); + sz = cellSizePtr(pParent, pCell); + } + } + iOvflSpace += sz; + assert( sz<=pBt->maxLocal+23 ); + assert( iOvflSpace <= (int)pBt->pageSize ); + insertCell(pParent, nxDiv+i, pCell, sz, pTemp, pNew->pgno, &rc); + if( rc!=SQLITE_OK ) goto balance_cleanup; + assert( sqlite3PagerIswriteable(pParent->pDbPage) ); + } + + /* Now update the actual sibling pages. The order in which they are updated + ** is important, as this code needs to avoid disrupting any page from which + ** cells may still to be read. In practice, this means: + ** + ** (1) If cells are moving left (from apNew[iPg] to apNew[iPg-1]) + ** then it is not safe to update page apNew[iPg] until after + ** the left-hand sibling apNew[iPg-1] has been updated. + ** + ** (2) If cells are moving right (from apNew[iPg] to apNew[iPg+1]) + ** then it is not safe to update page apNew[iPg] until after + ** the right-hand sibling apNew[iPg+1] has been updated. + ** + ** If neither of the above apply, the page is safe to update. + ** + ** The iPg value in the following loop starts at nNew-1 goes down + ** to 0, then back up to nNew-1 again, thus making two passes over + ** the pages. On the initial downward pass, only condition (1) above + ** needs to be tested because (2) will always be true from the previous + ** step. On the upward pass, both conditions are always true, so the + ** upwards pass simply processes pages that were missed on the downward + ** pass. + */ + for(i=1-nNew; i<nNew; i++){ + int iPg = i<0 ? -i : i; + assert( iPg>=0 && iPg<nNew ); + if( abDone[iPg] ) continue; /* Skip pages already processed */ + if( i>=0 /* On the upwards pass, or... */ + || cntOld[iPg-1]>=cntNew[iPg-1] /* Condition (1) is true */ + ){ + int iNew; + int iOld; + int nNewCell; + + /* Verify condition (1): If cells are moving left, update iPg + ** only after iPg-1 has already been updated. */ + assert( iPg==0 || cntOld[iPg-1]>=cntNew[iPg-1] || abDone[iPg-1] ); + + /* Verify condition (2): If cells are moving right, update iPg + ** only after iPg+1 has already been updated. */ + assert( cntNew[iPg]>=cntOld[iPg] || abDone[iPg+1] ); + + if( iPg==0 ){ + iNew = iOld = 0; + nNewCell = cntNew[0]; + }else{ + iOld = iPg<nOld ? (cntOld[iPg-1] + !leafData) : nCell; + iNew = cntNew[iPg-1] + !leafData; + nNewCell = cntNew[iPg] - iNew; + } + + editPage(apNew[iPg], iOld, iNew, nNewCell, apCell, szCell); + abDone[iPg]++; + apNew[iPg]->nFree = usableSpace-szNew[iPg]; + assert( apNew[iPg]->nOverflow==0 ); + assert( apNew[iPg]->nCell==nNewCell ); + } + } + + /* All pages have been processed exactly once */ + assert( memcmp(abDone, "\01\01\01\01\01", nNew)==0 ); + assert( nOld>0 ); assert( nNew>0 ); - if( (pageFlags & PTF_LEAF)==0 ){ - u8 *zChild = &apCopy[nOld-1]->aData[8]; - memcpy(&apNew[nNew-1]->aData[8], zChild, 4); - } if( isRoot && pParent->nCell==0 && pParent->hdrOffset<=apNew[0]->nFree ){ /* The root page of the b-tree now contains no cells. The only sibling ** page is the right-child of the parent. Copy the contents of the ** child page into the parent, decreasing the overall height of the @@ -56467,130 +60197,54 @@ ** ** If this is an auto-vacuum database, the call to copyNodeContent() ** sets all pointer-map entries corresponding to database image pages ** for which the pointer is stored within the content being copied. ** - ** The second assert below verifies that the child page is defragmented - ** (it must be, as it was just reconstructed using assemblePage()). This - ** is important if the parent page happens to be page 1 of the database - ** image. */ + ** It is critical that the child page be defragmented before being + ** copied into the parent, because if the parent is page 1 then it will + ** by smaller than the child due to the database header, and so all the + ** free space needs to be up front. + */ assert( nNew==1 ); + rc = defragmentPage(apNew[0]); + testcase( rc!=SQLITE_OK ); assert( apNew[0]->nFree == - (get2byte(&apNew[0]->aData[5])-apNew[0]->cellOffset-apNew[0]->nCell*2) + (get2byte(&apNew[0]->aData[5])-apNew[0]->cellOffset-apNew[0]->nCell*2) + || rc!=SQLITE_OK ); copyNodeContent(apNew[0], pParent, &rc); freePage(apNew[0], &rc); - }else if( ISAUTOVACUUM ){ - /* Fix the pointer-map entries for all the cells that were shifted around. - ** There are several different types of pointer-map entries that need to - ** be dealt with by this routine. Some of these have been set already, but - ** many have not. The following is a summary: - ** - ** 1) The entries associated with new sibling pages that were not - ** siblings when this function was called. These have already - ** been set. We don't need to worry about old siblings that were - ** moved to the free-list - the freePage() code has taken care - ** of those. - ** - ** 2) The pointer-map entries associated with the first overflow - ** page in any overflow chains used by new divider cells. These - ** have also already been taken care of by the insertCell() code. - ** - ** 3) If the sibling pages are not leaves, then the child pages of - ** cells stored on the sibling pages may need to be updated. - ** - ** 4) If the sibling pages are not internal intkey nodes, then any - ** overflow pages used by these cells may need to be updated - ** (internal intkey nodes never contain pointers to overflow pages). - ** - ** 5) If the sibling pages are not leaves, then the pointer-map - ** entries for the right-child pages of each sibling may need - ** to be updated. - ** - ** Cases 1 and 2 are dealt with above by other code. The next - ** block deals with cases 3 and 4 and the one after that, case 5. Since - ** setting a pointer map entry is a relatively expensive operation, this - ** code only sets pointer map entries for child or overflow pages that have - ** actually moved between pages. */ - MemPage *pNew = apNew[0]; - MemPage *pOld = apCopy[0]; - int nOverflow = pOld->nOverflow; - int iNextOld = pOld->nCell + nOverflow; - int iOverflow = (nOverflow ? pOld->aiOvfl[0] : -1); - j = 0; /* Current 'old' sibling page */ - k = 0; /* Current 'new' sibling page */ - for(i=0; i<nCell; i++){ - int isDivider = 0; - while( i==iNextOld ){ - /* Cell i is the cell immediately following the last cell on old - ** sibling page j. If the siblings are not leaf pages of an - ** intkey b-tree, then cell i was a divider cell. */ - assert( j+1 < ArraySize(apCopy) ); - assert( j+1 < nOld ); - pOld = apCopy[++j]; - iNextOld = i + !leafData + pOld->nCell + pOld->nOverflow; - if( pOld->nOverflow ){ - nOverflow = pOld->nOverflow; - iOverflow = i + !leafData + pOld->aiOvfl[0]; - } - isDivider = !leafData; - } - - assert(nOverflow>0 || iOverflow<i ); - assert(nOverflow<2 || pOld->aiOvfl[0]==pOld->aiOvfl[1]-1); - assert(nOverflow<3 || pOld->aiOvfl[1]==pOld->aiOvfl[2]-1); - if( i==iOverflow ){ - isDivider = 1; - if( (--nOverflow)>0 ){ - iOverflow++; - } - } - - if( i==cntNew[k] ){ - /* Cell i is the cell immediately following the last cell on new - ** sibling page k. If the siblings are not leaf pages of an - ** intkey b-tree, then cell i is a divider cell. */ - pNew = apNew[++k]; - if( !leafData ) continue; - } - assert( j<nOld ); - assert( k<nNew ); - - /* If the cell was originally divider cell (and is not now) or - ** an overflow cell, or if the cell was located on a different sibling - ** page before the balancing, then the pointer map entries associated - ** with any child or overflow pages need to be updated. */ - if( isDivider || pOld->pgno!=pNew->pgno ){ - if( !leafCorrection ){ - ptrmapPut(pBt, get4byte(apCell[i]), PTRMAP_BTREE, pNew->pgno, &rc); - } - if( szCell[i]>pNew->minLocal ){ - ptrmapPutOvflPtr(pNew, apCell[i], &rc); - } - } - } - - if( !leafCorrection ){ - for(i=0; i<nNew; i++){ - u32 key = get4byte(&apNew[i]->aData[8]); - ptrmapPut(pBt, key, PTRMAP_BTREE, apNew[i]->pgno, &rc); - } - } + }else if( ISAUTOVACUUM && !leafCorrection ){ + /* Fix the pointer map entries associated with the right-child of each + ** sibling page. All other pointer map entries have already been taken + ** care of. */ + for(i=0; i<nNew; i++){ + u32 key = get4byte(&apNew[i]->aData[8]); + ptrmapPut(pBt, key, PTRMAP_BTREE, apNew[i]->pgno, &rc); + } + } + + assert( pParent->isInit ); + TRACE(("BALANCE: finished: old=%d new=%d cells=%d\n", + nOld, nNew, nCell)); + + /* Free any old pages that were not reused as new pages. + */ + for(i=nNew; i<nOld; i++){ + freePage(apOld[i], &rc); + } #if 0 + if( ISAUTOVACUUM && rc==SQLITE_OK && apNew[0]->isInit ){ /* The ptrmapCheckPages() contains assert() statements that verify that ** all pointer map pages are set correctly. This is helpful while ** debugging. This is usually disabled because a corrupt database may ** cause an assert() statement to fail. */ ptrmapCheckPages(apNew, nNew); ptrmapCheckPages(&pParent, 1); + } #endif - } - - assert( pParent->isInit ); - TRACE(("BALANCE: finished: old=%d new=%d cells=%d\n", - nOld, nNew, nCell)); /* ** Cleanup before returning. */ balance_cleanup: @@ -56723,20 +60377,20 @@ int const iIdx = pCur->aiIdx[iPage-1]; rc = sqlite3PagerWrite(pParent->pDbPage); if( rc==SQLITE_OK ){ #ifndef SQLITE_OMIT_QUICKBALANCE - if( pPage->hasData + if( pPage->intKeyLeaf && pPage->nOverflow==1 && pPage->aiOvfl[0]==pPage->nCell && pParent->pgno!=1 && pParent->nCell==iIdx ){ /* Call balance_quick() to create a new sibling of pPage on which ** to store the overflow cell. balance_quick() inserts a new cell ** into pParent, which may cause pParent overflow. If this - ** happens, the next interation of the do-loop will balance pParent + ** happens, the next iteration of the do-loop will balance pParent ** use either balance_nonroot() or balance_deeper(). Until this ** happens, the overflow cell is stored in the aBalanceQuickSpace[] ** buffer. ** ** The purpose of the following assert() is to check that only a @@ -56765,11 +60419,12 @@ ** the previous call, as the overflow cell data will have been ** copied either into the body of a database page or into the new ** pSpace buffer passed to the latter call to balance_nonroot(). */ u8 *pSpace = sqlite3PageMalloc(pCur->pBt->pageSize); - rc = balance_nonroot(pParent, iIdx, pSpace, iPage==1, pCur->hints); + rc = balance_nonroot(pParent, iIdx, pSpace, iPage==1, + pCur->hints&BTREE_BULKLOAD); if( pFree ){ /* If pFree is not NULL, it points to the pSpace buffer used ** by a previous call to balance_nonroot(). Its contents are ** now stored either on real database pages or within the ** new pSpace buffer, so it may be safely freed here. */ @@ -56786,10 +60441,11 @@ pPage->nOverflow = 0; /* The next iteration of the do-loop balances the parent page. */ releasePage(pPage); pCur->iPage--; + assert( pCur->iPage>=0 ); } }while( rc==SQLITE_OK ); if( pFree ){ sqlite3PageFree(pFree); @@ -56809,11 +60465,11 @@ ** ** If the seekResult parameter is non-zero, then a successful call to ** MovetoUnpacked() to seek cursor pCur to (pKey, nKey) has already ** been performed. seekResult is the search result returned (a negative ** number if pCur points at an entry that is smaller than (pKey, nKey), or -** a positive value if pCur points at an etry that is larger than +** a positive value if pCur points at an entry that is larger than ** (pKey, nKey)). ** ** If the seekResult parameter is non-zero, then the caller guarantees that ** cursor pCur is pointing at the existing copy of a row that is to be ** overwritten. If the seekResult parameter is 0, then cursor pCur may @@ -56842,11 +60498,12 @@ assert( pCur->skipNext!=SQLITE_OK ); return pCur->skipNext; } assert( cursorHoldsMutex(pCur) ); - assert( pCur->wrFlag && pBt->inTransaction==TRANS_WRITE + assert( (pCur->curFlags & BTCF_WriteFlag)!=0 + && pBt->inTransaction==TRANS_WRITE && (pBt->btsFlags & BTS_READ_ONLY)==0 ); assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); /* Assert that the caller has been consistent. If this cursor was opened ** expecting an index b-tree, then the caller should be inserting blob @@ -56867,15 +60524,22 @@ ** not to clear the cursor here. */ rc = saveAllCursors(pBt, pCur->pgnoRoot, pCur); if( rc ) return rc; - /* If this is an insert into a table b-tree, invalidate any incrblob - ** cursors open on the row being replaced (assuming this is a replace - ** operation - if it is not, the following is a no-op). */ if( pCur->pKeyInfo==0 ){ + /* If this is an insert into a table b-tree, invalidate any incrblob + ** cursors open on the row being replaced */ invalidateIncrblobCursors(p, nKey, 0); + + /* If the cursor is currently on the last row and we are appending a + ** new row onto the end, set the "loc" to avoid an unnecessary btreeMoveto() + ** call */ + if( (pCur->curFlags&BTCF_ValidNKey)!=0 && nKey>0 + && pCur->info.nKey==nKey-1 ){ + loc = -1; + } } if( !loc ){ rc = btreeMoveto(pCur, pKey, nKey, appendBias, &loc); if( rc ) return rc; @@ -56888,13 +60552,12 @@ TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n", pCur->pgnoRoot, nKey, nData, pPage->pgno, loc==0 ? "overwrite" : "new entry")); assert( pPage->isInit ); - allocateTempSpace(pBt); newCell = pBt->pTmpSpace; - if( newCell==0 ) return SQLITE_NOMEM; + assert( newCell!=0 ); rc = fillInCell(pPage, newCell, pKey, nKey, pData, nData, nZero, &szNew); if( rc ) goto end_insert; assert( szNew==cellSizePtr(pPage, newCell) ); assert( szNew <= MX_CELL_SIZE(pBt) ); idx = pCur->aiIdx[pCur->iPage]; @@ -56907,12 +60570,11 @@ } oldCell = findCell(pPage, idx); if( !pPage->leaf ){ memcpy(newCell, oldCell, 4); } - szOld = cellSizePtr(pPage, oldCell); - rc = clearCell(pPage, oldCell); + rc = clearCell(pPage, oldCell, &szOld); dropCell(pPage, idx, szOld, &rc); if( rc ) goto end_insert; }else if( loc<0 && pPage->nCell>0 ){ assert( pPage->leaf ); idx = ++pCur->aiIdx[pCur->iPage]; @@ -56922,11 +60584,11 @@ insertCell(pPage, idx, newCell, szNew, 0, 0, &rc); assert( rc!=SQLITE_OK || pPage->nCell>0 || pPage->nOverflow>0 ); /* If no error has occurred and pPage has an overflow cell, call balance() ** to redistribute the cells within the tree. Since balance() may move - ** the cursor, zero the BtCursor.info.nSize and BtCursor.validNKey + ** the cursor, zero the BtCursor.info.nSize and BTCF_ValidNKey ** variables. ** ** Previous versions of SQLite called moveToRoot() to move the cursor ** back to the root page as balance() used to invalidate the contents ** of BtCursor.apPage[] and BtCursor.aiIdx[]. Instead of doing that, @@ -56941,12 +60603,12 @@ ** entry in the table, and the next row inserted has an integer key ** larger than the largest existing key, it is possible to insert the ** row without seeking the cursor. This can be a big performance boost. */ pCur->info.nSize = 0; - pCur->validNKey = 0; if( rc==SQLITE_OK && pPage->nOverflow ){ + pCur->curFlags &= ~(BTCF_ValidNKey); rc = balance(pCur); /* Must make sure nOverflow is reset to zero even if the balance() ** fails. Internal data structure corruption will result otherwise. ** Also, set the cursor state to invalid. This stops saveCursorPosition() @@ -56960,25 +60622,26 @@ return rc; } /* ** Delete the entry that the cursor is pointing to. The cursor -** is left pointing at a arbitrary location. +** is left pointing at an arbitrary location. */ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur){ Btree *p = pCur->pBtree; BtShared *pBt = p->pBt; int rc; /* Return code */ MemPage *pPage; /* Page to delete cell from */ unsigned char *pCell; /* Pointer to cell to delete */ int iCellIdx; /* Index of cell to delete */ int iCellDepth; /* Depth of node containing pCell */ + u16 szCell; /* Size of the cell being deleted */ assert( cursorHoldsMutex(pCur) ); assert( pBt->inTransaction==TRANS_WRITE ); assert( (pBt->btsFlags & BTS_READ_ONLY)==0 ); - assert( pCur->wrFlag ); + assert( pCur->curFlags & BTCF_WriteFlag ); assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); assert( !hasReadConflicts(p, pCur->pgnoRoot) ); if( NEVER(pCur->aiIdx[pCur->iPage]>=pCur->apPage[pCur->iPage]->nCell) || NEVER(pCur->eState!=CURSOR_VALID) @@ -56997,11 +60660,11 @@ ** from the internal node. The 'previous' entry is used for this instead ** of the 'next' entry, as the previous entry is always a part of the ** sub-tree headed by the child page of the cell being deleted. This makes ** balancing the tree following the delete operation easier. */ if( !pPage->leaf ){ - int notUsed; + int notUsed = 0; rc = sqlite3BtreePrevious(pCur, ¬Used); if( rc ) return rc; } /* Save the positions of any other cursors open on this table before @@ -57018,12 +60681,12 @@ invalidateIncrblobCursors(p, pCur->info.nKey, 0); } rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ) return rc; - rc = clearCell(pPage, pCell); - dropCell(pPage, iCellIdx, cellSizePtr(pPage, pCell), &rc); + rc = clearCell(pPage, pCell, &szCell); + dropCell(pPage, iCellIdx, szCell, &rc); if( rc ) return rc; /* If the cell deleted was not located on a leaf page, then the cursor ** is currently pointing to the largest entry in the sub-tree headed ** by the child-page of the cell that was just deleted from an internal @@ -57036,14 +60699,12 @@ unsigned char *pTmp; pCell = findCell(pLeaf, pLeaf->nCell-1); nCell = cellSizePtr(pLeaf, pCell); assert( MX_CELL_SIZE(pBt) >= nCell ); - - allocateTempSpace(pBt); pTmp = pBt->pTmpSpace; - + assert( pTmp!=0 ); rc = sqlite3PagerWrite(pLeaf->pDbPage); insertCell(pPage, iCellIdx, pCell-4, nCell+4, pTmp, n, &rc); dropCell(pLeaf, pLeaf->nCell-1, nCell, &rc); if( rc ) return rc; } @@ -57250,38 +60911,44 @@ ){ MemPage *pPage; int rc; unsigned char *pCell; int i; + int hdr; + u16 szCell; + u8 hasChildren; assert( sqlite3_mutex_held(pBt->mutex) ); if( pgno>btreePagecount(pBt) ){ return SQLITE_CORRUPT_BKPT; } rc = getAndInitPage(pBt, pgno, &pPage, 0); if( rc ) return rc; + hasChildren = !pPage->leaf; + pPage->leaf = 1; /* Block looping if the database is corrupt */ + hdr = pPage->hdrOffset; for(i=0; i<pPage->nCell; i++){ pCell = findCell(pPage, i); - if( !pPage->leaf ){ + if( hasChildren ){ rc = clearDatabasePage(pBt, get4byte(pCell), 1, pnChange); if( rc ) goto cleardatabasepage_out; } - rc = clearCell(pPage, pCell); + rc = clearCell(pPage, pCell, &szCell); if( rc ) goto cleardatabasepage_out; } - if( !pPage->leaf ){ - rc = clearDatabasePage(pBt, get4byte(&pPage->aData[8]), 1, pnChange); + if( hasChildren ){ + rc = clearDatabasePage(pBt, get4byte(&pPage->aData[hdr+8]), 1, pnChange); if( rc ) goto cleardatabasepage_out; }else if( pnChange ){ assert( pPage->intKey ); *pnChange += pPage->nCell; } if( freePageFlag ){ freePage(pPage, &rc); }else if( (rc = sqlite3PagerWrite(pPage->pDbPage))==0 ){ - zeroPage(pPage, pPage->aData[0] | PTF_LEAF); + zeroPage(pPage, pPage->aData[hdr] | PTF_LEAF); } cleardatabasepage_out: releasePage(pPage); return rc; @@ -57316,10 +60983,19 @@ rc = clearDatabasePage(pBt, (Pgno)iTable, 0, pnChange); } sqlite3BtreeLeave(p); return rc; } + +/* +** Delete all information from the single table that pCur is open on. +** +** This routine only work for pCur on an ephemeral table. +*/ +SQLITE_PRIVATE int sqlite3BtreeClearTableOfCursor(BtCursor *pCur){ + return sqlite3BtreeClearTable(pCur->pBtree, pCur->pgnoRoot, 0); +} /* ** Erase all information in a table and add the root of the table to ** the freelist. Except, the root of the principle table (the one on ** page 1) is never added to the freelist. @@ -57461,10 +61137,17 @@ ** is read-only, the others are read/write. ** ** The schema layer numbers meta values differently. At the schema ** layer (and the SetCookie and ReadCookie opcodes) the number of ** free pages is not visible. So Cookie[0] is the same as Meta[1]. +** +** This routine treats Meta[BTREE_DATA_VERSION] as a special case. Instead +** of reading the value out of the header, it instead loads the "DataVersion" +** from the pager. The BTREE_DATA_VERSION value is not actually stored in the +** database file. It is a number computed by the pager. But its access +** pattern is the same as header meta values, and so it is convenient to +** read it from this routine. */ SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){ BtShared *pBt = p->pBt; sqlite3BtreeEnter(p); @@ -57471,11 +61154,15 @@ assert( p->inTrans>TRANS_NONE ); assert( SQLITE_OK==querySharedCacheTableLock(p, MASTER_ROOT, READ_LOCK) ); assert( pBt->pPage1 ); assert( idx>=0 && idx<=15 ); - *pMeta = get4byte(&pBt->pPage1->aData[36 + idx*4]); + if( idx==BTREE_DATA_VERSION ){ + *pMeta = sqlite3PagerDataVersion(pBt->pPager) + p->iDataVersion; + }else{ + *pMeta = get4byte(&pBt->pPage1->aData[36 + idx*4]); + } /* If auto-vacuum is disabled in this build and this is an auto-vacuum ** database, mark the database as read-only. */ #ifdef SQLITE_OMIT_AUTOVACUUM if( idx==BTREE_LARGEST_ROOT_PAGE && *pMeta>0 ){ @@ -57562,11 +61249,11 @@ if( pPage->leaf ){ do { if( pCur->iPage==0 ){ /* All pages of the b-tree have been visited. Return successfully. */ *pnEntry = nEntry; - return SQLITE_OK; + return moveToRoot(pCur); } moveToParent(pCur); }while ( pCur->aiIdx[pCur->iPage]>=pCur->apPage[pCur->iPage]->nCell ); pCur->aiIdx[pCur->iPage]++; @@ -57601,24 +61288,25 @@ /* ** Append a message to the error message string. */ static void checkAppendMsg( IntegrityCk *pCheck, - char *zMsg1, const char *zFormat, ... ){ va_list ap; + char zBuf[200]; if( !pCheck->mxErr ) return; pCheck->mxErr--; pCheck->nErr++; va_start(ap, zFormat); if( pCheck->errMsg.nChar ){ sqlite3StrAccumAppend(&pCheck->errMsg, "\n", 1); } - if( zMsg1 ){ - sqlite3StrAccumAppend(&pCheck->errMsg, zMsg1, -1); + if( pCheck->zPfx ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, pCheck->zPfx, pCheck->v1, pCheck->v2); + sqlite3StrAccumAppendAll(&pCheck->errMsg, zBuf); } sqlite3VXPrintf(&pCheck->errMsg, 1, zFormat, ap); va_end(ap); if( pCheck->errMsg.accError==STRACCUM_NOMEM ){ pCheck->mallocFailed = 1; @@ -57647,23 +61335,23 @@ /* ** Add 1 to the reference count for page iPage. If this is the second ** reference to the page, add an error message to pCheck->zErrMsg. -** Return 1 if there are 2 ore more references to the page and 0 if +** Return 1 if there are 2 or more references to the page and 0 if ** if this is the first reference to the page. ** ** Also check that the page number is in bounds. */ -static int checkRef(IntegrityCk *pCheck, Pgno iPage, char *zContext){ +static int checkRef(IntegrityCk *pCheck, Pgno iPage){ if( iPage==0 ) return 1; if( iPage>pCheck->nPage ){ - checkAppendMsg(pCheck, zContext, "invalid page number %d", iPage); + checkAppendMsg(pCheck, "invalid page number %d", iPage); return 1; } if( getPageReferenced(pCheck, iPage) ){ - checkAppendMsg(pCheck, zContext, "2nd reference to page %d", iPage); + checkAppendMsg(pCheck, "2nd reference to page %d", iPage); return 1; } setPageReferenced(pCheck, iPage); return 0; } @@ -57676,26 +61364,25 @@ */ static void checkPtrmap( IntegrityCk *pCheck, /* Integrity check context */ Pgno iChild, /* Child page number */ u8 eType, /* Expected pointer map type */ - Pgno iParent, /* Expected pointer map parent page number */ - char *zContext /* Context description (used for error msg) */ + Pgno iParent /* Expected pointer map parent page number */ ){ int rc; u8 ePtrmapType; Pgno iPtrmapParent; rc = ptrmapGet(pCheck->pBt, iChild, &ePtrmapType, &iPtrmapParent); if( rc!=SQLITE_OK ){ if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ) pCheck->mallocFailed = 1; - checkAppendMsg(pCheck, zContext, "Failed to read ptrmap key=%d", iChild); + checkAppendMsg(pCheck, "Failed to read ptrmap key=%d", iChild); return; } if( ePtrmapType!=eType || iPtrmapParent!=iParent ){ - checkAppendMsg(pCheck, zContext, + checkAppendMsg(pCheck, "Bad ptr map entry key=%d expected=(%d,%d) got=(%d,%d)", iChild, eType, iParent, ePtrmapType, iPtrmapParent); } } #endif @@ -57706,51 +61393,50 @@ */ static void checkList( IntegrityCk *pCheck, /* Integrity checking context */ int isFreeList, /* True for a freelist. False for overflow page list */ int iPage, /* Page number for first page in the list */ - int N, /* Expected number of pages in the list */ - char *zContext /* Context for error messages */ + int N /* Expected number of pages in the list */ ){ int i; int expected = N; int iFirst = iPage; while( N-- > 0 && pCheck->mxErr ){ DbPage *pOvflPage; unsigned char *pOvflData; if( iPage<1 ){ - checkAppendMsg(pCheck, zContext, + checkAppendMsg(pCheck, "%d of %d pages missing from overflow list starting at %d", N+1, expected, iFirst); break; } - if( checkRef(pCheck, iPage, zContext) ) break; + if( checkRef(pCheck, iPage) ) break; if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage) ){ - checkAppendMsg(pCheck, zContext, "failed to get page %d", iPage); + checkAppendMsg(pCheck, "failed to get page %d", iPage); break; } pOvflData = (unsigned char *)sqlite3PagerGetData(pOvflPage); if( isFreeList ){ int n = get4byte(&pOvflData[4]); #ifndef SQLITE_OMIT_AUTOVACUUM if( pCheck->pBt->autoVacuum ){ - checkPtrmap(pCheck, iPage, PTRMAP_FREEPAGE, 0, zContext); + checkPtrmap(pCheck, iPage, PTRMAP_FREEPAGE, 0); } #endif if( n>(int)pCheck->pBt->usableSize/4-2 ){ - checkAppendMsg(pCheck, zContext, + checkAppendMsg(pCheck, "freelist leaf count too big on page %d", iPage); N--; }else{ for(i=0; i<n; i++){ Pgno iFreePage = get4byte(&pOvflData[8+i*4]); #ifndef SQLITE_OMIT_AUTOVACUUM if( pCheck->pBt->autoVacuum ){ - checkPtrmap(pCheck, iFreePage, PTRMAP_FREEPAGE, 0, zContext); + checkPtrmap(pCheck, iFreePage, PTRMAP_FREEPAGE, 0); } #endif - checkRef(pCheck, iFreePage, zContext); + checkRef(pCheck, iFreePage); } N -= n; } } #ifndef SQLITE_OMIT_AUTOVACUUM @@ -57759,11 +61445,11 @@ ** page in this overflow list, check that the pointer-map entry for ** the following page matches iPage. */ if( pCheck->pBt->autoVacuum && N>0 ){ i = get4byte(pOvflData); - checkPtrmap(pCheck, i, PTRMAP_OVERFLOW2, iPage, zContext); + checkPtrmap(pCheck, i, PTRMAP_OVERFLOW2, iPage); } } #endif iPage = get4byte(pOvflData); sqlite3PagerUnref(pOvflPage); @@ -57791,11 +61477,10 @@ ** the root of the tree. */ static int checkTreePage( IntegrityCk *pCheck, /* Context for the sanity check */ int iPage, /* Page number of the page to check */ - char *zParentContext, /* Parent context */ i64 *pnParentMinKey, i64 *pnParentMaxKey ){ MemPage *pPage; int i, rc, depth, d2, pgno, cnt; @@ -57802,38 +61487,42 @@ int hdr, cellStart; int nCell; u8 *data; BtShared *pBt; int usableSize; - char zContext[100]; char *hit = 0; i64 nMinKey = 0; i64 nMaxKey = 0; - - sqlite3_snprintf(sizeof(zContext), zContext, "Page %d: ", iPage); + const char *saved_zPfx = pCheck->zPfx; + int saved_v1 = pCheck->v1; + int saved_v2 = pCheck->v2; /* Check that the page exists */ pBt = pCheck->pBt; usableSize = pBt->usableSize; if( iPage==0 ) return 0; - if( checkRef(pCheck, iPage, zParentContext) ) return 0; + if( checkRef(pCheck, iPage) ) return 0; + pCheck->zPfx = "Page %d: "; + pCheck->v1 = iPage; if( (rc = btreeGetPage(pBt, (Pgno)iPage, &pPage, 0))!=0 ){ - checkAppendMsg(pCheck, zContext, + checkAppendMsg(pCheck, "unable to get the page. error code=%d", rc); - return 0; + depth = -1; + goto end_of_check; } /* Clear MemPage.isInit to make sure the corruption detection code in ** btreeInitPage() is executed. */ pPage->isInit = 0; if( (rc = btreeInitPage(pPage))!=0 ){ assert( rc==SQLITE_CORRUPT ); /* The only possible error from InitPage */ - checkAppendMsg(pCheck, zContext, + checkAppendMsg(pCheck, "btreeInitPage() returns error code %d", rc); releasePage(pPage); - return 0; + depth = -1; + goto end_of_check; } /* Check out all the cells. */ depth = 0; @@ -57842,99 +61531,101 @@ u32 sz; CellInfo info; /* Check payload overflow pages */ - sqlite3_snprintf(sizeof(zContext), zContext, - "On tree page %d cell %d: ", iPage, i); + pCheck->zPfx = "On tree page %d cell %d: "; + pCheck->v1 = iPage; + pCheck->v2 = i; pCell = findCell(pPage,i); btreeParseCellPtr(pPage, pCell, &info); - sz = info.nData; - if( !pPage->intKey ) sz += (int)info.nKey; + sz = info.nPayload; /* For intKey pages, check that the keys are in order. */ - else if( i==0 ) nMinKey = nMaxKey = info.nKey; - else{ - if( info.nKey <= nMaxKey ){ - checkAppendMsg(pCheck, zContext, - "Rowid %lld out of order (previous was %lld)", info.nKey, nMaxKey); + if( pPage->intKey ){ + if( i==0 ){ + nMinKey = nMaxKey = info.nKey; + }else if( info.nKey <= nMaxKey ){ + checkAppendMsg(pCheck, + "Rowid %lld out of order (previous was %lld)", info.nKey, nMaxKey); } nMaxKey = info.nKey; } - assert( sz==info.nPayload ); if( (sz>info.nLocal) && (&pCell[info.iOverflow]<=&pPage->aData[pBt->usableSize]) ){ int nPage = (sz - info.nLocal + usableSize - 5)/(usableSize - 4); Pgno pgnoOvfl = get4byte(&pCell[info.iOverflow]); #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum ){ - checkPtrmap(pCheck, pgnoOvfl, PTRMAP_OVERFLOW1, iPage, zContext); + checkPtrmap(pCheck, pgnoOvfl, PTRMAP_OVERFLOW1, iPage); } #endif - checkList(pCheck, 0, pgnoOvfl, nPage, zContext); + checkList(pCheck, 0, pgnoOvfl, nPage); } /* Check sanity of left child page. */ if( !pPage->leaf ){ pgno = get4byte(pCell); #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum ){ - checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage, zContext); + checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage); } #endif - d2 = checkTreePage(pCheck, pgno, zContext, &nMinKey, i==0 ? NULL : &nMaxKey); + d2 = checkTreePage(pCheck, pgno, &nMinKey, i==0?NULL:&nMaxKey); if( i>0 && d2!=depth ){ - checkAppendMsg(pCheck, zContext, "Child page depth differs"); + checkAppendMsg(pCheck, "Child page depth differs"); } depth = d2; } } if( !pPage->leaf ){ pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]); - sqlite3_snprintf(sizeof(zContext), zContext, - "On page %d at right child: ", iPage); + pCheck->zPfx = "On page %d at right child: "; + pCheck->v1 = iPage; #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum ){ - checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage, zContext); + checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage); } #endif - checkTreePage(pCheck, pgno, zContext, NULL, !pPage->nCell ? NULL : &nMaxKey); + checkTreePage(pCheck, pgno, NULL, !pPage->nCell?NULL:&nMaxKey); } /* For intKey leaf pages, check that the min/max keys are in order ** with any left/parent/right pages. */ + pCheck->zPfx = "Page %d: "; + pCheck->v1 = iPage; if( pPage->leaf && pPage->intKey ){ /* if we are a left child page */ if( pnParentMinKey ){ /* if we are the left most child page */ if( !pnParentMaxKey ){ if( nMaxKey > *pnParentMinKey ){ - checkAppendMsg(pCheck, zContext, + checkAppendMsg(pCheck, "Rowid %lld out of order (max larger than parent min of %lld)", nMaxKey, *pnParentMinKey); } }else{ if( nMinKey <= *pnParentMinKey ){ - checkAppendMsg(pCheck, zContext, + checkAppendMsg(pCheck, "Rowid %lld out of order (min less than parent min of %lld)", nMinKey, *pnParentMinKey); } if( nMaxKey > *pnParentMaxKey ){ - checkAppendMsg(pCheck, zContext, + checkAppendMsg(pCheck, "Rowid %lld out of order (max larger than parent max of %lld)", nMaxKey, *pnParentMaxKey); } *pnParentMinKey = nMaxKey; } /* else if we're a right child page */ } else if( pnParentMaxKey ){ if( nMinKey <= *pnParentMaxKey ){ - checkAppendMsg(pCheck, zContext, + checkAppendMsg(pCheck, "Rowid %lld out of order (min less than parent max of %lld)", nMinKey, *pnParentMaxKey); } } } @@ -57942,62 +61633,89 @@ /* Check for complete coverage of the page */ data = pPage->aData; hdr = pPage->hdrOffset; hit = sqlite3PageMalloc( pBt->pageSize ); + pCheck->zPfx = 0; if( hit==0 ){ pCheck->mallocFailed = 1; }else{ int contentOffset = get2byteNotZero(&data[hdr+5]); assert( contentOffset<=usableSize ); /* Enforced by btreeInitPage() */ memset(hit+contentOffset, 0, usableSize-contentOffset); memset(hit, 1, contentOffset); + /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the + ** number of cells on the page. */ nCell = get2byte(&data[hdr+3]); + /* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page + ** immediately follows the b-tree page header. */ cellStart = hdr + 12 - 4*pPage->leaf; + /* EVIDENCE-OF: R-02776-14802 The cell pointer array consists of K 2-byte + ** integer offsets to the cell contents. */ for(i=0; i<nCell; i++){ int pc = get2byte(&data[cellStart+i*2]); u32 size = 65536; int j; if( pc<=usableSize-4 ){ size = cellSizePtr(pPage, &data[pc]); } if( (int)(pc+size-1)>=usableSize ){ - checkAppendMsg(pCheck, 0, + pCheck->zPfx = 0; + checkAppendMsg(pCheck, "Corruption detected in cell %d on page %d",i,iPage); }else{ for(j=pc+size-1; j>=pc; j--) hit[j]++; } } + /* EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header + ** is the offset of the first freeblock, or zero if there are no + ** freeblocks on the page. */ i = get2byte(&data[hdr+1]); while( i>0 ){ int size, j; assert( i<=usableSize-4 ); /* Enforced by btreeInitPage() */ size = get2byte(&data[i+2]); assert( i+size<=usableSize ); /* Enforced by btreeInitPage() */ for(j=i+size-1; j>=i; j--) hit[j]++; + /* EVIDENCE-OF: R-58208-19414 The first 2 bytes of a freeblock are a + ** big-endian integer which is the offset in the b-tree page of the next + ** freeblock in the chain, or zero if the freeblock is the last on the + ** chain. */ j = get2byte(&data[i]); + /* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of + ** increasing offset. */ assert( j==0 || j>i+size ); /* Enforced by btreeInitPage() */ assert( j<=usableSize-4 ); /* Enforced by btreeInitPage() */ i = j; } for(i=cnt=0; i<usableSize; i++){ if( hit[i]==0 ){ cnt++; }else if( hit[i]>1 ){ - checkAppendMsg(pCheck, 0, + checkAppendMsg(pCheck, "Multiple uses for byte %d of page %d", i, iPage); break; } } + /* EVIDENCE-OF: R-43263-13491 The total number of bytes in all fragments + ** is stored in the fifth field of the b-tree page header. + ** EVIDENCE-OF: R-07161-27322 The one-byte integer at offset 7 gives the + ** number of fragmented free bytes within the cell content area. + */ if( cnt!=data[hdr+7] ){ - checkAppendMsg(pCheck, 0, + checkAppendMsg(pCheck, "Fragmentation of %d bytes reported as %d on page %d", cnt, data[hdr+7], iPage); } } sqlite3PageFree(hit); releasePage(pPage); + +end_of_check: + pCheck->zPfx = saved_zPfx; + pCheck->v1 = saved_v1; + pCheck->v2 = saved_v2; return depth+1; } #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ #ifndef SQLITE_OMIT_INTEGRITY_CHECK @@ -58034,10 +61752,13 @@ sCheck.pPager = pBt->pPager; sCheck.nPage = btreePagecount(sCheck.pBt); sCheck.mxErr = mxErr; sCheck.nErr = 0; sCheck.mallocFailed = 0; + sCheck.zPfx = 0; + sCheck.v1 = 0; + sCheck.v2 = 0; *pnErr = 0; if( sCheck.nPage==0 ){ sqlite3BtreeLeave(p); return 0; } @@ -58053,53 +61774,57 @@ sqlite3StrAccumInit(&sCheck.errMsg, zErr, sizeof(zErr), SQLITE_MAX_LENGTH); sCheck.errMsg.useMalloc = 2; /* Check the integrity of the freelist */ + sCheck.zPfx = "Main freelist: "; checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]), - get4byte(&pBt->pPage1->aData[36]), "Main freelist: "); + get4byte(&pBt->pPage1->aData[36])); + sCheck.zPfx = 0; /* Check all the tables. */ for(i=0; (int)i<nRoot && sCheck.mxErr; i++){ if( aRoot[i]==0 ) continue; #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum && aRoot[i]>1 ){ - checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0, 0); + checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0); } #endif - checkTreePage(&sCheck, aRoot[i], "List of tree roots: ", NULL, NULL); + sCheck.zPfx = "List of tree roots: "; + checkTreePage(&sCheck, aRoot[i], NULL, NULL); + sCheck.zPfx = 0; } /* Make sure every page in the file is referenced */ for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){ #ifdef SQLITE_OMIT_AUTOVACUUM if( getPageReferenced(&sCheck, i)==0 ){ - checkAppendMsg(&sCheck, 0, "Page %d is never used", i); + checkAppendMsg(&sCheck, "Page %d is never used", i); } #else /* If the database supports auto-vacuum, make sure no tables contain ** references to pointer-map pages. */ if( getPageReferenced(&sCheck, i)==0 && (PTRMAP_PAGENO(pBt, i)!=i || !pBt->autoVacuum) ){ - checkAppendMsg(&sCheck, 0, "Page %d is never used", i); + checkAppendMsg(&sCheck, "Page %d is never used", i); } if( getPageReferenced(&sCheck, i)!=0 && (PTRMAP_PAGENO(pBt, i)==i && pBt->autoVacuum) ){ - checkAppendMsg(&sCheck, 0, "Pointer map page %d is referenced", i); + checkAppendMsg(&sCheck, "Pointer map page %d is referenced", i); } #endif } /* Make sure this analysis did not leave any unref() pages. ** This is an internal consistency check; an integrity check ** of the integrity check. */ if( NEVER(nRef != sqlite3PagerRefcount(pBt->pPager)) ){ - checkAppendMsg(&sCheck, 0, + checkAppendMsg(&sCheck, "Outstanding page count goes from %d to %d during this analysis", nRef, sqlite3PagerRefcount(pBt->pPager) ); } @@ -58276,11 +62001,11 @@ */ SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){ int rc; assert( cursorHoldsMutex(pCsr) ); assert( sqlite3_mutex_held(pCsr->pBtree->db->mutex) ); - assert( pCsr->isIncrblobHandle ); + assert( pCsr->curFlags & BTCF_Incrblob ); rc = restoreCursorPosition(pCsr); if( rc!=SQLITE_OK ){ return rc; } @@ -58291,11 +62016,11 @@ /* Save the positions of all other cursors open on this table. This is ** required in case any of them are holding references to an xFetch ** version of the b-tree page modified by the accessPayload call below. ** - ** Note that pCsr must be open on a BTREE_INTKEY table and saveCursorPosition() + ** Note that pCsr must be open on a INTKEY table and saveCursorPosition() ** and hence saveAllCursors() cannot fail on a BTREE_INTKEY table, hence ** saveAllCursors can only return SQLITE_OK. */ VVA_ONLY(rc =) saveAllCursors(pCsr->pBt, pCsr->pgnoRoot, pCsr); assert( rc==SQLITE_OK ); @@ -58305,11 +62030,11 @@ ** (b) there is a read/write transaction open, ** (c) the connection holds a write-lock on the table (if required), ** (d) there are no conflicting read-locks, and ** (e) the cursor points at a valid row of an intKey table. */ - if( !pCsr->wrFlag ){ + if( (pCsr->curFlags & BTCF_WriteFlag)==0 ){ return SQLITE_READONLY; } assert( (pCsr->pBt->btsFlags & BTS_READ_ONLY)==0 && pCsr->pBt->inTransaction==TRANS_WRITE ); assert( hasSharedCacheTableLock(pCsr->pBtree, pCsr->pgnoRoot, 0, 2) ); @@ -58318,24 +62043,14 @@ return accessPayload(pCsr, offset, amt, (unsigned char *)z, 1); } /* -** Set a flag on this cursor to cache the locations of pages from the -** overflow list for the current row. This is used by cursors opened -** for incremental blob IO only. -** -** This function sets a flag only. The actual page location cache -** (stored in BtCursor.aOverflow[]) is allocated and used by function -** accessPayload() (the worker function for sqlite3BtreeData() and -** sqlite3BtreePutData()). +** Mark this cursor as an incremental blob cursor. */ -SQLITE_PRIVATE void sqlite3BtreeCacheOverflow(BtCursor *pCur){ - assert( cursorHoldsMutex(pCur) ); - assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); - invalidateOverflowCache(pCur); - pCur->isIncrblobHandle = 1; +SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *pCur){ + pCur->curFlags |= BTCF_Incrblob; } #endif /* ** Set both the "read version" (single byte at byte offset 18) and @@ -58372,18 +62087,39 @@ pBt->btsFlags &= ~BTS_NO_WAL; return rc; } /* -** set the mask of hint flags for cursor pCsr. Currently the only valid -** values are 0 and BTREE_BULKLOAD. +** set the mask of hint flags for cursor pCsr. */ SQLITE_PRIVATE void sqlite3BtreeCursorHints(BtCursor *pCsr, unsigned int mask){ - assert( mask==BTREE_BULKLOAD || mask==0 ); + assert( mask==BTREE_BULKLOAD || mask==BTREE_SEEK_EQ || mask==0 ); pCsr->hints = mask; } +#ifdef SQLITE_DEBUG +/* +** Return true if the cursor has a hint specified. This routine is +** only used from within assert() statements +*/ +SQLITE_PRIVATE int sqlite3BtreeCursorHasHint(BtCursor *pCsr, unsigned int mask){ + return (pCsr->hints & mask)!=0; +} +#endif + +/* +** Return true if the given Btree is read-only. +*/ +SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *p){ + return (p->pBt->btsFlags & BTS_READ_ONLY)!=0; +} + +/* +** Return the size of the header added to each page by this module. +*/ +SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); } + /************** End of btree.c ***********************************************/ /************** Begin file backup.c ******************************************/ /* ** 2009 January 28 ** @@ -58469,28 +62205,29 @@ if( i==1 ){ Parse *pParse; int rc = 0; pParse = sqlite3StackAllocZero(pErrorDb, sizeof(*pParse)); if( pParse==0 ){ - sqlite3Error(pErrorDb, SQLITE_NOMEM, "out of memory"); + sqlite3ErrorWithMsg(pErrorDb, SQLITE_NOMEM, "out of memory"); rc = SQLITE_NOMEM; }else{ pParse->db = pDb; if( sqlite3OpenTempDatabase(pParse) ){ - sqlite3Error(pErrorDb, pParse->rc, "%s", pParse->zErrMsg); + sqlite3ErrorWithMsg(pErrorDb, pParse->rc, "%s", pParse->zErrMsg); rc = SQLITE_ERROR; } sqlite3DbFree(pErrorDb, pParse->zErrMsg); + sqlite3ParserReset(pParse); sqlite3StackFree(pErrorDb, pParse); } if( rc ){ return 0; } } if( i<0 ){ - sqlite3Error(pErrorDb, SQLITE_ERROR, "unknown database %s", zDb); + sqlite3ErrorWithMsg(pErrorDb, SQLITE_ERROR, "unknown database %s", zDb); return 0; } return pDb->aDb[i].pBt; } @@ -58502,26 +62239,47 @@ static int setDestPgsz(sqlite3_backup *p){ int rc; rc = sqlite3BtreeSetPageSize(p->pDest,sqlite3BtreeGetPageSize(p->pSrc),-1,0); return rc; } + +/* +** Check that there is no open read-transaction on the b-tree passed as the +** second argument. If there is not, return SQLITE_OK. Otherwise, if there +** is an open read-transaction, return SQLITE_ERROR and leave an error +** message in database handle db. +*/ +static int checkReadTransaction(sqlite3 *db, Btree *p){ + if( sqlite3BtreeIsInReadTrans(p) ){ + sqlite3ErrorWithMsg(db, SQLITE_ERROR, "destination database is in use"); + return SQLITE_ERROR; + } + return SQLITE_OK; +} /* ** Create an sqlite3_backup process to copy the contents of zSrcDb from ** connection handle pSrcDb to zDestDb in pDestDb. If successful, return ** a pointer to the new sqlite3_backup object. ** ** If an error occurs, NULL is returned and an error code and error message ** stored in database handle pDestDb. */ -SQLITE_API sqlite3_backup *sqlite3_backup_init( +SQLITE_API sqlite3_backup *SQLITE_STDCALL sqlite3_backup_init( sqlite3* pDestDb, /* Database to write to */ const char *zDestDb, /* Name of database within pDestDb */ sqlite3* pSrcDb, /* Database connection to read from */ const char *zSrcDb /* Name of database within pSrcDb */ ){ sqlite3_backup *p; /* Value to return */ + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(pSrcDb)||!sqlite3SafetyCheckOk(pDestDb) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif /* Lock the source database handle. The destination database ** handle is not locked in this routine, but it is locked in ** sqlite3_backup_step(). The user is required to ensure that no ** other thread accesses the destination handle for the duration @@ -58531,11 +62289,11 @@ */ sqlite3_mutex_enter(pSrcDb->mutex); sqlite3_mutex_enter(pDestDb->mutex); if( pSrcDb==pDestDb ){ - sqlite3Error( + sqlite3ErrorWithMsg( pDestDb, SQLITE_ERROR, "source and destination must be distinct" ); p = 0; }else { /* Allocate space for a new sqlite3_backup object... @@ -58542,11 +62300,11 @@ ** EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a ** call to sqlite3_backup_init() and is destroyed by a call to ** sqlite3_backup_finish(). */ p = (sqlite3_backup *)sqlite3MallocZero(sizeof(sqlite3_backup)); if( !p ){ - sqlite3Error(pDestDb, SQLITE_NOMEM, 0); + sqlite3Error(pDestDb, SQLITE_NOMEM); } } /* If the allocation succeeded, populate the new object. */ if( p ){ @@ -58555,16 +62313,19 @@ p->pDestDb = pDestDb; p->pSrcDb = pSrcDb; p->iNext = 1; p->isAttached = 0; - if( 0==p->pSrc || 0==p->pDest || setDestPgsz(p)==SQLITE_NOMEM ){ + if( 0==p->pSrc || 0==p->pDest + || setDestPgsz(p)==SQLITE_NOMEM + || checkReadTransaction(pDestDb, p->pDest)!=SQLITE_OK + ){ /* One (or both) of the named databases did not exist or an OOM - ** error was hit. The error has already been written into the - ** pDestDb handle. All that is left to do here is free the - ** sqlite3_backup structure. - */ + ** error was hit. Or there is a transaction open on the destination + ** database. The error has already been written into the pDestDb + ** handle. All that is left to do here is free the sqlite3_backup + ** structure. */ sqlite3_free(p); p = 0; } } if( p ){ @@ -58604,11 +62365,11 @@ #ifdef SQLITE_HAS_CODEC /* Use BtreeGetReserveNoMutex() for the source b-tree, as although it is ** guaranteed that the shared-mutex is held by this thread, handle ** p->pSrc may not actually be the owner. */ int nSrcReserve = sqlite3BtreeGetReserveNoMutex(p->pSrc); - int nDestReserve = sqlite3BtreeGetReserve(p->pDest); + int nDestReserve = sqlite3BtreeGetOptimalReserve(p->pDest); #endif int rc = SQLITE_OK; i64 iOff; assert( sqlite3BtreeGetReserveNoMutex(p->pSrc)>=0 ); @@ -58709,16 +62470,19 @@ } /* ** Copy nPage pages from the source b-tree to the destination. */ -SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ +SQLITE_API int SQLITE_STDCALL sqlite3_backup_step(sqlite3_backup *p, int nPage){ int rc; int destMode; /* Destination journal mode */ int pgszSrc = 0; /* Source page size */ int pgszDest = 0; /* Destination page size */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(p->pSrcDb->mutex); sqlite3BtreeEnter(p->pSrc); if( p->pDestDb ){ sqlite3_mutex_enter(p->pDestDb->mutex); } @@ -58907,11 +62671,11 @@ rc = backupTruncateFile(pFile, iSize); } /* Sync the database file to disk. */ if( rc==SQLITE_OK ){ - rc = sqlite3PagerSync(pDestPager); + rc = sqlite3PagerSync(pDestPager, 0); } }else{ sqlite3PagerTruncateImage(pDestPager, nDestTruncate); rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 0); } @@ -58951,11 +62715,11 @@ } /* ** Release all resources associated with an sqlite3_backup* handle. */ -SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p){ +SQLITE_API int SQLITE_STDCALL sqlite3_backup_finish(sqlite3_backup *p){ sqlite3_backup **pp; /* Ptr to head of pagers backup list */ sqlite3 *pSrcDb; /* Source database connection */ int rc; /* Value to return */ /* Enter the mutexes */ @@ -58978,18 +62742,18 @@ } *pp = p->pNext; } /* If a transaction is still open on the Btree, roll it back. */ - sqlite3BtreeRollback(p->pDest, SQLITE_OK); + sqlite3BtreeRollback(p->pDest, SQLITE_OK, 0); /* Set the error code of the destination database handle. */ rc = (p->rc==SQLITE_DONE) ? SQLITE_OK : p->rc; - sqlite3Error(p->pDestDb, rc, 0); - - /* Exit the mutexes and free the backup context structure. */ if( p->pDestDb ){ + sqlite3Error(p->pDestDb, rc); + + /* Exit the mutexes and free the backup context structure. */ sqlite3LeaveMutexAndCloseZombie(p->pDestDb); } sqlite3BtreeLeave(p->pSrc); if( p->pDestDb ){ /* EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a @@ -59003,19 +62767,31 @@ /* ** Return the number of pages still to be backed up as of the most recent ** call to sqlite3_backup_step(). */ -SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p){ +SQLITE_API int SQLITE_STDCALL sqlite3_backup_remaining(sqlite3_backup *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return p->nRemaining; } /* ** Return the total number of pages in the source database as of the most ** recent call to sqlite3_backup_step(). */ -SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p){ +SQLITE_API int SQLITE_STDCALL sqlite3_backup_pagecount(sqlite3_backup *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return p->nPagecount; } /* ** This function is called after the contents of page iPage of the @@ -59147,10 +62923,57 @@ ** This file contains code use to manipulate "Mem" structure. A "Mem" ** stores a single value in the VDBE. Mem is an opaque structure visible ** only within the VDBE. Interface routines refer to a Mem using the ** name sqlite_value */ + +#ifdef SQLITE_DEBUG +/* +** Check invariants on a Mem object. +** +** This routine is intended for use inside of assert() statements, like +** this: assert( sqlite3VdbeCheckMemInvariants(pMem) ); +*/ +SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ + /* If MEM_Dyn is set then Mem.xDel!=0. + ** Mem.xDel is might not be initialized if MEM_Dyn is clear. + */ + assert( (p->flags & MEM_Dyn)==0 || p->xDel!=0 ); + + /* MEM_Dyn may only be set if Mem.szMalloc==0. In this way we + ** ensure that if Mem.szMalloc>0 then it is safe to do + ** Mem.z = Mem.zMalloc without having to check Mem.flags&MEM_Dyn. + ** That saves a few cycles in inner loops. */ + assert( (p->flags & MEM_Dyn)==0 || p->szMalloc==0 ); + + /* Cannot be both MEM_Int and MEM_Real at the same time */ + assert( (p->flags & (MEM_Int|MEM_Real))!=(MEM_Int|MEM_Real) ); + + /* The szMalloc field holds the correct memory allocation size */ + assert( p->szMalloc==0 + || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc) ); + + /* If p holds a string or blob, the Mem.z must point to exactly + ** one of the following: + ** + ** (1) Memory in Mem.zMalloc and managed by the Mem object + ** (2) Memory to be freed using Mem.xDel + ** (3) An ephemeral string or blob + ** (4) A static string or blob + */ + if( (p->flags & (MEM_Str|MEM_Blob)) && p->n>0 ){ + assert( + ((p->szMalloc>0 && p->z==p->zMalloc)? 1 : 0) + + ((p->flags&MEM_Dyn)!=0 ? 1 : 0) + + ((p->flags&MEM_Ephem)!=0 ? 1 : 0) + + ((p->flags&MEM_Static)!=0 ? 1 : 0) == 1 + ); + } + return 1; +} +#endif + /* ** If pMem is an object with a valid string representation, this routine ** ensures the internal encoding for the string representation is ** 'desiredEnc', one of SQLITE_UTF8, SQLITE_UTF16LE or SQLITE_UTF16BE. @@ -59189,78 +63012,98 @@ #endif } /* ** Make sure pMem->z points to a writable allocation of at least -** n bytes. -** -** If the third argument passed to this function is true, then memory -** cell pMem must contain a string or blob. In this case the content is -** preserved. Otherwise, if the third parameter to this function is false, -** any current string or blob value may be discarded. -** -** This function sets the MEM_Dyn flag and clears any xDel callback. -** It also clears MEM_Ephem and MEM_Static. If the preserve flag is -** not set, Mem.n is zeroed. -*/ -SQLITE_PRIVATE int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve){ - assert( 1 >= - ((pMem->zMalloc && pMem->zMalloc==pMem->z) ? 1 : 0) + - (((pMem->flags&MEM_Dyn)&&pMem->xDel) ? 1 : 0) + - ((pMem->flags&MEM_Ephem) ? 1 : 0) + - ((pMem->flags&MEM_Static) ? 1 : 0) - ); +** min(n,32) bytes. +** +** If the bPreserve argument is true, then copy of the content of +** pMem->z into the new allocation. pMem must be either a string or +** blob if bPreserve is true. If bPreserve is false, any prior content +** in pMem->z is discarded. +*/ +SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPreserve){ + assert( sqlite3VdbeCheckMemInvariants(pMem) ); assert( (pMem->flags&MEM_RowSet)==0 ); - /* If the preserve flag is set to true, then the memory cell must already + /* If the bPreserve flag is set to true, then the memory cell must already ** contain a valid string or blob value. */ - assert( preserve==0 || pMem->flags&(MEM_Blob|MEM_Str) ); + assert( bPreserve==0 || pMem->flags&(MEM_Blob|MEM_Str) ); + testcase( bPreserve && pMem->z==0 ); - if( n<32 ) n = 32; - if( sqlite3DbMallocSize(pMem->db, pMem->zMalloc)<n ){ - if( preserve && pMem->z==pMem->zMalloc ){ + assert( pMem->szMalloc==0 + || pMem->szMalloc==sqlite3DbMallocSize(pMem->db, pMem->zMalloc) ); + if( pMem->szMalloc<n ){ + if( n<32 ) n = 32; + if( bPreserve && pMem->szMalloc>0 && pMem->z==pMem->zMalloc ){ pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n); - preserve = 0; + bPreserve = 0; }else{ - sqlite3DbFree(pMem->db, pMem->zMalloc); + if( pMem->szMalloc>0 ) sqlite3DbFree(pMem->db, pMem->zMalloc); pMem->zMalloc = sqlite3DbMallocRaw(pMem->db, n); } + if( pMem->zMalloc==0 ){ + sqlite3VdbeMemSetNull(pMem); + pMem->z = 0; + pMem->szMalloc = 0; + return SQLITE_NOMEM; + }else{ + pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc); + } } - if( pMem->z && preserve && pMem->zMalloc && pMem->z!=pMem->zMalloc ){ + if( bPreserve && pMem->z && pMem->z!=pMem->zMalloc ){ memcpy(pMem->zMalloc, pMem->z, pMem->n); } - if( pMem->flags&MEM_Dyn && pMem->xDel ){ - assert( pMem->xDel!=SQLITE_DYNAMIC ); + if( (pMem->flags&MEM_Dyn)!=0 ){ + assert( pMem->xDel!=0 && pMem->xDel!=SQLITE_DYNAMIC ); pMem->xDel((void *)(pMem->z)); } pMem->z = pMem->zMalloc; - if( pMem->z==0 ){ - pMem->flags = MEM_Null; - }else{ - pMem->flags &= ~(MEM_Ephem|MEM_Static); - } - pMem->xDel = 0; - return (pMem->z ? SQLITE_OK : SQLITE_NOMEM); + pMem->flags &= ~(MEM_Dyn|MEM_Ephem|MEM_Static); + return SQLITE_OK; } /* -** Make the given Mem object MEM_Dyn. In other words, make it so -** that any TEXT or BLOB content is stored in memory obtained from -** malloc(). In this way, we know that the memory is safe to be -** overwritten or altered. +** Change the pMem->zMalloc allocation to be at least szNew bytes. +** If pMem->zMalloc already meets or exceeds the requested size, this +** routine is a no-op. +** +** Any prior string or blob content in the pMem object may be discarded. +** The pMem->xDel destructor is called, if it exists. Though MEM_Str +** and MEM_Blob values may be discarded, MEM_Int, MEM_Real, and MEM_Null +** values are preserved. +** +** Return SQLITE_OK on success or an error code (probably SQLITE_NOMEM) +** if unable to complete the resizing. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){ + assert( szNew>0 ); + assert( (pMem->flags & MEM_Dyn)==0 || pMem->szMalloc==0 ); + if( pMem->szMalloc<szNew ){ + return sqlite3VdbeMemGrow(pMem, szNew, 0); + } + assert( (pMem->flags & MEM_Dyn)==0 ); + pMem->z = pMem->zMalloc; + pMem->flags &= (MEM_Null|MEM_Int|MEM_Real); + return SQLITE_OK; +} + +/* +** Change pMem so that its MEM_Str or MEM_Blob value is stored in +** MEM.zMalloc, where it can be safely written. ** ** Return SQLITE_OK on success or SQLITE_NOMEM if malloc fails. */ SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){ int f; assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( (pMem->flags&MEM_RowSet)==0 ); ExpandBlob(pMem); f = pMem->flags; - if( (f&(MEM_Str|MEM_Blob)) && pMem->z!=pMem->zMalloc ){ + if( (f&(MEM_Str|MEM_Blob)) && (pMem->szMalloc==0 || pMem->z!=pMem->zMalloc) ){ if( sqlite3VdbeMemGrow(pMem, pMem->n + 2, 1) ){ return SQLITE_NOMEM; } pMem->z[pMem->n] = 0; pMem->z[pMem->n+1] = 0; @@ -59300,43 +63143,53 @@ } return SQLITE_OK; } #endif - /* -** Make sure the given Mem is \u0000 terminated. +** It is already known that pMem contains an unterminated string. +** Add the zero terminator. */ -SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem *pMem){ - assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); - if( (pMem->flags & MEM_Term)!=0 || (pMem->flags & MEM_Str)==0 ){ - return SQLITE_OK; /* Nothing to do */ - } +static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){ if( sqlite3VdbeMemGrow(pMem, pMem->n+2, 1) ){ return SQLITE_NOMEM; } pMem->z[pMem->n] = 0; pMem->z[pMem->n+1] = 0; pMem->flags |= MEM_Term; return SQLITE_OK; } + +/* +** Make sure the given Mem is \u0000 terminated. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem *pMem){ + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + testcase( (pMem->flags & (MEM_Term|MEM_Str))==(MEM_Term|MEM_Str) ); + testcase( (pMem->flags & (MEM_Term|MEM_Str))==0 ); + if( (pMem->flags & (MEM_Term|MEM_Str))!=MEM_Str ){ + return SQLITE_OK; /* Nothing to do */ + }else{ + return vdbeMemAddTerminator(pMem); + } +} /* ** Add MEM_Str to the set of representations for the given Mem. Numbers ** are converted using sqlite3_snprintf(). Converting a BLOB to a string ** is a no-op. ** -** Existing representations MEM_Int and MEM_Real are *not* invalidated. +** Existing representations MEM_Int and MEM_Real are invalidated if +** bForce is true but are retained if bForce is false. ** ** A MEM_Null value will never be passed to this function. This function is ** used for converting values to text for returning to the user (i.e. via ** sqlite3_value_text()), or for ensuring that values to be used as btree ** keys are strings. In the former case a NULL pointer is returned the -** user and the later is an internal programming error. +** user and the latter is an internal programming error. */ -SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem *pMem, int enc){ - int rc = SQLITE_OK; +SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ int fg = pMem->flags; const int nByte = 32; assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !(fg&MEM_Zero) ); @@ -59344,31 +63197,32 @@ assert( fg&(MEM_Int|MEM_Real) ); assert( (pMem->flags&MEM_RowSet)==0 ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); - if( sqlite3VdbeMemGrow(pMem, nByte, 0) ){ + if( sqlite3VdbeMemClearAndResize(pMem, nByte) ){ return SQLITE_NOMEM; } - /* For a Real or Integer, use sqlite3_mprintf() to produce the UTF-8 + /* For a Real or Integer, use sqlite3_snprintf() to produce the UTF-8 ** string representation of the value. Then, if the required encoding ** is UTF-16le or UTF-16be do a translation. ** ** FIX ME: It would be better if sqlite3_snprintf() could do UTF-16. */ if( fg & MEM_Int ){ sqlite3_snprintf(nByte, pMem->z, "%lld", pMem->u.i); }else{ assert( fg & MEM_Real ); - sqlite3_snprintf(nByte, pMem->z, "%!.15g", pMem->r); + sqlite3_snprintf(nByte, pMem->z, "%!.15g", pMem->u.r); } pMem->n = sqlite3Strlen30(pMem->z); pMem->enc = SQLITE_UTF8; pMem->flags |= MEM_Str|MEM_Term; + if( bForce ) pMem->flags &= ~(MEM_Int|MEM_Real); sqlite3VdbeChangeEncoding(pMem, enc); - return rc; + return SQLITE_OK; } /* ** Memory cell pMem contains the context of an aggregate function. ** This routine calls the finalize method for that function. The @@ -59379,73 +63233,100 @@ */ SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ int rc = SQLITE_OK; if( ALWAYS(pFunc && pFunc->xFinalize) ){ sqlite3_context ctx; + Mem t; assert( (pMem->flags & MEM_Null)!=0 || pFunc==pMem->u.pDef ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); memset(&ctx, 0, sizeof(ctx)); - ctx.s.flags = MEM_Null; - ctx.s.db = pMem->db; + memset(&t, 0, sizeof(t)); + t.flags = MEM_Null; + t.db = pMem->db; + ctx.pOut = &t; ctx.pMem = pMem; ctx.pFunc = pFunc; pFunc->xFinalize(&ctx); /* IMP: R-24505-23230 */ - assert( 0==(pMem->flags&MEM_Dyn) && !pMem->xDel ); - sqlite3DbFree(pMem->db, pMem->zMalloc); - memcpy(pMem, &ctx.s, sizeof(ctx.s)); + assert( (pMem->flags & MEM_Dyn)==0 ); + if( pMem->szMalloc>0 ) sqlite3DbFree(pMem->db, pMem->zMalloc); + memcpy(pMem, &t, sizeof(t)); rc = ctx.isError; } return rc; } /* -** If the memory cell contains a string value that must be freed by -** invoking an external callback, free it now. Calling this function -** does not free any Mem.zMalloc buffer. +** If the memory cell contains a value that must be freed by +** invoking the external callback in Mem.xDel, then this routine +** will free that value. It also sets Mem.flags to MEM_Null. +** +** This is a helper routine for sqlite3VdbeMemSetNull() and +** for sqlite3VdbeMemRelease(). Use those other routines as the +** entry point for releasing Mem resources. */ -SQLITE_PRIVATE void sqlite3VdbeMemReleaseExternal(Mem *p){ +static SQLITE_NOINLINE void vdbeMemClearExternAndSetNull(Mem *p){ assert( p->db==0 || sqlite3_mutex_held(p->db->mutex) ); + assert( VdbeMemDynamic(p) ); if( p->flags&MEM_Agg ){ sqlite3VdbeMemFinalize(p, p->u.pDef); assert( (p->flags & MEM_Agg)==0 ); - sqlite3VdbeMemRelease(p); - }else if( p->flags&MEM_Dyn && p->xDel ){ + testcase( p->flags & MEM_Dyn ); + } + if( p->flags&MEM_Dyn ){ assert( (p->flags&MEM_RowSet)==0 ); - assert( p->xDel!=SQLITE_DYNAMIC ); + assert( p->xDel!=SQLITE_DYNAMIC && p->xDel!=0 ); p->xDel((void *)p->z); - p->xDel = 0; }else if( p->flags&MEM_RowSet ){ sqlite3RowSetClear(p->u.pRowSet); }else if( p->flags&MEM_Frame ){ - sqlite3VdbeMemSetNull(p); + VdbeFrame *pFrame = p->u.pFrame; + pFrame->pParent = pFrame->v->pDelFrame; + pFrame->v->pDelFrame = pFrame; } + p->flags = MEM_Null; } /* -** Release any memory held by the Mem. This may leave the Mem in an -** inconsistent state, for example with (Mem.z==0) and -** (Mem.type==SQLITE_TEXT). +** Release memory held by the Mem p, both external memory cleared +** by p->xDel and memory in p->zMalloc. +** +** This is a helper routine invoked by sqlite3VdbeMemRelease() in +** the unusual case where there really is memory in p that needs +** to be freed. +*/ +static SQLITE_NOINLINE void vdbeMemClear(Mem *p){ + if( VdbeMemDynamic(p) ){ + vdbeMemClearExternAndSetNull(p); + } + if( p->szMalloc ){ + sqlite3DbFree(p->db, p->zMalloc); + p->szMalloc = 0; + } + p->z = 0; +} + +/* +** Release any memory resources held by the Mem. Both the memory that is +** free by Mem.xDel and the Mem.zMalloc allocation are freed. +** +** Use this routine prior to clean up prior to abandoning a Mem, or to +** reset a Mem back to its minimum memory utilization. +** +** Use sqlite3VdbeMemSetNull() to release just the Mem.xDel space +** prior to inserting new content into the Mem. */ SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p){ - VdbeMemRelease(p); - sqlite3DbFree(p->db, p->zMalloc); - p->z = 0; - p->zMalloc = 0; - p->xDel = 0; + assert( sqlite3VdbeCheckMemInvariants(p) ); + if( VdbeMemDynamic(p) || p->szMalloc ){ + vdbeMemClear(p); + } } /* ** Convert a 64-bit IEEE double into a 64-bit signed integer. -** If the double is too large, return 0x8000000000000000. -** -** Most systems appear to do this simply by assigning -** variables and without the extra range tests. But -** there are reports that windows throws an expection -** if the floating point value is out of range. (See ticket #2880.) -** Because we do not completely understand the problem, we will -** take the conservative approach and always do range tests -** before attempting the conversion. +** If the double is out of range of a 64-bit signed integer then +** return the closest available 64-bit signed integer. */ static i64 doubleToInt64(double r){ #ifdef SQLITE_OMIT_FLOATING_POINT /* When floating-point is omitted, double and int64 are the same thing */ return r; @@ -59458,18 +63339,14 @@ ** larger than a 32-bit integer constant. */ static const i64 maxInt = LARGEST_INT64; static const i64 minInt = SMALLEST_INT64; - if( r<(double)minInt ){ - return minInt; - }else if( r>(double)maxInt ){ - /* minInt is correct here - not maxInt. It turns out that assigning - ** a very large positive number to an integer results in a very large - ** negative integer. This makes no sense, but it is what x86 hardware - ** does so for compatibility we will do the same in software. */ - return minInt; + if( r<=(double)minInt ){ + return minInt; + }else if( r>=(double)maxInt ){ + return maxInt; }else{ return (i64)r; } #endif } @@ -59478,11 +63355,11 @@ ** Return some kind of integer value which is the best we can do ** at representing the value that *pMem describes as an integer. ** If pMem is an integer, then the value is exact. If pMem is ** a floating-point then the value returned is the integer part. ** If pMem is a string or blob, then we make an attempt to convert -** it into a integer and return that. If pMem represents an +** it into an integer and return that. If pMem represents an ** an SQL-NULL value, return 0. ** ** If pMem represents a string value, its encoding might be changed. */ SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem *pMem){ @@ -59491,15 +63368,14 @@ assert( EIGHT_BYTE_ALIGNMENT(pMem) ); flags = pMem->flags; if( flags & MEM_Int ){ return pMem->u.i; }else if( flags & MEM_Real ){ - return doubleToInt64(pMem->r); + return doubleToInt64(pMem->u.r); }else if( flags & (MEM_Str|MEM_Blob) ){ i64 value = 0; assert( pMem->z || pMem->n==0 ); - testcase( pMem->z==0 ); sqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc); return value; }else{ return 0; } @@ -59513,11 +63389,11 @@ */ SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); if( pMem->flags & MEM_Real ){ - return pMem->r; + return pMem->u.r; }else if( pMem->flags & MEM_Int ){ return (double)pMem->u.i; }else if( pMem->flags & (MEM_Str|MEM_Blob) ){ /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ double val = (double)0; @@ -59532,38 +63408,31 @@ /* ** The MEM structure is already a MEM_Real. Try to also make it a ** MEM_Int if we can. */ SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem *pMem){ + i64 ix; assert( pMem->flags & MEM_Real ); assert( (pMem->flags & MEM_RowSet)==0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); - pMem->u.i = doubleToInt64(pMem->r); + ix = doubleToInt64(pMem->u.r); /* Only mark the value as an integer if ** ** (1) the round-trip conversion real->int->real is a no-op, and ** (2) The integer is neither the largest nor the smallest ** possible integer (ticket #3922) ** ** The second and third terms in the following conditional enforces ** the second condition under the assumption that addition overflow causes - ** values to wrap around. On x86 hardware, the third term is always - ** true and could be omitted. But we leave it in because other - ** architectures might behave differently. + ** values to wrap around. */ - if( pMem->r==(double)pMem->u.i - && pMem->u.i>SMALLEST_INT64 -#if defined(__i486__) || defined(__x86_64__) - && ALWAYS(pMem->u.i<LARGEST_INT64) -#else - && pMem->u.i<LARGEST_INT64 -#endif - ){ - pMem->flags |= MEM_Int; + if( pMem->u.r==ix && ix>SMALLEST_INT64 && ix<LARGEST_INT64 ){ + pMem->u.i = ix; + MemSetTypeFlag(pMem, MEM_Int); } } /* ** Convert pMem to type integer. Invalidate any prior representations. @@ -59584,11 +63453,11 @@ */ SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem *pMem){ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); - pMem->r = sqlite3VdbeRealValue(pMem); + pMem->u.r = sqlite3VdbeRealValue(pMem); MemSetTypeFlag(pMem, MEM_Real); return SQLITE_OK; } /* @@ -59604,82 +63473,148 @@ assert( (pMem->flags & (MEM_Blob|MEM_Str))!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); if( 0==sqlite3Atoi64(pMem->z, &pMem->u.i, pMem->n, pMem->enc) ){ MemSetTypeFlag(pMem, MEM_Int); }else{ - pMem->r = sqlite3VdbeRealValue(pMem); + pMem->u.r = sqlite3VdbeRealValue(pMem); MemSetTypeFlag(pMem, MEM_Real); sqlite3VdbeIntegerAffinity(pMem); } } assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))!=0 ); pMem->flags &= ~(MEM_Str|MEM_Blob); return SQLITE_OK; } + +/* +** Cast the datatype of the value in pMem according to the affinity +** "aff". Casting is different from applying affinity in that a cast +** is forced. In other words, the value is converted into the desired +** affinity even if that results in loss of data. This routine is +** used (for example) to implement the SQL "cast()" operator. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ + if( pMem->flags & MEM_Null ) return; + switch( aff ){ + case SQLITE_AFF_NONE: { /* Really a cast to BLOB */ + if( (pMem->flags & MEM_Blob)==0 ){ + sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding); + assert( pMem->flags & MEM_Str || pMem->db->mallocFailed ); + MemSetTypeFlag(pMem, MEM_Blob); + }else{ + pMem->flags &= ~(MEM_TypeMask&~MEM_Blob); + } + break; + } + case SQLITE_AFF_NUMERIC: { + sqlite3VdbeMemNumerify(pMem); + break; + } + case SQLITE_AFF_INTEGER: { + sqlite3VdbeMemIntegerify(pMem); + break; + } + case SQLITE_AFF_REAL: { + sqlite3VdbeMemRealify(pMem); + break; + } + default: { + assert( aff==SQLITE_AFF_TEXT ); + assert( MEM_Str==(MEM_Blob>>3) ); + pMem->flags |= (pMem->flags&MEM_Blob)>>3; + sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding); + assert( pMem->flags & MEM_Str || pMem->db->mallocFailed ); + pMem->flags &= ~(MEM_Int|MEM_Real|MEM_Blob|MEM_Zero); + break; + } + } +} + +/* +** Initialize bulk memory to be a consistent Mem object. +** +** The minimum amount of initialization feasible is performed. +*/ +SQLITE_PRIVATE void sqlite3VdbeMemInit(Mem *pMem, sqlite3 *db, u16 flags){ + assert( (flags & ~MEM_TypeMask)==0 ); + pMem->flags = flags; + pMem->db = db; + pMem->szMalloc = 0; +} + /* ** Delete any previous value and set the value stored in *pMem to NULL. +** +** This routine calls the Mem.xDel destructor to dispose of values that +** require the destructor. But it preserves the Mem.zMalloc memory allocation. +** To free all resources, use sqlite3VdbeMemRelease(), which both calls this +** routine to invoke the destructor and deallocates Mem.zMalloc. +** +** Use this routine to reset the Mem prior to insert a new value. +** +** Use sqlite3VdbeMemRelease() to complete erase the Mem prior to abandoning it. */ SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem *pMem){ - if( pMem->flags & MEM_Frame ){ - VdbeFrame *pFrame = pMem->u.pFrame; - pFrame->pParent = pFrame->v->pDelFrame; - pFrame->v->pDelFrame = pFrame; - } - if( pMem->flags & MEM_RowSet ){ - sqlite3RowSetClear(pMem->u.pRowSet); - } - MemSetTypeFlag(pMem, MEM_Null); - pMem->type = SQLITE_NULL; + if( VdbeMemDynamic(pMem) ){ + vdbeMemClearExternAndSetNull(pMem); + }else{ + pMem->flags = MEM_Null; + } +} +SQLITE_PRIVATE void sqlite3ValueSetNull(sqlite3_value *p){ + sqlite3VdbeMemSetNull((Mem*)p); } /* ** Delete any previous value and set the value to be a BLOB of length ** n containing all zeros. */ SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ sqlite3VdbeMemRelease(pMem); pMem->flags = MEM_Blob|MEM_Zero; - pMem->type = SQLITE_BLOB; pMem->n = 0; if( n<0 ) n = 0; pMem->u.nZero = n; pMem->enc = SQLITE_UTF8; - -#ifdef SQLITE_OMIT_INCRBLOB - sqlite3VdbeMemGrow(pMem, n, 0); - if( pMem->z ){ - pMem->n = n; - memset(pMem->z, 0, n); - } -#endif + pMem->z = 0; +} + +/* +** The pMem is known to contain content that needs to be destroyed prior +** to a value change. So invoke the destructor, then set the value to +** a 64-bit integer. +*/ +static SQLITE_NOINLINE void vdbeReleaseAndSetInt64(Mem *pMem, i64 val){ + sqlite3VdbeMemSetNull(pMem); + pMem->u.i = val; + pMem->flags = MEM_Int; } /* ** Delete any previous value and set the value stored in *pMem to val, ** manifest type INTEGER. */ SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem *pMem, i64 val){ - sqlite3VdbeMemRelease(pMem); - pMem->u.i = val; - pMem->flags = MEM_Int; - pMem->type = SQLITE_INTEGER; + if( VdbeMemDynamic(pMem) ){ + vdbeReleaseAndSetInt64(pMem, val); + }else{ + pMem->u.i = val; + pMem->flags = MEM_Int; + } } #ifndef SQLITE_OMIT_FLOATING_POINT /* ** Delete any previous value and set the value stored in *pMem to val, ** manifest type REAL. */ SQLITE_PRIVATE void sqlite3VdbeMemSetDouble(Mem *pMem, double val){ - if( sqlite3IsNaN(val) ){ - sqlite3VdbeMemSetNull(pMem); - }else{ - sqlite3VdbeMemRelease(pMem); - pMem->r = val; + sqlite3VdbeMemSetNull(pMem); + if( !sqlite3IsNaN(val) ){ + pMem->u.r = val; pMem->flags = MEM_Real; - pMem->type = SQLITE_FLOAT; } } #endif /* @@ -59692,14 +63627,15 @@ assert( (pMem->flags & MEM_RowSet)==0 ); sqlite3VdbeMemRelease(pMem); pMem->zMalloc = sqlite3DbMallocRaw(db, 64); if( db->mallocFailed ){ pMem->flags = MEM_Null; + pMem->szMalloc = 0; }else{ assert( pMem->zMalloc ); - pMem->u.pRowSet = sqlite3RowSetInit(db, pMem->zMalloc, - sqlite3DbMallocSize(db, pMem->zMalloc)); + pMem->szMalloc = sqlite3DbMallocSize(db, pMem->zMalloc); + pMem->u.pRowSet = sqlite3RowSetInit(db, pMem->zMalloc, pMem->szMalloc); assert( pMem->u.pRowSet!=0 ); pMem->flags = MEM_RowSet; } } @@ -59719,11 +63655,11 @@ return 0; } #ifdef SQLITE_DEBUG /* -** This routine prepares a memory cell for modication by breaking +** This routine prepares a memory cell for modification by breaking ** its link to a shallow copy and by marking any current shallow ** copies of this cell as invalid. ** ** This is used for testing and debugging only - to make sure shallow ** copies are not misused. @@ -59731,11 +63667,11 @@ SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe *pVdbe, Mem *pMem){ int i; Mem *pX; for(i=1, pX=&pVdbe->aMem[1]; i<=pVdbe->nMem; i++, pX++){ if( pX->pScopyFrom==pMem ){ - pX->flags |= MEM_Invalid; + pX->flags |= MEM_Undefined; pX->pScopyFrom = 0; } } pMem->pScopyFrom = 0; } @@ -59742,23 +63678,23 @@ #endif /* SQLITE_DEBUG */ /* ** Size of struct Mem not including the Mem.zMalloc member. */ -#define MEMCELLSIZE (size_t)(&(((Mem *)0)->zMalloc)) +#define MEMCELLSIZE offsetof(Mem,zMalloc) /* ** Make an shallow copy of pFrom into pTo. Prior contents of ** pTo are freed. The pFrom->z field is not duplicated. If ** pFrom->z is used, then pTo->z points to the same thing as pFrom->z ** and flags gets srcType (either MEM_Ephem or MEM_Static). */ SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem *pTo, const Mem *pFrom, int srcType){ assert( (pFrom->flags & MEM_RowSet)==0 ); - VdbeMemRelease(pTo); + assert( pTo->db==pFrom->db ); + if( VdbeMemDynamic(pTo) ) vdbeMemClearExternAndSetNull(pTo); memcpy(pTo, pFrom, MEMCELLSIZE); - pTo->xDel = 0; if( (pFrom->flags&MEM_Static)==0 ){ pTo->flags &= ~(MEM_Dyn|MEM_Static|MEM_Ephem); assert( srcType==MEM_Ephem || srcType==MEM_Static ); pTo->flags |= srcType; } @@ -59769,15 +63705,15 @@ ** freed before the copy is made. */ SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem *pTo, const Mem *pFrom){ int rc = SQLITE_OK; + assert( pTo->db==pFrom->db ); assert( (pFrom->flags & MEM_RowSet)==0 ); - VdbeMemRelease(pTo); + if( VdbeMemDynamic(pTo) ) vdbeMemClearExternAndSetNull(pTo); memcpy(pTo, pFrom, MEMCELLSIZE); pTo->flags &= ~MEM_Dyn; - if( pTo->flags&(MEM_Str|MEM_Blob) ){ if( 0==(pFrom->flags&MEM_Static) ){ pTo->flags |= MEM_Ephem; rc = sqlite3VdbeMemMakeWriteable(pTo); } @@ -59798,12 +63734,11 @@ assert( pFrom->db==0 || pTo->db==0 || pFrom->db==pTo->db ); sqlite3VdbeMemRelease(pTo); memcpy(pTo, pFrom, sizeof(Mem)); pFrom->flags = MEM_Null; - pFrom->xDel = 0; - pFrom->zMalloc = 0; + pFrom->szMalloc = 0; } /* ** Change the value of a Mem to be a string or a BLOB. ** @@ -59846,11 +63781,12 @@ } flags = (enc==0?MEM_Blob:MEM_Str); if( nByte<0 ){ assert( enc!=0 ); if( enc==SQLITE_UTF8 ){ - for(nByte=0; nByte<=iLimit && z[nByte]; nByte++){} + nByte = sqlite3Strlen30(z); + if( nByte>iLimit ) nByte = iLimit+1; }else{ for(nByte=0; nByte<=iLimit && (z[nByte] | z[nByte+1]); nByte+=2){} } flags |= MEM_Term; } @@ -59865,18 +63801,21 @@ nAlloc += (enc==SQLITE_UTF8?1:2); } if( nByte>iLimit ){ return SQLITE_TOOBIG; } - if( sqlite3VdbeMemGrow(pMem, nAlloc, 0) ){ + testcase( nAlloc==0 ); + testcase( nAlloc==31 ); + testcase( nAlloc==32 ); + if( sqlite3VdbeMemClearAndResize(pMem, MAX(nAlloc,32)) ){ return SQLITE_NOMEM; } memcpy(pMem->z, z, nAlloc); }else if( xDel==SQLITE_DYNAMIC ){ sqlite3VdbeMemRelease(pMem); pMem->zMalloc = pMem->z = (char *)z; - pMem->xDel = 0; + pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc); }else{ sqlite3VdbeMemRelease(pMem); pMem->z = (char *)z; pMem->xDel = xDel; flags |= ((xDel==SQLITE_STATIC)?MEM_Static:MEM_Dyn); @@ -59883,11 +63822,10 @@ } pMem->n = nByte; pMem->flags = flags; pMem->enc = (enc==0 ? SQLITE_UTF8 : enc); - pMem->type = (enc==0 ? SQLITE_BLOB : SQLITE_TEXT); #ifndef SQLITE_OMIT_UTF16 if( pMem->enc!=SQLITE_UTF8 && sqlite3VdbeMemHandleBom(pMem) ){ return SQLITE_NOMEM; } @@ -59898,148 +63836,39 @@ } return SQLITE_OK; } -/* -** Compare the values contained by the two memory cells, returning -** negative, zero or positive if pMem1 is less than, equal to, or greater -** than pMem2. Sorting order is NULL's first, followed by numbers (integers -** and reals) sorted numerically, followed by text ordered by the collating -** sequence pColl and finally blob's ordered by memcmp(). -** -** Two NULL values are considered equal by this function. -*/ -SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const CollSeq *pColl){ - int rc; - int f1, f2; - int combined_flags; - - f1 = pMem1->flags; - f2 = pMem2->flags; - combined_flags = f1|f2; - assert( (combined_flags & MEM_RowSet)==0 ); - - /* If one value is NULL, it is less than the other. If both values - ** are NULL, return 0. - */ - if( combined_flags&MEM_Null ){ - return (f2&MEM_Null) - (f1&MEM_Null); - } - - /* If one value is a number and the other is not, the number is less. - ** If both are numbers, compare as reals if one is a real, or as integers - ** if both values are integers. - */ - if( combined_flags&(MEM_Int|MEM_Real) ){ - double r1, r2; - if( (f1 & f2 & MEM_Int)!=0 ){ - if( pMem1->u.i < pMem2->u.i ) return -1; - if( pMem1->u.i > pMem2->u.i ) return 1; - return 0; - } - if( (f1&MEM_Real)!=0 ){ - r1 = pMem1->r; - }else if( (f1&MEM_Int)!=0 ){ - r1 = (double)pMem1->u.i; - }else{ - return 1; - } - if( (f2&MEM_Real)!=0 ){ - r2 = pMem2->r; - }else if( (f2&MEM_Int)!=0 ){ - r2 = (double)pMem2->u.i; - }else{ - return -1; - } - if( r1<r2 ) return -1; - if( r1>r2 ) return 1; - return 0; - } - - /* If one value is a string and the other is a blob, the string is less. - ** If both are strings, compare using the collating functions. - */ - if( combined_flags&MEM_Str ){ - if( (f1 & MEM_Str)==0 ){ - return 1; - } - if( (f2 & MEM_Str)==0 ){ - return -1; - } - - assert( pMem1->enc==pMem2->enc ); - assert( pMem1->enc==SQLITE_UTF8 || - pMem1->enc==SQLITE_UTF16LE || pMem1->enc==SQLITE_UTF16BE ); - - /* The collation sequence must be defined at this point, even if - ** the user deletes the collation sequence after the vdbe program is - ** compiled (this was not always the case). - */ - assert( !pColl || pColl->xCmp ); - - if( pColl ){ - if( pMem1->enc==pColl->enc ){ - /* The strings are already in the correct encoding. Call the - ** comparison function directly */ - return pColl->xCmp(pColl->pUser,pMem1->n,pMem1->z,pMem2->n,pMem2->z); - }else{ - const void *v1, *v2; - int n1, n2; - Mem c1; - Mem c2; - memset(&c1, 0, sizeof(c1)); - memset(&c2, 0, sizeof(c2)); - sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); - sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); - v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); - n1 = v1==0 ? 0 : c1.n; - v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); - n2 = v2==0 ? 0 : c2.n; - rc = pColl->xCmp(pColl->pUser, n1, v1, n2, v2); - sqlite3VdbeMemRelease(&c1); - sqlite3VdbeMemRelease(&c2); - return rc; - } - } - /* If a NULL pointer was passed as the collate function, fall through - ** to the blob case and use memcmp(). */ - } - - /* Both values must be blobs. Compare using memcmp(). */ - rc = memcmp(pMem1->z, pMem2->z, (pMem1->n>pMem2->n)?pMem2->n:pMem1->n); - if( rc==0 ){ - rc = pMem1->n - pMem2->n; - } - return rc; -} - /* ** Move data out of a btree key or data field and into a Mem structure. ** The data or key is taken from the entry that pCur is currently pointing ** to. offset and amt determine what portion of the data or key to retrieve. ** key is true to get the key or false to get data. The result is written ** into the pMem element. ** -** The pMem structure is assumed to be uninitialized. Any prior content -** is overwritten without being freed. +** The pMem object must have been initialized. This routine will use +** pMem->zMalloc to hold the content from the btree, if possible. New +** pMem->zMalloc space will be allocated if necessary. The calling routine +** is responsible for making sure that the pMem object is eventually +** destroyed. ** ** If this routine fails for any reason (malloc returns NULL or unable ** to read from the disk) then the pMem is left in an inconsistent state. */ SQLITE_PRIVATE int sqlite3VdbeMemFromBtree( BtCursor *pCur, /* Cursor pointing at record to retrieve. */ - int offset, /* Offset from the start of data to return bytes from. */ - int amt, /* Number of bytes to return. */ + u32 offset, /* Offset from the start of data to return bytes from. */ + u32 amt, /* Number of bytes to return. */ int key, /* If true, retrieve from the btree key, not data. */ Mem *pMem /* OUT: Return data in this Mem structure. */ ){ char *zData; /* Data from the btree layer */ - int available = 0; /* Number of bytes available on the local btree page */ + u32 available = 0; /* Number of bytes available on the local btree page */ int rc = SQLITE_OK; /* Return code */ assert( sqlite3BtreeCursorIsValid(pCur) ); + assert( !VdbeMemDynamic(pMem) ); /* Note: the calls to BtreeKeyFetch() and DataFetch() below assert() ** that both the BtShared and database handle mutexes are held. */ assert( (pMem->flags & MEM_RowSet)==0 ); if( key ){ @@ -60047,32 +63876,73 @@ }else{ zData = (char *)sqlite3BtreeDataFetch(pCur, &available); } assert( zData!=0 ); - if( offset+amt<=available && (pMem->flags&MEM_Dyn)==0 ){ - sqlite3VdbeMemRelease(pMem); + if( offset+amt<=available ){ pMem->z = &zData[offset]; pMem->flags = MEM_Blob|MEM_Ephem; - }else if( SQLITE_OK==(rc = sqlite3VdbeMemGrow(pMem, amt+2, 0)) ){ - pMem->flags = MEM_Blob|MEM_Dyn|MEM_Term; - pMem->enc = 0; - pMem->type = SQLITE_BLOB; - if( key ){ - rc = sqlite3BtreeKey(pCur, offset, amt, pMem->z); - }else{ - rc = sqlite3BtreeData(pCur, offset, amt, pMem->z); - } - pMem->z[amt] = 0; - pMem->z[amt+1] = 0; - if( rc!=SQLITE_OK ){ - sqlite3VdbeMemRelease(pMem); - } - } - pMem->n = amt; - - return rc; + pMem->n = (int)amt; + }else{ + pMem->flags = MEM_Null; + if( SQLITE_OK==(rc = sqlite3VdbeMemClearAndResize(pMem, amt+2)) ){ + if( key ){ + rc = sqlite3BtreeKey(pCur, offset, amt, pMem->z); + }else{ + rc = sqlite3BtreeData(pCur, offset, amt, pMem->z); + } + if( rc==SQLITE_OK ){ + pMem->z[amt] = 0; + pMem->z[amt+1] = 0; + pMem->flags = MEM_Blob|MEM_Term; + pMem->n = (int)amt; + }else{ + sqlite3VdbeMemRelease(pMem); + } + } + } + + return rc; +} + +/* +** The pVal argument is known to be a value other than NULL. +** Convert it into a string with encoding enc and return a pointer +** to a zero-terminated version of that string. +*/ +static SQLITE_NOINLINE const void *valueToText(sqlite3_value* pVal, u8 enc){ + assert( pVal!=0 ); + assert( pVal->db==0 || sqlite3_mutex_held(pVal->db->mutex) ); + assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) ); + assert( (pVal->flags & MEM_RowSet)==0 ); + assert( (pVal->flags & (MEM_Null))==0 ); + if( pVal->flags & (MEM_Blob|MEM_Str) ){ + pVal->flags |= MEM_Str; + if( pVal->flags & MEM_Zero ){ + sqlite3VdbeMemExpandBlob(pVal); + } + if( pVal->enc != (enc & ~SQLITE_UTF16_ALIGNED) ){ + sqlite3VdbeChangeEncoding(pVal, enc & ~SQLITE_UTF16_ALIGNED); + } + if( (enc & SQLITE_UTF16_ALIGNED)!=0 && 1==(1&SQLITE_PTR_TO_INT(pVal->z)) ){ + assert( (pVal->flags & (MEM_Ephem|MEM_Static))!=0 ); + if( sqlite3VdbeMemMakeWriteable(pVal)!=SQLITE_OK ){ + return 0; + } + } + sqlite3VdbeMemNulTerminate(pVal); /* IMP: R-31275-44060 */ + }else{ + sqlite3VdbeMemStringify(pVal, enc, 0); + assert( 0==(1&SQLITE_PTR_TO_INT(pVal->z)) ); + } + assert(pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) || pVal->db==0 + || pVal->db->mallocFailed ); + if( pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) ){ + return pVal->z; + }else{ + return 0; + } } /* This function is only available internally, it is not part of the ** external API. It works in a similar way to sqlite3_value_text(), ** except the data returned is in the encoding specified by the second @@ -60083,52 +63953,29 @@ ** If that is the case, then the result must be aligned on an even byte ** boundary. */ SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){ if( !pVal ) return 0; - assert( pVal->db==0 || sqlite3_mutex_held(pVal->db->mutex) ); assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) ); assert( (pVal->flags & MEM_RowSet)==0 ); - + if( (pVal->flags&(MEM_Str|MEM_Term))==(MEM_Str|MEM_Term) && pVal->enc==enc ){ + return pVal->z; + } if( pVal->flags&MEM_Null ){ return 0; } - assert( (MEM_Blob>>3) == MEM_Str ); - pVal->flags |= (pVal->flags & MEM_Blob)>>3; - ExpandBlob(pVal); - if( pVal->flags&MEM_Str ){ - sqlite3VdbeChangeEncoding(pVal, enc & ~SQLITE_UTF16_ALIGNED); - if( (enc & SQLITE_UTF16_ALIGNED)!=0 && 1==(1&SQLITE_PTR_TO_INT(pVal->z)) ){ - assert( (pVal->flags & (MEM_Ephem|MEM_Static))!=0 ); - if( sqlite3VdbeMemMakeWriteable(pVal)!=SQLITE_OK ){ - return 0; - } - } - sqlite3VdbeMemNulTerminate(pVal); /* IMP: R-31275-44060 */ - }else{ - assert( (pVal->flags&MEM_Blob)==0 ); - sqlite3VdbeMemStringify(pVal, enc); - assert( 0==(1&SQLITE_PTR_TO_INT(pVal->z)) ); - } - assert(pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) || pVal->db==0 - || pVal->db->mallocFailed ); - if( pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) ){ - return pVal->z; - }else{ - return 0; - } + return valueToText(pVal, enc); } /* ** Create a new sqlite3_value object. */ SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *db){ Mem *p = sqlite3DbMallocZero(db, sizeof(*p)); if( p ){ p->flags = MEM_Null; - p->type = SQLITE_NULL; p->db = db; } return p; } @@ -60149,11 +63996,11 @@ ** by calling sqlite3ValueNew(). ** ** Otherwise, if the second argument is non-zero, then this function is ** being called indirectly by sqlite3Stat4ProbeSetValue(). If it has not ** already been allocated, allocate the UnpackedRecord structure that -** that function will return to its caller here. Then return a pointer +** that function will return to its caller here. Then return a pointer to ** an sqlite3_value within the UnpackedRecord.a[] array. */ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 if( p ){ @@ -60161,24 +64008,22 @@ if( pRec==0 ){ Index *pIdx = p->pIdx; /* Index being probed */ int nByte; /* Bytes of space to allocate */ int i; /* Counter variable */ - int nCol = pIdx->nColumn+1; /* Number of index columns including rowid */ + int nCol = pIdx->nColumn; /* Number of index columns including rowid */ - nByte = sizeof(Mem) * nCol + sizeof(UnpackedRecord); + nByte = sizeof(Mem) * nCol + ROUND8(sizeof(UnpackedRecord)); pRec = (UnpackedRecord*)sqlite3DbMallocZero(db, nByte); if( pRec ){ - pRec->pKeyInfo = sqlite3IndexKeyinfo(p->pParse, pIdx); + pRec->pKeyInfo = sqlite3KeyInfoOfIndex(p->pParse, pIdx); if( pRec->pKeyInfo ){ - assert( pRec->pKeyInfo->nField+1==nCol ); - pRec->pKeyInfo->enc = ENC(db); - pRec->flags = UNPACKED_PREFIX_MATCH; - pRec->aMem = (Mem *)&pRec[1]; + assert( pRec->pKeyInfo->nField+pRec->pKeyInfo->nXField==nCol ); + assert( pRec->pKeyInfo->enc==ENC(db) ); + pRec->aMem = (Mem *)((u8*)pRec + ROUND8(sizeof(UnpackedRecord))); for(i=0; i<nCol; i++){ pRec->aMem[i].flags = MEM_Null; - pRec->aMem[i].type = SQLITE_NULL; pRec->aMem[i].db = db; } }else{ sqlite3DbFree(db, pRec); pRec = 0; @@ -60189,14 +64034,123 @@ } pRec->nField = p->iVal+1; return &pRec->aMem[p->iVal]; } -#endif +#else + UNUSED_PARAMETER(p); +#endif /* defined(SQLITE_ENABLE_STAT3_OR_STAT4) */ return sqlite3ValueNew(db); } +/* +** The expression object indicated by the second argument is guaranteed +** to be a scalar SQL function. If +** +** * all function arguments are SQL literals, +** * the SQLITE_FUNC_CONSTANT function flag is set, and +** * the SQLITE_FUNC_NEEDCOLL function flag is not set, +** +** then this routine attempts to invoke the SQL function. Assuming no +** error occurs, output parameter (*ppVal) is set to point to a value +** object containing the result before returning SQLITE_OK. +** +** Affinity aff is applied to the result of the function before returning. +** If the result is a text value, the sqlite3_value object uses encoding +** enc. +** +** If the conditions above are not met, this function returns SQLITE_OK +** and sets (*ppVal) to NULL. Or, if an error occurs, (*ppVal) is set to +** NULL and an SQLite error code returned. +*/ +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +static int valueFromFunction( + sqlite3 *db, /* The database connection */ + Expr *p, /* The expression to evaluate */ + u8 enc, /* Encoding to use */ + u8 aff, /* Affinity to use */ + sqlite3_value **ppVal, /* Write the new value here */ + struct ValueNewStat4Ctx *pCtx /* Second argument for valueNew() */ +){ + sqlite3_context ctx; /* Context object for function invocation */ + sqlite3_value **apVal = 0; /* Function arguments */ + int nVal = 0; /* Size of apVal[] array */ + FuncDef *pFunc = 0; /* Function definition */ + sqlite3_value *pVal = 0; /* New value */ + int rc = SQLITE_OK; /* Return code */ + int nName; /* Size of function name in bytes */ + ExprList *pList = 0; /* Function arguments */ + int i; /* Iterator variable */ + + assert( pCtx!=0 ); + assert( (p->flags & EP_TokenOnly)==0 ); + pList = p->x.pList; + if( pList ) nVal = pList->nExpr; + nName = sqlite3Strlen30(p->u.zToken); + pFunc = sqlite3FindFunction(db, p->u.zToken, nName, nVal, enc, 0); + assert( pFunc ); + if( (pFunc->funcFlags & SQLITE_FUNC_CONSTANT)==0 + || (pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) + ){ + return SQLITE_OK; + } + + if( pList ){ + apVal = (sqlite3_value**)sqlite3DbMallocZero(db, sizeof(apVal[0]) * nVal); + if( apVal==0 ){ + rc = SQLITE_NOMEM; + goto value_from_function_out; + } + for(i=0; i<nVal; i++){ + rc = sqlite3ValueFromExpr(db, pList->a[i].pExpr, enc, aff, &apVal[i]); + if( apVal[i]==0 || rc!=SQLITE_OK ) goto value_from_function_out; + } + } + + pVal = valueNew(db, pCtx); + if( pVal==0 ){ + rc = SQLITE_NOMEM; + goto value_from_function_out; + } + + assert( pCtx->pParse->rc==SQLITE_OK ); + memset(&ctx, 0, sizeof(ctx)); + ctx.pOut = pVal; + ctx.pFunc = pFunc; + pFunc->xFunc(&ctx, nVal, apVal); + if( ctx.isError ){ + rc = ctx.isError; + sqlite3ErrorMsg(pCtx->pParse, "%s", sqlite3_value_text(pVal)); + }else{ + sqlite3ValueApplyAffinity(pVal, aff, SQLITE_UTF8); + assert( rc==SQLITE_OK ); + rc = sqlite3VdbeChangeEncoding(pVal, enc); + if( rc==SQLITE_OK && sqlite3VdbeMemTooBig(pVal) ){ + rc = SQLITE_TOOBIG; + pCtx->pParse->nErr++; + } + } + pCtx->pParse->rc = rc; + + value_from_function_out: + if( rc!=SQLITE_OK ){ + pVal = 0; + } + if( apVal ){ + for(i=0; i<nVal; i++){ + sqlite3ValueFree(apVal[i]); + } + sqlite3DbFree(db, apVal); + } + + *ppVal = pVal; + return rc; +} +#else +# define valueFromFunction(a,b,c,d,e,f) SQLITE_OK +#endif /* defined(SQLITE_ENABLE_STAT3_OR_STAT4) */ + /* ** Extract a value from the supplied expression in the manner described ** above sqlite3ValueFromExpr(). Allocate the sqlite3_value object ** using valueNew(). ** @@ -60203,11 +64157,11 @@ ** If pCtx is NULL and an error occurs after the sqlite3_value object ** has been allocated, it is freed before returning. Or, if pCtx is not ** NULL, it is assumed that the caller will free any allocated object ** in all cases. */ -int valueFromExpr( +static int valueFromExpr( sqlite3 *db, /* The database connection */ Expr *pExpr, /* The expression to evaluate */ u8 enc, /* Encoding to use */ u8 affinity, /* Affinity to use */ sqlite3_value **ppVal, /* Write the new value here */ @@ -60222,21 +64176,29 @@ if( !pExpr ){ *ppVal = 0; return SQLITE_OK; } - op = pExpr->op; - - /* op can only be TK_REGISTER if we have compiled with SQLITE_ENABLE_STAT4. - ** The ifdef here is to enable us to achieve 100% branch test coverage even - ** when SQLITE_ENABLE_STAT4 is omitted. - */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - if( op==TK_REGISTER ) op = pExpr->op2; -#else + while( (op = pExpr->op)==TK_UPLUS ) pExpr = pExpr->pLeft; if( NEVER(op==TK_REGISTER) ) op = pExpr->op2; -#endif + + /* Compressed expressions only appear when parsing the DEFAULT clause + ** on a table column definition, and hence only when pCtx==0. This + ** check ensures that an EP_TokenOnly expression is never passed down + ** into valueFromFunction(). */ + assert( (pExpr->flags & EP_TokenOnly)==0 || pCtx==0 ); + + if( op==TK_CAST ){ + u8 aff = sqlite3AffinityType(pExpr->u.zToken,0); + rc = valueFromExpr(db, pExpr->pLeft, enc, aff, ppVal, pCtx); + testcase( rc!=SQLITE_OK ); + if( *ppVal ){ + sqlite3VdbeMemCast(*ppVal, aff, SQLITE_UTF8); + sqlite3ValueApplyAffinity(*ppVal, affinity, SQLITE_UTF8); + } + return rc; + } /* Handle negative integers in a single step. This is needed in the ** case when the value is -9223372036854775808. */ if( op==TK_UMINUS @@ -60254,11 +64216,10 @@ sqlite3VdbeMemSetInt64(pVal, (i64)pExpr->u.iValue*negInt); }else{ zVal = sqlite3MPrintf(db, "%s%s", zNeg, pExpr->u.zToken); if( zVal==0 ) goto no_mem; sqlite3ValueSetStr(pVal, -1, zVal, SQLITE_UTF8, SQLITE_DYNAMIC); - if( op==TK_FLOAT ) pVal->type = SQLITE_FLOAT; } if( (op==TK_INTEGER || op==TK_FLOAT ) && affinity==SQLITE_AFF_NONE ){ sqlite3ValueApplyAffinity(pVal, SQLITE_AFF_NUMERIC, SQLITE_UTF8); }else{ sqlite3ValueApplyAffinity(pVal, affinity, SQLITE_UTF8); @@ -60271,18 +64232,18 @@ /* This branch happens for multiple negative signs. Ex: -(-5) */ if( SQLITE_OK==sqlite3ValueFromExpr(db,pExpr->pLeft,enc,affinity,&pVal) && pVal!=0 ){ sqlite3VdbeMemNumerify(pVal); - if( pVal->u.i==SMALLEST_INT64 ){ - pVal->flags &= MEM_Int; - pVal->flags |= MEM_Real; - pVal->r = (double)LARGEST_INT64; + if( pVal->flags & MEM_Real ){ + pVal->u.r = -pVal->u.r; + }else if( pVal->u.i==SMALLEST_INT64 ){ + pVal->u.r = -(double)SMALLEST_INT64; + MemSetTypeFlag(pVal, MEM_Real); }else{ pVal->u.i = -pVal->u.i; } - pVal->r = -pVal->r; sqlite3ValueApplyAffinity(pVal, affinity, enc); } }else if( op==TK_NULL ){ pVal = valueNew(db, pCtx); if( pVal==0 ) goto no_mem; @@ -60300,13 +64261,16 @@ sqlite3VdbeMemSetStr(pVal, sqlite3HexToBlob(db, zVal, nVal), nVal/2, 0, SQLITE_DYNAMIC); } #endif - if( pVal ){ - sqlite3VdbeMemStoreType(pVal); +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + else if( op==TK_FUNCTION && pCtx!=0 ){ + rc = valueFromFunction(db, pExpr, enc, affinity, &pVal, pCtx); } +#endif + *ppVal = pVal; return rc; no_mem: db->mallocFailed = 1; @@ -60360,10 +64324,11 @@ int nVal; /* Bytes of space required for argv[0] */ int nRet; sqlite3 *db; u8 *aRet; + UNUSED_PARAMETER( argc ); iSerial = sqlite3VdbeSerialType(argv[0], file_format); nSerial = sqlite3VarintLen(iSerial); nVal = sqlite3VdbeSerialTypeLen(iSerial); db = sqlite3_context_db_handle(context); @@ -60371,12 +64336,12 @@ aRet = sqlite3DbMallocRaw(db, nRet); if( aRet==0 ){ sqlite3_result_error_nomem(context); }else{ aRet[0] = nSerial+1; - sqlite3PutVarint(&aRet[1], iSerial); - sqlite3VdbeSerialPut(&aRet[1+nSerial], nVal, argv[0], file_format); + putVarint32(&aRet[1], iSerial); + sqlite3VdbeSerialPut(&aRet[1+nSerial], argv[0], iSerial); sqlite3_result_blob(context, aRet, nRet, SQLITE_TRANSIENT); sqlite3DbFree(db, aRet); } } @@ -60392,10 +64357,72 @@ FuncDef *aFunc = (FuncDef*)&GLOBAL(FuncDef, aAnalyzeTableFuncs); for(i=0; i<ArraySize(aAnalyzeTableFuncs); i++){ sqlite3FuncDefInsert(pHash, &aFunc[i]); } } + +/* +** Attempt to extract a value from pExpr and use it to construct *ppVal. +** +** If pAlloc is not NULL, then an UnpackedRecord object is created for +** pAlloc if one does not exist and the new value is added to the +** UnpackedRecord object. +** +** A value is extracted in the following cases: +** +** * (pExpr==0). In this case the value is assumed to be an SQL NULL, +** +** * The expression is a bound variable, and this is a reprepare, or +** +** * The expression is a literal value. +** +** On success, *ppVal is made to point to the extracted value. The caller +** is responsible for ensuring that the value is eventually freed. +*/ +static int stat4ValueFromExpr( + Parse *pParse, /* Parse context */ + Expr *pExpr, /* The expression to extract a value from */ + u8 affinity, /* Affinity to use */ + struct ValueNewStat4Ctx *pAlloc,/* How to allocate space. Or NULL */ + sqlite3_value **ppVal /* OUT: New value object (or NULL) */ +){ + int rc = SQLITE_OK; + sqlite3_value *pVal = 0; + sqlite3 *db = pParse->db; + + /* Skip over any TK_COLLATE nodes */ + pExpr = sqlite3ExprSkipCollate(pExpr); + + if( !pExpr ){ + pVal = valueNew(db, pAlloc); + if( pVal ){ + sqlite3VdbeMemSetNull((Mem*)pVal); + } + }else if( pExpr->op==TK_VARIABLE + || NEVER(pExpr->op==TK_REGISTER && pExpr->op2==TK_VARIABLE) + ){ + Vdbe *v; + int iBindVar = pExpr->iColumn; + sqlite3VdbeSetVarmask(pParse->pVdbe, iBindVar); + if( (v = pParse->pReprepare)!=0 ){ + pVal = valueNew(db, pAlloc); + if( pVal ){ + rc = sqlite3VdbeMemCopy((Mem*)pVal, &v->aVar[iBindVar-1]); + if( rc==SQLITE_OK ){ + sqlite3ValueApplyAffinity(pVal, affinity, ENC(db)); + } + pVal->db = pParse->db; + } + } + }else{ + rc = valueFromExpr(db, pExpr, ENC(db), affinity, &pVal, pAlloc); + } + + assert( pVal==0 || pVal->db==db ); + *ppVal = pVal; + return rc; +} /* ** This function is used to allocate and populate UnpackedRecord ** structures intended to be compared against sample index keys stored ** in the sqlite_stat4 table. @@ -60432,57 +64459,90 @@ Expr *pExpr, /* The expression to extract a value from */ u8 affinity, /* Affinity to use */ int iVal, /* Array element to populate */ int *pbOk /* OUT: True if value was extracted */ ){ - int rc = SQLITE_OK; + int rc; sqlite3_value *pVal = 0; - sqlite3 *db = pParse->db; - - struct ValueNewStat4Ctx alloc; + alloc.pParse = pParse; alloc.pIdx = pIdx; alloc.ppRec = ppRec; alloc.iVal = iVal; - /* Skip over any TK_COLLATE nodes */ - pExpr = sqlite3ExprSkipCollate(pExpr); - - if( !pExpr ){ - pVal = valueNew(db, &alloc); - if( pVal ){ - sqlite3VdbeMemSetNull((Mem*)pVal); - *pbOk = 1; - } - }else if( pExpr->op==TK_VARIABLE - || (pExpr->op==TK_REGISTER && pExpr->op2==TK_VARIABLE) - ){ - Vdbe *v; - int iBindVar = pExpr->iColumn; - sqlite3VdbeSetVarmask(pParse->pVdbe, iBindVar); - if( (v = pParse->pReprepare)!=0 ){ - pVal = valueNew(db, &alloc); - if( pVal ){ - rc = sqlite3VdbeMemCopy((Mem*)pVal, &v->aVar[iBindVar-1]); - if( rc==SQLITE_OK ){ - sqlite3ValueApplyAffinity(pVal, affinity, ENC(db)); - } - pVal->db = pParse->db; - *pbOk = 1; - sqlite3VdbeMemStoreType((Mem*)pVal); - } - }else{ - *pbOk = 0; - } - }else{ - rc = valueFromExpr(db, pExpr, ENC(db), affinity, &pVal, &alloc); - *pbOk = (pVal!=0); - } - - assert( pVal==0 || pVal->db==db ); - return rc; + rc = stat4ValueFromExpr(pParse, pExpr, affinity, &alloc, &pVal); + assert( pVal==0 || pVal->db==pParse->db ); + *pbOk = (pVal!=0); + return rc; +} + +/* +** Attempt to extract a value from expression pExpr using the methods +** as described for sqlite3Stat4ProbeSetValue() above. +** +** If successful, set *ppVal to point to a new value object and return +** SQLITE_OK. If no value can be extracted, but no other error occurs +** (e.g. OOM), return SQLITE_OK and set *ppVal to NULL. Or, if an error +** does occur, return an SQLite error code. The final value of *ppVal +** is undefined in this case. +*/ +SQLITE_PRIVATE int sqlite3Stat4ValueFromExpr( + Parse *pParse, /* Parse context */ + Expr *pExpr, /* The expression to extract a value from */ + u8 affinity, /* Affinity to use */ + sqlite3_value **ppVal /* OUT: New value object (or NULL) */ +){ + return stat4ValueFromExpr(pParse, pExpr, affinity, 0, ppVal); +} + +/* +** Extract the iCol-th column from the nRec-byte record in pRec. Write +** the column value into *ppVal. If *ppVal is initially NULL then a new +** sqlite3_value object is allocated. +** +** If *ppVal is initially NULL then the caller is responsible for +** ensuring that the value written into *ppVal is eventually freed. +*/ +SQLITE_PRIVATE int sqlite3Stat4Column( + sqlite3 *db, /* Database handle */ + const void *pRec, /* Pointer to buffer containing record */ + int nRec, /* Size of buffer pRec in bytes */ + int iCol, /* Column to extract */ + sqlite3_value **ppVal /* OUT: Extracted value */ +){ + u32 t; /* a column type code */ + int nHdr; /* Size of the header in the record */ + int iHdr; /* Next unread header byte */ + int iField; /* Next unread data byte */ + int szField; /* Size of the current data field */ + int i; /* Column index */ + u8 *a = (u8*)pRec; /* Typecast byte array */ + Mem *pMem = *ppVal; /* Write result into this Mem object */ + + assert( iCol>0 ); + iHdr = getVarint32(a, nHdr); + if( nHdr>nRec || iHdr>=nHdr ) return SQLITE_CORRUPT_BKPT; + iField = nHdr; + for(i=0; i<=iCol; i++){ + iHdr += getVarint32(&a[iHdr], t); + testcase( iHdr==nHdr ); + testcase( iHdr==nHdr+1 ); + if( iHdr>nHdr ) return SQLITE_CORRUPT_BKPT; + szField = sqlite3VdbeSerialTypeLen(t); + iField += szField; + } + testcase( iField==nRec ); + testcase( iField==nRec+1 ); + if( iField>nRec ) return SQLITE_CORRUPT_BKPT; + if( pMem==0 ){ + pMem = *ppVal = sqlite3ValueNew(db); + if( pMem==0 ) return SQLITE_NOMEM; + } + sqlite3VdbeSerialGet(&a[iField-szField], t, pMem); + pMem->enc = ENC(db); + return SQLITE_OK; } /* ** Unless it is NULL, the argument must be an UnpackedRecord object returned ** by an earlier call to sqlite3Stat4ProbeSetValue(). This call deletes @@ -60489,17 +64549,17 @@ ** the object. */ SQLITE_PRIVATE void sqlite3Stat4ProbeFree(UnpackedRecord *pRec){ if( pRec ){ int i; - int nCol = pRec->pKeyInfo->nField+1; + int nCol = pRec->pKeyInfo->nField+pRec->pKeyInfo->nXField; Mem *aMem = pRec->aMem; sqlite3 *db = aMem[0].db; for(i=0; i<nCol; i++){ - sqlite3DbFree(db, aMem[i].zMalloc); + if( aMem[i].szMalloc ) sqlite3DbFree(db, aMem[i].zMalloc); } - sqlite3DbFree(db, pRec->pKeyInfo); + sqlite3KeyInfoUnref(pRec->pKeyInfo); sqlite3DbFree(db, pRec); } } #endif /* ifdef SQLITE_ENABLE_STAT4 */ @@ -60553,19 +64613,18 @@ ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains code used for creating, destroying, and populating -** a VDBE (or an "sqlite3_stmt" as it is known to the outside world.) Prior -** to version 2.8.7, all this code was combined into the vdbe.c source file. -** But that file was getting too big so this subroutines were split out. +** a VDBE (or an "sqlite3_stmt" as it is known to the outside world.) */ /* ** Create a new virtual database engine. */ -SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(sqlite3 *db){ +SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse *pParse){ + sqlite3 *db = pParse->db; Vdbe *p; p = sqlite3DbMallocZero(db, sizeof(Vdbe) ); if( p==0 ) return 0; p->db = db; if( db->pVdbe ){ @@ -60573,10 +64632,14 @@ } p->pNext = db->pVdbe; p->pPrev = 0; db->pVdbe = p; p->magic = VDBE_MAGIC_INIT; + p->pParse = pParse; + assert( pParse->aLabel==0 ); + assert( pParse->nLabel==0 ); + assert( pParse->nOpAlloc==0 ); return p; } /* ** Remember the SQL string for a prepared statement. @@ -60593,11 +64656,11 @@ } /* ** Return the SQL associated with a prepared statement */ -SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt){ +SQLITE_API const char *SQLITE_STDCALL sqlite3_sql(sqlite3_stmt *pStmt){ Vdbe *p = (Vdbe *)pStmt; return (p && p->isPrepareV2) ? p->zSql : 0; } /* @@ -60619,38 +64682,58 @@ pA->zSql = pB->zSql; pB->zSql = zTmp; pB->isPrepareV2 = pA->isPrepareV2; } -#ifdef SQLITE_DEBUG -/* -** Turn tracing on or off -*/ -SQLITE_PRIVATE void sqlite3VdbeTrace(Vdbe *p, FILE *trace){ - p->trace = trace; -} -#endif - -/* -** Resize the Vdbe.aOp array so that it is at least one op larger than -** it was. +/* +** Resize the Vdbe.aOp array so that it is at least nOp elements larger +** than its current size. nOp is guaranteed to be less than or equal +** to 1024/sizeof(Op). ** ** If an out-of-memory error occurs while resizing the array, return -** SQLITE_NOMEM. In this case Vdbe.aOp and Vdbe.nOpAlloc remain +** SQLITE_NOMEM. In this case Vdbe.aOp and Parse.nOpAlloc remain ** unchanged (this is so that any opcodes already allocated can be ** correctly deallocated along with the rest of the Vdbe). */ -static int growOpArray(Vdbe *p){ +static int growOpArray(Vdbe *v, int nOp){ VdbeOp *pNew; + Parse *p = v->pParse; + + /* The SQLITE_TEST_REALLOC_STRESS compile-time option is designed to force + ** more frequent reallocs and hence provide more opportunities for + ** simulated OOM faults. SQLITE_TEST_REALLOC_STRESS is generally used + ** during testing only. With SQLITE_TEST_REALLOC_STRESS grow the op array + ** by the minimum* amount required until the size reaches 512. Normal + ** operation (without SQLITE_TEST_REALLOC_STRESS) is to double the current + ** size of the op array or add 1KB of space, whichever is smaller. */ +#ifdef SQLITE_TEST_REALLOC_STRESS + int nNew = (p->nOpAlloc>=512 ? p->nOpAlloc*2 : p->nOpAlloc+nOp); +#else int nNew = (p->nOpAlloc ? p->nOpAlloc*2 : (int)(1024/sizeof(Op))); - pNew = sqlite3DbRealloc(p->db, p->aOp, nNew*sizeof(Op)); + UNUSED_PARAMETER(nOp); +#endif + + assert( nOp<=(1024/sizeof(Op)) ); + assert( nNew>=(p->nOpAlloc+nOp) ); + pNew = sqlite3DbRealloc(p->db, v->aOp, nNew*sizeof(Op)); if( pNew ){ p->nOpAlloc = sqlite3DbMallocSize(p->db, pNew)/sizeof(Op); - p->aOp = pNew; + v->aOp = pNew; } return (pNew ? SQLITE_OK : SQLITE_NOMEM); } + +#ifdef SQLITE_DEBUG +/* This routine is just a convenient place to set a breakpoint that will +** fire after each opcode is inserted and displayed using +** "PRAGMA vdbe_addoptrace=on". +*/ +static void test_addop_breakpoint(void){ + static int n = 0; + n++; +} +#endif /* ** Add a new instruction to the list of instructions current in the ** VDBE. Return the address of the new instruction. ** @@ -60671,12 +64754,12 @@ VdbeOp *pOp; i = p->nOp; assert( p->magic==VDBE_MAGIC_INIT ); assert( op>0 && op<0xff ); - if( p->nOpAlloc<=i ){ - if( growOpArray(p) ){ + if( p->pParse->nOpAlloc<=i ){ + if( growOpArray(p, 1) ){ return 1; } } p->nOp++; pOp = &p->aOp[i]; @@ -60685,19 +64768,34 @@ pOp->p1 = p1; pOp->p2 = p2; pOp->p3 = p3; pOp->p4.p = 0; pOp->p4type = P4_NOTUSED; -#ifdef SQLITE_DEBUG +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS pOp->zComment = 0; +#endif +#ifdef SQLITE_DEBUG if( p->db->flags & SQLITE_VdbeAddopTrace ){ + int jj, kk; + Parse *pParse = p->pParse; + for(jj=kk=0; jj<SQLITE_N_COLCACHE; jj++){ + struct yColCache *x = pParse->aColCache + jj; + if( x->iLevel>pParse->iCacheLevel || x->iReg==0 ) continue; + printf(" r[%d]={%d:%d}", x->iReg, x->iTable, x->iColumn); + kk++; + } + if( kk ) printf("\n"); sqlite3VdbePrintOp(0, i, &p->aOp[i]); + test_addop_breakpoint(); } #endif #ifdef VDBE_PROFILE pOp->cycles = 0; pOp->cnt = 0; +#endif +#ifdef SQLITE_VDBE_COVERAGE + pOp->iSrcLine = 0; #endif return i; } SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe *p, int op){ return sqlite3VdbeAddOp3(p, op, 0, 0, 0); @@ -60770,13 +64868,14 @@ ** always negative and P2 values are suppose to be non-negative. ** Hence, a negative P2 value is a label that has yet to be resolved. ** ** Zero is returned if a malloc() fails. */ -SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe *p){ +SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe *v){ + Parse *p = v->pParse; int i = p->nLabel++; - assert( p->magic==VDBE_MAGIC_INIT ); + assert( v->magic==VDBE_MAGIC_INIT ); if( (i & (i-1))==0 ){ p->aLabel = sqlite3DbReallocOrFree(p->db, p->aLabel, (i*2+1)*sizeof(p->aLabel[0])); } if( p->aLabel ){ @@ -60788,17 +64887,19 @@ /* ** Resolve label "x" to be the address of the next instruction to ** be inserted. The parameter "x" must have been obtained from ** a prior call to sqlite3VdbeMakeLabel(). */ -SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *p, int x){ +SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){ + Parse *p = v->pParse; int j = -1-x; - assert( p->magic==VDBE_MAGIC_INIT ); + assert( v->magic==VDBE_MAGIC_INIT ); assert( j<p->nLabel ); - if( j>=0 && p->aLabel ){ - p->aLabel[j] = p->nOp; + if( ALWAYS(j>=0) && p->aLabel ){ + p->aLabel[j] = v->nOp; } + p->iFixedOp = v->nOp - 1; } /* ** Mark the VDBE as one that can only be run one time. */ @@ -60896,36 +64997,39 @@ ** ** assert( sqlite3VdbeAssertMayAbort(pParse->pVdbe, pParse->mayAbort) ); */ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ int hasAbort = 0; + int hasFkCounter = 0; Op *pOp; VdbeOpIter sIter; memset(&sIter, 0, sizeof(sIter)); sIter.v = v; while( (pOp = opIterNext(&sIter))!=0 ){ int opcode = pOp->opcode; if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename -#ifndef SQLITE_OMIT_FOREIGN_KEY - || (opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1) -#endif || ((opcode==OP_Halt || opcode==OP_HaltIfNull) && ((pOp->p1&0xff)==SQLITE_CONSTRAINT && pOp->p2==OE_Abort)) ){ hasAbort = 1; break; } +#ifndef SQLITE_OMIT_FOREIGN_KEY + if( opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1 ){ + hasFkCounter = 1; + } +#endif } sqlite3DbFree(v->db, sIter.apSub); /* Return true if hasAbort==mayAbort. Or if a malloc failure occurred. ** If malloc failed, then the while() loop above may not have iterated ** through all opcodes and hasAbort may be set incorrectly. Return ** true for this case to prevent the assert() in the callers frame ** from failing. */ - return ( v->db->mallocFailed || hasAbort==mayAbort ); + return ( v->db->mallocFailed || hasAbort==mayAbort || hasFkCounter ); } #endif /* SQLITE_DEBUG - the sqlite3AssertMayAbort() function */ /* ** Loop through the program looking for P2 values that are negative @@ -60942,11 +65046,12 @@ */ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ int i; int nMaxArgs = *pMaxFuncArgs; Op *pOp; - int *aLabel = p->aLabel; + Parse *pParse = p->pParse; + int *aLabel = pParse->aLabel; p->readOnly = 1; p->bIsReader = 0; for(pOp=p->aOp, i=p->nOp-1; i>=0; i--, pOp++){ u8 opcode = pOp->opcode; @@ -60989,32 +65094,35 @@ if( n>nMaxArgs ) nMaxArgs = n; break; } #endif case OP_Next: + case OP_NextIfOpen: case OP_SorterNext: { pOp->p4.xAdvance = sqlite3BtreeNext; pOp->p4type = P4_ADVANCE; break; } - case OP_Prev: { + case OP_Prev: + case OP_PrevIfOpen: { pOp->p4.xAdvance = sqlite3BtreePrevious; pOp->p4type = P4_ADVANCE; break; } } pOp->opflags = sqlite3OpcodeProperty[opcode]; if( (pOp->opflags & OPFLG_JUMP)!=0 && pOp->p2<0 ){ - assert( -1-pOp->p2<p->nLabel ); + assert( -1-pOp->p2<pParse->nLabel ); pOp->p2 = aLabel[-1-pOp->p2]; } } - sqlite3DbFree(p->db, p->aLabel); - p->aLabel = 0; + sqlite3DbFree(p->db, pParse->aLabel); + pParse->aLabel = 0; + pParse->nLabel = 0; *pMaxFuncArgs = nMaxArgs; - assert( p->bIsReader!=0 || p->btreeMask==0 ); + assert( p->bIsReader!=0 || DbMaskAllZero(p->btreeMask) ); } /* ** Return the address of the next instruction to be inserted. */ @@ -61037,11 +65145,11 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg){ VdbeOp *aOp = p->aOp; assert( aOp && !p->db->mallocFailed ); /* Check that sqlite3VdbeUsesBtree() was not called on this VM */ - assert( p->btreeMask==0 ); + assert( DbMaskAllZero(p->btreeMask) ); resolveP2Values(p, pnMaxArg); *pnOp = p->nOp; p->aOp = 0; return aOp; @@ -61049,14 +65157,14 @@ /* ** Add a whole list of operations to the operation stack. Return the ** address of the first operation added. */ -SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe *p, int nOp, VdbeOpList const *aOp){ +SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe *p, int nOp, VdbeOpList const *aOp, int iLineno){ int addr; assert( p->magic==VDBE_MAGIC_INIT ); - if( p->nOp + nOp > p->nOpAlloc && growOpArray(p) ){ + if( p->nOp + nOp > p->pParse->nOpAlloc && growOpArray(p, nOp) ){ return 0; } addr = p->nOp; if( ALWAYS(nOp>0) ){ int i; @@ -61064,30 +65172,66 @@ for(i=0; i<nOp; i++, pIn++){ int p2 = pIn->p2; VdbeOp *pOut = &p->aOp[i+addr]; pOut->opcode = pIn->opcode; pOut->p1 = pIn->p1; - if( p2<0 && (sqlite3OpcodeProperty[pOut->opcode] & OPFLG_JUMP)!=0 ){ + if( p2<0 ){ + assert( sqlite3OpcodeProperty[pOut->opcode] & OPFLG_JUMP ); pOut->p2 = addr + ADDR(p2); }else{ pOut->p2 = p2; } pOut->p3 = pIn->p3; pOut->p4type = P4_NOTUSED; pOut->p4.p = 0; pOut->p5 = 0; -#ifdef SQLITE_DEBUG +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS pOut->zComment = 0; +#endif +#ifdef SQLITE_VDBE_COVERAGE + pOut->iSrcLine = iLineno+i; +#else + (void)iLineno; +#endif +#ifdef SQLITE_DEBUG if( p->db->flags & SQLITE_VdbeAddopTrace ){ sqlite3VdbePrintOp(0, i+addr, &p->aOp[i+addr]); } #endif } p->nOp += nOp; } return addr; } + +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) +/* +** Add an entry to the array of counters managed by sqlite3_stmt_scanstatus(). +*/ +SQLITE_PRIVATE void sqlite3VdbeScanStatus( + Vdbe *p, /* VM to add scanstatus() to */ + int addrExplain, /* Address of OP_Explain (or 0) */ + int addrLoop, /* Address of loop counter */ + int addrVisit, /* Address of rows visited counter */ + LogEst nEst, /* Estimated number of output rows */ + const char *zName /* Name of table or index being scanned */ +){ + int nByte = (p->nScan+1) * sizeof(ScanStatus); + ScanStatus *aNew; + aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte); + if( aNew ){ + ScanStatus *pNew = &aNew[p->nScan++]; + pNew->addrExplain = addrExplain; + pNew->addrLoop = addrLoop; + pNew->addrVisit = addrVisit; + pNew->nEst = nEst; + pNew->zName = sqlite3DbStrDup(p->db, zName); + p->aScan = aNew; + } +} +#endif + /* ** Change the value of the P1 operand for a specific instruction. ** This routine is useful when a large program is loaded from a ** static array using sqlite3VdbeAddOpList but we want to make a @@ -61136,11 +65280,12 @@ /* ** Change the P2 operand of instruction addr so that it points to ** the address of the next instruction to be coded. */ SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe *p, int addr){ - if( ALWAYS(addr>=0) ) sqlite3VdbeChangeP2(p, addr, p->nOp); + sqlite3VdbeChangeP2(p, addr, p->nOp); + p->pParse->iFixedOp = p->nOp - 1; } /* ** If the input FuncDef structure is ephemeral, then free it. If @@ -61162,15 +65307,17 @@ assert( db ); switch( p4type ){ case P4_REAL: case P4_INT64: case P4_DYNAMIC: - case P4_KEYINFO: - case P4_INTARRAY: - case P4_KEYINFO_HANDOFF: { + case P4_INTARRAY: { sqlite3DbFree(db, p4); break; + } + case P4_KEYINFO: { + if( db->pnBytesFreed==0 ) sqlite3KeyInfoUnref((KeyInfo*)p4); + break; } case P4_MPRINTF: { if( db->pnBytesFreed==0 ) sqlite3_free(p4); break; } @@ -61181,11 +65328,11 @@ case P4_MEM: { if( db->pnBytesFreed==0 ){ sqlite3ValueFree((sqlite3_value*)p4); }else{ Mem *p = (Mem*)p4; - sqlite3DbFree(db, p->zMalloc); + if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); sqlite3DbFree(db, p); } break; } case P4_VTAB : { @@ -61204,11 +65351,11 @@ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ if( aOp ){ Op *pOp; for(pOp=aOp; pOp<&aOp[nOp]; pOp++){ freeP4(db, pOp->p4type, pOp->p4.p); -#ifdef SQLITE_DEBUG +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS sqlite3DbFree(db, pOp->zComment); #endif } } sqlite3DbFree(db, aOp); @@ -61226,16 +65373,30 @@ /* ** Change the opcode at addr into OP_Noop */ SQLITE_PRIVATE void sqlite3VdbeChangeToNoop(Vdbe *p, int addr){ - if( p->aOp ){ + if( addr<p->nOp ){ VdbeOp *pOp = &p->aOp[addr]; sqlite3 *db = p->db; freeP4(db, pOp->p4type, pOp->p4.p); memset(pOp, 0, sizeof(pOp[0])); pOp->opcode = OP_Noop; + if( addr==p->nOp-1 ) p->nOp--; + } +} + +/* +** If the last opcode is "op" and it is not a jump destination, +** then remove it. Return true if and only if an opcode was removed. +*/ +SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){ + if( (p->nOp-1)>(p->pParse->iFixedOp) && p->aOp[p->nOp-1].opcode==op ){ + sqlite3VdbeChangeToNoop(p, p->nOp-1); + return 1; + }else{ + return 0; } } /* ** Change the value of the P4 operand for a specific instruction. @@ -61245,18 +65406,10 @@ ** ** If n>=0 then the P4 operand is dynamic, meaning that a copy of ** the string is made into memory obtained from sqlite3_malloc(). ** A value of n==0 means copy bytes of zP4 up to and including the ** first null byte. If n>0 then copy n+1 bytes of zP4. -** -** If n==P4_KEYINFO it means that zP4 is a pointer to a KeyInfo structure. -** A copy is made of the KeyInfo structure into memory obtained from -** sqlite3_malloc, to be freed when the Vdbe is finalized. -** n==P4_KEYINFO_HANDOFF indicates that zP4 points to a KeyInfo structure -** stored in memory that the caller has obtained from sqlite3_malloc. The -** caller should not free the allocation, it will be freed when the Vdbe is -** finalized. ** ** Other values of n (P4_STATIC, P4_COLLSEQ etc.) indicate that zP4 points ** to a string or structure that is guaranteed to exist for the lifetime of ** the Vdbe. In these cases we can just copy the pointer. ** @@ -61267,11 +65420,11 @@ sqlite3 *db; assert( p!=0 ); db = p->db; assert( p->magic==VDBE_MAGIC_INIT ); if( p->aOp==0 || db->mallocFailed ){ - if ( n!=P4_KEYINFO && n!=P4_VTAB ) { + if( n!=P4_VTAB ){ freeP4(db, n, (void*)*(char**)&zP4); } return; } assert( p->nOp>0 ); @@ -61278,11 +65431,13 @@ assert( addr<p->nOp ); if( addr<0 ){ addr = p->nOp - 1; } pOp = &p->aOp[addr]; - assert( pOp->p4type==P4_NOTUSED || pOp->p4type==P4_INT32 ); + assert( pOp->p4type==P4_NOTUSED + || pOp->p4type==P4_INT32 + || pOp->p4type==P4_KEYINFO ); freeP4(db, pOp->p4type, pOp->p4.p); pOp->p4.p = 0; if( n==P4_INT32 ){ /* Note: this cast is safe, because the origin data point was an int ** that was cast to a (const char *). */ @@ -61290,23 +65445,10 @@ pOp->p4type = P4_INT32; }else if( zP4==0 ){ pOp->p4.p = 0; pOp->p4type = P4_NOTUSED; }else if( n==P4_KEYINFO ){ - KeyInfo *pOrig, *pNew; - - pOrig = (KeyInfo*)zP4; - pOp->p4.pKeyInfo = pNew = sqlite3KeyInfoAlloc(db, pOrig->nField); - if( pNew ){ - memcpy(pNew->aColl, pOrig->aColl, pOrig->nField*sizeof(pNew->aColl[0])); - memcpy(pNew->aSortOrder, pOrig->aSortOrder, pOrig->nField); - pOp->p4type = P4_KEYINFO; - }else{ - p->db->mallocFailed = 1; - pOp->p4type = P4_NOTUSED; - } - }else if( n==P4_KEYINFO_HANDOFF ){ pOp->p4.p = (void*)zP4; pOp->p4type = P4_KEYINFO; }else if( n==P4_VTAB ){ pOp->p4.p = (void*)zP4; pOp->p4type = P4_VTAB; @@ -61320,11 +65462,23 @@ pOp->p4.z = sqlite3DbStrNDup(p->db, zP4, n); pOp->p4type = P4_DYNAMIC; } } -#ifndef NDEBUG +/* +** Set the P4 on the most recently added opcode to the KeyInfo for the +** index given. +*/ +SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse *pParse, Index *pIdx){ + Vdbe *v = pParse->pVdbe; + assert( v!=0 ); + assert( pIdx!=0 ); + sqlite3VdbeChangeP4(v, -1, (char*)sqlite3KeyInfoOfIndex(pParse, pIdx), + P4_KEYINFO); +} + +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS /* ** Change the comment on the most recently coded instruction. Or ** insert a No-op and add the comment to that new instruction. This ** makes the code easier to read during debugging. None of this happens ** in a production build. @@ -61355,49 +65509,138 @@ va_end(ap); } } #endif /* NDEBUG */ +#ifdef SQLITE_VDBE_COVERAGE +/* +** Set the value if the iSrcLine field for the previously coded instruction. +*/ +SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe *v, int iLine){ + sqlite3VdbeGetOp(v,-1)->iSrcLine = iLine; +} +#endif /* SQLITE_VDBE_COVERAGE */ + /* ** Return the opcode for a given address. If the address is -1, then ** return the most recently inserted opcode. ** ** If a memory allocation error has occurred prior to the calling of this ** routine, then a pointer to a dummy VdbeOp will be returned. That opcode ** is readable but not writable, though it is cast to a writable value. ** The return of a dummy opcode allows the call to continue functioning -** after a OOM fault without having to check to see if the return from +** after an OOM fault without having to check to see if the return from ** this routine is a valid pointer. But because the dummy.opcode is 0, ** dummy will never be written to. This is verified by code inspection and ** by running with Valgrind. -** -** About the #ifdef SQLITE_OMIT_TRACE: Normally, this routine is never called -** unless p->nOp>0. This is because in the absense of SQLITE_OMIT_TRACE, -** an OP_Trace instruction is always inserted by sqlite3VdbeGet() as soon as -** a new VDBE is created. So we are free to set addr to p->nOp-1 without -** having to double-check to make sure that the result is non-negative. But -** if SQLITE_OMIT_TRACE is defined, the OP_Trace is omitted and we do need to -** check the value of p->nOp-1 before continuing. */ SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){ /* C89 specifies that the constant "dummy" will be initialized to all ** zeros, which is correct. MSVC generates a warning, nevertheless. */ static VdbeOp dummy; /* Ignore the MSVC warning about no initializer */ assert( p->magic==VDBE_MAGIC_INIT ); if( addr<0 ){ -#ifdef SQLITE_OMIT_TRACE - if( p->nOp==0 ) return (VdbeOp*)&dummy; -#endif addr = p->nOp - 1; } assert( (addr>=0 && addr<p->nOp) || p->db->mallocFailed ); if( p->db->mallocFailed ){ return (VdbeOp*)&dummy; }else{ return &p->aOp[addr]; } } + +#if defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) +/* +** Return an integer value for one of the parameters to the opcode pOp +** determined by character c. +*/ +static int translateP(char c, const Op *pOp){ + if( c=='1' ) return pOp->p1; + if( c=='2' ) return pOp->p2; + if( c=='3' ) return pOp->p3; + if( c=='4' ) return pOp->p4.i; + return pOp->p5; +} + +/* +** Compute a string for the "comment" field of a VDBE opcode listing. +** +** The Synopsis: field in comments in the vdbe.c source file gets converted +** to an extra string that is appended to the sqlite3OpcodeName(). In the +** absence of other comments, this synopsis becomes the comment on the opcode. +** Some translation occurs: +** +** "PX" -> "r[X]" +** "PX@PY" -> "r[X..X+Y-1]" or "r[x]" if y is 0 or 1 +** "PX@PY+1" -> "r[X..X+Y]" or "r[x]" if y is 0 +** "PY..PY" -> "r[X..Y]" or "r[x]" if y<=x +*/ +static int displayComment( + const Op *pOp, /* The opcode to be commented */ + const char *zP4, /* Previously obtained value for P4 */ + char *zTemp, /* Write result here */ + int nTemp /* Space available in zTemp[] */ +){ + const char *zOpName; + const char *zSynopsis; + int nOpName; + int ii, jj; + zOpName = sqlite3OpcodeName(pOp->opcode); + nOpName = sqlite3Strlen30(zOpName); + if( zOpName[nOpName+1] ){ + int seenCom = 0; + char c; + zSynopsis = zOpName += nOpName + 1; + for(ii=jj=0; jj<nTemp-1 && (c = zSynopsis[ii])!=0; ii++){ + if( c=='P' ){ + c = zSynopsis[++ii]; + if( c=='4' ){ + sqlite3_snprintf(nTemp-jj, zTemp+jj, "%s", zP4); + }else if( c=='X' ){ + sqlite3_snprintf(nTemp-jj, zTemp+jj, "%s", pOp->zComment); + seenCom = 1; + }else{ + int v1 = translateP(c, pOp); + int v2; + sqlite3_snprintf(nTemp-jj, zTemp+jj, "%d", v1); + if( strncmp(zSynopsis+ii+1, "@P", 2)==0 ){ + ii += 3; + jj += sqlite3Strlen30(zTemp+jj); + v2 = translateP(zSynopsis[ii], pOp); + if( strncmp(zSynopsis+ii+1,"+1",2)==0 ){ + ii += 2; + v2++; + } + if( v2>1 ){ + sqlite3_snprintf(nTemp-jj, zTemp+jj, "..%d", v1+v2-1); + } + }else if( strncmp(zSynopsis+ii+1, "..P3", 4)==0 && pOp->p3==0 ){ + ii += 4; + } + } + jj += sqlite3Strlen30(zTemp+jj); + }else{ + zTemp[jj++] = c; + } + } + if( !seenCom && jj<nTemp-5 && pOp->zComment ){ + sqlite3_snprintf(nTemp-jj, zTemp+jj, "; %s", pOp->zComment); + jj += sqlite3Strlen30(zTemp+jj); + } + if( jj<nTemp ) zTemp[jj] = 0; + }else if( pOp->zComment ){ + sqlite3_snprintf(nTemp, zTemp, "%s", pOp->zComment); + jj = sqlite3Strlen30(zTemp); + }else{ + zTemp[0] = 0; + jj = 0; + } + return jj; +} +#endif /* SQLITE_DEBUG */ + #if !defined(SQLITE_OMIT_EXPLAIN) || !defined(NDEBUG) \ || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG) /* ** Compute a string that describes the P4 parameter for an opcode. @@ -61405,21 +65648,24 @@ */ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ char *zP4 = zTemp; assert( nTemp>=20 ); switch( pOp->p4type ){ - case P4_KEYINFO_STATIC: case P4_KEYINFO: { int i, j; KeyInfo *pKeyInfo = pOp->p4.pKeyInfo; assert( pKeyInfo->aSortOrder!=0 ); - sqlite3_snprintf(nTemp, zTemp, "keyinfo(%d", pKeyInfo->nField); + sqlite3_snprintf(nTemp, zTemp, "k(%d", pKeyInfo->nField); i = sqlite3Strlen30(zTemp); for(j=0; j<pKeyInfo->nField; j++){ CollSeq *pColl = pKeyInfo->aColl[j]; const char *zColl = pColl ? pColl->zName : "nil"; int n = sqlite3Strlen30(zColl); + if( n==6 && memcmp(zColl,"BINARY",6)==0 ){ + zColl = "B"; + n = 1; + } if( i+n>nTemp-6 ){ memcpy(&zTemp[i],",...",4); break; } zTemp[i++] = ','; @@ -61434,11 +65680,11 @@ assert( i<nTemp ); break; } case P4_COLLSEQ: { CollSeq *pColl = pOp->p4.pColl; - sqlite3_snprintf(nTemp, zTemp, "collseq(%.20s)", pColl->zName); + sqlite3_snprintf(nTemp, zTemp, "(%.20s)", pColl->zName); break; } case P4_FUNCDEF: { FuncDef *pDef = pOp->p4.pFunc; sqlite3_snprintf(nTemp, zTemp, "%s(%d)", pDef->zName, pDef->nArg); @@ -61461,11 +65707,11 @@ if( pMem->flags & MEM_Str ){ zP4 = pMem->z; }else if( pMem->flags & MEM_Int ){ sqlite3_snprintf(nTemp, zTemp, "%lld", pMem->u.i); }else if( pMem->flags & MEM_Real ){ - sqlite3_snprintf(nTemp, zTemp, "%.16g", pMem->r); + sqlite3_snprintf(nTemp, zTemp, "%.16g", pMem->u.r); }else if( pMem->flags & MEM_Null ){ sqlite3_snprintf(nTemp, zTemp, "NULL"); }else{ assert( pMem->flags & MEM_Blob ); zP4 = "(blob)"; @@ -61473,11 +65719,11 @@ break; } #ifndef SQLITE_OMIT_VIRTUALTABLE case P4_VTAB: { sqlite3_vtab *pVtab = pOp->p4.pVtab->pVtab; - sqlite3_snprintf(nTemp, zTemp, "vtab:%p:%p", pVtab, pVtab->pModule); + sqlite3_snprintf(nTemp, zTemp, "vtab:%p", pVtab); break; } #endif case P4_INTARRAY: { sqlite3_snprintf(nTemp, zTemp, "intarray"); @@ -61513,13 +65759,13 @@ ** p->btreeMask of databases that will require a lock. */ SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe *p, int i){ assert( i>=0 && i<p->db->nDb && i<(int)sizeof(yDbMask)*8 ); assert( i<(int)sizeof(p->btreeMask)*8 ); - p->btreeMask |= ((yDbMask)1)<<i; + DbMaskSet(p->btreeMask, i); if( i!=1 && sqlite3BtreeSharable(p->db->aDb[i].pBt) ){ - p->lockMask |= ((yDbMask)1)<<i; + DbMaskSet(p->lockMask, i); } } #if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0 /* @@ -61543,20 +65789,19 @@ ** this routine is N*N. But as N is rarely more than 1, this should not ** be a problem. */ SQLITE_PRIVATE void sqlite3VdbeEnter(Vdbe *p){ int i; - yDbMask mask; sqlite3 *db; Db *aDb; int nDb; - if( p->lockMask==0 ) return; /* The common case */ + if( DbMaskAllZero(p->lockMask) ) return; /* The common case */ db = p->db; aDb = db->aDb; nDb = db->nDb; - for(i=0, mask=1; i<nDb; i++, mask += mask){ - if( i!=1 && (mask & p->lockMask)!=0 && ALWAYS(aDb[i].pBt!=0) ){ + for(i=0; i<nDb; i++){ + if( i!=1 && DbMaskTest(p->lockMask,i) && ALWAYS(aDb[i].pBt!=0) ){ sqlite3BtreeEnter(aDb[i].pBt); } } } #endif @@ -61565,20 +65810,19 @@ /* ** Unlock all of the btrees previously locked by a call to sqlite3VdbeEnter(). */ SQLITE_PRIVATE void sqlite3VdbeLeave(Vdbe *p){ int i; - yDbMask mask; sqlite3 *db; Db *aDb; int nDb; - if( p->lockMask==0 ) return; /* The common case */ + if( DbMaskAllZero(p->lockMask) ) return; /* The common case */ db = p->db; aDb = db->aDb; nDb = db->nDb; - for(i=0, mask=1; i<nDb; i++, mask += mask){ - if( i!=1 && (mask & p->lockMask)!=0 && ALWAYS(aDb[i].pBt!=0) ){ + for(i=0; i<nDb; i++){ + if( i!=1 && DbMaskTest(p->lockMask,i) && ALWAYS(aDb[i].pBt!=0) ){ sqlite3BtreeLeave(aDb[i].pBt); } } } #endif @@ -61588,20 +65832,25 @@ ** Print a single opcode. This routine is used for debugging only. */ SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE *pOut, int pc, Op *pOp){ char *zP4; char zPtr[50]; - static const char *zFormat1 = "%4d %-13s %4d %4d %4d %-4s %.2X %s\n"; + char zCom[100]; + static const char *zFormat1 = "%4d %-13s %4d %4d %4d %-13s %.2X %s\n"; if( pOut==0 ) pOut = stdout; zP4 = displayP4(pOp, zPtr, sizeof(zPtr)); +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + displayComment(pOp, zP4, zCom, sizeof(zCom)); +#else + zCom[0] = 0; +#endif + /* NB: The sqlite3OpcodeName() function is implemented by code created + ** by the mkopcodeh.awk and mkopcodec.awk scripts which extract the + ** information from the vdbe.c source text */ fprintf(pOut, zFormat1, pc, sqlite3OpcodeName(pOp->opcode), pOp->p1, pOp->p2, pOp->p3, zP4, pOp->p5, -#ifdef SQLITE_DEBUG - pOp->zComment ? pOp->zComment : "" -#else - "" -#endif + zCom ); fflush(pOut); } #endif @@ -61608,21 +65857,22 @@ /* ** Release an array of N Mem elements */ static void releaseMemArray(Mem *p, int N){ if( p && N ){ - Mem *pEnd; + Mem *pEnd = &p[N]; sqlite3 *db = p->db; u8 malloc_failed = db->mallocFailed; if( db->pnBytesFreed ){ - for(pEnd=&p[N]; p<pEnd; p++){ - sqlite3DbFree(db, p->zMalloc); - } + do{ + if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); + }while( (++p)<pEnd ); return; } - for(pEnd=&p[N]; p<pEnd; p++){ + do{ assert( (&p[1])==pEnd || p[0].db==p[1].db ); + assert( sqlite3VdbeCheckMemInvariants(p) ); /* This block is really an inlined version of sqlite3VdbeMemRelease() ** that takes advantage of the fact that the memory cell value is ** being set to NULL after releasing any dynamic resources. ** @@ -61632,19 +65882,23 @@ ** sqlite3MemRelease() were called from here. With -O2, this jumps ** to 6.6 percent. The test case is inserting 1000 rows into a table ** with no indexes using a single prepared INSERT statement, bind() ** and reset(). Inserts are grouped into a transaction. */ + testcase( p->flags & MEM_Agg ); + testcase( p->flags & MEM_Dyn ); + testcase( p->flags & MEM_Frame ); + testcase( p->flags & MEM_RowSet ); if( p->flags&(MEM_Agg|MEM_Dyn|MEM_Frame|MEM_RowSet) ){ sqlite3VdbeMemRelease(p); - }else if( p->zMalloc ){ + }else if( p->szMalloc ){ sqlite3DbFree(db, p->zMalloc); - p->zMalloc = 0; + p->szMalloc = 0; } - p->flags = MEM_Invalid; - } + p->flags = MEM_Undefined; + }while( (++p)<pEnd ); db->mallocFailed = malloc_failed; } } /* @@ -61743,11 +65997,11 @@ }else if( db->u1.isInterrupted ){ p->rc = SQLITE_INTERRUPT; rc = SQLITE_ERROR; sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3ErrStr(p->rc)); }else{ - char *z; + char *zP4; Op *pOp; if( i<p->nOp ){ /* The output line number is small enough that we are still in the ** main program. */ pOp = &p->aOp[i]; @@ -61761,19 +66015,17 @@ } pOp = &apSub[j]->aOp[i]; } if( p->explain==1 ){ pMem->flags = MEM_Int; - pMem->type = SQLITE_INTEGER; pMem->u.i = i; /* Program counter */ pMem++; pMem->flags = MEM_Static|MEM_Str|MEM_Term; - pMem->z = (char*)sqlite3OpcodeName(pOp->opcode); /* Opcode */ + pMem->z = (char*)sqlite3OpcodeName(pOp->opcode); /* Opcode */ assert( pMem->z!=0 ); pMem->n = sqlite3Strlen30(pMem->z); - pMem->type = SQLITE_TEXT; pMem->enc = SQLITE_UTF8; pMem++; /* When an OP_Program opcode is encounter (the only opcode that has ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms @@ -61795,64 +66047,57 @@ } } pMem->flags = MEM_Int; pMem->u.i = pOp->p1; /* P1 */ - pMem->type = SQLITE_INTEGER; pMem++; pMem->flags = MEM_Int; pMem->u.i = pOp->p2; /* P2 */ - pMem->type = SQLITE_INTEGER; pMem++; pMem->flags = MEM_Int; pMem->u.i = pOp->p3; /* P3 */ - pMem->type = SQLITE_INTEGER; pMem++; - if( sqlite3VdbeMemGrow(pMem, 32, 0) ){ /* P4 */ + if( sqlite3VdbeMemClearAndResize(pMem, 32) ){ /* P4 */ assert( p->db->mallocFailed ); return SQLITE_ERROR; } - pMem->flags = MEM_Dyn|MEM_Str|MEM_Term; - z = displayP4(pOp, pMem->z, 32); - if( z!=pMem->z ){ - sqlite3VdbeMemSetStr(pMem, z, -1, SQLITE_UTF8, 0); + pMem->flags = MEM_Str|MEM_Term; + zP4 = displayP4(pOp, pMem->z, 32); + if( zP4!=pMem->z ){ + sqlite3VdbeMemSetStr(pMem, zP4, -1, SQLITE_UTF8, 0); }else{ assert( pMem->z!=0 ); pMem->n = sqlite3Strlen30(pMem->z); pMem->enc = SQLITE_UTF8; } - pMem->type = SQLITE_TEXT; - pMem++; - - if( p->explain==1 ){ - if( sqlite3VdbeMemGrow(pMem, 4, 0) ){ - assert( p->db->mallocFailed ); - return SQLITE_ERROR; - } - pMem->flags = MEM_Dyn|MEM_Str|MEM_Term; - pMem->n = 2; - sqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */ - pMem->type = SQLITE_TEXT; - pMem->enc = SQLITE_UTF8; - pMem++; - -#ifdef SQLITE_DEBUG - if( pOp->zComment ){ - pMem->flags = MEM_Str|MEM_Term; - pMem->z = pOp->zComment; - pMem->n = sqlite3Strlen30(pMem->z); - pMem->enc = SQLITE_UTF8; - pMem->type = SQLITE_TEXT; - }else -#endif - { - pMem->flags = MEM_Null; /* Comment */ - pMem->type = SQLITE_NULL; - } + pMem++; + + if( p->explain==1 ){ + if( sqlite3VdbeMemClearAndResize(pMem, 4) ){ + assert( p->db->mallocFailed ); + return SQLITE_ERROR; + } + pMem->flags = MEM_Str|MEM_Term; + pMem->n = 2; + sqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */ + pMem->enc = SQLITE_UTF8; + pMem++; + +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + if( sqlite3VdbeMemClearAndResize(pMem, 500) ){ + assert( p->db->mallocFailed ); + return SQLITE_ERROR; + } + pMem->flags = MEM_Str|MEM_Term; + pMem->n = displayComment(pOp, zP4, pMem->z, 500); + pMem->enc = SQLITE_UTF8; +#else + pMem->flags = MEM_Null; /* Comment */ +#endif } p->nResColumn = 8 - 4*(p->explain-1); p->pResultSet = &p->aMem[1]; p->rc = SQLITE_OK; @@ -61865,19 +66110,21 @@ #ifdef SQLITE_DEBUG /* ** Print the SQL that was used to generate a VDBE program. */ SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe *p){ - int nOp = p->nOp; - VdbeOp *pOp; - if( nOp<1 ) return; - pOp = &p->aOp[0]; - if( pOp->opcode==OP_Trace && pOp->p4.z!=0 ){ - const char *z = pOp->p4.z; - while( sqlite3Isspace(*z) ) z++; - printf("SQL: [%s]\n", z); - } + const char *z = 0; + if( p->zSql ){ + z = p->zSql; + }else if( p->nOp>=1 ){ + const VdbeOp *pOp = &p->aOp[0]; + if( pOp->opcode==OP_Init && pOp->p4.z!=0 ){ + z = pOp->p4.z; + while( sqlite3Isspace(*z) ) z++; + } + } + if( z ) printf("SQL: [%s]\n", z); } #endif #if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE) /* @@ -61887,11 +66134,11 @@ int nOp = p->nOp; VdbeOp *pOp; if( sqlite3IoTrace==0 ) return; if( nOp<1 ) return; pOp = &p->aOp[0]; - if( pOp->opcode==OP_Trace && pOp->p4.z!=0 ){ + if( pOp->opcode==OP_Init && pOp->p4.z!=0 ){ int i, j; char z[1000]; sqlite3_snprintf(sizeof(z), z, "%s", pOp->p4.z); for(i=0; sqlite3Isspace(z[i]); i++){} for(j=0; z[i]; i++){ @@ -61990,17 +66237,17 @@ } /* ** Prepare a virtual machine for execution for the first time after ** creating the virtual machine. This involves things such -** as allocating stack space and initializing the program counter. +** as allocating registers and initializing the program counter. ** After the VDBE has be prepped, it can be executed by one or more ** calls to sqlite3VdbeExec(). ** -** This function may be called exact once on a each virtual machine. +** This function may be called exactly once on each virtual machine. ** After this routine is called the VM has been "packaged" and is ready -** to run. After this routine is called, futher calls to +** to run. After this routine is called, further calls to ** sqlite3VdbeAddOp() functions are prohibited. This routine disconnects ** the Vdbe from the Parse object that helped generate it so that the ** the Vdbe becomes an independent entity and the Parse object can be ** destroyed. ** @@ -62024,10 +66271,11 @@ assert( p!=0 ); assert( p->nOp>0 ); assert( pParse!=0 ); assert( p->magic==VDBE_MAGIC_INIT ); + assert( pParse==p->pParse ); db = p->db; assert( db->mallocFailed==0 ); nVar = pParse->nVar; nMem = pParse->nMem; nCursor = pParse->nTab; @@ -62047,12 +66295,12 @@ nMem += nCursor; /* Allocate space for memory registers, SQL variables, VDBE cursors and ** an array to marshal SQL function arguments in. */ - zCsr = (u8*)&p->aOp[p->nOp]; /* Memory avaliable for allocation */ - zEnd = (u8*)&p->aOp[p->nOpAlloc]; /* First byte past end of zCsr[] */ + zCsr = (u8*)&p->aOp[p->nOp]; /* Memory avaliable for allocation */ + zEnd = (u8*)&p->aOp[pParse->nOpAlloc]; /* First byte past end of zCsr[] */ resolveP2Values(p, &nArg); p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); if( pParse->explain && nMem<10 ){ nMem = 10; @@ -62079,10 +66327,13 @@ p->apArg = allocSpace(p->apArg, nArg*sizeof(Mem*), &zCsr, zEnd, &nByte); p->azVar = allocSpace(p->azVar, nVar*sizeof(char*), &zCsr, zEnd, &nByte); p->apCsr = allocSpace(p->apCsr, nCursor*sizeof(VdbeCursor*), &zCsr, zEnd, &nByte); p->aOnceFlag = allocSpace(p->aOnceFlag, nOnce, &zCsr, zEnd, &nByte); +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + p->anExec = allocSpace(p->anExec, p->nOp*sizeof(i64), &zCsr, zEnd, &nByte); +#endif if( nByte ){ p->pFree = sqlite3DbMallocZero(db, nByte); } zCsr = p->pFree; zEnd = &zCsr[nByte]; @@ -62095,20 +66346,20 @@ for(n=0; n<nVar; n++){ p->aVar[n].flags = MEM_Null; p->aVar[n].db = db; } } - if( p->azVar ){ + if( p->azVar && pParse->nzVar>0 ){ p->nzVar = pParse->nzVar; memcpy(p->azVar, pParse->azVar, p->nzVar*sizeof(p->azVar[0])); memset(pParse->azVar, 0, pParse->nzVar*sizeof(pParse->azVar[0])); } if( p->aMem ){ p->aMem--; /* aMem[] goes from 1..nMem */ p->nMem = nMem; /* not from 0..nMem-1 */ for(n=1; n<=nMem; n++){ - p->aMem[n].flags = MEM_Invalid; + p->aMem[n].flags = MEM_Undefined; p->aMem[n].db = db; } } p->explain = pParse->explain; sqlite3VdbeRewind(p); @@ -62129,16 +66380,16 @@ ** the call above. */ }else if( pCx->pCursor ){ sqlite3BtreeCloseCursor(pCx->pCursor); } #ifndef SQLITE_OMIT_VIRTUALTABLE - if( pCx->pVtabCursor ){ + else if( pCx->pVtabCursor ){ sqlite3_vtab_cursor *pVtabCursor = pCx->pVtabCursor; - const sqlite3_module *pModule = pCx->pModule; - p->inVtabMethod = 1; + const sqlite3_module *pModule = pVtabCursor->pVtab->pModule; + assert( pVtabCursor->pVtab->nRef>0 ); + pVtabCursor->pVtab->nRef--; pModule->xClose(pVtabCursor); - p->inVtabMethod = 0; } #endif } /* @@ -62146,10 +66397,13 @@ ** is used, for example, when a trigger sub-program is halted to restore ** control to the main program. */ SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ Vdbe *v = pFrame->v; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + v->anExec = pFrame->anExec; +#endif v->aOnceFlag = pFrame->aOnceFlag; v->nOnceFlag = pFrame->nOnceFlag; v->aOp = pFrame->aOp; v->nOp = pFrame->nOp; v->aMem = pFrame->aMem; @@ -62156,10 +66410,11 @@ v->nMem = pFrame->nMem; v->apCsr = pFrame->apCsr; v->nCursor = pFrame->nCursor; v->db->lastRowid = pFrame->lastRowid; v->nChange = pFrame->nChange; + v->db->nChange = pFrame->nDbChange; return pFrame->pc; } /* ** Close all cursors. @@ -62172,13 +66427,14 @@ static void closeAllCursors(Vdbe *p){ if( p->pFrame ){ VdbeFrame *pFrame; for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); sqlite3VdbeFrameRestore(pFrame); + p->pFrame = 0; + p->nFrame = 0; } - p->pFrame = 0; - p->nFrame = 0; + assert( p->nFrame==0 ); if( p->apCsr ){ int i; for(i=0; i<p->nCursor; i++){ VdbeCursor *pC = p->apCsr[i]; @@ -62196,20 +66452,16 @@ p->pDelFrame = pDel->pParent; sqlite3VdbeFrameDelete(pDel); } /* Delete any auxdata allocations made by the VM */ - sqlite3VdbeDeleteAuxData(p, -1, 0); + if( p->pAuxData ) sqlite3VdbeDeleteAuxData(p, -1, 0); assert( p->pAuxData==0 ); } /* -** Clean up the VM after execution. -** -** This routine will automatically close any cursors, lists, and/or -** sorters that were left open. It also deletes the values of -** variables in the aVar[] array. +** Clean up the VM after a single run. */ static void Cleanup(Vdbe *p){ sqlite3 *db = p->db; #ifdef SQLITE_DEBUG @@ -62216,11 +66468,11 @@ /* Execute assert() statements to ensure that the Vdbe.apCsr[] and ** Vdbe.aMem[] arrays have already been cleaned up. */ int i; if( p->apCsr ) for(i=0; i<p->nCursor; i++) assert( p->apCsr[i]==0 ); if( p->aMem ){ - for(i=1; i<=p->nMem; i++) assert( p->aMem[i].flags==MEM_Invalid ); + for(i=1; i<=p->nMem; i++) assert( p->aMem[i].flags==MEM_Undefined ); } #endif sqlite3DbFree(db, p->zErrMsg); p->zErrMsg = 0; @@ -62373,11 +66625,11 @@ } } /* The complex case - There is a multi-file write-transaction active. ** This requires a master journal file to ensure the transaction is - ** committed atomicly. + ** committed atomically. */ #ifndef SQLITE_OMIT_DISKIO else{ sqlite3_vfs *pVfs = db->pVfs; int needSync = 0; @@ -62492,11 +66744,11 @@ /* Delete the master journal file. This commits the transaction. After ** doing this the directory is synced again before any individual ** transaction files are deleted. */ - rc = sqlite3OsDelete(pVfs, zMaster, 1); + rc = sqlite3OsDelete(pVfs, zMaster, needSync); sqlite3DbFree(db, zMaster); zMaster = 0; if( rc ){ return rc; } @@ -62541,11 +66793,11 @@ int cnt = 0; int nWrite = 0; int nRead = 0; p = db->pVdbe; while( p ){ - if( p->magic==VDBE_MAGIC_RUN && p->pc>=0 ){ + if( sqlite3_stmt_busy((sqlite3_stmt*)p) ){ cnt++; if( p->readOnly==0 ) nWrite++; if( p->bIsReader ) nRead++; } p = p->pNext; @@ -62639,11 +66891,11 @@ if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0) || (!deferred && p->nFkConstraint>0) ){ p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; p->errorAction = OE_Abort; - sqlite3SetString(&p->zErrMsg, db, "foreign key constraint failed"); + sqlite3SetString(&p->zErrMsg, db, "FOREIGN KEY constraint failed"); return SQLITE_ERROR; } return SQLITE_OK; } #endif @@ -62701,11 +66953,10 @@ /* Lock all btrees used by the statement */ sqlite3VdbeEnter(p); /* Check for one of the special errors */ mrc = p->rc & 0xff; - assert( p->rc!=SQLITE_IOERR_BLOCKED ); /* This error no longer exists */ isSpecialError = mrc==SQLITE_NOMEM || mrc==SQLITE_IOERR || mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL; if( isSpecialError ){ /* If the query was read-only and the error code is SQLITE_INTERRUPT, ** no rollback is necessary. Otherwise, at least a savepoint @@ -62727,10 +66978,11 @@ ** so, abort any other statements this handle currently has active. */ sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); sqlite3CloseSavepoints(db); db->autoCommit = 1; + p->nChange = 0; } } } /* Check for immediate foreign key violations. */ @@ -62767,18 +67019,20 @@ sqlite3VdbeLeave(p); return SQLITE_BUSY; }else if( rc!=SQLITE_OK ){ p->rc = rc; sqlite3RollbackAll(db, SQLITE_OK); + p->nChange = 0; }else{ db->nDeferredCons = 0; db->nDeferredImmCons = 0; db->flags &= ~SQLITE_DeferFKs; sqlite3CommitInternalChanges(db); } }else{ sqlite3RollbackAll(db, SQLITE_OK); + p->nChange = 0; } db->nStatement = 0; }else if( eStatementOp==0 ){ if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){ eStatementOp = SAVEPOINT_RELEASE; @@ -62786,10 +67040,11 @@ eStatementOp = SAVEPOINT_ROLLBACK; }else{ sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); sqlite3CloseSavepoints(db); db->autoCommit = 1; + p->nChange = 0; } } /* If eStatementOp is non-zero, then a statement transaction needs to ** be committed or rolled back. Call sqlite3VdbeCloseStatement() to @@ -62806,10 +67061,11 @@ p->zErrMsg = 0; } sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); sqlite3CloseSavepoints(db); db->autoCommit = 1; + p->nChange = 0; } } /* If this was an INSERT, UPDATE or DELETE and no statement transaction ** has been rolled back, update the database connection change-counter. @@ -62875,16 +67131,17 @@ sqlite3 *db = p->db; int rc = p->rc; if( p->zErrMsg ){ u8 mallocFailed = db->mallocFailed; sqlite3BeginBenignMalloc(); + if( db->pErr==0 ) db->pErr = sqlite3ValueNew(db); sqlite3ValueSetStr(db->pErr, -1, p->zErrMsg, SQLITE_UTF8, SQLITE_TRANSIENT); sqlite3EndBenignMalloc(); db->mallocFailed = mallocFailed; db->errCode = rc; }else{ - sqlite3Error(db, rc, 0); + sqlite3Error(db, rc); } return rc; } #ifdef SQLITE_ENABLE_SQLLOG @@ -62943,12 +67200,11 @@ }else if( p->rc && p->expired ){ /* The expired flag was set on the VDBE before the first call ** to sqlite3_step(). For consistency (since sqlite3_step() was ** called), set the database error in this case as well. */ - sqlite3Error(db, p->rc, 0); - sqlite3ValueSetStr(db->pErr, -1, p->zErrMsg, SQLITE_UTF8, SQLITE_TRANSIENT); + sqlite3ErrorWithMsg(db, p->rc, p->zErrMsg ? "%s" : 0, p->zErrMsg); sqlite3DbFree(db, p->zErrMsg); p->zErrMsg = 0; } /* Reclaim all memory used by the VDBE @@ -62965,22 +67221,35 @@ fprintf(out, "---- "); for(i=0; i<p->nOp; i++){ fprintf(out, "%02x", p->aOp[i].opcode); } fprintf(out, "\n"); + if( p->zSql ){ + char c, pc = 0; + fprintf(out, "-- "); + for(i=0; (c = p->zSql[i])!=0; i++){ + if( pc=='\n' ) fprintf(out, "-- "); + putc(c, out); + pc = c; + } + if( pc!='\n' ) fprintf(out, "\n"); + } for(i=0; i<p->nOp; i++){ - fprintf(out, "%6d %10lld %8lld ", + char zHdr[100]; + sqlite3_snprintf(sizeof(zHdr), zHdr, "%6u %12llu %8llu ", p->aOp[i].cnt, p->aOp[i].cycles, p->aOp[i].cnt>0 ? p->aOp[i].cycles/p->aOp[i].cnt : 0 ); + fprintf(out, "%s", zHdr); sqlite3VdbePrintOp(out, i, &p->aOp[i]); } fclose(out); } } #endif + p->iCurrentTime = 0; p->magic = VDBE_MAGIC_INIT; return p->rc & db->errMask; } /* @@ -63009,19 +67278,20 @@ ** ** * the associated function parameter is the 32nd or later (counting ** from left to right), or ** ** * the corresponding bit in argument mask is clear (where the first -** function parameter corrsponds to bit 0 etc.). +** function parameter corresponds to bit 0 etc.). */ SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(Vdbe *pVdbe, int iOp, int mask){ AuxData **pp = &pVdbe->pAuxData; while( *pp ){ AuxData *pAux = *pp; if( (iOp<0) - || (pAux->iOp==iOp && (pAux->iArg>31 || !(mask & ((u32)1<<pAux->iArg)))) + || (pAux->iOp==iOp && (pAux->iArg>31 || !(mask & MASKBIT32(pAux->iArg)))) ){ + testcase( pAux->iArg==31 ); if( pAux->xDelete ){ pAux->xDelete(pAux->pAux); } *pp = pAux->pNext; sqlite3DbFree(pVdbe->db, pAux); @@ -63050,17 +67320,18 @@ vdbeFreeOpArray(db, pSub->aOp, pSub->nOp); sqlite3DbFree(db, pSub); } for(i=p->nzVar-1; i>=0; i--) sqlite3DbFree(db, p->azVar[i]); vdbeFreeOpArray(db, p->aOp, p->nOp); - sqlite3DbFree(db, p->aLabel); sqlite3DbFree(db, p->aColName); sqlite3DbFree(db, p->zSql); sqlite3DbFree(db, p->pFree); -#if defined(SQLITE_ENABLE_TREE_EXPLAIN) - sqlite3DbFree(db, p->zExplain); - sqlite3DbFree(db, p->pExplain); +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + for(i=0; i<p->nScan; i++){ + sqlite3DbFree(db, p->aScan[i].zName); + } + sqlite3DbFree(db, p->aScan); #endif } /* ** Delete an entire VDBE. @@ -63083,10 +67354,61 @@ } p->magic = VDBE_MAGIC_DEAD; p->db = 0; sqlite3DbFree(db, p); } + +/* +** The cursor "p" has a pending seek operation that has not yet been +** carried out. Seek the cursor now. If an error occurs, return +** the appropriate error code. +*/ +static int SQLITE_NOINLINE handleDeferredMoveto(VdbeCursor *p){ + int res, rc; +#ifdef SQLITE_TEST + extern int sqlite3_search_count; +#endif + assert( p->deferredMoveto ); + assert( p->isTable ); + rc = sqlite3BtreeMovetoUnpacked(p->pCursor, 0, p->movetoTarget, 0, &res); + if( rc ) return rc; + if( res!=0 ) return SQLITE_CORRUPT_BKPT; +#ifdef SQLITE_TEST + sqlite3_search_count++; +#endif + p->deferredMoveto = 0; + p->cacheStatus = CACHE_STALE; + return SQLITE_OK; +} + +/* +** Something has moved cursor "p" out of place. Maybe the row it was +** pointed to was deleted out from under it. Or maybe the btree was +** rebalanced. Whatever the cause, try to restore "p" to the place it +** is supposed to be pointing. If the row was deleted out from under the +** cursor, set the cursor to point to a NULL row. +*/ +static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){ + int isDifferentRow, rc; + assert( p->pCursor!=0 ); + assert( sqlite3BtreeCursorHasMoved(p->pCursor) ); + rc = sqlite3BtreeCursorRestore(p->pCursor, &isDifferentRow); + p->cacheStatus = CACHE_STALE; + if( isDifferentRow ) p->nullRow = 1; + return rc; +} + +/* +** Check to ensure that the cursor is valid. Restore the cursor +** if need be. Return any I/O error from the restore operation. +*/ +SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor *p){ + if( sqlite3BtreeCursorHasMoved(p->pCursor) ){ + return handleMovedCursor(p); + } + return SQLITE_OK; +} /* ** Make sure the cursor p is ready to read or write the row to which it ** was last positioned. Return an error code if an OOM fault or I/O error ** prevents us from positioning the cursor to its correct position. @@ -63099,33 +67421,14 @@ ** If the cursor is already pointing to the correct row and that row has ** not been deleted out from under the cursor, then this routine is a no-op. */ SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor *p){ if( p->deferredMoveto ){ - int res, rc; -#ifdef SQLITE_TEST - extern int sqlite3_search_count; -#endif - assert( p->isTable ); - rc = sqlite3BtreeMovetoUnpacked(p->pCursor, 0, p->movetoTarget, 0, &res); - if( rc ) return rc; - p->lastRowid = p->movetoTarget; - if( res!=0 ) return SQLITE_CORRUPT_BKPT; - p->rowidIsValid = 1; -#ifdef SQLITE_TEST - sqlite3_search_count++; -#endif - p->deferredMoveto = 0; - p->cacheStatus = CACHE_STALE; - }else if( ALWAYS(p->pCursor) ){ - int hasMoved; - int rc = sqlite3BtreeCursorHasMoved(p->pCursor, &hasMoved); - if( rc ) return rc; - if( hasMoved ){ - p->cacheStatus = CACHE_STALE; - p->nullRow = 1; - } + return handleDeferredMoveto(p); + } + if( p->pCursor && sqlite3BtreeCursorHasMoved(p->pCursor) ){ + return handleMovedCursor(p); } return SQLITE_OK; } /* @@ -63173,11 +67476,11 @@ /* ** Return the serial-type for the value stored in pMem. */ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format){ int flags = pMem->flags; - int n; + u32 n; if( flags&MEM_Null ){ return 0; } if( flags&MEM_Int ){ @@ -63184,13 +67487,11 @@ /* Figure out whether to use 1, 2, 4, 6 or 8 bytes. */ # define MAX_6BYTE ((((i64)0x00008000)<<32)-1) i64 i = pMem->u.i; u64 u; if( i<0 ){ - if( i<(-MAX_6BYTE) ) return 6; - /* Previous test prevents: u = -(-9223372036854775808) */ - u = -i; + u = ~i; }else{ u = i; } if( u<=127 ){ return ((i&1)==i && file_format>=4) ? 8+(u32)u : 1; @@ -63203,15 +67504,15 @@ } if( flags&MEM_Real ){ return 7; } assert( pMem->db->mallocFailed || flags&(MEM_Str|MEM_Blob) ); - n = pMem->n; + assert( pMem->n>=0 ); + n = (u32)pMem->n; if( flags & MEM_Zero ){ n += pMem->u.nZero; } - assert( n>=0 ); return ((n*2) + 12 + ((flags&MEM_Str)!=0)); } /* ** Return the length of the data corresponding to the supplied serial-type. @@ -63281,166 +67582,189 @@ /* ** Write the serialized data blob for the value stored in pMem into ** buf. It is assumed that the caller has allocated sufficient space. ** Return the number of bytes written. ** -** nBuf is the amount of space left in buf[]. nBuf must always be -** large enough to hold the entire field. Except, if the field is -** a blob with a zero-filled tail, then buf[] might be just the right -** size to hold everything except for the zero-filled tail. If buf[] -** is only big enough to hold the non-zero prefix, then only write that -** prefix into buf[]. But if buf[] is large enough to hold both the -** prefix and the tail then write the prefix and set the tail to all -** zeros. +** nBuf is the amount of space left in buf[]. The caller is responsible +** for allocating enough space to buf[] to hold the entire field, exclusive +** of the pMem->u.nZero bytes for a MEM_Zero value. ** ** Return the number of bytes actually written into buf[]. The number ** of bytes in the zero-filled tail is included in the return value only ** if those bytes were zeroed in buf[]. */ -SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, int nBuf, Mem *pMem, int file_format){ - u32 serial_type = sqlite3VdbeSerialType(pMem, file_format); +SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){ u32 len; /* Integer and Real */ if( serial_type<=7 && serial_type>0 ){ u64 v; u32 i; if( serial_type==7 ){ - assert( sizeof(v)==sizeof(pMem->r) ); - memcpy(&v, &pMem->r, sizeof(v)); + assert( sizeof(v)==sizeof(pMem->u.r) ); + memcpy(&v, &pMem->u.r, sizeof(v)); swapMixedEndianFloat(v); }else{ v = pMem->u.i; } len = i = sqlite3VdbeSerialTypeLen(serial_type); - assert( len<=(u32)nBuf ); - while( i-- ){ - buf[i] = (u8)(v&0xFF); + assert( i>0 ); + do{ + buf[--i] = (u8)(v&0xFF); v >>= 8; - } + }while( i ); return len; } /* String or blob */ if( serial_type>=12 ){ assert( pMem->n + ((pMem->flags & MEM_Zero)?pMem->u.nZero:0) == (int)sqlite3VdbeSerialTypeLen(serial_type) ); - assert( pMem->n<=nBuf ); len = pMem->n; memcpy(buf, pMem->z, len); - if( pMem->flags & MEM_Zero ){ - len += pMem->u.nZero; - assert( nBuf>=0 ); - if( len > (u32)nBuf ){ - len = (u32)nBuf; - } - memset(&buf[pMem->n], 0, len-pMem->n); - } return len; } /* NULL or constants 0 or 1 */ return 0; } +/* Input "x" is a sequence of unsigned characters that represent a +** big-endian integer. Return the equivalent native integer +*/ +#define ONE_BYTE_INT(x) ((i8)(x)[0]) +#define TWO_BYTE_INT(x) (256*(i8)((x)[0])|(x)[1]) +#define THREE_BYTE_INT(x) (65536*(i8)((x)[0])|((x)[1]<<8)|(x)[2]) +#define FOUR_BYTE_UINT(x) (((u32)(x)[0]<<24)|((x)[1]<<16)|((x)[2]<<8)|(x)[3]) +#define FOUR_BYTE_INT(x) (16777216*(i8)((x)[0])|((x)[1]<<16)|((x)[2]<<8)|(x)[3]) + /* ** Deserialize the data blob pointed to by buf as serial type serial_type ** and store the result in pMem. Return the number of bytes read. +** +** This function is implemented as two separate routines for performance. +** The few cases that require local variables are broken out into a separate +** routine so that in most cases the overhead of moving the stack pointer +** is avoided. */ +static u32 SQLITE_NOINLINE serialGet( + const unsigned char *buf, /* Buffer to deserialize from */ + u32 serial_type, /* Serial type to deserialize */ + Mem *pMem /* Memory cell to write value into */ +){ + u64 x = FOUR_BYTE_UINT(buf); + u32 y = FOUR_BYTE_UINT(buf+4); + x = (x<<32) + y; + if( serial_type==6 ){ + /* EVIDENCE-OF: R-29851-52272 Value is a big-endian 64-bit + ** twos-complement integer. */ + pMem->u.i = *(i64*)&x; + pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); + }else{ + /* EVIDENCE-OF: R-57343-49114 Value is a big-endian IEEE 754-2008 64-bit + ** floating point number. */ +#if !defined(NDEBUG) && !defined(SQLITE_OMIT_FLOATING_POINT) + /* Verify that integers and floating point values use the same + ** byte order. Or, that if SQLITE_MIXED_ENDIAN_64BIT_FLOAT is + ** defined that 64-bit floating point values really are mixed + ** endian. + */ + static const u64 t1 = ((u64)0x3ff00000)<<32; + static const double r1 = 1.0; + u64 t2 = t1; + swapMixedEndianFloat(t2); + assert( sizeof(r1)==sizeof(t2) && memcmp(&r1, &t2, sizeof(r1))==0 ); +#endif + assert( sizeof(x)==8 && sizeof(pMem->u.r)==8 ); + swapMixedEndianFloat(x); + memcpy(&pMem->u.r, &x, sizeof(x)); + pMem->flags = sqlite3IsNaN(pMem->u.r) ? MEM_Null : MEM_Real; + } + return 8; +} SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( const unsigned char *buf, /* Buffer to deserialize from */ u32 serial_type, /* Serial type to deserialize */ Mem *pMem /* Memory cell to write value into */ ){ switch( serial_type ){ case 10: /* Reserved for future use */ case 11: /* Reserved for future use */ - case 0: { /* NULL */ + case 0: { /* Null */ + /* EVIDENCE-OF: R-24078-09375 Value is a NULL. */ pMem->flags = MEM_Null; break; } - case 1: { /* 1-byte signed integer */ - pMem->u.i = (signed char)buf[0]; + case 1: { + /* EVIDENCE-OF: R-44885-25196 Value is an 8-bit twos-complement + ** integer. */ + pMem->u.i = ONE_BYTE_INT(buf); pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); return 1; } case 2: { /* 2-byte signed integer */ - pMem->u.i = (((signed char)buf[0])<<8) | buf[1]; + /* EVIDENCE-OF: R-49794-35026 Value is a big-endian 16-bit + ** twos-complement integer. */ + pMem->u.i = TWO_BYTE_INT(buf); pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); return 2; } case 3: { /* 3-byte signed integer */ - pMem->u.i = (((signed char)buf[0])<<16) | (buf[1]<<8) | buf[2]; + /* EVIDENCE-OF: R-37839-54301 Value is a big-endian 24-bit + ** twos-complement integer. */ + pMem->u.i = THREE_BYTE_INT(buf); pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); return 3; } case 4: { /* 4-byte signed integer */ - pMem->u.i = (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3]; + /* EVIDENCE-OF: R-01849-26079 Value is a big-endian 32-bit + ** twos-complement integer. */ + pMem->u.i = FOUR_BYTE_INT(buf); pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); return 4; } case 5: { /* 6-byte signed integer */ - u64 x = (((signed char)buf[0])<<8) | buf[1]; - u32 y = (buf[2]<<24) | (buf[3]<<16) | (buf[4]<<8) | buf[5]; - x = (x<<32) | y; - pMem->u.i = *(i64*)&x; + /* EVIDENCE-OF: R-50385-09674 Value is a big-endian 48-bit + ** twos-complement integer. */ + pMem->u.i = FOUR_BYTE_UINT(buf+2) + (((i64)1)<<32)*TWO_BYTE_INT(buf); pMem->flags = MEM_Int; + testcase( pMem->u.i<0 ); return 6; } case 6: /* 8-byte signed integer */ case 7: { /* IEEE floating point */ - u64 x; - u32 y; -#if !defined(NDEBUG) && !defined(SQLITE_OMIT_FLOATING_POINT) - /* Verify that integers and floating point values use the same - ** byte order. Or, that if SQLITE_MIXED_ENDIAN_64BIT_FLOAT is - ** defined that 64-bit floating point values really are mixed - ** endian. - */ - static const u64 t1 = ((u64)0x3ff00000)<<32; - static const double r1 = 1.0; - u64 t2 = t1; - swapMixedEndianFloat(t2); - assert( sizeof(r1)==sizeof(t2) && memcmp(&r1, &t2, sizeof(r1))==0 ); -#endif - - x = (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3]; - y = (buf[4]<<24) | (buf[5]<<16) | (buf[6]<<8) | buf[7]; - x = (x<<32) | y; - if( serial_type==6 ){ - pMem->u.i = *(i64*)&x; - pMem->flags = MEM_Int; - }else{ - assert( sizeof(x)==8 && sizeof(pMem->r)==8 ); - swapMixedEndianFloat(x); - memcpy(&pMem->r, &x, sizeof(x)); - pMem->flags = sqlite3IsNaN(pMem->r) ? MEM_Null : MEM_Real; - } - return 8; + /* These use local variables, so do them in a separate routine + ** to avoid having to move the frame pointer in the common case */ + return serialGet(buf,serial_type,pMem); } case 8: /* Integer 0 */ case 9: { /* Integer 1 */ + /* EVIDENCE-OF: R-12976-22893 Value is the integer 0. */ + /* EVIDENCE-OF: R-18143-12121 Value is the integer 1. */ pMem->u.i = serial_type-8; pMem->flags = MEM_Int; return 0; } default: { - u32 len = (serial_type-12)/2; + /* EVIDENCE-OF: R-14606-31564 Value is a BLOB that is (N-12)/2 bytes in + ** length. + ** EVIDENCE-OF: R-28401-00140 Value is a string in the text encoding and + ** (N-13)/2 bytes in length. */ + static const u16 aFlag[] = { MEM_Blob|MEM_Ephem, MEM_Str|MEM_Ephem }; pMem->z = (char *)buf; - pMem->n = len; - pMem->xDel = 0; - if( serial_type&0x01 ){ - pMem->flags = MEM_Str | MEM_Ephem; - }else{ - pMem->flags = MEM_Blob | MEM_Ephem; - } - return len; + pMem->n = (serial_type-12)/2; + pMem->flags = aFlag[serial_type&1]; + return pMem->n; } } return 0; } - /* ** This routine is used to allocate sufficient space for an UnpackedRecord ** structure large enough to be used with sqlite3VdbeRecordUnpack() if ** the first argument is a pointer to KeyInfo structure pKeyInfo. ** @@ -63501,51 +67825,47 @@ u32 idx; /* Offset in aKey[] to read from */ u16 u; /* Unsigned loop counter */ u32 szHdr; Mem *pMem = p->aMem; - p->flags = 0; + p->default_rc = 0; assert( EIGHT_BYTE_ALIGNMENT(pMem) ); idx = getVarint32(aKey, szHdr); d = szHdr; u = 0; - while( idx<szHdr && u<p->nField && d<=nKey ){ + while( idx<szHdr && d<=nKey ){ u32 serial_type; idx += getVarint32(&aKey[idx], serial_type); pMem->enc = pKeyInfo->enc; pMem->db = pKeyInfo->db; /* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */ - pMem->zMalloc = 0; + pMem->szMalloc = 0; d += sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); pMem++; - u++; + if( (++u)>=p->nField ) break; } assert( u<=pKeyInfo->nField + 1 ); p->nField = u; } +#if SQLITE_DEBUG /* -** This function compares the two table rows or index records -** specified by {nKey1, pKey1} and pPKey2. It returns a negative, zero -** or positive integer if key1 is less than, equal to or -** greater than key2. The {nKey1, pKey1} key must be a blob -** created by th OP_MakeRecord opcode of the VDBE. The pPKey2 -** key must be a parsed key such as obtained from -** sqlite3VdbeParseRecord. +** This function compares two index or table record keys in the same way +** as the sqlite3VdbeRecordCompare() routine. Unlike VdbeRecordCompare(), +** this function deserializes and compares values using the +** sqlite3VdbeSerialGet() and sqlite3MemCompare() functions. It is used +** in assert() statements to ensure that the optimized code in +** sqlite3VdbeRecordCompare() returns results with these two primitives. ** -** Key1 and Key2 do not have to contain the same number of fields. -** The key with fewer fields is usually compares less than the -** longer key. However if the UNPACKED_INCRKEY flags in pPKey2 is set -** and the common prefixes are equal, then key1 is less than key2. -** Or if the UNPACKED_MATCH_PREFIX flag is set and the prefixes are -** equal, then the keys are considered to be equal and -** the parts beyond the common prefix are ignored. +** Return true if the result of comparison is equivalent to desiredResult. +** Return false if there is a disagreement. */ -SQLITE_PRIVATE int sqlite3VdbeRecordCompare( +static int vdbeRecordCompareDebug( int nKey1, const void *pKey1, /* Left key */ - UnpackedRecord *pPKey2 /* Right key */ + const UnpackedRecord *pPKey2, /* Right key */ + int desiredResult /* Correct answer */ ){ u32 d1; /* Offset into aKey[] of next data element */ u32 idx1; /* Offset into aKey[] of next header element */ u32 szHdr1; /* Number of bytes in header */ int i = 0; @@ -63553,14 +67873,15 @@ const unsigned char *aKey1 = (const unsigned char *)pKey1; KeyInfo *pKeyInfo; Mem mem1; pKeyInfo = pPKey2->pKeyInfo; + if( pKeyInfo->db==0 ) return 1; mem1.enc = pKeyInfo->enc; mem1.db = pKeyInfo->db; /* mem1.flags = 0; // Will be initialized by sqlite3VdbeSerialGet() */ - VVA_ONLY( mem1.zMalloc = 0; ) /* Only needed by assert() statements */ + VVA_ONLY( mem1.szMalloc = 0; ) /* Only needed by assert() statements */ /* Compilers may complain that mem1.u.i is potentially uninitialized. ** We could initialize it, as shown here, to silence those complaints. ** But in fact, mem1.u.i will never actually be used uninitialized, and doing ** the unnecessary initialization has a measurable negative performance @@ -63569,13 +67890,15 @@ */ /* mem1.u.i = 0; // not needed, here to silence compiler warning */ idx1 = getVarint32(aKey1, szHdr1); d1 = szHdr1; - assert( pKeyInfo->nField+1>=pPKey2->nField ); + assert( pKeyInfo->nField+pKeyInfo->nXField>=pPKey2->nField || CORRUPT_DB ); assert( pKeyInfo->aSortOrder!=0 ); - while( idx1<szHdr1 && i<pPKey2->nField ){ + assert( pKeyInfo->nField>0 ); + assert( idx1<=szHdr1 || CORRUPT_DB ); + do{ u32 serial_type1; /* Read the serial types for the next element in each key. */ idx1 += getVarint32( aKey1+idx1, serial_type1 ); @@ -63597,59 +67920,663 @@ /* Do the comparison */ rc = sqlite3MemCompare(&mem1, &pPKey2->aMem[i], pKeyInfo->aColl[i]); if( rc!=0 ){ - assert( mem1.zMalloc==0 ); /* See comment below */ - - /* Invert the result if we are using DESC sort order. */ + assert( mem1.szMalloc==0 ); /* See comment below */ if( pKeyInfo->aSortOrder[i] ){ - rc = -rc; - } - - /* If the PREFIX_SEARCH flag is set and all fields except the final - ** rowid field were equal, then clear the PREFIX_SEARCH flag and set - ** pPKey2->rowid to the value of the rowid field in (pKey1, nKey1). - ** This is used by the OP_IsUnique opcode. - */ - if( (pPKey2->flags & UNPACKED_PREFIX_SEARCH) && i==(pPKey2->nField-1) ){ - assert( idx1==szHdr1 && rc ); - assert( mem1.flags & MEM_Int ); - pPKey2->flags &= ~UNPACKED_PREFIX_SEARCH; - pPKey2->rowid = mem1.u.i; - } - - return rc; + rc = -rc; /* Invert the result for DESC sort order. */ + } + goto debugCompareEnd; } i++; - } + }while( idx1<szHdr1 && i<pPKey2->nField ); /* No memory allocation is ever used on mem1. Prove this using ** the following assert(). If the assert() fails, it indicates a ** memory leak and a need to call sqlite3VdbeMemRelease(&mem1). */ - assert( mem1.zMalloc==0 ); + assert( mem1.szMalloc==0 ); /* rc==0 here means that one of the keys ran out of fields and - ** all the fields up to that point were equal. If the UNPACKED_INCRKEY - ** flag is set, then break the tie by treating key2 as larger. - ** If the UPACKED_PREFIX_MATCH flag is set, then keys with common prefixes - ** are considered to be equal. Otherwise, the longer key is the - ** larger. As it happens, the pPKey2 will always be the longer - ** if there is a difference. - */ - assert( rc==0 ); - if( pPKey2->flags & UNPACKED_INCRKEY ){ - rc = -1; - }else if( pPKey2->flags & UNPACKED_PREFIX_MATCH ){ - /* Leave rc==0 */ - }else if( idx1<szHdr1 ){ - rc = 1; - } - return rc; -} - + ** all the fields up to that point were equal. Return the default_rc + ** value. */ + rc = pPKey2->default_rc; + +debugCompareEnd: + if( desiredResult==0 && rc==0 ) return 1; + if( desiredResult<0 && rc<0 ) return 1; + if( desiredResult>0 && rc>0 ) return 1; + if( CORRUPT_DB ) return 1; + if( pKeyInfo->db->mallocFailed ) return 1; + return 0; +} +#endif + +#if SQLITE_DEBUG +/* +** Count the number of fields (a.k.a. columns) in the record given by +** pKey,nKey. The verify that this count is less than or equal to the +** limit given by pKeyInfo->nField + pKeyInfo->nXField. +** +** If this constraint is not satisfied, it means that the high-speed +** vdbeRecordCompareInt() and vdbeRecordCompareString() routines will +** not work correctly. If this assert() ever fires, it probably means +** that the KeyInfo.nField or KeyInfo.nXField values were computed +** incorrectly. +*/ +static void vdbeAssertFieldCountWithinLimits( + int nKey, const void *pKey, /* The record to verify */ + const KeyInfo *pKeyInfo /* Compare size with this KeyInfo */ +){ + int nField = 0; + u32 szHdr; + u32 idx; + u32 notUsed; + const unsigned char *aKey = (const unsigned char*)pKey; + + if( CORRUPT_DB ) return; + idx = getVarint32(aKey, szHdr); + assert( nKey>=0 ); + assert( szHdr<=(u32)nKey ); + while( idx<szHdr ){ + idx += getVarint32(aKey+idx, notUsed); + nField++; + } + assert( nField <= pKeyInfo->nField+pKeyInfo->nXField ); +} +#else +# define vdbeAssertFieldCountWithinLimits(A,B,C) +#endif + +/* +** Both *pMem1 and *pMem2 contain string values. Compare the two values +** using the collation sequence pColl. As usual, return a negative , zero +** or positive value if *pMem1 is less than, equal to or greater than +** *pMem2, respectively. Similar in spirit to "rc = (*pMem1) - (*pMem2);". +*/ +static int vdbeCompareMemString( + const Mem *pMem1, + const Mem *pMem2, + const CollSeq *pColl, + u8 *prcErr /* If an OOM occurs, set to SQLITE_NOMEM */ +){ + if( pMem1->enc==pColl->enc ){ + /* The strings are already in the correct encoding. Call the + ** comparison function directly */ + return pColl->xCmp(pColl->pUser,pMem1->n,pMem1->z,pMem2->n,pMem2->z); + }else{ + int rc; + const void *v1, *v2; + int n1, n2; + Mem c1; + Mem c2; + sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null); + sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null); + sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); + sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); + v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); + n1 = v1==0 ? 0 : c1.n; + v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); + n2 = v2==0 ? 0 : c2.n; + rc = pColl->xCmp(pColl->pUser, n1, v1, n2, v2); + sqlite3VdbeMemRelease(&c1); + sqlite3VdbeMemRelease(&c2); + if( (v1==0 || v2==0) && prcErr ) *prcErr = SQLITE_NOMEM; + return rc; + } +} + +/* +** Compare two blobs. Return negative, zero, or positive if the first +** is less than, equal to, or greater than the second, respectively. +** If one blob is a prefix of the other, then the shorter is the lessor. +*/ +static SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem *pB2){ + int c = memcmp(pB1->z, pB2->z, pB1->n>pB2->n ? pB2->n : pB1->n); + if( c ) return c; + return pB1->n - pB2->n; +} + + +/* +** Compare the values contained by the two memory cells, returning +** negative, zero or positive if pMem1 is less than, equal to, or greater +** than pMem2. Sorting order is NULL's first, followed by numbers (integers +** and reals) sorted numerically, followed by text ordered by the collating +** sequence pColl and finally blob's ordered by memcmp(). +** +** Two NULL values are considered equal by this function. +*/ +SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const CollSeq *pColl){ + int f1, f2; + int combined_flags; + + f1 = pMem1->flags; + f2 = pMem2->flags; + combined_flags = f1|f2; + assert( (combined_flags & MEM_RowSet)==0 ); + + /* If one value is NULL, it is less than the other. If both values + ** are NULL, return 0. + */ + if( combined_flags&MEM_Null ){ + return (f2&MEM_Null) - (f1&MEM_Null); + } + + /* If one value is a number and the other is not, the number is less. + ** If both are numbers, compare as reals if one is a real, or as integers + ** if both values are integers. + */ + if( combined_flags&(MEM_Int|MEM_Real) ){ + double r1, r2; + if( (f1 & f2 & MEM_Int)!=0 ){ + if( pMem1->u.i < pMem2->u.i ) return -1; + if( pMem1->u.i > pMem2->u.i ) return 1; + return 0; + } + if( (f1&MEM_Real)!=0 ){ + r1 = pMem1->u.r; + }else if( (f1&MEM_Int)!=0 ){ + r1 = (double)pMem1->u.i; + }else{ + return 1; + } + if( (f2&MEM_Real)!=0 ){ + r2 = pMem2->u.r; + }else if( (f2&MEM_Int)!=0 ){ + r2 = (double)pMem2->u.i; + }else{ + return -1; + } + if( r1<r2 ) return -1; + if( r1>r2 ) return 1; + return 0; + } + + /* If one value is a string and the other is a blob, the string is less. + ** If both are strings, compare using the collating functions. + */ + if( combined_flags&MEM_Str ){ + if( (f1 & MEM_Str)==0 ){ + return 1; + } + if( (f2 & MEM_Str)==0 ){ + return -1; + } + + assert( pMem1->enc==pMem2->enc ); + assert( pMem1->enc==SQLITE_UTF8 || + pMem1->enc==SQLITE_UTF16LE || pMem1->enc==SQLITE_UTF16BE ); + + /* The collation sequence must be defined at this point, even if + ** the user deletes the collation sequence after the vdbe program is + ** compiled (this was not always the case). + */ + assert( !pColl || pColl->xCmp ); + + if( pColl ){ + return vdbeCompareMemString(pMem1, pMem2, pColl, 0); + } + /* If a NULL pointer was passed as the collate function, fall through + ** to the blob case and use memcmp(). */ + } + + /* Both values must be blobs. Compare using memcmp(). */ + return sqlite3BlobCompare(pMem1, pMem2); +} + + +/* +** The first argument passed to this function is a serial-type that +** corresponds to an integer - all values between 1 and 9 inclusive +** except 7. The second points to a buffer containing an integer value +** serialized according to serial_type. This function deserializes +** and returns the value. +*/ +static i64 vdbeRecordDecodeInt(u32 serial_type, const u8 *aKey){ + u32 y; + assert( CORRUPT_DB || (serial_type>=1 && serial_type<=9 && serial_type!=7) ); + switch( serial_type ){ + case 0: + case 1: + testcase( aKey[0]&0x80 ); + return ONE_BYTE_INT(aKey); + case 2: + testcase( aKey[0]&0x80 ); + return TWO_BYTE_INT(aKey); + case 3: + testcase( aKey[0]&0x80 ); + return THREE_BYTE_INT(aKey); + case 4: { + testcase( aKey[0]&0x80 ); + y = FOUR_BYTE_UINT(aKey); + return (i64)*(int*)&y; + } + case 5: { + testcase( aKey[0]&0x80 ); + return FOUR_BYTE_UINT(aKey+2) + (((i64)1)<<32)*TWO_BYTE_INT(aKey); + } + case 6: { + u64 x = FOUR_BYTE_UINT(aKey); + testcase( aKey[0]&0x80 ); + x = (x<<32) | FOUR_BYTE_UINT(aKey+4); + return (i64)*(i64*)&x; + } + } + + return (serial_type - 8); +} + +/* +** This function compares the two table rows or index records +** specified by {nKey1, pKey1} and pPKey2. It returns a negative, zero +** or positive integer if key1 is less than, equal to or +** greater than key2. The {nKey1, pKey1} key must be a blob +** created by the OP_MakeRecord opcode of the VDBE. The pPKey2 +** key must be a parsed key such as obtained from +** sqlite3VdbeParseRecord. +** +** If argument bSkip is non-zero, it is assumed that the caller has already +** determined that the first fields of the keys are equal. +** +** Key1 and Key2 do not have to contain the same number of fields. If all +** fields that appear in both keys are equal, then pPKey2->default_rc is +** returned. +** +** If database corruption is discovered, set pPKey2->errCode to +** SQLITE_CORRUPT and return 0. If an OOM error is encountered, +** pPKey2->errCode is set to SQLITE_NOMEM and, if it is not NULL, the +** malloc-failed flag set on database handle (pPKey2->pKeyInfo->db). +*/ +static int vdbeRecordCompareWithSkip( + int nKey1, const void *pKey1, /* Left key */ + UnpackedRecord *pPKey2, /* Right key */ + int bSkip /* If true, skip the first field */ +){ + u32 d1; /* Offset into aKey[] of next data element */ + int i; /* Index of next field to compare */ + u32 szHdr1; /* Size of record header in bytes */ + u32 idx1; /* Offset of first type in header */ + int rc = 0; /* Return value */ + Mem *pRhs = pPKey2->aMem; /* Next field of pPKey2 to compare */ + KeyInfo *pKeyInfo = pPKey2->pKeyInfo; + const unsigned char *aKey1 = (const unsigned char *)pKey1; + Mem mem1; + + /* If bSkip is true, then the caller has already determined that the first + ** two elements in the keys are equal. Fix the various stack variables so + ** that this routine begins comparing at the second field. */ + if( bSkip ){ + u32 s1; + idx1 = 1 + getVarint32(&aKey1[1], s1); + szHdr1 = aKey1[0]; + d1 = szHdr1 + sqlite3VdbeSerialTypeLen(s1); + i = 1; + pRhs++; + }else{ + idx1 = getVarint32(aKey1, szHdr1); + d1 = szHdr1; + if( d1>(unsigned)nKey1 ){ + pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; + return 0; /* Corruption */ + } + i = 0; + } + + VVA_ONLY( mem1.szMalloc = 0; ) /* Only needed by assert() statements */ + assert( pPKey2->pKeyInfo->nField+pPKey2->pKeyInfo->nXField>=pPKey2->nField + || CORRUPT_DB ); + assert( pPKey2->pKeyInfo->aSortOrder!=0 ); + assert( pPKey2->pKeyInfo->nField>0 ); + assert( idx1<=szHdr1 || CORRUPT_DB ); + do{ + u32 serial_type; + + /* RHS is an integer */ + if( pRhs->flags & MEM_Int ){ + serial_type = aKey1[idx1]; + testcase( serial_type==12 ); + if( serial_type>=12 ){ + rc = +1; + }else if( serial_type==0 ){ + rc = -1; + }else if( serial_type==7 ){ + double rhs = (double)pRhs->u.i; + sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); + if( mem1.u.r<rhs ){ + rc = -1; + }else if( mem1.u.r>rhs ){ + rc = +1; + } + }else{ + i64 lhs = vdbeRecordDecodeInt(serial_type, &aKey1[d1]); + i64 rhs = pRhs->u.i; + if( lhs<rhs ){ + rc = -1; + }else if( lhs>rhs ){ + rc = +1; + } + } + } + + /* RHS is real */ + else if( pRhs->flags & MEM_Real ){ + serial_type = aKey1[idx1]; + if( serial_type>=12 ){ + rc = +1; + }else if( serial_type==0 ){ + rc = -1; + }else{ + double rhs = pRhs->u.r; + double lhs; + sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); + if( serial_type==7 ){ + lhs = mem1.u.r; + }else{ + lhs = (double)mem1.u.i; + } + if( lhs<rhs ){ + rc = -1; + }else if( lhs>rhs ){ + rc = +1; + } + } + } + + /* RHS is a string */ + else if( pRhs->flags & MEM_Str ){ + getVarint32(&aKey1[idx1], serial_type); + testcase( serial_type==12 ); + if( serial_type<12 ){ + rc = -1; + }else if( !(serial_type & 0x01) ){ + rc = +1; + }else{ + mem1.n = (serial_type - 12) / 2; + testcase( (d1+mem1.n)==(unsigned)nKey1 ); + testcase( (d1+mem1.n+1)==(unsigned)nKey1 ); + if( (d1+mem1.n) > (unsigned)nKey1 ){ + pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; + return 0; /* Corruption */ + }else if( pKeyInfo->aColl[i] ){ + mem1.enc = pKeyInfo->enc; + mem1.db = pKeyInfo->db; + mem1.flags = MEM_Str; + mem1.z = (char*)&aKey1[d1]; + rc = vdbeCompareMemString( + &mem1, pRhs, pKeyInfo->aColl[i], &pPKey2->errCode + ); + }else{ + int nCmp = MIN(mem1.n, pRhs->n); + rc = memcmp(&aKey1[d1], pRhs->z, nCmp); + if( rc==0 ) rc = mem1.n - pRhs->n; + } + } + } + + /* RHS is a blob */ + else if( pRhs->flags & MEM_Blob ){ + getVarint32(&aKey1[idx1], serial_type); + testcase( serial_type==12 ); + if( serial_type<12 || (serial_type & 0x01) ){ + rc = -1; + }else{ + int nStr = (serial_type - 12) / 2; + testcase( (d1+nStr)==(unsigned)nKey1 ); + testcase( (d1+nStr+1)==(unsigned)nKey1 ); + if( (d1+nStr) > (unsigned)nKey1 ){ + pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; + return 0; /* Corruption */ + }else{ + int nCmp = MIN(nStr, pRhs->n); + rc = memcmp(&aKey1[d1], pRhs->z, nCmp); + if( rc==0 ) rc = nStr - pRhs->n; + } + } + } + + /* RHS is null */ + else{ + serial_type = aKey1[idx1]; + rc = (serial_type!=0); + } + + if( rc!=0 ){ + if( pKeyInfo->aSortOrder[i] ){ + rc = -rc; + } + assert( vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, rc) ); + assert( mem1.szMalloc==0 ); /* See comment below */ + return rc; + } + + i++; + pRhs++; + d1 += sqlite3VdbeSerialTypeLen(serial_type); + idx1 += sqlite3VarintLen(serial_type); + }while( idx1<(unsigned)szHdr1 && i<pPKey2->nField && d1<=(unsigned)nKey1 ); + + /* No memory allocation is ever used on mem1. Prove this using + ** the following assert(). If the assert() fails, it indicates a + ** memory leak and a need to call sqlite3VdbeMemRelease(&mem1). */ + assert( mem1.szMalloc==0 ); + + /* rc==0 here means that one or both of the keys ran out of fields and + ** all the fields up to that point were equal. Return the default_rc + ** value. */ + assert( CORRUPT_DB + || vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, pPKey2->default_rc) + || pKeyInfo->db->mallocFailed + ); + return pPKey2->default_rc; +} +SQLITE_PRIVATE int sqlite3VdbeRecordCompare( + int nKey1, const void *pKey1, /* Left key */ + UnpackedRecord *pPKey2 /* Right key */ +){ + return vdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 0); +} + + +/* +** This function is an optimized version of sqlite3VdbeRecordCompare() +** that (a) the first field of pPKey2 is an integer, and (b) the +** size-of-header varint at the start of (pKey1/nKey1) fits in a single +** byte (i.e. is less than 128). +** +** To avoid concerns about buffer overreads, this routine is only used +** on schemas where the maximum valid header size is 63 bytes or less. +*/ +static int vdbeRecordCompareInt( + int nKey1, const void *pKey1, /* Left key */ + UnpackedRecord *pPKey2 /* Right key */ +){ + const u8 *aKey = &((const u8*)pKey1)[*(const u8*)pKey1 & 0x3F]; + int serial_type = ((const u8*)pKey1)[1]; + int res; + u32 y; + u64 x; + i64 v = pPKey2->aMem[0].u.i; + i64 lhs; + + vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); + assert( (*(u8*)pKey1)<=0x3F || CORRUPT_DB ); + switch( serial_type ){ + case 1: { /* 1-byte signed integer */ + lhs = ONE_BYTE_INT(aKey); + testcase( lhs<0 ); + break; + } + case 2: { /* 2-byte signed integer */ + lhs = TWO_BYTE_INT(aKey); + testcase( lhs<0 ); + break; + } + case 3: { /* 3-byte signed integer */ + lhs = THREE_BYTE_INT(aKey); + testcase( lhs<0 ); + break; + } + case 4: { /* 4-byte signed integer */ + y = FOUR_BYTE_UINT(aKey); + lhs = (i64)*(int*)&y; + testcase( lhs<0 ); + break; + } + case 5: { /* 6-byte signed integer */ + lhs = FOUR_BYTE_UINT(aKey+2) + (((i64)1)<<32)*TWO_BYTE_INT(aKey); + testcase( lhs<0 ); + break; + } + case 6: { /* 8-byte signed integer */ + x = FOUR_BYTE_UINT(aKey); + x = (x<<32) | FOUR_BYTE_UINT(aKey+4); + lhs = *(i64*)&x; + testcase( lhs<0 ); + break; + } + case 8: + lhs = 0; + break; + case 9: + lhs = 1; + break; + + /* This case could be removed without changing the results of running + ** this code. Including it causes gcc to generate a faster switch + ** statement (since the range of switch targets now starts at zero and + ** is contiguous) but does not cause any duplicate code to be generated + ** (as gcc is clever enough to combine the two like cases). Other + ** compilers might be similar. */ + case 0: case 7: + return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2); + + default: + return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2); + } + + if( v>lhs ){ + res = pPKey2->r1; + }else if( v<lhs ){ + res = pPKey2->r2; + }else if( pPKey2->nField>1 ){ + /* The first fields of the two keys are equal. Compare the trailing + ** fields. */ + res = vdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); + }else{ + /* The first fields of the two keys are equal and there are no trailing + ** fields. Return pPKey2->default_rc in this case. */ + res = pPKey2->default_rc; + } + + assert( vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, res) ); + return res; +} + +/* +** This function is an optimized version of sqlite3VdbeRecordCompare() +** that (a) the first field of pPKey2 is a string, that (b) the first field +** uses the collation sequence BINARY and (c) that the size-of-header varint +** at the start of (pKey1/nKey1) fits in a single byte. +*/ +static int vdbeRecordCompareString( + int nKey1, const void *pKey1, /* Left key */ + UnpackedRecord *pPKey2 /* Right key */ +){ + const u8 *aKey1 = (const u8*)pKey1; + int serial_type; + int res; + + vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); + getVarint32(&aKey1[1], serial_type); + if( serial_type<12 ){ + res = pPKey2->r1; /* (pKey1/nKey1) is a number or a null */ + }else if( !(serial_type & 0x01) ){ + res = pPKey2->r2; /* (pKey1/nKey1) is a blob */ + }else{ + int nCmp; + int nStr; + int szHdr = aKey1[0]; + + nStr = (serial_type-12) / 2; + if( (szHdr + nStr) > nKey1 ){ + pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; + return 0; /* Corruption */ + } + nCmp = MIN( pPKey2->aMem[0].n, nStr ); + res = memcmp(&aKey1[szHdr], pPKey2->aMem[0].z, nCmp); + + if( res==0 ){ + res = nStr - pPKey2->aMem[0].n; + if( res==0 ){ + if( pPKey2->nField>1 ){ + res = vdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); + }else{ + res = pPKey2->default_rc; + } + }else if( res>0 ){ + res = pPKey2->r2; + }else{ + res = pPKey2->r1; + } + }else if( res>0 ){ + res = pPKey2->r2; + }else{ + res = pPKey2->r1; + } + } + + assert( vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, res) + || CORRUPT_DB + || pPKey2->pKeyInfo->db->mallocFailed + ); + return res; +} + +/* +** Return a pointer to an sqlite3VdbeRecordCompare() compatible function +** suitable for comparing serialized records to the unpacked record passed +** as the only argument. +*/ +SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){ + /* varintRecordCompareInt() and varintRecordCompareString() both assume + ** that the size-of-header varint that occurs at the start of each record + ** fits in a single byte (i.e. is 127 or less). varintRecordCompareInt() + ** also assumes that it is safe to overread a buffer by at least the + ** maximum possible legal header size plus 8 bytes. Because there is + ** guaranteed to be at least 74 (but not 136) bytes of padding following each + ** buffer passed to varintRecordCompareInt() this makes it convenient to + ** limit the size of the header to 64 bytes in cases where the first field + ** is an integer. + ** + ** The easiest way to enforce this limit is to consider only records with + ** 13 fields or less. If the first field is an integer, the maximum legal + ** header size is (12*5 + 1 + 1) bytes. */ + if( (p->pKeyInfo->nField + p->pKeyInfo->nXField)<=13 ){ + int flags = p->aMem[0].flags; + if( p->pKeyInfo->aSortOrder[0] ){ + p->r1 = 1; + p->r2 = -1; + }else{ + p->r1 = -1; + p->r2 = 1; + } + if( (flags & MEM_Int) ){ + return vdbeRecordCompareInt; + } + testcase( flags & MEM_Real ); + testcase( flags & MEM_Null ); + testcase( flags & MEM_Blob ); + if( (flags & (MEM_Real|MEM_Null|MEM_Blob))==0 && p->pKeyInfo->aColl[0]==0 ){ + assert( flags & MEM_Str ); + return vdbeRecordCompareString; + } + } + + return sqlite3VdbeRecordCompare; +} /* ** pCur points at an index entry created using the OP_MakeRecord opcode. ** Read the rowid (the last field in the record) and store it in *rowid. ** Return SQLITE_OK if everything works, or an error code otherwise. @@ -63663,12 +68590,10 @@ u32 szHdr; /* Size of the header */ u32 typeRowid; /* Serial type of the rowid */ u32 lenRowid; /* Size of the rowid */ Mem m, v; - UNUSED_PARAMETER(db); - /* Get the size of the index entry. Only indices entries of less ** than 2GiB are support - anything large must be database corruption. ** Any corruption is detected in sqlite3BtreeParseCellPtr(), though, so ** this code can safely assume that nCellKey is 32-bits */ @@ -63676,12 +68601,12 @@ VVA_ONLY(rc =) sqlite3BtreeKeySize(pCur, &nCellKey); assert( rc==SQLITE_OK ); /* pCur is always valid so KeySize cannot fail */ assert( (nCellKey & SQLITE_MAX_U32)==(u64)nCellKey ); /* Read in the complete content of the index entry */ - memset(&m, 0, sizeof(m)); - rc = sqlite3VdbeMemFromBtree(pCur, 0, (int)nCellKey, 1, &m); + sqlite3VdbeMemInit(&m, db, 0); + rc = sqlite3VdbeMemFromBtree(pCur, 0, (u32)nCellKey, 1, &m); if( rc ){ return rc; } /* The index entry must begin with a header size */ @@ -63719,11 +68644,11 @@ return SQLITE_OK; /* Jump here if database corruption is detected after m has been ** allocated. Free the m object and return SQLITE_CORRUPT. */ idx_rowid_corruption: - testcase( m.zMalloc!=0 ); + testcase( m.szMalloc!=0 ); sqlite3VdbeMemRelease(&m); return SQLITE_CORRUPT_BKPT; } /* @@ -63736,34 +68661,34 @@ ** omits the rowid at the end. The rowid at the end of the index entry ** is ignored as well. Hence, this routine only compares the prefixes ** of the keys prior to the final rowid, not the entire key. */ SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare( - VdbeCursor *pC, /* The cursor to compare against */ - UnpackedRecord *pUnpacked, /* Unpacked version of key to compare against */ - int *res /* Write the comparison result here */ + sqlite3 *db, /* Database connection */ + VdbeCursor *pC, /* The cursor to compare against */ + UnpackedRecord *pUnpacked, /* Unpacked version of key */ + int *res /* Write the comparison result here */ ){ i64 nCellKey = 0; int rc; BtCursor *pCur = pC->pCursor; Mem m; assert( sqlite3BtreeCursorIsValid(pCur) ); VVA_ONLY(rc =) sqlite3BtreeKeySize(pCur, &nCellKey); assert( rc==SQLITE_OK ); /* pCur is always valid so KeySize cannot fail */ - /* nCellKey will always be between 0 and 0xffffffff because of the say + /* nCellKey will always be between 0 and 0xffffffff because of the way ** that btreeParseCellPtr() and sqlite3GetVarint32() are implemented */ if( nCellKey<=0 || nCellKey>0x7fffffff ){ *res = 0; return SQLITE_CORRUPT_BKPT; } - memset(&m, 0, sizeof(m)); - rc = sqlite3VdbeMemFromBtree(pC->pCursor, 0, (int)nCellKey, 1, &m); + sqlite3VdbeMemInit(&m, db, 0); + rc = sqlite3VdbeMemFromBtree(pC->pCursor, 0, (u32)nCellKey, 1, &m); if( rc ){ return rc; } - assert( pUnpacked->flags & UNPACKED_PREFIX_MATCH ); *res = sqlite3VdbeRecordCompare(m.n, m.z, pUnpacked); sqlite3VdbeMemRelease(&m); return SQLITE_OK; } @@ -63824,11 +68749,10 @@ if( 0==(pMem->flags & MEM_Null) ){ sqlite3_value *pRet = sqlite3ValueNew(v->db); if( pRet ){ sqlite3VdbeMemCopy((Mem *)pRet, pMem); sqlite3ValueApplyAffinity(pRet, aff, SQLITE_UTF8); - sqlite3VdbeMemStoreType((Mem *)pRet); } return pRet; } } return 0; @@ -63888,11 +68812,11 @@ ** execution environment changes in a way that would alter the program ** that sqlite3_prepare() generates. For example, if new functions or ** collating sequences are registered or if an authorizer function is ** added or changed. */ -SQLITE_API int sqlite3_expired(sqlite3_stmt *pStmt){ +SQLITE_API int SQLITE_STDCALL sqlite3_expired(sqlite3_stmt *pStmt){ Vdbe *p = (Vdbe*)pStmt; return p==0 || p->expired; } #endif @@ -63925,11 +68849,11 @@ ** machine. ** ** This routine sets the error code and string returned by ** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16(). */ -SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt){ +SQLITE_API int SQLITE_STDCALL sqlite3_finalize(sqlite3_stmt *pStmt){ int rc; if( pStmt==0 ){ /* IMPLEMENTATION-OF: R-57228-12904 Invoking sqlite3_finalize() on a NULL ** pointer is a harmless no-op. */ rc = SQLITE_OK; @@ -63951,11 +68875,11 @@ ** the prior execution is returned. ** ** This routine sets the error code and string returned by ** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16(). */ -SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt){ +SQLITE_API int SQLITE_STDCALL sqlite3_reset(sqlite3_stmt *pStmt){ int rc; if( pStmt==0 ){ rc = SQLITE_OK; }else{ Vdbe *v = (Vdbe*)pStmt; @@ -63970,11 +68894,11 @@ } /* ** Set all the parameters in the compiled SQL statement to NULL. */ -SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt *pStmt){ +SQLITE_API int SQLITE_STDCALL sqlite3_clear_bindings(sqlite3_stmt *pStmt){ int i; int rc = SQLITE_OK; Vdbe *p = (Vdbe*)pStmt; #if SQLITE_THREADSAFE sqlite3_mutex *mutex = ((Vdbe*)pStmt)->db->mutex; @@ -63994,184 +68918,273 @@ /**************************** sqlite3_value_ ******************************* ** The following routines extract information from a Mem or sqlite3_value ** structure. */ -SQLITE_API const void *sqlite3_value_blob(sqlite3_value *pVal){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_blob(sqlite3_value *pVal){ Mem *p = (Mem*)pVal; if( p->flags & (MEM_Blob|MEM_Str) ){ sqlite3VdbeMemExpandBlob(p); - p->flags &= ~MEM_Str; p->flags |= MEM_Blob; return p->n ? p->z : 0; }else{ return sqlite3_value_text(pVal); } } -SQLITE_API int sqlite3_value_bytes(sqlite3_value *pVal){ +SQLITE_API int SQLITE_STDCALL sqlite3_value_bytes(sqlite3_value *pVal){ return sqlite3ValueBytes(pVal, SQLITE_UTF8); } -SQLITE_API int sqlite3_value_bytes16(sqlite3_value *pVal){ +SQLITE_API int SQLITE_STDCALL sqlite3_value_bytes16(sqlite3_value *pVal){ return sqlite3ValueBytes(pVal, SQLITE_UTF16NATIVE); } -SQLITE_API double sqlite3_value_double(sqlite3_value *pVal){ +SQLITE_API double SQLITE_STDCALL sqlite3_value_double(sqlite3_value *pVal){ return sqlite3VdbeRealValue((Mem*)pVal); } -SQLITE_API int sqlite3_value_int(sqlite3_value *pVal){ +SQLITE_API int SQLITE_STDCALL sqlite3_value_int(sqlite3_value *pVal){ return (int)sqlite3VdbeIntValue((Mem*)pVal); } -SQLITE_API sqlite_int64 sqlite3_value_int64(sqlite3_value *pVal){ +SQLITE_API sqlite_int64 SQLITE_STDCALL sqlite3_value_int64(sqlite3_value *pVal){ return sqlite3VdbeIntValue((Mem*)pVal); } -SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value *pVal){ +SQLITE_API const unsigned char *SQLITE_STDCALL sqlite3_value_text(sqlite3_value *pVal){ return (const unsigned char *)sqlite3ValueText(pVal, SQLITE_UTF8); } #ifndef SQLITE_OMIT_UTF16 -SQLITE_API const void *sqlite3_value_text16(sqlite3_value* pVal){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16(sqlite3_value* pVal){ return sqlite3ValueText(pVal, SQLITE_UTF16NATIVE); } -SQLITE_API const void *sqlite3_value_text16be(sqlite3_value *pVal){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16be(sqlite3_value *pVal){ return sqlite3ValueText(pVal, SQLITE_UTF16BE); } -SQLITE_API const void *sqlite3_value_text16le(sqlite3_value *pVal){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16le(sqlite3_value *pVal){ return sqlite3ValueText(pVal, SQLITE_UTF16LE); } #endif /* SQLITE_OMIT_UTF16 */ -SQLITE_API int sqlite3_value_type(sqlite3_value* pVal){ - return pVal->type; +/* EVIDENCE-OF: R-12793-43283 Every value in SQLite has one of five +** fundamental datatypes: 64-bit signed integer 64-bit IEEE floating +** point number string BLOB NULL +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_value_type(sqlite3_value* pVal){ + static const u8 aType[] = { + SQLITE_BLOB, /* 0x00 */ + SQLITE_NULL, /* 0x01 */ + SQLITE_TEXT, /* 0x02 */ + SQLITE_NULL, /* 0x03 */ + SQLITE_INTEGER, /* 0x04 */ + SQLITE_NULL, /* 0x05 */ + SQLITE_INTEGER, /* 0x06 */ + SQLITE_NULL, /* 0x07 */ + SQLITE_FLOAT, /* 0x08 */ + SQLITE_NULL, /* 0x09 */ + SQLITE_FLOAT, /* 0x0a */ + SQLITE_NULL, /* 0x0b */ + SQLITE_INTEGER, /* 0x0c */ + SQLITE_NULL, /* 0x0d */ + SQLITE_INTEGER, /* 0x0e */ + SQLITE_NULL, /* 0x0f */ + SQLITE_BLOB, /* 0x10 */ + SQLITE_NULL, /* 0x11 */ + SQLITE_TEXT, /* 0x12 */ + SQLITE_NULL, /* 0x13 */ + SQLITE_INTEGER, /* 0x14 */ + SQLITE_NULL, /* 0x15 */ + SQLITE_INTEGER, /* 0x16 */ + SQLITE_NULL, /* 0x17 */ + SQLITE_FLOAT, /* 0x18 */ + SQLITE_NULL, /* 0x19 */ + SQLITE_FLOAT, /* 0x1a */ + SQLITE_NULL, /* 0x1b */ + SQLITE_INTEGER, /* 0x1c */ + SQLITE_NULL, /* 0x1d */ + SQLITE_INTEGER, /* 0x1e */ + SQLITE_NULL, /* 0x1f */ + }; + return aType[pVal->flags&MEM_AffMask]; } /**************************** sqlite3_result_ ******************************* ** The following routines are used by user-defined functions to specify ** the function result. ** -** The setStrOrError() funtion calls sqlite3VdbeMemSetStr() to store the +** The setStrOrError() function calls sqlite3VdbeMemSetStr() to store the ** result as a string or blob but if the string or blob is too large, it ** then sets the error code to SQLITE_TOOBIG +** +** The invokeValueDestructor(P,X) routine invokes destructor function X() +** on value P is not going to be used and need to be destroyed. */ static void setResultStrOrError( sqlite3_context *pCtx, /* Function context */ const char *z, /* String pointer */ int n, /* Bytes in string, or negative */ u8 enc, /* Encoding of z. 0 for BLOBs */ void (*xDel)(void*) /* Destructor function */ ){ - if( sqlite3VdbeMemSetStr(&pCtx->s, z, n, enc, xDel)==SQLITE_TOOBIG ){ + if( sqlite3VdbeMemSetStr(pCtx->pOut, z, n, enc, xDel)==SQLITE_TOOBIG ){ sqlite3_result_error_toobig(pCtx); } } -SQLITE_API void sqlite3_result_blob( +static int invokeValueDestructor( + const void *p, /* Value to destroy */ + void (*xDel)(void*), /* The destructor */ + sqlite3_context *pCtx /* Set a SQLITE_TOOBIG error if no NULL */ +){ + assert( xDel!=SQLITE_DYNAMIC ); + if( xDel==0 ){ + /* noop */ + }else if( xDel==SQLITE_TRANSIENT ){ + /* noop */ + }else{ + xDel((void*)p); + } + if( pCtx ) sqlite3_result_error_toobig(pCtx); + return SQLITE_TOOBIG; +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_blob( sqlite3_context *pCtx, const void *z, int n, void (*xDel)(void *) ){ assert( n>=0 ); - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - setResultStrOrError(pCtx, z, n, 0, xDel); -} -SQLITE_API void sqlite3_result_double(sqlite3_context *pCtx, double rVal){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - sqlite3VdbeMemSetDouble(&pCtx->s, rVal); -} -SQLITE_API void sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - pCtx->isError = SQLITE_ERROR; - pCtx->fErrorOrAux = 1; - sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF8, SQLITE_TRANSIENT); -} -#ifndef SQLITE_OMIT_UTF16 -SQLITE_API void sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - pCtx->isError = SQLITE_ERROR; - pCtx->fErrorOrAux = 1; - sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT); -} -#endif -SQLITE_API void sqlite3_result_int(sqlite3_context *pCtx, int iVal){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - sqlite3VdbeMemSetInt64(&pCtx->s, (i64)iVal); -} -SQLITE_API void sqlite3_result_int64(sqlite3_context *pCtx, i64 iVal){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - sqlite3VdbeMemSetInt64(&pCtx->s, iVal); -} -SQLITE_API void sqlite3_result_null(sqlite3_context *pCtx){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - sqlite3VdbeMemSetNull(&pCtx->s); -} -SQLITE_API void sqlite3_result_text( + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + setResultStrOrError(pCtx, z, n, 0, xDel); +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_blob64( + sqlite3_context *pCtx, + const void *z, + sqlite3_uint64 n, + void (*xDel)(void *) +){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + assert( xDel!=SQLITE_DYNAMIC ); + if( n>0x7fffffff ){ + (void)invokeValueDestructor(z, xDel, pCtx); + }else{ + setResultStrOrError(pCtx, z, (int)n, 0, xDel); + } +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_double(sqlite3_context *pCtx, double rVal){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemSetDouble(pCtx->pOut, rVal); +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + pCtx->isError = SQLITE_ERROR; + pCtx->fErrorOrAux = 1; + sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF8, SQLITE_TRANSIENT); +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API void SQLITE_STDCALL sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + pCtx->isError = SQLITE_ERROR; + pCtx->fErrorOrAux = 1; + sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT); +} +#endif +SQLITE_API void SQLITE_STDCALL sqlite3_result_int(sqlite3_context *pCtx, int iVal){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemSetInt64(pCtx->pOut, (i64)iVal); +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_int64(sqlite3_context *pCtx, i64 iVal){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemSetInt64(pCtx->pOut, iVal); +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_null(sqlite3_context *pCtx){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemSetNull(pCtx->pOut); +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_text( sqlite3_context *pCtx, const char *z, int n, void (*xDel)(void *) ){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - setResultStrOrError(pCtx, z, n, SQLITE_UTF8, xDel); -} -#ifndef SQLITE_OMIT_UTF16 -SQLITE_API void sqlite3_result_text16( - sqlite3_context *pCtx, - const void *z, - int n, - void (*xDel)(void *) -){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - setResultStrOrError(pCtx, z, n, SQLITE_UTF16NATIVE, xDel); -} -SQLITE_API void sqlite3_result_text16be( - sqlite3_context *pCtx, - const void *z, - int n, - void (*xDel)(void *) -){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - setResultStrOrError(pCtx, z, n, SQLITE_UTF16BE, xDel); -} -SQLITE_API void sqlite3_result_text16le( - sqlite3_context *pCtx, - const void *z, - int n, - void (*xDel)(void *) -){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - setResultStrOrError(pCtx, z, n, SQLITE_UTF16LE, xDel); -} -#endif /* SQLITE_OMIT_UTF16 */ -SQLITE_API void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - sqlite3VdbeMemCopy(&pCtx->s, pValue); -} -SQLITE_API void sqlite3_result_zeroblob(sqlite3_context *pCtx, int n){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - sqlite3VdbeMemSetZeroBlob(&pCtx->s, n); -} -SQLITE_API void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ - pCtx->isError = errCode; - pCtx->fErrorOrAux = 1; - if( pCtx->s.flags & MEM_Null ){ - sqlite3VdbeMemSetStr(&pCtx->s, sqlite3ErrStr(errCode), -1, - SQLITE_UTF8, SQLITE_STATIC); - } -} - -/* Force an SQLITE_TOOBIG error. */ -SQLITE_API void sqlite3_result_error_toobig(sqlite3_context *pCtx){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - pCtx->isError = SQLITE_TOOBIG; - pCtx->fErrorOrAux = 1; - sqlite3VdbeMemSetStr(&pCtx->s, "string or blob too big", -1, - SQLITE_UTF8, SQLITE_STATIC); -} - -/* An SQLITE_NOMEM error. */ -SQLITE_API void sqlite3_result_error_nomem(sqlite3_context *pCtx){ - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); - sqlite3VdbeMemSetNull(&pCtx->s); - pCtx->isError = SQLITE_NOMEM; - pCtx->fErrorOrAux = 1; - pCtx->s.db->mallocFailed = 1; + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + setResultStrOrError(pCtx, z, n, SQLITE_UTF8, xDel); +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_text64( + sqlite3_context *pCtx, + const char *z, + sqlite3_uint64 n, + void (*xDel)(void *), + unsigned char enc +){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + assert( xDel!=SQLITE_DYNAMIC ); + if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + if( n>0x7fffffff ){ + (void)invokeValueDestructor(z, xDel, pCtx); + }else{ + setResultStrOrError(pCtx, z, (int)n, enc, xDel); + } +} +#ifndef SQLITE_OMIT_UTF16 +SQLITE_API void SQLITE_STDCALL sqlite3_result_text16( + sqlite3_context *pCtx, + const void *z, + int n, + void (*xDel)(void *) +){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + setResultStrOrError(pCtx, z, n, SQLITE_UTF16NATIVE, xDel); +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_text16be( + sqlite3_context *pCtx, + const void *z, + int n, + void (*xDel)(void *) +){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + setResultStrOrError(pCtx, z, n, SQLITE_UTF16BE, xDel); +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_text16le( + sqlite3_context *pCtx, + const void *z, + int n, + void (*xDel)(void *) +){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + setResultStrOrError(pCtx, z, n, SQLITE_UTF16LE, xDel); +} +#endif /* SQLITE_OMIT_UTF16 */ +SQLITE_API void SQLITE_STDCALL sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemCopy(pCtx->pOut, pValue); +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_zeroblob(sqlite3_context *pCtx, int n){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemSetZeroBlob(pCtx->pOut, n); +} +SQLITE_API void SQLITE_STDCALL sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ + pCtx->isError = errCode; + pCtx->fErrorOrAux = 1; +#ifdef SQLITE_DEBUG + if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode; +#endif + if( pCtx->pOut->flags & MEM_Null ){ + sqlite3VdbeMemSetStr(pCtx->pOut, sqlite3ErrStr(errCode), -1, + SQLITE_UTF8, SQLITE_STATIC); + } +} + +/* Force an SQLITE_TOOBIG error. */ +SQLITE_API void SQLITE_STDCALL sqlite3_result_error_toobig(sqlite3_context *pCtx){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + pCtx->isError = SQLITE_TOOBIG; + pCtx->fErrorOrAux = 1; + sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1, + SQLITE_UTF8, SQLITE_STATIC); +} + +/* An SQLITE_NOMEM error. */ +SQLITE_API void SQLITE_STDCALL sqlite3_result_error_nomem(sqlite3_context *pCtx){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + sqlite3VdbeMemSetNull(pCtx->pOut); + pCtx->isError = SQLITE_NOMEM; + pCtx->fErrorOrAux = 1; + pCtx->pOut->db->mallocFailed = 1; } /* ** This function is called after a transaction has been committed. It ** invokes callbacks registered with sqlite3_wal_hook() as required. @@ -64181,11 +69194,14 @@ #ifndef SQLITE_OMIT_WAL int i; for(i=0; i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ - int nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt)); + int nEntry; + sqlite3BtreeEnter(pBt); + nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt)); + sqlite3BtreeLeave(pBt); if( db->xWalCallback && nEntry>0 && rc==SQLITE_OK ){ rc = db->xWalCallback(db->pWalArg, db, db->aDb[i].zName, nEntry); } } } @@ -64223,11 +69239,11 @@ ** legacy behavior of returning SQLITE_MISUSE for cases where the ** previous sqlite3_step() returned something other than a SQLITE_LOCKED ** or SQLITE_BUSY error. */ #ifdef SQLITE_OMIT_AUTORESET - if( p->rc==SQLITE_BUSY || p->rc==SQLITE_LOCKED ){ + if( (rc = p->rc&0xff)==SQLITE_BUSY || rc==SQLITE_LOCKED ){ sqlite3_reset((sqlite3_stmt*)p); }else{ return SQLITE_MISUSE_BKPT; } #else @@ -64269,10 +69285,13 @@ db->nVdbeActive++; if( p->readOnly==0 ) db->nVdbeWrite++; if( p->bIsReader ) db->nVdbeRead++; p->pc = 0; } +#ifdef SQLITE_DEBUG + p->rcApp = SQLITE_OK; +#endif #ifndef SQLITE_OMIT_EXPLAIN if( p->explain ){ rc = sqlite3VdbeList(p); }else #endif /* SQLITE_OMIT_EXPLAIN */ @@ -64313,11 +69332,11 @@ ** were called on statement p. */ assert( rc==SQLITE_ROW || rc==SQLITE_DONE || rc==SQLITE_ERROR || rc==SQLITE_BUSY || rc==SQLITE_MISUSE ); - assert( p->rc!=SQLITE_ROW && p->rc!=SQLITE_DONE ); + assert( (p->rc!=SQLITE_ROW && p->rc!=SQLITE_DONE) || p->rc==p->rcApp ); if( p->isPrepareV2 && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){ /* If this statement was prepared using sqlite3_prepare_v2(), and an ** error has occurred, then return the error code in p->rc to the ** caller. Set the error code in the database handle to the same value. */ @@ -64329,11 +69348,11 @@ /* ** This is the top-level implementation of sqlite3_step(). Call ** sqlite3Step() to do most of the work. If a schema error occurs, ** call sqlite3Reprepare() and try again. */ -SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){ +SQLITE_API int SQLITE_STDCALL sqlite3_step(sqlite3_stmt *pStmt){ int rc = SQLITE_OK; /* Result from sqlite3Step() */ int rc2 = SQLITE_OK; /* Result from sqlite3Reprepare() */ Vdbe *v = (Vdbe*)pStmt; /* the prepared statement */ int cnt = 0; /* Counter to prevent infinite loop of reprepares */ sqlite3 *db; /* The database connection */ @@ -64343,17 +69362,19 @@ } db = v->db; sqlite3_mutex_enter(db->mutex); v->doingRerun = 0; while( (rc = sqlite3Step(v))==SQLITE_SCHEMA - && cnt++ < SQLITE_MAX_SCHEMA_RETRY - && (rc2 = rc = sqlite3Reprepare(v))==SQLITE_OK ){ + && cnt++ < SQLITE_MAX_SCHEMA_RETRY ){ + int savedPc = v->pc; + rc2 = rc = sqlite3Reprepare(v); + if( rc!=SQLITE_OK) break; sqlite3_reset(pStmt); - v->doingRerun = 1; + if( savedPc>=0 ) v->doingRerun = 1; assert( v->expired==0 ); } - if( rc2!=SQLITE_OK && ALWAYS(v->isPrepareV2) && ALWAYS(db->pErr) ){ + if( rc2!=SQLITE_OK ){ /* This case occurs after failing to recompile an sql statement. ** The error message from the SQL compiler has already been loaded ** into the database handle. This block copies the error message ** from the database handle into the statement and sets the statement ** program counter to 0 to ensure that when the statement is @@ -64378,11 +69399,11 @@ /* ** Extract the user data from a sqlite3_context structure and return a ** pointer to it. */ -SQLITE_API void *sqlite3_user_data(sqlite3_context *p){ +SQLITE_API void *SQLITE_STDCALL sqlite3_user_data(sqlite3_context *p){ assert( p && p->pFunc ); return p->pFunc->pUserData; } /* @@ -64393,26 +69414,36 @@ ** returns a copy of the pointer to the database connection (the 1st ** parameter) of the sqlite3_create_function() and ** sqlite3_create_function16() routines that originally registered the ** application defined function. */ -SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){ +SQLITE_API sqlite3 *SQLITE_STDCALL sqlite3_context_db_handle(sqlite3_context *p){ assert( p && p->pFunc ); - return p->s.db; + return p->pOut->db; } /* -** Return the current time for a statement +** Return the current time for a statement. If the current time +** is requested more than once within the same run of a single prepared +** statement, the exact same time is returned for each invocation regardless +** of the amount of time that elapses between invocations. In other words, +** the time returned is always the time of the first call. */ SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context *p){ - Vdbe *v = p->pVdbe; int rc; - if( v->iCurrentTime==0 ){ - rc = sqlite3OsCurrentTimeInt64(p->s.db->pVfs, &v->iCurrentTime); - if( rc ) v->iCurrentTime = 0; +#ifndef SQLITE_ENABLE_STAT3_OR_STAT4 + sqlite3_int64 *piTime = &p->pVdbe->iCurrentTime; + assert( p->pVdbe!=0 ); +#else + sqlite3_int64 iTime = 0; + sqlite3_int64 *piTime = p->pVdbe!=0 ? &p->pVdbe->iCurrentTime : &iTime; +#endif + if( *piTime==0 ){ + rc = sqlite3OsCurrentTimeInt64(p->pOut->db->pVfs, piTime); + if( rc ) *piTime = 0; } - return v->iCurrentTime; + return *piTime; } /* ** The following is the implementation of an SQL function that always ** fails with an error message stating that the function is used in the @@ -64434,68 +69465,87 @@ sqlite3_result_error(context, zErr, -1); sqlite3_free(zErr); } /* -** Allocate or return the aggregate context for a user function. A new -** context is allocated on the first call. Subsequent calls return the -** same context that was returned on prior calls. +** Create a new aggregate context for p and return a pointer to +** its pMem->z element. */ -SQLITE_API void *sqlite3_aggregate_context(sqlite3_context *p, int nByte){ - Mem *pMem; - assert( p && p->pFunc && p->pFunc->xStep ); - assert( sqlite3_mutex_held(p->s.db->mutex) ); - pMem = p->pMem; - testcase( nByte<0 ); - if( (pMem->flags & MEM_Agg)==0 ){ - if( nByte<=0 ){ - sqlite3VdbeMemReleaseExternal(pMem); - pMem->flags = MEM_Null; - pMem->z = 0; - }else{ - sqlite3VdbeMemGrow(pMem, nByte, 0); - pMem->flags = MEM_Agg; - pMem->u.pDef = p->pFunc; - if( pMem->z ){ - memset(pMem->z, 0, nByte); - } +static SQLITE_NOINLINE void *createAggContext(sqlite3_context *p, int nByte){ + Mem *pMem = p->pMem; + assert( (pMem->flags & MEM_Agg)==0 ); + if( nByte<=0 ){ + sqlite3VdbeMemSetNull(pMem); + pMem->z = 0; + }else{ + sqlite3VdbeMemClearAndResize(pMem, nByte); + pMem->flags = MEM_Agg; + pMem->u.pDef = p->pFunc; + if( pMem->z ){ + memset(pMem->z, 0, nByte); } } return (void*)pMem->z; } /* -** Return the auxilary data pointer, if any, for the iArg'th argument to +** Allocate or return the aggregate context for a user function. A new +** context is allocated on the first call. Subsequent calls return the +** same context that was returned on prior calls. +*/ +SQLITE_API void *SQLITE_STDCALL sqlite3_aggregate_context(sqlite3_context *p, int nByte){ + assert( p && p->pFunc && p->pFunc->xStep ); + assert( sqlite3_mutex_held(p->pOut->db->mutex) ); + testcase( nByte<0 ); + if( (p->pMem->flags & MEM_Agg)==0 ){ + return createAggContext(p, nByte); + }else{ + return (void*)p->pMem->z; + } +} + +/* +** Return the auxiliary data pointer, if any, for the iArg'th argument to ** the user-function defined by pCtx. */ -SQLITE_API void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){ +SQLITE_API void *SQLITE_STDCALL sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){ AuxData *pAuxData; - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); +#if SQLITE_ENABLE_STAT3_OR_STAT4 + if( pCtx->pVdbe==0 ) return 0; +#else + assert( pCtx->pVdbe!=0 ); +#endif for(pAuxData=pCtx->pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNext){ if( pAuxData->iOp==pCtx->iOp && pAuxData->iArg==iArg ) break; } return (pAuxData ? pAuxData->pAux : 0); } /* -** Set the auxilary data pointer and delete function, for the iArg'th +** Set the auxiliary data pointer and delete function, for the iArg'th ** argument to the user-function defined by pCtx. Any previous value is ** deleted by calling the delete function specified when it was set. */ -SQLITE_API void sqlite3_set_auxdata( +SQLITE_API void SQLITE_STDCALL sqlite3_set_auxdata( sqlite3_context *pCtx, int iArg, void *pAux, void (*xDelete)(void*) ){ AuxData *pAuxData; Vdbe *pVdbe = pCtx->pVdbe; - assert( sqlite3_mutex_held(pCtx->s.db->mutex) ); + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); if( iArg<0 ) goto failed; +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + if( pVdbe==0 ) goto failed; +#else + assert( pVdbe!=0 ); +#endif for(pAuxData=pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNext){ if( pAuxData->iOp==pCtx->iOp && pAuxData->iArg==iArg ) break; } if( pAuxData==0 ){ @@ -64523,42 +69573,77 @@ } } #ifndef SQLITE_OMIT_DEPRECATED /* -** Return the number of times the Step function of a aggregate has been +** Return the number of times the Step function of an aggregate has been ** called. ** ** This function is deprecated. Do not use it for new code. It is ** provide only to avoid breaking legacy code. New aggregate function ** implementations should keep their own counts within their aggregate ** context. */ -SQLITE_API int sqlite3_aggregate_count(sqlite3_context *p){ +SQLITE_API int SQLITE_STDCALL sqlite3_aggregate_count(sqlite3_context *p){ assert( p && p->pMem && p->pFunc && p->pFunc->xStep ); return p->pMem->n; } #endif /* ** Return the number of columns in the result set for the statement pStmt. */ -SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt){ +SQLITE_API int SQLITE_STDCALL sqlite3_column_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; return pVm ? pVm->nResColumn : 0; } /* ** Return the number of values available from the current row of the ** currently executing statement pStmt. */ -SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt){ +SQLITE_API int SQLITE_STDCALL sqlite3_data_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; if( pVm==0 || pVm->pResultSet==0 ) return 0; return pVm->nResColumn; } +/* +** Return a pointer to static memory containing an SQL NULL value. +*/ +static const Mem *columnNullValue(void){ + /* Even though the Mem structure contains an element + ** of type i64, on certain architectures (x86) with certain compiler + ** switches (-Os), gcc may align this Mem object on a 4-byte boundary + ** instead of an 8-byte one. This all works fine, except that when + ** running with SQLITE_DEBUG defined the SQLite code sometimes assert()s + ** that a Mem structure is located on an 8-byte boundary. To prevent + ** these assert()s from failing, when building with SQLITE_DEBUG defined + ** using gcc, we force nullMem to be 8-byte aligned using the magical + ** __attribute__((aligned(8))) macro. */ + static const Mem nullMem +#if defined(SQLITE_DEBUG) && defined(__GNUC__) + __attribute__((aligned(8))) +#endif + = { + /* .u = */ {0}, + /* .flags = */ MEM_Null, + /* .enc = */ 0, + /* .n = */ 0, + /* .z = */ 0, + /* .zMalloc = */ 0, + /* .szMalloc = */ 0, + /* .iPadding1 = */ 0, + /* .db = */ 0, + /* .xDel = */ 0, +#ifdef SQLITE_DEBUG + /* .pScopyFrom = */ 0, + /* .pFiller = */ 0, +#endif + }; + return &nullMem; +} /* ** Check to see if column iCol of the given statement is valid. If ** it is, return a pointer to the Mem for the value of that column. ** If iCol is not valid, return a pointer to a Mem which has a value @@ -64571,36 +69656,15 @@ pVm = (Vdbe *)pStmt; if( pVm && pVm->pResultSet!=0 && i<pVm->nResColumn && i>=0 ){ sqlite3_mutex_enter(pVm->db->mutex); pOut = &pVm->pResultSet[i]; }else{ - /* If the value passed as the second argument is out of range, return - ** a pointer to the following static Mem object which contains the - ** value SQL NULL. Even though the Mem structure contains an element - ** of type i64, on certain architectures (x86) with certain compiler - ** switches (-Os), gcc may align this Mem object on a 4-byte boundary - ** instead of an 8-byte one. This all works fine, except that when - ** running with SQLITE_DEBUG defined the SQLite code sometimes assert()s - ** that a Mem structure is located on an 8-byte boundary. To prevent - ** these assert()s from failing, when building with SQLITE_DEBUG defined - ** using gcc, we force nullMem to be 8-byte aligned using the magical - ** __attribute__((aligned(8))) macro. */ - static const Mem nullMem -#if defined(SQLITE_DEBUG) && defined(__GNUC__) - __attribute__((aligned(8))) -#endif - = {0, "", (double)0, {0}, 0, MEM_Null, SQLITE_NULL, 0, -#ifdef SQLITE_DEBUG - 0, 0, /* pScopyFrom, pFiller */ -#endif - 0, 0 }; - if( pVm && ALWAYS(pVm->db) ){ sqlite3_mutex_enter(pVm->db->mutex); - sqlite3Error(pVm->db, SQLITE_RANGE, 0); + sqlite3Error(pVm->db, SQLITE_RANGE); } - pOut = (Mem*)&nullMem; + pOut = (Mem*)columnNullValue(); } return pOut; } /* @@ -64637,67 +69701,67 @@ /**************************** sqlite3_column_ ******************************* ** The following routines are used to access elements of the current row ** in the result set. */ -SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt *pStmt, int i){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_blob(sqlite3_stmt *pStmt, int i){ const void *val; val = sqlite3_value_blob( columnMem(pStmt,i) ); /* Even though there is no encoding conversion, value_blob() might ** need to call malloc() to expand the result of a zeroblob() ** expression. */ columnMallocFailure(pStmt); return val; } -SQLITE_API int sqlite3_column_bytes(sqlite3_stmt *pStmt, int i){ +SQLITE_API int SQLITE_STDCALL sqlite3_column_bytes(sqlite3_stmt *pStmt, int i){ int val = sqlite3_value_bytes( columnMem(pStmt,i) ); columnMallocFailure(pStmt); return val; } -SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt *pStmt, int i){ +SQLITE_API int SQLITE_STDCALL sqlite3_column_bytes16(sqlite3_stmt *pStmt, int i){ int val = sqlite3_value_bytes16( columnMem(pStmt,i) ); columnMallocFailure(pStmt); return val; } -SQLITE_API double sqlite3_column_double(sqlite3_stmt *pStmt, int i){ +SQLITE_API double SQLITE_STDCALL sqlite3_column_double(sqlite3_stmt *pStmt, int i){ double val = sqlite3_value_double( columnMem(pStmt,i) ); columnMallocFailure(pStmt); return val; } -SQLITE_API int sqlite3_column_int(sqlite3_stmt *pStmt, int i){ +SQLITE_API int SQLITE_STDCALL sqlite3_column_int(sqlite3_stmt *pStmt, int i){ int val = sqlite3_value_int( columnMem(pStmt,i) ); columnMallocFailure(pStmt); return val; } -SQLITE_API sqlite_int64 sqlite3_column_int64(sqlite3_stmt *pStmt, int i){ +SQLITE_API sqlite_int64 SQLITE_STDCALL sqlite3_column_int64(sqlite3_stmt *pStmt, int i){ sqlite_int64 val = sqlite3_value_int64( columnMem(pStmt,i) ); columnMallocFailure(pStmt); return val; } -SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt *pStmt, int i){ +SQLITE_API const unsigned char *SQLITE_STDCALL sqlite3_column_text(sqlite3_stmt *pStmt, int i){ const unsigned char *val = sqlite3_value_text( columnMem(pStmt,i) ); columnMallocFailure(pStmt); return val; } -SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt *pStmt, int i){ +SQLITE_API sqlite3_value *SQLITE_STDCALL sqlite3_column_value(sqlite3_stmt *pStmt, int i){ Mem *pOut = columnMem(pStmt, i); if( pOut->flags&MEM_Static ){ pOut->flags &= ~MEM_Static; pOut->flags |= MEM_Ephem; } columnMallocFailure(pStmt); return (sqlite3_value *)pOut; } #ifndef SQLITE_OMIT_UTF16 -SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt *pStmt, int i){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_text16(sqlite3_stmt *pStmt, int i){ const void *val = sqlite3_value_text16( columnMem(pStmt,i) ); columnMallocFailure(pStmt); return val; } #endif /* SQLITE_OMIT_UTF16 */ -SQLITE_API int sqlite3_column_type(sqlite3_stmt *pStmt, int i){ +SQLITE_API int SQLITE_STDCALL sqlite3_column_type(sqlite3_stmt *pStmt, int i){ int iType = sqlite3_value_type( columnMem(pStmt,i) ); columnMallocFailure(pStmt); return iType; } @@ -64721,15 +69785,23 @@ sqlite3_stmt *pStmt, int N, const void *(*xFunc)(Mem*), int useType ){ - const void *ret = 0; - Vdbe *p = (Vdbe *)pStmt; + const void *ret; + Vdbe *p; int n; - sqlite3 *db = p->db; - + sqlite3 *db; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pStmt==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + ret = 0; + p = (Vdbe *)pStmt; + db = p->db; assert( db!=0 ); n = sqlite3_column_count(pStmt); if( N<n && N>=0 ){ N += useType*n; sqlite3_mutex_enter(db->mutex); @@ -64749,16 +69821,16 @@ /* ** Return the name of the Nth column of the result set returned by SQL ** statement pStmt. */ -SQLITE_API const char *sqlite3_column_name(sqlite3_stmt *pStmt, int N){ +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_name(sqlite3_stmt *pStmt, int N){ return columnName( pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_NAME); } #ifndef SQLITE_OMIT_UTF16 -SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt *pStmt, int N){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_name16(sqlite3_stmt *pStmt, int N){ return columnName( pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_NAME); } #endif @@ -64774,16 +69846,16 @@ #ifndef SQLITE_OMIT_DECLTYPE /* ** Return the column declaration type (if applicable) of the 'i'th column ** of the result set of SQL statement pStmt. */ -SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt *pStmt, int N){ +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_decltype(sqlite3_stmt *pStmt, int N){ return columnName( pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_DECLTYPE); } #ifndef SQLITE_OMIT_UTF16 -SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt *pStmt, int N){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_decltype16(sqlite3_stmt *pStmt, int N){ return columnName( pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_DECLTYPE); } #endif /* SQLITE_OMIT_UTF16 */ #endif /* SQLITE_OMIT_DECLTYPE */ @@ -64790,50 +69862,50 @@ #ifdef SQLITE_ENABLE_COLUMN_METADATA /* ** Return the name of the database from which a result column derives. ** NULL is returned if the result column is an expression or constant or -** anything else which is not an unabiguous reference to a database column. +** anything else which is not an unambiguous reference to a database column. */ -SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt *pStmt, int N){ +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_database_name(sqlite3_stmt *pStmt, int N){ return columnName( pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_DATABASE); } #ifndef SQLITE_OMIT_UTF16 -SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt *pStmt, int N){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_database_name16(sqlite3_stmt *pStmt, int N){ return columnName( pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_DATABASE); } #endif /* SQLITE_OMIT_UTF16 */ /* ** Return the name of the table from which a result column derives. ** NULL is returned if the result column is an expression or constant or -** anything else which is not an unabiguous reference to a database column. +** anything else which is not an unambiguous reference to a database column. */ -SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt *pStmt, int N){ +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_table_name(sqlite3_stmt *pStmt, int N){ return columnName( pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_TABLE); } #ifndef SQLITE_OMIT_UTF16 -SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt *pStmt, int N){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_table_name16(sqlite3_stmt *pStmt, int N){ return columnName( pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_TABLE); } #endif /* SQLITE_OMIT_UTF16 */ /* ** Return the name of the table column from which a result column derives. ** NULL is returned if the result column is an expression or constant or -** anything else which is not an unabiguous reference to a database column. +** anything else which is not an unambiguous reference to a database column. */ -SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt *pStmt, int N){ +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_origin_name(sqlite3_stmt *pStmt, int N){ return columnName( pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_COLUMN); } #ifndef SQLITE_OMIT_UTF16 -SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){ return columnName( pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_COLUMN); } #endif /* SQLITE_OMIT_UTF16 */ #endif /* SQLITE_ENABLE_COLUMN_METADATA */ @@ -64859,26 +69931,26 @@ if( vdbeSafetyNotNull(p) ){ return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(p->db->mutex); if( p->magic!=VDBE_MAGIC_RUN || p->pc>=0 ){ - sqlite3Error(p->db, SQLITE_MISUSE, 0); + sqlite3Error(p->db, SQLITE_MISUSE); sqlite3_mutex_leave(p->db->mutex); sqlite3_log(SQLITE_MISUSE, "bind on a busy prepared statement: [%s]", p->zSql); return SQLITE_MISUSE_BKPT; } if( i<1 || i>p->nVar ){ - sqlite3Error(p->db, SQLITE_RANGE, 0); + sqlite3Error(p->db, SQLITE_RANGE); sqlite3_mutex_leave(p->db->mutex); return SQLITE_RANGE; } i--; pVar = &p->aVar[i]; sqlite3VdbeMemRelease(pVar); pVar->flags = MEM_Null; - sqlite3Error(p->db, SQLITE_OK, 0); + sqlite3Error(p->db, SQLITE_OK); /* If the bit corresponding to this variable in Vdbe.expmask is set, then ** binding a new value to this variable invalidates the current query plan. ** ** IMPLEMENTATION-OF: R-48440-37595 If the specific value bound to host @@ -64916,11 +69988,11 @@ pVar = &p->aVar[i-1]; rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); if( rc==SQLITE_OK && encoding!=0 ){ rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); } - sqlite3Error(p->db, rc, 0); + sqlite3Error(p->db, rc); rc = sqlite3ApiExit(p->db, rc); } sqlite3_mutex_leave(p->db->mutex); }else if( xDel!=SQLITE_STATIC && xDel!=SQLITE_TRANSIENT ){ xDel((void*)zData); @@ -64930,80 +70002,110 @@ /* ** Bind a blob value to an SQL statement variable. */ -SQLITE_API int sqlite3_bind_blob( +SQLITE_API int SQLITE_STDCALL sqlite3_bind_blob( sqlite3_stmt *pStmt, int i, const void *zData, int nData, void (*xDel)(void*) ){ return bindText(pStmt, i, zData, nData, xDel, 0); } -SQLITE_API int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){ +SQLITE_API int SQLITE_STDCALL sqlite3_bind_blob64( + sqlite3_stmt *pStmt, + int i, + const void *zData, + sqlite3_uint64 nData, + void (*xDel)(void*) +){ + assert( xDel!=SQLITE_DYNAMIC ); + if( nData>0x7fffffff ){ + return invokeValueDestructor(zData, xDel, 0); + }else{ + return bindText(pStmt, i, zData, (int)nData, xDel, 0); + } +} +SQLITE_API int SQLITE_STDCALL sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){ int rc; Vdbe *p = (Vdbe *)pStmt; rc = vdbeUnbind(p, i); if( rc==SQLITE_OK ){ sqlite3VdbeMemSetDouble(&p->aVar[i-1], rValue); sqlite3_mutex_leave(p->db->mutex); } return rc; } -SQLITE_API int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){ +SQLITE_API int SQLITE_STDCALL sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){ return sqlite3_bind_int64(p, i, (i64)iValue); } -SQLITE_API int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){ +SQLITE_API int SQLITE_STDCALL sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){ int rc; Vdbe *p = (Vdbe *)pStmt; rc = vdbeUnbind(p, i); if( rc==SQLITE_OK ){ sqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue); sqlite3_mutex_leave(p->db->mutex); } return rc; } -SQLITE_API int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){ +SQLITE_API int SQLITE_STDCALL sqlite3_bind_null(sqlite3_stmt *pStmt, int i){ int rc; Vdbe *p = (Vdbe*)pStmt; rc = vdbeUnbind(p, i); if( rc==SQLITE_OK ){ sqlite3_mutex_leave(p->db->mutex); } return rc; } -SQLITE_API int sqlite3_bind_text( +SQLITE_API int SQLITE_STDCALL sqlite3_bind_text( sqlite3_stmt *pStmt, int i, const char *zData, int nData, void (*xDel)(void*) ){ return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF8); +} +SQLITE_API int SQLITE_STDCALL sqlite3_bind_text64( + sqlite3_stmt *pStmt, + int i, + const char *zData, + sqlite3_uint64 nData, + void (*xDel)(void*), + unsigned char enc +){ + assert( xDel!=SQLITE_DYNAMIC ); + if( nData>0x7fffffff ){ + return invokeValueDestructor(zData, xDel, 0); + }else{ + if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + return bindText(pStmt, i, zData, (int)nData, xDel, enc); + } } #ifndef SQLITE_OMIT_UTF16 -SQLITE_API int sqlite3_bind_text16( +SQLITE_API int SQLITE_STDCALL sqlite3_bind_text16( sqlite3_stmt *pStmt, int i, const void *zData, int nData, void (*xDel)(void*) ){ return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF16NATIVE); } #endif /* SQLITE_OMIT_UTF16 */ -SQLITE_API int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){ +SQLITE_API int SQLITE_STDCALL sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){ int rc; - switch( pValue->type ){ + switch( sqlite3_value_type((sqlite3_value*)pValue) ){ case SQLITE_INTEGER: { rc = sqlite3_bind_int64(pStmt, i, pValue->u.i); break; } case SQLITE_FLOAT: { - rc = sqlite3_bind_double(pStmt, i, pValue->r); + rc = sqlite3_bind_double(pStmt, i, pValue->u.r); break; } case SQLITE_BLOB: { if( pValue->flags & MEM_Zero ){ rc = sqlite3_bind_zeroblob(pStmt, i, pValue->u.nZero); @@ -65022,11 +70124,11 @@ break; } } return rc; } -SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){ +SQLITE_API int SQLITE_STDCALL sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){ int rc; Vdbe *p = (Vdbe *)pStmt; rc = vdbeUnbind(p, i); if( rc==SQLITE_OK ){ sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n); @@ -65037,11 +70139,11 @@ /* ** Return the number of wildcards that can be potentially bound to. ** This routine is added to support DBD::SQLite. */ -SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt){ +SQLITE_API int SQLITE_STDCALL sqlite3_bind_parameter_count(sqlite3_stmt *pStmt){ Vdbe *p = (Vdbe*)pStmt; return p ? p->nVar : 0; } /* @@ -65048,11 +70150,11 @@ ** Return the name of a wildcard parameter. Return NULL if the index ** is out of range or if the wildcard is unnamed. ** ** The result is always UTF-8. */ -SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){ +SQLITE_API const char *SQLITE_STDCALL sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){ Vdbe *p = (Vdbe*)pStmt; if( p==0 || i<1 || i>p->nzVar ){ return 0; } return p->azVar[i-1]; @@ -65076,11 +70178,11 @@ } } } return 0; } -SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt *pStmt, const char *zName){ +SQLITE_API int SQLITE_STDCALL sqlite3_bind_parameter_index(sqlite3_stmt *pStmt, const char *zName){ return sqlite3VdbeParameterIndex((Vdbe*)pStmt, zName, sqlite3Strlen30(zName)); } /* ** Transfer all bindings from the first statement over to the second. @@ -65102,19 +70204,19 @@ #ifndef SQLITE_OMIT_DEPRECATED /* ** Deprecated external interface. Internal/core SQLite code ** should call sqlite3TransferBindings. ** -** Is is misuse to call this routine with statements from different +** It is misuse to call this routine with statements from different ** database connections. But as this is a deprecated interface, we ** will not bother to check for that condition. ** ** If the two statements contain a different number of bindings, then ** an SQLITE_ERROR is returned. Nothing else can go wrong, so otherwise ** SQLITE_OK is returned. */ -SQLITE_API int sqlite3_transfer_bindings(sqlite3_stmt *pFromStmt, sqlite3_stmt *pToStmt){ +SQLITE_API int SQLITE_STDCALL sqlite3_transfer_bindings(sqlite3_stmt *pFromStmt, sqlite3_stmt *pToStmt){ Vdbe *pFrom = (Vdbe*)pFromStmt; Vdbe *pTo = (Vdbe*)pToStmt; if( pFrom->nVar!=pTo->nVar ){ return SQLITE_ERROR; } @@ -65132,38 +70234,44 @@ ** Return the sqlite3* database handle to which the prepared statement given ** in the argument belongs. This is the same database handle that was ** the first argument to the sqlite3_prepare() that was used to create ** the statement in the first place. */ -SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt *pStmt){ +SQLITE_API sqlite3 *SQLITE_STDCALL sqlite3_db_handle(sqlite3_stmt *pStmt){ return pStmt ? ((Vdbe*)pStmt)->db : 0; } /* ** Return true if the prepared statement is guaranteed to not modify the ** database. */ -SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt){ +SQLITE_API int SQLITE_STDCALL sqlite3_stmt_readonly(sqlite3_stmt *pStmt){ return pStmt ? ((Vdbe*)pStmt)->readOnly : 1; } /* ** Return true if the prepared statement is in need of being reset. */ -SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ +SQLITE_API int SQLITE_STDCALL sqlite3_stmt_busy(sqlite3_stmt *pStmt){ Vdbe *v = (Vdbe*)pStmt; - return v!=0 && v->pc>0 && v->magic==VDBE_MAGIC_RUN; + return v!=0 && v->pc>=0 && v->magic==VDBE_MAGIC_RUN; } /* ** Return a pointer to the next prepared statement after pStmt associated ** with database connection pDb. If pStmt is NULL, return the first ** prepared statement for the database connection. Return NULL if there ** are no more. */ -SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt){ +SQLITE_API sqlite3_stmt *SQLITE_STDCALL sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt){ sqlite3_stmt *pNext; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(pDb) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(pDb->mutex); if( pStmt==0 ){ pNext = (sqlite3_stmt*)pDb->pVdbe; }else{ pNext = (sqlite3_stmt*)((Vdbe*)pStmt)->pNext; @@ -65173,17 +70281,93 @@ } /* ** Return the value of a status counter for a prepared statement */ -SQLITE_API int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ +SQLITE_API int SQLITE_STDCALL sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ Vdbe *pVdbe = (Vdbe*)pStmt; - u32 v = pVdbe->aCounter[op]; + u32 v; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !pStmt ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + v = pVdbe->aCounter[op]; if( resetFlag ) pVdbe->aCounter[op] = 0; return (int)v; } +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +/* +** Return status data for a single loop within query pStmt. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement being queried */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Which metric to return */ + void *pOut /* OUT: Write the answer here */ +){ + Vdbe *p = (Vdbe*)pStmt; + ScanStatus *pScan; + if( idx<0 || idx>=p->nScan ) return 1; + pScan = &p->aScan[idx]; + switch( iScanStatusOp ){ + case SQLITE_SCANSTAT_NLOOP: { + *(sqlite3_int64*)pOut = p->anExec[pScan->addrLoop]; + break; + } + case SQLITE_SCANSTAT_NVISIT: { + *(sqlite3_int64*)pOut = p->anExec[pScan->addrVisit]; + break; + } + case SQLITE_SCANSTAT_EST: { + double r = 1.0; + LogEst x = pScan->nEst; + while( x<100 ){ + x += 10; + r *= 0.5; + } + *(double*)pOut = r*sqlite3LogEstToInt(x); + break; + } + case SQLITE_SCANSTAT_NAME: { + *(const char**)pOut = pScan->zName; + break; + } + case SQLITE_SCANSTAT_EXPLAIN: { + if( pScan->addrExplain ){ + *(const char**)pOut = p->aOp[ pScan->addrExplain ].p4.z; + }else{ + *(const char**)pOut = 0; + } + break; + } + case SQLITE_SCANSTAT_SELECTID: { + if( pScan->addrExplain ){ + *(int*)pOut = p->aOp[ pScan->addrExplain ].p1; + }else{ + *(int*)pOut = -1; + } + break; + } + default: { + return 1; + } + } + return 0; +} + +/* +** Zero all counters associated with the sqlite3_stmt_scanstatus() data. +*/ +SQLITE_API void SQLITE_STDCALL sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){ + Vdbe *p = (Vdbe*)pStmt; + memset(p->anExec, 0, p->nOp * sizeof(i64)); +} +#endif /* SQLITE_ENABLE_STMT_SCANSTATUS */ + /************** End of vdbeapi.c *********************************************/ /************** Begin file vdbetrace.c ***************************************/ /* ** 2009 November 25 ** @@ -65246,11 +70430,11 @@ ** is eventually freed. ** ** ALGORITHM: Scan the input string looking for host parameters in any of ** these forms: ?, ?N, $A, @A, :A. Take care to avoid text within ** string literals, quoted identifier names, and comments. For text forms, -** the host parameter index is found by scanning the perpared +** the host parameter index is found by scanning the prepared ** statement for the corresponding OP_Variable opcode. Once the host ** parameter index is known, locate the value in p->aVar[]. Then render ** the value as a literal in place of the host parameter name. */ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( @@ -65274,10 +70458,11 @@ if( db->nVdbeExec>1 ){ while( *zRawSql ){ const char *zStart = zRawSql; while( *(zRawSql++)!='\n' && *zRawSql ); sqlite3StrAccumAppend(&out, "-- ", 3); + assert( (zRawSql - zStart) > 0 ); sqlite3StrAccumAppend(&out, zStart, (int)(zRawSql-zStart)); } }else{ while( zRawSql[0] ){ n = findNextHostParameter(zRawSql, &nToken); @@ -65306,13 +70491,13 @@ assert( idx>0 && idx<=p->nVar ); pVar = &p->aVar[idx-1]; if( pVar->flags & MEM_Null ){ sqlite3StrAccumAppend(&out, "NULL", 4); }else if( pVar->flags & MEM_Int ){ - sqlite3XPrintf(&out, "%lld", pVar->u.i); + sqlite3XPrintf(&out, 0, "%lld", pVar->u.i); }else if( pVar->flags & MEM_Real ){ - sqlite3XPrintf(&out, "%!.15g", pVar->r); + sqlite3XPrintf(&out, 0, "%!.15g", pVar->u.r); }else if( pVar->flags & MEM_Str ){ int nOut; /* Number of bytes of the string text to include in output */ #ifndef SQLITE_OMIT_UTF16 u8 enc = ENC(db); Mem utf8; @@ -65329,157 +70514,46 @@ if( nOut>SQLITE_TRACE_SIZE_LIMIT ){ nOut = SQLITE_TRACE_SIZE_LIMIT; while( nOut<pVar->n && (pVar->z[nOut]&0xc0)==0x80 ){ nOut++; } } #endif - sqlite3XPrintf(&out, "'%.*q'", nOut, pVar->z); + sqlite3XPrintf(&out, 0, "'%.*q'", nOut, pVar->z); #ifdef SQLITE_TRACE_SIZE_LIMIT - if( nOut<pVar->n ) sqlite3XPrintf(&out, "/*+%d bytes*/", pVar->n-nOut); + if( nOut<pVar->n ){ + sqlite3XPrintf(&out, 0, "/*+%d bytes*/", pVar->n-nOut); + } #endif #ifndef SQLITE_OMIT_UTF16 if( enc!=SQLITE_UTF8 ) sqlite3VdbeMemRelease(&utf8); #endif }else if( pVar->flags & MEM_Zero ){ - sqlite3XPrintf(&out, "zeroblob(%d)", pVar->u.nZero); + sqlite3XPrintf(&out, 0, "zeroblob(%d)", pVar->u.nZero); }else{ int nOut; /* Number of bytes of the blob to include in output */ assert( pVar->flags & MEM_Blob ); sqlite3StrAccumAppend(&out, "x'", 2); nOut = pVar->n; #ifdef SQLITE_TRACE_SIZE_LIMIT if( nOut>SQLITE_TRACE_SIZE_LIMIT ) nOut = SQLITE_TRACE_SIZE_LIMIT; #endif for(i=0; i<nOut; i++){ - sqlite3XPrintf(&out, "%02x", pVar->z[i]&0xff); + sqlite3XPrintf(&out, 0, "%02x", pVar->z[i]&0xff); } sqlite3StrAccumAppend(&out, "'", 1); #ifdef SQLITE_TRACE_SIZE_LIMIT - if( nOut<pVar->n ) sqlite3XPrintf(&out, "/*+%d bytes*/", pVar->n-nOut); + if( nOut<pVar->n ){ + sqlite3XPrintf(&out, 0, "/*+%d bytes*/", pVar->n-nOut); + } #endif } } } return sqlite3StrAccumFinish(&out); } #endif /* #ifndef SQLITE_OMIT_TRACE */ -/***************************************************************************** -** The following code implements the data-structure explaining logic -** for the Vdbe. -*/ - -#if defined(SQLITE_ENABLE_TREE_EXPLAIN) - -/* -** Allocate a new Explain object -*/ -SQLITE_PRIVATE void sqlite3ExplainBegin(Vdbe *pVdbe){ - if( pVdbe ){ - Explain *p; - sqlite3BeginBenignMalloc(); - p = (Explain *)sqlite3MallocZero( sizeof(Explain) ); - if( p ){ - p->pVdbe = pVdbe; - sqlite3_free(pVdbe->pExplain); - pVdbe->pExplain = p; - sqlite3StrAccumInit(&p->str, p->zBase, sizeof(p->zBase), - SQLITE_MAX_LENGTH); - p->str.useMalloc = 2; - }else{ - sqlite3EndBenignMalloc(); - } - } -} - -/* -** Return true if the Explain ends with a new-line. -*/ -static int endsWithNL(Explain *p){ - return p && p->str.zText && p->str.nChar - && p->str.zText[p->str.nChar-1]=='\n'; -} - -/* -** Append text to the indentation -*/ -SQLITE_PRIVATE void sqlite3ExplainPrintf(Vdbe *pVdbe, const char *zFormat, ...){ - Explain *p; - if( pVdbe && (p = pVdbe->pExplain)!=0 ){ - va_list ap; - if( p->nIndent && endsWithNL(p) ){ - int n = p->nIndent; - if( n>ArraySize(p->aIndent) ) n = ArraySize(p->aIndent); - sqlite3AppendSpace(&p->str, p->aIndent[n-1]); - } - va_start(ap, zFormat); - sqlite3VXPrintf(&p->str, 1, zFormat, ap); - va_end(ap); - } -} - -/* -** Append a '\n' if there is not already one. -*/ -SQLITE_PRIVATE void sqlite3ExplainNL(Vdbe *pVdbe){ - Explain *p; - if( pVdbe && (p = pVdbe->pExplain)!=0 && !endsWithNL(p) ){ - sqlite3StrAccumAppend(&p->str, "\n", 1); - } -} - -/* -** Push a new indentation level. Subsequent lines will be indented -** so that they begin at the current cursor position. -*/ -SQLITE_PRIVATE void sqlite3ExplainPush(Vdbe *pVdbe){ - Explain *p; - if( pVdbe && (p = pVdbe->pExplain)!=0 ){ - if( p->str.zText && p->nIndent<ArraySize(p->aIndent) ){ - const char *z = p->str.zText; - int i = p->str.nChar-1; - int x; - while( i>=0 && z[i]!='\n' ){ i--; } - x = (p->str.nChar - 1) - i; - if( p->nIndent && x<p->aIndent[p->nIndent-1] ){ - x = p->aIndent[p->nIndent-1]; - } - p->aIndent[p->nIndent] = x; - } - p->nIndent++; - } -} - -/* -** Pop the indentation stack by one level. -*/ -SQLITE_PRIVATE void sqlite3ExplainPop(Vdbe *p){ - if( p && p->pExplain ) p->pExplain->nIndent--; -} - -/* -** Free the indentation structure -*/ -SQLITE_PRIVATE void sqlite3ExplainFinish(Vdbe *pVdbe){ - if( pVdbe && pVdbe->pExplain ){ - sqlite3_free(pVdbe->zExplain); - sqlite3ExplainNL(pVdbe); - pVdbe->zExplain = sqlite3StrAccumFinish(&pVdbe->pExplain->str); - sqlite3_free(pVdbe->pExplain); - pVdbe->pExplain = 0; - sqlite3EndBenignMalloc(); - } -} - -/* -** Return the explanation of a virtual machine. -*/ -SQLITE_PRIVATE const char *sqlite3VdbeExplanation(Vdbe *pVdbe){ - return (pVdbe && pVdbe->zExplain) ? pVdbe->zExplain : 0; -} -#endif /* defined(SQLITE_DEBUG) */ - /************** End of vdbetrace.c *******************************************/ /************** Begin file vdbe.c ********************************************/ /* ** 2001 September 15 ** @@ -65489,37 +70563,12 @@ ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* -** The code in this file implements execution method of the -** Virtual Database Engine (VDBE). A separate file ("vdbeaux.c") -** handles housekeeping details such as creating and deleting -** VDBE instances. This file is solely interested in executing -** the VDBE program. -** -** In the external interface, an "sqlite3_stmt*" is an opaque pointer -** to a VDBE. -** -** The SQL parser generates a program which is then executed by -** the VDBE to do the work of the SQL statement. VDBE programs are -** similar in form to assembly language. The program consists of -** a linear sequence of operations. Each operation has an opcode -** and 5 operands. Operands P1, P2, and P3 are integers. Operand P4 -** is a null-terminated string. Operand P5 is an unsigned character. -** Few opcodes use all 5 operands. -** -** Computation results are stored on a set of registers numbered beginning -** with 1 and going up to Vdbe.nMem. Each register can store -** either an integer, a null-terminated string, a floating point -** number, or the SQL "NULL" value. An implicit conversion from one -** type to the other occurs as necessary. -** -** Most of the code in this file is taken up by the sqlite3VdbeExec() -** function which does the work of interpreting a VDBE program. -** But other routines are also provided to help in building up -** a program instruction by instruction. +** The code in this file implements the function that runs the +** bytecode of a prepared statement. ** ** Various scripts scan this source file in order to generate HTML ** documentation, headers files, or other derived files. The formatting ** of the code in this file is, therefore, important. See other comments ** in this file for details. If in doubt, do not deviate from existing @@ -65527,11 +70576,15 @@ */ /* ** Invoke this macro on memory cells just prior to changing the ** value of the cell. This macro verifies that shallow copies are -** not misused. +** not misused. A shallow copy of a string or blob just copies a +** pointer to the string or blob, not the content. If the original +** is changed while the copy is still in use, the string or blob might +** be changed out from under the copy. This macro verifies that nothing +** like that ever happens. */ #ifdef SQLITE_DEBUG # define memAboutToChange(P,M) sqlite3VdbeMemAboutToChange(P,M) #else # define memAboutToChange(P,M) @@ -65586,11 +70639,11 @@ } } #endif /* -** The next global variable is incremented each type the OP_Found opcode +** The next global variable is incremented each time the OP_Found opcode ** is executed. This is used to test whether or not the foreign key ** operation implemented using OP_FkIsZero is working. This variable ** has no function other than to help verify the correct operation of the ** library. */ @@ -65606,16 +70659,50 @@ # define UPDATE_MAX_BLOBSIZE(P) updateMaxBlobsize(P) #else # define UPDATE_MAX_BLOBSIZE(P) #endif +/* +** Invoke the VDBE coverage callback, if that callback is defined. This +** feature is used for test suite validation only and does not appear an +** production builds. +** +** M is an integer, 2 or 3, that indices how many different ways the +** branch can go. It is usually 2. "I" is the direction the branch +** goes. 0 means falls through. 1 means branch is taken. 2 means the +** second alternative branch is taken. +** +** iSrcLine is the source code line (from the __LINE__ macro) that +** generated the VDBE instruction. This instrumentation assumes that all +** source code is in a single file (the amalgamation). Special values 1 +** and 2 for the iSrcLine parameter mean that this particular branch is +** always taken or never taken, respectively. +*/ +#if !defined(SQLITE_VDBE_COVERAGE) +# define VdbeBranchTaken(I,M) +#else +# define VdbeBranchTaken(I,M) vdbeTakeBranch(pOp->iSrcLine,I,M) + static void vdbeTakeBranch(int iSrcLine, u8 I, u8 M){ + if( iSrcLine<=2 && ALWAYS(iSrcLine>0) ){ + M = iSrcLine; + /* Assert the truth of VdbeCoverageAlwaysTaken() and + ** VdbeCoverageNeverTaken() */ + assert( (M & I)==I ); + }else{ + if( sqlite3GlobalConfig.xVdbeBranch==0 ) return; /*NO_TEST*/ + sqlite3GlobalConfig.xVdbeBranch(sqlite3GlobalConfig.pVdbeBranchArg, + iSrcLine,I,M); + } + } +#endif + /* ** Convert the given register into a string if it isn't one ** already. Return non-zero if a malloc() fails. */ #define Stringify(P, enc) \ - if(((P)->flags&(MEM_Str|MEM_Blob))==0 && sqlite3VdbeMemStringify(P,enc)) \ + if(((P)->flags&(MEM_Str|MEM_Blob))==0 && sqlite3VdbeMemStringify(P,enc,0)) \ { goto no_mem; } /* ** An ephemeral string value (signified by the MEM_Ephem flag) contains ** a pointer to a dynamically allocated string where some other entity @@ -65623,42 +70710,18 @@ ** does not control the string, it might be deleted without the register ** knowing it. ** ** This routine converts an ephemeral string into a dynamically allocated ** string that the register itself controls. In other words, it -** converts an MEM_Ephem string into an MEM_Dyn string. +** converts an MEM_Ephem string into a string with P.z==P.zMalloc. */ #define Deephemeralize(P) \ if( ((P)->flags&MEM_Ephem)!=0 \ && sqlite3VdbeMemMakeWriteable(P) ){ goto no_mem;} /* Return true if the cursor was opened using the OP_OpenSorter opcode. */ -# define isSorter(x) ((x)->pSorter!=0) - -/* -** Argument pMem points at a register that will be passed to a -** user-defined function or returned to the user as the result of a query. -** This routine sets the pMem->type variable used by the sqlite3_value_*() -** routines. -*/ -SQLITE_PRIVATE void sqlite3VdbeMemStoreType(Mem *pMem){ - int flags = pMem->flags; - if( flags & MEM_Null ){ - pMem->type = SQLITE_NULL; - } - else if( flags & MEM_Int ){ - pMem->type = SQLITE_INTEGER; - } - else if( flags & MEM_Real ){ - pMem->type = SQLITE_FLOAT; - } - else if( flags & MEM_Str ){ - pMem->type = SQLITE_TEXT; - }else{ - pMem->type = SQLITE_BLOB; - } -} +#define isSorter(x) ((x)->pSorter!=0) /* ** Allocate VdbeCursor number iCur. Return a pointer to it. Return NULL ** if we run out of memory. */ @@ -65690,30 +70753,27 @@ Mem *pMem = &p->aMem[p->nMem-iCur]; int nByte; VdbeCursor *pCx = 0; nByte = - ROUND8(sizeof(VdbeCursor)) + - (isBtreeCursor?sqlite3BtreeCursorSize():0) + - 2*nField*sizeof(u32); + ROUND8(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + + (isBtreeCursor?sqlite3BtreeCursorSize():0); assert( iCur<p->nCursor ); if( p->apCsr[iCur] ){ sqlite3VdbeFreeCursor(p, p->apCsr[iCur]); p->apCsr[iCur] = 0; } - if( SQLITE_OK==sqlite3VdbeMemGrow(pMem, nByte, 0) ){ + if( SQLITE_OK==sqlite3VdbeMemClearAndResize(pMem, nByte) ){ p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->z; memset(pCx, 0, sizeof(VdbeCursor)); pCx->iDb = iDb; pCx->nField = nField; - if( nField ){ - pCx->aType = (u32 *)&pMem->z[ROUND8(sizeof(VdbeCursor))]; - } + pCx->aOffset = &pCx->aType[nField]; if( isBtreeCursor ){ pCx->pCursor = (BtCursor*) - &pMem->z[ROUND8(sizeof(VdbeCursor))+2*nField*sizeof(u32)]; + &pMem->z[ROUND8(sizeof(VdbeCursor))+2*sizeof(u32)*nField]; sqlite3BtreeCursorZero(pCx->pCursor); } } return pCx; } @@ -65721,25 +70781,33 @@ /* ** Try to convert a value into a numeric representation if we can ** do so without loss of information. In other words, if the string ** looks like a number, convert it into a number. If it does not ** look like a number, leave it alone. +** +** If the bTryForInt flag is true, then extra effort is made to give +** an integer representation. Strings that look like floating point +** values but which have no fractional component (example: '48.00') +** will have a MEM_Int representation when bTryForInt is true. +** +** If bTryForInt is false, then if the input string contains a decimal +** point or exponential notation, the result is only MEM_Real, even +** if there is an exact integer representation of the quantity. */ -static void applyNumericAffinity(Mem *pRec){ - if( (pRec->flags & (MEM_Real|MEM_Int))==0 ){ - double rValue; - i64 iValue; - u8 enc = pRec->enc; - if( (pRec->flags&MEM_Str)==0 ) return; - if( sqlite3AtoF(pRec->z, &rValue, pRec->n, enc)==0 ) return; - if( 0==sqlite3Atoi64(pRec->z, &iValue, pRec->n, enc) ){ - pRec->u.i = iValue; - pRec->flags |= MEM_Int; - }else{ - pRec->r = rValue; - pRec->flags |= MEM_Real; - } +static void applyNumericAffinity(Mem *pRec, int bTryForInt){ + double rValue; + i64 iValue; + u8 enc = pRec->enc; + assert( (pRec->flags & (MEM_Str|MEM_Int|MEM_Real))==MEM_Str ); + if( sqlite3AtoF(pRec->z, &rValue, pRec->n, enc)==0 ) return; + if( 0==sqlite3Atoi64(pRec->z, &iValue, pRec->n, enc) ){ + pRec->u.i = iValue; + pRec->flags |= MEM_Int; + }else{ + pRec->u.r = rValue; + pRec->flags |= MEM_Real; + if( bTryForInt ) sqlite3VdbeIntegerAffinity(pRec); } } /* ** Processing is determine by the affinity parameter: @@ -65762,25 +70830,27 @@ static void applyAffinity( Mem *pRec, /* The value to apply affinity to */ char affinity, /* The affinity to be applied */ u8 enc /* Use this text encoding */ ){ - if( affinity==SQLITE_AFF_TEXT ){ + if( affinity>=SQLITE_AFF_NUMERIC ){ + assert( affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL + || affinity==SQLITE_AFF_NUMERIC ); + if( (pRec->flags & MEM_Int)==0 ){ + if( (pRec->flags & MEM_Real)==0 ){ + if( pRec->flags & MEM_Str ) applyNumericAffinity(pRec,1); + }else{ + sqlite3VdbeIntegerAffinity(pRec); + } + } + }else if( affinity==SQLITE_AFF_TEXT ){ /* Only attempt the conversion to TEXT if there is an integer or real ** representation (blob and NULL do not get converted) but no string ** representation. */ if( 0==(pRec->flags&MEM_Str) && (pRec->flags&(MEM_Real|MEM_Int)) ){ - sqlite3VdbeMemStringify(pRec, enc); - } - pRec->flags &= ~(MEM_Real|MEM_Int); - }else if( affinity!=SQLITE_AFF_NONE ){ - assert( affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL - || affinity==SQLITE_AFF_NUMERIC ); - applyNumericAffinity(pRec); - if( pRec->flags & MEM_Real ){ - sqlite3VdbeIntegerAffinity(pRec); + sqlite3VdbeMemStringify(pRec, enc, 1); } } } /* @@ -65787,17 +70857,18 @@ ** Try to convert the type of a function argument or a result column ** into a numeric representation. Use either INTEGER or REAL whichever ** is appropriate. But only do the conversion if it is possible without ** loss of information and return the revised type of the argument. */ -SQLITE_API int sqlite3_value_numeric_type(sqlite3_value *pVal){ - Mem *pMem = (Mem*)pVal; - if( pMem->type==SQLITE_TEXT ){ - applyNumericAffinity(pMem); - sqlite3VdbeMemStoreType(pMem); +SQLITE_API int SQLITE_STDCALL sqlite3_value_numeric_type(sqlite3_value *pVal){ + int eType = sqlite3_value_type(pVal); + if( eType==SQLITE_TEXT ){ + Mem *pMem = (Mem*)pVal; + applyNumericAffinity(pMem, 0); + eType = sqlite3_value_type(pVal); } - return pMem->type; + return eType; } /* ** Exported version of applyAffinity(). This one works on sqlite3_value*, ** not the internal Mem* type. @@ -65807,10 +70878,45 @@ u8 affinity, u8 enc ){ applyAffinity((Mem *)pVal, affinity, enc); } + +/* +** pMem currently only holds a string type (or maybe a BLOB that we can +** interpret as a string if we want to). Compute its corresponding +** numeric type, if has one. Set the pMem->u.r and pMem->u.i fields +** accordingly. +*/ +static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){ + assert( (pMem->flags & (MEM_Int|MEM_Real))==0 ); + assert( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ); + if( sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc)==0 ){ + return 0; + } + if( sqlite3Atoi64(pMem->z, &pMem->u.i, pMem->n, pMem->enc)==SQLITE_OK ){ + return MEM_Int; + } + return MEM_Real; +} + +/* +** Return the numeric type for pMem, either MEM_Int or MEM_Real or both or +** none. +** +** Unlike applyNumericAffinity(), this routine does not modify pMem->flags. +** But it does set pMem->u.r and pMem->u.i appropriately. +*/ +static u16 numericType(Mem *pMem){ + if( pMem->flags & (MEM_Int|MEM_Real) ){ + return pMem->flags & (MEM_Int|MEM_Real); + } + if( pMem->flags & (MEM_Str|MEM_Blob) ){ + return computeNumericType(pMem); + } + return 0; +} #ifdef SQLITE_DEBUG /* ** Write a nice string representation of the contents of cell pMem ** into buffer zBuf, length nBuf. @@ -65895,41 +71001,40 @@ #ifdef SQLITE_DEBUG /* ** Print the value of a register for tracing purposes: */ -static void memTracePrint(FILE *out, Mem *p){ - if( p->flags & MEM_Invalid ){ - fprintf(out, " undefined"); +static void memTracePrint(Mem *p){ + if( p->flags & MEM_Undefined ){ + printf(" undefined"); }else if( p->flags & MEM_Null ){ - fprintf(out, " NULL"); + printf(" NULL"); }else if( (p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str) ){ - fprintf(out, " si:%lld", p->u.i); + printf(" si:%lld", p->u.i); }else if( p->flags & MEM_Int ){ - fprintf(out, " i:%lld", p->u.i); + printf(" i:%lld", p->u.i); #ifndef SQLITE_OMIT_FLOATING_POINT }else if( p->flags & MEM_Real ){ - fprintf(out, " r:%g", p->r); + printf(" r:%g", p->u.r); #endif }else if( p->flags & MEM_RowSet ){ - fprintf(out, " (rowset)"); + printf(" (rowset)"); }else{ char zBuf[200]; sqlite3VdbeMemPrettyPrint(p, zBuf); - fprintf(out, " "); - fprintf(out, "%s", zBuf); + printf(" %s", zBuf); } } -static void registerTrace(FILE *out, int iReg, Mem *p){ - fprintf(out, "REG[%d] = ", iReg); - memTracePrint(out, p); - fprintf(out, "\n"); +static void registerTrace(int iReg, Mem *p){ + printf("REG[%d] = ", iReg); + memTracePrint(p); + printf("\n"); } #endif #ifdef SQLITE_DEBUG -# define REGISTER_TRACE(R,M) if(p->trace)registerTrace(p->trace,R,M) +# define REGISTER_TRACE(R,M) if(db->flags&SQLITE_VdbeTrace)registerTrace(R,M) #else # define REGISTER_TRACE(R,M) #endif @@ -66029,24 +71134,10 @@ /************** End of hwtime.h **********************************************/ /************** Continuing where we left off in vdbe.c ***********************/ #endif - -/* -** The CHECK_FOR_INTERRUPT macro defined here looks to see if the -** sqlite3_interrupt() routine has been called. If it has been, then -** processing of the VDBE program is interrupted. -** -** This macro added to every instruction that does a jump in order to -** implement a loop. This test used to be on every single instruction, -** but that meant we more testing than we needed. By only testing the -** flag on jump instructions, we get a (small) speed improvement. -*/ -#define CHECK_FOR_INTERRUPT \ - if( db->u1.isInterrupted ) goto abort_due_to_interrupt; - #ifndef NDEBUG /* ** This function is only called from within an assert() expression. It ** checks that the sqlite3.nTransaction variable is correctly set to @@ -66066,39 +71157,12 @@ } #endif /* -** Execute as much of a VDBE program as we can then return. -** -** sqlite3VdbeMakeReady() must be called before this routine in order to -** close the program with a final OP_Halt and to set up the callbacks -** and the error message pointer. -** -** Whenever a row or result data is available, this routine will either -** invoke the result callback (if there is one) or return with -** SQLITE_ROW. -** -** If an attempt is made to open a locked database, then this routine -** will either invoke the busy callback (if there is one) or it will -** return SQLITE_BUSY. -** -** If an error occurs, an error message is written to memory obtained -** from sqlite3_malloc() and p->zErrMsg is made to point to that memory. -** The error code is stored in p->rc and this routine returns SQLITE_ERROR. -** -** If the callback ever returns non-zero, then the program exits -** immediately. There will be no error message but the p->rc field is -** set to SQLITE_ABORT and this routine will return SQLITE_ERROR. -** -** A memory allocation error causes p->rc to be set to SQLITE_NOMEM and this -** routine to return SQLITE_ERROR. -** -** Other fatal errors return SQLITE_ERROR. -** -** After this routine has finished, sqlite3VdbeFinalize() should be -** used to clean up the mess that was left behind. +** Execute as much of a VDBE program as we can. +** This is the core of sqlite3_step(). */ SQLITE_PRIVATE int sqlite3VdbeExec( Vdbe *p /* The VDBE */ ){ int pc=0; /* The program counter */ @@ -66120,442 +71184,12 @@ Mem *pOut = 0; /* Output operand */ int *aPermute = 0; /* Permutation of columns for OP_Compare */ i64 lastRowid = db->lastRowid; /* Saved value of the last insert ROWID */ #ifdef VDBE_PROFILE u64 start; /* CPU clock count at start of opcode */ - int origPc; /* Program counter at start of opcode */ -#endif - /******************************************************************** - ** Automatically generated code - ** - ** The following union is automatically generated by the - ** vdbe-compress.tcl script. The purpose of this union is to - ** reduce the amount of stack space required by this function. - ** See comments in the vdbe-compress.tcl script for details. - */ - union vdbeExecUnion { - struct OP_Yield_stack_vars { - int pcDest; - } aa; - struct OP_Null_stack_vars { - int cnt; - u16 nullFlag; - } ab; - struct OP_Variable_stack_vars { - Mem *pVar; /* Value being transferred */ - } ac; - struct OP_Move_stack_vars { - char *zMalloc; /* Holding variable for allocated memory */ - int n; /* Number of registers left to copy */ - int p1; /* Register to copy from */ - int p2; /* Register to copy to */ - } ad; - struct OP_Copy_stack_vars { - int n; - } ae; - struct OP_ResultRow_stack_vars { - Mem *pMem; - int i; - } af; - struct OP_Concat_stack_vars { - i64 nByte; - } ag; - struct OP_Remainder_stack_vars { - char bIntint; /* Started out as two integer operands */ - int flags; /* Combined MEM_* flags from both inputs */ - i64 iA; /* Integer value of left operand */ - i64 iB; /* Integer value of right operand */ - double rA; /* Real value of left operand */ - double rB; /* Real value of right operand */ - } ah; - struct OP_Function_stack_vars { - int i; - Mem *pArg; - sqlite3_context ctx; - sqlite3_value **apVal; - int n; - } ai; - struct OP_ShiftRight_stack_vars { - i64 iA; - u64 uA; - i64 iB; - u8 op; - } aj; - struct OP_Ge_stack_vars { - int res; /* Result of the comparison of pIn1 against pIn3 */ - char affinity; /* Affinity to use for comparison */ - u16 flags1; /* Copy of initial value of pIn1->flags */ - u16 flags3; /* Copy of initial value of pIn3->flags */ - } ak; - struct OP_Compare_stack_vars { - int n; - int i; - int p1; - int p2; - const KeyInfo *pKeyInfo; - int idx; - CollSeq *pColl; /* Collating sequence to use on this term */ - int bRev; /* True for DESCENDING sort order */ - } al; - struct OP_Or_stack_vars { - int v1; /* Left operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */ - int v2; /* Right operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */ - } am; - struct OP_IfNot_stack_vars { - int c; - } an; - struct OP_Column_stack_vars { - u32 payloadSize; /* Number of bytes in the record */ - i64 payloadSize64; /* Number of bytes in the record */ - int p1; /* P1 value of the opcode */ - int p2; /* column number to retrieve */ - VdbeCursor *pC; /* The VDBE cursor */ - char *zRec; /* Pointer to complete record-data */ - BtCursor *pCrsr; /* The BTree cursor */ - u32 *aType; /* aType[i] holds the numeric type of the i-th column */ - u32 *aOffset; /* aOffset[i] is offset to start of data for i-th column */ - int nField; /* number of fields in the record */ - int len; /* The length of the serialized data for the column */ - int i; /* Loop counter */ - char *zData; /* Part of the record being decoded */ - Mem *pDest; /* Where to write the extracted value */ - Mem sMem; /* For storing the record being decoded */ - u8 *zIdx; /* Index into header */ - u8 *zEndHdr; /* Pointer to first byte after the header */ - u32 offset; /* Offset into the data */ - u32 szField; /* Number of bytes in the content of a field */ - int szHdr; /* Size of the header size field at start of record */ - int avail; /* Number of bytes of available data */ - u32 t; /* A type code from the record header */ - Mem *pReg; /* PseudoTable input register */ - } ao; - struct OP_Affinity_stack_vars { - const char *zAffinity; /* The affinity to be applied */ - char cAff; /* A single character of affinity */ - } ap; - struct OP_MakeRecord_stack_vars { - u8 *zNewRecord; /* A buffer to hold the data for the new record */ - Mem *pRec; /* The new record */ - u64 nData; /* Number of bytes of data space */ - int nHdr; /* Number of bytes of header space */ - i64 nByte; /* Data space required for this record */ - int nZero; /* Number of zero bytes at the end of the record */ - int nVarint; /* Number of bytes in a varint */ - u32 serial_type; /* Type field */ - Mem *pData0; /* First field to be combined into the record */ - Mem *pLast; /* Last field of the record */ - int nField; /* Number of fields in the record */ - char *zAffinity; /* The affinity string for the record */ - int file_format; /* File format to use for encoding */ - int i; /* Space used in zNewRecord[] */ - int len; /* Length of a field */ - } aq; - struct OP_Count_stack_vars { - i64 nEntry; - BtCursor *pCrsr; - } ar; - struct OP_Savepoint_stack_vars { - int p1; /* Value of P1 operand */ - char *zName; /* Name of savepoint */ - int nName; - Savepoint *pNew; - Savepoint *pSavepoint; - Savepoint *pTmp; - int iSavepoint; - int ii; - } as; - struct OP_AutoCommit_stack_vars { - int desiredAutoCommit; - int iRollback; - int turnOnAC; - } at; - struct OP_Transaction_stack_vars { - Btree *pBt; - } au; - struct OP_ReadCookie_stack_vars { - int iMeta; - int iDb; - int iCookie; - } av; - struct OP_SetCookie_stack_vars { - Db *pDb; - } aw; - struct OP_VerifyCookie_stack_vars { - int iMeta; - int iGen; - Btree *pBt; - } ax; - struct OP_OpenWrite_stack_vars { - int nField; - KeyInfo *pKeyInfo; - int p2; - int iDb; - int wrFlag; - Btree *pX; - VdbeCursor *pCur; - Db *pDb; - } ay; - struct OP_OpenEphemeral_stack_vars { - VdbeCursor *pCx; - } az; - struct OP_SorterOpen_stack_vars { - VdbeCursor *pCx; - } ba; - struct OP_OpenPseudo_stack_vars { - VdbeCursor *pCx; - } bb; - struct OP_SeekGt_stack_vars { - int res; - int oc; - VdbeCursor *pC; - UnpackedRecord r; - int nField; - i64 iKey; /* The rowid we are to seek to */ - } bc; - struct OP_Seek_stack_vars { - VdbeCursor *pC; - } bd; - struct OP_Found_stack_vars { - int alreadyExists; - VdbeCursor *pC; - int res; - char *pFree; - UnpackedRecord *pIdxKey; - UnpackedRecord r; - char aTempRec[ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*3 + 7]; - } be; - struct OP_IsUnique_stack_vars { - u16 ii; - VdbeCursor *pCx; - BtCursor *pCrsr; - u16 nField; - Mem *aMx; - UnpackedRecord r; /* B-Tree index search key */ - i64 R; /* Rowid stored in register P3 */ - } bf; - struct OP_NotExists_stack_vars { - VdbeCursor *pC; - BtCursor *pCrsr; - int res; - u64 iKey; - } bg; - struct OP_NewRowid_stack_vars { - i64 v; /* The new rowid */ - VdbeCursor *pC; /* Cursor of table to get the new rowid */ - int res; /* Result of an sqlite3BtreeLast() */ - int cnt; /* Counter to limit the number of searches */ - Mem *pMem; /* Register holding largest rowid for AUTOINCREMENT */ - VdbeFrame *pFrame; /* Root frame of VDBE */ - } bh; - struct OP_InsertInt_stack_vars { - Mem *pData; /* MEM cell holding data for the record to be inserted */ - Mem *pKey; /* MEM cell holding key for the record */ - i64 iKey; /* The integer ROWID or key for the record to be inserted */ - VdbeCursor *pC; /* Cursor to table into which insert is written */ - int nZero; /* Number of zero-bytes to append */ - int seekResult; /* Result of prior seek or 0 if no USESEEKRESULT flag */ - const char *zDb; /* database name - used by the update hook */ - const char *zTbl; /* Table name - used by the opdate hook */ - int op; /* Opcode for update hook: SQLITE_UPDATE or SQLITE_INSERT */ - } bi; - struct OP_Delete_stack_vars { - i64 iKey; - VdbeCursor *pC; - } bj; - struct OP_SorterCompare_stack_vars { - VdbeCursor *pC; - int res; - } bk; - struct OP_SorterData_stack_vars { - VdbeCursor *pC; - } bl; - struct OP_RowData_stack_vars { - VdbeCursor *pC; - BtCursor *pCrsr; - u32 n; - i64 n64; - } bm; - struct OP_Rowid_stack_vars { - VdbeCursor *pC; - i64 v; - sqlite3_vtab *pVtab; - const sqlite3_module *pModule; - } bn; - struct OP_NullRow_stack_vars { - VdbeCursor *pC; - } bo; - struct OP_Last_stack_vars { - VdbeCursor *pC; - BtCursor *pCrsr; - int res; - } bp; - struct OP_Rewind_stack_vars { - VdbeCursor *pC; - BtCursor *pCrsr; - int res; - } bq; - struct OP_Next_stack_vars { - VdbeCursor *pC; - int res; - } br; - struct OP_IdxInsert_stack_vars { - VdbeCursor *pC; - BtCursor *pCrsr; - int nKey; - const char *zKey; - } bs; - struct OP_IdxDelete_stack_vars { - VdbeCursor *pC; - BtCursor *pCrsr; - int res; - UnpackedRecord r; - } bt; - struct OP_IdxRowid_stack_vars { - BtCursor *pCrsr; - VdbeCursor *pC; - i64 rowid; - } bu; - struct OP_IdxGE_stack_vars { - VdbeCursor *pC; - int res; - UnpackedRecord r; - } bv; - struct OP_Destroy_stack_vars { - int iMoved; - int iCnt; - Vdbe *pVdbe; - int iDb; - } bw; - struct OP_Clear_stack_vars { - int nChange; - } bx; - struct OP_CreateTable_stack_vars { - int pgno; - int flags; - Db *pDb; - } by; - struct OP_ParseSchema_stack_vars { - int iDb; - const char *zMaster; - char *zSql; - InitData initData; - } bz; - struct OP_IntegrityCk_stack_vars { - int nRoot; /* Number of tables to check. (Number of root pages.) */ - int *aRoot; /* Array of rootpage numbers for tables to be checked */ - int j; /* Loop counter */ - int nErr; /* Number of errors reported */ - char *z; /* Text of the error report */ - Mem *pnErr; /* Register keeping track of errors remaining */ - } ca; - struct OP_RowSetRead_stack_vars { - i64 val; - } cb; - struct OP_RowSetTest_stack_vars { - int iSet; - int exists; - } cc; - struct OP_Program_stack_vars { - int nMem; /* Number of memory registers for sub-program */ - int nByte; /* Bytes of runtime space required for sub-program */ - Mem *pRt; /* Register to allocate runtime space */ - Mem *pMem; /* Used to iterate through memory cells */ - Mem *pEnd; /* Last memory cell in new array */ - VdbeFrame *pFrame; /* New vdbe frame to execute in */ - SubProgram *pProgram; /* Sub-program to execute */ - void *t; /* Token identifying trigger */ - } cd; - struct OP_Param_stack_vars { - VdbeFrame *pFrame; - Mem *pIn; - } ce; - struct OP_MemMax_stack_vars { - Mem *pIn1; - VdbeFrame *pFrame; - } cf; - struct OP_AggStep_stack_vars { - int n; - int i; - Mem *pMem; - Mem *pRec; - sqlite3_context ctx; - sqlite3_value **apVal; - } cg; - struct OP_AggFinal_stack_vars { - Mem *pMem; - } ch; - struct OP_Checkpoint_stack_vars { - int i; /* Loop counter */ - int aRes[3]; /* Results */ - Mem *pMem; /* Write results here */ - } ci; - struct OP_JournalMode_stack_vars { - Btree *pBt; /* Btree to change journal mode of */ - Pager *pPager; /* Pager associated with pBt */ - int eNew; /* New journal mode */ - int eOld; /* The old journal mode */ -#ifndef SQLITE_OMIT_WAL - const char *zFilename; /* Name of database file for pPager */ -#endif - } cj; - struct OP_IncrVacuum_stack_vars { - Btree *pBt; - } ck; - struct OP_VBegin_stack_vars { - VTable *pVTab; - } cl; - struct OP_VOpen_stack_vars { - VdbeCursor *pCur; - sqlite3_vtab_cursor *pVtabCursor; - sqlite3_vtab *pVtab; - sqlite3_module *pModule; - } cm; - struct OP_VFilter_stack_vars { - int nArg; - int iQuery; - const sqlite3_module *pModule; - Mem *pQuery; - Mem *pArgc; - sqlite3_vtab_cursor *pVtabCursor; - sqlite3_vtab *pVtab; - VdbeCursor *pCur; - int res; - int i; - Mem **apArg; - } cn; - struct OP_VColumn_stack_vars { - sqlite3_vtab *pVtab; - const sqlite3_module *pModule; - Mem *pDest; - sqlite3_context sContext; - } co; - struct OP_VNext_stack_vars { - sqlite3_vtab *pVtab; - const sqlite3_module *pModule; - int res; - VdbeCursor *pCur; - } cp; - struct OP_VRename_stack_vars { - sqlite3_vtab *pVtab; - Mem *pName; - } cq; - struct OP_VUpdate_stack_vars { - sqlite3_vtab *pVtab; - sqlite3_module *pModule; - int nArg; - int i; - sqlite_int64 rowid; - Mem **apArg; - Mem *pX; - } cr; - struct OP_Trace_stack_vars { - char *zTrace; - char *z; - } cs; - } u; - /* End automatically generated code - ********************************************************************/ +#endif + /*** INSERT STACK UNION HERE ***/ assert( p->magic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */ sqlite3VdbeEnter(p); if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or @@ -66567,11 +71201,11 @@ p->rc = SQLITE_OK; p->iCurrentTime = 0; assert( p->explain==0 ); p->pResultSet = 0; db->busyHandler.nBusy = 0; - CHECK_FOR_INTERRUPT; + if( db->u1.isInterrupted ) goto abort_due_to_interrupt; sqlite3VdbeIOTraceSql(p); #ifndef SQLITE_OMIT_PROGRESS_CALLBACK if( db->xProgress ){ assert( 0 < db->nProgressOps ); nProgressLimit = (unsigned)p->aCounter[SQLITE_STMTSTATUS_VM_STEP]; @@ -66582,39 +71216,52 @@ } } #endif #ifdef SQLITE_DEBUG sqlite3BeginBenignMalloc(); - if( p->pc==0 && (p->db->flags & SQLITE_VdbeListing)!=0 ){ + if( p->pc==0 + && (p->db->flags & (SQLITE_VdbeListing|SQLITE_VdbeEQP|SQLITE_VdbeTrace))!=0 + ){ int i; - printf("VDBE Program Listing:\n"); + int once = 1; sqlite3VdbePrintSql(p); - for(i=0; i<p->nOp; i++){ - sqlite3VdbePrintOp(stdout, i, &aOp[i]); + if( p->db->flags & SQLITE_VdbeListing ){ + printf("VDBE Program Listing:\n"); + for(i=0; i<p->nOp; i++){ + sqlite3VdbePrintOp(stdout, i, &aOp[i]); + } } + if( p->db->flags & SQLITE_VdbeEQP ){ + for(i=0; i<p->nOp; i++){ + if( aOp[i].opcode==OP_Explain ){ + if( once ) printf("VDBE Query Plan:\n"); + printf("%s\n", aOp[i].p4.z); + once = 0; + } + } + } + if( p->db->flags & SQLITE_VdbeTrace ) printf("VDBE Trace:\n"); } sqlite3EndBenignMalloc(); #endif for(pc=p->pc; rc==SQLITE_OK; pc++){ assert( pc>=0 && pc<p->nOp ); if( db->mallocFailed ) goto no_mem; #ifdef VDBE_PROFILE - origPc = pc; start = sqlite3Hwtime(); #endif nVmStep++; pOp = &aOp[pc]; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + if( p->anExec ) p->anExec[pc]++; +#endif /* Only allow tracing if SQLITE_DEBUG is defined. */ #ifdef SQLITE_DEBUG - if( p->trace ){ - if( pc==0 ){ - printf("VDBE Execution Trace:\n"); - sqlite3VdbePrintSql(p); - } - sqlite3VdbePrintOp(p->trace, pc, pOp); + if( db->flags & SQLITE_VdbeTrace ){ + sqlite3VdbePrintOp(stdout, pc, pOp); } #endif /* Check to see if we need to simulate an interrupt. This only happens @@ -66638,32 +71285,35 @@ if( pOp->opflags & OPFLG_OUT2_PRERELEASE ){ assert( pOp->p2>0 ); assert( pOp->p2<=(p->nMem-p->nCursor) ); pOut = &aMem[pOp->p2]; memAboutToChange(p, pOut); - VdbeMemRelease(pOut); + if( VdbeMemDynamic(pOut) ) sqlite3VdbeMemSetNull(pOut); pOut->flags = MEM_Int; } /* Sanity checking on other operands */ #ifdef SQLITE_DEBUG if( (pOp->opflags & OPFLG_IN1)!=0 ){ assert( pOp->p1>0 ); assert( pOp->p1<=(p->nMem-p->nCursor) ); assert( memIsValid(&aMem[pOp->p1]) ); + assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p1]) ); REGISTER_TRACE(pOp->p1, &aMem[pOp->p1]); } if( (pOp->opflags & OPFLG_IN2)!=0 ){ assert( pOp->p2>0 ); assert( pOp->p2<=(p->nMem-p->nCursor) ); assert( memIsValid(&aMem[pOp->p2]) ); + assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p2]) ); REGISTER_TRACE(pOp->p2, &aMem[pOp->p2]); } if( (pOp->opflags & OPFLG_IN3)!=0 ){ assert( pOp->p3>0 ); assert( pOp->p3<=(p->nMem-p->nCursor) ); assert( memIsValid(&aMem[pOp->p3]) ); + assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p3]) ); REGISTER_TRACE(pOp->p3, &aMem[pOp->p3]); } if( (pOp->opflags & OPFLG_OUT2)!=0 ){ assert( pOp->p2>0 ); assert( pOp->p2<=(p->nMem-p->nCursor) ); @@ -66717,10 +71367,15 @@ ** ** An unconditional jump to address P2. ** The next instruction executed will be ** the one at index P2 from the beginning of ** the program. +** +** The P1 parameter is not actually used by this opcode. However, it +** is sometimes set to 1 instead of 0 as a hint to the command-line shell +** that this Goto is the bottom of a loop and that the lines from P2 down +** to the current line should be indented for EXPLAIN output. */ case OP_Goto: { /* jump */ pc = pOp->p2 - 1; /* Opcodes that are used as the bottom of a loop (OP_Next, OP_Prev, @@ -66732,28 +71387,25 @@ ** But that is not due to sloppy coding habits. The code is written this ** way for performance, to avoid having to run the interrupt and progress ** checks on every opcode. This helps sqlite3_step() to run about 1.5% ** faster according to "valgrind --tool=cachegrind" */ check_for_interrupt: - CHECK_FOR_INTERRUPT; + if( db->u1.isInterrupted ) goto abort_due_to_interrupt; #ifndef SQLITE_OMIT_PROGRESS_CALLBACK /* Call the progress callback if it is configured and the required number ** of VDBE ops have been executed (either since this invocation of ** sqlite3VdbeExec() or since last time the progress callback was called). ** If the progress callback returns non-zero, exit the virtual machine with ** a return code SQLITE_ABORT. */ if( db->xProgress!=0 && nVmStep>=nProgressLimit ){ - int prc; - prc = db->xProgress(db->pProgressArg); - if( prc!=0 ){ + assert( db->nProgressOps!=0 ); + nProgressLimit = nVmStep + db->nProgressOps - (nVmStep%db->nProgressOps); + if( db->xProgress(db->pProgressArg) ){ rc = SQLITE_INTERRUPT; goto vdbe_error_halt; } - if( db->xProgress!=0 ){ - nProgressLimit = nVmStep + db->nProgressOps - (nVmStep%db->nProgressOps); - } } #endif break; } @@ -66764,11 +71416,11 @@ ** and then jump to address P2. */ case OP_Gosub: { /* jump */ assert( pOp->p1>0 && pOp->p1<=(p->nMem-p->nCursor) ); pIn1 = &aMem[pOp->p1]; - assert( (pIn1->flags & MEM_Dyn)==0 ); + assert( VdbeMemDynamic(pIn1)==0 ); memAboutToChange(p, pIn1); pIn1->flags = MEM_Int; pIn1->u.i = pc; REGISTER_TRACE(pOp->p1, pIn1); pc = pOp->p2 - 1; @@ -66775,50 +71427,105 @@ break; } /* Opcode: Return P1 * * * * ** -** Jump to the next instruction after the address in register P1. +** Jump to the next instruction after the address in register P1. After +** the jump, register P1 becomes undefined. */ case OP_Return: { /* in1 */ pIn1 = &aMem[pOp->p1]; - assert( pIn1->flags & MEM_Int ); + assert( pIn1->flags==MEM_Int ); pc = (int)pIn1->u.i; + pIn1->flags = MEM_Undefined; break; } -/* Opcode: Yield P1 * * * * +/* Opcode: InitCoroutine P1 P2 P3 * * ** -** Swap the program counter with the value in register P1. +** Set up register P1 so that it will Yield to the coroutine +** located at address P3. +** +** If P2!=0 then the coroutine implementation immediately follows +** this opcode. So jump over the coroutine implementation to +** address P2. +** +** See also: EndCoroutine */ -case OP_Yield: { /* in1 */ -#if 0 /* local variables moved into u.aa */ +case OP_InitCoroutine: { /* jump */ + assert( pOp->p1>0 && pOp->p1<=(p->nMem-p->nCursor) ); + assert( pOp->p2>=0 && pOp->p2<p->nOp ); + assert( pOp->p3>=0 && pOp->p3<p->nOp ); + pOut = &aMem[pOp->p1]; + assert( !VdbeMemDynamic(pOut) ); + pOut->u.i = pOp->p3 - 1; + pOut->flags = MEM_Int; + if( pOp->p2 ) pc = pOp->p2 - 1; + break; +} + +/* Opcode: EndCoroutine P1 * * * * +** +** The instruction at the address in register P1 is a Yield. +** Jump to the P2 parameter of that Yield. +** After the jump, register P1 becomes undefined. +** +** See also: InitCoroutine +*/ +case OP_EndCoroutine: { /* in1 */ + VdbeOp *pCaller; + pIn1 = &aMem[pOp->p1]; + assert( pIn1->flags==MEM_Int ); + assert( pIn1->u.i>=0 && pIn1->u.i<p->nOp ); + pCaller = &aOp[pIn1->u.i]; + assert( pCaller->opcode==OP_Yield ); + assert( pCaller->p2>=0 && pCaller->p2<p->nOp ); + pc = pCaller->p2 - 1; + pIn1->flags = MEM_Undefined; + break; +} + +/* Opcode: Yield P1 P2 * * * +** +** Swap the program counter with the value in register P1. This +** has the effect of yielding to a coroutine. +** +** If the coroutine that is launched by this instruction ends with +** Yield or Return then continue to the next instruction. But if +** the coroutine launched by this instruction ends with +** EndCoroutine, then jump to P2 rather than continuing with the +** next instruction. +** +** See also: InitCoroutine +*/ +case OP_Yield: { /* in1, jump */ int pcDest; -#endif /* local variables moved into u.aa */ pIn1 = &aMem[pOp->p1]; - assert( (pIn1->flags & MEM_Dyn)==0 ); + assert( VdbeMemDynamic(pIn1)==0 ); pIn1->flags = MEM_Int; - u.aa.pcDest = (int)pIn1->u.i; + pcDest = (int)pIn1->u.i; pIn1->u.i = pc; REGISTER_TRACE(pOp->p1, pIn1); - pc = u.aa.pcDest; + pc = pcDest; break; } -/* Opcode: HaltIfNull P1 P2 P3 P4 * +/* Opcode: HaltIfNull P1 P2 P3 P4 P5 +** Synopsis: if r[P3]=null halt ** ** Check the value in register P3. If it is NULL then Halt using ** parameter P1, P2, and P4 as if this were a Halt instruction. If the ** value in register P3 is not NULL, then this routine is a no-op. +** The P5 parameter should be 1. */ case OP_HaltIfNull: { /* in3 */ pIn3 = &aMem[pOp->p3]; if( (pIn3->flags & MEM_Null)==0 ) break; /* Fall through into OP_Halt */ } -/* Opcode: Halt P1 P2 * P4 * +/* Opcode: Halt P1 P2 * P4 P5 ** ** Exit immediately. All open cursors, etc are closed ** automatically. ** ** P1 is the result code returned by sqlite3_exec(), sqlite3_reset(), @@ -66828,16 +71535,30 @@ ** if P2==OE_Fail. Do the rollback if P2==OE_Rollback. If P2==OE_Abort, ** then back out all changes that have occurred during this execution of the ** VDBE, but do not rollback the transaction. ** ** If P4 is not null then it is an error message string. +** +** P5 is a value between 0 and 4, inclusive, that modifies the P4 string. +** +** 0: (no change) +** 1: NOT NULL contraint failed: P4 +** 2: UNIQUE constraint failed: P4 +** 3: CHECK constraint failed: P4 +** 4: FOREIGN KEY constraint failed: P4 +** +** If P5 is not zero and P4 is NULL, then everything after the ":" is +** omitted. ** ** There is an implied "Halt 0 0 0" instruction inserted at the very end of ** every program. So a jump past the last instruction of the program ** is the same as executing Halt. */ case OP_Halt: { + const char *zType; + const char *zLogFmt; + if( pOp->p1==SQLITE_OK && p->pFrame ){ /* Halt the sub-program. Return control to the parent frame. */ VdbeFrame *pFrame = p->pFrame; p->pFrame = pFrame->pParent; p->nFrame--; @@ -66854,22 +71575,37 @@ } aOp = p->aOp; aMem = p->aMem; break; } - p->rc = pOp->p1; p->errorAction = (u8)pOp->p2; p->pc = pc; - if( pOp->p4.z ){ - assert( p->rc!=SQLITE_OK ); - sqlite3SetString(&p->zErrMsg, db, "%s", pOp->p4.z); - testcase( sqlite3GlobalConfig.xLog!=0 ); - sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pc, p->zSql, pOp->p4.z); - }else if( p->rc ){ - testcase( sqlite3GlobalConfig.xLog!=0 ); - sqlite3_log(pOp->p1, "constraint failed at %d in [%s]", pc, p->zSql); + if( p->rc ){ + if( pOp->p5 ){ + static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK", + "FOREIGN KEY" }; + assert( pOp->p5>=1 && pOp->p5<=4 ); + testcase( pOp->p5==1 ); + testcase( pOp->p5==2 ); + testcase( pOp->p5==3 ); + testcase( pOp->p5==4 ); + zType = azType[pOp->p5-1]; + }else{ + zType = 0; + } + assert( zType!=0 || pOp->p4.z!=0 ); + zLogFmt = "abort at %d in [%s]: %s"; + if( zType && pOp->p4.z ){ + sqlite3SetString(&p->zErrMsg, db, "%s constraint failed: %s", + zType, pOp->p4.z); + }else if( pOp->p4.z ){ + sqlite3SetString(&p->zErrMsg, db, "%s", pOp->p4.z); + }else{ + sqlite3SetString(&p->zErrMsg, db, "%s constraint failed", zType); + } + sqlite3_log(pOp->p1, zLogFmt, pc, p->zSql, p->zErrMsg); } rc = sqlite3VdbeHalt(p); assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR ); if( rc==SQLITE_BUSY ){ p->rc = rc = SQLITE_BUSY; @@ -66880,19 +71616,21 @@ } goto vdbe_return; } /* Opcode: Integer P1 P2 * * * +** Synopsis: r[P2]=P1 ** ** The 32-bit integer value P1 is written into register P2. */ case OP_Integer: { /* out2-prerelease */ pOut->u.i = pOp->p1; break; } /* Opcode: Int64 * P2 * P4 * +** Synopsis: r[P2]=P4 ** ** P4 is a pointer to a 64-bit integer value. ** Write that value into register P2. */ case OP_Int64: { /* out2-prerelease */ @@ -66901,26 +71639,30 @@ break; } #ifndef SQLITE_OMIT_FLOATING_POINT /* Opcode: Real * P2 * P4 * +** Synopsis: r[P2]=P4 ** ** P4 is a pointer to a 64-bit floating point value. ** Write that value into register P2. */ case OP_Real: { /* same as TK_FLOAT, out2-prerelease */ pOut->flags = MEM_Real; assert( !sqlite3IsNaN(*pOp->p4.pReal) ); - pOut->r = *pOp->p4.pReal; + pOut->u.r = *pOp->p4.pReal; break; } #endif /* Opcode: String8 * P2 * P4 * +** Synopsis: r[P2]='P4' ** ** P4 points to a nul terminated UTF-8 string. This opcode is transformed -** into an OP_String before it is executed for the first time. +** into a String opcode before it is executed for the first time. During +** this transformation, the length of string P4 is computed and stored +** as the P1 parameter. */ case OP_String8: { /* same as TK_STRING, out2-prerelease */ assert( pOp->p4.z!=0 ); pOp->opcode = OP_String; pOp->p1 = sqlite3Strlen30(pOp->p4.z); @@ -66928,15 +71670,14 @@ #ifndef SQLITE_OMIT_UTF16 if( encoding!=SQLITE_UTF8 ){ rc = sqlite3VdbeMemSetStr(pOut, pOp->p4.z, -1, SQLITE_UTF8, SQLITE_STATIC); if( rc==SQLITE_TOOBIG ) goto too_big; if( SQLITE_OK!=sqlite3VdbeChangeEncoding(pOut, encoding) ) goto no_mem; - assert( pOut->zMalloc==pOut->z ); - assert( pOut->flags & MEM_Dyn ); - pOut->zMalloc = 0; + assert( pOut->szMalloc>0 && pOut->zMalloc==pOut->z ); + assert( VdbeMemDynamic(pOut)==0 ); + pOut->szMalloc = 0; pOut->flags |= MEM_Static; - pOut->flags &= ~MEM_Dyn; if( pOp->p4type==P4_DYNAMIC ){ sqlite3DbFree(db, pOp->p4.z); } pOp->p4type = P4_DYNAMIC; pOp->p4.z = pOut->z; @@ -66947,25 +71688,39 @@ goto too_big; } /* Fall through to the next case, OP_String */ } -/* Opcode: String P1 P2 * P4 * +/* Opcode: String P1 P2 P3 P4 P5 +** Synopsis: r[P2]='P4' (len=P1) ** ** The string value P4 of length P1 (bytes) is stored in register P2. +** +** If P5!=0 and the content of register P3 is greater than zero, then +** the datatype of the register P2 is converted to BLOB. The content is +** the same sequence of bytes, it is merely interpreted as a BLOB instead +** of a string, as if it had been CAST. */ case OP_String: { /* out2-prerelease */ assert( pOp->p4.z!=0 ); pOut->flags = MEM_Str|MEM_Static|MEM_Term; pOut->z = pOp->p4.z; pOut->n = pOp->p1; pOut->enc = encoding; UPDATE_MAX_BLOBSIZE(pOut); + if( pOp->p5 ){ + assert( pOp->p3>0 ); + assert( pOp->p3<=(p->nMem-p->nCursor) ); + pIn3 = &aMem[pOp->p3]; + assert( pIn3->flags & MEM_Int ); + if( pIn3->u.i ) pOut->flags = MEM_Blob|MEM_Static|MEM_Term; + } break; } /* Opcode: Null P1 P2 P3 * * +** Synopsis: r[P2..P3]=NULL ** ** Write a NULL into registers P2. If P3 greater than P2, then also write ** NULL into register P3 and every register in between P2 and P3. If P3 ** is less than P2 (typically P3 is zero) then only register P2 is ** set to NULL. @@ -66973,29 +71728,42 @@ ** If the P1 value is non-zero, then also set the MEM_Cleared flag so that ** NULL values will not compare equal even if SQLITE_NULLEQ is set on ** OP_Ne or OP_Eq. */ case OP_Null: { /* out2-prerelease */ -#if 0 /* local variables moved into u.ab */ int cnt; u16 nullFlag; -#endif /* local variables moved into u.ab */ - u.ab.cnt = pOp->p3-pOp->p2; + cnt = pOp->p3-pOp->p2; assert( pOp->p3<=(p->nMem-p->nCursor) ); - pOut->flags = u.ab.nullFlag = pOp->p1 ? (MEM_Null|MEM_Cleared) : MEM_Null; - while( u.ab.cnt>0 ){ + pOut->flags = nullFlag = pOp->p1 ? (MEM_Null|MEM_Cleared) : MEM_Null; + while( cnt>0 ){ pOut++; memAboutToChange(p, pOut); - VdbeMemRelease(pOut); - pOut->flags = u.ab.nullFlag; - u.ab.cnt--; + sqlite3VdbeMemSetNull(pOut); + pOut->flags = nullFlag; + cnt--; } break; } +/* Opcode: SoftNull P1 * * * * +** Synopsis: r[P1]=NULL +** +** Set register P1 to have the value NULL as seen by the OP_MakeRecord +** instruction, but do not free any string or blob memory associated with +** the register, so that if the value was a string or blob that was +** previously copied using OP_SCopy, the copies will continue to be valid. +*/ +case OP_SoftNull: { + assert( pOp->p1>0 && pOp->p1<=(p->nMem-p->nCursor) ); + pOut = &aMem[pOp->p1]; + pOut->flags = (pOut->flags|MEM_Null)&~MEM_Undefined; + break; +} -/* Opcode: Blob P1 P2 * P4 +/* Opcode: Blob P1 P2 * P4 * +** Synopsis: r[P2]=P4 (len=P1) ** ** P4 points to a blob of data P1 bytes long. Store this ** blob in register P2. */ case OP_Blob: { /* out2-prerelease */ @@ -67005,107 +71773,102 @@ UPDATE_MAX_BLOBSIZE(pOut); break; } /* Opcode: Variable P1 P2 * P4 * +** Synopsis: r[P2]=parameter(P1,P4) ** ** Transfer the values of bound parameter P1 into register P2 ** -** If the parameter is named, then its name appears in P4 and P3==1. +** If the parameter is named, then its name appears in P4. ** The P4 value is used by sqlite3_bind_parameter_name(). */ case OP_Variable: { /* out2-prerelease */ -#if 0 /* local variables moved into u.ac */ Mem *pVar; /* Value being transferred */ -#endif /* local variables moved into u.ac */ assert( pOp->p1>0 && pOp->p1<=p->nVar ); assert( pOp->p4.z==0 || pOp->p4.z==p->azVar[pOp->p1-1] ); - u.ac.pVar = &p->aVar[pOp->p1 - 1]; - if( sqlite3VdbeMemTooBig(u.ac.pVar) ){ + pVar = &p->aVar[pOp->p1 - 1]; + if( sqlite3VdbeMemTooBig(pVar) ){ goto too_big; } - sqlite3VdbeMemShallowCopy(pOut, u.ac.pVar, MEM_Static); + sqlite3VdbeMemShallowCopy(pOut, pVar, MEM_Static); UPDATE_MAX_BLOBSIZE(pOut); break; } /* Opcode: Move P1 P2 P3 * * +** Synopsis: r[P2@P3]=r[P1@P3] ** -** Move the values in register P1..P1+P3 over into -** registers P2..P2+P3. Registers P1..P1+P3 are +** Move the P3 values in register P1..P1+P3-1 over into +** registers P2..P2+P3-1. Registers P1..P1+P3-1 are ** left holding a NULL. It is an error for register ranges -** P1..P1+P3 and P2..P2+P3 to overlap. +** P1..P1+P3-1 and P2..P2+P3-1 to overlap. It is an error +** for P3 to be less than 1. */ case OP_Move: { -#if 0 /* local variables moved into u.ad */ - char *zMalloc; /* Holding variable for allocated memory */ int n; /* Number of registers left to copy */ int p1; /* Register to copy from */ int p2; /* Register to copy to */ -#endif /* local variables moved into u.ad */ - - u.ad.n = pOp->p3 + 1; - u.ad.p1 = pOp->p1; - u.ad.p2 = pOp->p2; - assert( u.ad.n>0 && u.ad.p1>0 && u.ad.p2>0 ); - assert( u.ad.p1+u.ad.n<=u.ad.p2 || u.ad.p2+u.ad.n<=u.ad.p1 ); - - pIn1 = &aMem[u.ad.p1]; - pOut = &aMem[u.ad.p2]; - while( u.ad.n-- ){ + + n = pOp->p3; + p1 = pOp->p1; + p2 = pOp->p2; + assert( n>0 && p1>0 && p2>0 ); + assert( p1+n<=p2 || p2+n<=p1 ); + + pIn1 = &aMem[p1]; + pOut = &aMem[p2]; + do{ assert( pOut<=&aMem[(p->nMem-p->nCursor)] ); assert( pIn1<=&aMem[(p->nMem-p->nCursor)] ); assert( memIsValid(pIn1) ); memAboutToChange(p, pOut); - u.ad.zMalloc = pOut->zMalloc; - pOut->zMalloc = 0; sqlite3VdbeMemMove(pOut, pIn1); #ifdef SQLITE_DEBUG - if( pOut->pScopyFrom>=&aMem[u.ad.p1] && pOut->pScopyFrom<&aMem[u.ad.p1+pOp->p3] ){ - pOut->pScopyFrom += u.ad.p1 - pOp->p2; + if( pOut->pScopyFrom>=&aMem[p1] && pOut->pScopyFrom<&aMem[p1+pOp->p3] ){ + pOut->pScopyFrom += p1 - pOp->p2; } #endif - pIn1->zMalloc = u.ad.zMalloc; - REGISTER_TRACE(u.ad.p2++, pOut); + REGISTER_TRACE(p2++, pOut); pIn1++; pOut++; - } + }while( --n ); break; } /* Opcode: Copy P1 P2 P3 * * +** Synopsis: r[P2@P3+1]=r[P1@P3+1] ** ** Make a copy of registers P1..P1+P3 into registers P2..P2+P3. ** ** This instruction makes a deep copy of the value. A duplicate ** is made of any string or blob constant. See also OP_SCopy. */ case OP_Copy: { -#if 0 /* local variables moved into u.ae */ int n; -#endif /* local variables moved into u.ae */ - u.ae.n = pOp->p3; + n = pOp->p3; pIn1 = &aMem[pOp->p1]; pOut = &aMem[pOp->p2]; assert( pOut!=pIn1 ); while( 1 ){ sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem); Deephemeralize(pOut); #ifdef SQLITE_DEBUG pOut->pScopyFrom = 0; #endif - REGISTER_TRACE(pOp->p2+pOp->p3-u.ae.n, pOut); - if( (u.ae.n--)==0 ) break; + REGISTER_TRACE(pOp->p2+pOp->p3-n, pOut); + if( (n--)==0 ) break; pOut++; pIn1++; } break; } /* Opcode: SCopy P1 P2 * * * +** Synopsis: r[P2]=r[P1] ** ** Make a shallow copy of register P1 into register P2. ** ** This instruction makes a shallow copy of the value. If the value ** is a string or blob, then the copy is only a pointer to the @@ -67113,38 +71876,48 @@ ** Worse, if the original is deallocated, the copy becomes invalid. ** Thus the program must guarantee that the original will not change ** during the lifetime of the copy. Use OP_Copy to make a complete ** copy. */ -case OP_SCopy: { /* in1, out2 */ +case OP_SCopy: { /* out2 */ pIn1 = &aMem[pOp->p1]; pOut = &aMem[pOp->p2]; assert( pOut!=pIn1 ); sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem); #ifdef SQLITE_DEBUG if( pOut->pScopyFrom==0 ) pOut->pScopyFrom = pIn1; #endif - REGISTER_TRACE(pOp->p2, pOut); break; } /* Opcode: ResultRow P1 P2 * * * +** Synopsis: output=r[P1@P2] ** ** The registers P1 through P1+P2-1 contain a single row of ** results. This opcode causes the sqlite3_step() call to terminate ** with an SQLITE_ROW return code and it sets up the sqlite3_stmt -** structure to provide access to the top P1 values as the result -** row. +** structure to provide access to the r(P1)..r(P1+P2-1) values as +** the result row. */ case OP_ResultRow: { -#if 0 /* local variables moved into u.af */ Mem *pMem; int i; -#endif /* local variables moved into u.af */ assert( p->nResColumn==pOp->p2 ); assert( pOp->p1>0 ); assert( pOp->p1+pOp->p2<=(p->nMem-p->nCursor)+1 ); + +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + /* Run the progress counter just before returning. + */ + if( db->xProgress!=0 + && nVmStep>=nProgressLimit + && db->xProgress(db->pProgressArg)!=0 + ){ + rc = SQLITE_INTERRUPT; + goto vdbe_error_halt; + } +#endif /* If this statement has violated immediate foreign key constraints, do ** not return the number of rows modified. And do not RELEASE the statement ** transaction. It needs to be rolled back. */ if( SQLITE_OK!=(rc = sqlite3VdbeCheckFk(p, 0)) ){ @@ -67151,12 +71924,12 @@ assert( db->flags&SQLITE_CountRows ); assert( p->usesStmtJournal ); break; } - /* If the SQLITE_CountRows flag is set in sqlite3.flags mask, then - ** DML statements invoke this opcode to return the number of rows + /* If the SQLITE_CountRows flag is set in sqlite3.flags mask, then + ** DML statements invoke this opcode to return the number of rows ** modified to the user. This is the only way that a VM that ** opens a statement transaction may invoke this opcode. ** ** In case this is such a statement, close any statement transaction ** opened by this VM before returning control to the user. This is to @@ -67179,19 +71952,18 @@ /* Make sure the results of the current row are \000 terminated ** and have an assigned type. The results are de-ephemeralized as ** a side effect. */ - u.af.pMem = p->pResultSet = &aMem[pOp->p1]; - for(u.af.i=0; u.af.i<pOp->p2; u.af.i++){ - assert( memIsValid(&u.af.pMem[u.af.i]) ); - Deephemeralize(&u.af.pMem[u.af.i]); - assert( (u.af.pMem[u.af.i].flags & MEM_Ephem)==0 - || (u.af.pMem[u.af.i].flags & (MEM_Str|MEM_Blob))==0 ); - sqlite3VdbeMemNulTerminate(&u.af.pMem[u.af.i]); - sqlite3VdbeMemStoreType(&u.af.pMem[u.af.i]); - REGISTER_TRACE(pOp->p1+u.af.i, &u.af.pMem[u.af.i]); + pMem = p->pResultSet = &aMem[pOp->p1]; + for(i=0; i<pOp->p2; i++){ + assert( memIsValid(&pMem[i]) ); + Deephemeralize(&pMem[i]); + assert( (pMem[i].flags & MEM_Ephem)==0 + || (pMem[i].flags & (MEM_Str|MEM_Blob))==0 ); + sqlite3VdbeMemNulTerminate(&pMem[i]); + REGISTER_TRACE(pOp->p1+i, &pMem[i]); } if( db->mallocFailed ) goto no_mem; /* Return SQLITE_ROW */ @@ -67199,10 +71971,11 @@ rc = SQLITE_ROW; goto vdbe_return; } /* Opcode: Concat P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]+r[P1] ** ** Add the text in register P1 onto the end of the text in ** register P2 and store the result in register P3. ** If either the P1 or P2 text are NULL then store NULL in P3. ** @@ -67211,13 +71984,11 @@ ** It is illegal for P1 and P3 to be the same register. Sometimes, ** if P3 is the same register as P2, the implementation is able ** to avoid a memcpy(). */ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ -#if 0 /* local variables moved into u.ag */ i64 nByte; -#endif /* local variables moved into u.ag */ pIn1 = &aMem[pOp->p1]; pIn2 = &aMem[pOp->p2]; pOut = &aMem[pOp->p3]; assert( pIn1!=pOut ); @@ -67226,142 +71997,147 @@ break; } if( ExpandBlob(pIn1) || ExpandBlob(pIn2) ) goto no_mem; Stringify(pIn1, encoding); Stringify(pIn2, encoding); - u.ag.nByte = pIn1->n + pIn2->n; - if( u.ag.nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ + nByte = pIn1->n + pIn2->n; + if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } - MemSetTypeFlag(pOut, MEM_Str); - if( sqlite3VdbeMemGrow(pOut, (int)u.ag.nByte+2, pOut==pIn2) ){ + if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){ goto no_mem; } + MemSetTypeFlag(pOut, MEM_Str); if( pOut!=pIn2 ){ memcpy(pOut->z, pIn2->z, pIn2->n); } memcpy(&pOut->z[pIn2->n], pIn1->z, pIn1->n); - pOut->z[u.ag.nByte] = 0; - pOut->z[u.ag.nByte+1] = 0; + pOut->z[nByte]=0; + pOut->z[nByte+1] = 0; pOut->flags |= MEM_Term; - pOut->n = (int)u.ag.nByte; + pOut->n = (int)nByte; pOut->enc = encoding; UPDATE_MAX_BLOBSIZE(pOut); break; } /* Opcode: Add P1 P2 P3 * * +** Synopsis: r[P3]=r[P1]+r[P2] ** ** Add the value in register P1 to the value in register P2 ** and store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: Multiply P1 P2 P3 * * +** Synopsis: r[P3]=r[P1]*r[P2] ** ** ** Multiply the value in register P1 by the value in register P2 ** and store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: Subtract P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]-r[P1] ** ** Subtract the value in register P1 from the value in register P2 ** and store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: Divide P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]/r[P1] ** ** Divide the value in register P1 by the value in register P2 ** and store the result in register P3 (P3=P2/P1). If the value in ** register P1 is zero, then the result is NULL. If either input is ** NULL, the result is NULL. */ /* Opcode: Remainder P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]%r[P1] ** -** Compute the remainder after integer division of the value in -** register P1 by the value in register P2 and store the result in P3. -** If the value in register P2 is zero the result is NULL. +** Compute the remainder after integer register P2 is divided by +** register P1 and store the result in register P3. +** If the value in register P1 is zero the result is NULL. ** If either operand is NULL, the result is NULL. */ case OP_Add: /* same as TK_PLUS, in1, in2, out3 */ case OP_Subtract: /* same as TK_MINUS, in1, in2, out3 */ case OP_Multiply: /* same as TK_STAR, in1, in2, out3 */ case OP_Divide: /* same as TK_SLASH, in1, in2, out3 */ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */ -#if 0 /* local variables moved into u.ah */ char bIntint; /* Started out as two integer operands */ - int flags; /* Combined MEM_* flags from both inputs */ + u16 flags; /* Combined MEM_* flags from both inputs */ + u16 type1; /* Numeric type of left operand */ + u16 type2; /* Numeric type of right operand */ i64 iA; /* Integer value of left operand */ i64 iB; /* Integer value of right operand */ double rA; /* Real value of left operand */ double rB; /* Real value of right operand */ -#endif /* local variables moved into u.ah */ pIn1 = &aMem[pOp->p1]; - applyNumericAffinity(pIn1); + type1 = numericType(pIn1); pIn2 = &aMem[pOp->p2]; - applyNumericAffinity(pIn2); + type2 = numericType(pIn2); pOut = &aMem[pOp->p3]; - u.ah.flags = pIn1->flags | pIn2->flags; - if( (u.ah.flags & MEM_Null)!=0 ) goto arithmetic_result_is_null; - if( (pIn1->flags & pIn2->flags & MEM_Int)==MEM_Int ){ - u.ah.iA = pIn1->u.i; - u.ah.iB = pIn2->u.i; - u.ah.bIntint = 1; + flags = pIn1->flags | pIn2->flags; + if( (flags & MEM_Null)!=0 ) goto arithmetic_result_is_null; + if( (type1 & type2 & MEM_Int)!=0 ){ + iA = pIn1->u.i; + iB = pIn2->u.i; + bIntint = 1; switch( pOp->opcode ){ - case OP_Add: if( sqlite3AddInt64(&u.ah.iB,u.ah.iA) ) goto fp_math; break; - case OP_Subtract: if( sqlite3SubInt64(&u.ah.iB,u.ah.iA) ) goto fp_math; break; - case OP_Multiply: if( sqlite3MulInt64(&u.ah.iB,u.ah.iA) ) goto fp_math; break; + case OP_Add: if( sqlite3AddInt64(&iB,iA) ) goto fp_math; break; + case OP_Subtract: if( sqlite3SubInt64(&iB,iA) ) goto fp_math; break; + case OP_Multiply: if( sqlite3MulInt64(&iB,iA) ) goto fp_math; break; case OP_Divide: { - if( u.ah.iA==0 ) goto arithmetic_result_is_null; - if( u.ah.iA==-1 && u.ah.iB==SMALLEST_INT64 ) goto fp_math; - u.ah.iB /= u.ah.iA; + if( iA==0 ) goto arithmetic_result_is_null; + if( iA==-1 && iB==SMALLEST_INT64 ) goto fp_math; + iB /= iA; break; } default: { - if( u.ah.iA==0 ) goto arithmetic_result_is_null; - if( u.ah.iA==-1 ) u.ah.iA = 1; - u.ah.iB %= u.ah.iA; + if( iA==0 ) goto arithmetic_result_is_null; + if( iA==-1 ) iA = 1; + iB %= iA; break; } } - pOut->u.i = u.ah.iB; + pOut->u.i = iB; MemSetTypeFlag(pOut, MEM_Int); }else{ - u.ah.bIntint = 0; + bIntint = 0; fp_math: - u.ah.rA = sqlite3VdbeRealValue(pIn1); - u.ah.rB = sqlite3VdbeRealValue(pIn2); + rA = sqlite3VdbeRealValue(pIn1); + rB = sqlite3VdbeRealValue(pIn2); switch( pOp->opcode ){ - case OP_Add: u.ah.rB += u.ah.rA; break; - case OP_Subtract: u.ah.rB -= u.ah.rA; break; - case OP_Multiply: u.ah.rB *= u.ah.rA; break; + case OP_Add: rB += rA; break; + case OP_Subtract: rB -= rA; break; + case OP_Multiply: rB *= rA; break; case OP_Divide: { /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ - if( u.ah.rA==(double)0 ) goto arithmetic_result_is_null; - u.ah.rB /= u.ah.rA; + if( rA==(double)0 ) goto arithmetic_result_is_null; + rB /= rA; break; } default: { - u.ah.iA = (i64)u.ah.rA; - u.ah.iB = (i64)u.ah.rB; - if( u.ah.iA==0 ) goto arithmetic_result_is_null; - if( u.ah.iA==-1 ) u.ah.iA = 1; - u.ah.rB = (double)(u.ah.iB % u.ah.iA); + iA = (i64)rA; + iB = (i64)rB; + if( iA==0 ) goto arithmetic_result_is_null; + if( iA==-1 ) iA = 1; + rB = (double)(iB % iA); break; } } #ifdef SQLITE_OMIT_FLOATING_POINT - pOut->u.i = u.ah.rB; + pOut->u.i = rB; MemSetTypeFlag(pOut, MEM_Int); #else - if( sqlite3IsNaN(u.ah.rB) ){ + if( sqlite3IsNaN(rB) ){ goto arithmetic_result_is_null; } - pOut->r = u.ah.rB; + pOut->u.r = rB; MemSetTypeFlag(pOut, MEM_Real); - if( (u.ah.flags & MEM_Real)==0 && !u.ah.bIntint ){ + if( ((type1|type2)&MEM_Real)==0 && !bIntint ){ sqlite3VdbeIntegerAffinity(pOut); } #endif } break; @@ -67382,11 +72158,11 @@ ** max() aggregate will set to 1 if the current row is not the minimum or ** maximum. The P1 register is initialized to 0 by this instruction. ** ** The interface used by the implementation of the aforementioned functions ** to retrieve the collation sequence set by this opcode is not available -** publicly, only to user functions defined in func.c. +** publicly. Only built-in functions have access to this feature. */ case OP_CollSeq: { assert( pOp->p4type==P4_COLLSEQ ); if( pOp->p1 ){ sqlite3VdbeMemSetInt64(&aMem[pOp->p1], 0); @@ -67393,10 +72169,11 @@ } break; } /* Opcode: Function P1 P2 P3 P4 P5 +** Synopsis: r[P3]=func(r[P2@P5]) ** ** Invoke a user function (P4 is a pointer to a Function structure that ** defines the function) with P5 arguments taken from register P2 and ** successors. The result of the function is stored in register P3. ** Register P3 must not be one of the function inputs. @@ -67409,122 +72186,87 @@ ** invocation of this opcode. ** ** See also: AggStep and AggFinal */ case OP_Function: { -#if 0 /* local variables moved into u.ai */ int i; Mem *pArg; sqlite3_context ctx; sqlite3_value **apVal; int n; -#endif /* local variables moved into u.ai */ - u.ai.n = pOp->p5; - u.ai.apVal = p->apArg; - assert( u.ai.apVal || u.ai.n==0 ); + n = pOp->p5; + apVal = p->apArg; + assert( apVal || n==0 ); assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); - pOut = &aMem[pOp->p3]; - memAboutToChange(p, pOut); - - assert( u.ai.n==0 || (pOp->p2>0 && pOp->p2+u.ai.n<=(p->nMem-p->nCursor)+1) ); - assert( pOp->p3<pOp->p2 || pOp->p3>=pOp->p2+u.ai.n ); - u.ai.pArg = &aMem[pOp->p2]; - for(u.ai.i=0; u.ai.i<u.ai.n; u.ai.i++, u.ai.pArg++){ - assert( memIsValid(u.ai.pArg) ); - u.ai.apVal[u.ai.i] = u.ai.pArg; - Deephemeralize(u.ai.pArg); - sqlite3VdbeMemStoreType(u.ai.pArg); - REGISTER_TRACE(pOp->p2+u.ai.i, u.ai.pArg); + ctx.pOut = &aMem[pOp->p3]; + memAboutToChange(p, ctx.pOut); + + assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem-p->nCursor)+1) ); + assert( pOp->p3<pOp->p2 || pOp->p3>=pOp->p2+n ); + pArg = &aMem[pOp->p2]; + for(i=0; i<n; i++, pArg++){ + assert( memIsValid(pArg) ); + apVal[i] = pArg; + Deephemeralize(pArg); + REGISTER_TRACE(pOp->p2+i, pArg); } assert( pOp->p4type==P4_FUNCDEF ); - u.ai.ctx.pFunc = pOp->p4.pFunc; - u.ai.ctx.s.flags = MEM_Null; - u.ai.ctx.s.db = db; - u.ai.ctx.s.xDel = 0; - u.ai.ctx.s.zMalloc = 0; - u.ai.ctx.iOp = pc; - u.ai.ctx.pVdbe = p; - - /* The output cell may already have a buffer allocated. Move - ** the pointer to u.ai.ctx.s so in case the user-function can use - ** the already allocated buffer instead of allocating a new one. - */ - sqlite3VdbeMemMove(&u.ai.ctx.s, pOut); - MemSetTypeFlag(&u.ai.ctx.s, MEM_Null); - - u.ai.ctx.fErrorOrAux = 0; - if( u.ai.ctx.pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ - assert( pOp>aOp ); - assert( pOp[-1].p4type==P4_COLLSEQ ); - assert( pOp[-1].opcode==OP_CollSeq ); - u.ai.ctx.pColl = pOp[-1].p4.pColl; - } + ctx.pFunc = pOp->p4.pFunc; + ctx.iOp = pc; + ctx.pVdbe = p; + MemSetTypeFlag(ctx.pOut, MEM_Null); + ctx.fErrorOrAux = 0; db->lastRowid = lastRowid; - (*u.ai.ctx.pFunc->xFunc)(&u.ai.ctx, u.ai.n, u.ai.apVal); /* IMP: R-24505-23230 */ - lastRowid = db->lastRowid; - - if( db->mallocFailed ){ - /* Even though a malloc() has failed, the implementation of the - ** user function may have called an sqlite3_result_XXX() function - ** to return a value. The following call releases any resources - ** associated with such a value. - */ - sqlite3VdbeMemRelease(&u.ai.ctx.s); - goto no_mem; - } + (*ctx.pFunc->xFunc)(&ctx, n, apVal); /* IMP: R-24505-23230 */ + lastRowid = db->lastRowid; /* Remember rowid changes made by xFunc */ /* If the function returned an error, throw an exception */ - if( u.ai.ctx.fErrorOrAux ){ - if( u.ai.ctx.isError ){ - sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3_value_text(&u.ai.ctx.s)); - rc = u.ai.ctx.isError; + if( ctx.fErrorOrAux ){ + if( ctx.isError ){ + sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3_value_text(ctx.pOut)); + rc = ctx.isError; } sqlite3VdbeDeleteAuxData(p, pc, pOp->p1); } /* Copy the result of the function into register P3 */ - sqlite3VdbeChangeEncoding(&u.ai.ctx.s, encoding); - sqlite3VdbeMemMove(pOut, &u.ai.ctx.s); - if( sqlite3VdbeMemTooBig(pOut) ){ + sqlite3VdbeChangeEncoding(ctx.pOut, encoding); + if( sqlite3VdbeMemTooBig(ctx.pOut) ){ goto too_big; } -#if 0 - /* The app-defined function has done something that as caused this - ** statement to expire. (Perhaps the function called sqlite3_exec() - ** with a CREATE TABLE statement.) - */ - if( p->expired ) rc = SQLITE_ABORT; -#endif - - REGISTER_TRACE(pOp->p3, pOut); - UPDATE_MAX_BLOBSIZE(pOut); + REGISTER_TRACE(pOp->p3, ctx.pOut); + UPDATE_MAX_BLOBSIZE(ctx.pOut); break; } /* Opcode: BitAnd P1 P2 P3 * * +** Synopsis: r[P3]=r[P1]&r[P2] ** ** Take the bit-wise AND of the values in register P1 and P2 and ** store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: BitOr P1 P2 P3 * * +** Synopsis: r[P3]=r[P1]|r[P2] ** ** Take the bit-wise OR of the values in register P1 and P2 and ** store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: ShiftLeft P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]<<r[P1] ** ** Shift the integer value in register P2 to the left by the ** number of bits specified by the integer in register P1. ** Store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: ShiftRight P1 P2 P3 * * +** Synopsis: r[P3]=r[P2]>>r[P1] ** ** Shift the integer value in register P2 to the right by the ** number of bits specified by the integer in register P1. ** Store the result in register P3. ** If either input is NULL, the result is NULL. @@ -67531,61 +72273,60 @@ */ case OP_BitAnd: /* same as TK_BITAND, in1, in2, out3 */ case OP_BitOr: /* same as TK_BITOR, in1, in2, out3 */ case OP_ShiftLeft: /* same as TK_LSHIFT, in1, in2, out3 */ case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */ -#if 0 /* local variables moved into u.aj */ i64 iA; u64 uA; i64 iB; u8 op; -#endif /* local variables moved into u.aj */ pIn1 = &aMem[pOp->p1]; pIn2 = &aMem[pOp->p2]; pOut = &aMem[pOp->p3]; if( (pIn1->flags | pIn2->flags) & MEM_Null ){ sqlite3VdbeMemSetNull(pOut); break; } - u.aj.iA = sqlite3VdbeIntValue(pIn2); - u.aj.iB = sqlite3VdbeIntValue(pIn1); - u.aj.op = pOp->opcode; - if( u.aj.op==OP_BitAnd ){ - u.aj.iA &= u.aj.iB; - }else if( u.aj.op==OP_BitOr ){ - u.aj.iA |= u.aj.iB; - }else if( u.aj.iB!=0 ){ - assert( u.aj.op==OP_ShiftRight || u.aj.op==OP_ShiftLeft ); + iA = sqlite3VdbeIntValue(pIn2); + iB = sqlite3VdbeIntValue(pIn1); + op = pOp->opcode; + if( op==OP_BitAnd ){ + iA &= iB; + }else if( op==OP_BitOr ){ + iA |= iB; + }else if( iB!=0 ){ + assert( op==OP_ShiftRight || op==OP_ShiftLeft ); /* If shifting by a negative amount, shift in the other direction */ - if( u.aj.iB<0 ){ + if( iB<0 ){ assert( OP_ShiftRight==OP_ShiftLeft+1 ); - u.aj.op = 2*OP_ShiftLeft + 1 - u.aj.op; - u.aj.iB = u.aj.iB>(-64) ? -u.aj.iB : 64; + op = 2*OP_ShiftLeft + 1 - op; + iB = iB>(-64) ? -iB : 64; } - if( u.aj.iB>=64 ){ - u.aj.iA = (u.aj.iA>=0 || u.aj.op==OP_ShiftLeft) ? 0 : -1; - }else{ - memcpy(&u.aj.uA, &u.aj.iA, sizeof(u.aj.uA)); - if( u.aj.op==OP_ShiftLeft ){ - u.aj.uA <<= u.aj.iB; - }else{ - u.aj.uA >>= u.aj.iB; + if( iB>=64 ){ + iA = (iA>=0 || op==OP_ShiftLeft) ? 0 : -1; + }else{ + memcpy(&uA, &iA, sizeof(uA)); + if( op==OP_ShiftLeft ){ + uA <<= iB; + }else{ + uA >>= iB; /* Sign-extend on a right shift of a negative number */ - if( u.aj.iA<0 ) u.aj.uA |= ((((u64)0xffffffff)<<32)|0xffffffff) << (64-u.aj.iB); + if( iA<0 ) uA |= ((((u64)0xffffffff)<<32)|0xffffffff) << (64-iB); } - memcpy(&u.aj.iA, &u.aj.uA, sizeof(u.aj.iA)); + memcpy(&iA, &uA, sizeof(iA)); } } - pOut->u.i = u.aj.iA; + pOut->u.i = iA; MemSetTypeFlag(pOut, MEM_Int); break; } /* Opcode: AddImm P1 P2 * * * +** Synopsis: r[P1]=r[P1]+P2 ** ** Add the constant P2 to the value in register P1. ** The result is always an integer. ** ** To force any register to be an integer, just add 0. @@ -67605,21 +72346,24 @@ ** without data loss, then jump immediately to P2, or if P2==0 ** raise an SQLITE_MISMATCH exception. */ case OP_MustBeInt: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; - applyAffinity(pIn1, SQLITE_AFF_NUMERIC, encoding); if( (pIn1->flags & MEM_Int)==0 ){ - if( pOp->p2==0 ){ - rc = SQLITE_MISMATCH; - goto abort_due_to_error; - }else{ - pc = pOp->p2 - 1; - } - }else{ - MemSetTypeFlag(pIn1, MEM_Int); - } + applyAffinity(pIn1, SQLITE_AFF_NUMERIC, encoding); + VdbeBranchTaken((pIn1->flags&MEM_Int)==0, 2); + if( (pIn1->flags & MEM_Int)==0 ){ + if( pOp->p2==0 ){ + rc = SQLITE_MISMATCH; + goto abort_due_to_error; + }else{ + pc = pOp->p2 - 1; + break; + } + } + } + MemSetTypeFlag(pIn1, MEM_Int); break; } #ifndef SQLITE_OMIT_FLOATING_POINT /* Opcode: RealAffinity P1 * * * * @@ -67639,111 +72383,43 @@ break; } #endif #ifndef SQLITE_OMIT_CAST -/* Opcode: ToText P1 * * * * -** -** Force the value in register P1 to be text. -** If the value is numeric, convert it to a string using the -** equivalent of printf(). Blob values are unchanged and -** are afterwards simply interpreted as text. -** -** A NULL value is not changed by this routine. It remains NULL. -*/ -case OP_ToText: { /* same as TK_TO_TEXT, in1 */ - pIn1 = &aMem[pOp->p1]; - memAboutToChange(p, pIn1); - if( pIn1->flags & MEM_Null ) break; - assert( MEM_Str==(MEM_Blob>>3) ); - pIn1->flags |= (pIn1->flags&MEM_Blob)>>3; - applyAffinity(pIn1, SQLITE_AFF_TEXT, encoding); - rc = ExpandBlob(pIn1); - assert( pIn1->flags & MEM_Str || db->mallocFailed ); - pIn1->flags &= ~(MEM_Int|MEM_Real|MEM_Blob|MEM_Zero); - UPDATE_MAX_BLOBSIZE(pIn1); - break; -} - -/* Opcode: ToBlob P1 * * * * -** -** Force the value in register P1 to be a BLOB. -** If the value is numeric, convert it to a string first. -** Strings are simply reinterpreted as blobs with no change -** to the underlying data. -** -** A NULL value is not changed by this routine. It remains NULL. -*/ -case OP_ToBlob: { /* same as TK_TO_BLOB, in1 */ - pIn1 = &aMem[pOp->p1]; - if( pIn1->flags & MEM_Null ) break; - if( (pIn1->flags & MEM_Blob)==0 ){ - applyAffinity(pIn1, SQLITE_AFF_TEXT, encoding); - assert( pIn1->flags & MEM_Str || db->mallocFailed ); - MemSetTypeFlag(pIn1, MEM_Blob); - }else{ - pIn1->flags &= ~(MEM_TypeMask&~MEM_Blob); - } - UPDATE_MAX_BLOBSIZE(pIn1); - break; -} - -/* Opcode: ToNumeric P1 * * * * -** -** Force the value in register P1 to be numeric (either an -** integer or a floating-point number.) -** If the value is text or blob, try to convert it to an using the -** equivalent of atoi() or atof() and store 0 if no such conversion -** is possible. -** -** A NULL value is not changed by this routine. It remains NULL. -*/ -case OP_ToNumeric: { /* same as TK_TO_NUMERIC, in1 */ - pIn1 = &aMem[pOp->p1]; - sqlite3VdbeMemNumerify(pIn1); +/* Opcode: Cast P1 P2 * * * +** Synopsis: affinity(r[P1]) +** +** Force the value in register P1 to be the type defined by P2. +** +** <ul> +** <li value="97"> TEXT +** <li value="98"> BLOB +** <li value="99"> NUMERIC +** <li value="100"> INTEGER +** <li value="101"> REAL +** </ul> +** +** A NULL value is not changed by this routine. It remains NULL. +*/ +case OP_Cast: { /* in1 */ + assert( pOp->p2>=SQLITE_AFF_NONE && pOp->p2<=SQLITE_AFF_REAL ); + testcase( pOp->p2==SQLITE_AFF_TEXT ); + testcase( pOp->p2==SQLITE_AFF_NONE ); + testcase( pOp->p2==SQLITE_AFF_NUMERIC ); + testcase( pOp->p2==SQLITE_AFF_INTEGER ); + testcase( pOp->p2==SQLITE_AFF_REAL ); + pIn1 = &aMem[pOp->p1]; + memAboutToChange(p, pIn1); + rc = ExpandBlob(pIn1); + sqlite3VdbeMemCast(pIn1, pOp->p2, encoding); + UPDATE_MAX_BLOBSIZE(pIn1); break; } #endif /* SQLITE_OMIT_CAST */ -/* Opcode: ToInt P1 * * * * -** -** Force the value in register P1 to be an integer. If -** The value is currently a real number, drop its fractional part. -** If the value is text or blob, try to convert it to an integer using the -** equivalent of atoi() and store 0 if no such conversion is possible. -** -** A NULL value is not changed by this routine. It remains NULL. -*/ -case OP_ToInt: { /* same as TK_TO_INT, in1 */ - pIn1 = &aMem[pOp->p1]; - if( (pIn1->flags & MEM_Null)==0 ){ - sqlite3VdbeMemIntegerify(pIn1); - } - break; -} - -#if !defined(SQLITE_OMIT_CAST) && !defined(SQLITE_OMIT_FLOATING_POINT) -/* Opcode: ToReal P1 * * * * -** -** Force the value in register P1 to be a floating point number. -** If The value is currently an integer, convert it. -** If the value is text or blob, try to convert it to an integer using the -** equivalent of atoi() and store 0.0 if no such conversion is possible. -** -** A NULL value is not changed by this routine. It remains NULL. -*/ -case OP_ToReal: { /* same as TK_TO_REAL, in1 */ - pIn1 = &aMem[pOp->p1]; - memAboutToChange(p, pIn1); - if( (pIn1->flags & MEM_Null)==0 ){ - sqlite3VdbeMemRealify(pIn1); - } - break; -} -#endif /* !defined(SQLITE_OMIT_CAST) && !defined(SQLITE_OMIT_FLOATING_POINT) */ - /* Opcode: Lt P1 P2 P3 P4 P5 +** Synopsis: if r[P1]<r[P3] goto P2 ** ** Compare the values in register P1 and P3. If reg(P3)<reg(P1) then ** jump to address P2. ** ** If the SQLITE_JUMPIFNULL bit of P5 is set and either reg(P1) or @@ -67774,10 +72450,11 @@ ** If the SQLITE_NULLEQ bit is set in P5, then NULL values are considered ** equal to one another, provided that they do not have their MEM_Cleared ** bit set. */ /* Opcode: Ne P1 P2 P3 P4 P5 +** Synopsis: if r[P1]!=r[P3] goto P2 ** ** This works just like the Lt opcode except that the jump is taken if ** the operands in registers P1 and P3 are not equal. See the Lt opcode for ** additional information. ** @@ -67786,10 +72463,11 @@ ** of comparison is false. If either operand is NULL then the result is true. ** If neither operand is NULL the result is the same as it would be if ** the SQLITE_NULLEQ flag were omitted from P5. */ /* Opcode: Eq P1 P2 P3 P4 P5 +** Synopsis: if r[P1]==r[P3] goto P2 ** ** This works just like the Lt opcode except that the jump is taken if ** the operands in registers P1 and P3 are equal. ** See the Lt opcode for additional information. ** @@ -67798,22 +72476,25 @@ ** of comparison is true. If either operand is NULL then the result is false. ** If neither operand is NULL the result is the same as it would be if ** the SQLITE_NULLEQ flag were omitted from P5. */ /* Opcode: Le P1 P2 P3 P4 P5 +** Synopsis: if r[P1]<=r[P3] goto P2 ** ** This works just like the Lt opcode except that the jump is taken if ** the content of register P3 is less than or equal to the content of ** register P1. See the Lt opcode for additional information. */ /* Opcode: Gt P1 P2 P3 P4 P5 +** Synopsis: if r[P1]>r[P3] goto P2 ** ** This works just like the Lt opcode except that the jump is taken if ** the content of register P3 is greater than the content of ** register P1. See the Lt opcode for additional information. */ /* Opcode: Ge P1 P2 P3 P4 P5 +** Synopsis: if r[P1]>=r[P3] goto P2 ** ** This works just like the Lt opcode except that the jump is taken if ** the content of register P3 is greater than or equal to the content of ** register P1. See the Lt opcode for additional information. */ @@ -67821,88 +72502,112 @@ case OP_Ne: /* same as TK_NE, jump, in1, in3 */ case OP_Lt: /* same as TK_LT, jump, in1, in3 */ case OP_Le: /* same as TK_LE, jump, in1, in3 */ case OP_Gt: /* same as TK_GT, jump, in1, in3 */ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ -#if 0 /* local variables moved into u.ak */ int res; /* Result of the comparison of pIn1 against pIn3 */ char affinity; /* Affinity to use for comparison */ u16 flags1; /* Copy of initial value of pIn1->flags */ u16 flags3; /* Copy of initial value of pIn3->flags */ -#endif /* local variables moved into u.ak */ pIn1 = &aMem[pOp->p1]; pIn3 = &aMem[pOp->p3]; - u.ak.flags1 = pIn1->flags; - u.ak.flags3 = pIn3->flags; - if( (u.ak.flags1 | u.ak.flags3)&MEM_Null ){ + flags1 = pIn1->flags; + flags3 = pIn3->flags; + if( (flags1 | flags3)&MEM_Null ){ /* One or both operands are NULL */ if( pOp->p5 & SQLITE_NULLEQ ){ /* If SQLITE_NULLEQ is set (which will only happen if the operator is ** OP_Eq or OP_Ne) then take the jump or not depending on whether ** or not both operands are null. */ assert( pOp->opcode==OP_Eq || pOp->opcode==OP_Ne ); - assert( (u.ak.flags1 & MEM_Cleared)==0 ); - if( (u.ak.flags1&MEM_Null)!=0 - && (u.ak.flags3&MEM_Null)!=0 - && (u.ak.flags3&MEM_Cleared)==0 + assert( (flags1 & MEM_Cleared)==0 ); + assert( (pOp->p5 & SQLITE_JUMPIFNULL)==0 ); + if( (flags1&MEM_Null)!=0 + && (flags3&MEM_Null)!=0 + && (flags3&MEM_Cleared)==0 ){ - u.ak.res = 0; /* Results are equal */ + res = 0; /* Results are equal */ }else{ - u.ak.res = 1; /* Results are not equal */ + res = 1; /* Results are not equal */ } }else{ /* SQLITE_NULLEQ is clear and at least one operand is NULL, ** then the result is always NULL. ** The jump is taken if the SQLITE_JUMPIFNULL bit is set. */ - if( pOp->p5 & SQLITE_JUMPIFNULL ){ - pc = pOp->p2-1; - }else if( pOp->p5 & SQLITE_STOREP2 ){ + if( pOp->p5 & SQLITE_STOREP2 ){ pOut = &aMem[pOp->p2]; MemSetTypeFlag(pOut, MEM_Null); REGISTER_TRACE(pOp->p2, pOut); + }else{ + VdbeBranchTaken(2,3); + if( pOp->p5 & SQLITE_JUMPIFNULL ){ + pc = pOp->p2-1; + } } break; } }else{ /* Neither operand is NULL. Do a comparison. */ - u.ak.affinity = pOp->p5 & SQLITE_AFF_MASK; - if( u.ak.affinity ){ - applyAffinity(pIn1, u.ak.affinity, encoding); - applyAffinity(pIn3, u.ak.affinity, encoding); - if( db->mallocFailed ) goto no_mem; + affinity = pOp->p5 & SQLITE_AFF_MASK; + if( affinity>=SQLITE_AFF_NUMERIC ){ + if( (pIn1->flags & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ + applyNumericAffinity(pIn1,0); + } + if( (pIn3->flags & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ + applyNumericAffinity(pIn3,0); + } + }else if( affinity==SQLITE_AFF_TEXT ){ + if( (pIn1->flags & MEM_Str)==0 && (pIn1->flags & (MEM_Int|MEM_Real))!=0 ){ + testcase( pIn1->flags & MEM_Int ); + testcase( pIn1->flags & MEM_Real ); + sqlite3VdbeMemStringify(pIn1, encoding, 1); + } + if( (pIn3->flags & MEM_Str)==0 && (pIn3->flags & (MEM_Int|MEM_Real))!=0 ){ + testcase( pIn3->flags & MEM_Int ); + testcase( pIn3->flags & MEM_Real ); + sqlite3VdbeMemStringify(pIn3, encoding, 1); + } } - assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 ); - ExpandBlob(pIn1); - ExpandBlob(pIn3); - u.ak.res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl); + if( pIn1->flags & MEM_Zero ){ + sqlite3VdbeMemExpandBlob(pIn1); + flags1 &= ~MEM_Zero; + } + if( pIn3->flags & MEM_Zero ){ + sqlite3VdbeMemExpandBlob(pIn3); + flags3 &= ~MEM_Zero; + } + if( db->mallocFailed ) goto no_mem; + res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl); } switch( pOp->opcode ){ - case OP_Eq: u.ak.res = u.ak.res==0; break; - case OP_Ne: u.ak.res = u.ak.res!=0; break; - case OP_Lt: u.ak.res = u.ak.res<0; break; - case OP_Le: u.ak.res = u.ak.res<=0; break; - case OP_Gt: u.ak.res = u.ak.res>0; break; - default: u.ak.res = u.ak.res>=0; break; + case OP_Eq: res = res==0; break; + case OP_Ne: res = res!=0; break; + case OP_Lt: res = res<0; break; + case OP_Le: res = res<=0; break; + case OP_Gt: res = res>0; break; + default: res = res>=0; break; } if( pOp->p5 & SQLITE_STOREP2 ){ pOut = &aMem[pOp->p2]; memAboutToChange(p, pOut); MemSetTypeFlag(pOut, MEM_Int); - pOut->u.i = u.ak.res; + pOut->u.i = res; REGISTER_TRACE(pOp->p2, pOut); - }else if( u.ak.res ){ - pc = pOp->p2-1; + }else{ + VdbeBranchTaken(res!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3); + if( res ){ + pc = pOp->p2-1; + } } - /* Undo any changes made by applyAffinity() to the input registers. */ - pIn1->flags = (pIn1->flags&~MEM_TypeMask) | (u.ak.flags1&MEM_TypeMask); - pIn3->flags = (pIn3->flags&~MEM_TypeMask) | (u.ak.flags3&MEM_TypeMask); + pIn1->flags = flags1; + pIn3->flags = flags3; break; } /* Opcode: Permutation * * * P4 * ** @@ -67919,10 +72624,11 @@ aPermute = pOp->p4.ai; break; } /* Opcode: Compare P1 P2 P3 P4 P5 +** Synopsis: r[P1@P3] <-> r[P2@P3] ** ** Compare two vectors of registers in reg(P1)..reg(P1+P3-1) (call this ** vector "A") and in reg(P2)..reg(P2+P3-1) ("B"). Save the result of ** the comparison for use by the next OP_Jump instruct. ** @@ -67938,51 +72644,49 @@ ** The comparison is a sort comparison, so NULLs compare equal, ** NULLs are less than numbers, numbers are less than strings, ** and strings are less than blobs. */ case OP_Compare: { -#if 0 /* local variables moved into u.al */ int n; int i; int p1; int p2; const KeyInfo *pKeyInfo; int idx; CollSeq *pColl; /* Collating sequence to use on this term */ int bRev; /* True for DESCENDING sort order */ -#endif /* local variables moved into u.al */ if( (pOp->p5 & OPFLAG_PERMUTE)==0 ) aPermute = 0; - u.al.n = pOp->p3; - u.al.pKeyInfo = pOp->p4.pKeyInfo; - assert( u.al.n>0 ); - assert( u.al.pKeyInfo!=0 ); - u.al.p1 = pOp->p1; - u.al.p2 = pOp->p2; + n = pOp->p3; + pKeyInfo = pOp->p4.pKeyInfo; + assert( n>0 ); + assert( pKeyInfo!=0 ); + p1 = pOp->p1; + p2 = pOp->p2; #if SQLITE_DEBUG if( aPermute ){ int k, mx = 0; - for(k=0; k<u.al.n; k++) if( aPermute[k]>mx ) mx = aPermute[k]; - assert( u.al.p1>0 && u.al.p1+mx<=(p->nMem-p->nCursor)+1 ); - assert( u.al.p2>0 && u.al.p2+mx<=(p->nMem-p->nCursor)+1 ); + for(k=0; k<n; k++) if( aPermute[k]>mx ) mx = aPermute[k]; + assert( p1>0 && p1+mx<=(p->nMem-p->nCursor)+1 ); + assert( p2>0 && p2+mx<=(p->nMem-p->nCursor)+1 ); }else{ - assert( u.al.p1>0 && u.al.p1+u.al.n<=(p->nMem-p->nCursor)+1 ); - assert( u.al.p2>0 && u.al.p2+u.al.n<=(p->nMem-p->nCursor)+1 ); + assert( p1>0 && p1+n<=(p->nMem-p->nCursor)+1 ); + assert( p2>0 && p2+n<=(p->nMem-p->nCursor)+1 ); } #endif /* SQLITE_DEBUG */ - for(u.al.i=0; u.al.i<u.al.n; u.al.i++){ - u.al.idx = aPermute ? aPermute[u.al.i] : u.al.i; - assert( memIsValid(&aMem[u.al.p1+u.al.idx]) ); - assert( memIsValid(&aMem[u.al.p2+u.al.idx]) ); - REGISTER_TRACE(u.al.p1+u.al.idx, &aMem[u.al.p1+u.al.idx]); - REGISTER_TRACE(u.al.p2+u.al.idx, &aMem[u.al.p2+u.al.idx]); - assert( u.al.i<u.al.pKeyInfo->nField ); - u.al.pColl = u.al.pKeyInfo->aColl[u.al.i]; - u.al.bRev = u.al.pKeyInfo->aSortOrder[u.al.i]; - iCompare = sqlite3MemCompare(&aMem[u.al.p1+u.al.idx], &aMem[u.al.p2+u.al.idx], u.al.pColl); + for(i=0; i<n; i++){ + idx = aPermute ? aPermute[i] : i; + assert( memIsValid(&aMem[p1+idx]) ); + assert( memIsValid(&aMem[p2+idx]) ); + REGISTER_TRACE(p1+idx, &aMem[p1+idx]); + REGISTER_TRACE(p2+idx, &aMem[p2+idx]); + assert( i<pKeyInfo->nField ); + pColl = pKeyInfo->aColl[i]; + bRev = pKeyInfo->aSortOrder[i]; + iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl); if( iCompare ){ - if( u.al.bRev ) iCompare = -iCompare; + if( bRev ) iCompare = -iCompare; break; } } aPermute = 0; break; @@ -67994,29 +72698,31 @@ ** in the most recent OP_Compare instruction the P1 vector was less than ** equal to, or greater than the P2 vector, respectively. */ case OP_Jump: { /* jump */ if( iCompare<0 ){ - pc = pOp->p1 - 1; + pc = pOp->p1 - 1; VdbeBranchTaken(0,3); }else if( iCompare==0 ){ - pc = pOp->p2 - 1; + pc = pOp->p2 - 1; VdbeBranchTaken(1,3); }else{ - pc = pOp->p3 - 1; + pc = pOp->p3 - 1; VdbeBranchTaken(2,3); } break; } /* Opcode: And P1 P2 P3 * * +** Synopsis: r[P3]=(r[P1] && r[P2]) ** ** Take the logical AND of the values in registers P1 and P2 and ** write the result into register P3. ** ** If either P1 or P2 is 0 (false) then the result is 0 even if ** the other input is NULL. A NULL and true or two NULLs give ** a NULL output. */ /* Opcode: Or P1 P2 P3 * * +** Synopsis: r[P3]=(r[P1] || r[P2]) ** ** Take the logical OR of the values in register P1 and P2 and ** store the answer in register P3. ** ** If either P1 or P2 is nonzero (true) then the result is 1 (true) @@ -68023,85 +72729,92 @@ ** even if the other input is NULL. A NULL and false or two NULLs ** give a NULL output. */ case OP_And: /* same as TK_AND, in1, in2, out3 */ case OP_Or: { /* same as TK_OR, in1, in2, out3 */ -#if 0 /* local variables moved into u.am */ int v1; /* Left operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */ int v2; /* Right operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */ -#endif /* local variables moved into u.am */ pIn1 = &aMem[pOp->p1]; if( pIn1->flags & MEM_Null ){ - u.am.v1 = 2; + v1 = 2; }else{ - u.am.v1 = sqlite3VdbeIntValue(pIn1)!=0; + v1 = sqlite3VdbeIntValue(pIn1)!=0; } pIn2 = &aMem[pOp->p2]; if( pIn2->flags & MEM_Null ){ - u.am.v2 = 2; + v2 = 2; }else{ - u.am.v2 = sqlite3VdbeIntValue(pIn2)!=0; + v2 = sqlite3VdbeIntValue(pIn2)!=0; } if( pOp->opcode==OP_And ){ static const unsigned char and_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 }; - u.am.v1 = and_logic[u.am.v1*3+u.am.v2]; + v1 = and_logic[v1*3+v2]; }else{ static const unsigned char or_logic[] = { 0, 1, 2, 1, 1, 1, 2, 1, 2 }; - u.am.v1 = or_logic[u.am.v1*3+u.am.v2]; + v1 = or_logic[v1*3+v2]; } pOut = &aMem[pOp->p3]; - if( u.am.v1==2 ){ + if( v1==2 ){ MemSetTypeFlag(pOut, MEM_Null); }else{ - pOut->u.i = u.am.v1; + pOut->u.i = v1; MemSetTypeFlag(pOut, MEM_Int); } break; } /* Opcode: Not P1 P2 * * * +** Synopsis: r[P2]= !r[P1] ** ** Interpret the value in register P1 as a boolean value. Store the ** boolean complement in register P2. If the value in register P1 is ** NULL, then a NULL is stored in P2. */ case OP_Not: { /* same as TK_NOT, in1, out2 */ pIn1 = &aMem[pOp->p1]; pOut = &aMem[pOp->p2]; - if( pIn1->flags & MEM_Null ){ - sqlite3VdbeMemSetNull(pOut); - }else{ - sqlite3VdbeMemSetInt64(pOut, !sqlite3VdbeIntValue(pIn1)); + sqlite3VdbeMemSetNull(pOut); + if( (pIn1->flags & MEM_Null)==0 ){ + pOut->flags = MEM_Int; + pOut->u.i = !sqlite3VdbeIntValue(pIn1); } break; } /* Opcode: BitNot P1 P2 * * * +** Synopsis: r[P1]= ~r[P1] ** ** Interpret the content of register P1 as an integer. Store the ** ones-complement of the P1 value into register P2. If P1 holds ** a NULL then store a NULL in P2. */ case OP_BitNot: { /* same as TK_BITNOT, in1, out2 */ pIn1 = &aMem[pOp->p1]; pOut = &aMem[pOp->p2]; - if( pIn1->flags & MEM_Null ){ - sqlite3VdbeMemSetNull(pOut); - }else{ - sqlite3VdbeMemSetInt64(pOut, ~sqlite3VdbeIntValue(pIn1)); + sqlite3VdbeMemSetNull(pOut); + if( (pIn1->flags & MEM_Null)==0 ){ + pOut->flags = MEM_Int; + pOut->u.i = ~sqlite3VdbeIntValue(pIn1); } break; } /* Opcode: Once P1 P2 * * * ** -** Check if OP_Once flag P1 is set. If so, jump to instruction P2. Otherwise, -** set the flag and fall through to the next instruction. +** Check the "once" flag number P1. If it is set, jump to instruction P2. +** Otherwise, set the flag and fall through to the next instruction. +** In other words, this opcode causes all following opcodes up through P2 +** (but not including P2) to run just once and to be skipped on subsequent +** times through the loop. +** +** All "once" flags are initially cleared whenever a prepared statement +** first begins to run. */ case OP_Once: { /* jump */ assert( pOp->p1<p->nOnceFlag ); + VdbeBranchTaken(p->aOnceFlag[pOp->p1]!=0, 2); if( p->aOnceFlag[pOp->p1] ){ pc = pOp->p2-1; }else{ p->aOnceFlag[pOp->p1] = 1; } @@ -68110,65 +72823,69 @@ /* Opcode: If P1 P2 P3 * * ** ** Jump to P2 if the value in register P1 is true. The value ** is considered true if it is numeric and non-zero. If the value -** in P1 is NULL then take the jump if P3 is non-zero. +** in P1 is NULL then take the jump if and only if P3 is non-zero. */ /* Opcode: IfNot P1 P2 P3 * * ** ** Jump to P2 if the value in register P1 is False. The value ** is considered false if it has a numeric value of zero. If the value -** in P1 is NULL then take the jump if P3 is zero. +** in P1 is NULL then take the jump if and only if P3 is non-zero. */ case OP_If: /* jump, in1 */ case OP_IfNot: { /* jump, in1 */ -#if 0 /* local variables moved into u.an */ int c; -#endif /* local variables moved into u.an */ pIn1 = &aMem[pOp->p1]; if( pIn1->flags & MEM_Null ){ - u.an.c = pOp->p3; + c = pOp->p3; }else{ #ifdef SQLITE_OMIT_FLOATING_POINT - u.an.c = sqlite3VdbeIntValue(pIn1)!=0; + c = sqlite3VdbeIntValue(pIn1)!=0; #else - u.an.c = sqlite3VdbeRealValue(pIn1)!=0.0; + c = sqlite3VdbeRealValue(pIn1)!=0.0; #endif - if( pOp->opcode==OP_IfNot ) u.an.c = !u.an.c; + if( pOp->opcode==OP_IfNot ) c = !c; } - if( u.an.c ){ + VdbeBranchTaken(c!=0, 2); + if( c ){ pc = pOp->p2-1; } break; } /* Opcode: IsNull P1 P2 * * * +** Synopsis: if r[P1]==NULL goto P2 ** ** Jump to P2 if the value in register P1 is NULL. */ case OP_IsNull: { /* same as TK_ISNULL, jump, in1 */ pIn1 = &aMem[pOp->p1]; + VdbeBranchTaken( (pIn1->flags & MEM_Null)!=0, 2); if( (pIn1->flags & MEM_Null)!=0 ){ pc = pOp->p2 - 1; } break; } /* Opcode: NotNull P1 P2 * * * +** Synopsis: if r[P1]!=NULL goto P2 ** ** Jump to P2 if the value in register P1 is not NULL. */ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */ pIn1 = &aMem[pOp->p1]; + VdbeBranchTaken( (pIn1->flags & MEM_Null)==0, 2); if( (pIn1->flags & MEM_Null)==0 ){ pc = pOp->p2 - 1; } break; } /* Opcode: Column P1 P2 P3 P4 P5 +** Synopsis: r[P3]=PX ** ** Interpret the data that cursor P1 points to as a structure built using ** the MakeRecord instruction. (See the MakeRecord opcode for additional ** information about the format of the data.) Extract the P2-th column ** from this record. If there are less that (P2+1) @@ -68189,159 +72906,93 @@ ** the result is guaranteed to only be used as the argument of a length() ** or typeof() function, respectively. The loading of large blobs can be ** skipped for length() and all content loading can be skipped for typeof(). */ case OP_Column: { -#if 0 /* local variables moved into u.ao */ - u32 payloadSize; /* Number of bytes in the record */ i64 payloadSize64; /* Number of bytes in the record */ - int p1; /* P1 value of the opcode */ int p2; /* column number to retrieve */ VdbeCursor *pC; /* The VDBE cursor */ - char *zRec; /* Pointer to complete record-data */ BtCursor *pCrsr; /* The BTree cursor */ - u32 *aType; /* aType[i] holds the numeric type of the i-th column */ u32 *aOffset; /* aOffset[i] is offset to start of data for i-th column */ - int nField; /* number of fields in the record */ int len; /* The length of the serialized data for the column */ int i; /* Loop counter */ - char *zData; /* Part of the record being decoded */ Mem *pDest; /* Where to write the extracted value */ Mem sMem; /* For storing the record being decoded */ - u8 *zIdx; /* Index into header */ - u8 *zEndHdr; /* Pointer to first byte after the header */ + const u8 *zData; /* Part of the record being decoded */ + const u8 *zHdr; /* Next unparsed byte of the header */ + const u8 *zEndHdr; /* Pointer to first byte after the header */ u32 offset; /* Offset into the data */ u32 szField; /* Number of bytes in the content of a field */ - int szHdr; /* Size of the header size field at start of record */ - int avail; /* Number of bytes of available data */ + u32 avail; /* Number of bytes of available data */ u32 t; /* A type code from the record header */ + u16 fx; /* pDest->flags value */ Mem *pReg; /* PseudoTable input register */ -#endif /* local variables moved into u.ao */ - - - u.ao.p1 = pOp->p1; - u.ao.p2 = pOp->p2; - u.ao.pC = 0; - memset(&u.ao.sMem, 0, sizeof(u.ao.sMem)); - assert( u.ao.p1<p->nCursor ); + + p2 = pOp->p2; assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); - u.ao.pDest = &aMem[pOp->p3]; - memAboutToChange(p, u.ao.pDest); - u.ao.zRec = 0; - - /* This block sets the variable u.ao.payloadSize to be the total number of - ** bytes in the record. - ** - ** u.ao.zRec is set to be the complete text of the record if it is available. - ** The complete record text is always available for pseudo-tables - ** If the record is stored in a cursor, the complete record text - ** might be available in the u.ao.pC->aRow cache. Or it might not be. - ** If the data is unavailable, u.ao.zRec is set to NULL. - ** - ** We also compute the number of columns in the record. For cursors, - ** the number of columns is stored in the VdbeCursor.nField element. - */ - u.ao.pC = p->apCsr[u.ao.p1]; - assert( u.ao.pC!=0 ); + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( p2<pC->nField ); + aOffset = pC->aOffset; #ifndef SQLITE_OMIT_VIRTUALTABLE - assert( u.ao.pC->pVtabCursor==0 ); -#endif - u.ao.pCrsr = u.ao.pC->pCursor; - if( u.ao.pCrsr!=0 ){ - /* The record is stored in a B-Tree */ - rc = sqlite3VdbeCursorMoveto(u.ao.pC); - if( rc ) goto abort_due_to_error; - if( u.ao.pC->nullRow ){ - u.ao.payloadSize = 0; - }else if( u.ao.pC->cacheStatus==p->cacheCtr ){ - u.ao.payloadSize = u.ao.pC->payloadSize; - u.ao.zRec = (char*)u.ao.pC->aRow; - }else if( u.ao.pC->isIndex ){ - assert( sqlite3BtreeCursorIsValid(u.ao.pCrsr) ); - VVA_ONLY(rc =) sqlite3BtreeKeySize(u.ao.pCrsr, &u.ao.payloadSize64); - assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */ - /* sqlite3BtreeParseCellPtr() uses getVarint32() to extract the - ** payload size, so it is impossible for u.ao.payloadSize64 to be - ** larger than 32 bits. */ - assert( (u.ao.payloadSize64 & SQLITE_MAX_U32)==(u64)u.ao.payloadSize64 ); - u.ao.payloadSize = (u32)u.ao.payloadSize64; - }else{ - assert( sqlite3BtreeCursorIsValid(u.ao.pCrsr) ); - VVA_ONLY(rc =) sqlite3BtreeDataSize(u.ao.pCrsr, &u.ao.payloadSize); - assert( rc==SQLITE_OK ); /* DataSize() cannot fail */ - } - }else if( ALWAYS(u.ao.pC->pseudoTableReg>0) ){ - u.ao.pReg = &aMem[u.ao.pC->pseudoTableReg]; - if( u.ao.pC->multiPseudo ){ - sqlite3VdbeMemShallowCopy(u.ao.pDest, u.ao.pReg+u.ao.p2, MEM_Ephem); - Deephemeralize(u.ao.pDest); - goto op_column_out; - } - assert( u.ao.pReg->flags & MEM_Blob ); - assert( memIsValid(u.ao.pReg) ); - u.ao.payloadSize = u.ao.pReg->n; - u.ao.zRec = u.ao.pReg->z; - u.ao.pC->cacheStatus = (pOp->p5&OPFLAG_CLEARCACHE) ? CACHE_STALE : p->cacheCtr; - assert( u.ao.payloadSize==0 || u.ao.zRec!=0 ); - }else{ - /* Consider the row to be NULL */ - u.ao.payloadSize = 0; - } - - /* If u.ao.payloadSize is 0, then just store a NULL. This can happen because of - ** nullRow or because of a corrupt database. */ - if( u.ao.payloadSize==0 ){ - MemSetTypeFlag(u.ao.pDest, MEM_Null); - goto op_column_out; - } - assert( db->aLimit[SQLITE_LIMIT_LENGTH]>=0 ); - if( u.ao.payloadSize > (u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ - goto too_big; - } - - u.ao.nField = u.ao.pC->nField; - assert( u.ao.p2<u.ao.nField ); - - /* Read and parse the table header. Store the results of the parse - ** into the record header cache fields of the cursor. - */ - u.ao.aType = u.ao.pC->aType; - if( u.ao.pC->cacheStatus==p->cacheCtr ){ - u.ao.aOffset = u.ao.pC->aOffset; - }else{ - assert(u.ao.aType); - u.ao.avail = 0; - u.ao.pC->aOffset = u.ao.aOffset = &u.ao.aType[u.ao.nField]; - u.ao.pC->payloadSize = u.ao.payloadSize; - u.ao.pC->cacheStatus = p->cacheCtr; - - /* Figure out how many bytes are in the header */ - if( u.ao.zRec ){ - u.ao.zData = u.ao.zRec; - }else{ - if( u.ao.pC->isIndex ){ - u.ao.zData = (char*)sqlite3BtreeKeyFetch(u.ao.pCrsr, &u.ao.avail); - }else{ - u.ao.zData = (char*)sqlite3BtreeDataFetch(u.ao.pCrsr, &u.ao.avail); - } - /* If KeyFetch()/DataFetch() managed to get the entire payload, - ** save the payload in the u.ao.pC->aRow cache. That will save us from - ** having to make additional calls to fetch the content portion of - ** the record. - */ - assert( u.ao.avail>=0 ); - if( u.ao.payloadSize <= (u32)u.ao.avail ){ - u.ao.zRec = u.ao.zData; - u.ao.pC->aRow = (u8*)u.ao.zData; - }else{ - u.ao.pC->aRow = 0; - } - } - /* The following assert is true in all cases except when - ** the database file has been corrupted externally. - ** assert( u.ao.zRec!=0 || u.ao.avail>=u.ao.payloadSize || u.ao.avail>=9 ); */ - u.ao.szHdr = getVarint32((u8*)u.ao.zData, u.ao.offset); + assert( pC->pVtabCursor==0 ); /* OP_Column never called on virtual table */ +#endif + pCrsr = pC->pCursor; + assert( pCrsr!=0 || pC->pseudoTableReg>0 ); /* pCrsr NULL on PseudoTables */ + assert( pCrsr!=0 || pC->nullRow ); /* pC->nullRow on PseudoTables */ + + /* If the cursor cache is stale, bring it up-to-date */ + rc = sqlite3VdbeCursorMoveto(pC); + if( rc ) goto abort_due_to_error; + if( pC->cacheStatus!=p->cacheCtr ){ + if( pC->nullRow ){ + if( pCrsr==0 ){ + assert( pC->pseudoTableReg>0 ); + pReg = &aMem[pC->pseudoTableReg]; + assert( pReg->flags & MEM_Blob ); + assert( memIsValid(pReg) ); + pC->payloadSize = pC->szRow = avail = pReg->n; + pC->aRow = (u8*)pReg->z; + }else{ + sqlite3VdbeMemSetNull(pDest); + goto op_column_out; + } + }else{ + assert( pCrsr ); + if( pC->isTable==0 ){ + assert( sqlite3BtreeCursorIsValid(pCrsr) ); + VVA_ONLY(rc =) sqlite3BtreeKeySize(pCrsr, &payloadSize64); + assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */ + /* sqlite3BtreeParseCellPtr() uses getVarint32() to extract the + ** payload size, so it is impossible for payloadSize64 to be + ** larger than 32 bits. */ + assert( (payloadSize64 & SQLITE_MAX_U32)==(u64)payloadSize64 ); + pC->aRow = sqlite3BtreeKeyFetch(pCrsr, &avail); + pC->payloadSize = (u32)payloadSize64; + }else{ + assert( sqlite3BtreeCursorIsValid(pCrsr) ); + VVA_ONLY(rc =) sqlite3BtreeDataSize(pCrsr, &pC->payloadSize); + assert( rc==SQLITE_OK ); /* DataSize() cannot fail */ + pC->aRow = sqlite3BtreeDataFetch(pCrsr, &avail); + } + assert( avail<=65536 ); /* Maximum page size is 64KiB */ + if( pC->payloadSize <= (u32)avail ){ + pC->szRow = pC->payloadSize; + }else{ + pC->szRow = avail; + } + if( pC->payloadSize > (u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ + goto too_big; + } + } + pC->cacheStatus = p->cacheCtr; + pC->iHdrOffset = getVarint32(pC->aRow, offset); + pC->nHdrParsed = 0; + aOffset[0] = offset; /* Make sure a corrupt database has not given us an oversize header. ** Do this now to avoid an oversize memory allocation. ** ** Type entries can be between 1 and 5 bytes each. But 4 and 5 byte @@ -68348,193 +72999,204 @@ ** types use so much data space that there can only be 4096 and 32 of ** them, respectively. So the maximum header length results from a ** 3-byte type for each of the maximum of 32768 columns plus three ** extra bytes for the header length itself. 32768*3 + 3 = 98307. */ - if( u.ao.offset > 98307 ){ - rc = SQLITE_CORRUPT_BKPT; - goto op_column_out; - } - - /* Compute in u.ao.len the number of bytes of data we need to read in order - ** to get u.ao.nField type values. u.ao.offset is an upper bound on this. But - ** u.ao.nField might be significantly less than the true number of columns - ** in the table, and in that case, 5*u.ao.nField+3 might be smaller than u.ao.offset. - ** We want to minimize u.ao.len in order to limit the size of the memory - ** allocation, especially if a corrupt database file has caused u.ao.offset - ** to be oversized. Offset is limited to 98307 above. But 98307 might - ** still exceed Robson memory allocation limits on some configurations. - ** On systems that cannot tolerate large memory allocations, u.ao.nField*5+3 - ** will likely be much smaller since u.ao.nField will likely be less than - ** 20 or so. This insures that Robson memory allocation limits are - ** not exceeded even for corrupt database files. - */ - u.ao.len = u.ao.nField*5 + 3; - if( u.ao.len > (int)u.ao.offset ) u.ao.len = (int)u.ao.offset; - - /* The KeyFetch() or DataFetch() above are fast and will get the entire - ** record header in most cases. But they will fail to get the complete - ** record header if the record header does not fit on a single page - ** in the B-Tree. When that happens, use sqlite3VdbeMemFromBtree() to - ** acquire the complete header text. - */ - if( !u.ao.zRec && u.ao.avail<u.ao.len ){ - u.ao.sMem.flags = 0; - u.ao.sMem.db = 0; - rc = sqlite3VdbeMemFromBtree(u.ao.pCrsr, 0, u.ao.len, u.ao.pC->isIndex, &u.ao.sMem); - if( rc!=SQLITE_OK ){ - goto op_column_out; - } - u.ao.zData = u.ao.sMem.z; - } - u.ao.zEndHdr = (u8 *)&u.ao.zData[u.ao.len]; - u.ao.zIdx = (u8 *)&u.ao.zData[u.ao.szHdr]; - - /* Scan the header and use it to fill in the u.ao.aType[] and u.ao.aOffset[] - ** arrays. u.ao.aType[u.ao.i] will contain the type integer for the u.ao.i-th - ** column and u.ao.aOffset[u.ao.i] will contain the u.ao.offset from the beginning - ** of the record to the start of the data for the u.ao.i-th column - */ - for(u.ao.i=0; u.ao.i<u.ao.nField; u.ao.i++){ - if( u.ao.zIdx<u.ao.zEndHdr ){ - u.ao.aOffset[u.ao.i] = u.ao.offset; - if( u.ao.zIdx[0]<0x80 ){ - u.ao.t = u.ao.zIdx[0]; - u.ao.zIdx++; - }else{ - u.ao.zIdx += sqlite3GetVarint32(u.ao.zIdx, &u.ao.t); - } - u.ao.aType[u.ao.i] = u.ao.t; - u.ao.szField = sqlite3VdbeSerialTypeLen(u.ao.t); - u.ao.offset += u.ao.szField; - if( u.ao.offset<u.ao.szField ){ /* True if u.ao.offset overflows */ - u.ao.zIdx = &u.ao.zEndHdr[1]; /* Forces SQLITE_CORRUPT return below */ - break; - } - }else{ - /* If u.ao.i is less that u.ao.nField, then there are fewer fields in this - ** record than SetNumColumns indicated there are columns in the - ** table. Set the u.ao.offset for any extra columns not present in - ** the record to 0. This tells code below to store the default value - ** for the column instead of deserializing a value from the record. - */ - u.ao.aOffset[u.ao.i] = 0; - } - } - sqlite3VdbeMemRelease(&u.ao.sMem); - u.ao.sMem.flags = MEM_Null; - - /* If we have read more header data than was contained in the header, - ** or if the end of the last field appears to be past the end of the - ** record, or if the end of the last field appears to be before the end - ** of the record (when all fields present), then we must be dealing - ** with a corrupt database. - */ - if( (u.ao.zIdx > u.ao.zEndHdr) || (u.ao.offset > u.ao.payloadSize) - || (u.ao.zIdx==u.ao.zEndHdr && u.ao.offset!=u.ao.payloadSize) ){ - rc = SQLITE_CORRUPT_BKPT; - goto op_column_out; - } - } - - /* Get the column information. If u.ao.aOffset[u.ao.p2] is non-zero, then - ** deserialize the value from the record. If u.ao.aOffset[u.ao.p2] is zero, - ** then there are not enough fields in the record to satisfy the - ** request. In this case, set the value NULL or to P4 if P4 is - ** a pointer to a Mem object. - */ - if( u.ao.aOffset[u.ao.p2] ){ - assert( rc==SQLITE_OK ); - if( u.ao.zRec ){ - /* This is the common case where the whole row fits on a single page */ - VdbeMemRelease(u.ao.pDest); - sqlite3VdbeSerialGet((u8 *)&u.ao.zRec[u.ao.aOffset[u.ao.p2]], u.ao.aType[u.ao.p2], u.ao.pDest); - }else{ - /* This branch happens only when the row overflows onto multiple pages */ - u.ao.t = u.ao.aType[u.ao.p2]; - if( (pOp->p5 & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG))!=0 - && ((u.ao.t>=12 && (u.ao.t&1)==0) || (pOp->p5 & OPFLAG_TYPEOFARG)!=0) - ){ - /* Content is irrelevant for the typeof() function and for - ** the length(X) function if X is a blob. So we might as well use - ** bogus content rather than reading content from disk. NULL works - ** for text and blob and whatever is in the u.ao.payloadSize64 variable - ** will work for everything else. */ - u.ao.zData = u.ao.t<12 ? (char*)&u.ao.payloadSize64 : 0; - }else{ - u.ao.len = sqlite3VdbeSerialTypeLen(u.ao.t); - sqlite3VdbeMemMove(&u.ao.sMem, u.ao.pDest); - rc = sqlite3VdbeMemFromBtree(u.ao.pCrsr, u.ao.aOffset[u.ao.p2], u.ao.len, u.ao.pC->isIndex, - &u.ao.sMem); - if( rc!=SQLITE_OK ){ - goto op_column_out; - } - u.ao.zData = u.ao.sMem.z; - } - sqlite3VdbeSerialGet((u8*)u.ao.zData, u.ao.t, u.ao.pDest); - } - u.ao.pDest->enc = encoding; - }else{ - if( pOp->p4type==P4_MEM ){ - sqlite3VdbeMemShallowCopy(u.ao.pDest, pOp->p4.pMem, MEM_Static); - }else{ - MemSetTypeFlag(u.ao.pDest, MEM_Null); - } - } - - /* If we dynamically allocated space to hold the data (in the - ** sqlite3VdbeMemFromBtree() call above) then transfer control of that - ** dynamically allocated space over to the u.ao.pDest structure. - ** This prevents a memory copy. - */ - if( u.ao.sMem.zMalloc ){ - assert( u.ao.sMem.z==u.ao.sMem.zMalloc ); - assert( !(u.ao.pDest->flags & MEM_Dyn) ); - assert( !(u.ao.pDest->flags & (MEM_Blob|MEM_Str)) || u.ao.pDest->z==u.ao.sMem.z ); - u.ao.pDest->flags &= ~(MEM_Ephem|MEM_Static); - u.ao.pDest->flags |= MEM_Term; - u.ao.pDest->z = u.ao.sMem.z; - u.ao.pDest->zMalloc = u.ao.sMem.zMalloc; - } - - rc = sqlite3VdbeMemMakeWriteable(u.ao.pDest); - -op_column_out: - UPDATE_MAX_BLOBSIZE(u.ao.pDest); - REGISTER_TRACE(pOp->p3, u.ao.pDest); - break; -} - -/* Opcode: Affinity P1 P2 * P4 * + if( offset > 98307 || offset > pC->payloadSize ){ + rc = SQLITE_CORRUPT_BKPT; + goto op_column_error; + } + + if( avail<offset ){ + /* pC->aRow does not have to hold the entire row, but it does at least + ** need to cover the header of the record. If pC->aRow does not contain + ** the complete header, then set it to zero, forcing the header to be + ** dynamically allocated. */ + pC->aRow = 0; + pC->szRow = 0; + } + + /* The following goto is an optimization. It can be omitted and + ** everything will still work. But OP_Column is measurably faster + ** by skipping the subsequent conditional, which is always true. + */ + assert( pC->nHdrParsed<=p2 ); /* Conditional skipped */ + goto op_column_read_header; + } + + /* Make sure at least the first p2+1 entries of the header have been + ** parsed and valid information is in aOffset[] and pC->aType[]. + */ + if( pC->nHdrParsed<=p2 ){ + /* If there is more header available for parsing in the record, try + ** to extract additional fields up through the p2+1-th field + */ + op_column_read_header: + if( pC->iHdrOffset<aOffset[0] ){ + /* Make sure zData points to enough of the record to cover the header. */ + if( pC->aRow==0 ){ + memset(&sMem, 0, sizeof(sMem)); + rc = sqlite3VdbeMemFromBtree(pCrsr, 0, aOffset[0], + !pC->isTable, &sMem); + if( rc!=SQLITE_OK ){ + goto op_column_error; + } + zData = (u8*)sMem.z; + }else{ + zData = pC->aRow; + } + + /* Fill in pC->aType[i] and aOffset[i] values through the p2-th field. */ + i = pC->nHdrParsed; + offset = aOffset[i]; + zHdr = zData + pC->iHdrOffset; + zEndHdr = zData + aOffset[0]; + assert( i<=p2 && zHdr<zEndHdr ); + do{ + if( zHdr[0]<0x80 ){ + t = zHdr[0]; + zHdr++; + }else{ + zHdr += sqlite3GetVarint32(zHdr, &t); + } + pC->aType[i] = t; + szField = sqlite3VdbeSerialTypeLen(t); + offset += szField; + if( offset<szField ){ /* True if offset overflows */ + zHdr = &zEndHdr[1]; /* Forces SQLITE_CORRUPT return below */ + break; + } + i++; + aOffset[i] = offset; + }while( i<=p2 && zHdr<zEndHdr ); + pC->nHdrParsed = i; + pC->iHdrOffset = (u32)(zHdr - zData); + if( pC->aRow==0 ){ + sqlite3VdbeMemRelease(&sMem); + sMem.flags = MEM_Null; + } + + /* The record is corrupt if any of the following are true: + ** (1) the bytes of the header extend past the declared header size + ** (zHdr>zEndHdr) + ** (2) the entire header was used but not all data was used + ** (zHdr==zEndHdr && offset!=pC->payloadSize) + ** (3) the end of the data extends beyond the end of the record. + ** (offset > pC->payloadSize) + */ + if( (zHdr>=zEndHdr && (zHdr>zEndHdr || offset!=pC->payloadSize)) + || (offset > pC->payloadSize) + ){ + rc = SQLITE_CORRUPT_BKPT; + goto op_column_error; + } + } + + /* If after trying to extra new entries from the header, nHdrParsed is + ** still not up to p2, that means that the record has fewer than p2 + ** columns. So the result will be either the default value or a NULL. + */ + if( pC->nHdrParsed<=p2 ){ + if( pOp->p4type==P4_MEM ){ + sqlite3VdbeMemShallowCopy(pDest, pOp->p4.pMem, MEM_Static); + }else{ + sqlite3VdbeMemSetNull(pDest); + } + goto op_column_out; + } + } + + /* Extract the content for the p2+1-th column. Control can only + ** reach this point if aOffset[p2], aOffset[p2+1], and pC->aType[p2] are + ** all valid. + */ + assert( p2<pC->nHdrParsed ); + assert( rc==SQLITE_OK ); + assert( sqlite3VdbeCheckMemInvariants(pDest) ); + if( VdbeMemDynamic(pDest) ) sqlite3VdbeMemSetNull(pDest); + t = pC->aType[p2]; + if( pC->szRow>=aOffset[p2+1] ){ + /* This is the common case where the desired content fits on the original + ** page - where the content is not on an overflow page */ + sqlite3VdbeSerialGet(pC->aRow+aOffset[p2], t, pDest); + }else{ + /* This branch happens only when content is on overflow pages */ + if( ((pOp->p5 & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG))!=0 + && ((t>=12 && (t&1)==0) || (pOp->p5 & OPFLAG_TYPEOFARG)!=0)) + || (len = sqlite3VdbeSerialTypeLen(t))==0 + ){ + /* Content is irrelevant for + ** 1. the typeof() function, + ** 2. the length(X) function if X is a blob, and + ** 3. if the content length is zero. + ** So we might as well use bogus content rather than reading + ** content from disk. NULL will work for the value for strings + ** and blobs and whatever is in the payloadSize64 variable + ** will work for everything else. */ + sqlite3VdbeSerialGet(t<=13 ? (u8*)&payloadSize64 : 0, t, pDest); + }else{ + rc = sqlite3VdbeMemFromBtree(pCrsr, aOffset[p2], len, !pC->isTable, + pDest); + if( rc!=SQLITE_OK ){ + goto op_column_error; + } + sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); + pDest->flags &= ~MEM_Ephem; + } + } + pDest->enc = encoding; + +op_column_out: + /* If the column value is an ephemeral string, go ahead and persist + ** that string in case the cursor moves before the column value is + ** used. The following code does the equivalent of Deephemeralize() + ** but does it faster. */ + if( (pDest->flags & MEM_Ephem)!=0 && pDest->z ){ + fx = pDest->flags & (MEM_Str|MEM_Blob); + assert( fx!=0 ); + zData = (const u8*)pDest->z; + len = pDest->n; + if( sqlite3VdbeMemClearAndResize(pDest, len+2) ) goto no_mem; + memcpy(pDest->z, zData, len); + pDest->z[len] = 0; + pDest->z[len+1] = 0; + pDest->flags = fx|MEM_Term; + } +op_column_error: + UPDATE_MAX_BLOBSIZE(pDest); + REGISTER_TRACE(pOp->p3, pDest); + break; +} + +/* Opcode: Affinity P1 P2 * P4 * +** Synopsis: affinity(r[P1@P2]) ** ** Apply affinities to a range of P2 registers starting with P1. ** ** P4 is a string that is P2 characters long. The nth character of the ** string indicates the column affinity that should be used for the nth ** memory cell in the range. */ case OP_Affinity: { -#if 0 /* local variables moved into u.ap */ const char *zAffinity; /* The affinity to be applied */ char cAff; /* A single character of affinity */ -#endif /* local variables moved into u.ap */ - u.ap.zAffinity = pOp->p4.z; - assert( u.ap.zAffinity!=0 ); - assert( u.ap.zAffinity[pOp->p2]==0 ); + zAffinity = pOp->p4.z; + assert( zAffinity!=0 ); + assert( zAffinity[pOp->p2]==0 ); pIn1 = &aMem[pOp->p1]; - while( (u.ap.cAff = *(u.ap.zAffinity++))!=0 ){ + while( (cAff = *(zAffinity++))!=0 ){ assert( pIn1 <= &p->aMem[(p->nMem-p->nCursor)] ); assert( memIsValid(pIn1) ); - ExpandBlob(pIn1); - applyAffinity(pIn1, u.ap.cAff, encoding); + applyAffinity(pIn1, cAff, encoding); pIn1++; } break; } /* Opcode: MakeRecord P1 P2 P3 P4 * +** Synopsis: r[P3]=mkrec(r[P1@P2]) ** ** Convert P2 registers beginning with P1 into the [record format] ** use as a data record in a database table or as a key ** in an index. The OP_Column opcode can decode the record later. ** @@ -68546,11 +73208,10 @@ ** macros defined in sqliteInt.h. ** ** If P4 is NULL then all index fields have the affinity NONE. */ case OP_MakeRecord: { -#if 0 /* local variables moved into u.aq */ u8 *zNewRecord; /* A buffer to hold the data for the new record */ Mem *pRec; /* The new record */ u64 nData; /* Number of bytes of data space */ int nHdr; /* Number of bytes of header space */ i64 nByte; /* Data space required for this record */ @@ -68560,133 +73221,154 @@ Mem *pData0; /* First field to be combined into the record */ Mem *pLast; /* Last field of the record */ int nField; /* Number of fields in the record */ char *zAffinity; /* The affinity string for the record */ int file_format; /* File format to use for encoding */ - int i; /* Space used in zNewRecord[] */ + int i; /* Space used in zNewRecord[] header */ + int j; /* Space used in zNewRecord[] content */ int len; /* Length of a field */ -#endif /* local variables moved into u.aq */ /* Assuming the record contains N fields, the record format looks ** like this: ** ** ------------------------------------------------------------------------ - ** | hdr-size | type 0 | type 1 | ... | type N-1 | data0 | ... | data N-1 | + ** | hdr-size | type 0 | type 1 | ... | type N-1 | data0 | ... | data N-1 | ** ------------------------------------------------------------------------ ** ** Data(0) is taken from register P1. Data(1) comes from register P1+1 - ** and so froth. + ** and so forth. ** - ** Each type field is a varint representing the serial type of the + ** Each type field is a varint representing the serial type of the ** corresponding data element (see sqlite3VdbeSerialType()). The ** hdr-size field is also a varint which is the offset from the beginning ** of the record to data0. */ - u.aq.nData = 0; /* Number of bytes of data space */ - u.aq.nHdr = 0; /* Number of bytes of header space */ - u.aq.nZero = 0; /* Number of zero bytes at the end of the record */ - u.aq.nField = pOp->p1; - u.aq.zAffinity = pOp->p4.z; - assert( u.aq.nField>0 && pOp->p2>0 && pOp->p2+u.aq.nField<=(p->nMem-p->nCursor)+1 ); - u.aq.pData0 = &aMem[u.aq.nField]; - u.aq.nField = pOp->p2; - u.aq.pLast = &u.aq.pData0[u.aq.nField-1]; - u.aq.file_format = p->minWriteFileFormat; + nData = 0; /* Number of bytes of data space */ + nHdr = 0; /* Number of bytes of header space */ + nZero = 0; /* Number of zero bytes at the end of the record */ + nField = pOp->p1; + zAffinity = pOp->p4.z; + assert( nField>0 && pOp->p2>0 && pOp->p2+nField<=(p->nMem-p->nCursor)+1 ); + pData0 = &aMem[nField]; + nField = pOp->p2; + pLast = &pData0[nField-1]; + file_format = p->minWriteFileFormat; /* Identify the output register */ assert( pOp->p3<pOp->p1 || pOp->p3>=pOp->p1+pOp->p2 ); pOut = &aMem[pOp->p3]; memAboutToChange(p, pOut); + + /* Apply the requested affinity to all inputs + */ + assert( pData0<=pLast ); + if( zAffinity ){ + pRec = pData0; + do{ + applyAffinity(pRec++, *(zAffinity++), encoding); + assert( zAffinity[0]==0 || pRec<=pLast ); + }while( zAffinity[0] ); + } /* Loop through the elements that will make up the record to figure ** out how much space is required for the new record. */ - for(u.aq.pRec=u.aq.pData0; u.aq.pRec<=u.aq.pLast; u.aq.pRec++){ - assert( memIsValid(u.aq.pRec) ); - if( u.aq.zAffinity ){ - applyAffinity(u.aq.pRec, u.aq.zAffinity[u.aq.pRec-u.aq.pData0], encoding); - } - if( u.aq.pRec->flags&MEM_Zero && u.aq.pRec->n>0 ){ - sqlite3VdbeMemExpandBlob(u.aq.pRec); - } - u.aq.serial_type = sqlite3VdbeSerialType(u.aq.pRec, u.aq.file_format); - u.aq.len = sqlite3VdbeSerialTypeLen(u.aq.serial_type); - u.aq.nData += u.aq.len; - u.aq.nHdr += sqlite3VarintLen(u.aq.serial_type); - if( u.aq.pRec->flags & MEM_Zero ){ - /* Only pure zero-filled BLOBs can be input to this Opcode. - ** We do not allow blobs with a prefix and a zero-filled tail. */ - u.aq.nZero += u.aq.pRec->u.nZero; - }else if( u.aq.len ){ - u.aq.nZero = 0; - } - } - - /* Add the initial header varint and total the size */ - u.aq.nHdr += u.aq.nVarint = sqlite3VarintLen(u.aq.nHdr); - if( u.aq.nVarint<sqlite3VarintLen(u.aq.nHdr) ){ - u.aq.nHdr++; - } - u.aq.nByte = u.aq.nHdr+u.aq.nData-u.aq.nZero; - if( u.aq.nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ + pRec = pLast; + do{ + assert( memIsValid(pRec) ); + pRec->uTemp = serial_type = sqlite3VdbeSerialType(pRec, file_format); + len = sqlite3VdbeSerialTypeLen(serial_type); + if( pRec->flags & MEM_Zero ){ + if( nData ){ + sqlite3VdbeMemExpandBlob(pRec); + }else{ + nZero += pRec->u.nZero; + len -= pRec->u.nZero; + } + } + nData += len; + testcase( serial_type==127 ); + testcase( serial_type==128 ); + nHdr += serial_type<=127 ? 1 : sqlite3VarintLen(serial_type); + }while( (--pRec)>=pData0 ); + + /* EVIDENCE-OF: R-22564-11647 The header begins with a single varint + ** which determines the total number of bytes in the header. The varint + ** value is the size of the header in bytes including the size varint + ** itself. */ + testcase( nHdr==126 ); + testcase( nHdr==127 ); + if( nHdr<=126 ){ + /* The common case */ + nHdr += 1; + }else{ + /* Rare case of a really large header */ + nVarint = sqlite3VarintLen(nHdr); + nHdr += nVarint; + if( nVarint<sqlite3VarintLen(nHdr) ) nHdr++; + } + nByte = nHdr+nData; + if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } - /* Make sure the output register has a buffer large enough to store + /* Make sure the output register has a buffer large enough to store ** the new record. The output register (pOp->p3) is not allowed to ** be one of the input registers (because the following call to - ** sqlite3VdbeMemGrow() could clobber the value before it is used). + ** sqlite3VdbeMemClearAndResize() could clobber the value before it is used). */ - if( sqlite3VdbeMemGrow(pOut, (int)u.aq.nByte, 0) ){ + if( sqlite3VdbeMemClearAndResize(pOut, (int)nByte) ){ goto no_mem; } - u.aq.zNewRecord = (u8 *)pOut->z; + zNewRecord = (u8 *)pOut->z; /* Write the record */ - u.aq.i = putVarint32(u.aq.zNewRecord, u.aq.nHdr); - for(u.aq.pRec=u.aq.pData0; u.aq.pRec<=u.aq.pLast; u.aq.pRec++){ - u.aq.serial_type = sqlite3VdbeSerialType(u.aq.pRec, u.aq.file_format); - u.aq.i += putVarint32(&u.aq.zNewRecord[u.aq.i], u.aq.serial_type); /* serial type */ - } - for(u.aq.pRec=u.aq.pData0; u.aq.pRec<=u.aq.pLast; u.aq.pRec++){ /* serial data */ - u.aq.i += sqlite3VdbeSerialPut(&u.aq.zNewRecord[u.aq.i], (int)(u.aq.nByte-u.aq.i), u.aq.pRec,u.aq.file_format); - } - assert( u.aq.i==u.aq.nByte ); + i = putVarint32(zNewRecord, nHdr); + j = nHdr; + assert( pData0<=pLast ); + pRec = pData0; + do{ + serial_type = pRec->uTemp; + /* EVIDENCE-OF: R-06529-47362 Following the size varint are one or more + ** additional varints, one per column. */ + i += putVarint32(&zNewRecord[i], serial_type); /* serial type */ + /* EVIDENCE-OF: R-64536-51728 The values for each column in the record + ** immediately follow the header. */ + j += sqlite3VdbeSerialPut(&zNewRecord[j], pRec, serial_type); /* content */ + }while( (++pRec)<=pLast ); + assert( i==nHdr ); + assert( j==nByte ); assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); - pOut->n = (int)u.aq.nByte; - pOut->flags = MEM_Blob | MEM_Dyn; - pOut->xDel = 0; - if( u.aq.nZero ){ - pOut->u.nZero = u.aq.nZero; + pOut->n = (int)nByte; + pOut->flags = MEM_Blob; + if( nZero ){ + pOut->u.nZero = nZero; pOut->flags |= MEM_Zero; } pOut->enc = SQLITE_UTF8; /* In case the blob is ever converted to text */ REGISTER_TRACE(pOp->p3, pOut); UPDATE_MAX_BLOBSIZE(pOut); break; } /* Opcode: Count P1 P2 * * * +** Synopsis: r[P2]=count() ** ** Store the number of entries (an integer value) in the table or index ** opened by cursor P1 in register P2 */ #ifndef SQLITE_OMIT_BTREECOUNT case OP_Count: { /* out2-prerelease */ -#if 0 /* local variables moved into u.ar */ i64 nEntry; BtCursor *pCrsr; -#endif /* local variables moved into u.ar */ - - u.ar.pCrsr = p->apCsr[pOp->p1]->pCursor; - if( ALWAYS(u.ar.pCrsr) ){ - rc = sqlite3BtreeCount(u.ar.pCrsr, &u.ar.nEntry); - }else{ - u.ar.nEntry = 0; - } - pOut->u.i = u.ar.nEntry; + + pCrsr = p->apCsr[pOp->p1]->pCursor; + assert( pCrsr ); + nEntry = 0; /* Not needed. Only used to silence a warning. */ + rc = sqlite3BtreeCount(pCrsr, &nEntry); + pOut->u.i = nEntry; break; } #endif /* Opcode: Savepoint P1 * * P4 * @@ -68694,43 +73376,41 @@ ** Open, release or rollback the savepoint named by parameter P4, depending ** on the value of P1. To open a new savepoint, P1==0. To release (commit) an ** existing savepoint, P1==1, or to rollback an existing savepoint P1==2. */ case OP_Savepoint: { -#if 0 /* local variables moved into u.as */ int p1; /* Value of P1 operand */ char *zName; /* Name of savepoint */ int nName; Savepoint *pNew; Savepoint *pSavepoint; Savepoint *pTmp; int iSavepoint; int ii; -#endif /* local variables moved into u.as */ - u.as.p1 = pOp->p1; - u.as.zName = pOp->p4.z; + p1 = pOp->p1; + zName = pOp->p4.z; - /* Assert that the u.as.p1 parameter is valid. Also that if there is no open - ** transaction, then there cannot be any savepoints. + /* Assert that the p1 parameter is valid. Also that if there is no open + ** transaction, then there cannot be any savepoints. */ assert( db->pSavepoint==0 || db->autoCommit==0 ); - assert( u.as.p1==SAVEPOINT_BEGIN||u.as.p1==SAVEPOINT_RELEASE||u.as.p1==SAVEPOINT_ROLLBACK ); + assert( p1==SAVEPOINT_BEGIN||p1==SAVEPOINT_RELEASE||p1==SAVEPOINT_ROLLBACK ); assert( db->pSavepoint || db->isTransactionSavepoint==0 ); assert( checkSavepointCount(db) ); assert( p->bIsReader ); - if( u.as.p1==SAVEPOINT_BEGIN ){ + if( p1==SAVEPOINT_BEGIN ){ if( db->nVdbeWrite>0 ){ - /* A new savepoint cannot be created if there are active write + /* A new savepoint cannot be created if there are active write ** statements (i.e. open read/write incremental blob handles). */ sqlite3SetString(&p->zErrMsg, db, "cannot open savepoint - " "SQL statements in progress"); rc = SQLITE_BUSY; }else{ - u.as.nName = sqlite3Strlen30(u.as.zName); + nName = sqlite3Strlen30(zName); #ifndef SQLITE_OMIT_VIRTUALTABLE /* This call is Ok even if this savepoint is actually a transaction ** savepoint (and therefore should not prompt xSavepoint()) callbacks. ** If this is a transaction savepoint being opened, it is guaranteed @@ -68740,62 +73420,62 @@ db->nStatement+db->nSavepoint); if( rc!=SQLITE_OK ) goto abort_due_to_error; #endif /* Create a new savepoint structure. */ - u.as.pNew = sqlite3DbMallocRaw(db, sizeof(Savepoint)+u.as.nName+1); - if( u.as.pNew ){ - u.as.pNew->zName = (char *)&u.as.pNew[1]; - memcpy(u.as.pNew->zName, u.as.zName, u.as.nName+1); - + pNew = sqlite3DbMallocRaw(db, sizeof(Savepoint)+nName+1); + if( pNew ){ + pNew->zName = (char *)&pNew[1]; + memcpy(pNew->zName, zName, nName+1); + /* If there is no open transaction, then mark this as a special ** "transaction savepoint". */ if( db->autoCommit ){ db->autoCommit = 0; db->isTransactionSavepoint = 1; }else{ db->nSavepoint++; } - + /* Link the new savepoint into the database handle's list. */ - u.as.pNew->pNext = db->pSavepoint; - db->pSavepoint = u.as.pNew; - u.as.pNew->nDeferredCons = db->nDeferredCons; - u.as.pNew->nDeferredImmCons = db->nDeferredImmCons; + pNew->pNext = db->pSavepoint; + db->pSavepoint = pNew; + pNew->nDeferredCons = db->nDeferredCons; + pNew->nDeferredImmCons = db->nDeferredImmCons; } } }else{ - u.as.iSavepoint = 0; + iSavepoint = 0; /* Find the named savepoint. If there is no such savepoint, then an ** an error is returned to the user. */ for( - u.as.pSavepoint = db->pSavepoint; - u.as.pSavepoint && sqlite3StrICmp(u.as.pSavepoint->zName, u.as.zName); - u.as.pSavepoint = u.as.pSavepoint->pNext + pSavepoint = db->pSavepoint; + pSavepoint && sqlite3StrICmp(pSavepoint->zName, zName); + pSavepoint = pSavepoint->pNext ){ - u.as.iSavepoint++; + iSavepoint++; } - if( !u.as.pSavepoint ){ - sqlite3SetString(&p->zErrMsg, db, "no such savepoint: %s", u.as.zName); + if( !pSavepoint ){ + sqlite3SetString(&p->zErrMsg, db, "no such savepoint: %s", zName); rc = SQLITE_ERROR; - }else if( db->nVdbeWrite>0 && u.as.p1==SAVEPOINT_RELEASE ){ - /* It is not possible to release (commit) a savepoint if there are + }else if( db->nVdbeWrite>0 && p1==SAVEPOINT_RELEASE ){ + /* It is not possible to release (commit) a savepoint if there are ** active write statements. */ - sqlite3SetString(&p->zErrMsg, db, + sqlite3SetString(&p->zErrMsg, db, "cannot release savepoint - SQL statements in progress" ); rc = SQLITE_BUSY; }else{ /* Determine whether or not this is a transaction savepoint. If so, - ** and this is a RELEASE command, then the current transaction - ** is committed. + ** and this is a RELEASE command, then the current transaction + ** is committed. */ - int isTransaction = u.as.pSavepoint->pNext==0 && db->isTransactionSavepoint; - if( isTransaction && u.as.p1==SAVEPOINT_RELEASE ){ + int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint; + if( isTransaction && p1==SAVEPOINT_RELEASE ){ if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ goto vdbe_return; } db->autoCommit = 1; if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ @@ -68805,56 +73485,63 @@ goto vdbe_return; } db->isTransactionSavepoint = 0; rc = p->rc; }else{ - u.as.iSavepoint = db->nSavepoint - u.as.iSavepoint - 1; - if( u.as.p1==SAVEPOINT_ROLLBACK ){ - for(u.as.ii=0; u.as.ii<db->nDb; u.as.ii++){ - sqlite3BtreeTripAllCursors(db->aDb[u.as.ii].pBt, SQLITE_ABORT); - } - } - for(u.as.ii=0; u.as.ii<db->nDb; u.as.ii++){ - rc = sqlite3BtreeSavepoint(db->aDb[u.as.ii].pBt, u.as.p1, u.as.iSavepoint); + int isSchemaChange; + iSavepoint = db->nSavepoint - iSavepoint - 1; + if( p1==SAVEPOINT_ROLLBACK ){ + isSchemaChange = (db->flags & SQLITE_InternChanges)!=0; + for(ii=0; ii<db->nDb; ii++){ + rc = sqlite3BtreeTripAllCursors(db->aDb[ii].pBt, + SQLITE_ABORT_ROLLBACK, + isSchemaChange==0); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + } + }else{ + isSchemaChange = 0; + } + for(ii=0; ii<db->nDb; ii++){ + rc = sqlite3BtreeSavepoint(db->aDb[ii].pBt, p1, iSavepoint); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } } - if( u.as.p1==SAVEPOINT_ROLLBACK && (db->flags&SQLITE_InternChanges)!=0 ){ + if( isSchemaChange ){ sqlite3ExpirePreparedStatements(db); sqlite3ResetAllSchemasOfConnection(db); db->flags = (db->flags | SQLITE_InternChanges); } } - - /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all + + /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all ** savepoints nested inside of the savepoint being operated on. */ - while( db->pSavepoint!=u.as.pSavepoint ){ - u.as.pTmp = db->pSavepoint; - db->pSavepoint = u.as.pTmp->pNext; - sqlite3DbFree(db, u.as.pTmp); + while( db->pSavepoint!=pSavepoint ){ + pTmp = db->pSavepoint; + db->pSavepoint = pTmp->pNext; + sqlite3DbFree(db, pTmp); db->nSavepoint--; } - /* If it is a RELEASE, then destroy the savepoint being operated on - ** too. If it is a ROLLBACK TO, then set the number of deferred + /* If it is a RELEASE, then destroy the savepoint being operated on + ** too. If it is a ROLLBACK TO, then set the number of deferred ** constraint violations present in the database to the value stored ** when the savepoint was created. */ - if( u.as.p1==SAVEPOINT_RELEASE ){ - assert( u.as.pSavepoint==db->pSavepoint ); - db->pSavepoint = u.as.pSavepoint->pNext; - sqlite3DbFree(db, u.as.pSavepoint); + if( p1==SAVEPOINT_RELEASE ){ + assert( pSavepoint==db->pSavepoint ); + db->pSavepoint = pSavepoint->pNext; + sqlite3DbFree(db, pSavepoint); if( !isTransaction ){ db->nSavepoint--; } }else{ - db->nDeferredCons = u.as.pSavepoint->nDeferredCons; - db->nDeferredImmCons = u.as.pSavepoint->nDeferredImmCons; + db->nDeferredCons = pSavepoint->nDeferredCons; + db->nDeferredImmCons = pSavepoint->nDeferredImmCons; } if( !isTransaction ){ - rc = sqlite3VtabSavepoint(db, u.as.p1, u.as.iSavepoint); + rc = sqlite3VtabSavepoint(db, p1, iSavepoint); if( rc!=SQLITE_OK ) goto abort_due_to_error; } } } @@ -68869,54 +73556,52 @@ ** there are active writing VMs or active VMs that use shared cache. ** ** This instruction causes the VM to halt. */ case OP_AutoCommit: { -#if 0 /* local variables moved into u.at */ int desiredAutoCommit; int iRollback; int turnOnAC; -#endif /* local variables moved into u.at */ - u.at.desiredAutoCommit = pOp->p1; - u.at.iRollback = pOp->p2; - u.at.turnOnAC = u.at.desiredAutoCommit && !db->autoCommit; - assert( u.at.desiredAutoCommit==1 || u.at.desiredAutoCommit==0 ); - assert( u.at.desiredAutoCommit==1 || u.at.iRollback==0 ); + desiredAutoCommit = pOp->p1; + iRollback = pOp->p2; + turnOnAC = desiredAutoCommit && !db->autoCommit; + assert( desiredAutoCommit==1 || desiredAutoCommit==0 ); + assert( desiredAutoCommit==1 || iRollback==0 ); assert( db->nVdbeActive>0 ); /* At least this one VM is active */ assert( p->bIsReader ); #if 0 - if( u.at.turnOnAC && u.at.iRollback && db->nVdbeActive>1 ){ + if( turnOnAC && iRollback && db->nVdbeActive>1 ){ /* If this instruction implements a ROLLBACK and other VMs are ** still running, and a transaction is active, return an error indicating - ** that the other VMs must complete first. + ** that the other VMs must complete first. */ sqlite3SetString(&p->zErrMsg, db, "cannot rollback transaction - " "SQL statements in progress"); rc = SQLITE_BUSY; }else #endif - if( u.at.turnOnAC && !u.at.iRollback && db->nVdbeWrite>0 ){ + if( turnOnAC && !iRollback && db->nVdbeWrite>0 ){ /* If this instruction implements a COMMIT and other VMs are writing - ** return an error indicating that the other VMs must complete first. + ** return an error indicating that the other VMs must complete first. */ sqlite3SetString(&p->zErrMsg, db, "cannot commit transaction - " "SQL statements in progress"); rc = SQLITE_BUSY; - }else if( u.at.desiredAutoCommit!=db->autoCommit ){ - if( u.at.iRollback ){ - assert( u.at.desiredAutoCommit==1 ); + }else if( desiredAutoCommit!=db->autoCommit ){ + if( iRollback ){ + assert( desiredAutoCommit==1 ); sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); db->autoCommit = 1; }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ goto vdbe_return; }else{ - db->autoCommit = (u8)u.at.desiredAutoCommit; + db->autoCommit = (u8)desiredAutoCommit; if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ p->pc = pc; - db->autoCommit = (u8)(1-u.at.desiredAutoCommit); + db->autoCommit = (u8)(1-desiredAutoCommit); p->rc = rc = SQLITE_BUSY; goto vdbe_return; } } assert( db->nStatement==0 ); @@ -68927,38 +73612,32 @@ rc = SQLITE_ERROR; } goto vdbe_return; }else{ sqlite3SetString(&p->zErrMsg, db, - (!u.at.desiredAutoCommit)?"cannot start a transaction within a transaction":( - (u.at.iRollback)?"cannot rollback - no transaction is active": + (!desiredAutoCommit)?"cannot start a transaction within a transaction":( + (iRollback)?"cannot rollback - no transaction is active": "cannot commit - no transaction is active")); - + rc = SQLITE_ERROR; } break; } -/* Opcode: Transaction P1 P2 * * * +/* Opcode: Transaction P1 P2 P3 P4 P5 ** -** Begin a transaction. The transaction ends when a Commit or Rollback -** opcode is encountered. Depending on the ON CONFLICT setting, the -** transaction might also be rolled back if an error is encountered. +** Begin a transaction on database P1 if a transaction is not already +** active. +** If P2 is non-zero, then a write-transaction is started, or if a +** read-transaction is already active, it is upgraded to a write-transaction. +** If P2 is zero, then a read-transaction is started. ** ** P1 is the index of the database file on which the transaction is ** started. Index 0 is the main database file and index 1 is the ** file used for temporary tables. Indices of 2 or more are used for ** attached databases. ** -** If P2 is non-zero, then a write-transaction is started. A RESERVED lock is -** obtained on the database file when a write-transaction is started. No -** other process can start another write transaction while this transaction is -** underway. Starting a write transaction also creates a rollback journal. A -** write transaction must be started before any changes can be made to the -** database. If P2 is greater than or equal to 2 then an EXCLUSIVE lock is -** also obtained on the file. -** ** If a write-transaction is started and the Vdbe.usesStmtJournal flag is ** true (this flag is set if the Vdbe may modify more than one row and may ** throw an ABORT exception), a statement transaction may also be opened. ** More specifically, a statement transaction is opened iff the database ** connection is currently not in autocommit mode, or if there are other @@ -68965,59 +73644,102 @@ ** active statements. A statement transaction allows the changes made by this ** VDBE to be rolled back after an error without having to roll back the ** entire transaction. If no error is encountered, the statement transaction ** will automatically commit when the VDBE halts. ** -** If P2 is zero, then a read-lock is obtained on the database file. +** If P5!=0 then this opcode also checks the schema cookie against P3 +** and the schema generation counter against P4. +** The cookie changes its value whenever the database schema changes. +** This operation is used to detect when that the cookie has changed +** and that the current process needs to reread the schema. If the schema +** cookie in P3 differs from the schema cookie in the database header or +** if the schema generation counter in P4 differs from the current +** generation counter, then an SQLITE_SCHEMA error is raised and execution +** halts. The sqlite3_step() wrapper function might then reprepare the +** statement and rerun it from the beginning. */ case OP_Transaction: { -#if 0 /* local variables moved into u.au */ Btree *pBt; -#endif /* local variables moved into u.au */ + int iMeta; + int iGen; assert( p->bIsReader ); assert( p->readOnly==0 || pOp->p2==0 ); assert( pOp->p1>=0 && pOp->p1<db->nDb ); - assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 ); + assert( DbMaskTest(p->btreeMask, pOp->p1) ); if( pOp->p2 && (db->flags & SQLITE_QueryOnly)!=0 ){ rc = SQLITE_READONLY; goto abort_due_to_error; } - u.au.pBt = db->aDb[pOp->p1].pBt; + pBt = db->aDb[pOp->p1].pBt; - if( u.au.pBt ){ - rc = sqlite3BtreeBeginTrans(u.au.pBt, pOp->p2); + if( pBt ){ + rc = sqlite3BtreeBeginTrans(pBt, pOp->p2); if( rc==SQLITE_BUSY ){ p->pc = pc; p->rc = rc = SQLITE_BUSY; goto vdbe_return; } if( rc!=SQLITE_OK ){ goto abort_due_to_error; } - if( pOp->p2 && p->usesStmtJournal - && (db->autoCommit==0 || db->nVdbeRead>1) + if( pOp->p2 && p->usesStmtJournal + && (db->autoCommit==0 || db->nVdbeRead>1) ){ - assert( sqlite3BtreeIsInTrans(u.au.pBt) ); + assert( sqlite3BtreeIsInTrans(pBt) ); if( p->iStatement==0 ){ assert( db->nStatement>=0 && db->nSavepoint>=0 ); - db->nStatement++; + db->nStatement++; p->iStatement = db->nSavepoint + db->nStatement; } rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, p->iStatement-1); if( rc==SQLITE_OK ){ - rc = sqlite3BtreeBeginStmt(u.au.pBt, p->iStatement); + rc = sqlite3BtreeBeginStmt(pBt, p->iStatement); } /* Store the current value of the database handles deferred constraint ** counter. If the statement transaction needs to be rolled back, ** the value of this counter needs to be restored too. */ p->nStmtDefCons = db->nDeferredCons; p->nStmtDefImmCons = db->nDeferredImmCons; } + + /* Gather the schema version number for checking: + ** IMPLEMENTATION-OF: R-32195-19465 The schema version is used by SQLite + ** each time a query is executed to ensure that the internal cache of the + ** schema used when compiling the SQL query matches the schema of the + ** database against which the compiled query is actually executed. + */ + sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&iMeta); + iGen = db->aDb[pOp->p1].pSchema->iGeneration; + }else{ + iGen = iMeta = 0; + } + assert( pOp->p5==0 || pOp->p4type==P4_INT32 ); + if( pOp->p5 && (iMeta!=pOp->p3 || iGen!=pOp->p4.i) ){ + sqlite3DbFree(db, p->zErrMsg); + p->zErrMsg = sqlite3DbStrDup(db, "database schema has changed"); + /* If the schema-cookie from the database file matches the cookie + ** stored with the in-memory representation of the schema, do + ** not reload the schema from the database file. + ** + ** If virtual-tables are in use, this is not just an optimization. + ** Often, v-tables store their data in other SQLite tables, which + ** are queried from within xNext() and other v-table methods using + ** prepared queries. If such a query is out-of-date, we do not want to + ** discard the database schema, as the user code implementing the + ** v-table would have to be ready for the sqlite3_vtab structure itself + ** to be invalidated whenever sqlite3_step() is called from within + ** a v-table method. + */ + if( db->aDb[pOp->p1].pSchema->schema_cookie!=iMeta ){ + sqlite3ResetOneSchema(db, pOp->p1); + } + p->expired = 1; + rc = SQLITE_SCHEMA; } break; } /* Opcode: ReadCookie P1 P2 P3 * * @@ -69031,26 +73753,24 @@ ** There must be a read-lock on the database (either a transaction ** must be started or there must be an open cursor) before ** executing this instruction. */ case OP_ReadCookie: { /* out2-prerelease */ -#if 0 /* local variables moved into u.av */ int iMeta; int iDb; int iCookie; -#endif /* local variables moved into u.av */ assert( p->bIsReader ); - u.av.iDb = pOp->p1; - u.av.iCookie = pOp->p3; + iDb = pOp->p1; + iCookie = pOp->p3; assert( pOp->p3<SQLITE_N_BTREE_META ); - assert( u.av.iDb>=0 && u.av.iDb<db->nDb ); - assert( db->aDb[u.av.iDb].pBt!=0 ); - assert( (p->btreeMask & (((yDbMask)1)<<u.av.iDb))!=0 ); + assert( iDb>=0 && iDb<db->nDb ); + assert( db->aDb[iDb].pBt!=0 ); + assert( DbMaskTest(p->btreeMask, iDb) ); - sqlite3BtreeGetMeta(db->aDb[u.av.iDb].pBt, u.av.iCookie, (u32 *)&u.av.iMeta); - pOut->u.i = u.av.iMeta; + sqlite3BtreeGetMeta(db->aDb[iDb].pBt, iCookie, (u32 *)&iMeta); + pOut->u.i = iMeta; break; } /* Opcode: SetCookie P1 P2 P3 * * ** @@ -69061,31 +73781,29 @@ ** database file used to store temporary tables. ** ** A transaction must be started before executing this opcode. */ case OP_SetCookie: { /* in3 */ -#if 0 /* local variables moved into u.aw */ Db *pDb; -#endif /* local variables moved into u.aw */ assert( pOp->p2<SQLITE_N_BTREE_META ); assert( pOp->p1>=0 && pOp->p1<db->nDb ); - assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 ); + assert( DbMaskTest(p->btreeMask, pOp->p1) ); assert( p->readOnly==0 ); - u.aw.pDb = &db->aDb[pOp->p1]; - assert( u.aw.pDb->pBt!=0 ); + pDb = &db->aDb[pOp->p1]; + assert( pDb->pBt!=0 ); assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) ); pIn3 = &aMem[pOp->p3]; sqlite3VdbeMemIntegerify(pIn3); /* See note about index shifting on OP_ReadCookie */ - rc = sqlite3BtreeUpdateMeta(u.aw.pDb->pBt, pOp->p2, (int)pIn3->u.i); + rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, (int)pIn3->u.i); if( pOp->p2==BTREE_SCHEMA_VERSION ){ /* When the schema cookie changes, record the new cookie internally */ - u.aw.pDb->pSchema->schema_cookie = (int)pIn3->u.i; + pDb->pSchema->schema_cookie = (int)pIn3->u.i; db->flags |= SQLITE_InternChanges; }else if( pOp->p2==BTREE_FILE_FORMAT ){ /* Record changes in the file format */ - u.aw.pDb->pSchema->file_format = (u8)pIn3->u.i; + pDb->pSchema->file_format = (u8)pIn3->u.i; } if( pOp->p1==1 ){ /* Invalidate all prepared statements whenever the TEMP database ** schema is changed. Ticket #1644 */ sqlite3ExpirePreparedStatements(db); @@ -69092,73 +73810,12 @@ p->expired = 0; } break; } -/* Opcode: VerifyCookie P1 P2 P3 * * -** -** Check the value of global database parameter number 0 (the -** schema version) and make sure it is equal to P2 and that the -** generation counter on the local schema parse equals P3. -** -** P1 is the database number which is 0 for the main database file -** and 1 for the file holding temporary tables and some higher number -** for auxiliary databases. -** -** The cookie changes its value whenever the database schema changes. -** This operation is used to detect when that the cookie has changed -** and that the current process needs to reread the schema. -** -** Either a transaction needs to have been started or an OP_Open needs -** to be executed (to establish a read lock) before this opcode is -** invoked. -*/ -case OP_VerifyCookie: { -#if 0 /* local variables moved into u.ax */ - int iMeta; - int iGen; - Btree *pBt; -#endif /* local variables moved into u.ax */ - - assert( pOp->p1>=0 && pOp->p1<db->nDb ); - assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 ); - assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) ); - assert( p->bIsReader ); - u.ax.pBt = db->aDb[pOp->p1].pBt; - if( u.ax.pBt ){ - sqlite3BtreeGetMeta(u.ax.pBt, BTREE_SCHEMA_VERSION, (u32 *)&u.ax.iMeta); - u.ax.iGen = db->aDb[pOp->p1].pSchema->iGeneration; - }else{ - u.ax.iGen = u.ax.iMeta = 0; - } - if( u.ax.iMeta!=pOp->p2 || u.ax.iGen!=pOp->p3 ){ - sqlite3DbFree(db, p->zErrMsg); - p->zErrMsg = sqlite3DbStrDup(db, "database schema has changed"); - /* If the schema-cookie from the database file matches the cookie - ** stored with the in-memory representation of the schema, do - ** not reload the schema from the database file. - ** - ** If virtual-tables are in use, this is not just an optimization. - ** Often, v-tables store their data in other SQLite tables, which - ** are queried from within xNext() and other v-table methods using - ** prepared queries. If such a query is out-of-date, we do not want to - ** discard the database schema, as the user code implementing the - ** v-table would have to be ready for the sqlite3_vtab structure itself - ** to be invalidated whenever sqlite3_step() is called from within - ** a v-table method. - */ - if( db->aDb[pOp->p1].pSchema->schema_cookie!=u.ax.iMeta ){ - sqlite3ResetOneSchema(db, pOp->p1); - } - - p->expired = 1; - rc = SQLITE_SCHEMA; - } - break; -} - /* Opcode: OpenRead P1 P2 P3 P4 P5 +** Synopsis: root=P2 iDb=P3 ** ** Open a read-only cursor for the database table whose root page is ** P2 in a database file. The database file is determined by P3. ** P3==0 means the main database, P3==1 means the database used for ** temporary tables, and P3>1 means used the corresponding attached @@ -69182,13 +73839,28 @@ ** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo ** structure, then said structure defines the content and collating ** sequence of the index being opened. Otherwise, if P4 is an integer ** value, it is set to the number of columns in the table. ** -** See also OpenWrite. +** See also: OpenWrite, ReopenIdx +*/ +/* Opcode: ReopenIdx P1 P2 P3 P4 P5 +** Synopsis: root=P2 iDb=P3 +** +** The ReopenIdx opcode works exactly like ReadOpen except that it first +** checks to see if the cursor on P1 is already open with a root page +** number of P2 and if it is this opcode becomes a no-op. In other words, +** if the cursor is already open, do not reopen it. +** +** The ReopenIdx opcode may only be used with P5==0 and with P4 being +** a P4_KEYINFO object. Furthermore, the P3 value must be the same as +** every other ReopenIdx or OpenRead for the same cursor number. +** +** See the OpenRead opcode documentation for additional information. */ /* Opcode: OpenWrite P1 P2 P3 P4 P5 +** Synopsis: root=P2 iDb=P3 ** ** Open a read/write cursor named P1 on the table or index whose root ** page is P2. Or if P5!=0 use the content of register P2 to find the ** root page. ** @@ -69203,99 +73875,112 @@ ** in read/write mode. For a given table, there can be one or more read-only ** cursors or a single read/write cursor but not both. ** ** See also OpenRead. */ -case OP_OpenRead: -case OP_OpenWrite: { -#if 0 /* local variables moved into u.ay */ +case OP_ReopenIdx: { int nField; KeyInfo *pKeyInfo; int p2; int iDb; int wrFlag; Btree *pX; VdbeCursor *pCur; Db *pDb; -#endif /* local variables moved into u.ay */ - assert( (pOp->p5&(OPFLAG_P2ISREG|OPFLAG_BULKCSR))==pOp->p5 ); - assert( pOp->opcode==OP_OpenWrite || pOp->p5==0 ); + assert( pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ ); + assert( pOp->p4type==P4_KEYINFO ); + pCur = p->apCsr[pOp->p1]; + if( pCur && pCur->pgnoRoot==(u32)pOp->p2 ){ + assert( pCur->iDb==pOp->p3 ); /* Guaranteed by the code generator */ + goto open_cursor_set_hints; + } + /* If the cursor is not currently open or is open on a different + ** index, then fall through into OP_OpenRead to force a reopen */ +case OP_OpenRead: +case OP_OpenWrite: + + assert( (pOp->p5&(OPFLAG_P2ISREG|OPFLAG_BULKCSR|OPFLAG_SEEKEQ))==pOp->p5 ); + assert( pOp->opcode==OP_OpenWrite || pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ ); assert( p->bIsReader ); - assert( pOp->opcode==OP_OpenRead || p->readOnly==0 ); + assert( pOp->opcode==OP_OpenRead || pOp->opcode==OP_ReopenIdx + || p->readOnly==0 ); if( p->expired ){ - rc = SQLITE_ABORT; + rc = SQLITE_ABORT_ROLLBACK; break; } - u.ay.nField = 0; - u.ay.pKeyInfo = 0; - u.ay.p2 = pOp->p2; - u.ay.iDb = pOp->p3; - assert( u.ay.iDb>=0 && u.ay.iDb<db->nDb ); - assert( (p->btreeMask & (((yDbMask)1)<<u.ay.iDb))!=0 ); - u.ay.pDb = &db->aDb[u.ay.iDb]; - u.ay.pX = u.ay.pDb->pBt; - assert( u.ay.pX!=0 ); + nField = 0; + pKeyInfo = 0; + p2 = pOp->p2; + iDb = pOp->p3; + assert( iDb>=0 && iDb<db->nDb ); + assert( DbMaskTest(p->btreeMask, iDb) ); + pDb = &db->aDb[iDb]; + pX = pDb->pBt; + assert( pX!=0 ); if( pOp->opcode==OP_OpenWrite ){ - u.ay.wrFlag = 1; - assert( sqlite3SchemaMutexHeld(db, u.ay.iDb, 0) ); - if( u.ay.pDb->pSchema->file_format < p->minWriteFileFormat ){ - p->minWriteFileFormat = u.ay.pDb->pSchema->file_format; + wrFlag = 1; + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + if( pDb->pSchema->file_format < p->minWriteFileFormat ){ + p->minWriteFileFormat = pDb->pSchema->file_format; } }else{ - u.ay.wrFlag = 0; + wrFlag = 0; } if( pOp->p5 & OPFLAG_P2ISREG ){ - assert( u.ay.p2>0 ); - assert( u.ay.p2<=(p->nMem-p->nCursor) ); - pIn2 = &aMem[u.ay.p2]; + assert( p2>0 ); + assert( p2<=(p->nMem-p->nCursor) ); + pIn2 = &aMem[p2]; assert( memIsValid(pIn2) ); assert( (pIn2->flags & MEM_Int)!=0 ); sqlite3VdbeMemIntegerify(pIn2); - u.ay.p2 = (int)pIn2->u.i; - /* The u.ay.p2 value always comes from a prior OP_CreateTable opcode and - ** that opcode will always set the u.ay.p2 value to 2 or more or else fail. + p2 = (int)pIn2->u.i; + /* The p2 value always comes from a prior OP_CreateTable opcode and + ** that opcode will always set the p2 value to 2 or more or else fail. ** If there were a failure, the prepared statement would have halted ** before reaching this instruction. */ - if( NEVER(u.ay.p2<2) ) { + if( NEVER(p2<2) ) { rc = SQLITE_CORRUPT_BKPT; goto abort_due_to_error; } } if( pOp->p4type==P4_KEYINFO ){ - u.ay.pKeyInfo = pOp->p4.pKeyInfo; - u.ay.pKeyInfo->enc = ENC(p->db); - u.ay.nField = u.ay.pKeyInfo->nField+1; + pKeyInfo = pOp->p4.pKeyInfo; + assert( pKeyInfo->enc==ENC(db) ); + assert( pKeyInfo->db==db ); + nField = pKeyInfo->nField+pKeyInfo->nXField; }else if( pOp->p4type==P4_INT32 ){ - u.ay.nField = pOp->p4.i; + nField = pOp->p4.i; } assert( pOp->p1>=0 ); - u.ay.pCur = allocateCursor(p, pOp->p1, u.ay.nField, u.ay.iDb, 1); - if( u.ay.pCur==0 ) goto no_mem; - u.ay.pCur->nullRow = 1; - u.ay.pCur->isOrdered = 1; - rc = sqlite3BtreeCursor(u.ay.pX, u.ay.p2, u.ay.wrFlag, u.ay.pKeyInfo, u.ay.pCur->pCursor); - u.ay.pCur->pKeyInfo = u.ay.pKeyInfo; - assert( OPFLAG_BULKCSR==BTREE_BULKLOAD ); - sqlite3BtreeCursorHints(u.ay.pCur->pCursor, (pOp->p5 & OPFLAG_BULKCSR)); - - /* Since it performs no memory allocation or IO, the only value that - ** sqlite3BtreeCursor() may return is SQLITE_OK. */ - assert( rc==SQLITE_OK ); - - /* Set the VdbeCursor.isTable and isIndex variables. Previous versions of + assert( nField>=0 ); + testcase( nField==0 ); /* Table with INTEGER PRIMARY KEY and nothing else */ + pCur = allocateCursor(p, pOp->p1, nField, iDb, 1); + if( pCur==0 ) goto no_mem; + pCur->nullRow = 1; + pCur->isOrdered = 1; + pCur->pgnoRoot = p2; + rc = sqlite3BtreeCursor(pX, p2, wrFlag, pKeyInfo, pCur->pCursor); + pCur->pKeyInfo = pKeyInfo; + /* Set the VdbeCursor.isTable variable. Previous versions of ** SQLite used to check if the root-page flags were sane at this point ** and report database corruption if they were not, but this check has - ** since moved into the btree layer. */ - u.ay.pCur->isTable = pOp->p4type!=P4_KEYINFO; - u.ay.pCur->isIndex = !u.ay.pCur->isTable; + ** since moved into the btree layer. */ + pCur->isTable = pOp->p4type!=P4_KEYINFO; + +open_cursor_set_hints: + assert( OPFLAG_BULKCSR==BTREE_BULKLOAD ); + assert( OPFLAG_SEEKEQ==BTREE_SEEK_EQ ); + sqlite3BtreeCursorHints(pCur->pCursor, + (pOp->p5 & (OPFLAG_BULKCSR|OPFLAG_SEEKEQ))); break; } /* Opcode: OpenEphemeral P1 P2 * P4 P5 +** Synopsis: nColumn=P2 ** ** Open a new cursor P1 to a transient table. ** The cursor is always opened read/write even if ** the main database is read-only. The ephemeral ** table is deleted automatically when the cursor is closed. @@ -69303,104 +73988,121 @@ ** P2 is the number of columns in the ephemeral table. ** The cursor points to a BTree table if P4==0 and to a BTree index ** if P4 is not 0. If P4 is not NULL, it points to a KeyInfo structure ** that defines the format of keys in the index. ** -** This opcode was once called OpenTemp. But that created -** confusion because the term "temp table", might refer either -** to a TEMP table at the SQL level, or to a table opened by -** this opcode. Then this opcode was call OpenVirtual. But -** that created confusion with the whole virtual-table idea. -** ** The P5 parameter can be a mask of the BTREE_* flags defined ** in btree.h. These flags control aspects of the operation of ** the btree. The BTREE_OMIT_JOURNAL and BTREE_SINGLE flags are ** added automatically. */ /* Opcode: OpenAutoindex P1 P2 * P4 * +** Synopsis: nColumn=P2 ** ** This opcode works the same as OP_OpenEphemeral. It has a ** different name to distinguish its use. Tables created using ** by this opcode will be used for automatically created transient ** indices in joins. */ case OP_OpenAutoindex: case OP_OpenEphemeral: { -#if 0 /* local variables moved into u.az */ VdbeCursor *pCx; -#endif /* local variables moved into u.az */ - static const int vfsFlags = + KeyInfo *pKeyInfo; + + static const int vfsFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE | SQLITE_OPEN_TRANSIENT_DB; - assert( pOp->p1>=0 ); - u.az.pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, 1); - if( u.az.pCx==0 ) goto no_mem; - u.az.pCx->nullRow = 1; - rc = sqlite3BtreeOpen(db->pVfs, 0, db, &u.az.pCx->pBt, + assert( pOp->p2>=0 ); + pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, 1); + if( pCx==0 ) goto no_mem; + pCx->nullRow = 1; + pCx->isEphemeral = 1; + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBt, BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, vfsFlags); if( rc==SQLITE_OK ){ - rc = sqlite3BtreeBeginTrans(u.az.pCx->pBt, 1); + rc = sqlite3BtreeBeginTrans(pCx->pBt, 1); } if( rc==SQLITE_OK ){ /* If a transient index is required, create it by calling ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before ** opening it. If a transient table is required, just use the ** automatically created table with root-page 1 (an BLOB_INTKEY table). */ - if( pOp->p4.pKeyInfo ){ + if( (pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ int pgno; assert( pOp->p4type==P4_KEYINFO ); - rc = sqlite3BtreeCreateTable(u.az.pCx->pBt, &pgno, BTREE_BLOBKEY | pOp->p5); + rc = sqlite3BtreeCreateTable(pCx->pBt, &pgno, BTREE_BLOBKEY | pOp->p5); if( rc==SQLITE_OK ){ assert( pgno==MASTER_ROOT+1 ); - rc = sqlite3BtreeCursor(u.az.pCx->pBt, pgno, 1, - (KeyInfo*)pOp->p4.z, u.az.pCx->pCursor); - u.az.pCx->pKeyInfo = pOp->p4.pKeyInfo; - u.az.pCx->pKeyInfo->enc = ENC(p->db); - } - u.az.pCx->isTable = 0; - }else{ - rc = sqlite3BtreeCursor(u.az.pCx->pBt, MASTER_ROOT, 1, 0, u.az.pCx->pCursor); - u.az.pCx->isTable = 1; + assert( pKeyInfo->db==db ); + assert( pKeyInfo->enc==ENC(db) ); + pCx->pKeyInfo = pKeyInfo; + rc = sqlite3BtreeCursor(pCx->pBt, pgno, 1, pKeyInfo, pCx->pCursor); + } + pCx->isTable = 0; + }else{ + rc = sqlite3BtreeCursor(pCx->pBt, MASTER_ROOT, 1, 0, pCx->pCursor); + pCx->isTable = 1; } } - u.az.pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); - u.az.pCx->isIndex = !u.az.pCx->isTable; + pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); break; } -/* Opcode: SorterOpen P1 P2 * P4 * +/* Opcode: SorterOpen P1 P2 P3 P4 * ** ** This opcode works like OP_OpenEphemeral except that it opens ** a transient index that is specifically designed to sort large ** tables using an external merge-sort algorithm. +** +** If argument P3 is non-zero, then it indicates that the sorter may +** assume that a stable sort considering the first P3 fields of each +** key is sufficient to produce the required results. */ case OP_SorterOpen: { -#if 0 /* local variables moved into u.ba */ VdbeCursor *pCx; -#endif /* local variables moved into u.ba */ - - u.ba.pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, 1); - if( u.ba.pCx==0 ) goto no_mem; - u.ba.pCx->pKeyInfo = pOp->p4.pKeyInfo; - u.ba.pCx->pKeyInfo->enc = ENC(p->db); - u.ba.pCx->isSorter = 1; - rc = sqlite3VdbeSorterInit(db, u.ba.pCx); + + assert( pOp->p1>=0 ); + assert( pOp->p2>=0 ); + pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, 1); + if( pCx==0 ) goto no_mem; + pCx->pKeyInfo = pOp->p4.pKeyInfo; + assert( pCx->pKeyInfo->db==db ); + assert( pCx->pKeyInfo->enc==ENC(db) ); + rc = sqlite3VdbeSorterInit(db, pOp->p3, pCx); break; } -/* Opcode: OpenPseudo P1 P2 P3 * P5 +/* Opcode: SequenceTest P1 P2 * * * +** Synopsis: if( cursor[P1].ctr++ ) pc = P2 +** +** P1 is a sorter cursor. If the sequence counter is currently zero, jump +** to P2. Regardless of whether or not the jump is taken, increment the +** the sequence value. +*/ +case OP_SequenceTest: { + VdbeCursor *pC; + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC->pSorter ); + if( (pC->seqCount++)==0 ){ + pc = pOp->p2 - 1; + } + break; +} + +/* Opcode: OpenPseudo P1 P2 P3 * * +** Synopsis: P3 columns in r[P2] ** ** Open a new cursor that points to a fake table that contains a single -** row of data. The content of that one row in the content of memory -** register P2 when P5==0. In other words, cursor P1 becomes an alias for the -** MEM_Blob content contained in register P2. When P5==1, then the -** row is represented by P3 consecutive registers beginning with P2. +** row of data. The content of that one row is the content of memory +** register P2. In other words, cursor P1 becomes an alias for the +** MEM_Blob content contained in register P2. ** ** A pseudo-table created by this opcode is used to hold a single ** row output from the sorter so that the row can be decomposed into ** individual columns using the OP_Column opcode. The OP_Column opcode ** is the only cursor opcode that works with a pseudo-table. @@ -69407,22 +74109,20 @@ ** ** P3 is the number of fields in the records that will be stored by ** the pseudo-table. */ case OP_OpenPseudo: { -#if 0 /* local variables moved into u.bb */ VdbeCursor *pCx; -#endif /* local variables moved into u.bb */ assert( pOp->p1>=0 ); - u.bb.pCx = allocateCursor(p, pOp->p1, pOp->p3, -1, 0); - if( u.bb.pCx==0 ) goto no_mem; - u.bb.pCx->nullRow = 1; - u.bb.pCx->pseudoTableReg = pOp->p2; - u.bb.pCx->isTable = 1; - u.bb.pCx->isIndex = 0; - u.bb.pCx->multiPseudo = pOp->p5; + assert( pOp->p3>=0 ); + pCx = allocateCursor(p, pOp->p1, pOp->p3, -1, 0); + if( pCx==0 ) goto no_mem; + pCx->nullRow = 1; + pCx->pseudoTableReg = pOp->p2; + pCx->isTable = 1; + assert( pOp->p5==0 ); break; } /* Opcode: Close P1 * * * * ** @@ -69434,11 +74134,12 @@ sqlite3VdbeFreeCursor(p, p->apCsr[pOp->p1]); p->apCsr[pOp->p1] = 0; break; } -/* Opcode: SeekGe P1 P2 P3 P4 * +/* Opcode: SeekGE P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] ** ** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), ** use the value in register P3 as the key. If cursor P1 refers ** to an SQL index, then P3 is the first in an array of P4 registers ** that are used as an unpacked index key. @@ -69445,13 +74146,18 @@ ** ** Reposition cursor P1 so that it points to the smallest entry that ** is greater than or equal to the key value. If there are no records ** greater than or equal to the key and P2 is not zero, then jump to P2. ** -** See also: Found, NotFound, Distinct, SeekLt, SeekGt, SeekLe +** This opcode leaves the cursor configured to move in forward order, +** from the beginning toward the end. In other words, the cursor is +** configured to use Next, not Prev. +** +** See also: Found, NotFound, SeekLt, SeekGt, SeekLe */ -/* Opcode: SeekGt P1 P2 P3 P4 * +/* Opcode: SeekGT P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] ** ** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), ** use the value in register P3 as a key. If cursor P1 refers ** to an SQL index, then P3 is the first in an array of P4 registers ** that are used as an unpacked index key. @@ -69458,13 +74164,18 @@ ** ** Reposition cursor P1 so that it points to the smallest entry that ** is greater than the key value. If there are no records greater than ** the key and P2 is not zero, then jump to P2. ** -** See also: Found, NotFound, Distinct, SeekLt, SeekGe, SeekLe +** This opcode leaves the cursor configured to move in forward order, +** from the beginning toward the end. In other words, the cursor is +** configured to use Next, not Prev. +** +** See also: Found, NotFound, SeekLt, SeekGe, SeekLe */ -/* Opcode: SeekLt P1 P2 P3 P4 * +/* Opcode: SeekLT P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] ** ** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), ** use the value in register P3 as a key. If cursor P1 refers ** to an SQL index, then P3 is the first in an array of P4 registers ** that are used as an unpacked index key. @@ -69471,13 +74182,18 @@ ** ** Reposition cursor P1 so that it points to the largest entry that ** is less than the key value. If there are no records less than ** the key and P2 is not zero, then jump to P2. ** -** See also: Found, NotFound, Distinct, SeekGt, SeekGe, SeekLe +** This opcode leaves the cursor configured to move in reverse order, +** from the end toward the beginning. In other words, the cursor is +** configured to use Prev, not Next. +** +** See also: Found, NotFound, SeekGt, SeekGe, SeekLe */ -/* Opcode: SeekLe P1 P2 P3 P4 * +/* Opcode: SeekLE P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] ** ** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), ** use the value in register P3 as a key. If cursor P1 refers ** to an SQL index, then P3 is the first in an array of P4 registers ** that are used as an unpacked index key. @@ -69484,206 +74200,215 @@ ** ** Reposition cursor P1 so that it points to the largest entry that ** is less than or equal to the key value. If there are no records ** less than or equal to the key and P2 is not zero, then jump to P2. ** -** See also: Found, NotFound, Distinct, SeekGt, SeekGe, SeekLt +** This opcode leaves the cursor configured to move in reverse order, +** from the end toward the beginning. In other words, the cursor is +** configured to use Prev, not Next. +** +** See also: Found, NotFound, SeekGt, SeekGe, SeekLt */ -case OP_SeekLt: /* jump, in3 */ -case OP_SeekLe: /* jump, in3 */ -case OP_SeekGe: /* jump, in3 */ -case OP_SeekGt: { /* jump, in3 */ -#if 0 /* local variables moved into u.bc */ +case OP_SeekLT: /* jump, in3 */ +case OP_SeekLE: /* jump, in3 */ +case OP_SeekGE: /* jump, in3 */ +case OP_SeekGT: { /* jump, in3 */ int res; int oc; VdbeCursor *pC; UnpackedRecord r; int nField; i64 iKey; /* The rowid we are to seek to */ -#endif /* local variables moved into u.bc */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); assert( pOp->p2!=0 ); - u.bc.pC = p->apCsr[pOp->p1]; - assert( u.bc.pC!=0 ); - assert( u.bc.pC->pseudoTableReg==0 ); - assert( OP_SeekLe == OP_SeekLt+1 ); - assert( OP_SeekGe == OP_SeekLt+2 ); - assert( OP_SeekGt == OP_SeekLt+3 ); - assert( u.bc.pC->isOrdered ); - if( ALWAYS(u.bc.pC->pCursor!=0) ){ - u.bc.oc = pOp->opcode; - u.bc.pC->nullRow = 0; - if( u.bc.pC->isTable ){ - /* The input value in P3 might be of any type: integer, real, string, - ** blob, or NULL. But it needs to be an integer before we can do - ** the seek, so covert it. */ - pIn3 = &aMem[pOp->p3]; - applyNumericAffinity(pIn3); - u.bc.iKey = sqlite3VdbeIntValue(pIn3); - u.bc.pC->rowidIsValid = 0; - - /* If the P3 value could not be converted into an integer without - ** loss of information, then special processing is required... */ - if( (pIn3->flags & MEM_Int)==0 ){ - if( (pIn3->flags & MEM_Real)==0 ){ - /* If the P3 value cannot be converted into any kind of a number, - ** then the seek is not possible, so jump to P2 */ - pc = pOp->p2 - 1; - break; - } - /* If we reach this point, then the P3 value must be a floating - ** point number. */ - assert( (pIn3->flags & MEM_Real)!=0 ); - - if( u.bc.iKey==SMALLEST_INT64 && (pIn3->r<(double)u.bc.iKey || pIn3->r>0) ){ - /* The P3 value is too large in magnitude to be expressed as an - ** integer. */ - u.bc.res = 1; - if( pIn3->r<0 ){ - if( u.bc.oc>=OP_SeekGe ){ assert( u.bc.oc==OP_SeekGe || u.bc.oc==OP_SeekGt ); - rc = sqlite3BtreeFirst(u.bc.pC->pCursor, &u.bc.res); - if( rc!=SQLITE_OK ) goto abort_due_to_error; - } - }else{ - if( u.bc.oc<=OP_SeekLe ){ assert( u.bc.oc==OP_SeekLt || u.bc.oc==OP_SeekLe ); - rc = sqlite3BtreeLast(u.bc.pC->pCursor, &u.bc.res); - if( rc!=SQLITE_OK ) goto abort_due_to_error; - } - } - if( u.bc.res ){ - pc = pOp->p2 - 1; - } - break; - }else if( u.bc.oc==OP_SeekLt || u.bc.oc==OP_SeekGe ){ - /* Use the ceiling() function to convert real->int */ - if( pIn3->r > (double)u.bc.iKey ) u.bc.iKey++; - }else{ - /* Use the floor() function to convert real->int */ - assert( u.bc.oc==OP_SeekLe || u.bc.oc==OP_SeekGt ); - if( pIn3->r < (double)u.bc.iKey ) u.bc.iKey--; - } - } - rc = sqlite3BtreeMovetoUnpacked(u.bc.pC->pCursor, 0, (u64)u.bc.iKey, 0, &u.bc.res); - if( rc!=SQLITE_OK ){ - goto abort_due_to_error; - } - if( u.bc.res==0 ){ - u.bc.pC->rowidIsValid = 1; - u.bc.pC->lastRowid = u.bc.iKey; - } - }else{ - u.bc.nField = pOp->p4.i; - assert( pOp->p4type==P4_INT32 ); - assert( u.bc.nField>0 ); - u.bc.r.pKeyInfo = u.bc.pC->pKeyInfo; - u.bc.r.nField = (u16)u.bc.nField; - - /* The next line of code computes as follows, only faster: - ** if( u.bc.oc==OP_SeekGt || u.bc.oc==OP_SeekLe ){ - ** u.bc.r.flags = UNPACKED_INCRKEY; - ** }else{ - ** u.bc.r.flags = 0; - ** } - */ - u.bc.r.flags = (u8)(UNPACKED_INCRKEY * (1 & (u.bc.oc - OP_SeekLt))); - assert( u.bc.oc!=OP_SeekGt || u.bc.r.flags==UNPACKED_INCRKEY ); - assert( u.bc.oc!=OP_SeekLe || u.bc.r.flags==UNPACKED_INCRKEY ); - assert( u.bc.oc!=OP_SeekGe || u.bc.r.flags==0 ); - assert( u.bc.oc!=OP_SeekLt || u.bc.r.flags==0 ); - - u.bc.r.aMem = &aMem[pOp->p3]; + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->pseudoTableReg==0 ); + assert( OP_SeekLE == OP_SeekLT+1 ); + assert( OP_SeekGE == OP_SeekLT+2 ); + assert( OP_SeekGT == OP_SeekLT+3 ); + assert( pC->isOrdered ); + assert( pC->pCursor!=0 ); + oc = pOp->opcode; + pC->nullRow = 0; +#ifdef SQLITE_DEBUG + pC->seekOp = pOp->opcode; +#endif + + /* For a cursor with the BTREE_SEEK_EQ hint, only the OP_SeekGE and + ** OP_SeekLE opcodes are allowed, and these must be immediately followed + ** by an OP_IdxGT or OP_IdxLT opcode, respectively, with the same key. + */ +#ifdef SQLITE_DEBUG + if( sqlite3BtreeCursorHasHint(pC->pCursor, BTREE_SEEK_EQ) ){ + assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE ); + assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT ); + assert( pOp[1].p1==pOp[0].p1 ); + assert( pOp[1].p2==pOp[0].p2 ); + assert( pOp[1].p3==pOp[0].p3 ); + assert( pOp[1].p4.i==pOp[0].p4.i ); + } +#endif + + if( pC->isTable ){ + /* The input value in P3 might be of any type: integer, real, string, + ** blob, or NULL. But it needs to be an integer before we can do + ** the seek, so convert it. */ + pIn3 = &aMem[pOp->p3]; + if( (pIn3->flags & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ + applyNumericAffinity(pIn3, 0); + } + iKey = sqlite3VdbeIntValue(pIn3); + + /* If the P3 value could not be converted into an integer without + ** loss of information, then special processing is required... */ + if( (pIn3->flags & MEM_Int)==0 ){ + if( (pIn3->flags & MEM_Real)==0 ){ + /* If the P3 value cannot be converted into any kind of a number, + ** then the seek is not possible, so jump to P2 */ + pc = pOp->p2 - 1; VdbeBranchTaken(1,2); + break; + } + + /* If the approximation iKey is larger than the actual real search + ** term, substitute >= for > and < for <=. e.g. if the search term + ** is 4.9 and the integer approximation 5: + ** + ** (x > 4.9) -> (x >= 5) + ** (x <= 4.9) -> (x < 5) + */ + if( pIn3->u.r<(double)iKey ){ + assert( OP_SeekGE==(OP_SeekGT-1) ); + assert( OP_SeekLT==(OP_SeekLE-1) ); + assert( (OP_SeekLE & 0x0001)==(OP_SeekGT & 0x0001) ); + if( (oc & 0x0001)==(OP_SeekGT & 0x0001) ) oc--; + } + + /* If the approximation iKey is smaller than the actual real search + ** term, substitute <= for < and > for >=. */ + else if( pIn3->u.r>(double)iKey ){ + assert( OP_SeekLE==(OP_SeekLT+1) ); + assert( OP_SeekGT==(OP_SeekGE+1) ); + assert( (OP_SeekLT & 0x0001)==(OP_SeekGE & 0x0001) ); + if( (oc & 0x0001)==(OP_SeekLT & 0x0001) ) oc++; + } + } + rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, 0, (u64)iKey, 0, &res); + pC->movetoTarget = iKey; /* Used by OP_Delete */ + if( rc!=SQLITE_OK ){ + goto abort_due_to_error; + } + }else{ + nField = pOp->p4.i; + assert( pOp->p4type==P4_INT32 ); + assert( nField>0 ); + r.pKeyInfo = pC->pKeyInfo; + r.nField = (u16)nField; + + /* The next line of code computes as follows, only faster: + ** if( oc==OP_SeekGT || oc==OP_SeekLE ){ + ** r.default_rc = -1; + ** }else{ + ** r.default_rc = +1; + ** } + */ + r.default_rc = ((1 & (oc - OP_SeekLT)) ? -1 : +1); + assert( oc!=OP_SeekGT || r.default_rc==-1 ); + assert( oc!=OP_SeekLE || r.default_rc==-1 ); + assert( oc!=OP_SeekGE || r.default_rc==+1 ); + assert( oc!=OP_SeekLT || r.default_rc==+1 ); + + r.aMem = &aMem[pOp->p3]; #ifdef SQLITE_DEBUG - { int i; for(i=0; i<u.bc.r.nField; i++) assert( memIsValid(&u.bc.r.aMem[i]) ); } + { int i; for(i=0; i<r.nField; i++) assert( memIsValid(&r.aMem[i]) ); } #endif - ExpandBlob(u.bc.r.aMem); - rc = sqlite3BtreeMovetoUnpacked(u.bc.pC->pCursor, &u.bc.r, 0, 0, &u.bc.res); - if( rc!=SQLITE_OK ){ - goto abort_due_to_error; - } - u.bc.pC->rowidIsValid = 0; - } - u.bc.pC->deferredMoveto = 0; - u.bc.pC->cacheStatus = CACHE_STALE; + ExpandBlob(r.aMem); + rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, &r, 0, 0, &res); + if( rc!=SQLITE_OK ){ + goto abort_due_to_error; + } + } + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; #ifdef SQLITE_TEST - sqlite3_search_count++; -#endif - if( u.bc.oc>=OP_SeekGe ){ assert( u.bc.oc==OP_SeekGe || u.bc.oc==OP_SeekGt ); - if( u.bc.res<0 || (u.bc.res==0 && u.bc.oc==OP_SeekGt) ){ - rc = sqlite3BtreeNext(u.bc.pC->pCursor, &u.bc.res); - if( rc!=SQLITE_OK ) goto abort_due_to_error; - u.bc.pC->rowidIsValid = 0; - }else{ - u.bc.res = 0; - } - }else{ - assert( u.bc.oc==OP_SeekLt || u.bc.oc==OP_SeekLe ); - if( u.bc.res>0 || (u.bc.res==0 && u.bc.oc==OP_SeekLt) ){ - rc = sqlite3BtreePrevious(u.bc.pC->pCursor, &u.bc.res); - if( rc!=SQLITE_OK ) goto abort_due_to_error; - u.bc.pC->rowidIsValid = 0; - }else{ - /* u.bc.res might be negative because the table is empty. Check to - ** see if this is the case. - */ - u.bc.res = sqlite3BtreeEof(u.bc.pC->pCursor); - } - } - assert( pOp->p2>0 ); - if( u.bc.res ){ - pc = pOp->p2 - 1; - } - }else{ - /* This happens when attempting to open the sqlite3_master table - ** for read access returns SQLITE_EMPTY. In this case always - ** take the jump (since there are no records in the table). - */ + sqlite3_search_count++; +#endif + if( oc>=OP_SeekGE ){ assert( oc==OP_SeekGE || oc==OP_SeekGT ); + if( res<0 || (res==0 && oc==OP_SeekGT) ){ + res = 0; + rc = sqlite3BtreeNext(pC->pCursor, &res); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + }else{ + res = 0; + } + }else{ + assert( oc==OP_SeekLT || oc==OP_SeekLE ); + if( res>0 || (res==0 && oc==OP_SeekLT) ){ + res = 0; + rc = sqlite3BtreePrevious(pC->pCursor, &res); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + }else{ + /* res might be negative because the table is empty. Check to + ** see if this is the case. + */ + res = sqlite3BtreeEof(pC->pCursor); + } + } + assert( pOp->p2>0 ); + VdbeBranchTaken(res!=0,2); + if( res ){ pc = pOp->p2 - 1; } break; } /* Opcode: Seek P1 P2 * * * +** Synopsis: intkey=r[P2] ** ** P1 is an open table cursor and P2 is a rowid integer. Arrange ** for P1 to move so that it points to the rowid given by P2. ** ** This is actually a deferred seek. Nothing actually happens until ** the cursor is used to read a record. That way, if no reads ** occur, no unnecessary I/O happens. */ case OP_Seek: { /* in2 */ -#if 0 /* local variables moved into u.bd */ VdbeCursor *pC; -#endif /* local variables moved into u.bd */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bd.pC = p->apCsr[pOp->p1]; - assert( u.bd.pC!=0 ); - if( ALWAYS(u.bd.pC->pCursor!=0) ){ - assert( u.bd.pC->isTable ); - u.bd.pC->nullRow = 0; - pIn2 = &aMem[pOp->p2]; - u.bd.pC->movetoTarget = sqlite3VdbeIntValue(pIn2); - u.bd.pC->rowidIsValid = 0; - u.bd.pC->deferredMoveto = 1; - } + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->pCursor!=0 ); + assert( pC->isTable ); + pC->nullRow = 0; + pIn2 = &aMem[pOp->p2]; + pC->movetoTarget = sqlite3VdbeIntValue(pIn2); + pC->deferredMoveto = 1; break; } /* Opcode: Found P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] ** ** If P4==0 then register P3 holds a blob constructed by MakeRecord. If ** P4>0 then register P3 is the first of P4 registers that form an unpacked ** record. ** ** Cursor P1 is on an index btree. If the record identified by P3 and P4 ** is a prefix of any entry in P1 then a jump is made to P2 and ** P1 is left pointing at the matching entry. +** +** This operation leaves the cursor in a state where it can be +** advanced in the forward direction. The Next instruction will work, +** but not the Prev instruction. +** +** See also: NotFound, NoConflict, NotExists. SeekGe */ /* Opcode: NotFound P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] ** ** If P4==0 then register P3 holds a blob constructed by MakeRecord. If ** P4>0 then register P3 is the first of P4 registers that form an unpacked ** record. ** @@ -69691,220 +74416,174 @@ ** is not the prefix of any entry in P1 then a jump is made to P2. If P1 ** does contain an entry whose prefix matches the P3/P4 record then control ** falls through to the next instruction and P1 is left pointing at the ** matching entry. ** -** See also: Found, NotExists, IsUnique +** This operation leaves the cursor in a state where it cannot be +** advanced in either direction. In other words, the Next and Prev +** opcodes do not work after this operation. +** +** See also: Found, NotExists, NoConflict */ +/* Opcode: NoConflict P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** If P4==0 then register P3 holds a blob constructed by MakeRecord. If +** P4>0 then register P3 is the first of P4 registers that form an unpacked +** record. +** +** Cursor P1 is on an index btree. If the record identified by P3 and P4 +** contains any NULL value, jump immediately to P2. If all terms of the +** record are not-NULL then a check is done to determine if any row in the +** P1 index btree has a matching key prefix. If there are no matches, jump +** immediately to P2. If there is a match, fall through and leave the P1 +** cursor pointing to the matching row. +** +** This opcode is similar to OP_NotFound with the exceptions that the +** branch is always taken if any part of the search key input is NULL. +** +** This operation leaves the cursor in a state where it cannot be +** advanced in either direction. In other words, the Next and Prev +** opcodes do not work after this operation. +** +** See also: NotFound, Found, NotExists +*/ +case OP_NoConflict: /* jump, in3 */ case OP_NotFound: /* jump, in3 */ case OP_Found: { /* jump, in3 */ -#if 0 /* local variables moved into u.be */ int alreadyExists; + int ii; VdbeCursor *pC; int res; char *pFree; UnpackedRecord *pIdxKey; UnpackedRecord r; - char aTempRec[ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*3 + 7]; -#endif /* local variables moved into u.be */ + char aTempRec[ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*4 + 7]; #ifdef SQLITE_TEST - sqlite3_found_count++; + if( pOp->opcode!=OP_NoConflict ) sqlite3_found_count++; #endif - u.be.alreadyExists = 0; assert( pOp->p1>=0 && pOp->p1<p->nCursor ); assert( pOp->p4type==P4_INT32 ); - u.be.pC = p->apCsr[pOp->p1]; - assert( u.be.pC!=0 ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); +#ifdef SQLITE_DEBUG + pC->seekOp = pOp->opcode; +#endif pIn3 = &aMem[pOp->p3]; - if( ALWAYS(u.be.pC->pCursor!=0) ){ - - assert( u.be.pC->isTable==0 ); - if( pOp->p4.i>0 ){ - u.be.r.pKeyInfo = u.be.pC->pKeyInfo; - u.be.r.nField = (u16)pOp->p4.i; - u.be.r.aMem = pIn3; + assert( pC->pCursor!=0 ); + assert( pC->isTable==0 ); + pFree = 0; /* Not needed. Only used to suppress a compiler warning. */ + if( pOp->p4.i>0 ){ + r.pKeyInfo = pC->pKeyInfo; + r.nField = (u16)pOp->p4.i; + r.aMem = pIn3; + for(ii=0; ii<r.nField; ii++){ + assert( memIsValid(&r.aMem[ii]) ); + ExpandBlob(&r.aMem[ii]); #ifdef SQLITE_DEBUG - { int i; for(i=0; i<u.be.r.nField; i++) assert( memIsValid(&u.be.r.aMem[i]) ); } + if( ii ) REGISTER_TRACE(pOp->p3+ii, &r.aMem[ii]); #endif - u.be.r.flags = UNPACKED_PREFIX_MATCH; - u.be.pIdxKey = &u.be.r; - }else{ - u.be.pIdxKey = sqlite3VdbeAllocUnpackedRecord( - u.be.pC->pKeyInfo, u.be.aTempRec, sizeof(u.be.aTempRec), &u.be.pFree - ); - if( u.be.pIdxKey==0 ) goto no_mem; - assert( pIn3->flags & MEM_Blob ); - assert( (pIn3->flags & MEM_Zero)==0 ); /* zeroblobs already expanded */ - sqlite3VdbeRecordUnpack(u.be.pC->pKeyInfo, pIn3->n, pIn3->z, u.be.pIdxKey); - u.be.pIdxKey->flags |= UNPACKED_PREFIX_MATCH; - } - rc = sqlite3BtreeMovetoUnpacked(u.be.pC->pCursor, u.be.pIdxKey, 0, 0, &u.be.res); - if( pOp->p4.i==0 ){ - sqlite3DbFree(db, u.be.pFree); - } - if( rc!=SQLITE_OK ){ - break; - } - u.be.alreadyExists = (u.be.res==0); - u.be.pC->deferredMoveto = 0; - u.be.pC->cacheStatus = CACHE_STALE; - } + } + pIdxKey = &r; + }else{ + pIdxKey = sqlite3VdbeAllocUnpackedRecord( + pC->pKeyInfo, aTempRec, sizeof(aTempRec), &pFree + ); + if( pIdxKey==0 ) goto no_mem; + assert( pIn3->flags & MEM_Blob ); + ExpandBlob(pIn3); + sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey); + } + pIdxKey->default_rc = 0; + if( pOp->opcode==OP_NoConflict ){ + /* For the OP_NoConflict opcode, take the jump if any of the + ** input fields are NULL, since any key with a NULL will not + ** conflict */ + for(ii=0; ii<pIdxKey->nField; ii++){ + if( pIdxKey->aMem[ii].flags & MEM_Null ){ + pc = pOp->p2 - 1; VdbeBranchTaken(1,2); + break; + } + } + } + rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, pIdxKey, 0, 0, &res); + if( pOp->p4.i==0 ){ + sqlite3DbFree(db, pFree); + } + if( rc!=SQLITE_OK ){ + break; + } + pC->seekResult = res; + alreadyExists = (res==0); + pC->nullRow = 1-alreadyExists; + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; if( pOp->opcode==OP_Found ){ - if( u.be.alreadyExists ) pc = pOp->p2 - 1; - }else{ - if( !u.be.alreadyExists ) pc = pOp->p2 - 1; - } - break; -} - -/* Opcode: IsUnique P1 P2 P3 P4 * -** -** Cursor P1 is open on an index b-tree - that is to say, a btree which -** no data and where the key are records generated by OP_MakeRecord with -** the list field being the integer ROWID of the entry that the index -** entry refers to. -** -** The P3 register contains an integer record number. Call this record -** number R. Register P4 is the first in a set of N contiguous registers -** that make up an unpacked index key that can be used with cursor P1. -** The value of N can be inferred from the cursor. N includes the rowid -** value appended to the end of the index record. This rowid value may -** or may not be the same as R. -** -** If any of the N registers beginning with register P4 contains a NULL -** value, jump immediately to P2. -** -** Otherwise, this instruction checks if cursor P1 contains an entry -** where the first (N-1) fields match but the rowid value at the end -** of the index entry is not R. If there is no such entry, control jumps -** to instruction P2. Otherwise, the rowid of the conflicting index -** entry is copied to register P3 and control falls through to the next -** instruction. -** -** See also: NotFound, NotExists, Found -*/ -case OP_IsUnique: { /* jump, in3 */ -#if 0 /* local variables moved into u.bf */ - u16 ii; - VdbeCursor *pCx; - BtCursor *pCrsr; - u16 nField; - Mem *aMx; - UnpackedRecord r; /* B-Tree index search key */ - i64 R; /* Rowid stored in register P3 */ -#endif /* local variables moved into u.bf */ - - pIn3 = &aMem[pOp->p3]; - u.bf.aMx = &aMem[pOp->p4.i]; - /* Assert that the values of parameters P1 and P4 are in range. */ - assert( pOp->p4type==P4_INT32 ); - assert( pOp->p4.i>0 && pOp->p4.i<=(p->nMem-p->nCursor) ); - assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - - /* Find the index cursor. */ - u.bf.pCx = p->apCsr[pOp->p1]; - assert( u.bf.pCx->deferredMoveto==0 ); - u.bf.pCx->seekResult = 0; - u.bf.pCx->cacheStatus = CACHE_STALE; - u.bf.pCrsr = u.bf.pCx->pCursor; - - /* If any of the values are NULL, take the jump. */ - u.bf.nField = u.bf.pCx->pKeyInfo->nField; - for(u.bf.ii=0; u.bf.ii<u.bf.nField; u.bf.ii++){ - if( u.bf.aMx[u.bf.ii].flags & MEM_Null ){ - pc = pOp->p2 - 1; - u.bf.pCrsr = 0; - break; - } - } - assert( (u.bf.aMx[u.bf.nField].flags & MEM_Null)==0 ); - - if( u.bf.pCrsr!=0 ){ - /* Populate the index search key. */ - u.bf.r.pKeyInfo = u.bf.pCx->pKeyInfo; - u.bf.r.nField = u.bf.nField + 1; - u.bf.r.flags = UNPACKED_PREFIX_SEARCH; - u.bf.r.aMem = u.bf.aMx; -#ifdef SQLITE_DEBUG - { int i; for(i=0; i<u.bf.r.nField; i++) assert( memIsValid(&u.bf.r.aMem[i]) ); } -#endif - - /* Extract the value of u.bf.R from register P3. */ - sqlite3VdbeMemIntegerify(pIn3); - u.bf.R = pIn3->u.i; - - /* Search the B-Tree index. If no conflicting record is found, jump - ** to P2. Otherwise, copy the rowid of the conflicting record to - ** register P3 and fall through to the next instruction. */ - rc = sqlite3BtreeMovetoUnpacked(u.bf.pCrsr, &u.bf.r, 0, 0, &u.bf.pCx->seekResult); - if( (u.bf.r.flags & UNPACKED_PREFIX_SEARCH) || u.bf.r.rowid==u.bf.R ){ - pc = pOp->p2 - 1; - }else{ - pIn3->u.i = u.bf.r.rowid; - } + VdbeBranchTaken(alreadyExists!=0,2); + if( alreadyExists ) pc = pOp->p2 - 1; + }else{ + VdbeBranchTaken(alreadyExists==0,2); + if( !alreadyExists ) pc = pOp->p2 - 1; } break; } /* Opcode: NotExists P1 P2 P3 * * -** -** Use the content of register P3 as an integer key. If a record -** with that key does not exist in table of P1, then jump to P2. -** If the record does exist, then fall through. The cursor is left -** pointing to the record if it exists. -** -** The difference between this operation and NotFound is that this -** operation assumes the key is an integer and that P1 is a table whereas -** NotFound assumes key is a blob constructed from MakeRecord and -** P1 is an index. -** -** See also: Found, NotFound, IsUnique +** Synopsis: intkey=r[P3] +** +** P1 is the index of a cursor open on an SQL table btree (with integer +** keys). P3 is an integer rowid. If P1 does not contain a record with +** rowid P3 then jump immediately to P2. If P1 does contain a record +** with rowid P3 then leave the cursor pointing at that record and fall +** through to the next instruction. +** +** The OP_NotFound opcode performs the same operation on index btrees +** (with arbitrary multi-value keys). +** +** This opcode leaves the cursor in a state where it cannot be advanced +** in either direction. In other words, the Next and Prev opcodes will +** not work following this opcode. +** +** See also: Found, NotFound, NoConflict */ case OP_NotExists: { /* jump, in3 */ -#if 0 /* local variables moved into u.bg */ VdbeCursor *pC; BtCursor *pCrsr; int res; u64 iKey; -#endif /* local variables moved into u.bg */ pIn3 = &aMem[pOp->p3]; assert( pIn3->flags & MEM_Int ); assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bg.pC = p->apCsr[pOp->p1]; - assert( u.bg.pC!=0 ); - assert( u.bg.pC->isTable ); - assert( u.bg.pC->pseudoTableReg==0 ); - u.bg.pCrsr = u.bg.pC->pCursor; - if( ALWAYS(u.bg.pCrsr!=0) ){ - u.bg.res = 0; - u.bg.iKey = pIn3->u.i; - rc = sqlite3BtreeMovetoUnpacked(u.bg.pCrsr, 0, u.bg.iKey, 0, &u.bg.res); - u.bg.pC->lastRowid = pIn3->u.i; - u.bg.pC->rowidIsValid = u.bg.res==0 ?1:0; - u.bg.pC->nullRow = 0; - u.bg.pC->cacheStatus = CACHE_STALE; - u.bg.pC->deferredMoveto = 0; - if( u.bg.res!=0 ){ - pc = pOp->p2 - 1; - assert( u.bg.pC->rowidIsValid==0 ); - } - u.bg.pC->seekResult = u.bg.res; - }else{ - /* This happens when an attempt to open a read cursor on the - ** sqlite_master table returns SQLITE_EMPTY. - */ - pc = pOp->p2 - 1; - assert( u.bg.pC->rowidIsValid==0 ); - u.bg.pC->seekResult = 0; - } + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); +#ifdef SQLITE_DEBUG + pC->seekOp = 0; +#endif + assert( pC->isTable ); + assert( pC->pseudoTableReg==0 ); + pCrsr = pC->pCursor; + assert( pCrsr!=0 ); + res = 0; + iKey = pIn3->u.i; + rc = sqlite3BtreeMovetoUnpacked(pCrsr, 0, iKey, 0, &res); + pC->movetoTarget = iKey; /* Used by OP_Delete */ + pC->nullRow = 0; + pC->cacheStatus = CACHE_STALE; + pC->deferredMoveto = 0; + VdbeBranchTaken(res!=0,2); + if( res!=0 ){ + pc = pOp->p2 - 1; + } + pC->seekResult = res; break; } /* Opcode: Sequence P1 P2 * * * +** Synopsis: r[P2]=cursor[P1].ctr++ ** ** Find the next available sequence number for cursor P1. ** Write the sequence number into register P2. ** The sequence number on the cursor is incremented after this ** instruction. @@ -69916,10 +74595,11 @@ break; } /* Opcode: NewRowid P1 P2 P3 * * +** Synopsis: r[P2]=rowid ** ** Get a new integer record number (a.k.a "rowid") used as the key to a table. ** The record number is not previously used as a key in the database ** table that cursor P1 points to. The new record number is written ** written to register P2. @@ -69930,25 +74610,23 @@ ** an SQLITE_FULL error is generated. The P3 register is updated with the ' ** generated record number. This P3 mechanism is used to help implement the ** AUTOINCREMENT feature. */ case OP_NewRowid: { /* out2-prerelease */ -#if 0 /* local variables moved into u.bh */ i64 v; /* The new rowid */ VdbeCursor *pC; /* Cursor of table to get the new rowid */ int res; /* Result of an sqlite3BtreeLast() */ int cnt; /* Counter to limit the number of searches */ Mem *pMem; /* Register holding largest rowid for AUTOINCREMENT */ VdbeFrame *pFrame; /* Root frame of VDBE */ -#endif /* local variables moved into u.bh */ - u.bh.v = 0; - u.bh.res = 0; + v = 0; + res = 0; assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bh.pC = p->apCsr[pOp->p1]; - assert( u.bh.pC!=0 ); - if( NEVER(u.bh.pC->pCursor==0) ){ + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + if( NEVER(pC->pCursor==0) ){ /* The zero initialization above is all that is needed */ }else{ /* The next rowid or record number (different terms for the same ** thing) is obtained in a two-step algorithm. ** @@ -69960,11 +74638,11 @@ ** The second algorithm is to select a rowid at random and see if ** it already exists in the table. If it does not exist, we have ** succeeded. If the random rowid does exist, we select a new one ** and try again, up to 100 times. */ - assert( u.bh.pC->isTable ); + assert( pC->isTable ); #ifdef SQLITE_32BIT_ROWID # define MAX_ROWID 0x7fffffff #else /* Some compilers complain about constants of the form 0x7fffffffffffffff. @@ -69972,105 +74650,89 @@ ** to provide the constant while making all compilers happy. */ # define MAX_ROWID (i64)( (((u64)0x7fffffff)<<32) | (u64)0xffffffff ) #endif - if( !u.bh.pC->useRandomRowid ){ - u.bh.v = sqlite3BtreeGetCachedRowid(u.bh.pC->pCursor); - if( u.bh.v==0 ){ - rc = sqlite3BtreeLast(u.bh.pC->pCursor, &u.bh.res); - if( rc!=SQLITE_OK ){ - goto abort_due_to_error; - } - if( u.bh.res ){ - u.bh.v = 1; /* IMP: R-61914-48074 */ - }else{ - assert( sqlite3BtreeCursorIsValid(u.bh.pC->pCursor) ); - rc = sqlite3BtreeKeySize(u.bh.pC->pCursor, &u.bh.v); - assert( rc==SQLITE_OK ); /* Cannot fail following BtreeLast() */ - if( u.bh.v>=MAX_ROWID ){ - u.bh.pC->useRandomRowid = 1; - }else{ - u.bh.v++; /* IMP: R-29538-34987 */ - } - } - } - -#ifndef SQLITE_OMIT_AUTOINCREMENT - if( pOp->p3 ){ - /* Assert that P3 is a valid memory cell. */ - assert( pOp->p3>0 ); - if( p->pFrame ){ - for(u.bh.pFrame=p->pFrame; u.bh.pFrame->pParent; u.bh.pFrame=u.bh.pFrame->pParent); - /* Assert that P3 is a valid memory cell. */ - assert( pOp->p3<=u.bh.pFrame->nMem ); - u.bh.pMem = &u.bh.pFrame->aMem[pOp->p3]; - }else{ - /* Assert that P3 is a valid memory cell. */ - assert( pOp->p3<=(p->nMem-p->nCursor) ); - u.bh.pMem = &aMem[pOp->p3]; - memAboutToChange(p, u.bh.pMem); - } - assert( memIsValid(u.bh.pMem) ); - - REGISTER_TRACE(pOp->p3, u.bh.pMem); - sqlite3VdbeMemIntegerify(u.bh.pMem); - assert( (u.bh.pMem->flags & MEM_Int)!=0 ); /* mem(P3) holds an integer */ - if( u.bh.pMem->u.i==MAX_ROWID || u.bh.pC->useRandomRowid ){ - rc = SQLITE_FULL; /* IMP: R-12275-61338 */ - goto abort_due_to_error; - } - if( u.bh.v<u.bh.pMem->u.i+1 ){ - u.bh.v = u.bh.pMem->u.i + 1; - } - u.bh.pMem->u.i = u.bh.v; - } -#endif - - sqlite3BtreeSetCachedRowid(u.bh.pC->pCursor, u.bh.v<MAX_ROWID ? u.bh.v+1 : 0); - } - if( u.bh.pC->useRandomRowid ){ + if( !pC->useRandomRowid ){ + rc = sqlite3BtreeLast(pC->pCursor, &res); + if( rc!=SQLITE_OK ){ + goto abort_due_to_error; + } + if( res ){ + v = 1; /* IMP: R-61914-48074 */ + }else{ + assert( sqlite3BtreeCursorIsValid(pC->pCursor) ); + rc = sqlite3BtreeKeySize(pC->pCursor, &v); + assert( rc==SQLITE_OK ); /* Cannot fail following BtreeLast() */ + if( v>=MAX_ROWID ){ + pC->useRandomRowid = 1; + }else{ + v++; /* IMP: R-29538-34987 */ + } + } + } + +#ifndef SQLITE_OMIT_AUTOINCREMENT + if( pOp->p3 ){ + /* Assert that P3 is a valid memory cell. */ + assert( pOp->p3>0 ); + if( p->pFrame ){ + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + /* Assert that P3 is a valid memory cell. */ + assert( pOp->p3<=pFrame->nMem ); + pMem = &pFrame->aMem[pOp->p3]; + }else{ + /* Assert that P3 is a valid memory cell. */ + assert( pOp->p3<=(p->nMem-p->nCursor) ); + pMem = &aMem[pOp->p3]; + memAboutToChange(p, pMem); + } + assert( memIsValid(pMem) ); + + REGISTER_TRACE(pOp->p3, pMem); + sqlite3VdbeMemIntegerify(pMem); + assert( (pMem->flags & MEM_Int)!=0 ); /* mem(P3) holds an integer */ + if( pMem->u.i==MAX_ROWID || pC->useRandomRowid ){ + rc = SQLITE_FULL; /* IMP: R-12275-61338 */ + goto abort_due_to_error; + } + if( v<pMem->u.i+1 ){ + v = pMem->u.i + 1; + } + pMem->u.i = v; + } +#endif + if( pC->useRandomRowid ){ /* IMPLEMENTATION-OF: R-07677-41881 If the largest ROWID is equal to the ** largest possible integer (9223372036854775807) then the database ** engine starts picking positive candidate ROWIDs at random until ** it finds one that is not previously used. */ assert( pOp->p3==0 ); /* We cannot be in random rowid mode if this is ** an AUTOINCREMENT table. */ - /* on the first attempt, simply do one more than previous */ - u.bh.v = lastRowid; - u.bh.v &= (MAX_ROWID>>1); /* ensure doesn't go negative */ - u.bh.v++; /* ensure non-zero */ - u.bh.cnt = 0; - while( ((rc = sqlite3BtreeMovetoUnpacked(u.bh.pC->pCursor, 0, (u64)u.bh.v, - 0, &u.bh.res))==SQLITE_OK) - && (u.bh.res==0) - && (++u.bh.cnt<100)){ - /* collision - try another random rowid */ - sqlite3_randomness(sizeof(u.bh.v), &u.bh.v); - if( u.bh.cnt<5 ){ - /* try "small" random rowids for the initial attempts */ - u.bh.v &= 0xffffff; - }else{ - u.bh.v &= (MAX_ROWID>>1); /* ensure doesn't go negative */ - } - u.bh.v++; /* ensure non-zero */ - } - if( rc==SQLITE_OK && u.bh.res==0 ){ + cnt = 0; + do{ + sqlite3_randomness(sizeof(v), &v); + v &= (MAX_ROWID>>1); v++; /* Ensure that v is greater than zero */ + }while( ((rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, 0, (u64)v, + 0, &res))==SQLITE_OK) + && (res==0) + && (++cnt<100)); + if( rc==SQLITE_OK && res==0 ){ rc = SQLITE_FULL; /* IMP: R-38219-53002 */ goto abort_due_to_error; } - assert( u.bh.v>0 ); /* EV: R-40812-03570 */ + assert( v>0 ); /* EV: R-40812-03570 */ } - u.bh.pC->rowidIsValid = 0; - u.bh.pC->deferredMoveto = 0; - u.bh.pC->cacheStatus = CACHE_STALE; + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; } - pOut->u.i = u.bh.v; + pOut->u.i = v; break; } /* Opcode: Insert P1 P2 P3 P4 P5 +** Synopsis: intkey=r[P3] data=r[P2] ** ** Write an entry into the table of cursor P1. A new entry is ** created if it doesn't already exist or the data for an existing ** entry is overwritten. The data is the value MEM_Blob stored in register ** number P2. The key is stored in register P3. The key must @@ -70106,80 +74768,77 @@ ** ** This instruction only works on tables. The equivalent instruction ** for indices is OP_IdxInsert. */ /* Opcode: InsertInt P1 P2 P3 P4 P5 +** Synopsis: intkey=P3 data=r[P2] ** ** This works exactly like OP_Insert except that the key is the ** integer value P3, not the value of the integer stored in register P3. */ case OP_Insert: case OP_InsertInt: { -#if 0 /* local variables moved into u.bi */ Mem *pData; /* MEM cell holding data for the record to be inserted */ Mem *pKey; /* MEM cell holding key for the record */ i64 iKey; /* The integer ROWID or key for the record to be inserted */ VdbeCursor *pC; /* Cursor to table into which insert is written */ int nZero; /* Number of zero-bytes to append */ int seekResult; /* Result of prior seek or 0 if no USESEEKRESULT flag */ const char *zDb; /* database name - used by the update hook */ const char *zTbl; /* Table name - used by the opdate hook */ int op; /* Opcode for update hook: SQLITE_UPDATE or SQLITE_INSERT */ -#endif /* local variables moved into u.bi */ - u.bi.pData = &aMem[pOp->p2]; + pData = &aMem[pOp->p2]; assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - assert( memIsValid(u.bi.pData) ); - u.bi.pC = p->apCsr[pOp->p1]; - assert( u.bi.pC!=0 ); - assert( u.bi.pC->pCursor!=0 ); - assert( u.bi.pC->pseudoTableReg==0 ); - assert( u.bi.pC->isTable ); - REGISTER_TRACE(pOp->p2, u.bi.pData); + assert( memIsValid(pData) ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->pCursor!=0 ); + assert( pC->pseudoTableReg==0 ); + assert( pC->isTable ); + REGISTER_TRACE(pOp->p2, pData); if( pOp->opcode==OP_Insert ){ - u.bi.pKey = &aMem[pOp->p3]; - assert( u.bi.pKey->flags & MEM_Int ); - assert( memIsValid(u.bi.pKey) ); - REGISTER_TRACE(pOp->p3, u.bi.pKey); - u.bi.iKey = u.bi.pKey->u.i; + pKey = &aMem[pOp->p3]; + assert( pKey->flags & MEM_Int ); + assert( memIsValid(pKey) ); + REGISTER_TRACE(pOp->p3, pKey); + iKey = pKey->u.i; }else{ assert( pOp->opcode==OP_InsertInt ); - u.bi.iKey = pOp->p3; + iKey = pOp->p3; } if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; - if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = lastRowid = u.bi.iKey; - if( u.bi.pData->flags & MEM_Null ){ - u.bi.pData->z = 0; - u.bi.pData->n = 0; - }else{ - assert( u.bi.pData->flags & (MEM_Blob|MEM_Str) ); - } - u.bi.seekResult = ((pOp->p5 & OPFLAG_USESEEKRESULT) ? u.bi.pC->seekResult : 0); - if( u.bi.pData->flags & MEM_Zero ){ - u.bi.nZero = u.bi.pData->u.nZero; - }else{ - u.bi.nZero = 0; - } - sqlite3BtreeSetCachedRowid(u.bi.pC->pCursor, 0); - rc = sqlite3BtreeInsert(u.bi.pC->pCursor, 0, u.bi.iKey, - u.bi.pData->z, u.bi.pData->n, u.bi.nZero, - pOp->p5 & OPFLAG_APPEND, u.bi.seekResult - ); - u.bi.pC->rowidIsValid = 0; - u.bi.pC->deferredMoveto = 0; - u.bi.pC->cacheStatus = CACHE_STALE; + if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = lastRowid = iKey; + if( pData->flags & MEM_Null ){ + pData->z = 0; + pData->n = 0; + }else{ + assert( pData->flags & (MEM_Blob|MEM_Str) ); + } + seekResult = ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0); + if( pData->flags & MEM_Zero ){ + nZero = pData->u.nZero; + }else{ + nZero = 0; + } + rc = sqlite3BtreeInsert(pC->pCursor, 0, iKey, + pData->z, pData->n, nZero, + (pOp->p5 & OPFLAG_APPEND)!=0, seekResult + ); + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; /* Invoke the update-hook if required. */ if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p4.z ){ - u.bi.zDb = db->aDb[u.bi.pC->iDb].zName; - u.bi.zTbl = pOp->p4.z; - u.bi.op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT); - assert( u.bi.pC->isTable ); - db->xUpdateCallback(db->pUpdateArg, u.bi.op, u.bi.zDb, u.bi.zTbl, u.bi.iKey); - assert( u.bi.pC->iDb>=0 ); + zDb = db->aDb[pC->iDb].zName; + zTbl = pOp->p4.z; + op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT); + assert( pC->isTable ); + db->xUpdateCallback(db->pUpdateArg, op, zDb, zTbl, iKey); + assert( pC->iDb>=0 ); } break; } /* Opcode: Delete P1 P2 * P4 * @@ -70187,11 +74846,11 @@ ** Delete the record at which the P1 cursor is currently pointing. ** ** The cursor will be left pointing at either the next or the previous ** record in the table. If it is left pointing at the next record, then ** the next Next instruction will be a no-op. Hence it is OK to delete -** a record from within an Next loop. +** a record from within a Next loop. ** ** If the OPFLAG_NCHANGE flag of P2 is set, then the row change count is ** incremented (otherwise not). ** ** P1 must not be pseudo-table. It has to be a real table with @@ -70201,51 +74860,37 @@ ** pointing to. The update hook will be invoked, if it exists. ** If P4 is not NULL then the P1 cursor must have been positioned ** using OP_NotFound prior to invoking this opcode. */ case OP_Delete: { -#if 0 /* local variables moved into u.bj */ - i64 iKey; VdbeCursor *pC; -#endif /* local variables moved into u.bj */ - u.bj.iKey = 0; assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bj.pC = p->apCsr[pOp->p1]; - assert( u.bj.pC!=0 ); - assert( u.bj.pC->pCursor!=0 ); /* Only valid for real tables, no pseudotables */ - - /* If the update-hook will be invoked, set u.bj.iKey to the rowid of the - ** row being deleted. - */ - if( db->xUpdateCallback && pOp->p4.z ){ - assert( u.bj.pC->isTable ); - assert( u.bj.pC->rowidIsValid ); /* lastRowid set by previous OP_NotFound */ - u.bj.iKey = u.bj.pC->lastRowid; - } - - /* The OP_Delete opcode always follows an OP_NotExists or OP_Last or - ** OP_Column on the same table without any intervening operations that - ** might move or invalidate the cursor. Hence cursor u.bj.pC is always pointing - ** to the row to be deleted and the sqlite3VdbeCursorMoveto() operation - ** below is always a no-op and cannot fail. We will run it anyhow, though, - ** to guard against future changes to the code generator. - **/ - assert( u.bj.pC->deferredMoveto==0 ); - rc = sqlite3VdbeCursorMoveto(u.bj.pC); - if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error; - - sqlite3BtreeSetCachedRowid(u.bj.pC->pCursor, 0); - rc = sqlite3BtreeDelete(u.bj.pC->pCursor); - u.bj.pC->cacheStatus = CACHE_STALE; + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->pCursor!=0 ); /* Only valid for real tables, no pseudotables */ + assert( pC->deferredMoveto==0 ); + +#ifdef SQLITE_DEBUG + /* The seek operation that positioned the cursor prior to OP_Delete will + ** have also set the pC->movetoTarget field to the rowid of the row that + ** is being deleted */ + if( pOp->p4.z && pC->isTable ){ + i64 iKey = 0; + sqlite3BtreeKeySize(pC->pCursor, &iKey); + assert( pC->movetoTarget==iKey ); + } +#endif + + rc = sqlite3BtreeDelete(pC->pCursor); + pC->cacheStatus = CACHE_STALE; /* Invoke the update-hook if required. */ - if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p4.z ){ - const char *zDb = db->aDb[u.bj.pC->iDb].zName; - const char *zTbl = pOp->p4.z; - db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, zTbl, u.bj.iKey); - assert( u.bj.pC->iDb>=0 ); + if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p4.z && pC->isTable ){ + db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, + db->aDb[pC->iDb].zName, pOp->p4.z, pC->movetoTarget); + assert( pC->iDb>=0 ); } if( pOp->p2 & OPFLAG_NCHANGE ) p->nChange++; break; } /* Opcode: ResetCount * * * * * @@ -70259,50 +74904,71 @@ sqlite3VdbeSetChanges(db, p->nChange); p->nChange = 0; break; } -/* Opcode: SorterCompare P1 P2 P3 +/* Opcode: SorterCompare P1 P2 P3 P4 +** Synopsis: if key(P1)!=trim(r[P3],P4) goto P2 ** -** P1 is a sorter cursor. This instruction compares the record blob in -** register P3 with the entry that the sorter cursor currently points to. -** If, excluding the rowid fields at the end, the two records are a match, -** fall through to the next instruction. Otherwise, jump to instruction P2. +** P1 is a sorter cursor. This instruction compares a prefix of the +** record blob in register P3 against a prefix of the entry that +** the sorter cursor currently points to. Only the first P4 fields +** of r[P3] and the sorter record are compared. +** +** If either P3 or the sorter contains a NULL in one of their significant +** fields (not counting the P4 fields at the end which are ignored) then +** the comparison is assumed to be equal. +** +** Fall through to next instruction if the two records compare equal to +** each other. Jump to P2 if they are different. */ case OP_SorterCompare: { -#if 0 /* local variables moved into u.bk */ VdbeCursor *pC; int res; -#endif /* local variables moved into u.bk */ + int nKeyCol; - u.bk.pC = p->apCsr[pOp->p1]; - assert( isSorter(u.bk.pC) ); + pC = p->apCsr[pOp->p1]; + assert( isSorter(pC) ); + assert( pOp->p4type==P4_INT32 ); pIn3 = &aMem[pOp->p3]; - rc = sqlite3VdbeSorterCompare(u.bk.pC, pIn3, &u.bk.res); - if( u.bk.res ){ + nKeyCol = pOp->p4.i; + res = 0; + rc = sqlite3VdbeSorterCompare(pC, pIn3, nKeyCol, &res); + VdbeBranchTaken(res!=0,2); + if( res ){ pc = pOp->p2-1; } break; }; -/* Opcode: SorterData P1 P2 * * * +/* Opcode: SorterData P1 P2 P3 * * +** Synopsis: r[P2]=data ** ** Write into register P2 the current sorter data for sorter cursor P1. +** Then clear the column header cache on cursor P3. +** +** This opcode is normally use to move a record out of the sorter and into +** a register that is the source for a pseudo-table cursor created using +** OpenPseudo. That pseudo-table cursor is the one that is identified by +** parameter P3. Clearing the P3 column cache as part of this opcode saves +** us from having to issue a separate NullRow instruction to clear that cache. */ case OP_SorterData: { -#if 0 /* local variables moved into u.bl */ VdbeCursor *pC; -#endif /* local variables moved into u.bl */ pOut = &aMem[pOp->p2]; - u.bl.pC = p->apCsr[pOp->p1]; - assert( u.bl.pC->isSorter ); - rc = sqlite3VdbeSorterRowkey(u.bl.pC, pOut); + pC = p->apCsr[pOp->p1]; + assert( isSorter(pC) ); + rc = sqlite3VdbeSorterRowkey(pC, pOut); + assert( rc!=SQLITE_OK || (pOut->flags & MEM_Blob) ); + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + p->apCsr[pOp->p3]->cacheStatus = CACHE_STALE; break; } /* Opcode: RowData P1 P2 * * * +** Synopsis: r[P2]=data ** ** Write into register P2 the complete row data for cursor P1. ** There is no interpretation of the data. ** It is just copied onto the P2 register exactly as ** it is found in the database file. @@ -70309,129 +74975,133 @@ ** ** If the P1 cursor must be pointing to a valid row (not a NULL row) ** of a real table, not a pseudo-table. */ /* Opcode: RowKey P1 P2 * * * +** Synopsis: r[P2]=key ** ** Write into register P2 the complete row key for cursor P1. ** There is no interpretation of the data. -** The key is copied onto the P3 register exactly as +** The key is copied onto the P2 register exactly as ** it is found in the database file. ** ** If the P1 cursor must be pointing to a valid row (not a NULL row) ** of a real table, not a pseudo-table. */ case OP_RowKey: case OP_RowData: { -#if 0 /* local variables moved into u.bm */ VdbeCursor *pC; BtCursor *pCrsr; u32 n; i64 n64; -#endif /* local variables moved into u.bm */ pOut = &aMem[pOp->p2]; memAboutToChange(p, pOut); /* Note that RowKey and RowData are really exactly the same instruction */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bm.pC = p->apCsr[pOp->p1]; - assert( u.bm.pC->isSorter==0 ); - assert( u.bm.pC->isTable || pOp->opcode!=OP_RowData ); - assert( u.bm.pC->isIndex || pOp->opcode==OP_RowData ); - assert( u.bm.pC!=0 ); - assert( u.bm.pC->nullRow==0 ); - assert( u.bm.pC->pseudoTableReg==0 ); - assert( u.bm.pC->pCursor!=0 ); - u.bm.pCrsr = u.bm.pC->pCursor; - assert( sqlite3BtreeCursorIsValid(u.bm.pCrsr) ); + pC = p->apCsr[pOp->p1]; + assert( isSorter(pC)==0 ); + assert( pC->isTable || pOp->opcode!=OP_RowData ); + assert( pC->isTable==0 || pOp->opcode==OP_RowData ); + assert( pC!=0 ); + assert( pC->nullRow==0 ); + assert( pC->pseudoTableReg==0 ); + assert( pC->pCursor!=0 ); + pCrsr = pC->pCursor; /* The OP_RowKey and OP_RowData opcodes always follow OP_NotExists or ** OP_Rewind/Op_Next with no intervening instructions that might invalidate - ** the cursor. Hence the following sqlite3VdbeCursorMoveto() call is always - ** a no-op and can never fail. But we leave it in place as a safety. - */ - assert( u.bm.pC->deferredMoveto==0 ); - rc = sqlite3VdbeCursorMoveto(u.bm.pC); - if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error; - - if( u.bm.pC->isIndex ){ - assert( !u.bm.pC->isTable ); - VVA_ONLY(rc =) sqlite3BtreeKeySize(u.bm.pCrsr, &u.bm.n64); - assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */ - if( u.bm.n64>db->aLimit[SQLITE_LIMIT_LENGTH] ){ - goto too_big; - } - u.bm.n = (u32)u.bm.n64; - }else{ - VVA_ONLY(rc =) sqlite3BtreeDataSize(u.bm.pCrsr, &u.bm.n); - assert( rc==SQLITE_OK ); /* DataSize() cannot fail */ - if( u.bm.n>(u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ - goto too_big; - } - } - if( sqlite3VdbeMemGrow(pOut, u.bm.n, 0) ){ - goto no_mem; - } - pOut->n = u.bm.n; - MemSetTypeFlag(pOut, MEM_Blob); - if( u.bm.pC->isIndex ){ - rc = sqlite3BtreeKey(u.bm.pCrsr, 0, u.bm.n, pOut->z); - }else{ - rc = sqlite3BtreeData(u.bm.pCrsr, 0, u.bm.n, pOut->z); - } - pOut->enc = SQLITE_UTF8; /* In case the blob is ever cast to text */ - UPDATE_MAX_BLOBSIZE(pOut); + ** the cursor. If this where not the case, on of the following assert()s + ** would fail. Should this ever change (because of changes in the code + ** generator) then the fix would be to insert a call to + ** sqlite3VdbeCursorMoveto(). + */ + assert( pC->deferredMoveto==0 ); + assert( sqlite3BtreeCursorIsValid(pCrsr) ); +#if 0 /* Not required due to the previous to assert() statements */ + rc = sqlite3VdbeCursorMoveto(pC); + if( rc!=SQLITE_OK ) goto abort_due_to_error; +#endif + + if( pC->isTable==0 ){ + assert( !pC->isTable ); + VVA_ONLY(rc =) sqlite3BtreeKeySize(pCrsr, &n64); + assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */ + if( n64>db->aLimit[SQLITE_LIMIT_LENGTH] ){ + goto too_big; + } + n = (u32)n64; + }else{ + VVA_ONLY(rc =) sqlite3BtreeDataSize(pCrsr, &n); + assert( rc==SQLITE_OK ); /* DataSize() cannot fail */ + if( n>(u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ + goto too_big; + } + } + testcase( n==0 ); + if( sqlite3VdbeMemClearAndResize(pOut, MAX(n,32)) ){ + goto no_mem; + } + pOut->n = n; + MemSetTypeFlag(pOut, MEM_Blob); + if( pC->isTable==0 ){ + rc = sqlite3BtreeKey(pCrsr, 0, n, pOut->z); + }else{ + rc = sqlite3BtreeData(pCrsr, 0, n, pOut->z); + } + pOut->enc = SQLITE_UTF8; /* In case the blob is ever cast to text */ + UPDATE_MAX_BLOBSIZE(pOut); + REGISTER_TRACE(pOp->p2, pOut); break; } /* Opcode: Rowid P1 P2 * * * +** Synopsis: r[P2]=rowid ** ** Store in register P2 an integer which is the key of the table entry that ** P1 is currently point to. ** ** P1 can be either an ordinary table or a virtual table. There used to ** be a separate OP_VRowid opcode for use with virtual tables, but this ** one opcode now works for both table types. */ case OP_Rowid: { /* out2-prerelease */ -#if 0 /* local variables moved into u.bn */ VdbeCursor *pC; i64 v; sqlite3_vtab *pVtab; const sqlite3_module *pModule; -#endif /* local variables moved into u.bn */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bn.pC = p->apCsr[pOp->p1]; - assert( u.bn.pC!=0 ); - assert( u.bn.pC->pseudoTableReg==0 || u.bn.pC->nullRow ); - if( u.bn.pC->nullRow ){ + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->pseudoTableReg==0 || pC->nullRow ); + if( pC->nullRow ){ pOut->flags = MEM_Null; break; - }else if( u.bn.pC->deferredMoveto ){ - u.bn.v = u.bn.pC->movetoTarget; + }else if( pC->deferredMoveto ){ + v = pC->movetoTarget; #ifndef SQLITE_OMIT_VIRTUALTABLE - }else if( u.bn.pC->pVtabCursor ){ - u.bn.pVtab = u.bn.pC->pVtabCursor->pVtab; - u.bn.pModule = u.bn.pVtab->pModule; - assert( u.bn.pModule->xRowid ); - rc = u.bn.pModule->xRowid(u.bn.pC->pVtabCursor, &u.bn.v); - sqlite3VtabImportErrmsg(p, u.bn.pVtab); + }else if( pC->pVtabCursor ){ + pVtab = pC->pVtabCursor->pVtab; + pModule = pVtab->pModule; + assert( pModule->xRowid ); + rc = pModule->xRowid(pC->pVtabCursor, &v); + sqlite3VtabImportErrmsg(p, pVtab); #endif /* SQLITE_OMIT_VIRTUALTABLE */ }else{ - assert( u.bn.pC->pCursor!=0 ); - rc = sqlite3VdbeCursorMoveto(u.bn.pC); + assert( pC->pCursor!=0 ); + rc = sqlite3VdbeCursorRestore(pC); if( rc ) goto abort_due_to_error; - if( u.bn.pC->rowidIsValid ){ - u.bn.v = u.bn.pC->lastRowid; - }else{ - rc = sqlite3BtreeKeySize(u.bn.pC->pCursor, &u.bn.v); - assert( rc==SQLITE_OK ); /* Always so because of CursorMoveto() above */ - } - } - pOut->u.i = u.bn.v; + if( pC->nullRow ){ + pOut->flags = MEM_Null; + break; + } + rc = sqlite3BtreeKeySize(pC->pCursor, &v); + assert( rc==SQLITE_OK ); /* Always so because of CursorRestore() above */ + } + pOut->u.i = v; break; } /* Opcode: NullRow P1 * * * * ** @@ -70438,55 +75108,56 @@ ** Move the cursor P1 to a null row. Any OP_Column operations ** that occur while the cursor is on the null row will always ** write a NULL. */ case OP_NullRow: { -#if 0 /* local variables moved into u.bo */ VdbeCursor *pC; -#endif /* local variables moved into u.bo */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bo.pC = p->apCsr[pOp->p1]; - assert( u.bo.pC!=0 ); - u.bo.pC->nullRow = 1; - u.bo.pC->rowidIsValid = 0; - assert( u.bo.pC->pCursor || u.bo.pC->pVtabCursor ); - if( u.bo.pC->pCursor ){ - sqlite3BtreeClearCursor(u.bo.pC->pCursor); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + pC->nullRow = 1; + pC->cacheStatus = CACHE_STALE; + if( pC->pCursor ){ + sqlite3BtreeClearCursor(pC->pCursor); } break; } /* Opcode: Last P1 P2 * * * ** -** The next use of the Rowid or Column or Next instruction for P1 +** The next use of the Rowid or Column or Prev instruction for P1 ** will refer to the last entry in the database table or index. ** If the table or index is empty and P2>0, then jump immediately to P2. ** If P2 is 0 or if the table or index is not empty, fall through ** to the following instruction. +** +** This opcode leaves the cursor configured to move in reverse order, +** from the end toward the beginning. In other words, the cursor is +** configured to use Prev, not Next. */ case OP_Last: { /* jump */ -#if 0 /* local variables moved into u.bp */ VdbeCursor *pC; BtCursor *pCrsr; int res; -#endif /* local variables moved into u.bp */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bp.pC = p->apCsr[pOp->p1]; - assert( u.bp.pC!=0 ); - u.bp.pCrsr = u.bp.pC->pCursor; - u.bp.res = 0; - if( ALWAYS(u.bp.pCrsr!=0) ){ - rc = sqlite3BtreeLast(u.bp.pCrsr, &u.bp.res); - } - u.bp.pC->nullRow = (u8)u.bp.res; - u.bp.pC->deferredMoveto = 0; - u.bp.pC->rowidIsValid = 0; - u.bp.pC->cacheStatus = CACHE_STALE; - if( pOp->p2>0 && u.bp.res ){ - pc = pOp->p2 - 1; + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + pCrsr = pC->pCursor; + res = 0; + assert( pCrsr!=0 ); + rc = sqlite3BtreeLast(pCrsr, &res); + pC->nullRow = (u8)res; + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; +#ifdef SQLITE_DEBUG + pC->seekOp = OP_Last; +#endif + if( pOp->p2>0 ){ + VdbeBranchTaken(res!=0,2); + if( res ) pc = pOp->p2 - 1; } break; } @@ -70513,302 +75184,387 @@ } /* Opcode: Rewind P1 P2 * * * ** ** The next use of the Rowid or Column or Next instruction for P1 ** will refer to the first entry in the database table or index. -** If the table or index is empty and P2>0, then jump immediately to P2. -** If P2 is 0 or if the table or index is not empty, fall through -** to the following instruction. +** If the table or index is empty, jump immediately to P2. +** If the table or index is not empty, fall through to the following +** instruction. +** +** This opcode leaves the cursor configured to move in forward order, +** from the beginning toward the end. In other words, the cursor is +** configured to use Next, not Prev. */ case OP_Rewind: { /* jump */ -#if 0 /* local variables moved into u.bq */ VdbeCursor *pC; BtCursor *pCrsr; int res; -#endif /* local variables moved into u.bq */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bq.pC = p->apCsr[pOp->p1]; - assert( u.bq.pC!=0 ); - assert( u.bq.pC->isSorter==(pOp->opcode==OP_SorterSort) ); - u.bq.res = 1; - if( isSorter(u.bq.pC) ){ - rc = sqlite3VdbeSorterRewind(db, u.bq.pC, &u.bq.res); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( isSorter(pC)==(pOp->opcode==OP_SorterSort) ); + res = 1; +#ifdef SQLITE_DEBUG + pC->seekOp = OP_Rewind; +#endif + if( isSorter(pC) ){ + rc = sqlite3VdbeSorterRewind(pC, &res); }else{ - u.bq.pCrsr = u.bq.pC->pCursor; - assert( u.bq.pCrsr ); - rc = sqlite3BtreeFirst(u.bq.pCrsr, &u.bq.res); - u.bq.pC->atFirst = u.bq.res==0 ?1:0; - u.bq.pC->deferredMoveto = 0; - u.bq.pC->cacheStatus = CACHE_STALE; - u.bq.pC->rowidIsValid = 0; - } - u.bq.pC->nullRow = (u8)u.bq.res; + pCrsr = pC->pCursor; + assert( pCrsr ); + rc = sqlite3BtreeFirst(pCrsr, &res); + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; + } + pC->nullRow = (u8)res; assert( pOp->p2>0 && pOp->p2<p->nOp ); - if( u.bq.res ){ + VdbeBranchTaken(res!=0,2); + if( res ){ pc = pOp->p2 - 1; } break; } -/* Opcode: Next P1 P2 * P4 P5 +/* Opcode: Next P1 P2 P3 P4 P5 ** ** Advance cursor P1 so that it points to the next key/data pair in its ** table or index. If there are no more key/value pairs then fall through ** to the following instruction. But if the cursor advance was successful, ** jump immediately to P2. ** -** The P1 cursor must be for a real table, not a pseudo-table. +** The Next opcode is only valid following an SeekGT, SeekGE, or +** OP_Rewind opcode used to position the cursor. Next is not allowed +** to follow SeekLT, SeekLE, or OP_Last. +** +** The P1 cursor must be for a real table, not a pseudo-table. P1 must have +** been opened prior to this opcode or the program will segfault. +** +** The P3 value is a hint to the btree implementation. If P3==1, that +** means P1 is an SQL index and that this instruction could have been +** omitted if that index had been unique. P3 is usually 0. P3 is +** always either 0 or 1. ** ** P4 is always of type P4_ADVANCE. The function pointer points to ** sqlite3BtreeNext(). ** ** If P5 is positive and the jump is taken, then event counter ** number P5-1 in the prepared statement is incremented. ** -** See also: Prev +** See also: Prev, NextIfOpen */ -/* Opcode: Prev P1 P2 * * P5 +/* Opcode: NextIfOpen P1 P2 P3 P4 P5 +** +** This opcode works just like Next except that if cursor P1 is not +** open it behaves a no-op. +*/ +/* Opcode: Prev P1 P2 P3 P4 P5 ** ** Back up cursor P1 so that it points to the previous key/data pair in its ** table or index. If there is no previous key/value pairs then fall through ** to the following instruction. But if the cursor backup was successful, ** jump immediately to P2. ** -** The P1 cursor must be for a real table, not a pseudo-table. +** +** The Prev opcode is only valid following an SeekLT, SeekLE, or +** OP_Last opcode used to position the cursor. Prev is not allowed +** to follow SeekGT, SeekGE, or OP_Rewind. +** +** The P1 cursor must be for a real table, not a pseudo-table. If P1 is +** not open then the behavior is undefined. +** +** The P3 value is a hint to the btree implementation. If P3==1, that +** means P1 is an SQL index and that this instruction could have been +** omitted if that index had been unique. P3 is usually 0. P3 is +** always either 0 or 1. ** ** P4 is always of type P4_ADVANCE. The function pointer points to ** sqlite3BtreePrevious(). ** ** If P5 is positive and the jump is taken, then event counter ** number P5-1 in the prepared statement is incremented. */ -case OP_SorterNext: /* jump */ +/* Opcode: PrevIfOpen P1 P2 P3 P4 P5 +** +** This opcode works just like Prev except that if cursor P1 is not +** open it behaves a no-op. +*/ +case OP_SorterNext: { /* jump */ + VdbeCursor *pC; + int res; + + pC = p->apCsr[pOp->p1]; + assert( isSorter(pC) ); + res = 0; + rc = sqlite3VdbeSorterNext(db, pC, &res); + goto next_tail; +case OP_PrevIfOpen: /* jump */ +case OP_NextIfOpen: /* jump */ + if( p->apCsr[pOp->p1]==0 ) break; + /* Fall through */ case OP_Prev: /* jump */ -case OP_Next: { /* jump */ -#if 0 /* local variables moved into u.br */ - VdbeCursor *pC; - int res; -#endif /* local variables moved into u.br */ - +case OP_Next: /* jump */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); assert( pOp->p5<ArraySize(p->aCounter) ); - u.br.pC = p->apCsr[pOp->p1]; - if( u.br.pC==0 ){ - break; /* See ticket #2273 */ - } - assert( u.br.pC->isSorter==(pOp->opcode==OP_SorterNext) ); - if( isSorter(u.br.pC) ){ - assert( pOp->opcode==OP_SorterNext ); - rc = sqlite3VdbeSorterNext(db, u.br.pC, &u.br.res); - }else{ - /* u.br.res = 1; // Always initialized by the xAdvance() call */ - assert( u.br.pC->deferredMoveto==0 ); - assert( u.br.pC->pCursor ); - assert( pOp->opcode!=OP_Next || pOp->p4.xAdvance==sqlite3BtreeNext ); - assert( pOp->opcode!=OP_Prev || pOp->p4.xAdvance==sqlite3BtreePrevious ); - rc = pOp->p4.xAdvance(u.br.pC->pCursor, &u.br.res); - } - u.br.pC->nullRow = (u8)u.br.res; - u.br.pC->cacheStatus = CACHE_STALE; - if( u.br.res==0 ){ + pC = p->apCsr[pOp->p1]; + res = pOp->p3; + assert( pC!=0 ); + assert( pC->deferredMoveto==0 ); + assert( pC->pCursor ); + assert( res==0 || (res==1 && pC->isTable==0) ); + testcase( res==1 ); + assert( pOp->opcode!=OP_Next || pOp->p4.xAdvance==sqlite3BtreeNext ); + assert( pOp->opcode!=OP_Prev || pOp->p4.xAdvance==sqlite3BtreePrevious ); + assert( pOp->opcode!=OP_NextIfOpen || pOp->p4.xAdvance==sqlite3BtreeNext ); + assert( pOp->opcode!=OP_PrevIfOpen || pOp->p4.xAdvance==sqlite3BtreePrevious); + + /* The Next opcode is only used after SeekGT, SeekGE, and Rewind. + ** The Prev opcode is only used after SeekLT, SeekLE, and Last. */ + assert( pOp->opcode!=OP_Next || pOp->opcode!=OP_NextIfOpen + || pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE + || pC->seekOp==OP_Rewind || pC->seekOp==OP_Found); + assert( pOp->opcode!=OP_Prev || pOp->opcode!=OP_PrevIfOpen + || pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE + || pC->seekOp==OP_Last ); + + rc = pOp->p4.xAdvance(pC->pCursor, &res); +next_tail: + pC->cacheStatus = CACHE_STALE; + VdbeBranchTaken(res==0,2); + if( res==0 ){ + pC->nullRow = 0; pc = pOp->p2 - 1; p->aCounter[pOp->p5]++; #ifdef SQLITE_TEST sqlite3_search_count++; #endif + }else{ + pC->nullRow = 1; } - u.br.pC->rowidIsValid = 0; goto check_for_interrupt; } /* Opcode: IdxInsert P1 P2 P3 * P5 +** Synopsis: key=r[P2] ** ** Register P2 holds an SQL index key made using the ** MakeRecord instructions. This opcode writes that key ** into the index P1. Data for the entry is nil. ** ** P3 is a flag that provides a hint to the b-tree layer that this ** insert is likely to be an append. +** +** If P5 has the OPFLAG_NCHANGE bit set, then the change counter is +** incremented by this instruction. If the OPFLAG_NCHANGE bit is clear, +** then the change counter is unchanged. +** +** If P5 has the OPFLAG_USESEEKRESULT bit set, then the cursor must have +** just done a seek to the spot where the new entry is to be inserted. +** This flag avoids doing an extra seek. ** ** This instruction only works for indices. The equivalent instruction ** for tables is OP_Insert. */ case OP_SorterInsert: /* in2 */ case OP_IdxInsert: { /* in2 */ -#if 0 /* local variables moved into u.bs */ VdbeCursor *pC; BtCursor *pCrsr; int nKey; const char *zKey; -#endif /* local variables moved into u.bs */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bs.pC = p->apCsr[pOp->p1]; - assert( u.bs.pC!=0 ); - assert( u.bs.pC->isSorter==(pOp->opcode==OP_SorterInsert) ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( isSorter(pC)==(pOp->opcode==OP_SorterInsert) ); pIn2 = &aMem[pOp->p2]; assert( pIn2->flags & MEM_Blob ); - u.bs.pCrsr = u.bs.pC->pCursor; - if( ALWAYS(u.bs.pCrsr!=0) ){ - assert( u.bs.pC->isTable==0 ); - rc = ExpandBlob(pIn2); - if( rc==SQLITE_OK ){ - if( isSorter(u.bs.pC) ){ - rc = sqlite3VdbeSorterWrite(db, u.bs.pC, pIn2); - }else{ - u.bs.nKey = pIn2->n; - u.bs.zKey = pIn2->z; - rc = sqlite3BtreeInsert(u.bs.pCrsr, u.bs.zKey, u.bs.nKey, "", 0, 0, pOp->p3, - ((pOp->p5 & OPFLAG_USESEEKRESULT) ? u.bs.pC->seekResult : 0) - ); - assert( u.bs.pC->deferredMoveto==0 ); - u.bs.pC->cacheStatus = CACHE_STALE; - } + pCrsr = pC->pCursor; + if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; + assert( pCrsr!=0 ); + assert( pC->isTable==0 ); + rc = ExpandBlob(pIn2); + if( rc==SQLITE_OK ){ + if( isSorter(pC) ){ + rc = sqlite3VdbeSorterWrite(pC, pIn2); + }else{ + nKey = pIn2->n; + zKey = pIn2->z; + rc = sqlite3BtreeInsert(pCrsr, zKey, nKey, "", 0, 0, pOp->p3, + ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0) + ); + assert( pC->deferredMoveto==0 ); + pC->cacheStatus = CACHE_STALE; } } break; } /* Opcode: IdxDelete P1 P2 P3 * * +** Synopsis: key=r[P2@P3] ** ** The content of P3 registers starting at register P2 form ** an unpacked index key. This opcode removes that entry from the ** index opened by cursor P1. */ case OP_IdxDelete: { -#if 0 /* local variables moved into u.bt */ VdbeCursor *pC; BtCursor *pCrsr; int res; UnpackedRecord r; -#endif /* local variables moved into u.bt */ assert( pOp->p3>0 ); assert( pOp->p2>0 && pOp->p2+pOp->p3<=(p->nMem-p->nCursor)+1 ); assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bt.pC = p->apCsr[pOp->p1]; - assert( u.bt.pC!=0 ); - u.bt.pCrsr = u.bt.pC->pCursor; - if( ALWAYS(u.bt.pCrsr!=0) ){ - u.bt.r.pKeyInfo = u.bt.pC->pKeyInfo; - u.bt.r.nField = (u16)pOp->p3; - u.bt.r.flags = 0; - u.bt.r.aMem = &aMem[pOp->p2]; + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + pCrsr = pC->pCursor; + assert( pCrsr!=0 ); + assert( pOp->p5==0 ); + r.pKeyInfo = pC->pKeyInfo; + r.nField = (u16)pOp->p3; + r.default_rc = 0; + r.aMem = &aMem[pOp->p2]; #ifdef SQLITE_DEBUG - { int i; for(i=0; i<u.bt.r.nField; i++) assert( memIsValid(&u.bt.r.aMem[i]) ); } + { int i; for(i=0; i<r.nField; i++) assert( memIsValid(&r.aMem[i]) ); } #endif - rc = sqlite3BtreeMovetoUnpacked(u.bt.pCrsr, &u.bt.r, 0, 0, &u.bt.res); - if( rc==SQLITE_OK && u.bt.res==0 ){ - rc = sqlite3BtreeDelete(u.bt.pCrsr); - } - assert( u.bt.pC->deferredMoveto==0 ); - u.bt.pC->cacheStatus = CACHE_STALE; + rc = sqlite3BtreeMovetoUnpacked(pCrsr, &r, 0, 0, &res); + if( rc==SQLITE_OK && res==0 ){ + rc = sqlite3BtreeDelete(pCrsr); } + assert( pC->deferredMoveto==0 ); + pC->cacheStatus = CACHE_STALE; break; } /* Opcode: IdxRowid P1 P2 * * * +** Synopsis: r[P2]=rowid ** ** Write into register P2 an integer which is the last entry in the record at ** the end of the index key pointed to by cursor P1. This integer should be ** the rowid of the table entry to which this index entry points. ** ** See also: Rowid, MakeRecord. */ case OP_IdxRowid: { /* out2-prerelease */ -#if 0 /* local variables moved into u.bu */ BtCursor *pCrsr; VdbeCursor *pC; i64 rowid; -#endif /* local variables moved into u.bu */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bu.pC = p->apCsr[pOp->p1]; - assert( u.bu.pC!=0 ); - u.bu.pCrsr = u.bu.pC->pCursor; + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + pCrsr = pC->pCursor; + assert( pCrsr!=0 ); pOut->flags = MEM_Null; - if( ALWAYS(u.bu.pCrsr!=0) ){ - rc = sqlite3VdbeCursorMoveto(u.bu.pC); - if( NEVER(rc) ) goto abort_due_to_error; - assert( u.bu.pC->deferredMoveto==0 ); - assert( u.bu.pC->isTable==0 ); - if( !u.bu.pC->nullRow ){ - rc = sqlite3VdbeIdxRowid(db, u.bu.pCrsr, &u.bu.rowid); - if( rc!=SQLITE_OK ){ - goto abort_due_to_error; - } - pOut->u.i = u.bu.rowid; - pOut->flags = MEM_Int; - } + assert( pC->isTable==0 ); + assert( pC->deferredMoveto==0 ); + + /* sqlite3VbeCursorRestore() can only fail if the record has been deleted + ** out from under the cursor. That will never happend for an IdxRowid + ** opcode, hence the NEVER() arround the check of the return value. + */ + rc = sqlite3VdbeCursorRestore(pC); + if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error; + + if( !pC->nullRow ){ + rowid = 0; /* Not needed. Only used to silence a warning. */ + rc = sqlite3VdbeIdxRowid(db, pCrsr, &rowid); + if( rc!=SQLITE_OK ){ + goto abort_due_to_error; + } + pOut->u.i = rowid; + pOut->flags = MEM_Int; } break; } /* Opcode: IdxGE P1 P2 P3 P4 P5 +** Synopsis: key=r[P3@P4] ** ** The P4 register values beginning with P3 form an unpacked index -** key that omits the ROWID. Compare this key value against the index -** that P1 is currently pointing to, ignoring the ROWID on the P1 index. +** key that omits the PRIMARY KEY. Compare this key value against the index +** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID +** fields at the end. ** ** If the P1 index entry is greater than or equal to the key value ** then jump to P2. Otherwise fall through to the next instruction. +*/ +/* Opcode: IdxGT P1 P2 P3 P4 P5 +** Synopsis: key=r[P3@P4] ** -** If P5 is non-zero then the key value is increased by an epsilon -** prior to the comparison. This make the opcode work like IdxGT except -** that if the key from register P3 is a prefix of the key in the cursor, -** the result is false whereas it would be true with IdxGT. +** The P4 register values beginning with P3 form an unpacked index +** key that omits the PRIMARY KEY. Compare this key value against the index +** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID +** fields at the end. +** +** If the P1 index entry is greater than the key value +** then jump to P2. Otherwise fall through to the next instruction. */ /* Opcode: IdxLT P1 P2 P3 P4 P5 +** Synopsis: key=r[P3@P4] ** ** The P4 register values beginning with P3 form an unpacked index -** key that omits the ROWID. Compare this key value against the index -** that P1 is currently pointing to, ignoring the ROWID on the P1 index. +** key that omits the PRIMARY KEY or ROWID. Compare this key value against +** the index that P1 is currently pointing to, ignoring the PRIMARY KEY or +** ROWID on the P1 index. ** ** If the P1 index entry is less than the key value then jump to P2. ** Otherwise fall through to the next instruction. +*/ +/* Opcode: IdxLE P1 P2 P3 P4 P5 +** Synopsis: key=r[P3@P4] ** -** If P5 is non-zero then the key value is increased by an epsilon prior -** to the comparison. This makes the opcode work like IdxLE. +** The P4 register values beginning with P3 form an unpacked index +** key that omits the PRIMARY KEY or ROWID. Compare this key value against +** the index that P1 is currently pointing to, ignoring the PRIMARY KEY or +** ROWID on the P1 index. +** +** If the P1 index entry is less than or equal to the key value then jump +** to P2. Otherwise fall through to the next instruction. */ +case OP_IdxLE: /* jump */ +case OP_IdxGT: /* jump */ case OP_IdxLT: /* jump */ -case OP_IdxGE: { /* jump */ -#if 0 /* local variables moved into u.bv */ +case OP_IdxGE: { /* jump */ VdbeCursor *pC; int res; UnpackedRecord r; -#endif /* local variables moved into u.bv */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); - u.bv.pC = p->apCsr[pOp->p1]; - assert( u.bv.pC!=0 ); - assert( u.bv.pC->isOrdered ); - if( ALWAYS(u.bv.pC->pCursor!=0) ){ - assert( u.bv.pC->deferredMoveto==0 ); - assert( pOp->p5==0 || pOp->p5==1 ); - assert( pOp->p4type==P4_INT32 ); - u.bv.r.pKeyInfo = u.bv.pC->pKeyInfo; - u.bv.r.nField = (u16)pOp->p4.i; - if( pOp->p5 ){ - u.bv.r.flags = UNPACKED_INCRKEY | UNPACKED_PREFIX_MATCH; - }else{ - u.bv.r.flags = UNPACKED_PREFIX_MATCH; - } - u.bv.r.aMem = &aMem[pOp->p3]; + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->isOrdered ); + assert( pC->pCursor!=0); + assert( pC->deferredMoveto==0 ); + assert( pOp->p5==0 || pOp->p5==1 ); + assert( pOp->p4type==P4_INT32 ); + r.pKeyInfo = pC->pKeyInfo; + r.nField = (u16)pOp->p4.i; + if( pOp->opcode<OP_IdxLT ){ + assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxGT ); + r.default_rc = -1; + }else{ + assert( pOp->opcode==OP_IdxGE || pOp->opcode==OP_IdxLT ); + r.default_rc = 0; + } + r.aMem = &aMem[pOp->p3]; #ifdef SQLITE_DEBUG - { int i; for(i=0; i<u.bv.r.nField; i++) assert( memIsValid(&u.bv.r.aMem[i]) ); } + { int i; for(i=0; i<r.nField; i++) assert( memIsValid(&r.aMem[i]) ); } #endif - rc = sqlite3VdbeIdxKeyCompare(u.bv.pC, &u.bv.r, &u.bv.res); - if( pOp->opcode==OP_IdxLT ){ - u.bv.res = -u.bv.res; - }else{ - assert( pOp->opcode==OP_IdxGE ); - u.bv.res++; - } - if( u.bv.res>0 ){ - pc = pOp->p2 - 1 ; - } + res = 0; /* Not needed. Only used to silence a warning. */ + rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res); + assert( (OP_IdxLE&1)==(OP_IdxLT&1) && (OP_IdxGE&1)==(OP_IdxGT&1) ); + if( (pOp->opcode&1)==(OP_IdxLT&1) ){ + assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxLT ); + res = -res; + }else{ + assert( pOp->opcode==OP_IdxGE || pOp->opcode==OP_IdxGT ); + res++; + } + VdbeBranchTaken(res>0,2); + if( res>0 ){ + pc = pOp->p2 - 1 ; } break; } /* Opcode: Destroy P1 P2 P3 * * @@ -70830,47 +75586,31 @@ ** If AUTOVACUUM is disabled then a zero is stored in register P2. ** ** See also: Clear */ case OP_Destroy: { /* out2-prerelease */ -#if 0 /* local variables moved into u.bw */ int iMoved; - int iCnt; - Vdbe *pVdbe; int iDb; -#endif /* local variables moved into u.bw */ assert( p->readOnly==0 ); -#ifndef SQLITE_OMIT_VIRTUALTABLE - u.bw.iCnt = 0; - for(u.bw.pVdbe=db->pVdbe; u.bw.pVdbe; u.bw.pVdbe = u.bw.pVdbe->pNext){ - if( u.bw.pVdbe->magic==VDBE_MAGIC_RUN && u.bw.pVdbe->bIsReader - && u.bw.pVdbe->inVtabMethod<2 && u.bw.pVdbe->pc>=0 - ){ - u.bw.iCnt++; - } - } -#else - u.bw.iCnt = db->nVdbeRead; -#endif pOut->flags = MEM_Null; - if( u.bw.iCnt>1 ){ + if( db->nVdbeRead > db->nVDestroy+1 ){ rc = SQLITE_LOCKED; p->errorAction = OE_Abort; }else{ - u.bw.iDb = pOp->p3; - assert( u.bw.iCnt==1 ); - assert( (p->btreeMask & (((yDbMask)1)<<u.bw.iDb))!=0 ); - rc = sqlite3BtreeDropTable(db->aDb[u.bw.iDb].pBt, pOp->p1, &u.bw.iMoved); + iDb = pOp->p3; + assert( DbMaskTest(p->btreeMask, iDb) ); + iMoved = 0; /* Not needed. Only to silence a warning. */ + rc = sqlite3BtreeDropTable(db->aDb[iDb].pBt, pOp->p1, &iMoved); pOut->flags = MEM_Int; - pOut->u.i = u.bw.iMoved; + pOut->u.i = iMoved; #ifndef SQLITE_OMIT_AUTOVACUUM - if( rc==SQLITE_OK && u.bw.iMoved!=0 ){ - sqlite3RootPageMoved(db, u.bw.iDb, u.bw.iMoved, pOp->p1); + if( rc==SQLITE_OK && iMoved!=0 ){ + sqlite3RootPageMoved(db, iDb, iMoved, pOp->p1); /* All OP_Destroy operations occur on the same btree */ - assert( resetSchemaOnFault==0 || resetSchemaOnFault==u.bw.iDb+1 ); - resetSchemaOnFault = u.bw.iDb+1; + assert( resetSchemaOnFault==0 || resetSchemaOnFault==iDb+1 ); + resetSchemaOnFault = iDb+1; } #endif } break; } @@ -70892,33 +75632,54 @@ ** also incremented by the number of rows in the table being cleared. ** ** See also: Destroy */ case OP_Clear: { -#if 0 /* local variables moved into u.bx */ int nChange; -#endif /* local variables moved into u.bx */ - - u.bx.nChange = 0; + + nChange = 0; assert( p->readOnly==0 ); - assert( pOp->p1!=1 ); - assert( (p->btreeMask & (((yDbMask)1)<<pOp->p2))!=0 ); + assert( DbMaskTest(p->btreeMask, pOp->p2) ); rc = sqlite3BtreeClearTable( - db->aDb[pOp->p2].pBt, pOp->p1, (pOp->p3 ? &u.bx.nChange : 0) + db->aDb[pOp->p2].pBt, pOp->p1, (pOp->p3 ? &nChange : 0) ); if( pOp->p3 ){ - p->nChange += u.bx.nChange; + p->nChange += nChange; if( pOp->p3>0 ){ assert( memIsValid(&aMem[pOp->p3]) ); memAboutToChange(p, &aMem[pOp->p3]); - aMem[pOp->p3].u.i += u.bx.nChange; + aMem[pOp->p3].u.i += nChange; } } break; } + +/* Opcode: ResetSorter P1 * * * * +** +** Delete all contents from the ephemeral table or sorter +** that is open on cursor P1. +** +** This opcode only works for cursors used for sorting and +** opened with OP_OpenEphemeral or OP_SorterOpen. +*/ +case OP_ResetSorter: { + VdbeCursor *pC; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + if( pC->pSorter ){ + sqlite3VdbeSorterReset(db, pC->pSorter); + }else{ + assert( pC->isEphemeral ); + rc = sqlite3BtreeClearTableOfCursor(pC->pCursor); + } + break; +} /* Opcode: CreateTable P1 P2 * * * +** Synopsis: r[P2]=root iDb=P1 ** ** Allocate a new table in the main database file if P1==0 or in the ** auxiliary database file if P1==1 or in an attached database if ** P1>1. Write the root page number of the new table into ** register P2 @@ -70928,10 +75689,11 @@ ** has an arbitrary key but no data. ** ** See also: CreateIndex */ /* Opcode: CreateIndex P1 P2 * * * +** Synopsis: r[P2]=root iDb=P1 ** ** Allocate a new index in the main database file if P1==0 or in the ** auxiliary database file if P1==1 or in an attached database if ** P1>1. Write the root page number of the new table into ** register P2. @@ -70938,30 +75700,28 @@ ** ** See documentation on OP_CreateTable for additional information. */ case OP_CreateIndex: /* out2-prerelease */ case OP_CreateTable: { /* out2-prerelease */ -#if 0 /* local variables moved into u.by */ int pgno; int flags; Db *pDb; -#endif /* local variables moved into u.by */ - u.by.pgno = 0; + pgno = 0; assert( pOp->p1>=0 && pOp->p1<db->nDb ); - assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 ); + assert( DbMaskTest(p->btreeMask, pOp->p1) ); assert( p->readOnly==0 ); - u.by.pDb = &db->aDb[pOp->p1]; - assert( u.by.pDb->pBt!=0 ); + pDb = &db->aDb[pOp->p1]; + assert( pDb->pBt!=0 ); if( pOp->opcode==OP_CreateTable ){ - /* u.by.flags = BTREE_INTKEY; */ - u.by.flags = BTREE_INTKEY; + /* flags = BTREE_INTKEY; */ + flags = BTREE_INTKEY; }else{ - u.by.flags = BTREE_BLOBKEY; + flags = BTREE_BLOBKEY; } - rc = sqlite3BtreeCreateTable(u.by.pDb->pBt, &u.by.pgno, u.by.flags); - pOut->u.i = u.by.pgno; + rc = sqlite3BtreeCreateTable(pDb->pBt, &pgno, flags); + pOut->u.i = pgno; break; } /* Opcode: ParseSchema P1 * * P4 * ** @@ -70970,56 +75730,54 @@ ** ** This opcode invokes the parser to create a new virtual machine, ** then runs the new virtual machine. It is thus a re-entrant opcode. */ case OP_ParseSchema: { -#if 0 /* local variables moved into u.bz */ int iDb; const char *zMaster; char *zSql; InitData initData; -#endif /* local variables moved into u.bz */ /* Any prepared statement that invokes this opcode will hold mutexes - ** on every btree. This is a prerequisite for invoking + ** on every btree. This is a prerequisite for invoking ** sqlite3InitCallback(). */ #ifdef SQLITE_DEBUG - for(u.bz.iDb=0; u.bz.iDb<db->nDb; u.bz.iDb++){ - assert( u.bz.iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[u.bz.iDb].pBt) ); + for(iDb=0; iDb<db->nDb; iDb++){ + assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) ); } #endif - u.bz.iDb = pOp->p1; - assert( u.bz.iDb>=0 && u.bz.iDb<db->nDb ); - assert( DbHasProperty(db, u.bz.iDb, DB_SchemaLoaded) ); + iDb = pOp->p1; + assert( iDb>=0 && iDb<db->nDb ); + assert( DbHasProperty(db, iDb, DB_SchemaLoaded) ); /* Used to be a conditional */ { - u.bz.zMaster = SCHEMA_TABLE(u.bz.iDb); - u.bz.initData.db = db; - u.bz.initData.iDb = pOp->p1; - u.bz.initData.pzErrMsg = &p->zErrMsg; - u.bz.zSql = sqlite3MPrintf(db, + zMaster = SCHEMA_TABLE(iDb); + initData.db = db; + initData.iDb = pOp->p1; + initData.pzErrMsg = &p->zErrMsg; + zSql = sqlite3MPrintf(db, "SELECT name, rootpage, sql FROM '%q'.%s WHERE %s ORDER BY rowid", - db->aDb[u.bz.iDb].zName, u.bz.zMaster, pOp->p4.z); - if( u.bz.zSql==0 ){ + db->aDb[iDb].zName, zMaster, pOp->p4.z); + if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ assert( db->init.busy==0 ); db->init.busy = 1; - u.bz.initData.rc = SQLITE_OK; + initData.rc = SQLITE_OK; assert( !db->mallocFailed ); - rc = sqlite3_exec(db, u.bz.zSql, sqlite3InitCallback, &u.bz.initData, 0); - if( rc==SQLITE_OK ) rc = u.bz.initData.rc; - sqlite3DbFree(db, u.bz.zSql); + rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0); + if( rc==SQLITE_OK ) rc = initData.rc; + sqlite3DbFree(db, zSql); db->init.busy = 0; } } if( rc ) sqlite3ResetAllSchemasOfConnection(db); if( rc==SQLITE_NOMEM ){ goto no_mem; } - break; + break; } #if !defined(SQLITE_OMIT_ANALYZE) /* Opcode: LoadAnalysis P1 * * * * ** @@ -71036,11 +75794,12 @@ /* Opcode: DropTable P1 * * P4 * ** ** Remove the internal (in-memory) data structures that describe ** the table named P4 in database P1. This is called after a table -** is dropped in order to keep the internal representation of the +** is dropped from disk (using the Destroy opcode) in order to keep +** the internal representation of the ** schema consistent with what is on disk. */ case OP_DropTable: { sqlite3UnlinkAndDeleteTable(db, pOp->p1, pOp->p4.z); break; @@ -71048,11 +75807,12 @@ /* Opcode: DropIndex P1 * * P4 * ** ** Remove the internal (in-memory) data structures that describe ** the index named P4 in database P1. This is called after an index -** is dropped in order to keep the internal representation of the +** is dropped from disk (using the Destroy opcode) +** in order to keep the internal representation of the ** schema consistent with what is on disk. */ case OP_DropIndex: { sqlite3UnlinkAndDeleteIndex(db, pOp->p1, pOp->p4.z); break; @@ -71060,11 +75820,12 @@ /* Opcode: DropTrigger P1 * * P4 * ** ** Remove the internal (in-memory) data structures that describe ** the trigger named P4 in database P1. This is called after a trigger -** is dropped in order to keep the internal representation of the +** is dropped from disk (using the Destroy opcode) in order to keep +** the internal representation of the ** schema consistent with what is on disk. */ case OP_DropTrigger: { sqlite3UnlinkAndDeleteTrigger(db, pOp->p1, pOp->p4.z); break; @@ -71091,54 +75852,53 @@ ** file, not the main database file. ** ** This opcode is used to implement the integrity_check pragma. */ case OP_IntegrityCk: { -#if 0 /* local variables moved into u.ca */ int nRoot; /* Number of tables to check. (Number of root pages.) */ int *aRoot; /* Array of rootpage numbers for tables to be checked */ int j; /* Loop counter */ int nErr; /* Number of errors reported */ char *z; /* Text of the error report */ Mem *pnErr; /* Register keeping track of errors remaining */ -#endif /* local variables moved into u.ca */ assert( p->bIsReader ); - u.ca.nRoot = pOp->p2; - assert( u.ca.nRoot>0 ); - u.ca.aRoot = sqlite3DbMallocRaw(db, sizeof(int)*(u.ca.nRoot+1) ); - if( u.ca.aRoot==0 ) goto no_mem; + nRoot = pOp->p2; + assert( nRoot>0 ); + aRoot = sqlite3DbMallocRaw(db, sizeof(int)*(nRoot+1) ); + if( aRoot==0 ) goto no_mem; assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); - u.ca.pnErr = &aMem[pOp->p3]; - assert( (u.ca.pnErr->flags & MEM_Int)!=0 ); - assert( (u.ca.pnErr->flags & (MEM_Str|MEM_Blob))==0 ); + pnErr = &aMem[pOp->p3]; + assert( (pnErr->flags & MEM_Int)!=0 ); + assert( (pnErr->flags & (MEM_Str|MEM_Blob))==0 ); pIn1 = &aMem[pOp->p1]; - for(u.ca.j=0; u.ca.j<u.ca.nRoot; u.ca.j++){ - u.ca.aRoot[u.ca.j] = (int)sqlite3VdbeIntValue(&pIn1[u.ca.j]); + for(j=0; j<nRoot; j++){ + aRoot[j] = (int)sqlite3VdbeIntValue(&pIn1[j]); } - u.ca.aRoot[u.ca.j] = 0; + aRoot[j] = 0; assert( pOp->p5<db->nDb ); - assert( (p->btreeMask & (((yDbMask)1)<<pOp->p5))!=0 ); - u.ca.z = sqlite3BtreeIntegrityCheck(db->aDb[pOp->p5].pBt, u.ca.aRoot, u.ca.nRoot, - (int)u.ca.pnErr->u.i, &u.ca.nErr); - sqlite3DbFree(db, u.ca.aRoot); - u.ca.pnErr->u.i -= u.ca.nErr; + assert( DbMaskTest(p->btreeMask, pOp->p5) ); + z = sqlite3BtreeIntegrityCheck(db->aDb[pOp->p5].pBt, aRoot, nRoot, + (int)pnErr->u.i, &nErr); + sqlite3DbFree(db, aRoot); + pnErr->u.i -= nErr; sqlite3VdbeMemSetNull(pIn1); - if( u.ca.nErr==0 ){ - assert( u.ca.z==0 ); - }else if( u.ca.z==0 ){ + if( nErr==0 ){ + assert( z==0 ); + }else if( z==0 ){ goto no_mem; }else{ - sqlite3VdbeMemSetStr(pIn1, u.ca.z, -1, SQLITE_UTF8, sqlite3_free); + sqlite3VdbeMemSetStr(pIn1, z, -1, SQLITE_UTF8, sqlite3_free); } UPDATE_MAX_BLOBSIZE(pIn1); sqlite3VdbeChangeEncoding(pIn1, encoding); break; } #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ /* Opcode: RowSetAdd P1 P2 * * * +** Synopsis: rowset(P1)=r[P2] ** ** Insert the integer value held by register P2 into a boolean index ** held in register P1. ** ** An assertion fails if P2 is not an integer. @@ -71154,35 +75914,37 @@ sqlite3RowSetInsert(pIn1->u.pRowSet, pIn2->u.i); break; } /* Opcode: RowSetRead P1 P2 P3 * * +** Synopsis: r[P3]=rowset(P1) ** ** Extract the smallest value from boolean index P1 and put that value into ** register P3. Or, if boolean index P1 is initially empty, leave P3 ** unchanged and jump to instruction P2. */ case OP_RowSetRead: { /* jump, in1, out3 */ -#if 0 /* local variables moved into u.cb */ i64 val; -#endif /* local variables moved into u.cb */ pIn1 = &aMem[pOp->p1]; - if( (pIn1->flags & MEM_RowSet)==0 - || sqlite3RowSetNext(pIn1->u.pRowSet, &u.cb.val)==0 + if( (pIn1->flags & MEM_RowSet)==0 + || sqlite3RowSetNext(pIn1->u.pRowSet, &val)==0 ){ /* The boolean index is empty */ sqlite3VdbeMemSetNull(pIn1); pc = pOp->p2 - 1; + VdbeBranchTaken(1,2); }else{ /* A value was pulled from the index */ - sqlite3VdbeMemSetInt64(&aMem[pOp->p3], u.cb.val); + sqlite3VdbeMemSetInt64(&aMem[pOp->p3], val); + VdbeBranchTaken(0,2); } goto check_for_interrupt; } /* Opcode: RowSetTest P1 P2 P3 P4 +** Synopsis: if r[P3] in rowset(P1) goto P2 ** ** Register P3 is assumed to hold a 64-bit integer value. If register P1 ** contains a RowSet object and that RowSet object contains ** the value held in P3, jump to register P2. Otherwise, insert the ** integer in P3 into the RowSet and continue on to the @@ -71202,18 +75964,16 @@ ** inserted, there is no need to search to see if the same value was ** previously inserted as part of set X (only if it was previously ** inserted as part of some other set). */ case OP_RowSetTest: { /* jump, in1, in3 */ -#if 0 /* local variables moved into u.cc */ int iSet; int exists; -#endif /* local variables moved into u.cc */ pIn1 = &aMem[pOp->p1]; pIn3 = &aMem[pOp->p3]; - u.cc.iSet = pOp->p4.i; + iSet = pOp->p4.i; assert( pIn3->flags&MEM_Int ); /* If there is anything other than a rowset object in memory cell P1, ** delete it now and initialize P1 with an empty rowset */ @@ -71221,30 +75981,29 @@ sqlite3VdbeMemSetRowSet(pIn1); if( (pIn1->flags & MEM_RowSet)==0 ) goto no_mem; } assert( pOp->p4type==P4_INT32 ); - assert( u.cc.iSet==-1 || u.cc.iSet>=0 ); - if( u.cc.iSet ){ - u.cc.exists = sqlite3RowSetTest(pIn1->u.pRowSet, - (u8)(u.cc.iSet>=0 ? u.cc.iSet & 0xf : 0xff), - pIn3->u.i); - if( u.cc.exists ){ + assert( iSet==-1 || iSet>=0 ); + if( iSet ){ + exists = sqlite3RowSetTest(pIn1->u.pRowSet, iSet, pIn3->u.i); + VdbeBranchTaken(exists!=0,2); + if( exists ){ pc = pOp->p2 - 1; break; } } - if( u.cc.iSet>=0 ){ + if( iSet>=0 ){ sqlite3RowSetInsert(pIn1->u.pRowSet, pIn3->u.i); } break; } #ifndef SQLITE_OMIT_TRIGGER -/* Opcode: Program P1 P2 P3 P4 * +/* Opcode: Program P1 P2 P3 P4 P5 ** ** Execute the trigger program passed as P4 (type P4_SUBPROGRAM). ** ** P1 contains the address of the memory cell that contains the first memory ** cell in an array of values used as arguments to the sub-program. P2 @@ -71252,113 +76011,120 @@ ** exception using the RAISE() function. Register P3 contains the address ** of a memory cell in this (the parent) VM that is used to allocate the ** memory required by the sub-vdbe at runtime. ** ** P4 is a pointer to the VM containing the trigger program. +** +** If P5 is non-zero, then recursive program invocation is enabled. */ case OP_Program: { /* jump */ -#if 0 /* local variables moved into u.cd */ int nMem; /* Number of memory registers for sub-program */ int nByte; /* Bytes of runtime space required for sub-program */ Mem *pRt; /* Register to allocate runtime space */ Mem *pMem; /* Used to iterate through memory cells */ Mem *pEnd; /* Last memory cell in new array */ VdbeFrame *pFrame; /* New vdbe frame to execute in */ SubProgram *pProgram; /* Sub-program to execute */ void *t; /* Token identifying trigger */ -#endif /* local variables moved into u.cd */ - u.cd.pProgram = pOp->p4.pProgram; - u.cd.pRt = &aMem[pOp->p3]; - assert( u.cd.pProgram->nOp>0 ); - - /* If the p5 flag is clear, then recursive invocation of triggers is + pProgram = pOp->p4.pProgram; + pRt = &aMem[pOp->p3]; + assert( pProgram->nOp>0 ); + + /* If the p5 flag is clear, then recursive invocation of triggers is ** disabled for backwards compatibility (p5 is set if this sub-program ** is really a trigger, not a foreign key action, and the flag set ** and cleared by the "PRAGMA recursive_triggers" command is clear). - ** - ** It is recursive invocation of triggers, at the SQL level, that is - ** disabled. In some cases a single trigger may generate more than one - ** SubProgram (if the trigger may be executed with more than one different + ** + ** It is recursive invocation of triggers, at the SQL level, that is + ** disabled. In some cases a single trigger may generate more than one + ** SubProgram (if the trigger may be executed with more than one different ** ON CONFLICT algorithm). SubProgram structures associated with a - ** single trigger all have the same value for the SubProgram.token + ** single trigger all have the same value for the SubProgram.token ** variable. */ if( pOp->p5 ){ - u.cd.t = u.cd.pProgram->token; - for(u.cd.pFrame=p->pFrame; u.cd.pFrame && u.cd.pFrame->token!=u.cd.t; u.cd.pFrame=u.cd.pFrame->pParent); - if( u.cd.pFrame ) break; + t = pProgram->token; + for(pFrame=p->pFrame; pFrame && pFrame->token!=t; pFrame=pFrame->pParent); + if( pFrame ) break; } if( p->nFrame>=db->aLimit[SQLITE_LIMIT_TRIGGER_DEPTH] ){ rc = SQLITE_ERROR; sqlite3SetString(&p->zErrMsg, db, "too many levels of trigger recursion"); break; } - /* Register u.cd.pRt is used to store the memory required to save the state + /* Register pRt is used to store the memory required to save the state ** of the current program, and the memory required at runtime to execute - ** the trigger program. If this trigger has been fired before, then u.cd.pRt + ** the trigger program. If this trigger has been fired before, then pRt ** is already allocated. Otherwise, it must be initialized. */ - if( (u.cd.pRt->flags&MEM_Frame)==0 ){ - /* SubProgram.nMem is set to the number of memory cells used by the + if( (pRt->flags&MEM_Frame)==0 ){ + /* SubProgram.nMem is set to the number of memory cells used by the ** program stored in SubProgram.aOp. As well as these, one memory ** cell is required for each cursor used by the program. Set local - ** variable u.cd.nMem (and later, VdbeFrame.nChildMem) to this value. + ** variable nMem (and later, VdbeFrame.nChildMem) to this value. */ - u.cd.nMem = u.cd.pProgram->nMem + u.cd.pProgram->nCsr; - u.cd.nByte = ROUND8(sizeof(VdbeFrame)) - + u.cd.nMem * sizeof(Mem) - + u.cd.pProgram->nCsr * sizeof(VdbeCursor *) - + u.cd.pProgram->nOnce * sizeof(u8); - u.cd.pFrame = sqlite3DbMallocZero(db, u.cd.nByte); - if( !u.cd.pFrame ){ + nMem = pProgram->nMem + pProgram->nCsr; + nByte = ROUND8(sizeof(VdbeFrame)) + + nMem * sizeof(Mem) + + pProgram->nCsr * sizeof(VdbeCursor *) + + pProgram->nOnce * sizeof(u8); + pFrame = sqlite3DbMallocZero(db, nByte); + if( !pFrame ){ goto no_mem; } - sqlite3VdbeMemRelease(u.cd.pRt); - u.cd.pRt->flags = MEM_Frame; - u.cd.pRt->u.pFrame = u.cd.pFrame; - - u.cd.pFrame->v = p; - u.cd.pFrame->nChildMem = u.cd.nMem; - u.cd.pFrame->nChildCsr = u.cd.pProgram->nCsr; - u.cd.pFrame->pc = pc; - u.cd.pFrame->aMem = p->aMem; - u.cd.pFrame->nMem = p->nMem; - u.cd.pFrame->apCsr = p->apCsr; - u.cd.pFrame->nCursor = p->nCursor; - u.cd.pFrame->aOp = p->aOp; - u.cd.pFrame->nOp = p->nOp; - u.cd.pFrame->token = u.cd.pProgram->token; - u.cd.pFrame->aOnceFlag = p->aOnceFlag; - u.cd.pFrame->nOnceFlag = p->nOnceFlag; - - u.cd.pEnd = &VdbeFrameMem(u.cd.pFrame)[u.cd.pFrame->nChildMem]; - for(u.cd.pMem=VdbeFrameMem(u.cd.pFrame); u.cd.pMem!=u.cd.pEnd; u.cd.pMem++){ - u.cd.pMem->flags = MEM_Invalid; - u.cd.pMem->db = db; + sqlite3VdbeMemRelease(pRt); + pRt->flags = MEM_Frame; + pRt->u.pFrame = pFrame; + + pFrame->v = p; + pFrame->nChildMem = nMem; + pFrame->nChildCsr = pProgram->nCsr; + pFrame->pc = pc; + pFrame->aMem = p->aMem; + pFrame->nMem = p->nMem; + pFrame->apCsr = p->apCsr; + pFrame->nCursor = p->nCursor; + pFrame->aOp = p->aOp; + pFrame->nOp = p->nOp; + pFrame->token = pProgram->token; + pFrame->aOnceFlag = p->aOnceFlag; + pFrame->nOnceFlag = p->nOnceFlag; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + pFrame->anExec = p->anExec; +#endif + + pEnd = &VdbeFrameMem(pFrame)[pFrame->nChildMem]; + for(pMem=VdbeFrameMem(pFrame); pMem!=pEnd; pMem++){ + pMem->flags = MEM_Undefined; + pMem->db = db; } }else{ - u.cd.pFrame = u.cd.pRt->u.pFrame; - assert( u.cd.pProgram->nMem+u.cd.pProgram->nCsr==u.cd.pFrame->nChildMem ); - assert( u.cd.pProgram->nCsr==u.cd.pFrame->nChildCsr ); - assert( pc==u.cd.pFrame->pc ); + pFrame = pRt->u.pFrame; + assert( pProgram->nMem+pProgram->nCsr==pFrame->nChildMem ); + assert( pProgram->nCsr==pFrame->nChildCsr ); + assert( pc==pFrame->pc ); } p->nFrame++; - u.cd.pFrame->pParent = p->pFrame; - u.cd.pFrame->lastRowid = lastRowid; - u.cd.pFrame->nChange = p->nChange; + pFrame->pParent = p->pFrame; + pFrame->lastRowid = lastRowid; + pFrame->nChange = p->nChange; + pFrame->nDbChange = p->db->nChange; p->nChange = 0; - p->pFrame = u.cd.pFrame; - p->aMem = aMem = &VdbeFrameMem(u.cd.pFrame)[-1]; - p->nMem = u.cd.pFrame->nChildMem; - p->nCursor = (u16)u.cd.pFrame->nChildCsr; + p->pFrame = pFrame; + p->aMem = aMem = &VdbeFrameMem(pFrame)[-1]; + p->nMem = pFrame->nChildMem; + p->nCursor = (u16)pFrame->nChildCsr; p->apCsr = (VdbeCursor **)&aMem[p->nMem+1]; - p->aOp = aOp = u.cd.pProgram->aOp; - p->nOp = u.cd.pProgram->nOp; + p->aOp = aOp = pProgram->aOp; + p->nOp = pProgram->nOp; p->aOnceFlag = (u8 *)&p->apCsr[p->nCursor]; - p->nOnceFlag = u.cd.pProgram->nOnce; + p->nOnceFlag = pProgram->nOnce; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + p->anExec = 0; +#endif pc = -1; memset(p->aOnceFlag, 0, p->nOnceFlag); break; } @@ -71374,24 +76140,23 @@ ** The address of the cell in the parent frame is determined by adding ** the value of the P1 argument to the value of the P1 argument to the ** calling OP_Program instruction. */ case OP_Param: { /* out2-prerelease */ -#if 0 /* local variables moved into u.ce */ VdbeFrame *pFrame; Mem *pIn; -#endif /* local variables moved into u.ce */ - u.ce.pFrame = p->pFrame; - u.ce.pIn = &u.ce.pFrame->aMem[pOp->p1 + u.ce.pFrame->aOp[u.ce.pFrame->pc].p1]; - sqlite3VdbeMemShallowCopy(pOut, u.ce.pIn, MEM_Ephem); + pFrame = p->pFrame; + pIn = &pFrame->aMem[pOp->p1 + pFrame->aOp[pFrame->pc].p1]; + sqlite3VdbeMemShallowCopy(pOut, pIn, MEM_Ephem); break; } #endif /* #ifndef SQLITE_OMIT_TRIGGER */ #ifndef SQLITE_OMIT_FOREIGN_KEY /* Opcode: FkCounter P1 P2 * * * +** Synopsis: fkctr[P1]+=P2 ** ** Increment a "constraint counter" by P2 (P2 may be negative or positive). ** If P1 is non-zero, the database constraint counter is incremented ** (deferred foreign key constraints). Otherwise, if P1 is zero, the ** statement counter is incremented (immediate foreign key constraints). @@ -71406,10 +76171,11 @@ } break; } /* Opcode: FkIfZero P1 P2 * * * +** Synopsis: if fkctr[P1]==0 goto P2 ** ** This opcode tests if a foreign key constraint-counter is currently zero. ** If so, jump to instruction P2. Otherwise, fall through to the next ** instruction. ** @@ -71418,20 +76184,23 @@ ** zero, the jump is taken if the statement constraint-counter is zero ** (immediate foreign key constraint violations). */ case OP_FkIfZero: { /* jump */ if( pOp->p1 ){ + VdbeBranchTaken(db->nDeferredCons==0 && db->nDeferredImmCons==0, 2); if( db->nDeferredCons==0 && db->nDeferredImmCons==0 ) pc = pOp->p2-1; }else{ + VdbeBranchTaken(p->nFkConstraint==0 && db->nDeferredImmCons==0, 2); if( p->nFkConstraint==0 && db->nDeferredImmCons==0 ) pc = pOp->p2-1; } break; } #endif /* #ifndef SQLITE_OMIT_FOREIGN_KEY */ #ifndef SQLITE_OMIT_AUTOINCREMENT /* Opcode: MemMax P1 P2 * * * +** Synopsis: r[P1]=max(r[P1],r[P2]) ** ** P1 is a register in the root frame of this VM (the root frame is ** different from the current frame if this instruction is being executed ** within a sub-program). Set the value of register P1 to the maximum of ** its current value and the value in register P2. @@ -71438,82 +76207,120 @@ ** ** This instruction throws an error if the memory cell is not initially ** an integer. */ case OP_MemMax: { /* in2 */ -#if 0 /* local variables moved into u.cf */ - Mem *pIn1; VdbeFrame *pFrame; -#endif /* local variables moved into u.cf */ if( p->pFrame ){ - for(u.cf.pFrame=p->pFrame; u.cf.pFrame->pParent; u.cf.pFrame=u.cf.pFrame->pParent); - u.cf.pIn1 = &u.cf.pFrame->aMem[pOp->p1]; + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + pIn1 = &pFrame->aMem[pOp->p1]; }else{ - u.cf.pIn1 = &aMem[pOp->p1]; + pIn1 = &aMem[pOp->p1]; } - assert( memIsValid(u.cf.pIn1) ); - sqlite3VdbeMemIntegerify(u.cf.pIn1); + assert( memIsValid(pIn1) ); + sqlite3VdbeMemIntegerify(pIn1); pIn2 = &aMem[pOp->p2]; sqlite3VdbeMemIntegerify(pIn2); - if( u.cf.pIn1->u.i<pIn2->u.i){ - u.cf.pIn1->u.i = pIn2->u.i; + if( pIn1->u.i<pIn2->u.i){ + pIn1->u.i = pIn2->u.i; } break; } #endif /* SQLITE_OMIT_AUTOINCREMENT */ /* Opcode: IfPos P1 P2 * * * +** Synopsis: if r[P1]>0 goto P2 ** -** If the value of register P1 is 1 or greater, jump to P2. +** Register P1 must contain an integer. +** If the value of register P1 is 1 or greater, jump to P2 and +** add the literal value P3 to register P1. ** -** It is illegal to use this instruction on a register that does -** not contain an integer. An assertion fault will result if you try. +** If the initial value of register P1 is less than 1, then the +** value is unchanged and control passes through to the next instruction. */ case OP_IfPos: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; assert( pIn1->flags&MEM_Int ); + VdbeBranchTaken( pIn1->u.i>0, 2); if( pIn1->u.i>0 ){ pc = pOp->p2 - 1; } break; } -/* Opcode: IfNeg P1 P2 * * * +/* Opcode: IfNeg P1 P2 P3 * * +** Synopsis: r[P1]+=P3, if r[P1]<0 goto P2 ** -** If the value of register P1 is less than zero, jump to P2. -** -** It is illegal to use this instruction on a register that does -** not contain an integer. An assertion fault will result if you try. +** Register P1 must contain an integer. Add literal P3 to the value in +** register P1 then if the value of register P1 is less than zero, jump to P2. */ case OP_IfNeg: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; assert( pIn1->flags&MEM_Int ); + pIn1->u.i += pOp->p3; + VdbeBranchTaken(pIn1->u.i<0, 2); if( pIn1->u.i<0 ){ pc = pOp->p2 - 1; } break; } -/* Opcode: IfZero P1 P2 P3 * * -** -** The register P1 must contain an integer. Add literal P3 to the -** value in register P1. If the result is exactly 0, jump to P2. -** -** It is illegal to use this instruction on a register that does -** not contain an integer. An assertion fault will result if you try. -*/ -case OP_IfZero: { /* jump, in1 */ +/* Opcode: IfNotZero P1 P2 P3 * * +** Synopsis: if r[P1]!=0 then r[P1]+=P3, goto P2 +** +** Register P1 must contain an integer. If the content of register P1 is +** initially nonzero, then add P3 to P1 and jump to P2. If register P1 is +** initially zero, leave it unchanged and fall through. +*/ +case OP_IfNotZero: { /* jump, in1 */ + pIn1 = &aMem[pOp->p1]; + assert( pIn1->flags&MEM_Int ); + VdbeBranchTaken(pIn1->u.i<0, 2); + if( pIn1->u.i ){ + pIn1->u.i += pOp->p3; + pc = pOp->p2 - 1; + } + break; +} + +/* Opcode: DecrJumpZero P1 P2 * * * +** Synopsis: if (--r[P1])==0 goto P2 +** +** Register P1 must hold an integer. Decrement the value in register P1 +** then jump to P2 if the new value is exactly zero. +*/ +case OP_DecrJumpZero: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; assert( pIn1->flags&MEM_Int ); - pIn1->u.i += pOp->p3; + pIn1->u.i--; + VdbeBranchTaken(pIn1->u.i==0, 2); if( pIn1->u.i==0 ){ pc = pOp->p2 - 1; } break; } + +/* Opcode: JumpZeroIncr P1 P2 * * * +** Synopsis: if (r[P1]++)==0 ) goto P2 +** +** The register P1 must contain an integer. If register P1 is initially +** zero, then jump to P2. Increment register P1 regardless of whether or +** not the jump is taken. +*/ +case OP_JumpZeroIncr: { /* jump, in1 */ + pIn1 = &aMem[pOp->p1]; + assert( pIn1->flags&MEM_Int ); + VdbeBranchTaken(pIn1->u.i==0, 2); + if( (pIn1->u.i++)==0 ){ + pc = pOp->p2 - 1; + } + break; +} + /* Opcode: AggStep * P2 P3 P4 P5 +** Synopsis: accum=r[P3] step(r[P2@P5]) ** ** Execute the step function for an aggregate. The ** function has P5 arguments. P4 is a pointer to the FuncDef ** structure that specifies the function. Use register ** P3 as the accumulator. @@ -71520,65 +76327,54 @@ ** ** The P5 arguments are taken from register P2 and its ** successors. */ case OP_AggStep: { -#if 0 /* local variables moved into u.cg */ int n; int i; Mem *pMem; Mem *pRec; + Mem t; sqlite3_context ctx; sqlite3_value **apVal; -#endif /* local variables moved into u.cg */ - - u.cg.n = pOp->p5; - assert( u.cg.n>=0 ); - u.cg.pRec = &aMem[pOp->p2]; - u.cg.apVal = p->apArg; - assert( u.cg.apVal || u.cg.n==0 ); - for(u.cg.i=0; u.cg.i<u.cg.n; u.cg.i++, u.cg.pRec++){ - assert( memIsValid(u.cg.pRec) ); - u.cg.apVal[u.cg.i] = u.cg.pRec; - memAboutToChange(p, u.cg.pRec); - sqlite3VdbeMemStoreType(u.cg.pRec); - } - u.cg.ctx.pFunc = pOp->p4.pFunc; - assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); - u.cg.ctx.pMem = u.cg.pMem = &aMem[pOp->p3]; - u.cg.pMem->n++; - u.cg.ctx.s.flags = MEM_Null; - u.cg.ctx.s.z = 0; - u.cg.ctx.s.zMalloc = 0; - u.cg.ctx.s.xDel = 0; - u.cg.ctx.s.db = db; - u.cg.ctx.isError = 0; - u.cg.ctx.pColl = 0; - u.cg.ctx.skipFlag = 0; - if( u.cg.ctx.pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ - assert( pOp>p->aOp ); - assert( pOp[-1].p4type==P4_COLLSEQ ); - assert( pOp[-1].opcode==OP_CollSeq ); - u.cg.ctx.pColl = pOp[-1].p4.pColl; - } - (u.cg.ctx.pFunc->xStep)(&u.cg.ctx, u.cg.n, u.cg.apVal); /* IMP: R-24505-23230 */ - if( u.cg.ctx.isError ){ - sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3_value_text(&u.cg.ctx.s)); - rc = u.cg.ctx.isError; - } - if( u.cg.ctx.skipFlag ){ - assert( pOp[-1].opcode==OP_CollSeq ); - u.cg.i = pOp[-1].p1; - if( u.cg.i ) sqlite3VdbeMemSetInt64(&aMem[u.cg.i], 1); - } - - sqlite3VdbeMemRelease(&u.cg.ctx.s); - + + n = pOp->p5; + assert( n>=0 ); + pRec = &aMem[pOp->p2]; + apVal = p->apArg; + assert( apVal || n==0 ); + for(i=0; i<n; i++, pRec++){ + assert( memIsValid(pRec) ); + apVal[i] = pRec; + memAboutToChange(p, pRec); + } + ctx.pFunc = pOp->p4.pFunc; + assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); + ctx.pMem = pMem = &aMem[pOp->p3]; + pMem->n++; + sqlite3VdbeMemInit(&t, db, MEM_Null); + ctx.pOut = &t; + ctx.isError = 0; + ctx.pVdbe = p; + ctx.iOp = pc; + ctx.skipFlag = 0; + (ctx.pFunc->xStep)(&ctx, n, apVal); /* IMP: R-24505-23230 */ + if( ctx.isError ){ + sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3_value_text(&t)); + rc = ctx.isError; + } + if( ctx.skipFlag ){ + assert( pOp[-1].opcode==OP_CollSeq ); + i = pOp[-1].p1; + if( i ) sqlite3VdbeMemSetInt64(&aMem[i], 1); + } + sqlite3VdbeMemRelease(&t); break; } /* Opcode: AggFinal P1 P2 * P4 * +** Synopsis: accum=r[P1] N=P2 ** ** Execute the finalizer function for an aggregate. P1 is ** the memory location that is the accumulator for the aggregate. ** ** P2 is the number of arguments that the step function takes and @@ -71587,68 +76383,65 @@ ** functions that can take varying numbers of arguments. The ** P4 argument is only needed for the degenerate case where ** the step function was not previously called. */ case OP_AggFinal: { -#if 0 /* local variables moved into u.ch */ Mem *pMem; -#endif /* local variables moved into u.ch */ assert( pOp->p1>0 && pOp->p1<=(p->nMem-p->nCursor) ); - u.ch.pMem = &aMem[pOp->p1]; - assert( (u.ch.pMem->flags & ~(MEM_Null|MEM_Agg))==0 ); - rc = sqlite3VdbeMemFinalize(u.ch.pMem, pOp->p4.pFunc); + pMem = &aMem[pOp->p1]; + assert( (pMem->flags & ~(MEM_Null|MEM_Agg))==0 ); + rc = sqlite3VdbeMemFinalize(pMem, pOp->p4.pFunc); if( rc ){ - sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3_value_text(u.ch.pMem)); + sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3_value_text(pMem)); } - sqlite3VdbeChangeEncoding(u.ch.pMem, encoding); - UPDATE_MAX_BLOBSIZE(u.ch.pMem); - if( sqlite3VdbeMemTooBig(u.ch.pMem) ){ + sqlite3VdbeChangeEncoding(pMem, encoding); + UPDATE_MAX_BLOBSIZE(pMem); + if( sqlite3VdbeMemTooBig(pMem) ){ goto too_big; } break; } #ifndef SQLITE_OMIT_WAL /* Opcode: Checkpoint P1 P2 P3 * * ** ** Checkpoint database P1. This is a no-op if P1 is not currently in -** WAL mode. Parameter P2 is one of SQLITE_CHECKPOINT_PASSIVE, FULL -** or RESTART. Write 1 or 0 into mem[P3] if the checkpoint returns +** WAL mode. Parameter P2 is one of SQLITE_CHECKPOINT_PASSIVE, FULL, +** RESTART, or TRUNCATE. Write 1 or 0 into mem[P3] if the checkpoint returns ** SQLITE_BUSY or not, respectively. Write the number of pages in the ** WAL after the checkpoint into mem[P3+1] and the number of pages ** in the WAL that have been checkpointed after the checkpoint ** completes into mem[P3+2]. However on an error, mem[P3+1] and ** mem[P3+2] are initialized to -1. */ case OP_Checkpoint: { -#if 0 /* local variables moved into u.ci */ int i; /* Loop counter */ int aRes[3]; /* Results */ Mem *pMem; /* Write results here */ -#endif /* local variables moved into u.ci */ assert( p->readOnly==0 ); - u.ci.aRes[0] = 0; - u.ci.aRes[1] = u.ci.aRes[2] = -1; + aRes[0] = 0; + aRes[1] = aRes[2] = -1; assert( pOp->p2==SQLITE_CHECKPOINT_PASSIVE || pOp->p2==SQLITE_CHECKPOINT_FULL || pOp->p2==SQLITE_CHECKPOINT_RESTART + || pOp->p2==SQLITE_CHECKPOINT_TRUNCATE ); - rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &u.ci.aRes[1], &u.ci.aRes[2]); + rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]); if( rc==SQLITE_BUSY ){ rc = SQLITE_OK; - u.ci.aRes[0] = 1; + aRes[0] = 1; } - for(u.ci.i=0, u.ci.pMem = &aMem[pOp->p3]; u.ci.i<3; u.ci.i++, u.ci.pMem++){ - sqlite3VdbeMemSetInt64(u.ci.pMem, (i64)u.ci.aRes[u.ci.i]); - } + for(i=0, pMem = &aMem[pOp->p3]; i<3; i++, pMem++){ + sqlite3VdbeMemSetInt64(pMem, (i64)aRes[i]); + } break; }; #endif #ifndef SQLITE_OMIT_PRAGMA -/* Opcode: JournalMode P1 P2 P3 * P5 +/* Opcode: JournalMode P1 P2 P3 * * ** ** Change the journal mode of database P1 to P3. P3 must be one of the ** PAGER_JOURNALMODE_XXX values. If changing between the various rollback ** modes (delete, truncate, persist, off and memory), this is a simple ** operation. No IO is required. @@ -71656,98 +76449,96 @@ ** If changing into or out of WAL mode the procedure is more complicated. ** ** Write a string containing the final journal-mode to register P2. */ case OP_JournalMode: { /* out2-prerelease */ -#if 0 /* local variables moved into u.cj */ Btree *pBt; /* Btree to change journal mode of */ Pager *pPager; /* Pager associated with pBt */ int eNew; /* New journal mode */ int eOld; /* The old journal mode */ #ifndef SQLITE_OMIT_WAL const char *zFilename; /* Name of database file for pPager */ #endif -#endif /* local variables moved into u.cj */ - - u.cj.eNew = pOp->p3; - assert( u.cj.eNew==PAGER_JOURNALMODE_DELETE - || u.cj.eNew==PAGER_JOURNALMODE_TRUNCATE - || u.cj.eNew==PAGER_JOURNALMODE_PERSIST - || u.cj.eNew==PAGER_JOURNALMODE_OFF - || u.cj.eNew==PAGER_JOURNALMODE_MEMORY - || u.cj.eNew==PAGER_JOURNALMODE_WAL - || u.cj.eNew==PAGER_JOURNALMODE_QUERY + + eNew = pOp->p3; + assert( eNew==PAGER_JOURNALMODE_DELETE + || eNew==PAGER_JOURNALMODE_TRUNCATE + || eNew==PAGER_JOURNALMODE_PERSIST + || eNew==PAGER_JOURNALMODE_OFF + || eNew==PAGER_JOURNALMODE_MEMORY + || eNew==PAGER_JOURNALMODE_WAL + || eNew==PAGER_JOURNALMODE_QUERY ); assert( pOp->p1>=0 && pOp->p1<db->nDb ); assert( p->readOnly==0 ); - u.cj.pBt = db->aDb[pOp->p1].pBt; - u.cj.pPager = sqlite3BtreePager(u.cj.pBt); - u.cj.eOld = sqlite3PagerGetJournalMode(u.cj.pPager); - if( u.cj.eNew==PAGER_JOURNALMODE_QUERY ) u.cj.eNew = u.cj.eOld; - if( !sqlite3PagerOkToChangeJournalMode(u.cj.pPager) ) u.cj.eNew = u.cj.eOld; + pBt = db->aDb[pOp->p1].pBt; + pPager = sqlite3BtreePager(pBt); + eOld = sqlite3PagerGetJournalMode(pPager); + if( eNew==PAGER_JOURNALMODE_QUERY ) eNew = eOld; + if( !sqlite3PagerOkToChangeJournalMode(pPager) ) eNew = eOld; #ifndef SQLITE_OMIT_WAL - u.cj.zFilename = sqlite3PagerFilename(u.cj.pPager, 1); + zFilename = sqlite3PagerFilename(pPager, 1); /* Do not allow a transition to journal_mode=WAL for a database - ** in temporary storage or if the VFS does not support shared memory + ** in temporary storage or if the VFS does not support shared memory */ - if( u.cj.eNew==PAGER_JOURNALMODE_WAL - && (sqlite3Strlen30(u.cj.zFilename)==0 /* Temp file */ - || !sqlite3PagerWalSupported(u.cj.pPager)) /* No shared-memory support */ + if( eNew==PAGER_JOURNALMODE_WAL + && (sqlite3Strlen30(zFilename)==0 /* Temp file */ + || !sqlite3PagerWalSupported(pPager)) /* No shared-memory support */ ){ - u.cj.eNew = u.cj.eOld; + eNew = eOld; } - if( (u.cj.eNew!=u.cj.eOld) - && (u.cj.eOld==PAGER_JOURNALMODE_WAL || u.cj.eNew==PAGER_JOURNALMODE_WAL) + if( (eNew!=eOld) + && (eOld==PAGER_JOURNALMODE_WAL || eNew==PAGER_JOURNALMODE_WAL) ){ if( !db->autoCommit || db->nVdbeRead>1 ){ rc = SQLITE_ERROR; - sqlite3SetString(&p->zErrMsg, db, + sqlite3SetString(&p->zErrMsg, db, "cannot change %s wal mode from within a transaction", - (u.cj.eNew==PAGER_JOURNALMODE_WAL ? "into" : "out of") + (eNew==PAGER_JOURNALMODE_WAL ? "into" : "out of") ); break; }else{ - - if( u.cj.eOld==PAGER_JOURNALMODE_WAL ){ + + if( eOld==PAGER_JOURNALMODE_WAL ){ /* If leaving WAL mode, close the log file. If successful, the call - ** to PagerCloseWal() checkpoints and deletes the write-ahead-log - ** file. An EXCLUSIVE lock may still be held on the database file - ** after a successful return. + ** to PagerCloseWal() checkpoints and deletes the write-ahead-log + ** file. An EXCLUSIVE lock may still be held on the database file + ** after a successful return. */ - rc = sqlite3PagerCloseWal(u.cj.pPager); + rc = sqlite3PagerCloseWal(pPager); if( rc==SQLITE_OK ){ - sqlite3PagerSetJournalMode(u.cj.pPager, u.cj.eNew); + sqlite3PagerSetJournalMode(pPager, eNew); } - }else if( u.cj.eOld==PAGER_JOURNALMODE_MEMORY ){ + }else if( eOld==PAGER_JOURNALMODE_MEMORY ){ /* Cannot transition directly from MEMORY to WAL. Use mode OFF ** as an intermediate */ - sqlite3PagerSetJournalMode(u.cj.pPager, PAGER_JOURNALMODE_OFF); + sqlite3PagerSetJournalMode(pPager, PAGER_JOURNALMODE_OFF); } - + /* Open a transaction on the database file. Regardless of the journal ** mode, this transaction always uses a rollback journal. */ - assert( sqlite3BtreeIsInTrans(u.cj.pBt)==0 ); + assert( sqlite3BtreeIsInTrans(pBt)==0 ); if( rc==SQLITE_OK ){ - rc = sqlite3BtreeSetVersion(u.cj.pBt, (u.cj.eNew==PAGER_JOURNALMODE_WAL ? 2 : 1)); + rc = sqlite3BtreeSetVersion(pBt, (eNew==PAGER_JOURNALMODE_WAL ? 2 : 1)); } } } #endif /* ifndef SQLITE_OMIT_WAL */ if( rc ){ - u.cj.eNew = u.cj.eOld; + eNew = eOld; } - u.cj.eNew = sqlite3PagerSetJournalMode(u.cj.pPager, u.cj.eNew); + eNew = sqlite3PagerSetJournalMode(pPager, eNew); pOut = &aMem[pOp->p2]; pOut->flags = MEM_Str|MEM_Static|MEM_Term; - pOut->z = (char *)sqlite3JournalModename(u.cj.eNew); + pOut->z = (char *)sqlite3JournalModename(eNew); pOut->n = sqlite3Strlen30(pOut->z); pOut->enc = SQLITE_UTF8; sqlite3VdbeChangeEncoding(pOut, encoding); break; }; @@ -71773,19 +76564,18 @@ ** Perform a single step of the incremental vacuum procedure on ** the P1 database. If the vacuum has finished, jump to instruction ** P2. Otherwise, fall through to the next instruction. */ case OP_IncrVacuum: { /* jump */ -#if 0 /* local variables moved into u.ck */ Btree *pBt; -#endif /* local variables moved into u.ck */ assert( pOp->p1>=0 && pOp->p1<db->nDb ); - assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 ); + assert( DbMaskTest(p->btreeMask, pOp->p1) ); assert( p->readOnly==0 ); - u.ck.pBt = db->aDb[pOp->p1].pBt; - rc = sqlite3BtreeIncrVacuum(u.ck.pBt); + pBt = db->aDb[pOp->p1].pBt; + rc = sqlite3BtreeIncrVacuum(pBt); + VdbeBranchTaken(rc==SQLITE_DONE,2); if( rc==SQLITE_DONE ){ pc = pOp->p2 - 1; rc = SQLITE_OK; } break; @@ -71792,16 +76582,17 @@ } #endif /* Opcode: Expire P1 * * * * ** -** Cause precompiled statements to become expired. An expired statement -** fails with an error code of SQLITE_SCHEMA if it is ever executed -** (via sqlite3_step()). +** Cause precompiled statements to expire. When an expired statement +** is executed using sqlite3_step() it will either automatically +** reprepare itself (if it was originally created using sqlite3_prepare_v2()) +** or it will fail with SQLITE_SCHEMA. ** ** If P1 is 0, then all SQL statements become expired. If P1 is non-zero, -** then only the currently executing statement is affected. +** then only the currently executing statement is expired. */ case OP_Expire: { if( !pOp->p1 ){ sqlite3ExpirePreparedStatements(db); }else{ @@ -71810,10 +76601,11 @@ break; } #ifndef SQLITE_OMIT_SHARED_CACHE /* Opcode: TableLock P1 P2 P3 P4 * +** Synopsis: iDb=P1 root=P2 write=P3 ** ** Obtain a lock on a particular table. This instruction is only used when ** the shared-cache feature is enabled. ** ** P1 is the index of the database in sqlite3.aDb[] of the database @@ -71828,11 +76620,11 @@ case OP_TableLock: { u8 isWriteLock = (u8)pOp->p3; if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommitted) ){ int p1 = pOp->p1; assert( p1>=0 && p1<db->nDb ); - assert( (p->btreeMask & (((yDbMask)1)<<p1))!=0 ); + assert( DbMaskTest(p->btreeMask, p1) ); assert( isWriteLock==0 || isWriteLock==1 ); rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock); if( (rc&0xFF)==SQLITE_LOCKED ){ const char *z = pOp->p4.z; sqlite3SetString(&p->zErrMsg, db, "database table is locked: %s", z); @@ -71851,28 +76643,42 @@ ** Also, whether or not P4 is set, check that this is not being called from ** within a callback to a virtual table xSync() method. If it is, the error ** code will be set to SQLITE_LOCKED. */ case OP_VBegin: { -#if 0 /* local variables moved into u.cl */ VTable *pVTab; -#endif /* local variables moved into u.cl */ - u.cl.pVTab = pOp->p4.pVtab; - rc = sqlite3VtabBegin(db, u.cl.pVTab); - if( u.cl.pVTab ) sqlite3VtabImportErrmsg(p, u.cl.pVTab->pVtab); + pVTab = pOp->p4.pVtab; + rc = sqlite3VtabBegin(db, pVTab); + if( pVTab ) sqlite3VtabImportErrmsg(p, pVTab->pVtab); break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ #ifndef SQLITE_OMIT_VIRTUALTABLE -/* Opcode: VCreate P1 * * P4 * +/* Opcode: VCreate P1 P2 * * * ** -** P4 is the name of a virtual table in database P1. Call the xCreate method -** for that table. +** P2 is a register that holds the name of a virtual table in database +** P1. Call the xCreate method for that table. */ case OP_VCreate: { - rc = sqlite3VtabCallCreate(db, pOp->p1, pOp->p4.z, &p->zErrMsg); + Mem sMem; /* For storing the record being decoded */ + const char *zTab; /* Name of the virtual table */ + + memset(&sMem, 0, sizeof(sMem)); + sMem.db = db; + /* Because P2 is always a static string, it is impossible for the + ** sqlite3VdbeMemCopy() to fail */ + assert( (aMem[pOp->p2].flags & MEM_Str)!=0 ); + assert( (aMem[pOp->p2].flags & MEM_Static)!=0 ); + rc = sqlite3VdbeMemCopy(&sMem, &aMem[pOp->p2]); + assert( rc==SQLITE_OK ); + zTab = (const char*)sqlite3_value_text(&sMem); + assert( zTab || db->mallocFailed ); + if( zTab ){ + rc = sqlite3VtabCallCreate(db, pOp->p1, zTab, &p->zErrMsg); + } + sqlite3VdbeMemRelease(&sMem); break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -71880,13 +76686,13 @@ ** ** P4 is the name of a virtual table in database P1. Call the xDestroy method ** of that table. */ case OP_VDestroy: { - p->inVtabMethod = 2; + db->nVDestroy++; rc = sqlite3VtabCallDestroy(db, pOp->p1, pOp->p4.z); - p->inVtabMethod = 0; + db->nVDestroy--; break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -71895,45 +76701,47 @@ ** P4 is a pointer to a virtual table object, an sqlite3_vtab structure. ** P1 is a cursor number. This opcode opens a cursor to the virtual ** table and stores that cursor in P1. */ case OP_VOpen: { -#if 0 /* local variables moved into u.cm */ VdbeCursor *pCur; sqlite3_vtab_cursor *pVtabCursor; sqlite3_vtab *pVtab; - sqlite3_module *pModule; -#endif /* local variables moved into u.cm */ + const sqlite3_module *pModule; assert( p->bIsReader ); - u.cm.pCur = 0; - u.cm.pVtabCursor = 0; - u.cm.pVtab = pOp->p4.pVtab->pVtab; - u.cm.pModule = (sqlite3_module *)u.cm.pVtab->pModule; - assert(u.cm.pVtab && u.cm.pModule); - rc = u.cm.pModule->xOpen(u.cm.pVtab, &u.cm.pVtabCursor); - sqlite3VtabImportErrmsg(p, u.cm.pVtab); + pCur = 0; + pVtabCursor = 0; + pVtab = pOp->p4.pVtab->pVtab; + if( pVtab==0 || NEVER(pVtab->pModule==0) ){ + rc = SQLITE_LOCKED; + break; + } + pModule = pVtab->pModule; + rc = pModule->xOpen(pVtab, &pVtabCursor); + sqlite3VtabImportErrmsg(p, pVtab); if( SQLITE_OK==rc ){ /* Initialize sqlite3_vtab_cursor base class */ - u.cm.pVtabCursor->pVtab = u.cm.pVtab; + pVtabCursor->pVtab = pVtab; /* Initialize vdbe cursor object */ - u.cm.pCur = allocateCursor(p, pOp->p1, 0, -1, 0); - if( u.cm.pCur ){ - u.cm.pCur->pVtabCursor = u.cm.pVtabCursor; - u.cm.pCur->pModule = u.cm.pVtabCursor->pVtab->pModule; + pCur = allocateCursor(p, pOp->p1, 0, -1, 0); + if( pCur ){ + pCur->pVtabCursor = pVtabCursor; + pVtab->nRef++; }else{ db->mallocFailed = 1; - u.cm.pModule->xClose(u.cm.pVtabCursor); + pModule->xClose(pVtabCursor); } } break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VFilter P1 P2 P3 P4 * +** Synopsis: iplan=r[P3] zplan='P4' ** ** P1 is a cursor opened using VOpen. P2 is an address to jump to if ** the filtered result set is empty. ** ** P4 is either NULL or a string that was generated by the xBestIndex @@ -71948,11 +76756,10 @@ ** xFilter as argv. Register P3+2 becomes argv[0] when passed to xFilter. ** ** A jump is made to P2 if the result set after filtering would be empty. */ case OP_VFilter: { /* jump */ -#if 0 /* local variables moved into u.cn */ int nArg; int iQuery; const sqlite3_module *pModule; Mem *pQuery; Mem *pArgc; @@ -71960,107 +76767,89 @@ sqlite3_vtab *pVtab; VdbeCursor *pCur; int res; int i; Mem **apArg; -#endif /* local variables moved into u.cn */ - - u.cn.pQuery = &aMem[pOp->p3]; - u.cn.pArgc = &u.cn.pQuery[1]; - u.cn.pCur = p->apCsr[pOp->p1]; - assert( memIsValid(u.cn.pQuery) ); - REGISTER_TRACE(pOp->p3, u.cn.pQuery); - assert( u.cn.pCur->pVtabCursor ); - u.cn.pVtabCursor = u.cn.pCur->pVtabCursor; - u.cn.pVtab = u.cn.pVtabCursor->pVtab; - u.cn.pModule = u.cn.pVtab->pModule; + + pQuery = &aMem[pOp->p3]; + pArgc = &pQuery[1]; + pCur = p->apCsr[pOp->p1]; + assert( memIsValid(pQuery) ); + REGISTER_TRACE(pOp->p3, pQuery); + assert( pCur->pVtabCursor ); + pVtabCursor = pCur->pVtabCursor; + pVtab = pVtabCursor->pVtab; + pModule = pVtab->pModule; /* Grab the index number and argc parameters */ - assert( (u.cn.pQuery->flags&MEM_Int)!=0 && u.cn.pArgc->flags==MEM_Int ); - u.cn.nArg = (int)u.cn.pArgc->u.i; - u.cn.iQuery = (int)u.cn.pQuery->u.i; + assert( (pQuery->flags&MEM_Int)!=0 && pArgc->flags==MEM_Int ); + nArg = (int)pArgc->u.i; + iQuery = (int)pQuery->u.i; /* Invoke the xFilter method */ { - u.cn.res = 0; - u.cn.apArg = p->apArg; - for(u.cn.i = 0; u.cn.i<u.cn.nArg; u.cn.i++){ - u.cn.apArg[u.cn.i] = &u.cn.pArgc[u.cn.i+1]; - sqlite3VdbeMemStoreType(u.cn.apArg[u.cn.i]); - } - - p->inVtabMethod = 1; - rc = u.cn.pModule->xFilter(u.cn.pVtabCursor, u.cn.iQuery, pOp->p4.z, u.cn.nArg, u.cn.apArg); - p->inVtabMethod = 0; - sqlite3VtabImportErrmsg(p, u.cn.pVtab); - if( rc==SQLITE_OK ){ - u.cn.res = u.cn.pModule->xEof(u.cn.pVtabCursor); - } - - if( u.cn.res ){ + res = 0; + apArg = p->apArg; + for(i = 0; i<nArg; i++){ + apArg[i] = &pArgc[i+1]; + } + + rc = pModule->xFilter(pVtabCursor, iQuery, pOp->p4.z, nArg, apArg); + sqlite3VtabImportErrmsg(p, pVtab); + if( rc==SQLITE_OK ){ + res = pModule->xEof(pVtabCursor); + } + VdbeBranchTaken(res!=0,2); + if( res ){ pc = pOp->p2 - 1; } } - u.cn.pCur->nullRow = 0; + pCur->nullRow = 0; break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VColumn P1 P2 P3 * * +** Synopsis: r[P3]=vcolumn(P2) ** ** Store the value of the P2-th column of ** the row of the virtual-table that the ** P1 cursor is pointing to into register P3. */ case OP_VColumn: { -#if 0 /* local variables moved into u.co */ sqlite3_vtab *pVtab; const sqlite3_module *pModule; Mem *pDest; sqlite3_context sContext; -#endif /* local variables moved into u.co */ VdbeCursor *pCur = p->apCsr[pOp->p1]; assert( pCur->pVtabCursor ); assert( pOp->p3>0 && pOp->p3<=(p->nMem-p->nCursor) ); - u.co.pDest = &aMem[pOp->p3]; - memAboutToChange(p, u.co.pDest); + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); if( pCur->nullRow ){ - sqlite3VdbeMemSetNull(u.co.pDest); + sqlite3VdbeMemSetNull(pDest); break; } - u.co.pVtab = pCur->pVtabCursor->pVtab; - u.co.pModule = u.co.pVtab->pModule; - assert( u.co.pModule->xColumn ); - memset(&u.co.sContext, 0, sizeof(u.co.sContext)); - - /* The output cell may already have a buffer allocated. Move - ** the current contents to u.co.sContext.s so in case the user-function - ** can use the already allocated buffer instead of allocating a - ** new one. - */ - sqlite3VdbeMemMove(&u.co.sContext.s, u.co.pDest); - MemSetTypeFlag(&u.co.sContext.s, MEM_Null); - - rc = u.co.pModule->xColumn(pCur->pVtabCursor, &u.co.sContext, pOp->p2); - sqlite3VtabImportErrmsg(p, u.co.pVtab); - if( u.co.sContext.isError ){ - rc = u.co.sContext.isError; - } - - /* Copy the result of the function to the P3 register. We - ** do this regardless of whether or not an error occurred to ensure any - ** dynamic allocation in u.co.sContext.s (a Mem struct) is released. - */ - sqlite3VdbeChangeEncoding(&u.co.sContext.s, encoding); - sqlite3VdbeMemMove(u.co.pDest, &u.co.sContext.s); - REGISTER_TRACE(pOp->p3, u.co.pDest); - UPDATE_MAX_BLOBSIZE(u.co.pDest); - - if( sqlite3VdbeMemTooBig(u.co.pDest) ){ + pVtab = pCur->pVtabCursor->pVtab; + pModule = pVtab->pModule; + assert( pModule->xColumn ); + memset(&sContext, 0, sizeof(sContext)); + sContext.pOut = pDest; + MemSetTypeFlag(pDest, MEM_Null); + rc = pModule->xColumn(pCur->pVtabCursor, &sContext, pOp->p2); + sqlite3VtabImportErrmsg(p, pVtab); + if( sContext.isError ){ + rc = sContext.isError; + } + sqlite3VdbeChangeEncoding(pDest, encoding); + REGISTER_TRACE(pOp->p3, pDest); + UPDATE_MAX_BLOBSIZE(pDest); + + if( sqlite3VdbeMemTooBig(pDest) ){ goto too_big; } break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -72071,42 +76860,38 @@ ** Advance virtual table P1 to the next row in its result set and ** jump to instruction P2. Or, if the virtual table has reached ** the end of its result set, then fall through to the next instruction. */ case OP_VNext: { /* jump */ -#if 0 /* local variables moved into u.cp */ sqlite3_vtab *pVtab; const sqlite3_module *pModule; int res; VdbeCursor *pCur; -#endif /* local variables moved into u.cp */ - u.cp.res = 0; - u.cp.pCur = p->apCsr[pOp->p1]; - assert( u.cp.pCur->pVtabCursor ); - if( u.cp.pCur->nullRow ){ + res = 0; + pCur = p->apCsr[pOp->p1]; + assert( pCur->pVtabCursor ); + if( pCur->nullRow ){ break; } - u.cp.pVtab = u.cp.pCur->pVtabCursor->pVtab; - u.cp.pModule = u.cp.pVtab->pModule; - assert( u.cp.pModule->xNext ); + pVtab = pCur->pVtabCursor->pVtab; + pModule = pVtab->pModule; + assert( pModule->xNext ); /* Invoke the xNext() method of the module. There is no way for the ** underlying implementation to return an error if one occurs during - ** xNext(). Instead, if an error occurs, true is returned (indicating that + ** xNext(). Instead, if an error occurs, true is returned (indicating that ** data is available) and the error code returned when xColumn or ** some other method is next invoked on the save virtual table cursor. */ - p->inVtabMethod = 1; - rc = u.cp.pModule->xNext(u.cp.pCur->pVtabCursor); - p->inVtabMethod = 0; - sqlite3VtabImportErrmsg(p, u.cp.pVtab); + rc = pModule->xNext(pCur->pVtabCursor); + sqlite3VtabImportErrmsg(p, pVtab); if( rc==SQLITE_OK ){ - u.cp.res = u.cp.pModule->xEof(u.cp.pCur->pVtabCursor); + res = pModule->xEof(pCur->pVtabCursor); } - - if( !u.cp.res ){ + VdbeBranchTaken(!res,2); + if( !res ){ /* If there is data, jump to P2 */ pc = pOp->p2 - 1; } goto check_for_interrupt; } @@ -72118,37 +76903,36 @@ ** P4 is a pointer to a virtual table object, an sqlite3_vtab structure. ** This opcode invokes the corresponding xRename method. The value ** in register P1 is passed as the zName argument to the xRename method. */ case OP_VRename: { -#if 0 /* local variables moved into u.cq */ sqlite3_vtab *pVtab; Mem *pName; -#endif /* local variables moved into u.cq */ - u.cq.pVtab = pOp->p4.pVtab->pVtab; - u.cq.pName = &aMem[pOp->p1]; - assert( u.cq.pVtab->pModule->xRename ); - assert( memIsValid(u.cq.pName) ); + pVtab = pOp->p4.pVtab->pVtab; + pName = &aMem[pOp->p1]; + assert( pVtab->pModule->xRename ); + assert( memIsValid(pName) ); assert( p->readOnly==0 ); - REGISTER_TRACE(pOp->p1, u.cq.pName); - assert( u.cq.pName->flags & MEM_Str ); - testcase( u.cq.pName->enc==SQLITE_UTF8 ); - testcase( u.cq.pName->enc==SQLITE_UTF16BE ); - testcase( u.cq.pName->enc==SQLITE_UTF16LE ); - rc = sqlite3VdbeChangeEncoding(u.cq.pName, SQLITE_UTF8); + REGISTER_TRACE(pOp->p1, pName); + assert( pName->flags & MEM_Str ); + testcase( pName->enc==SQLITE_UTF8 ); + testcase( pName->enc==SQLITE_UTF16BE ); + testcase( pName->enc==SQLITE_UTF16LE ); + rc = sqlite3VdbeChangeEncoding(pName, SQLITE_UTF8); if( rc==SQLITE_OK ){ - rc = u.cq.pVtab->pModule->xRename(u.cq.pVtab, u.cq.pName->z); - sqlite3VtabImportErrmsg(p, u.cq.pVtab); + rc = pVtab->pModule->xRename(pVtab, pName->z); + sqlite3VtabImportErrmsg(p, pVtab); p->expired = 0; } break; } #endif #ifndef SQLITE_OMIT_VIRTUALTABLE -/* Opcode: VUpdate P1 P2 P3 P4 * +/* Opcode: VUpdate P1 P2 P3 P4 P5 +** Synopsis: data=r[P3@P2] ** ** P4 is a pointer to a virtual table object, an sqlite3_vtab structure. ** This opcode invokes the corresponding xUpdate method. P2 values ** are contiguous memory cells starting at P3 to pass to the xUpdate ** invocation. The value in register (P3+P2-1) corresponds to the @@ -72166,48 +76950,52 @@ ** a row to delete. ** ** P1 is a boolean flag. If it is set to true and the xUpdate call ** is successful, then the value returned by sqlite3_last_insert_rowid() ** is set to the value of the rowid for the row just inserted. +** +** P5 is the error actions (OE_Replace, OE_Fail, OE_Ignore, etc) to +** apply in the case of a constraint failure on an insert or update. */ case OP_VUpdate: { -#if 0 /* local variables moved into u.cr */ sqlite3_vtab *pVtab; - sqlite3_module *pModule; + const sqlite3_module *pModule; int nArg; int i; sqlite_int64 rowid; Mem **apArg; Mem *pX; -#endif /* local variables moved into u.cr */ - assert( pOp->p2==1 || pOp->p5==OE_Fail || pOp->p5==OE_Rollback + assert( pOp->p2==1 || pOp->p5==OE_Fail || pOp->p5==OE_Rollback || pOp->p5==OE_Abort || pOp->p5==OE_Ignore || pOp->p5==OE_Replace ); assert( p->readOnly==0 ); - u.cr.pVtab = pOp->p4.pVtab->pVtab; - u.cr.pModule = (sqlite3_module *)u.cr.pVtab->pModule; - u.cr.nArg = pOp->p2; + pVtab = pOp->p4.pVtab->pVtab; + if( pVtab==0 || NEVER(pVtab->pModule==0) ){ + rc = SQLITE_LOCKED; + break; + } + pModule = pVtab->pModule; + nArg = pOp->p2; assert( pOp->p4type==P4_VTAB ); - if( ALWAYS(u.cr.pModule->xUpdate) ){ + if( ALWAYS(pModule->xUpdate) ){ u8 vtabOnConflict = db->vtabOnConflict; - u.cr.apArg = p->apArg; - u.cr.pX = &aMem[pOp->p3]; - for(u.cr.i=0; u.cr.i<u.cr.nArg; u.cr.i++){ - assert( memIsValid(u.cr.pX) ); - memAboutToChange(p, u.cr.pX); - sqlite3VdbeMemStoreType(u.cr.pX); - u.cr.apArg[u.cr.i] = u.cr.pX; - u.cr.pX++; + apArg = p->apArg; + pX = &aMem[pOp->p3]; + for(i=0; i<nArg; i++){ + assert( memIsValid(pX) ); + memAboutToChange(p, pX); + apArg[i] = pX; + pX++; } db->vtabOnConflict = pOp->p5; - rc = u.cr.pModule->xUpdate(u.cr.pVtab, u.cr.nArg, u.cr.apArg, &u.cr.rowid); + rc = pModule->xUpdate(pVtab, nArg, apArg, &rowid); db->vtabOnConflict = vtabOnConflict; - sqlite3VtabImportErrmsg(p, u.cr.pVtab); + sqlite3VtabImportErrmsg(p, pVtab); if( rc==SQLITE_OK && pOp->p1 ){ - assert( u.cr.nArg>1 && u.cr.apArg[0] && (u.cr.apArg[0]->flags&MEM_Null) ); - db->lastRowid = lastRowid = u.cr.rowid; + assert( nArg>1 && apArg[0] && (apArg[0]->flags&MEM_Null) ); + db->lastRowid = lastRowid = rowid; } if( (rc&0xff)==SQLITE_CONSTRAINT && pOp->p4.pVtab->bConstraint ){ if( pOp->p5==OE_Ignore ){ rc = SQLITE_OK; }else{ @@ -72256,40 +77044,58 @@ break; } #endif -#ifndef SQLITE_OMIT_TRACE -/* Opcode: Trace * * * P4 * +/* Opcode: Init * P2 * P4 * +** Synopsis: Start at P2 +** +** Programs contain a single instance of this opcode as the very first +** opcode. ** ** If tracing is enabled (by the sqlite3_trace()) interface, then ** the UTF-8 string contained in P4 is emitted on the trace callback. +** Or if P4 is blank, use the string returned by sqlite3_sql(). +** +** If P2 is not zero, jump to instruction P2. */ -case OP_Trace: { -#if 0 /* local variables moved into u.cs */ +case OP_Init: { /* jump */ char *zTrace; char *z; -#endif /* local variables moved into u.cs */ + if( pOp->p2 ){ + pc = pOp->p2 - 1; + } +#ifndef SQLITE_OMIT_TRACE if( db->xTrace && !p->doingRerun - && (u.cs.zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0 + && (zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0 ){ - u.cs.z = sqlite3VdbeExpandSql(p, u.cs.zTrace); - db->xTrace(db->pTraceArg, u.cs.z); - sqlite3DbFree(db, u.cs.z); + z = sqlite3VdbeExpandSql(p, zTrace); + db->xTrace(db->pTraceArg, z); + sqlite3DbFree(db, z); } +#ifdef SQLITE_USE_FCNTL_TRACE + zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql); + if( zTrace ){ + int i; + for(i=0; i<db->nDb; i++){ + if( DbMaskTest(p->btreeMask, i)==0 ) continue; + sqlite3_file_control(db, db->aDb[i].zName, SQLITE_FCNTL_TRACE, zTrace); + } + } +#endif /* SQLITE_USE_FCNTL_TRACE */ #ifdef SQLITE_DEBUG if( (db->flags & SQLITE_SqlTrace)!=0 - && (u.cs.zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0 + && (zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0 ){ - sqlite3DebugPrintf("SQL-trace: %s\n", u.cs.zTrace); + sqlite3DebugPrintf("SQL-trace: %s\n", zTrace); } #endif /* SQLITE_DEBUG */ +#endif /* SQLITE_OMIT_TRACE */ break; } -#endif /* Opcode: Noop * * * * * ** ** Do nothing. This instruction is often useful as a jump @@ -72314,17 +77120,13 @@ *****************************************************************************/ } #ifdef VDBE_PROFILE { - u64 elapsed = sqlite3Hwtime() - start; - pOp->cycles += elapsed; + u64 endTime = sqlite3Hwtime(); + if( endTime>start ) pOp->cycles += endTime - start; pOp->cnt++; -#if 0 - fprintf(stdout, "%10llu ", elapsed); - sqlite3VdbePrintOp(stdout, origPc, &aOp[origPc]); -#endif } #endif /* The following code adds nothing to the actual functionality ** of the program. It is only here for testing and debugging. @@ -72333,17 +77135,17 @@ */ #ifndef NDEBUG assert( pc>=-1 && pc<p->nOp ); #ifdef SQLITE_DEBUG - if( p->trace ){ - if( rc!=0 ) fprintf(p->trace,"rc=%d\n",rc); + if( db->flags & SQLITE_VdbeTrace ){ + if( rc!=0 ) printf("rc=%d\n",rc); if( pOp->opflags & (OPFLG_OUT2_PRERELEASE|OPFLG_OUT2) ){ - registerTrace(p->trace, pOp->p2, &aMem[pOp->p2]); + registerTrace(pOp->p2, &aMem[pOp->p2]); } if( pOp->opflags & OPFLG_OUT3 ){ - registerTrace(p->trace, pOp->p3, &aMem[pOp->p3]); + registerTrace(pOp->p3, &aMem[pOp->p3]); } } #endif /* SQLITE_DEBUG */ #endif /* NDEBUG */ } /* The end of the for(;;) loop the loops through opcodes */ @@ -72409,10 +77211,11 @@ rc = SQLITE_INTERRUPT; p->rc = rc; sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3ErrStr(rc)); goto vdbe_error_halt; } + /************** End of vdbe.c ************************************************/ /************** Begin file vdbeblob.c ****************************************/ /* ** 2007 May 1 @@ -72476,25 +77279,24 @@ assert( v->aVar[0].flags&MEM_Int ); v->aVar[0].u.i = iRow; rc = sqlite3_step(p->pStmt); if( rc==SQLITE_ROW ){ - u32 type = v->apCsr[0]->aType[p->iCol]; + VdbeCursor *pC = v->apCsr[0]; + u32 type = pC->aType[p->iCol]; if( type<12 ){ zErr = sqlite3MPrintf(p->db, "cannot open value of type %s", type==0?"null": type==7?"real": "integer" ); rc = SQLITE_ERROR; sqlite3_finalize(p->pStmt); p->pStmt = 0; }else{ - p->iOffset = v->apCsr[0]->aOffset[p->iCol]; + p->iOffset = pC->aType[p->iCol + pC->nField]; p->nByte = sqlite3VdbeSerialTypeLen(type); - p->pCsr = v->apCsr[0]->pCursor; - sqlite3BtreeEnterCursor(p->pCsr); - sqlite3BtreeCacheOverflow(p->pCsr); - sqlite3BtreeLeaveCursor(p->pCsr); + p->pCsr = pC->pCursor; + sqlite3BtreeIncrblobCursor(p->pCsr); } } if( rc==SQLITE_ROW ){ rc = SQLITE_OK; @@ -72517,11 +77319,11 @@ } /* ** Open a blob handle. */ -SQLITE_API int sqlite3_blob_open( +SQLITE_API int SQLITE_STDCALL sqlite3_blob_open( sqlite3* db, /* The database connection */ const char *zDb, /* The attached database containing the blob */ const char *zTable, /* The table containing the blob */ const char *zColumn, /* The column containing the blob */ sqlite_int64 iRow, /* The row containing the glob */ @@ -72544,36 +77346,44 @@ ** ** The sqlite3_blob_close() function finalizes the vdbe program, ** which closes the b-tree cursor and (possibly) commits the ** transaction. */ + static const int iLn = VDBE_OFFSET_LINENO(4); static const VdbeOpList openBlob[] = { - {OP_Transaction, 0, 0, 0}, /* 0: Start a transaction */ - {OP_VerifyCookie, 0, 0, 0}, /* 1: Check the schema cookie */ - {OP_TableLock, 0, 0, 0}, /* 2: Acquire a read or write lock */ - + /* {OP_Transaction, 0, 0, 0}, // 0: Inserted separately */ + {OP_TableLock, 0, 0, 0}, /* 1: Acquire a read or write lock */ /* One of the following two instructions is replaced by an OP_Noop. */ - {OP_OpenRead, 0, 0, 0}, /* 3: Open cursor 0 for reading */ - {OP_OpenWrite, 0, 0, 0}, /* 4: Open cursor 0 for read/write */ - - {OP_Variable, 1, 1, 1}, /* 5: Push the rowid to the stack */ - {OP_NotExists, 0, 10, 1}, /* 6: Seek the cursor */ - {OP_Column, 0, 0, 1}, /* 7 */ - {OP_ResultRow, 1, 0, 0}, /* 8 */ - {OP_Goto, 0, 5, 0}, /* 9 */ - {OP_Close, 0, 0, 0}, /* 10 */ - {OP_Halt, 0, 0, 0}, /* 11 */ + {OP_OpenRead, 0, 0, 0}, /* 2: Open cursor 0 for reading */ + {OP_OpenWrite, 0, 0, 0}, /* 3: Open cursor 0 for read/write */ + {OP_Variable, 1, 1, 1}, /* 4: Push the rowid to the stack */ + {OP_NotExists, 0, 10, 1}, /* 5: Seek the cursor */ + {OP_Column, 0, 0, 1}, /* 6 */ + {OP_ResultRow, 1, 0, 0}, /* 7 */ + {OP_Goto, 0, 4, 0}, /* 8 */ + {OP_Close, 0, 0, 0}, /* 9 */ + {OP_Halt, 0, 0, 0}, /* 10 */ }; int rc = SQLITE_OK; char *zErr = 0; Table *pTab; Parse *pParse = 0; Incrblob *pBlob = 0; - flags = !!flags; /* flags = (flags ? 1 : 0); */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppBlob==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif *ppBlob = 0; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zTable==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + flags = !!flags; /* flags = (flags ? 1 : 0); */ sqlite3_mutex_enter(db->mutex); pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob)); if( !pBlob ) goto blob_open_out; @@ -72590,10 +77400,14 @@ pTab = sqlite3LocateTable(pParse, 0, zTable, zDb); if( pTab && IsVirtual(pTab) ){ pTab = 0; sqlite3ErrorMsg(pParse, "cannot open virtual table: %s", zTable); } + if( pTab && !HasRowid(pTab) ){ + pTab = 0; + sqlite3ErrorMsg(pParse, "cannot open table without rowid: %s", zTable); + } #ifndef SQLITE_OMIT_VIEW if( pTab && pTab->pSelect ){ pTab = 0; sqlite3ErrorMsg(pParse, "cannot open view: %s", zTable); } @@ -72647,11 +77461,11 @@ } } #endif for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ int j; - for(j=0; j<pIdx->nColumn; j++){ + for(j=0; j<pIdx->nKeyCol; j++){ if( pIdx->aiColumn[j]==iCol ){ zFault = "indexed"; } } } @@ -72662,56 +77476,51 @@ sqlite3BtreeLeaveAll(db); goto blob_open_out; } } - pBlob->pStmt = (sqlite3_stmt *)sqlite3VdbeCreate(db); + pBlob->pStmt = (sqlite3_stmt *)sqlite3VdbeCreate(pParse); assert( pBlob->pStmt || db->mallocFailed ); if( pBlob->pStmt ){ Vdbe *v = (Vdbe *)pBlob->pStmt; int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - sqlite3VdbeAddOpList(v, sizeof(openBlob)/sizeof(VdbeOpList), openBlob); - - - /* Configure the OP_Transaction */ - sqlite3VdbeChangeP1(v, 0, iDb); - sqlite3VdbeChangeP2(v, 0, flags); - - /* Configure the OP_VerifyCookie */ - sqlite3VdbeChangeP1(v, 1, iDb); - sqlite3VdbeChangeP2(v, 1, pTab->pSchema->schema_cookie); - sqlite3VdbeChangeP3(v, 1, pTab->pSchema->iGeneration); + + sqlite3VdbeAddOp4Int(v, OP_Transaction, iDb, flags, + pTab->pSchema->schema_cookie, + pTab->pSchema->iGeneration); + sqlite3VdbeChangeP5(v, 1); + sqlite3VdbeAddOpList(v, ArraySize(openBlob), openBlob, iLn); /* Make sure a mutex is held on the table to be accessed */ sqlite3VdbeUsesBtree(v, iDb); /* Configure the OP_TableLock instruction */ #ifdef SQLITE_OMIT_SHARED_CACHE - sqlite3VdbeChangeToNoop(v, 2); + sqlite3VdbeChangeToNoop(v, 1); #else - sqlite3VdbeChangeP1(v, 2, iDb); - sqlite3VdbeChangeP2(v, 2, pTab->tnum); - sqlite3VdbeChangeP3(v, 2, flags); - sqlite3VdbeChangeP4(v, 2, pTab->zName, P4_TRANSIENT); + sqlite3VdbeChangeP1(v, 1, iDb); + sqlite3VdbeChangeP2(v, 1, pTab->tnum); + sqlite3VdbeChangeP3(v, 1, flags); + sqlite3VdbeChangeP4(v, 1, pTab->zName, P4_TRANSIENT); #endif /* Remove either the OP_OpenWrite or OpenRead. Set the P2 ** parameter of the other to pTab->tnum. */ - sqlite3VdbeChangeToNoop(v, 4 - flags); - sqlite3VdbeChangeP2(v, 3 + flags, pTab->tnum); - sqlite3VdbeChangeP3(v, 3 + flags, iDb); + sqlite3VdbeChangeToNoop(v, 3 - flags); + sqlite3VdbeChangeP2(v, 2 + flags, pTab->tnum); + sqlite3VdbeChangeP3(v, 2 + flags, iDb); /* Configure the number of columns. Configure the cursor to ** think that the table has one more column than it really ** does. An OP_Column to retrieve this imaginary column will ** always return an SQL NULL. This is useful because it means ** we can invoke OP_Column to fill in the vdbe cursors type ** and offset cache without causing any IO. */ - sqlite3VdbeChangeP4(v, 3+flags, SQLITE_INT_TO_PTR(pTab->nCol+1),P4_INT32); - sqlite3VdbeChangeP2(v, 7, pTab->nCol); + sqlite3VdbeChangeP4(v, 2+flags, SQLITE_INT_TO_PTR(pTab->nCol+1),P4_INT32); + sqlite3VdbeChangeP2(v, 6, pTab->nCol); if( !db->mallocFailed ){ pParse->nVar = 1; pParse->nMem = 1; pParse->nTab = 1; sqlite3VdbeMakeReady(v, pParse); @@ -72734,12 +77543,13 @@ *ppBlob = (sqlite3_blob *)pBlob; }else{ if( pBlob && pBlob->pStmt ) sqlite3VdbeFinalize((Vdbe *)pBlob->pStmt); sqlite3DbFree(db, pBlob); } - sqlite3Error(db, rc, (zErr ? "%s" : 0), zErr); + sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr); sqlite3DbFree(db, zErr); + sqlite3ParserReset(pParse); sqlite3StackFree(db, pParse); rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; } @@ -72746,11 +77556,11 @@ /* ** Close a blob handle that was previously created using ** sqlite3_blob_open(). */ -SQLITE_API int sqlite3_blob_close(sqlite3_blob *pBlob){ +SQLITE_API int SQLITE_STDCALL sqlite3_blob_close(sqlite3_blob *pBlob){ Incrblob *p = (Incrblob *)pBlob; int rc; sqlite3 *db; if( p ){ @@ -72783,14 +77593,13 @@ if( p==0 ) return SQLITE_MISUSE_BKPT; db = p->db; sqlite3_mutex_enter(db->mutex); v = (Vdbe*)p->pStmt; - if( n<0 || iOffset<0 || (iOffset+n)>p->nByte ){ + if( n<0 || iOffset<0 || ((sqlite3_int64)iOffset+n)>p->nByte ){ /* Request is out of range. Return a transient error. */ rc = SQLITE_ERROR; - sqlite3Error(db, SQLITE_ERROR, 0); }else if( v==0 ){ /* If there is no statement handle, then the blob-handle has ** already been invalidated. Return SQLITE_ABORT in this case. */ rc = SQLITE_ABORT; @@ -72804,40 +77613,40 @@ sqlite3BtreeLeaveCursor(p->pCsr); if( rc==SQLITE_ABORT ){ sqlite3VdbeFinalize(v); p->pStmt = 0; }else{ - db->errCode = rc; v->rc = rc; } } + sqlite3Error(db, rc); rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; } /* ** Read data from a blob handle. */ -SQLITE_API int sqlite3_blob_read(sqlite3_blob *pBlob, void *z, int n, int iOffset){ +SQLITE_API int SQLITE_STDCALL sqlite3_blob_read(sqlite3_blob *pBlob, void *z, int n, int iOffset){ return blobReadWrite(pBlob, z, n, iOffset, sqlite3BtreeData); } /* ** Write data to a blob handle. */ -SQLITE_API int sqlite3_blob_write(sqlite3_blob *pBlob, const void *z, int n, int iOffset){ +SQLITE_API int SQLITE_STDCALL sqlite3_blob_write(sqlite3_blob *pBlob, const void *z, int n, int iOffset){ return blobReadWrite(pBlob, (void *)z, n, iOffset, sqlite3BtreePutData); } /* ** Query a blob handle for the size of the data. ** ** The Incrblob.nByte field is fixed for the lifetime of the Incrblob ** so no mutex is required for access. */ -SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *pBlob){ +SQLITE_API int SQLITE_STDCALL sqlite3_blob_bytes(sqlite3_blob *pBlob){ Incrblob *p = (Incrblob *)pBlob; return (p && p->pStmt) ? p->nByte : 0; } /* @@ -72848,11 +77657,11 @@ ** contain a blob or text value, then an error code is returned and the ** database handle error code and message set. If this happens, then all ** subsequent calls to sqlite3_blob_xxx() functions (except blob_close()) ** immediately return SQLITE_ABORT. */ -SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ +SQLITE_API int SQLITE_STDCALL sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ int rc; Incrblob *p = (Incrblob *)pBlob; sqlite3 *db; if( p==0 ) return SQLITE_MISUSE_BKPT; @@ -72866,11 +77675,11 @@ rc = SQLITE_ABORT; }else{ char *zErr; rc = blobSeekToRow(p, iRow, &zErr); if( rc!=SQLITE_OK ){ - sqlite3Error(db, rc, (zErr ? "%s" : 0), zErr); + sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr); sqlite3DbFree(db, zErr); } assert( rc!=SQLITE_SCHEMA ); } @@ -72883,11 +77692,11 @@ #endif /* #ifndef SQLITE_OMIT_INCRBLOB */ /************** End of vdbeblob.c ********************************************/ /************** Begin file vdbesort.c ****************************************/ /* -** 2011 July 9 +** 2011-07-09 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. @@ -72894,181 +77703,488 @@ ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains code for the VdbeSorter object, used in concert with -** a VdbeCursor to sort large numbers of keys (as may be required, for -** example, by CREATE INDEX statements on tables too large to fit in main -** memory). -*/ - - - -typedef struct VdbeSorterIter VdbeSorterIter; -typedef struct SorterRecord SorterRecord; -typedef struct FileWriter FileWriter; - -/* -** NOTES ON DATA STRUCTURE USED FOR N-WAY MERGES: -** -** As keys are added to the sorter, they are written to disk in a series -** of sorted packed-memory-arrays (PMAs). The size of each PMA is roughly -** the same as the cache-size allowed for temporary databases. In order -** to allow the caller to extract keys from the sorter in sorted order, -** all PMAs currently stored on disk must be merged together. This comment -** describes the data structure used to do so. The structure supports -** merging any number of arrays in a single pass with no redundant comparison -** operations. -** -** The aIter[] array contains an iterator for each of the PMAs being merged. -** An aIter[] iterator either points to a valid key or else is at EOF. For -** the purposes of the paragraphs below, we assume that the array is actually -** N elements in size, where N is the smallest power of 2 greater to or equal -** to the number of iterators being merged. The extra aIter[] elements are -** treated as if they are empty (always at EOF). +** a VdbeCursor to sort large numbers of keys for CREATE INDEX statements +** or by SELECT statements with ORDER BY clauses that cannot be satisfied +** using indexes and without LIMIT clauses. +** +** The VdbeSorter object implements a multi-threaded external merge sort +** algorithm that is efficient even if the number of elements being sorted +** exceeds the available memory. +** +** Here is the (internal, non-API) interface between this module and the +** rest of the SQLite system: +** +** sqlite3VdbeSorterInit() Create a new VdbeSorter object. +** +** sqlite3VdbeSorterWrite() Add a single new row to the VdbeSorter +** object. The row is a binary blob in the +** OP_MakeRecord format that contains both +** the ORDER BY key columns and result columns +** in the case of a SELECT w/ ORDER BY, or +** the complete record for an index entry +** in the case of a CREATE INDEX. +** +** sqlite3VdbeSorterRewind() Sort all content previously added. +** Position the read cursor on the +** first sorted element. +** +** sqlite3VdbeSorterNext() Advance the read cursor to the next sorted +** element. +** +** sqlite3VdbeSorterRowkey() Return the complete binary blob for the +** row currently under the read cursor. +** +** sqlite3VdbeSorterCompare() Compare the binary blob for the row +** currently under the read cursor against +** another binary blob X and report if +** X is strictly less than the read cursor. +** Used to enforce uniqueness in a +** CREATE UNIQUE INDEX statement. +** +** sqlite3VdbeSorterClose() Close the VdbeSorter object and reclaim +** all resources. +** +** sqlite3VdbeSorterReset() Refurbish the VdbeSorter for reuse. This +** is like Close() followed by Init() only +** much faster. +** +** The interfaces above must be called in a particular order. Write() can +** only occur in between Init()/Reset() and Rewind(). Next(), Rowkey(), and +** Compare() can only occur in between Rewind() and Close()/Reset(). i.e. +** +** Init() +** for each record: Write() +** Rewind() +** Rowkey()/Compare() +** Next() +** Close() +** +** Algorithm: +** +** Records passed to the sorter via calls to Write() are initially held +** unsorted in main memory. Assuming the amount of memory used never exceeds +** a threshold, when Rewind() is called the set of records is sorted using +** an in-memory merge sort. In this case, no temporary files are required +** and subsequent calls to Rowkey(), Next() and Compare() read records +** directly from main memory. +** +** If the amount of space used to store records in main memory exceeds the +** threshold, then the set of records currently in memory are sorted and +** written to a temporary file in "Packed Memory Array" (PMA) format. +** A PMA created at this point is known as a "level-0 PMA". Higher levels +** of PMAs may be created by merging existing PMAs together - for example +** merging two or more level-0 PMAs together creates a level-1 PMA. +** +** The threshold for the amount of main memory to use before flushing +** records to a PMA is roughly the same as the limit configured for the +** page-cache of the main database. Specifically, the threshold is set to +** the value returned by "PRAGMA main.page_size" multipled by +** that returned by "PRAGMA main.cache_size", in bytes. +** +** If the sorter is running in single-threaded mode, then all PMAs generated +** are appended to a single temporary file. Or, if the sorter is running in +** multi-threaded mode then up to (N+1) temporary files may be opened, where +** N is the configured number of worker threads. In this case, instead of +** sorting the records and writing the PMA to a temporary file itself, the +** calling thread usually launches a worker thread to do so. Except, if +** there are already N worker threads running, the main thread does the work +** itself. +** +** The sorter is running in multi-threaded mode if (a) the library was built +** with pre-processor symbol SQLITE_MAX_WORKER_THREADS set to a value greater +** than zero, and (b) worker threads have been enabled at runtime by calling +** "PRAGMA threads=N" with some value of N greater than 0. +** +** When Rewind() is called, any data remaining in memory is flushed to a +** final PMA. So at this point the data is stored in some number of sorted +** PMAs within temporary files on disk. +** +** If there are fewer than SORTER_MAX_MERGE_COUNT PMAs in total and the +** sorter is running in single-threaded mode, then these PMAs are merged +** incrementally as keys are retreived from the sorter by the VDBE. The +** MergeEngine object, described in further detail below, performs this +** merge. +** +** Or, if running in multi-threaded mode, then a background thread is +** launched to merge the existing PMAs. Once the background thread has +** merged T bytes of data into a single sorted PMA, the main thread +** begins reading keys from that PMA while the background thread proceeds +** with merging the next T bytes of data. And so on. +** +** Parameter T is set to half the value of the memory threshold used +** by Write() above to determine when to create a new PMA. +** +** If there are more than SORTER_MAX_MERGE_COUNT PMAs in total when +** Rewind() is called, then a hierarchy of incremental-merges is used. +** First, T bytes of data from the first SORTER_MAX_MERGE_COUNT PMAs on +** disk are merged together. Then T bytes of data from the second set, and +** so on, such that no operation ever merges more than SORTER_MAX_MERGE_COUNT +** PMAs at a time. This done is to improve locality. +** +** If running in multi-threaded mode and there are more than +** SORTER_MAX_MERGE_COUNT PMAs on disk when Rewind() is called, then more +** than one background thread may be created. Specifically, there may be +** one background thread for each temporary file on disk, and one background +** thread to merge the output of each of the others to a single PMA for +** the main thread to read from. +*/ + +/* +** If SQLITE_DEBUG_SORTER_THREADS is defined, this module outputs various +** messages to stderr that may be helpful in understanding the performance +** characteristics of the sorter in multi-threaded mode. +*/ +#if 0 +# define SQLITE_DEBUG_SORTER_THREADS 1 +#endif + +/* +** Hard-coded maximum amount of data to accumulate in memory before flushing +** to a level 0 PMA. The purpose of this limit is to prevent various integer +** overflows. 512MiB. +*/ +#define SQLITE_MAX_PMASZ (1<<29) + +/* +** Private objects used by the sorter +*/ +typedef struct MergeEngine MergeEngine; /* Merge PMAs together */ +typedef struct PmaReader PmaReader; /* Incrementally read one PMA */ +typedef struct PmaWriter PmaWriter; /* Incrementally write one PMA */ +typedef struct SorterRecord SorterRecord; /* A record being sorted */ +typedef struct SortSubtask SortSubtask; /* A sub-task in the sort process */ +typedef struct SorterFile SorterFile; /* Temporary file object wrapper */ +typedef struct SorterList SorterList; /* In-memory list of records */ +typedef struct IncrMerger IncrMerger; /* Read & merge multiple PMAs */ + +/* +** A container for a temp file handle and the current amount of data +** stored in the file. +*/ +struct SorterFile { + sqlite3_file *pFd; /* File handle */ + i64 iEof; /* Bytes of data stored in pFd */ +}; + +/* +** An in-memory list of objects to be sorted. +** +** If aMemory==0 then each object is allocated separately and the objects +** are connected using SorterRecord.u.pNext. If aMemory!=0 then all objects +** are stored in the aMemory[] bulk memory, one right after the other, and +** are connected using SorterRecord.u.iNext. +*/ +struct SorterList { + SorterRecord *pList; /* Linked list of records */ + u8 *aMemory; /* If non-NULL, bulk memory to hold pList */ + int szPMA; /* Size of pList as PMA in bytes */ +}; + +/* +** The MergeEngine object is used to combine two or more smaller PMAs into +** one big PMA using a merge operation. Separate PMAs all need to be +** combined into one big PMA in order to be able to step through the sorted +** records in order. +** +** The aReadr[] array contains a PmaReader object for each of the PMAs being +** merged. An aReadr[] object either points to a valid key or else is at EOF. +** ("EOF" means "End Of File". When aReadr[] is at EOF there is no more data.) +** For the purposes of the paragraphs below, we assume that the array is +** actually N elements in size, where N is the smallest power of 2 greater +** to or equal to the number of PMAs being merged. The extra aReadr[] elements +** are treated as if they are empty (always at EOF). ** ** The aTree[] array is also N elements in size. The value of N is stored in -** the VdbeSorter.nTree variable. +** the MergeEngine.nTree variable. ** ** The final (N/2) elements of aTree[] contain the results of comparing -** pairs of iterator keys together. Element i contains the result of -** comparing aIter[2*i-N] and aIter[2*i-N+1]. Whichever key is smaller, the +** pairs of PMA keys together. Element i contains the result of +** comparing aReadr[2*i-N] and aReadr[2*i-N+1]. Whichever key is smaller, the ** aTree element is set to the index of it. ** ** For the purposes of this comparison, EOF is considered greater than any ** other key value. If the keys are equal (only possible with two EOF ** values), it doesn't matter which index is stored. ** ** The (N/4) elements of aTree[] that precede the final (N/2) described -** above contains the index of the smallest of each block of 4 iterators. -** And so on. So that aTree[1] contains the index of the iterator that +** above contains the index of the smallest of each block of 4 PmaReaders +** And so on. So that aTree[1] contains the index of the PmaReader that ** currently points to the smallest key value. aTree[0] is unused. ** ** Example: ** -** aIter[0] -> Banana -** aIter[1] -> Feijoa -** aIter[2] -> Elderberry -** aIter[3] -> Currant -** aIter[4] -> Grapefruit -** aIter[5] -> Apple -** aIter[6] -> Durian -** aIter[7] -> EOF +** aReadr[0] -> Banana +** aReadr[1] -> Feijoa +** aReadr[2] -> Elderberry +** aReadr[3] -> Currant +** aReadr[4] -> Grapefruit +** aReadr[5] -> Apple +** aReadr[6] -> Durian +** aReadr[7] -> EOF ** ** aTree[] = { X, 5 0, 5 0, 3, 5, 6 } ** ** The current element is "Apple" (the value of the key indicated by -** iterator 5). When the Next() operation is invoked, iterator 5 will +** PmaReader 5). When the Next() operation is invoked, PmaReader 5 will ** be advanced to the next key in its segment. Say the next key is ** "Eggplant": ** -** aIter[5] -> Eggplant +** aReadr[5] -> Eggplant ** -** The contents of aTree[] are updated first by comparing the new iterator -** 5 key to the current key of iterator 4 (still "Grapefruit"). The iterator +** The contents of aTree[] are updated first by comparing the new PmaReader +** 5 key to the current key of PmaReader 4 (still "Grapefruit"). The PmaReader ** 5 value is still smaller, so aTree[6] is set to 5. And so on up the tree. -** The value of iterator 6 - "Durian" - is now smaller than that of iterator +** The value of PmaReader 6 - "Durian" - is now smaller than that of PmaReader ** 5, so aTree[3] is set to 6. Key 0 is smaller than key 6 (Banana<Durian), ** so the value written into element 1 of the array is 0. As follows: ** ** aTree[] = { X, 0 0, 6 0, 3, 5, 6 } ** ** In other words, each time we advance to the next sorter element, log2(N) ** key comparison operations are required, where N is the number of segments ** being merged (rounded up to the next power of 2). */ +struct MergeEngine { + int nTree; /* Used size of aTree/aReadr (power of 2) */ + SortSubtask *pTask; /* Used by this thread only */ + int *aTree; /* Current state of incremental merge */ + PmaReader *aReadr; /* Array of PmaReaders to merge data from */ +}; + +/* +** This object represents a single thread of control in a sort operation. +** Exactly VdbeSorter.nTask instances of this object are allocated +** as part of each VdbeSorter object. Instances are never allocated any +** other way. VdbeSorter.nTask is set to the number of worker threads allowed +** (see SQLITE_CONFIG_WORKER_THREADS) plus one (the main thread). Thus for +** single-threaded operation, there is exactly one instance of this object +** and for multi-threaded operation there are two or more instances. +** +** Essentially, this structure contains all those fields of the VdbeSorter +** structure for which each thread requires a separate instance. For example, +** each thread requries its own UnpackedRecord object to unpack records in +** as part of comparison operations. +** +** Before a background thread is launched, variable bDone is set to 0. Then, +** right before it exits, the thread itself sets bDone to 1. This is used for +** two purposes: +** +** 1. When flushing the contents of memory to a level-0 PMA on disk, to +** attempt to select a SortSubtask for which there is not already an +** active background thread (since doing so causes the main thread +** to block until it finishes). +** +** 2. If SQLITE_DEBUG_SORTER_THREADS is defined, to determine if a call +** to sqlite3ThreadJoin() is likely to block. Cases that are likely to +** block provoke debugging output. +** +** In both cases, the effects of the main thread seeing (bDone==0) even +** after the thread has finished are not dire. So we don't worry about +** memory barriers and such here. +*/ +struct SortSubtask { + SQLiteThread *pThread; /* Background thread, if any */ + int bDone; /* Set if thread is finished but not joined */ + VdbeSorter *pSorter; /* Sorter that owns this sub-task */ + UnpackedRecord *pUnpacked; /* Space to unpack a record */ + SorterList list; /* List for thread to write to a PMA */ + int nPMA; /* Number of PMAs currently in file */ + SorterFile file; /* Temp file for level-0 PMAs */ + SorterFile file2; /* Space for other PMAs */ +}; + +/* +** Main sorter structure. A single instance of this is allocated for each +** sorter cursor created by the VDBE. +** +** mxKeysize: +** As records are added to the sorter by calls to sqlite3VdbeSorterWrite(), +** this variable is updated so as to be set to the size on disk of the +** largest record in the sorter. +*/ struct VdbeSorter { - i64 iWriteOff; /* Current write offset within file pTemp1 */ - i64 iReadOff; /* Current read offset within file pTemp1 */ - int nInMemory; /* Current size of pRecord list as PMA */ - int nTree; /* Used size of aTree/aIter (power of 2) */ - int nPMA; /* Number of PMAs stored in pTemp1 */ int mnPmaSize; /* Minimum PMA size, in bytes */ int mxPmaSize; /* Maximum PMA size, in bytes. 0==no limit */ - VdbeSorterIter *aIter; /* Array of iterators to merge */ - int *aTree; /* Current state of incremental merge */ - sqlite3_file *pTemp1; /* PMA file 1 */ - SorterRecord *pRecord; /* Head of in-memory record list */ - UnpackedRecord *pUnpacked; /* Used to unpack keys */ -}; - -/* -** The following type is an iterator for a PMA. It caches the current key in -** variables nKey/aKey. If the iterator is at EOF, pFile==0. -*/ -struct VdbeSorterIter { - i64 iReadOff; /* Current read offset */ - i64 iEof; /* 1 byte past EOF for this iterator */ - int nAlloc; /* Bytes of space at aAlloc */ - int nKey; /* Number of bytes in key */ - sqlite3_file *pFile; /* File iterator is reading from */ - u8 *aAlloc; /* Allocated space */ - u8 *aKey; /* Pointer to current key */ - u8 *aBuffer; /* Current read buffer */ - int nBuffer; /* Size of read buffer in bytes */ -}; - -/* -** An instance of this structure is used to organize the stream of records -** being written to files by the merge-sort code into aligned, page-sized -** blocks. Doing all I/O in aligned page-sized blocks helps I/O to go -** faster on many operating systems. -*/ -struct FileWriter { + int mxKeysize; /* Largest serialized key seen so far */ + int pgsz; /* Main database page size */ + PmaReader *pReader; /* Readr data from here after Rewind() */ + MergeEngine *pMerger; /* Or here, if bUseThreads==0 */ + sqlite3 *db; /* Database connection */ + KeyInfo *pKeyInfo; /* How to compare records */ + UnpackedRecord *pUnpacked; /* Used by VdbeSorterCompare() */ + SorterList list; /* List of in-memory records */ + int iMemory; /* Offset of free space in list.aMemory */ + int nMemory; /* Size of list.aMemory allocation in bytes */ + u8 bUsePMA; /* True if one or more PMAs created */ + u8 bUseThreads; /* True to use background threads */ + u8 iPrev; /* Previous thread used to flush PMA */ + u8 nTask; /* Size of aTask[] array */ + SortSubtask aTask[1]; /* One or more subtasks */ +}; + +/* +** An instance of the following object is used to read records out of a +** PMA, in sorted order. The next key to be read is cached in nKey/aKey. +** aKey might point into aMap or into aBuffer. If neither of those locations +** contain a contiguous representation of the key, then aAlloc is allocated +** and the key is copied into aAlloc and aKey is made to poitn to aAlloc. +** +** pFd==0 at EOF. +*/ +struct PmaReader { + i64 iReadOff; /* Current read offset */ + i64 iEof; /* 1 byte past EOF for this PmaReader */ + int nAlloc; /* Bytes of space at aAlloc */ + int nKey; /* Number of bytes in key */ + sqlite3_file *pFd; /* File handle we are reading from */ + u8 *aAlloc; /* Space for aKey if aBuffer and pMap wont work */ + u8 *aKey; /* Pointer to current key */ + u8 *aBuffer; /* Current read buffer */ + int nBuffer; /* Size of read buffer in bytes */ + u8 *aMap; /* Pointer to mapping of entire file */ + IncrMerger *pIncr; /* Incremental merger */ +}; + +/* +** Normally, a PmaReader object iterates through an existing PMA stored +** within a temp file. However, if the PmaReader.pIncr variable points to +** an object of the following type, it may be used to iterate/merge through +** multiple PMAs simultaneously. +** +** There are two types of IncrMerger object - single (bUseThread==0) and +** multi-threaded (bUseThread==1). +** +** A multi-threaded IncrMerger object uses two temporary files - aFile[0] +** and aFile[1]. Neither file is allowed to grow to more than mxSz bytes in +** size. When the IncrMerger is initialized, it reads enough data from +** pMerger to populate aFile[0]. It then sets variables within the +** corresponding PmaReader object to read from that file and kicks off +** a background thread to populate aFile[1] with the next mxSz bytes of +** sorted record data from pMerger. +** +** When the PmaReader reaches the end of aFile[0], it blocks until the +** background thread has finished populating aFile[1]. It then exchanges +** the contents of the aFile[0] and aFile[1] variables within this structure, +** sets the PmaReader fields to read from the new aFile[0] and kicks off +** another background thread to populate the new aFile[1]. And so on, until +** the contents of pMerger are exhausted. +** +** A single-threaded IncrMerger does not open any temporary files of its +** own. Instead, it has exclusive access to mxSz bytes of space beginning +** at offset iStartOff of file pTask->file2. And instead of using a +** background thread to prepare data for the PmaReader, with a single +** threaded IncrMerger the allocate part of pTask->file2 is "refilled" with +** keys from pMerger by the calling thread whenever the PmaReader runs out +** of data. +*/ +struct IncrMerger { + SortSubtask *pTask; /* Task that owns this merger */ + MergeEngine *pMerger; /* Merge engine thread reads data from */ + i64 iStartOff; /* Offset to start writing file at */ + int mxSz; /* Maximum bytes of data to store */ + int bEof; /* Set to true when merge is finished */ + int bUseThread; /* True to use a bg thread for this object */ + SorterFile aFile[2]; /* aFile[0] for reading, [1] for writing */ +}; + +/* +** An instance of this object is used for writing a PMA. +** +** The PMA is written one record at a time. Each record is of an arbitrary +** size. But I/O is more efficient if it occurs in page-sized blocks where +** each block is aligned on a page boundary. This object caches writes to +** the PMA so that aligned, page-size blocks are written. +*/ +struct PmaWriter { int eFWErr; /* Non-zero if in an error state */ u8 *aBuffer; /* Pointer to write buffer */ int nBuffer; /* Size of write buffer in bytes */ int iBufStart; /* First byte of buffer to write */ int iBufEnd; /* Last byte of buffer to write */ i64 iWriteOff; /* Offset of start of buffer in file */ - sqlite3_file *pFile; /* File to write to */ + sqlite3_file *pFd; /* File handle to write to */ }; /* -** A structure to store a single record. All in-memory records are connected -** together into a linked list headed at VdbeSorter.pRecord using the -** SorterRecord.pNext pointer. +** This object is the header on a single record while that record is being +** held in memory and prior to being written out as part of a PMA. +** +** How the linked list is connected depends on how memory is being managed +** by this module. If using a separate allocation for each in-memory record +** (VdbeSorter.list.aMemory==0), then the list is always connected using the +** SorterRecord.u.pNext pointers. +** +** Or, if using the single large allocation method (VdbeSorter.list.aMemory!=0), +** then while records are being accumulated the list is linked using the +** SorterRecord.u.iNext offset. This is because the aMemory[] array may +** be sqlite3Realloc()ed while records are being accumulated. Once the VM +** has finished passing records to the sorter, or when the in-memory buffer +** is full, the list is sorted. As part of the sorting process, it is +** converted to use the SorterRecord.u.pNext pointers. See function +** vdbeSorterSort() for details. */ struct SorterRecord { - void *pVal; - int nVal; - SorterRecord *pNext; + int nVal; /* Size of the record in bytes */ + union { + SorterRecord *pNext; /* Pointer to next record in list */ + int iNext; /* Offset within aMemory of next record */ + } u; + /* The data for the record immediately follows this header */ }; -/* Minimum allowable value for the VdbeSorter.nWorking variable */ -#define SORTER_MIN_WORKING 10 +/* Return a pointer to the buffer containing the record data for SorterRecord +** object p. Should be used as if: +** +** void *SRVAL(SorterRecord *p) { return (void*)&p[1]; } +*/ +#define SRVAL(p) ((void*)((SorterRecord*)(p) + 1)) -/* Maximum number of segments to merge in a single pass. */ + +/* Maximum number of PMAs that a single MergeEngine can merge */ #define SORTER_MAX_MERGE_COUNT 16 +static int vdbeIncrSwap(IncrMerger*); +static void vdbeIncrFree(IncrMerger *); + /* -** Free all memory belonging to the VdbeSorterIter object passed as the second +** Free all memory belonging to the PmaReader object passed as the ** argument. All structure fields are set to zero before returning. */ -static void vdbeSorterIterZero(sqlite3 *db, VdbeSorterIter *pIter){ - sqlite3DbFree(db, pIter->aAlloc); - sqlite3DbFree(db, pIter->aBuffer); - memset(pIter, 0, sizeof(VdbeSorterIter)); +static void vdbePmaReaderClear(PmaReader *pReadr){ + sqlite3_free(pReadr->aAlloc); + sqlite3_free(pReadr->aBuffer); + if( pReadr->aMap ) sqlite3OsUnfetch(pReadr->pFd, 0, pReadr->aMap); + vdbeIncrFree(pReadr->pIncr); + memset(pReadr, 0, sizeof(PmaReader)); } /* -** Read nByte bytes of data from the stream of data iterated by object p. +** Read the next nByte bytes of data from the PMA p. ** If successful, set *ppOut to point to a buffer containing the data ** and return SQLITE_OK. Otherwise, if an error occurs, return an SQLite ** error code. ** -** The buffer indicated by *ppOut may only be considered valid until the +** The buffer returned in *ppOut is only valid until the ** next call to this function. */ -static int vdbeSorterIterRead( - sqlite3 *db, /* Database handle (for malloc) */ - VdbeSorterIter *p, /* Iterator */ +static int vdbePmaReadBlob( + PmaReader *p, /* PmaReader from which to take the blob */ int nByte, /* Bytes of data to read */ u8 **ppOut /* OUT: Pointer to buffer containing data */ ){ int iBuf; /* Offset within buffer to read from */ int nAvail; /* Bytes of data available in buffer */ + + if( p->aMap ){ + *ppOut = &p->aMap[p->iReadOff]; + p->iReadOff += nByte; + return SQLITE_OK; + } + assert( p->aBuffer ); /* If there is no more data to be read from the buffer, read the next ** p->nBuffer bytes of data from the file into it. Or, if there are less ** than p->nBuffer bytes remaining in the PMA, read all remaining data. */ @@ -73083,12 +78199,12 @@ }else{ nRead = (int)(p->iEof - p->iReadOff); } assert( nRead>0 ); - /* Read data from the file. Return early if an error occurs. */ - rc = sqlite3OsRead(p->pFile, p->aBuffer, nRead, p->iReadOff); + /* Readr data from the file. Return early if an error occurs. */ + rc = sqlite3OsRead(p->pFd, p->aBuffer, nRead, p->iReadOff); assert( rc!=SQLITE_IOERR_SHORT_READ ); if( rc!=SQLITE_OK ) return rc; } nAvail = p->nBuffer - iBuf; @@ -73104,15 +78220,17 @@ ** range into. Then return a copy of pointer p->aAlloc to the caller. */ int nRem; /* Bytes remaining to copy */ /* Extend the p->aAlloc[] allocation if required. */ if( p->nAlloc<nByte ){ - int nNew = p->nAlloc*2; + u8 *aNew; + int nNew = MAX(128, p->nAlloc*2); while( nByte>nNew ) nNew = nNew*2; - p->aAlloc = sqlite3DbReallocOrFree(db, p->aAlloc, nNew); - if( !p->aAlloc ) return SQLITE_NOMEM; + aNew = sqlite3Realloc(p->aAlloc, nNew); + if( !aNew ) return SQLITE_NOMEM; p->nAlloc = nNew; + p->aAlloc = aNew; } /* Copy as much data as is available in the buffer into the start of ** p->aAlloc[]. */ memcpy(p->aAlloc, &p->aBuffer[iBuf], nAvail); @@ -73120,17 +78238,17 @@ nRem = nByte - nAvail; /* The following loop copies up to p->nBuffer bytes per iteration into ** the p->aAlloc[] buffer. */ while( nRem>0 ){ - int rc; /* vdbeSorterIterRead() return code */ + int rc; /* vdbePmaReadBlob() return code */ int nCopy; /* Number of bytes to copy */ u8 *aNext; /* Pointer to buffer to copy data from */ nCopy = nRem; if( nRem>p->nBuffer ) nCopy = p->nBuffer; - rc = vdbeSorterIterRead(db, p, nCopy, &aNext); + rc = vdbePmaReadBlob(p, nCopy, &aNext); if( rc!=SQLITE_OK ) return rc; assert( aNext!=p->aAlloc ); memcpy(&p->aAlloc[nByte - nRem], aNext, nCopy); nRem -= nCopy; } @@ -73143,390 +78261,742 @@ /* ** Read a varint from the stream of data accessed by p. Set *pnOut to ** the value read. */ -static int vdbeSorterIterVarint(sqlite3 *db, VdbeSorterIter *p, u64 *pnOut){ - int iBuf; - - iBuf = p->iReadOff % p->nBuffer; - if( iBuf && (p->nBuffer-iBuf)>=9 ){ - p->iReadOff += sqlite3GetVarint(&p->aBuffer[iBuf], pnOut); - }else{ - u8 aVarint[16], *a; - int i = 0, rc; - do{ - rc = vdbeSorterIterRead(db, p, 1, &a); - if( rc ) return rc; - aVarint[(i++)&0xf] = a[0]; - }while( (a[0]&0x80)!=0 ); - sqlite3GetVarint(aVarint, pnOut); - } - - return SQLITE_OK; -} - - -/* -** Advance iterator pIter to the next key in its PMA. Return SQLITE_OK if -** no error occurs, or an SQLite error code if one does. -*/ -static int vdbeSorterIterNext( - sqlite3 *db, /* Database handle (for sqlite3DbMalloc() ) */ - VdbeSorterIter *pIter /* Iterator to advance */ -){ - int rc; /* Return Code */ - u64 nRec = 0; /* Size of record in bytes */ - - if( pIter->iReadOff>=pIter->iEof ){ - /* This is an EOF condition */ - vdbeSorterIterZero(db, pIter); - return SQLITE_OK; - } - - rc = vdbeSorterIterVarint(db, pIter, &nRec); - if( rc==SQLITE_OK ){ - pIter->nKey = (int)nRec; - rc = vdbeSorterIterRead(db, pIter, (int)nRec, &pIter->aKey); - } - - return rc; -} - -/* -** Initialize iterator pIter to scan through the PMA stored in file pFile -** starting at offset iStart and ending at offset iEof-1. This function -** leaves the iterator pointing to the first key in the PMA (or EOF if the -** PMA is empty). -*/ -static int vdbeSorterIterInit( - sqlite3 *db, /* Database handle */ - const VdbeSorter *pSorter, /* Sorter object */ - i64 iStart, /* Start offset in pFile */ - VdbeSorterIter *pIter, /* Iterator to populate */ - i64 *pnByte /* IN/OUT: Increment this value by PMA size */ -){ - int rc = SQLITE_OK; - int nBuf; - - nBuf = sqlite3BtreeGetPageSize(db->aDb[0].pBt); - - assert( pSorter->iWriteOff>iStart ); - assert( pIter->aAlloc==0 ); - assert( pIter->aBuffer==0 ); - pIter->pFile = pSorter->pTemp1; - pIter->iReadOff = iStart; - pIter->nAlloc = 128; - pIter->aAlloc = (u8 *)sqlite3DbMallocRaw(db, pIter->nAlloc); - pIter->nBuffer = nBuf; - pIter->aBuffer = (u8 *)sqlite3DbMallocRaw(db, nBuf); - - if( !pIter->aBuffer ){ - rc = SQLITE_NOMEM; - }else{ - int iBuf; - - iBuf = iStart % nBuf; - if( iBuf ){ - int nRead = nBuf - iBuf; - if( (iStart + nRead) > pSorter->iWriteOff ){ - nRead = (int)(pSorter->iWriteOff - iStart); +static int vdbePmaReadVarint(PmaReader *p, u64 *pnOut){ + int iBuf; + + if( p->aMap ){ + p->iReadOff += sqlite3GetVarint(&p->aMap[p->iReadOff], pnOut); + }else{ + iBuf = p->iReadOff % p->nBuffer; + if( iBuf && (p->nBuffer-iBuf)>=9 ){ + p->iReadOff += sqlite3GetVarint(&p->aBuffer[iBuf], pnOut); + }else{ + u8 aVarint[16], *a; + int i = 0, rc; + do{ + rc = vdbePmaReadBlob(p, 1, &a); + if( rc ) return rc; + aVarint[(i++)&0xf] = a[0]; + }while( (a[0]&0x80)!=0 ); + sqlite3GetVarint(aVarint, pnOut); + } + } + + return SQLITE_OK; +} + +/* +** Attempt to memory map file pFile. If successful, set *pp to point to the +** new mapping and return SQLITE_OK. If the mapping is not attempted +** (because the file is too large or the VFS layer is configured not to use +** mmap), return SQLITE_OK and set *pp to NULL. +** +** Or, if an error occurs, return an SQLite error code. The final value of +** *pp is undefined in this case. +*/ +static int vdbeSorterMapFile(SortSubtask *pTask, SorterFile *pFile, u8 **pp){ + int rc = SQLITE_OK; + if( pFile->iEof<=(i64)(pTask->pSorter->db->nMaxSorterMmap) ){ + sqlite3_file *pFd = pFile->pFd; + if( pFd->pMethods->iVersion>=3 ){ + rc = sqlite3OsFetch(pFd, 0, (int)pFile->iEof, (void**)pp); + testcase( rc!=SQLITE_OK ); + } + } + return rc; +} + +/* +** Attach PmaReader pReadr to file pFile (if it is not already attached to +** that file) and seek it to offset iOff within the file. Return SQLITE_OK +** if successful, or an SQLite error code if an error occurs. +*/ +static int vdbePmaReaderSeek( + SortSubtask *pTask, /* Task context */ + PmaReader *pReadr, /* Reader whose cursor is to be moved */ + SorterFile *pFile, /* Sorter file to read from */ + i64 iOff /* Offset in pFile */ +){ + int rc = SQLITE_OK; + + assert( pReadr->pIncr==0 || pReadr->pIncr->bEof==0 ); + + if( sqlite3FaultSim(201) ) return SQLITE_IOERR_READ; + if( pReadr->aMap ){ + sqlite3OsUnfetch(pReadr->pFd, 0, pReadr->aMap); + pReadr->aMap = 0; + } + pReadr->iReadOff = iOff; + pReadr->iEof = pFile->iEof; + pReadr->pFd = pFile->pFd; + + rc = vdbeSorterMapFile(pTask, pFile, &pReadr->aMap); + if( rc==SQLITE_OK && pReadr->aMap==0 ){ + int pgsz = pTask->pSorter->pgsz; + int iBuf = pReadr->iReadOff % pgsz; + if( pReadr->aBuffer==0 ){ + pReadr->aBuffer = (u8*)sqlite3Malloc(pgsz); + if( pReadr->aBuffer==0 ) rc = SQLITE_NOMEM; + pReadr->nBuffer = pgsz; + } + if( rc==SQLITE_OK && iBuf ){ + int nRead = pgsz - iBuf; + if( (pReadr->iReadOff + nRead) > pReadr->iEof ){ + nRead = (int)(pReadr->iEof - pReadr->iReadOff); } rc = sqlite3OsRead( - pSorter->pTemp1, &pIter->aBuffer[iBuf], nRead, iStart + pReadr->pFd, &pReadr->aBuffer[iBuf], nRead, pReadr->iReadOff ); - assert( rc!=SQLITE_IOERR_SHORT_READ ); + testcase( rc!=SQLITE_OK ); + } + } + + return rc; +} + +/* +** Advance PmaReader pReadr to the next key in its PMA. Return SQLITE_OK if +** no error occurs, or an SQLite error code if one does. +*/ +static int vdbePmaReaderNext(PmaReader *pReadr){ + int rc = SQLITE_OK; /* Return Code */ + u64 nRec = 0; /* Size of record in bytes */ + + + if( pReadr->iReadOff>=pReadr->iEof ){ + IncrMerger *pIncr = pReadr->pIncr; + int bEof = 1; + if( pIncr ){ + rc = vdbeIncrSwap(pIncr); + if( rc==SQLITE_OK && pIncr->bEof==0 ){ + rc = vdbePmaReaderSeek( + pIncr->pTask, pReadr, &pIncr->aFile[0], pIncr->iStartOff + ); + bEof = 0; + } } - if( rc==SQLITE_OK ){ - u64 nByte; /* Size of PMA in bytes */ - pIter->iEof = pSorter->iWriteOff; - rc = vdbeSorterIterVarint(db, pIter, &nByte); - pIter->iEof = pIter->iReadOff + nByte; - *pnByte += nByte; + if( bEof ){ + /* This is an EOF condition */ + vdbePmaReaderClear(pReadr); + testcase( rc!=SQLITE_OK ); + return rc; } } if( rc==SQLITE_OK ){ - rc = vdbeSorterIterNext(db, pIter); + rc = vdbePmaReadVarint(pReadr, &nRec); + } + if( rc==SQLITE_OK ){ + pReadr->nKey = (int)nRec; + rc = vdbePmaReadBlob(pReadr, (int)nRec, &pReadr->aKey); + testcase( rc!=SQLITE_OK ); + } + + return rc; +} + +/* +** Initialize PmaReader pReadr to scan through the PMA stored in file pFile +** starting at offset iStart and ending at offset iEof-1. This function +** leaves the PmaReader pointing to the first key in the PMA (or EOF if the +** PMA is empty). +** +** If the pnByte parameter is NULL, then it is assumed that the file +** contains a single PMA, and that that PMA omits the initial length varint. +*/ +static int vdbePmaReaderInit( + SortSubtask *pTask, /* Task context */ + SorterFile *pFile, /* Sorter file to read from */ + i64 iStart, /* Start offset in pFile */ + PmaReader *pReadr, /* PmaReader to populate */ + i64 *pnByte /* IN/OUT: Increment this value by PMA size */ +){ + int rc; + + assert( pFile->iEof>iStart ); + assert( pReadr->aAlloc==0 && pReadr->nAlloc==0 ); + assert( pReadr->aBuffer==0 ); + assert( pReadr->aMap==0 ); + + rc = vdbePmaReaderSeek(pTask, pReadr, pFile, iStart); + if( rc==SQLITE_OK ){ + u64 nByte; /* Size of PMA in bytes */ + rc = vdbePmaReadVarint(pReadr, &nByte); + pReadr->iEof = pReadr->iReadOff + nByte; + *pnByte += nByte; + } + + if( rc==SQLITE_OK ){ + rc = vdbePmaReaderNext(pReadr); } return rc; } /* ** Compare key1 (buffer pKey1, size nKey1 bytes) with key2 (buffer pKey2, -** size nKey2 bytes). Argument pKeyInfo supplies the collation functions -** used by the comparison. If an error occurs, return an SQLite error code. -** Otherwise, return SQLITE_OK and set *pRes to a negative, zero or positive -** value, depending on whether key1 is smaller, equal to or larger than key2. -** -** If the bOmitRowid argument is non-zero, assume both keys end in a rowid -** field. For the purposes of the comparison, ignore it. Also, if bOmitRowid -** is true and key1 contains even a single NULL value, it is considered to -** be less than key2. Even if key2 also contains NULL values. -** -** If pKey2 is passed a NULL pointer, then it is assumed that the pCsr->aSpace -** has been allocated and contains an unpacked record that is used as key2. -*/ -static void vdbeSorterCompare( - const VdbeCursor *pCsr, /* Cursor object (for pKeyInfo) */ - int bOmitRowid, /* Ignore rowid field at end of keys */ +** size nKey2 bytes). Use (pTask->pKeyInfo) for the collation sequences +** used by the comparison. Return the result of the comparison. +** +** Before returning, object (pTask->pUnpacked) is populated with the +** unpacked version of key2. Or, if pKey2 is passed a NULL pointer, then it +** is assumed that the (pTask->pUnpacked) structure already contains the +** unpacked key to use as key2. +** +** If an OOM error is encountered, (pTask->pUnpacked->error_rc) is set +** to SQLITE_NOMEM. +*/ +static int vdbeSorterCompare( + SortSubtask *pTask, /* Subtask context (for pKeyInfo) */ const void *pKey1, int nKey1, /* Left side of comparison */ - const void *pKey2, int nKey2, /* Right side of comparison */ - int *pRes /* OUT: Result of comparison */ + const void *pKey2, int nKey2 /* Right side of comparison */ ){ - KeyInfo *pKeyInfo = pCsr->pKeyInfo; - VdbeSorter *pSorter = pCsr->pSorter; - UnpackedRecord *r2 = pSorter->pUnpacked; - int i; - + UnpackedRecord *r2 = pTask->pUnpacked; if( pKey2 ){ - sqlite3VdbeRecordUnpack(pKeyInfo, nKey2, pKey2, r2); - } - - if( bOmitRowid ){ - r2->nField = pKeyInfo->nField; - assert( r2->nField>0 ); - for(i=0; i<r2->nField; i++){ - if( r2->aMem[i].flags & MEM_Null ){ - *pRes = -1; - return; - } - } - r2->flags |= UNPACKED_PREFIX_MATCH; - } - - *pRes = sqlite3VdbeRecordCompare(nKey1, pKey1, r2); -} - -/* -** This function is called to compare two iterator keys when merging -** multiple b-tree segments. Parameter iOut is the index of the aTree[] -** value to recalculate. -*/ -static int vdbeSorterDoCompare(const VdbeCursor *pCsr, int iOut){ - VdbeSorter *pSorter = pCsr->pSorter; - int i1; - int i2; - int iRes; - VdbeSorterIter *p1; - VdbeSorterIter *p2; - - assert( iOut<pSorter->nTree && iOut>0 ); - - if( iOut>=(pSorter->nTree/2) ){ - i1 = (iOut - pSorter->nTree/2) * 2; - i2 = i1 + 1; - }else{ - i1 = pSorter->aTree[iOut*2]; - i2 = pSorter->aTree[iOut*2+1]; - } - - p1 = &pSorter->aIter[i1]; - p2 = &pSorter->aIter[i2]; - - if( p1->pFile==0 ){ - iRes = i2; - }else if( p2->pFile==0 ){ - iRes = i1; - }else{ - int res; - assert( pCsr->pSorter->pUnpacked!=0 ); /* allocated in vdbeSorterMerge() */ - vdbeSorterCompare( - pCsr, 0, p1->aKey, p1->nKey, p2->aKey, p2->nKey, &res - ); - if( res<=0 ){ - iRes = i1; - }else{ - iRes = i2; - } - } - - pSorter->aTree[iOut] = iRes; - return SQLITE_OK; + sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2); + } + return sqlite3VdbeRecordCompare(nKey1, pKey1, r2); } /* ** Initialize the temporary index cursor just opened as a sorter cursor. +** +** Usually, the sorter module uses the value of (pCsr->pKeyInfo->nField) +** to determine the number of fields that should be compared from the +** records being sorted. However, if the value passed as argument nField +** is non-zero and the sorter is able to guarantee a stable sort, nField +** is used instead. This is used when sorting records for a CREATE INDEX +** statement. In this case, keys are always delivered to the sorter in +** order of the primary key, which happens to be make up the final part +** of the records being sorted. So if the sort is stable, there is never +** any reason to compare PK fields and they can be ignored for a small +** performance boost. +** +** The sorter can guarantee a stable sort when running in single-threaded +** mode, but not in multi-threaded mode. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. */ -SQLITE_PRIVATE int sqlite3VdbeSorterInit(sqlite3 *db, VdbeCursor *pCsr){ +SQLITE_PRIVATE int sqlite3VdbeSorterInit( + sqlite3 *db, /* Database connection (for malloc()) */ + int nField, /* Number of key fields in each record */ + VdbeCursor *pCsr /* Cursor that holds the new sorter */ +){ int pgsz; /* Page size of main database */ + int i; /* Used to iterate through aTask[] */ int mxCache; /* Cache size */ VdbeSorter *pSorter; /* The new sorter */ - char *d; /* Dummy */ + KeyInfo *pKeyInfo; /* Copy of pCsr->pKeyInfo with db==0 */ + int szKeyInfo; /* Size of pCsr->pKeyInfo in bytes */ + int sz; /* Size of pSorter in bytes */ + int rc = SQLITE_OK; +#if SQLITE_MAX_WORKER_THREADS==0 +# define nWorker 0 +#else + int nWorker; +#endif + + /* Initialize the upper limit on the number of worker threads */ +#if SQLITE_MAX_WORKER_THREADS>0 + if( sqlite3TempInMemory(db) || sqlite3GlobalConfig.bCoreMutex==0 ){ + nWorker = 0; + }else{ + nWorker = db->aLimit[SQLITE_LIMIT_WORKER_THREADS]; + } +#endif + + /* Do not allow the total number of threads (main thread + all workers) + ** to exceed the maximum merge count */ +#if SQLITE_MAX_WORKER_THREADS>=SORTER_MAX_MERGE_COUNT + if( nWorker>=SORTER_MAX_MERGE_COUNT ){ + nWorker = SORTER_MAX_MERGE_COUNT-1; + } +#endif assert( pCsr->pKeyInfo && pCsr->pBt==0 ); - pCsr->pSorter = pSorter = sqlite3DbMallocZero(db, sizeof(VdbeSorter)); + szKeyInfo = sizeof(KeyInfo) + (pCsr->pKeyInfo->nField-1)*sizeof(CollSeq*); + sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask); + + pSorter = (VdbeSorter*)sqlite3DbMallocZero(db, sz + szKeyInfo); + pCsr->pSorter = pSorter; if( pSorter==0 ){ - return SQLITE_NOMEM; - } - - pSorter->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pCsr->pKeyInfo, 0, 0, &d); - if( pSorter->pUnpacked==0 ) return SQLITE_NOMEM; - assert( pSorter->pUnpacked==(UnpackedRecord *)d ); - - if( !sqlite3TempInMemory(db) ){ - pgsz = sqlite3BtreeGetPageSize(db->aDb[0].pBt); - pSorter->mnPmaSize = SORTER_MIN_WORKING * pgsz; - mxCache = db->aDb[0].pSchema->cache_size; - if( mxCache<SORTER_MIN_WORKING ) mxCache = SORTER_MIN_WORKING; - pSorter->mxPmaSize = mxCache * pgsz; - } - - return SQLITE_OK; -} + rc = SQLITE_NOMEM; + }else{ + pSorter->pKeyInfo = pKeyInfo = (KeyInfo*)((u8*)pSorter + sz); + memcpy(pKeyInfo, pCsr->pKeyInfo, szKeyInfo); + pKeyInfo->db = 0; + if( nField && nWorker==0 ) pKeyInfo->nField = nField; + pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(db->aDb[0].pBt); + pSorter->nTask = nWorker + 1; + pSorter->bUseThreads = (pSorter->nTask>1); + pSorter->db = db; + for(i=0; i<pSorter->nTask; i++){ + SortSubtask *pTask = &pSorter->aTask[i]; + pTask->pSorter = pSorter; + } + + if( !sqlite3TempInMemory(db) ){ + u32 szPma = sqlite3GlobalConfig.szPma; + pSorter->mnPmaSize = szPma * pgsz; + mxCache = db->aDb[0].pSchema->cache_size; + if( mxCache<(int)szPma ) mxCache = (int)szPma; + pSorter->mxPmaSize = MIN((i64)mxCache*pgsz, SQLITE_MAX_PMASZ); + + /* EVIDENCE-OF: R-26747-61719 When the application provides any amount of + ** scratch memory using SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary + ** large heap allocations. + */ + if( sqlite3GlobalConfig.pScratch==0 ){ + assert( pSorter->iMemory==0 ); + pSorter->nMemory = pgsz; + pSorter->list.aMemory = (u8*)sqlite3Malloc(pgsz); + if( !pSorter->list.aMemory ) rc = SQLITE_NOMEM; + } + } + } + + return rc; +} +#undef nWorker /* Defined at the top of this function */ /* ** Free the list of sorted records starting at pRecord. */ static void vdbeSorterRecordFree(sqlite3 *db, SorterRecord *pRecord){ SorterRecord *p; SorterRecord *pNext; for(p=pRecord; p; p=pNext){ - pNext = p->pNext; + pNext = p->u.pNext; sqlite3DbFree(db, p); } } + +/* +** Free all resources owned by the object indicated by argument pTask. All +** fields of *pTask are zeroed before returning. +*/ +static void vdbeSortSubtaskCleanup(sqlite3 *db, SortSubtask *pTask){ + sqlite3DbFree(db, pTask->pUnpacked); + pTask->pUnpacked = 0; +#if SQLITE_MAX_WORKER_THREADS>0 + /* pTask->list.aMemory can only be non-zero if it was handed memory + ** from the main thread. That only occurs SQLITE_MAX_WORKER_THREADS>0 */ + if( pTask->list.aMemory ){ + sqlite3_free(pTask->list.aMemory); + pTask->list.aMemory = 0; + }else +#endif + { + assert( pTask->list.aMemory==0 ); + vdbeSorterRecordFree(0, pTask->list.pList); + } + pTask->list.pList = 0; + if( pTask->file.pFd ){ + sqlite3OsCloseFree(pTask->file.pFd); + pTask->file.pFd = 0; + pTask->file.iEof = 0; + } + if( pTask->file2.pFd ){ + sqlite3OsCloseFree(pTask->file2.pFd); + pTask->file2.pFd = 0; + pTask->file2.iEof = 0; + } +} + +#ifdef SQLITE_DEBUG_SORTER_THREADS +static void vdbeSorterWorkDebug(SortSubtask *pTask, const char *zEvent){ + i64 t; + int iTask = (pTask - pTask->pSorter->aTask); + sqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t); + fprintf(stderr, "%lld:%d %s\n", t, iTask, zEvent); +} +static void vdbeSorterRewindDebug(const char *zEvent){ + i64 t; + sqlite3OsCurrentTimeInt64(sqlite3_vfs_find(0), &t); + fprintf(stderr, "%lld:X %s\n", t, zEvent); +} +static void vdbeSorterPopulateDebug( + SortSubtask *pTask, + const char *zEvent +){ + i64 t; + int iTask = (pTask - pTask->pSorter->aTask); + sqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t); + fprintf(stderr, "%lld:bg%d %s\n", t, iTask, zEvent); +} +static void vdbeSorterBlockDebug( + SortSubtask *pTask, + int bBlocked, + const char *zEvent +){ + if( bBlocked ){ + i64 t; + sqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t); + fprintf(stderr, "%lld:main %s\n", t, zEvent); + } +} +#else +# define vdbeSorterWorkDebug(x,y) +# define vdbeSorterRewindDebug(y) +# define vdbeSorterPopulateDebug(x,y) +# define vdbeSorterBlockDebug(x,y,z) +#endif + +#if SQLITE_MAX_WORKER_THREADS>0 +/* +** Join thread pTask->thread. +*/ +static int vdbeSorterJoinThread(SortSubtask *pTask){ + int rc = SQLITE_OK; + if( pTask->pThread ){ +#ifdef SQLITE_DEBUG_SORTER_THREADS + int bDone = pTask->bDone; +#endif + void *pRet = SQLITE_INT_TO_PTR(SQLITE_ERROR); + vdbeSorterBlockDebug(pTask, !bDone, "enter"); + (void)sqlite3ThreadJoin(pTask->pThread, &pRet); + vdbeSorterBlockDebug(pTask, !bDone, "exit"); + rc = SQLITE_PTR_TO_INT(pRet); + assert( pTask->bDone==1 ); + pTask->bDone = 0; + pTask->pThread = 0; + } + return rc; +} + +/* +** Launch a background thread to run xTask(pIn). +*/ +static int vdbeSorterCreateThread( + SortSubtask *pTask, /* Thread will use this task object */ + void *(*xTask)(void*), /* Routine to run in a separate thread */ + void *pIn /* Argument passed into xTask() */ +){ + assert( pTask->pThread==0 && pTask->bDone==0 ); + return sqlite3ThreadCreate(&pTask->pThread, xTask, pIn); +} + +/* +** Join all outstanding threads launched by SorterWrite() to create +** level-0 PMAs. +*/ +static int vdbeSorterJoinAll(VdbeSorter *pSorter, int rcin){ + int rc = rcin; + int i; + + /* This function is always called by the main user thread. + ** + ** If this function is being called after SorterRewind() has been called, + ** it is possible that thread pSorter->aTask[pSorter->nTask-1].pThread + ** is currently attempt to join one of the other threads. To avoid a race + ** condition where this thread also attempts to join the same object, join + ** thread pSorter->aTask[pSorter->nTask-1].pThread first. */ + for(i=pSorter->nTask-1; i>=0; i--){ + SortSubtask *pTask = &pSorter->aTask[i]; + int rc2 = vdbeSorterJoinThread(pTask); + if( rc==SQLITE_OK ) rc = rc2; + } + return rc; +} +#else +# define vdbeSorterJoinAll(x,rcin) (rcin) +# define vdbeSorterJoinThread(pTask) SQLITE_OK +#endif + +/* +** Allocate a new MergeEngine object capable of handling up to +** nReader PmaReader inputs. +** +** nReader is automatically rounded up to the next power of two. +** nReader may not exceed SORTER_MAX_MERGE_COUNT even after rounding up. +*/ +static MergeEngine *vdbeMergeEngineNew(int nReader){ + int N = 2; /* Smallest power of two >= nReader */ + int nByte; /* Total bytes of space to allocate */ + MergeEngine *pNew; /* Pointer to allocated object to return */ + + assert( nReader<=SORTER_MAX_MERGE_COUNT ); + + while( N<nReader ) N += N; + nByte = sizeof(MergeEngine) + N * (sizeof(int) + sizeof(PmaReader)); + + pNew = sqlite3FaultSim(100) ? 0 : (MergeEngine*)sqlite3MallocZero(nByte); + if( pNew ){ + pNew->nTree = N; + pNew->pTask = 0; + pNew->aReadr = (PmaReader*)&pNew[1]; + pNew->aTree = (int*)&pNew->aReadr[N]; + } + return pNew; +} + +/* +** Free the MergeEngine object passed as the only argument. +*/ +static void vdbeMergeEngineFree(MergeEngine *pMerger){ + int i; + if( pMerger ){ + for(i=0; i<pMerger->nTree; i++){ + vdbePmaReaderClear(&pMerger->aReadr[i]); + } + } + sqlite3_free(pMerger); +} + +/* +** Free all resources associated with the IncrMerger object indicated by +** the first argument. +*/ +static void vdbeIncrFree(IncrMerger *pIncr){ + if( pIncr ){ +#if SQLITE_MAX_WORKER_THREADS>0 + if( pIncr->bUseThread ){ + vdbeSorterJoinThread(pIncr->pTask); + if( pIncr->aFile[0].pFd ) sqlite3OsCloseFree(pIncr->aFile[0].pFd); + if( pIncr->aFile[1].pFd ) sqlite3OsCloseFree(pIncr->aFile[1].pFd); + } +#endif + vdbeMergeEngineFree(pIncr->pMerger); + sqlite3_free(pIncr); + } +} + +/* +** Reset a sorting cursor back to its original empty state. +*/ +SQLITE_PRIVATE void sqlite3VdbeSorterReset(sqlite3 *db, VdbeSorter *pSorter){ + int i; + (void)vdbeSorterJoinAll(pSorter, SQLITE_OK); + assert( pSorter->bUseThreads || pSorter->pReader==0 ); +#if SQLITE_MAX_WORKER_THREADS>0 + if( pSorter->pReader ){ + vdbePmaReaderClear(pSorter->pReader); + sqlite3DbFree(db, pSorter->pReader); + pSorter->pReader = 0; + } +#endif + vdbeMergeEngineFree(pSorter->pMerger); + pSorter->pMerger = 0; + for(i=0; i<pSorter->nTask; i++){ + SortSubtask *pTask = &pSorter->aTask[i]; + vdbeSortSubtaskCleanup(db, pTask); + } + if( pSorter->list.aMemory==0 ){ + vdbeSorterRecordFree(0, pSorter->list.pList); + } + pSorter->list.pList = 0; + pSorter->list.szPMA = 0; + pSorter->bUsePMA = 0; + pSorter->iMemory = 0; + pSorter->mxKeysize = 0; + sqlite3DbFree(db, pSorter->pUnpacked); + pSorter->pUnpacked = 0; +} /* ** Free any cursor components allocated by sqlite3VdbeSorterXXX routines. */ SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){ VdbeSorter *pSorter = pCsr->pSorter; if( pSorter ){ - if( pSorter->aIter ){ - int i; - for(i=0; i<pSorter->nTree; i++){ - vdbeSorterIterZero(db, &pSorter->aIter[i]); - } - sqlite3DbFree(db, pSorter->aIter); - } - if( pSorter->pTemp1 ){ - sqlite3OsCloseFree(pSorter->pTemp1); - } - vdbeSorterRecordFree(db, pSorter->pRecord); - sqlite3DbFree(db, pSorter->pUnpacked); + sqlite3VdbeSorterReset(db, pSorter); + sqlite3_free(pSorter->list.aMemory); sqlite3DbFree(db, pSorter); pCsr->pSorter = 0; } } +#if SQLITE_MAX_MMAP_SIZE>0 +/* +** The first argument is a file-handle open on a temporary file. The file +** is guaranteed to be nByte bytes or smaller in size. This function +** attempts to extend the file to nByte bytes in size and to ensure that +** the VFS has memory mapped it. +** +** Whether or not the file does end up memory mapped of course depends on +** the specific VFS implementation. +*/ +static void vdbeSorterExtendFile(sqlite3 *db, sqlite3_file *pFd, i64 nByte){ + if( nByte<=(i64)(db->nMaxSorterMmap) && pFd->pMethods->iVersion>=3 ){ + void *p = 0; + int chunksize = 4*1024; + sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_CHUNK_SIZE, &chunksize); + sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_SIZE_HINT, &nByte); + sqlite3OsFetch(pFd, 0, (int)nByte, &p); + sqlite3OsUnfetch(pFd, 0, p); + } +} +#else +# define vdbeSorterExtendFile(x,y,z) +#endif + /* ** Allocate space for a file-handle and open a temporary file. If successful, -** set *ppFile to point to the malloc'd file-handle and return SQLITE_OK. -** Otherwise, set *ppFile to 0 and return an SQLite error code. +** set *ppFd to point to the malloc'd file-handle and return SQLITE_OK. +** Otherwise, set *ppFd to 0 and return an SQLite error code. */ -static int vdbeSorterOpenTempFile(sqlite3 *db, sqlite3_file **ppFile){ - int dummy; - return sqlite3OsOpenMalloc(db->pVfs, 0, ppFile, +static int vdbeSorterOpenTempFile( + sqlite3 *db, /* Database handle doing sort */ + i64 nExtend, /* Attempt to extend file to this size */ + sqlite3_file **ppFd +){ + int rc; + if( sqlite3FaultSim(202) ) return SQLITE_IOERR_ACCESS; + rc = sqlite3OsOpenMalloc(db->pVfs, 0, ppFd, SQLITE_OPEN_TEMP_JOURNAL | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | - SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE, &dummy + SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE, &rc ); + if( rc==SQLITE_OK ){ + i64 max = SQLITE_MAX_MMAP_SIZE; + sqlite3OsFileControlHint(*ppFd, SQLITE_FCNTL_MMAP_SIZE, (void*)&max); + if( nExtend>0 ){ + vdbeSorterExtendFile(db, *ppFd, nExtend); + } + } + return rc; } + +/* +** If it has not already been allocated, allocate the UnpackedRecord +** structure at pTask->pUnpacked. Return SQLITE_OK if successful (or +** if no allocation was required), or SQLITE_NOMEM otherwise. +*/ +static int vdbeSortAllocUnpacked(SortSubtask *pTask){ + if( pTask->pUnpacked==0 ){ + char *pFree; + pTask->pUnpacked = sqlite3VdbeAllocUnpackedRecord( + pTask->pSorter->pKeyInfo, 0, 0, &pFree + ); + assert( pTask->pUnpacked==(UnpackedRecord*)pFree ); + if( pFree==0 ) return SQLITE_NOMEM; + pTask->pUnpacked->nField = pTask->pSorter->pKeyInfo->nField; + pTask->pUnpacked->errCode = 0; + } + return SQLITE_OK; +} + /* ** Merge the two sorted lists p1 and p2 into a single list. ** Set *ppOut to the head of the new list. */ static void vdbeSorterMerge( - const VdbeCursor *pCsr, /* For pKeyInfo */ + SortSubtask *pTask, /* Calling thread context */ SorterRecord *p1, /* First list to merge */ SorterRecord *p2, /* Second list to merge */ SorterRecord **ppOut /* OUT: Head of merged list */ ){ SorterRecord *pFinal = 0; SorterRecord **pp = &pFinal; - void *pVal2 = p2 ? p2->pVal : 0; + void *pVal2 = p2 ? SRVAL(p2) : 0; while( p1 && p2 ){ int res; - vdbeSorterCompare(pCsr, 0, p1->pVal, p1->nVal, pVal2, p2->nVal, &res); + res = vdbeSorterCompare(pTask, SRVAL(p1), p1->nVal, pVal2, p2->nVal); if( res<=0 ){ *pp = p1; - pp = &p1->pNext; - p1 = p1->pNext; + pp = &p1->u.pNext; + p1 = p1->u.pNext; pVal2 = 0; }else{ *pp = p2; - pp = &p2->pNext; - p2 = p2->pNext; + pp = &p2->u.pNext; + p2 = p2->u.pNext; if( p2==0 ) break; - pVal2 = p2->pVal; + pVal2 = SRVAL(p2); } } *pp = p1 ? p1 : p2; *ppOut = pFinal; } /* -** Sort the linked list of records headed at pCsr->pRecord. Return SQLITE_OK -** if successful, or an SQLite error code (i.e. SQLITE_NOMEM) if an error -** occurs. +** Sort the linked list of records headed at pTask->pList. Return +** SQLITE_OK if successful, or an SQLite error code (i.e. SQLITE_NOMEM) if +** an error occurs. */ -static int vdbeSorterSort(const VdbeCursor *pCsr){ +static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){ int i; SorterRecord **aSlot; SorterRecord *p; - VdbeSorter *pSorter = pCsr->pSorter; + int rc; + + rc = vdbeSortAllocUnpacked(pTask); + if( rc!=SQLITE_OK ) return rc; aSlot = (SorterRecord **)sqlite3MallocZero(64 * sizeof(SorterRecord *)); if( !aSlot ){ return SQLITE_NOMEM; } - p = pSorter->pRecord; + p = pList->pList; while( p ){ - SorterRecord *pNext = p->pNext; - p->pNext = 0; + SorterRecord *pNext; + if( pList->aMemory ){ + if( (u8*)p==pList->aMemory ){ + pNext = 0; + }else{ + assert( p->u.iNext<sqlite3MallocSize(pList->aMemory) ); + pNext = (SorterRecord*)&pList->aMemory[p->u.iNext]; + } + }else{ + pNext = p->u.pNext; + } + + p->u.pNext = 0; for(i=0; aSlot[i]; i++){ - vdbeSorterMerge(pCsr, p, aSlot[i], &p); + vdbeSorterMerge(pTask, p, aSlot[i], &p); aSlot[i] = 0; } aSlot[i] = p; p = pNext; } p = 0; for(i=0; i<64; i++){ - vdbeSorterMerge(pCsr, p, aSlot[i], &p); + vdbeSorterMerge(pTask, p, aSlot[i], &p); } - pSorter->pRecord = p; + pList->pList = p; sqlite3_free(aSlot); - return SQLITE_OK; + assert( pTask->pUnpacked->errCode==SQLITE_OK + || pTask->pUnpacked->errCode==SQLITE_NOMEM + ); + return pTask->pUnpacked->errCode; } /* -** Initialize a file-writer object. +** Initialize a PMA-writer object. */ -static void fileWriterInit( - sqlite3 *db, /* Database (for malloc) */ - sqlite3_file *pFile, /* File to write to */ - FileWriter *p, /* Object to populate */ - i64 iStart /* Offset of pFile to begin writing at */ +static void vdbePmaWriterInit( + sqlite3_file *pFd, /* File handle to write to */ + PmaWriter *p, /* Object to populate */ + int nBuf, /* Buffer size */ + i64 iStart /* Offset of pFd to begin writing at */ ){ - int nBuf = sqlite3BtreeGetPageSize(db->aDb[0].pBt); - - memset(p, 0, sizeof(FileWriter)); - p->aBuffer = (u8 *)sqlite3DbMallocRaw(db, nBuf); + memset(p, 0, sizeof(PmaWriter)); + p->aBuffer = (u8*)sqlite3Malloc(nBuf); if( !p->aBuffer ){ p->eFWErr = SQLITE_NOMEM; }else{ p->iBufEnd = p->iBufStart = (iStart % nBuf); p->iWriteOff = iStart - p->iBufStart; p->nBuffer = nBuf; - p->pFile = pFile; + p->pFd = pFd; } } /* -** Write nData bytes of data to the file-write object. Return SQLITE_OK +** Write nData bytes of data to the PMA. Return SQLITE_OK ** if successful, or an SQLite error code if an error occurs. */ -static void fileWriterWrite(FileWriter *p, u8 *pData, int nData){ +static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){ int nRem = nData; while( nRem>0 && p->eFWErr==0 ){ int nCopy = nRem; if( nCopy>(p->nBuffer - p->iBufEnd) ){ nCopy = p->nBuffer - p->iBufEnd; @@ -73533,11 +79003,11 @@ } memcpy(&p->aBuffer[p->iBufEnd], &pData[nData-nRem], nCopy); p->iBufEnd += nCopy; if( p->iBufEnd==p->nBuffer ){ - p->eFWErr = sqlite3OsWrite(p->pFile, + p->eFWErr = sqlite3OsWrite(p->pFd, &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, p->iWriteOff + p->iBufStart ); p->iBufStart = p->iBufEnd = 0; p->iWriteOff += p->nBuffer; @@ -73547,47 +79017,48 @@ nRem -= nCopy; } } /* -** Flush any buffered data to disk and clean up the file-writer object. -** The results of using the file-writer after this call are undefined. +** Flush any buffered data to disk and clean up the PMA-writer object. +** The results of using the PMA-writer after this call are undefined. ** Return SQLITE_OK if flushing the buffered data succeeds or is not ** required. Otherwise, return an SQLite error code. ** ** Before returning, set *piEof to the offset immediately following the ** last byte written to the file. */ -static int fileWriterFinish(sqlite3 *db, FileWriter *p, i64 *piEof){ +static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){ int rc; if( p->eFWErr==0 && ALWAYS(p->aBuffer) && p->iBufEnd>p->iBufStart ){ - p->eFWErr = sqlite3OsWrite(p->pFile, + p->eFWErr = sqlite3OsWrite(p->pFd, &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, p->iWriteOff + p->iBufStart ); } *piEof = (p->iWriteOff + p->iBufEnd); - sqlite3DbFree(db, p->aBuffer); + sqlite3_free(p->aBuffer); rc = p->eFWErr; - memset(p, 0, sizeof(FileWriter)); + memset(p, 0, sizeof(PmaWriter)); return rc; } /* -** Write value iVal encoded as a varint to the file-write object. Return +** Write value iVal encoded as a varint to the PMA. Return ** SQLITE_OK if successful, or an SQLite error code if an error occurs. */ -static void fileWriterWriteVarint(FileWriter *p, u64 iVal){ +static void vdbePmaWriteVarint(PmaWriter *p, u64 iVal){ int nByte; u8 aByte[10]; nByte = sqlite3PutVarint(aByte, iVal); - fileWriterWrite(p, aByte, nByte); + vdbePmaWriteBlob(p, aByte, nByte); } /* -** Write the current contents of the in-memory linked-list to a PMA. Return -** SQLITE_OK if successful, or an SQLite error code otherwise. +** Write the current contents of in-memory linked-list pList to a level-0 +** PMA in the temp file belonging to sub-task pTask. Return SQLITE_OK if +** successful, or an SQLite error code otherwise. ** ** The format of a PMA is: ** ** * A varint. This varint contains the total number of bytes of content ** in the PMA (not including the varint itself). @@ -73594,242 +79065,1029 @@ ** ** * One or more records packed end-to-end in order of ascending keys. ** Each record consists of a varint followed by a blob of data (the ** key). The varint is the number of bytes in the blob of data. */ -static int vdbeSorterListToPMA(sqlite3 *db, const VdbeCursor *pCsr){ +static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){ + sqlite3 *db = pTask->pSorter->db; int rc = SQLITE_OK; /* Return code */ - VdbeSorter *pSorter = pCsr->pSorter; - FileWriter writer; - - memset(&writer, 0, sizeof(FileWriter)); - - if( pSorter->nInMemory==0 ){ - assert( pSorter->pRecord==0 ); - return rc; - } - - rc = vdbeSorterSort(pCsr); + PmaWriter writer; /* Object used to write to the file */ + +#ifdef SQLITE_DEBUG + /* Set iSz to the expected size of file pTask->file after writing the PMA. + ** This is used by an assert() statement at the end of this function. */ + i64 iSz = pList->szPMA + sqlite3VarintLen(pList->szPMA) + pTask->file.iEof; +#endif + + vdbeSorterWorkDebug(pTask, "enter"); + memset(&writer, 0, sizeof(PmaWriter)); + assert( pList->szPMA>0 ); /* If the first temporary PMA file has not been opened, open it now. */ - if( rc==SQLITE_OK && pSorter->pTemp1==0 ){ - rc = vdbeSorterOpenTempFile(db, &pSorter->pTemp1); - assert( rc!=SQLITE_OK || pSorter->pTemp1 ); - assert( pSorter->iWriteOff==0 ); - assert( pSorter->nPMA==0 ); + if( pTask->file.pFd==0 ){ + rc = vdbeSorterOpenTempFile(db, 0, &pTask->file.pFd); + assert( rc!=SQLITE_OK || pTask->file.pFd ); + assert( pTask->file.iEof==0 ); + assert( pTask->nPMA==0 ); + } + + /* Try to get the file to memory map */ + if( rc==SQLITE_OK ){ + vdbeSorterExtendFile(db, pTask->file.pFd, pTask->file.iEof+pList->szPMA+9); + } + + /* Sort the list */ + if( rc==SQLITE_OK ){ + rc = vdbeSorterSort(pTask, pList); } if( rc==SQLITE_OK ){ SorterRecord *p; SorterRecord *pNext = 0; - fileWriterInit(db, pSorter->pTemp1, &writer, pSorter->iWriteOff); - pSorter->nPMA++; - fileWriterWriteVarint(&writer, pSorter->nInMemory); - for(p=pSorter->pRecord; p; p=pNext){ - pNext = p->pNext; - fileWriterWriteVarint(&writer, p->nVal); - fileWriterWrite(&writer, p->pVal, p->nVal); - sqlite3DbFree(db, p); - } - pSorter->pRecord = p; - rc = fileWriterFinish(db, &writer, &pSorter->iWriteOff); + vdbePmaWriterInit(pTask->file.pFd, &writer, pTask->pSorter->pgsz, + pTask->file.iEof); + pTask->nPMA++; + vdbePmaWriteVarint(&writer, pList->szPMA); + for(p=pList->pList; p; p=pNext){ + pNext = p->u.pNext; + vdbePmaWriteVarint(&writer, p->nVal); + vdbePmaWriteBlob(&writer, SRVAL(p), p->nVal); + if( pList->aMemory==0 ) sqlite3_free(p); + } + pList->pList = p; + rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof); + } + + vdbeSorterWorkDebug(pTask, "exit"); + assert( rc!=SQLITE_OK || pList->pList==0 ); + assert( rc!=SQLITE_OK || pTask->file.iEof==iSz ); + return rc; +} + +/* +** Advance the MergeEngine to its next entry. +** Set *pbEof to true there is no next entry because +** the MergeEngine has reached the end of all its inputs. +** +** Return SQLITE_OK if successful or an error code if an error occurs. +*/ +static int vdbeMergeEngineStep( + MergeEngine *pMerger, /* The merge engine to advance to the next row */ + int *pbEof /* Set TRUE at EOF. Set false for more content */ +){ + int rc; + int iPrev = pMerger->aTree[1];/* Index of PmaReader to advance */ + SortSubtask *pTask = pMerger->pTask; + + /* Advance the current PmaReader */ + rc = vdbePmaReaderNext(&pMerger->aReadr[iPrev]); + + /* Update contents of aTree[] */ + if( rc==SQLITE_OK ){ + int i; /* Index of aTree[] to recalculate */ + PmaReader *pReadr1; /* First PmaReader to compare */ + PmaReader *pReadr2; /* Second PmaReader to compare */ + u8 *pKey2; /* To pReadr2->aKey, or 0 if record cached */ + + /* Find the first two PmaReaders to compare. The one that was just + ** advanced (iPrev) and the one next to it in the array. */ + pReadr1 = &pMerger->aReadr[(iPrev & 0xFFFE)]; + pReadr2 = &pMerger->aReadr[(iPrev | 0x0001)]; + pKey2 = pReadr2->aKey; + + for(i=(pMerger->nTree+iPrev)/2; i>0; i=i/2){ + /* Compare pReadr1 and pReadr2. Store the result in variable iRes. */ + int iRes; + if( pReadr1->pFd==0 ){ + iRes = +1; + }else if( pReadr2->pFd==0 ){ + iRes = -1; + }else{ + iRes = vdbeSorterCompare(pTask, + pReadr1->aKey, pReadr1->nKey, pKey2, pReadr2->nKey + ); + } + + /* If pReadr1 contained the smaller value, set aTree[i] to its index. + ** Then set pReadr2 to the next PmaReader to compare to pReadr1. In this + ** case there is no cache of pReadr2 in pTask->pUnpacked, so set + ** pKey2 to point to the record belonging to pReadr2. + ** + ** Alternatively, if pReadr2 contains the smaller of the two values, + ** set aTree[i] to its index and update pReadr1. If vdbeSorterCompare() + ** was actually called above, then pTask->pUnpacked now contains + ** a value equivalent to pReadr2. So set pKey2 to NULL to prevent + ** vdbeSorterCompare() from decoding pReadr2 again. + ** + ** If the two values were equal, then the value from the oldest + ** PMA should be considered smaller. The VdbeSorter.aReadr[] array + ** is sorted from oldest to newest, so pReadr1 contains older values + ** than pReadr2 iff (pReadr1<pReadr2). */ + if( iRes<0 || (iRes==0 && pReadr1<pReadr2) ){ + pMerger->aTree[i] = (int)(pReadr1 - pMerger->aReadr); + pReadr2 = &pMerger->aReadr[ pMerger->aTree[i ^ 0x0001] ]; + pKey2 = pReadr2->aKey; + }else{ + if( pReadr1->pFd ) pKey2 = 0; + pMerger->aTree[i] = (int)(pReadr2 - pMerger->aReadr); + pReadr1 = &pMerger->aReadr[ pMerger->aTree[i ^ 0x0001] ]; + } + } + *pbEof = (pMerger->aReadr[pMerger->aTree[1]].pFd==0); + } + + return (rc==SQLITE_OK ? pTask->pUnpacked->errCode : rc); +} + +#if SQLITE_MAX_WORKER_THREADS>0 +/* +** The main routine for background threads that write level-0 PMAs. +*/ +static void *vdbeSorterFlushThread(void *pCtx){ + SortSubtask *pTask = (SortSubtask*)pCtx; + int rc; /* Return code */ + assert( pTask->bDone==0 ); + rc = vdbeSorterListToPMA(pTask, &pTask->list); + pTask->bDone = 1; + return SQLITE_INT_TO_PTR(rc); +} +#endif /* SQLITE_MAX_WORKER_THREADS>0 */ + +/* +** Flush the current contents of VdbeSorter.list to a new PMA, possibly +** using a background thread. +*/ +static int vdbeSorterFlushPMA(VdbeSorter *pSorter){ +#if SQLITE_MAX_WORKER_THREADS==0 + pSorter->bUsePMA = 1; + return vdbeSorterListToPMA(&pSorter->aTask[0], &pSorter->list); +#else + int rc = SQLITE_OK; + int i; + SortSubtask *pTask = 0; /* Thread context used to create new PMA */ + int nWorker = (pSorter->nTask-1); + + /* Set the flag to indicate that at least one PMA has been written. + ** Or will be, anyhow. */ + pSorter->bUsePMA = 1; + + /* Select a sub-task to sort and flush the current list of in-memory + ** records to disk. If the sorter is running in multi-threaded mode, + ** round-robin between the first (pSorter->nTask-1) tasks. Except, if + ** the background thread from a sub-tasks previous turn is still running, + ** skip it. If the first (pSorter->nTask-1) sub-tasks are all still busy, + ** fall back to using the final sub-task. The first (pSorter->nTask-1) + ** sub-tasks are prefered as they use background threads - the final + ** sub-task uses the main thread. */ + for(i=0; i<nWorker; i++){ + int iTest = (pSorter->iPrev + i + 1) % nWorker; + pTask = &pSorter->aTask[iTest]; + if( pTask->bDone ){ + rc = vdbeSorterJoinThread(pTask); + } + if( rc!=SQLITE_OK || pTask->pThread==0 ) break; + } + + if( rc==SQLITE_OK ){ + if( i==nWorker ){ + /* Use the foreground thread for this operation */ + rc = vdbeSorterListToPMA(&pSorter->aTask[nWorker], &pSorter->list); + }else{ + /* Launch a background thread for this operation */ + u8 *aMem = pTask->list.aMemory; + void *pCtx = (void*)pTask; + + assert( pTask->pThread==0 && pTask->bDone==0 ); + assert( pTask->list.pList==0 ); + assert( pTask->list.aMemory==0 || pSorter->list.aMemory!=0 ); + + pSorter->iPrev = (u8)(pTask - pSorter->aTask); + pTask->list = pSorter->list; + pSorter->list.pList = 0; + pSorter->list.szPMA = 0; + if( aMem ){ + pSorter->list.aMemory = aMem; + pSorter->nMemory = sqlite3MallocSize(aMem); + }else if( pSorter->list.aMemory ){ + pSorter->list.aMemory = sqlite3Malloc(pSorter->nMemory); + if( !pSorter->list.aMemory ) return SQLITE_NOMEM; + } + + rc = vdbeSorterCreateThread(pTask, vdbeSorterFlushThread, pCtx); + } } return rc; +#endif /* SQLITE_MAX_WORKER_THREADS!=0 */ } /* ** Add a record to the sorter. */ SQLITE_PRIVATE int sqlite3VdbeSorterWrite( - sqlite3 *db, /* Database handle */ - const VdbeCursor *pCsr, /* Sorter cursor */ + const VdbeCursor *pCsr, /* Sorter cursor */ Mem *pVal /* Memory cell containing record */ ){ VdbeSorter *pSorter = pCsr->pSorter; int rc = SQLITE_OK; /* Return Code */ SorterRecord *pNew; /* New list element */ + int bFlush; /* True to flush contents of memory to PMA */ + int nReq; /* Bytes of memory required */ + int nPMA; /* Bytes of PMA space required */ + assert( pSorter ); - pSorter->nInMemory += sqlite3VarintLen(pVal->n) + pVal->n; - - pNew = (SorterRecord *)sqlite3DbMallocRaw(db, pVal->n + sizeof(SorterRecord)); - if( pNew==0 ){ - rc = SQLITE_NOMEM; - }else{ - pNew->pVal = (void *)&pNew[1]; - memcpy(pNew->pVal, pVal->z, pVal->n); - pNew->nVal = pVal->n; - pNew->pNext = pSorter->pRecord; - pSorter->pRecord = pNew; - } - - /* See if the contents of the sorter should now be written out. They - ** are written out when either of the following are true: + + /* Figure out whether or not the current contents of memory should be + ** flushed to a PMA before continuing. If so, do so. + ** + ** If using the single large allocation mode (pSorter->aMemory!=0), then + ** flush the contents of memory to a new PMA if (a) at least one value is + ** already in memory and (b) the new value will not fit in memory. + ** + ** Or, if using separate allocations for each record, flush the contents + ** of memory to a PMA if either of the following are true: ** ** * The total memory allocated for the in-memory list is greater ** than (page-size * cache-size), or ** ** * The total memory allocated for the in-memory list is greater ** than (page-size * 10) and sqlite3HeapNearlyFull() returns true. */ - if( rc==SQLITE_OK && pSorter->mxPmaSize>0 && ( - (pSorter->nInMemory>pSorter->mxPmaSize) - || (pSorter->nInMemory>pSorter->mnPmaSize && sqlite3HeapNearlyFull()) - )){ -#ifdef SQLITE_DEBUG - i64 nExpect = pSorter->iWriteOff - + sqlite3VarintLen(pSorter->nInMemory) - + pSorter->nInMemory; -#endif - rc = vdbeSorterListToPMA(db, pCsr); - pSorter->nInMemory = 0; - assert( rc!=SQLITE_OK || (nExpect==pSorter->iWriteOff) ); - } - - return rc; -} - -/* -** Helper function for sqlite3VdbeSorterRewind(). -*/ -static int vdbeSorterInitMerge( - sqlite3 *db, /* Database handle */ - const VdbeCursor *pCsr, /* Cursor handle for this sorter */ - i64 *pnByte /* Sum of bytes in all opened PMAs */ -){ + nReq = pVal->n + sizeof(SorterRecord); + nPMA = pVal->n + sqlite3VarintLen(pVal->n); + if( pSorter->mxPmaSize ){ + if( pSorter->list.aMemory ){ + bFlush = pSorter->iMemory && (pSorter->iMemory+nReq) > pSorter->mxPmaSize; + }else{ + bFlush = ( + (pSorter->list.szPMA > pSorter->mxPmaSize) + || (pSorter->list.szPMA > pSorter->mnPmaSize && sqlite3HeapNearlyFull()) + ); + } + if( bFlush ){ + rc = vdbeSorterFlushPMA(pSorter); + pSorter->list.szPMA = 0; + pSorter->iMemory = 0; + assert( rc!=SQLITE_OK || pSorter->list.pList==0 ); + } + } + + pSorter->list.szPMA += nPMA; + if( nPMA>pSorter->mxKeysize ){ + pSorter->mxKeysize = nPMA; + } + + if( pSorter->list.aMemory ){ + int nMin = pSorter->iMemory + nReq; + + if( nMin>pSorter->nMemory ){ + u8 *aNew; + int nNew = pSorter->nMemory * 2; + while( nNew < nMin ) nNew = nNew*2; + if( nNew > pSorter->mxPmaSize ) nNew = pSorter->mxPmaSize; + if( nNew < nMin ) nNew = nMin; + + aNew = sqlite3Realloc(pSorter->list.aMemory, nNew); + if( !aNew ) return SQLITE_NOMEM; + pSorter->list.pList = (SorterRecord*)( + aNew + ((u8*)pSorter->list.pList - pSorter->list.aMemory) + ); + pSorter->list.aMemory = aNew; + pSorter->nMemory = nNew; + } + + pNew = (SorterRecord*)&pSorter->list.aMemory[pSorter->iMemory]; + pSorter->iMemory += ROUND8(nReq); + pNew->u.iNext = (int)((u8*)(pSorter->list.pList) - pSorter->list.aMemory); + }else{ + pNew = (SorterRecord *)sqlite3Malloc(nReq); + if( pNew==0 ){ + return SQLITE_NOMEM; + } + pNew->u.pNext = pSorter->list.pList; + } + + memcpy(SRVAL(pNew), pVal->z, pVal->n); + pNew->nVal = pVal->n; + pSorter->list.pList = pNew; + + return rc; +} + +/* +** Read keys from pIncr->pMerger and populate pIncr->aFile[1]. The format +** of the data stored in aFile[1] is the same as that used by regular PMAs, +** except that the number-of-bytes varint is omitted from the start. +*/ +static int vdbeIncrPopulate(IncrMerger *pIncr){ + int rc = SQLITE_OK; + int rc2; + i64 iStart = pIncr->iStartOff; + SorterFile *pOut = &pIncr->aFile[1]; + SortSubtask *pTask = pIncr->pTask; + MergeEngine *pMerger = pIncr->pMerger; + PmaWriter writer; + assert( pIncr->bEof==0 ); + + vdbeSorterPopulateDebug(pTask, "enter"); + + vdbePmaWriterInit(pOut->pFd, &writer, pTask->pSorter->pgsz, iStart); + while( rc==SQLITE_OK ){ + int dummy; + PmaReader *pReader = &pMerger->aReadr[ pMerger->aTree[1] ]; + int nKey = pReader->nKey; + i64 iEof = writer.iWriteOff + writer.iBufEnd; + + /* Check if the output file is full or if the input has been exhausted. + ** In either case exit the loop. */ + if( pReader->pFd==0 ) break; + if( (iEof + nKey + sqlite3VarintLen(nKey))>(iStart + pIncr->mxSz) ) break; + + /* Write the next key to the output. */ + vdbePmaWriteVarint(&writer, nKey); + vdbePmaWriteBlob(&writer, pReader->aKey, nKey); + assert( pIncr->pMerger->pTask==pTask ); + rc = vdbeMergeEngineStep(pIncr->pMerger, &dummy); + } + + rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof); + if( rc==SQLITE_OK ) rc = rc2; + vdbeSorterPopulateDebug(pTask, "exit"); + return rc; +} + +#if SQLITE_MAX_WORKER_THREADS>0 +/* +** The main routine for background threads that populate aFile[1] of +** multi-threaded IncrMerger objects. +*/ +static void *vdbeIncrPopulateThread(void *pCtx){ + IncrMerger *pIncr = (IncrMerger*)pCtx; + void *pRet = SQLITE_INT_TO_PTR( vdbeIncrPopulate(pIncr) ); + pIncr->pTask->bDone = 1; + return pRet; +} + +/* +** Launch a background thread to populate aFile[1] of pIncr. +*/ +static int vdbeIncrBgPopulate(IncrMerger *pIncr){ + void *p = (void*)pIncr; + assert( pIncr->bUseThread ); + return vdbeSorterCreateThread(pIncr->pTask, vdbeIncrPopulateThread, p); +} +#endif + +/* +** This function is called when the PmaReader corresponding to pIncr has +** finished reading the contents of aFile[0]. Its purpose is to "refill" +** aFile[0] such that the PmaReader should start rereading it from the +** beginning. +** +** For single-threaded objects, this is accomplished by literally reading +** keys from pIncr->pMerger and repopulating aFile[0]. +** +** For multi-threaded objects, all that is required is to wait until the +** background thread is finished (if it is not already) and then swap +** aFile[0] and aFile[1] in place. If the contents of pMerger have not +** been exhausted, this function also launches a new background thread +** to populate the new aFile[1]. +** +** SQLITE_OK is returned on success, or an SQLite error code otherwise. +*/ +static int vdbeIncrSwap(IncrMerger *pIncr){ + int rc = SQLITE_OK; + +#if SQLITE_MAX_WORKER_THREADS>0 + if( pIncr->bUseThread ){ + rc = vdbeSorterJoinThread(pIncr->pTask); + + if( rc==SQLITE_OK ){ + SorterFile f0 = pIncr->aFile[0]; + pIncr->aFile[0] = pIncr->aFile[1]; + pIncr->aFile[1] = f0; + } + + if( rc==SQLITE_OK ){ + if( pIncr->aFile[0].iEof==pIncr->iStartOff ){ + pIncr->bEof = 1; + }else{ + rc = vdbeIncrBgPopulate(pIncr); + } + } + }else +#endif + { + rc = vdbeIncrPopulate(pIncr); + pIncr->aFile[0] = pIncr->aFile[1]; + if( pIncr->aFile[0].iEof==pIncr->iStartOff ){ + pIncr->bEof = 1; + } + } + + return rc; +} + +/* +** Allocate and return a new IncrMerger object to read data from pMerger. +** +** If an OOM condition is encountered, return NULL. In this case free the +** pMerger argument before returning. +*/ +static int vdbeIncrMergerNew( + SortSubtask *pTask, /* The thread that will be using the new IncrMerger */ + MergeEngine *pMerger, /* The MergeEngine that the IncrMerger will control */ + IncrMerger **ppOut /* Write the new IncrMerger here */ +){ + int rc = SQLITE_OK; + IncrMerger *pIncr = *ppOut = (IncrMerger*) + (sqlite3FaultSim(100) ? 0 : sqlite3MallocZero(sizeof(*pIncr))); + if( pIncr ){ + pIncr->pMerger = pMerger; + pIncr->pTask = pTask; + pIncr->mxSz = MAX(pTask->pSorter->mxKeysize+9,pTask->pSorter->mxPmaSize/2); + pTask->file2.iEof += pIncr->mxSz; + }else{ + vdbeMergeEngineFree(pMerger); + rc = SQLITE_NOMEM; + } + return rc; +} + +#if SQLITE_MAX_WORKER_THREADS>0 +/* +** Set the "use-threads" flag on object pIncr. +*/ +static void vdbeIncrMergerSetThreads(IncrMerger *pIncr){ + pIncr->bUseThread = 1; + pIncr->pTask->file2.iEof -= pIncr->mxSz; +} +#endif /* SQLITE_MAX_WORKER_THREADS>0 */ + + + +/* +** Recompute pMerger->aTree[iOut] by comparing the next keys on the +** two PmaReaders that feed that entry. Neither of the PmaReaders +** are advanced. This routine merely does the comparison. +*/ +static void vdbeMergeEngineCompare( + MergeEngine *pMerger, /* Merge engine containing PmaReaders to compare */ + int iOut /* Store the result in pMerger->aTree[iOut] */ +){ + int i1; + int i2; + int iRes; + PmaReader *p1; + PmaReader *p2; + + assert( iOut<pMerger->nTree && iOut>0 ); + + if( iOut>=(pMerger->nTree/2) ){ + i1 = (iOut - pMerger->nTree/2) * 2; + i2 = i1 + 1; + }else{ + i1 = pMerger->aTree[iOut*2]; + i2 = pMerger->aTree[iOut*2+1]; + } + + p1 = &pMerger->aReadr[i1]; + p2 = &pMerger->aReadr[i2]; + + if( p1->pFd==0 ){ + iRes = i2; + }else if( p2->pFd==0 ){ + iRes = i1; + }else{ + int res; + assert( pMerger->pTask->pUnpacked!=0 ); /* from vdbeSortSubtaskMain() */ + res = vdbeSorterCompare( + pMerger->pTask, p1->aKey, p1->nKey, p2->aKey, p2->nKey + ); + if( res<=0 ){ + iRes = i1; + }else{ + iRes = i2; + } + } + + pMerger->aTree[iOut] = iRes; +} + +/* +** Allowed values for the eMode parameter to vdbeMergeEngineInit() +** and vdbePmaReaderIncrMergeInit(). +** +** Only INCRINIT_NORMAL is valid in single-threaded builds (when +** SQLITE_MAX_WORKER_THREADS==0). The other values are only used +** when there exists one or more separate worker threads. +*/ +#define INCRINIT_NORMAL 0 +#define INCRINIT_TASK 1 +#define INCRINIT_ROOT 2 + +/* Forward reference. +** The vdbeIncrMergeInit() and vdbePmaReaderIncrMergeInit() routines call each +** other (when building a merge tree). +*/ +static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode); + +/* +** Initialize the MergeEngine object passed as the second argument. Once this +** function returns, the first key of merged data may be read from the +** MergeEngine object in the usual fashion. +** +** If argument eMode is INCRINIT_ROOT, then it is assumed that any IncrMerge +** objects attached to the PmaReader objects that the merger reads from have +** already been populated, but that they have not yet populated aFile[0] and +** set the PmaReader objects up to read from it. In this case all that is +** required is to call vdbePmaReaderNext() on each PmaReader to point it at +** its first key. +** +** Otherwise, if eMode is any value other than INCRINIT_ROOT, then use +** vdbePmaReaderIncrMergeInit() to initialize each PmaReader that feeds data +** to pMerger. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +static int vdbeMergeEngineInit( + SortSubtask *pTask, /* Thread that will run pMerger */ + MergeEngine *pMerger, /* MergeEngine to initialize */ + int eMode /* One of the INCRINIT_XXX constants */ +){ + int rc = SQLITE_OK; /* Return code */ + int i; /* For looping over PmaReader objects */ + int nTree = pMerger->nTree; + + /* eMode is always INCRINIT_NORMAL in single-threaded mode */ + assert( SQLITE_MAX_WORKER_THREADS>0 || eMode==INCRINIT_NORMAL ); + + /* Verify that the MergeEngine is assigned to a single thread */ + assert( pMerger->pTask==0 ); + pMerger->pTask = pTask; + + for(i=0; i<nTree; i++){ + if( SQLITE_MAX_WORKER_THREADS>0 && eMode==INCRINIT_ROOT ){ + /* PmaReaders should be normally initialized in order, as if they are + ** reading from the same temp file this makes for more linear file IO. + ** However, in the INCRINIT_ROOT case, if PmaReader aReadr[nTask-1] is + ** in use it will block the vdbePmaReaderNext() call while it uses + ** the main thread to fill its buffer. So calling PmaReaderNext() + ** on this PmaReader before any of the multi-threaded PmaReaders takes + ** better advantage of multi-processor hardware. */ + rc = vdbePmaReaderNext(&pMerger->aReadr[nTree-i-1]); + }else{ + rc = vdbePmaReaderIncrMergeInit(&pMerger->aReadr[i], INCRINIT_NORMAL); + } + if( rc!=SQLITE_OK ) return rc; + } + + for(i=pMerger->nTree-1; i>0; i--){ + vdbeMergeEngineCompare(pMerger, i); + } + return pTask->pUnpacked->errCode; +} + +/* +** Initialize the IncrMerge field of a PmaReader. +** +** If the PmaReader passed as the first argument is not an incremental-reader +** (if pReadr->pIncr==0), then this function is a no-op. Otherwise, it serves +** to open and/or initialize the temp file related fields of the IncrMerge +** object at (pReadr->pIncr). +** +** If argument eMode is set to INCRINIT_NORMAL, then all PmaReaders +** in the sub-tree headed by pReadr are also initialized. Data is then loaded +** into the buffers belonging to pReadr and it is set to +** point to the first key in its range. +** +** If argument eMode is set to INCRINIT_TASK, then pReadr is guaranteed +** to be a multi-threaded PmaReader and this function is being called in a +** background thread. In this case all PmaReaders in the sub-tree are +** initialized as for INCRINIT_NORMAL and the aFile[1] buffer belonging to +** pReadr is populated. However, pReadr itself is not set up to point +** to its first key. A call to vdbePmaReaderNext() is still required to do +** that. +** +** The reason this function does not call vdbePmaReaderNext() immediately +** in the INCRINIT_TASK case is that vdbePmaReaderNext() assumes that it has +** to block on thread (pTask->thread) before accessing aFile[1]. But, since +** this entire function is being run by thread (pTask->thread), that will +** lead to the current background thread attempting to join itself. +** +** Finally, if argument eMode is set to INCRINIT_ROOT, it may be assumed +** that pReadr->pIncr is a multi-threaded IncrMerge objects, and that all +** child-trees have already been initialized using IncrInit(INCRINIT_TASK). +** In this case vdbePmaReaderNext() is called on all child PmaReaders and +** the current PmaReader set to point to the first key in its range. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){ + int rc = SQLITE_OK; + IncrMerger *pIncr = pReadr->pIncr; + + /* eMode is always INCRINIT_NORMAL in single-threaded mode */ + assert( SQLITE_MAX_WORKER_THREADS>0 || eMode==INCRINIT_NORMAL ); + + if( pIncr ){ + SortSubtask *pTask = pIncr->pTask; + sqlite3 *db = pTask->pSorter->db; + + rc = vdbeMergeEngineInit(pTask, pIncr->pMerger, eMode); + + /* Set up the required files for pIncr. A multi-theaded IncrMerge object + ** requires two temp files to itself, whereas a single-threaded object + ** only requires a region of pTask->file2. */ + if( rc==SQLITE_OK ){ + int mxSz = pIncr->mxSz; +#if SQLITE_MAX_WORKER_THREADS>0 + if( pIncr->bUseThread ){ + rc = vdbeSorterOpenTempFile(db, mxSz, &pIncr->aFile[0].pFd); + if( rc==SQLITE_OK ){ + rc = vdbeSorterOpenTempFile(db, mxSz, &pIncr->aFile[1].pFd); + } + }else +#endif + /*if( !pIncr->bUseThread )*/{ + if( pTask->file2.pFd==0 ){ + assert( pTask->file2.iEof>0 ); + rc = vdbeSorterOpenTempFile(db, pTask->file2.iEof, &pTask->file2.pFd); + pTask->file2.iEof = 0; + } + if( rc==SQLITE_OK ){ + pIncr->aFile[1].pFd = pTask->file2.pFd; + pIncr->iStartOff = pTask->file2.iEof; + pTask->file2.iEof += mxSz; + } + } + } + +#if SQLITE_MAX_WORKER_THREADS>0 + if( rc==SQLITE_OK && pIncr->bUseThread ){ + /* Use the current thread to populate aFile[1], even though this + ** PmaReader is multi-threaded. The reason being that this function + ** is already running in background thread pIncr->pTask->thread. */ + assert( eMode==INCRINIT_ROOT || eMode==INCRINIT_TASK ); + rc = vdbeIncrPopulate(pIncr); + } +#endif + + if( rc==SQLITE_OK + && (SQLITE_MAX_WORKER_THREADS==0 || eMode!=INCRINIT_TASK) + ){ + rc = vdbePmaReaderNext(pReadr); + } + } + return rc; +} + +#if SQLITE_MAX_WORKER_THREADS>0 +/* +** The main routine for vdbePmaReaderIncrMergeInit() operations run in +** background threads. +*/ +static void *vdbePmaReaderBgInit(void *pCtx){ + PmaReader *pReader = (PmaReader*)pCtx; + void *pRet = SQLITE_INT_TO_PTR( + vdbePmaReaderIncrMergeInit(pReader,INCRINIT_TASK) + ); + pReader->pIncr->pTask->bDone = 1; + return pRet; +} + +/* +** Use a background thread to invoke vdbePmaReaderIncrMergeInit(INCRINIT_TASK) +** on the PmaReader object passed as the first argument. +** +** This call will initialize the various fields of the pReadr->pIncr +** structure and, if it is a multi-threaded IncrMerger, launch a +** background thread to populate aFile[1]. +*/ +static int vdbePmaReaderBgIncrInit(PmaReader *pReadr){ + void *pCtx = (void*)pReadr; + return vdbeSorterCreateThread(pReadr->pIncr->pTask, vdbePmaReaderBgInit, pCtx); +} +#endif + +/* +** Allocate a new MergeEngine object to merge the contents of nPMA level-0 +** PMAs from pTask->file. If no error occurs, set *ppOut to point to +** the new object and return SQLITE_OK. Or, if an error does occur, set *ppOut +** to NULL and return an SQLite error code. +** +** When this function is called, *piOffset is set to the offset of the +** first PMA to read from pTask->file. Assuming no error occurs, it is +** set to the offset immediately following the last byte of the last +** PMA before returning. If an error does occur, then the final value of +** *piOffset is undefined. +*/ +static int vdbeMergeEngineLevel0( + SortSubtask *pTask, /* Sorter task to read from */ + int nPMA, /* Number of PMAs to read */ + i64 *piOffset, /* IN/OUT: Readr offset in pTask->file */ + MergeEngine **ppOut /* OUT: New merge-engine */ +){ + MergeEngine *pNew; /* Merge engine to return */ + i64 iOff = *piOffset; + int i; + int rc = SQLITE_OK; + + *ppOut = pNew = vdbeMergeEngineNew(nPMA); + if( pNew==0 ) rc = SQLITE_NOMEM; + + for(i=0; i<nPMA && rc==SQLITE_OK; i++){ + i64 nDummy; + PmaReader *pReadr = &pNew->aReadr[i]; + rc = vdbePmaReaderInit(pTask, &pTask->file, iOff, pReadr, &nDummy); + iOff = pReadr->iEof; + } + + if( rc!=SQLITE_OK ){ + vdbeMergeEngineFree(pNew); + *ppOut = 0; + } + *piOffset = iOff; + return rc; +} + +/* +** Return the depth of a tree comprising nPMA PMAs, assuming a fanout of +** SORTER_MAX_MERGE_COUNT. The returned value does not include leaf nodes. +** +** i.e. +** +** nPMA<=16 -> TreeDepth() == 0 +** nPMA<=256 -> TreeDepth() == 1 +** nPMA<=65536 -> TreeDepth() == 2 +*/ +static int vdbeSorterTreeDepth(int nPMA){ + int nDepth = 0; + i64 nDiv = SORTER_MAX_MERGE_COUNT; + while( nDiv < (i64)nPMA ){ + nDiv = nDiv * SORTER_MAX_MERGE_COUNT; + nDepth++; + } + return nDepth; +} + +/* +** pRoot is the root of an incremental merge-tree with depth nDepth (according +** to vdbeSorterTreeDepth()). pLeaf is the iSeq'th leaf to be added to the +** tree, counting from zero. This function adds pLeaf to the tree. +** +** If successful, SQLITE_OK is returned. If an error occurs, an SQLite error +** code is returned and pLeaf is freed. +*/ +static int vdbeSorterAddToTree( + SortSubtask *pTask, /* Task context */ + int nDepth, /* Depth of tree according to TreeDepth() */ + int iSeq, /* Sequence number of leaf within tree */ + MergeEngine *pRoot, /* Root of tree */ + MergeEngine *pLeaf /* Leaf to add to tree */ +){ + int rc = SQLITE_OK; + int nDiv = 1; + int i; + MergeEngine *p = pRoot; + IncrMerger *pIncr; + + rc = vdbeIncrMergerNew(pTask, pLeaf, &pIncr); + + for(i=1; i<nDepth; i++){ + nDiv = nDiv * SORTER_MAX_MERGE_COUNT; + } + + for(i=1; i<nDepth && rc==SQLITE_OK; i++){ + int iIter = (iSeq / nDiv) % SORTER_MAX_MERGE_COUNT; + PmaReader *pReadr = &p->aReadr[iIter]; + + if( pReadr->pIncr==0 ){ + MergeEngine *pNew = vdbeMergeEngineNew(SORTER_MAX_MERGE_COUNT); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = vdbeIncrMergerNew(pTask, pNew, &pReadr->pIncr); + } + } + if( rc==SQLITE_OK ){ + p = pReadr->pIncr->pMerger; + nDiv = nDiv / SORTER_MAX_MERGE_COUNT; + } + } + + if( rc==SQLITE_OK ){ + p->aReadr[iSeq % SORTER_MAX_MERGE_COUNT].pIncr = pIncr; + }else{ + vdbeIncrFree(pIncr); + } + return rc; +} + +/* +** This function is called as part of a SorterRewind() operation on a sorter +** that has already written two or more level-0 PMAs to one or more temp +** files. It builds a tree of MergeEngine/IncrMerger/PmaReader objects that +** can be used to incrementally merge all PMAs on disk. +** +** If successful, SQLITE_OK is returned and *ppOut set to point to the +** MergeEngine object at the root of the tree before returning. Or, if an +** error occurs, an SQLite error code is returned and the final value +** of *ppOut is undefined. +*/ +static int vdbeSorterMergeTreeBuild( + VdbeSorter *pSorter, /* The VDBE cursor that implements the sort */ + MergeEngine **ppOut /* Write the MergeEngine here */ +){ + MergeEngine *pMain = 0; + int rc = SQLITE_OK; + int iTask; + +#if SQLITE_MAX_WORKER_THREADS>0 + /* If the sorter uses more than one task, then create the top-level + ** MergeEngine here. This MergeEngine will read data from exactly + ** one PmaReader per sub-task. */ + assert( pSorter->bUseThreads || pSorter->nTask==1 ); + if( pSorter->nTask>1 ){ + pMain = vdbeMergeEngineNew(pSorter->nTask); + if( pMain==0 ) rc = SQLITE_NOMEM; + } +#endif + + for(iTask=0; rc==SQLITE_OK && iTask<pSorter->nTask; iTask++){ + SortSubtask *pTask = &pSorter->aTask[iTask]; + assert( pTask->nPMA>0 || SQLITE_MAX_WORKER_THREADS>0 ); + if( SQLITE_MAX_WORKER_THREADS==0 || pTask->nPMA ){ + MergeEngine *pRoot = 0; /* Root node of tree for this task */ + int nDepth = vdbeSorterTreeDepth(pTask->nPMA); + i64 iReadOff = 0; + + if( pTask->nPMA<=SORTER_MAX_MERGE_COUNT ){ + rc = vdbeMergeEngineLevel0(pTask, pTask->nPMA, &iReadOff, &pRoot); + }else{ + int i; + int iSeq = 0; + pRoot = vdbeMergeEngineNew(SORTER_MAX_MERGE_COUNT); + if( pRoot==0 ) rc = SQLITE_NOMEM; + for(i=0; i<pTask->nPMA && rc==SQLITE_OK; i += SORTER_MAX_MERGE_COUNT){ + MergeEngine *pMerger = 0; /* New level-0 PMA merger */ + int nReader; /* Number of level-0 PMAs to merge */ + + nReader = MIN(pTask->nPMA - i, SORTER_MAX_MERGE_COUNT); + rc = vdbeMergeEngineLevel0(pTask, nReader, &iReadOff, &pMerger); + if( rc==SQLITE_OK ){ + rc = vdbeSorterAddToTree(pTask, nDepth, iSeq++, pRoot, pMerger); + } + } + } + + if( rc==SQLITE_OK ){ +#if SQLITE_MAX_WORKER_THREADS>0 + if( pMain!=0 ){ + rc = vdbeIncrMergerNew(pTask, pRoot, &pMain->aReadr[iTask].pIncr); + }else +#endif + { + assert( pMain==0 ); + pMain = pRoot; + } + }else{ + vdbeMergeEngineFree(pRoot); + } + } + } + + if( rc!=SQLITE_OK ){ + vdbeMergeEngineFree(pMain); + pMain = 0; + } + *ppOut = pMain; + return rc; +} + +/* +** This function is called as part of an sqlite3VdbeSorterRewind() operation +** on a sorter that has written two or more PMAs to temporary files. It sets +** up either VdbeSorter.pMerger (for single threaded sorters) or pReader +** (for multi-threaded sorters) so that it can be used to iterate through +** all records stored in the sorter. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +static int vdbeSorterSetupMerge(VdbeSorter *pSorter){ + int rc; /* Return code */ + SortSubtask *pTask0 = &pSorter->aTask[0]; + MergeEngine *pMain = 0; +#if SQLITE_MAX_WORKER_THREADS + sqlite3 *db = pTask0->pSorter->db; +#endif + + rc = vdbeSorterMergeTreeBuild(pSorter, &pMain); + if( rc==SQLITE_OK ){ +#if SQLITE_MAX_WORKER_THREADS + assert( pSorter->bUseThreads==0 || pSorter->nTask>1 ); + if( pSorter->bUseThreads ){ + int iTask; + PmaReader *pReadr = 0; + SortSubtask *pLast = &pSorter->aTask[pSorter->nTask-1]; + rc = vdbeSortAllocUnpacked(pLast); + if( rc==SQLITE_OK ){ + pReadr = (PmaReader*)sqlite3DbMallocZero(db, sizeof(PmaReader)); + pSorter->pReader = pReadr; + if( pReadr==0 ) rc = SQLITE_NOMEM; + } + if( rc==SQLITE_OK ){ + rc = vdbeIncrMergerNew(pLast, pMain, &pReadr->pIncr); + if( rc==SQLITE_OK ){ + vdbeIncrMergerSetThreads(pReadr->pIncr); + for(iTask=0; iTask<(pSorter->nTask-1); iTask++){ + IncrMerger *pIncr; + if( (pIncr = pMain->aReadr[iTask].pIncr) ){ + vdbeIncrMergerSetThreads(pIncr); + assert( pIncr->pTask!=pLast ); + } + } + for(iTask=0; rc==SQLITE_OK && iTask<pSorter->nTask; iTask++){ + PmaReader *p = &pMain->aReadr[iTask]; + assert( p->pIncr==0 || p->pIncr->pTask==&pSorter->aTask[iTask] ); + if( p->pIncr ){ + if( iTask==pSorter->nTask-1 ){ + rc = vdbePmaReaderIncrMergeInit(p, INCRINIT_TASK); + }else{ + rc = vdbePmaReaderBgIncrInit(p); + } + } + } + } + pMain = 0; + } + if( rc==SQLITE_OK ){ + rc = vdbePmaReaderIncrMergeInit(pReadr, INCRINIT_ROOT); + } + }else +#endif + { + rc = vdbeMergeEngineInit(pTask0, pMain, INCRINIT_NORMAL); + pSorter->pMerger = pMain; + pMain = 0; + } + } + + if( rc!=SQLITE_OK ){ + vdbeMergeEngineFree(pMain); + } + return rc; +} + + +/* +** Once the sorter has been populated by calls to sqlite3VdbeSorterWrite, +** this function is called to prepare for iterating through the records +** in sorted order. +*/ +SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){ VdbeSorter *pSorter = pCsr->pSorter; int rc = SQLITE_OK; /* Return code */ - int i; /* Used to iterator through aIter[] */ - i64 nByte = 0; /* Total bytes in all opened PMAs */ - - /* Initialize the iterators. */ - for(i=0; i<SORTER_MAX_MERGE_COUNT; i++){ - VdbeSorterIter *pIter = &pSorter->aIter[i]; - rc = vdbeSorterIterInit(db, pSorter, pSorter->iReadOff, pIter, &nByte); - pSorter->iReadOff = pIter->iEof; - assert( rc!=SQLITE_OK || pSorter->iReadOff<=pSorter->iWriteOff ); - if( rc!=SQLITE_OK || pSorter->iReadOff>=pSorter->iWriteOff ) break; - } - - /* Initialize the aTree[] array. */ - for(i=pSorter->nTree-1; rc==SQLITE_OK && i>0; i--){ - rc = vdbeSorterDoCompare(pCsr, i); - } - - *pnByte = nByte; - return rc; -} - -/* -** Once the sorter has been populated, this function is called to prepare -** for iterating through its contents in sorted order. -*/ -SQLITE_PRIVATE int sqlite3VdbeSorterRewind(sqlite3 *db, const VdbeCursor *pCsr, int *pbEof){ - VdbeSorter *pSorter = pCsr->pSorter; - int rc; /* Return code */ - sqlite3_file *pTemp2 = 0; /* Second temp file to use */ - i64 iWrite2 = 0; /* Write offset for pTemp2 */ - int nIter; /* Number of iterators used */ - int nByte; /* Bytes of space required for aIter/aTree */ - int N = 2; /* Power of 2 >= nIter */ assert( pSorter ); /* If no data has been written to disk, then do not do so now. Instead, ** sort the VdbeSorter.pRecord list. The vdbe layer will read data directly ** from the in-memory list. */ - if( pSorter->nPMA==0 ){ - *pbEof = !pSorter->pRecord; - assert( pSorter->aTree==0 ); - return vdbeSorterSort(pCsr); - } - - /* Write the current in-memory list to a PMA. */ - rc = vdbeSorterListToPMA(db, pCsr); - if( rc!=SQLITE_OK ) return rc; - - /* Allocate space for aIter[] and aTree[]. */ - nIter = pSorter->nPMA; - if( nIter>SORTER_MAX_MERGE_COUNT ) nIter = SORTER_MAX_MERGE_COUNT; - assert( nIter>0 ); - while( N<nIter ) N += N; - nByte = N * (sizeof(int) + sizeof(VdbeSorterIter)); - pSorter->aIter = (VdbeSorterIter *)sqlite3DbMallocZero(db, nByte); - if( !pSorter->aIter ) return SQLITE_NOMEM; - pSorter->aTree = (int *)&pSorter->aIter[N]; - pSorter->nTree = N; - - do { - int iNew; /* Index of new, merged, PMA */ - - for(iNew=0; - rc==SQLITE_OK && iNew*SORTER_MAX_MERGE_COUNT<pSorter->nPMA; - iNew++ - ){ - int rc2; /* Return code from fileWriterFinish() */ - FileWriter writer; /* Object used to write to disk */ - i64 nWrite; /* Number of bytes in new PMA */ - - memset(&writer, 0, sizeof(FileWriter)); - - /* If there are SORTER_MAX_MERGE_COUNT or less PMAs in file pTemp1, - ** initialize an iterator for each of them and break out of the loop. - ** These iterators will be incrementally merged as the VDBE layer calls - ** sqlite3VdbeSorterNext(). - ** - ** Otherwise, if pTemp1 contains more than SORTER_MAX_MERGE_COUNT PMAs, - ** initialize interators for SORTER_MAX_MERGE_COUNT of them. These PMAs - ** are merged into a single PMA that is written to file pTemp2. - */ - rc = vdbeSorterInitMerge(db, pCsr, &nWrite); - assert( rc!=SQLITE_OK || pSorter->aIter[ pSorter->aTree[1] ].pFile ); - if( rc!=SQLITE_OK || pSorter->nPMA<=SORTER_MAX_MERGE_COUNT ){ - break; - } - - /* Open the second temp file, if it is not already open. */ - if( pTemp2==0 ){ - assert( iWrite2==0 ); - rc = vdbeSorterOpenTempFile(db, &pTemp2); - } - - if( rc==SQLITE_OK ){ - int bEof = 0; - fileWriterInit(db, pTemp2, &writer, iWrite2); - fileWriterWriteVarint(&writer, nWrite); - while( rc==SQLITE_OK && bEof==0 ){ - VdbeSorterIter *pIter = &pSorter->aIter[ pSorter->aTree[1] ]; - assert( pIter->pFile ); - - fileWriterWriteVarint(&writer, pIter->nKey); - fileWriterWrite(&writer, pIter->aKey, pIter->nKey); - rc = sqlite3VdbeSorterNext(db, pCsr, &bEof); - } - rc2 = fileWriterFinish(db, &writer, &iWrite2); - if( rc==SQLITE_OK ) rc = rc2; - } - } - - if( pSorter->nPMA<=SORTER_MAX_MERGE_COUNT ){ - break; - }else{ - sqlite3_file *pTmp = pSorter->pTemp1; - pSorter->nPMA = iNew; - pSorter->pTemp1 = pTemp2; - pTemp2 = pTmp; - pSorter->iWriteOff = iWrite2; - pSorter->iReadOff = 0; - iWrite2 = 0; - } - }while( rc==SQLITE_OK ); - - if( pTemp2 ){ - sqlite3OsCloseFree(pTemp2); - } - *pbEof = (pSorter->aIter[pSorter->aTree[1]].pFile==0); + if( pSorter->bUsePMA==0 ){ + if( pSorter->list.pList ){ + *pbEof = 0; + rc = vdbeSorterSort(&pSorter->aTask[0], &pSorter->list); + }else{ + *pbEof = 1; + } + return rc; + } + + /* Write the current in-memory list to a PMA. When the VdbeSorterWrite() + ** function flushes the contents of memory to disk, it immediately always + ** creates a new list consisting of a single key immediately afterwards. + ** So the list is never empty at this point. */ + assert( pSorter->list.pList ); + rc = vdbeSorterFlushPMA(pSorter); + + /* Join all threads */ + rc = vdbeSorterJoinAll(pSorter, rc); + + vdbeSorterRewindDebug("rewind"); + + /* Assuming no errors have occurred, set up a merger structure to + ** incrementally read and merge all remaining PMAs. */ + assert( pSorter->pReader==0 ); + if( rc==SQLITE_OK ){ + rc = vdbeSorterSetupMerge(pSorter); + *pbEof = 0; + } + + vdbeSorterRewindDebug("rewinddone"); return rc; } /* ** Advance to the next element in the sorter. @@ -73836,26 +80094,32 @@ */ SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr, int *pbEof){ VdbeSorter *pSorter = pCsr->pSorter; int rc; /* Return code */ - if( pSorter->aTree ){ - int iPrev = pSorter->aTree[1];/* Index of iterator to advance */ - int i; /* Index of aTree[] to recalculate */ - - rc = vdbeSorterIterNext(db, &pSorter->aIter[iPrev]); - for(i=(pSorter->nTree+iPrev)/2; rc==SQLITE_OK && i>0; i=i/2){ - rc = vdbeSorterDoCompare(pCsr, i); - } - - *pbEof = (pSorter->aIter[pSorter->aTree[1]].pFile==0); - }else{ - SorterRecord *pFree = pSorter->pRecord; - pSorter->pRecord = pFree->pNext; - pFree->pNext = 0; - vdbeSorterRecordFree(db, pFree); - *pbEof = !pSorter->pRecord; + assert( pSorter->bUsePMA || (pSorter->pReader==0 && pSorter->pMerger==0) ); + if( pSorter->bUsePMA ){ + assert( pSorter->pReader==0 || pSorter->pMerger==0 ); + assert( pSorter->bUseThreads==0 || pSorter->pReader ); + assert( pSorter->bUseThreads==1 || pSorter->pMerger ); +#if SQLITE_MAX_WORKER_THREADS>0 + if( pSorter->bUseThreads ){ + rc = vdbePmaReaderNext(pSorter->pReader); + *pbEof = (pSorter->pReader->pFd==0); + }else +#endif + /*if( !pSorter->bUseThreads )*/ { + assert( pSorter->pMerger!=0 ); + assert( pSorter->pMerger->pTask==(&pSorter->aTask[0]) ); + rc = vdbeMergeEngineStep(pSorter->pMerger, pbEof); + } + }else{ + SorterRecord *pFree = pSorter->list.pList; + pSorter->list.pList = pFree->u.pNext; + pFree->u.pNext = 0; + if( pSorter->list.aMemory==0 ) vdbeSorterRecordFree(db, pFree); + *pbEof = !pSorter->list.pList; rc = SQLITE_OK; } return rc; } @@ -73866,18 +80130,25 @@ static void *vdbeSorterRowkey( const VdbeSorter *pSorter, /* Sorter object */ int *pnKey /* OUT: Size of current key in bytes */ ){ void *pKey; - if( pSorter->aTree ){ - VdbeSorterIter *pIter; - pIter = &pSorter->aIter[ pSorter->aTree[1] ]; - *pnKey = pIter->nKey; - pKey = pIter->aKey; + if( pSorter->bUsePMA ){ + PmaReader *pReader; +#if SQLITE_MAX_WORKER_THREADS>0 + if( pSorter->bUseThreads ){ + pReader = pSorter->pReader; + }else +#endif + /*if( !pSorter->bUseThreads )*/{ + pReader = &pSorter->pMerger->aReadr[pSorter->pMerger->aTree[1]]; + } + *pnKey = pReader->nKey; + pKey = pReader->aKey; }else{ - *pnKey = pSorter->pRecord->nVal; - pKey = pSorter->pRecord->pVal; + *pnKey = pSorter->list.pList->nVal; + pKey = SRVAL(pSorter->list.pList); } return pKey; } /* @@ -73886,11 +80157,11 @@ SQLITE_PRIVATE int sqlite3VdbeSorterRowkey(const VdbeCursor *pCsr, Mem *pOut){ VdbeSorter *pSorter = pCsr->pSorter; void *pKey; int nKey; /* Sorter key to copy into pOut */ pKey = vdbeSorterRowkey(pSorter, &nKey); - if( sqlite3VdbeMemGrow(pOut, nKey, 0) ){ + if( sqlite3VdbeMemClearAndResize(pOut, nKey) ){ return SQLITE_NOMEM; } pOut->n = nKey; MemSetTypeFlag(pOut, MEM_Blob); memcpy(pOut->z, pKey, nKey); @@ -73900,26 +80171,53 @@ /* ** Compare the key in memory cell pVal with the key that the sorter cursor ** passed as the first argument currently points to. For the purposes of ** the comparison, ignore the rowid field at the end of each record. +** +** If the sorter cursor key contains any NULL values, consider it to be +** less than pVal. Even if pVal also contains NULL values. ** ** If an error occurs, return an SQLite error code (i.e. SQLITE_NOMEM). ** Otherwise, set *pRes to a negative, zero or positive value if the ** key in pVal is smaller than, equal to or larger than the current sorter ** key. +** +** This routine forms the core of the OP_SorterCompare opcode, which in +** turn is used to verify uniqueness when constructing a UNIQUE INDEX. */ SQLITE_PRIVATE int sqlite3VdbeSorterCompare( const VdbeCursor *pCsr, /* Sorter cursor */ Mem *pVal, /* Value to compare to current sorter key */ + int nKeyCol, /* Compare this many columns */ int *pRes /* OUT: Result of comparison */ ){ VdbeSorter *pSorter = pCsr->pSorter; + UnpackedRecord *r2 = pSorter->pUnpacked; + KeyInfo *pKeyInfo = pCsr->pKeyInfo; + int i; void *pKey; int nKey; /* Sorter key to compare pVal with */ + if( r2==0 ){ + char *p; + r2 = pSorter->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pKeyInfo,0,0,&p); + assert( pSorter->pUnpacked==(UnpackedRecord*)p ); + if( r2==0 ) return SQLITE_NOMEM; + r2->nField = nKeyCol; + } + assert( r2->nField==nKeyCol ); + pKey = vdbeSorterRowkey(pSorter, &nKey); - vdbeSorterCompare(pCsr, 1, pVal->z, pVal->n, pKey, nKey, pRes); + sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, r2); + for(i=0; i<nKeyCol; i++){ + if( r2->aMem[i].flags & MEM_Null ){ + *pRes = -1; + return SQLITE_OK; + } + } + + *pRes = sqlite3VdbeRecordCompare(pVal->n, pVal->z, r2); return SQLITE_OK; } /************** End of vdbesort.c ********************************************/ /************** Begin file journal.c *****************************************/ @@ -74206,11 +80504,11 @@ /* Space to hold the rollback journal is allocated in increments of ** this many bytes. ** ** The size chosen is a little less than a power of two. That way, ** the FileChunk object will have a size that almost exactly fills -** a power-of-two allocation. This mimimizes wasted space in power-of-two +** a power-of-two allocation. This minimizes wasted space in power-of-two ** memory allocators. */ #define JOURNAL_CHUNKSIZE ((int)(1024-sizeof(FileChunk*))) /* @@ -74456,11 +80754,11 @@ /* #include <string.h> */ /* ** Walk an expression tree. Invoke the callback once for each node -** of the expression, while decending. (In other words, the callback +** of the expression, while descending. (In other words, the callback ** is invoked before visiting children.) ** ** The return value from the callback should be one of the WRC_* ** constants to specify how to proceed with the walk. ** @@ -74550,42 +80848,43 @@ } /* ** Call sqlite3WalkExpr() for every expression in Select statement p. ** Invoke sqlite3WalkSelect() for subqueries in the FROM clause and -** on the compound select chain, p->pPrior. Invoke the xSelectCallback() -** either before or after the walk of expressions and FROM clause, depending -** on whether pWalker->bSelectDepthFirst is false or true, respectively. +** on the compound select chain, p->pPrior. +** +** If it is not NULL, the xSelectCallback() callback is invoked before +** the walk of the expressions and FROM clause. The xSelectCallback2() +** method, if it is not NULL, is invoked following the walk of the +** expressions and FROM clause. ** ** Return WRC_Continue under normal conditions. Return WRC_Abort if ** there is an abort request. ** ** If the Walker does not have an xSelectCallback() then this routine ** is a no-op returning WRC_Continue. */ SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){ int rc; - if( p==0 || pWalker->xSelectCallback==0 ) return WRC_Continue; + if( p==0 || (pWalker->xSelectCallback==0 && pWalker->xSelectCallback2==0) ){ + return WRC_Continue; + } rc = WRC_Continue; pWalker->walkerDepth++; while( p ){ - if( !pWalker->bSelectDepthFirst ){ + if( pWalker->xSelectCallback ){ rc = pWalker->xSelectCallback(pWalker, p); if( rc ) break; } if( sqlite3WalkSelectExpr(pWalker, p) || sqlite3WalkSelectFrom(pWalker, p) ){ pWalker->walkerDepth--; return WRC_Abort; } - if( pWalker->bSelectDepthFirst ){ - rc = pWalker->xSelectCallback(pWalker, p); - /* Depth-first search is currently only used for - ** selectAddSubqueryTypeInfo() and that routine always returns - ** WRC_Continue (0). So the following branch is never taken. */ - if( NEVER(rc) ) break; + if( pWalker->xSelectCallback2 ){ + pWalker->xSelectCallback2(pWalker, p); } p = p->pPrior; } pWalker->walkerDepth--; return rc & WRC_Abort; @@ -74620,19 +80919,19 @@ ** ** incrAggFunctionDepth(pExpr,n) is the main routine. incrAggDepth(..) ** is a helper function - a callback for the tree walker. */ static int incrAggDepth(Walker *pWalker, Expr *pExpr){ - if( pExpr->op==TK_AGG_FUNCTION ) pExpr->op2 += pWalker->u.i; + if( pExpr->op==TK_AGG_FUNCTION ) pExpr->op2 += pWalker->u.n; return WRC_Continue; } static void incrAggFunctionDepth(Expr *pExpr, int N){ if( N>0 ){ Walker w; memset(&w, 0, sizeof(w)); w.xExprCallback = incrAggDepth; - w.u.i = N; + w.u.n = N; sqlite3WalkExpr(&w, pExpr); } } /* @@ -74700,14 +80999,14 @@ if( pOrig->op!=TK_COLUMN && zType[0]!='G' ){ incrAggFunctionDepth(pDup, nSubquery); pDup = sqlite3PExpr(pParse, TK_AS, pDup, 0, 0); if( pDup==0 ) return; ExprSetProperty(pDup, EP_Skip); - if( pEList->a[iCol].iAlias==0 ){ - pEList->a[iCol].iAlias = (u16)(++pParse->nAlias); + if( pEList->a[iCol].u.x.iAlias==0 ){ + pEList->a[iCol].u.x.iAlias = (u16)(++pParse->nAlias); } - pDup->iTable = pEList->a[iCol].iAlias; + pDup->iTable = pEList->a[iCol].u.x.iAlias; } if( pExpr->op==TK_COLLATE ){ pDup = sqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken); } @@ -74818,11 +81117,13 @@ sqlite3 *db = pParse->db; /* The database connection */ struct SrcList_item *pItem; /* Use for looping over pSrcList items */ struct SrcList_item *pMatch = 0; /* The matching pSrcList item */ NameContext *pTopNC = pNC; /* First namecontext in the list */ Schema *pSchema = 0; /* Schema of the expression */ - int isTrigger = 0; + int isTrigger = 0; /* True if resolved to a trigger column */ + Table *pTab = 0; /* Table hold the row */ + Column *pCol; /* A column of pTab */ assert( pNC ); /* the name context cannot be NULL. */ assert( zCol ); /* The Z in X.Y.Z cannot be NULL */ assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); @@ -74837,13 +81138,14 @@ */ if( zDb ){ testcase( pNC->ncFlags & NC_PartIdx ); testcase( pNC->ncFlags & NC_IsCheck ); if( (pNC->ncFlags & (NC_PartIdx|NC_IsCheck))!=0 ){ - /* Silently ignore database qualifiers inside CHECK constraints and partial - ** indices. Do not raise errors because that might break legacy and - ** because it does not hurt anything to just ignore the database name. */ + /* Silently ignore database qualifiers inside CHECK constraints and + ** partial indices. Do not raise errors because that might break + ** legacy and because it does not hurt anything to just ignore the + ** database name. */ zDb = 0; }else{ for(i=0; i<db->nDb; i++){ assert( db->aDb[i].zName ); if( sqlite3StrICmp(db->aDb[i].zName,zDb)==0 ){ @@ -74859,13 +81161,10 @@ ExprList *pEList; SrcList *pSrcList = pNC->pSrcList; if( pSrcList ){ for(i=0, pItem=pSrcList->a; i<pSrcList->nSrc; i++, pItem++){ - Table *pTab; - Column *pCol; - pTab = pItem->pTab; assert( pTab!=0 && pTab->zName!=0 ); assert( pTab->nCol>0 ); if( pItem->pSelect && (pItem->pSelect->selFlags & SF_NestedFrom)!=0 ){ int hit = 0; @@ -74913,45 +81212,52 @@ } } if( pMatch ){ pExpr->iTable = pMatch->iCursor; pExpr->pTab = pMatch->pTab; + /* RIGHT JOIN not (yet) supported */ + assert( (pMatch->jointype & JT_RIGHT)==0 ); + if( (pMatch->jointype & JT_LEFT)!=0 ){ + ExprSetProperty(pExpr, EP_CanBeNull); + } pSchema = pExpr->pTab->pSchema; } } /* if( pSrcList ) */ #ifndef SQLITE_OMIT_TRIGGER /* If we have not already resolved the name, then maybe ** it is a new.* or old.* trigger argument reference */ - if( zDb==0 && zTab!=0 && cnt==0 && pParse->pTriggerTab!=0 ){ + if( zDb==0 && zTab!=0 && cntTab==0 && pParse->pTriggerTab!=0 ){ int op = pParse->eTriggerOp; - Table *pTab = 0; assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT ); if( op!=TK_DELETE && sqlite3StrICmp("new",zTab) == 0 ){ pExpr->iTable = 1; pTab = pParse->pTriggerTab; }else if( op!=TK_INSERT && sqlite3StrICmp("old",zTab)==0 ){ pExpr->iTable = 0; pTab = pParse->pTriggerTab; + }else{ + pTab = 0; } if( pTab ){ int iCol; pSchema = pTab->pSchema; cntTab++; - for(iCol=0; iCol<pTab->nCol; iCol++){ - Column *pCol = &pTab->aCol[iCol]; + for(iCol=0, pCol=pTab->aCol; iCol<pTab->nCol; iCol++, pCol++){ if( sqlite3StrICmp(pCol->zName, zCol)==0 ){ if( iCol==pTab->iPKey ){ iCol = -1; } break; } } - if( iCol>=pTab->nCol && sqlite3IsRowid(zCol) ){ - iCol = -1; /* IMP: R-44911-55124 */ + if( iCol>=pTab->nCol && sqlite3IsRowid(zCol) && HasRowid(pTab) ){ + /* IMP: R-51414-32910 */ + /* IMP: R-44911-55124 */ + iCol = -1; } if( iCol<pTab->nCol ){ cnt++; if( iCol<0 ){ pExpr->affinity = SQLITE_AFF_INTEGER; @@ -74973,11 +81279,12 @@ #endif /* !defined(SQLITE_OMIT_TRIGGER) */ /* ** Perhaps the name is a reference to the ROWID */ - if( cnt==0 && cntTab==1 && sqlite3IsRowid(zCol) ){ + if( cnt==0 && cntTab==1 && pMatch && sqlite3IsRowid(zCol) + && HasRowid(pMatch->pTab) ){ cnt = 1; pExpr->iColumn = -1; /* IMP: R-44911-55124 */ pExpr->affinity = SQLITE_AFF_INTEGER; } @@ -75174,11 +81481,11 @@ double r = -1.0; if( p->op!=TK_FLOAT ) return -1; sqlite3AtoF(p->u.zToken, &r, sqlite3Strlen30(p->u.zToken), SQLITE_UTF8); assert( r>=0.0 ); if( r>1.0 ) return -1; - return (int)(r*1000.0); + return (int)(r*134217728.0); } /* ** This routine is callback for sqlite3WalkExpr(). ** @@ -75227,11 +81534,12 @@ pExpr->iTable = pItem->iCursor; pExpr->iColumn = -1; pExpr->affinity = SQLITE_AFF_INTEGER; break; } -#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) */ +#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) + && !defined(SQLITE_OMIT_SUBQUERY) */ /* A lone identifier is the name of a column. */ case TK_ID: { return lookupName(pParse, 0, 0, pExpr->u.zToken, pNC, pExpr); @@ -75261,11 +81569,10 @@ return lookupName(pParse, zDb, zTable, zColumn, pNC, pExpr); } /* Resolve function names */ - case TK_CONST_FUNC: case TK_FUNCTION: { ExprList *pList = pExpr->x.pList; /* The argument list */ int n = pList ? pList->nExpr : 0; /* Number of arguments */ int no_such_func = 0; /* True if no such function exists */ int wrong_num_args = 0; /* True if wrong number of arguments */ @@ -75274,11 +81581,10 @@ int nId; /* Number of characters in function name */ const char *zId; /* The function name. */ FuncDef *pDef; /* Information about the function */ u8 enc = ENC(pParse->db); /* The database encoding */ - testcase( pExpr->op==TK_CONST_FUNC ); assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); notValidPartIdxWhere(pParse, pNC, "functions"); zId = pExpr->u.zToken; nId = sqlite3Strlen30(zId); pDef = sqlite3FindFunction(pParse->db, zId, nId, n, enc, 0); @@ -75294,21 +81600,29 @@ if( pDef->funcFlags & SQLITE_FUNC_UNLIKELY ){ ExprSetProperty(pExpr, EP_Unlikely|EP_Skip); if( n==2 ){ pExpr->iTable = exprProbability(pList->a[1].pExpr); if( pExpr->iTable<0 ){ - sqlite3ErrorMsg(pParse, "second argument to likelihood() must be a " - "constant between 0.0 and 1.0"); + sqlite3ErrorMsg(pParse, + "second argument to likelihood() must be a " + "constant between 0.0 and 1.0"); pNC->nErr++; } }else{ - pExpr->iTable = 62; /* TUNING: Default 2nd arg to unlikely() is 0.0625 */ + /* EVIDENCE-OF: R-61304-29449 The unlikely(X) function is + ** equivalent to likelihood(X, 0.0625). + ** EVIDENCE-OF: R-01283-11636 The unlikely(X) function is + ** short-hand for likelihood(X,0.0625). + ** EVIDENCE-OF: R-36850-34127 The likely(X) function is short-hand + ** for likelihood(X,0.9375). + ** EVIDENCE-OF: R-53436-40973 The likely(X) function is equivalent + ** to likelihood(X,0.9375). */ + /* TUNING: unlikely() probability is 0.0625. likely() is 0.9375 */ + pExpr->iTable = pDef->zName[0]=='u' ? 8388608 : 125829120; } } - } #ifndef SQLITE_OMIT_AUTHORIZATION - if( pDef ){ auth = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0, pDef->zName, 0); if( auth!=SQLITE_OK ){ if( auth==SQLITE_DENY ){ sqlite3ErrorMsg(pParse, "not authorized to use function: %s", pDef->zName); @@ -75315,12 +81629,15 @@ pNC->nErr++; } pExpr->op = TK_NULL; return WRC_Prune; } +#endif + if( pDef->funcFlags & SQLITE_FUNC_CONSTANT ){ + ExprSetProperty(pExpr,EP_ConstFunc); + } } -#endif if( is_agg && (pNC->ncFlags & NC_AllowAgg)==0 ){ sqlite3ErrorMsg(pParse, "misuse of aggregate function %.*s()", nId,zId); pNC->nErr++; is_agg = 0; }else if( no_such_func && pParse->db->init.busy==0 ){ @@ -75339,11 +81656,17 @@ pExpr->op2 = 0; while( pNC2 && !sqlite3FunctionUsesThisSrc(pExpr, pNC2->pSrcList) ){ pExpr->op2++; pNC2 = pNC2->pNext; } - if( pNC2 ) pNC2->ncFlags |= NC_HasAgg; + assert( pDef!=0 ); + if( pNC2 ){ + assert( SQLITE_FUNC_MINMAX==NC_MinMaxAgg ); + testcase( (pDef->funcFlags & SQLITE_FUNC_MINMAX)!=0 ); + pNC2->ncFlags |= NC_HasAgg | (pDef->funcFlags & SQLITE_FUNC_MINMAX); + + } pNC->ncFlags |= NC_AllowAgg; } /* FIX ME: Compute pExpr->affinity based on the expected return ** type of the function */ @@ -75566,11 +81889,11 @@ assert( pItem->pExpr->op==TK_COLLATE ); assert( pItem->pExpr->pLeft==pE ); pItem->pExpr->pLeft = pNew; } sqlite3ExprDelete(db, pE); - pItem->iOrderByCol = (u16)iCol; + pItem->u.x.iOrderByCol = (u16)iCol; pItem->done = 1; }else{ moreToDo = 1; } } @@ -75587,12 +81910,12 @@ } /* ** Check every term in the ORDER BY or GROUP BY clause pOrderBy of ** the SELECT statement pSelect. If any term is reference to a -** result set expression (as determined by the ExprList.a.iOrderByCol field) -** then convert that term into a copy of the corresponding result set +** result set expression (as determined by the ExprList.a.u.x.iOrderByCol +** field) then convert that term into a copy of the corresponding result set ** column. ** ** If any errors are detected, add an error message to pParse and ** return non-zero. Return zero if no errors are seen. */ @@ -75615,16 +81938,17 @@ } #endif pEList = pSelect->pEList; assert( pEList!=0 ); /* sqlite3SelectNew() guarantees this */ for(i=0, pItem=pOrderBy->a; i<pOrderBy->nExpr; i++, pItem++){ - if( pItem->iOrderByCol ){ - if( pItem->iOrderByCol>pEList->nExpr ){ + if( pItem->u.x.iOrderByCol ){ + if( pItem->u.x.iOrderByCol>pEList->nExpr ){ resolveOutOfRangeError(pParse, zType, i+1, pEList->nExpr); return 1; } - resolveAlias(pParse, pEList, pItem->iOrderByCol-1, pItem->pExpr, zType,0); + resolveAlias(pParse, pEList, pItem->u.x.iOrderByCol-1, pItem->pExpr, + zType,0); } } return 0; } @@ -75669,11 +81993,11 @@ if( iCol>0 ){ /* If an AS-name match is found, mark this ORDER BY column as being ** a copy of the iCol-th result-set column. The subsequent call to ** sqlite3ResolveOrderGroupBy() will convert the expression to a ** copy of the iCol-th result-set expression. */ - pItem->iOrderByCol = (u16)iCol; + pItem->u.x.iOrderByCol = (u16)iCol; continue; } } if( sqlite3ExprIsInteger(pE2, &iCol) ){ /* The ORDER BY term is an integer constant. Again, set the column @@ -75681,30 +82005,30 @@ ** order-by term to a copy of the result-set expression */ if( iCol<1 || iCol>0xffff ){ resolveOutOfRangeError(pParse, zType, i+1, nResult); return 1; } - pItem->iOrderByCol = (u16)iCol; + pItem->u.x.iOrderByCol = (u16)iCol; continue; } /* Otherwise, treat the ORDER BY term as an ordinary expression */ - pItem->iOrderByCol = 0; + pItem->u.x.iOrderByCol = 0; if( sqlite3ResolveExprNames(pNC, pE) ){ return 1; } for(j=0; j<pSelect->pEList->nExpr; j++){ if( sqlite3ExprCompare(pE, pSelect->pEList->a[j].pExpr, -1)==0 ){ - pItem->iOrderByCol = j+1; + pItem->u.x.iOrderByCol = j+1; } } } return sqlite3ResolveOrderGroupBy(pParse, pSelect, pOrderBy, zType); } /* -** Resolve names in the SELECT statement p and all of its descendents. +** Resolve names in the SELECT statement p and all of its descendants. */ static int resolveSelectStep(Walker *pWalker, Select *p){ NameContext *pOuterNC; /* Context that contains this SELECT */ NameContext sNC; /* Name context of this SELECT */ int isCompound; /* True if p is a compound select */ @@ -75804,11 +82128,12 @@ ** expression, do not allow aggregates in any of the other expressions. */ assert( (p->selFlags & SF_Aggregate)==0 ); pGroupBy = p->pGroupBy; if( pGroupBy || (sNC.ncFlags & NC_HasAgg)!=0 ){ - p->selFlags |= SF_Aggregate; + assert( NC_MinMaxAgg==SF_MinMaxAgg ); + p->selFlags |= SF_Aggregate | (sNC.ncFlags&NC_MinMaxAgg); }else{ sNC.ncFlags &= ~NC_AllowAgg; } /* If a HAVING clause is present, then there must be a GROUP BY clause. @@ -75932,11 +82257,11 @@ */ SQLITE_PRIVATE int sqlite3ResolveExprNames( NameContext *pNC, /* Namespace to resolve expressions in. */ Expr *pExpr /* The expression to be analyzed. */ ){ - u8 savedHasAgg; + u16 savedHasAgg; Walker w; if( pExpr==0 ) return 0; #if SQLITE_MAX_EXPR_DEPTH>0 { @@ -75945,12 +82270,12 @@ return 1; } pParse->nHeight += pExpr->nHeight; } #endif - savedHasAgg = pNC->ncFlags & NC_HasAgg; - pNC->ncFlags &= ~NC_HasAgg; + savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg); memset(&w, 0, sizeof(w)); w.xExprCallback = resolveExprStep; w.xSelectCallback = resolveSelectStep; w.pParse = pNC->pParse; w.u.pNC = pNC; @@ -75961,13 +82286,12 @@ if( pNC->nErr>0 || w.pParse->nErr>0 ){ ExprSetProperty(pExpr, EP_Error); } if( pNC->ncFlags & NC_HasAgg ){ ExprSetProperty(pExpr, EP_Agg); - }else if( savedHasAgg ){ - pNC->ncFlags |= NC_HasAgg; } + pNC->ncFlags |= savedHasAgg; return ExprHasProperty(pExpr, EP_Error); } /* @@ -76063,11 +82387,11 @@ ** If pExpr is a column, a reference to a column via an 'AS' alias, ** or a sub-select with a column as the return value, then the ** affinity of that column is returned. Otherwise, 0x00 is returned, ** indicating no affinity for the expression. ** -** i.e. the WHERE clause expresssions in the following statements all +** i.e. the WHERE clause expressions in the following statements all ** have an affinity: ** ** CREATE TABLE t1(a); ** SELECT * FROM t1 WHERE a; ** SELECT a AS b FROM t1 WHERE b; @@ -76074,19 +82398,20 @@ ** SELECT * FROM t1 WHERE (select a from t1); */ SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){ int op; pExpr = sqlite3ExprSkipCollate(pExpr); + if( pExpr->flags & EP_Generic ) return 0; op = pExpr->op; if( op==TK_SELECT ){ assert( pExpr->flags&EP_xIsSelect ); return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr); } #ifndef SQLITE_OMIT_CAST if( op==TK_CAST ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); - return sqlite3AffinityType(pExpr->u.zToken); + return sqlite3AffinityType(pExpr->u.zToken, 0); } #endif if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_REGISTER) && pExpr->pTab!=0 ){ @@ -76106,13 +82431,18 @@ ** implements the COLLATE operator. ** ** If a memory allocation error occurs, that fact is recorded in pParse->db ** and the pExpr parameter is returned unchanged. */ -SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr *pExpr, Token *pCollName){ +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* Add the "COLLATE" clause to this expression */ + const Token *pCollName, /* Name of collating sequence */ + int dequote /* True to dequote pCollName */ +){ if( pCollName->n>0 ){ - Expr *pNew = sqlite3ExprAlloc(pParse->db, TK_COLLATE, pCollName, 1); + Expr *pNew = sqlite3ExprAlloc(pParse->db, TK_COLLATE, pCollName, dequote); if( pNew ){ pNew->pLeft = pExpr; pNew->flags |= EP_Collate|EP_Skip; pExpr = pNew; } @@ -76122,11 +82452,11 @@ SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, const char *zC){ Token s; assert( zC!=0 ); s.z = zC; s.n = sqlite3Strlen30(s.z); - return sqlite3ExprAddCollateToken(pParse, pExpr, &s); + return sqlite3ExprAddCollateToken(pParse, pExpr, &s, 0); } /* ** Skip over any TK_COLLATE or TK_AS operators and any unlikely() ** or likelihood() function at the root of an expression. @@ -76159,21 +82489,22 @@ sqlite3 *db = pParse->db; CollSeq *pColl = 0; Expr *p = pExpr; while( p ){ int op = p->op; + if( p->flags & EP_Generic ) break; if( op==TK_CAST || op==TK_UPLUS ){ p = p->pLeft; continue; } if( op==TK_COLLATE || (op==TK_REGISTER && p->op2==TK_COLLATE) ){ pColl = sqlite3GetCollSeq(pParse, ENC(db), 0, p->u.zToken); break; } - if( p->pTab!=0 - && (op==TK_AGG_COLUMN || op==TK_COLUMN + if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_REGISTER || op==TK_TRIGGER) + && p->pTab!=0 ){ /* op==TK_REGISTER && p->pTab!=0 happens when pExpr was originally ** a TK_COLUMN but was previously evaluated and cached in a register */ int j = p->iColumn; if( j>=0 ){ @@ -76181,14 +82512,29 @@ pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); } break; } if( p->flags & EP_Collate ){ - if( ALWAYS(p->pLeft) && (p->pLeft->flags & EP_Collate)!=0 ){ + if( p->pLeft && (p->pLeft->flags & EP_Collate)!=0 ){ p = p->pLeft; }else{ - p = p->pRight; + Expr *pNext = p->pRight; + /* The Expr.x union is never used at the same time as Expr.pRight */ + assert( p->x.pList==0 || p->pRight==0 ); + /* p->flags holds EP_Collate and p->pLeft->flags does not. And + ** p->x.pSelect cannot. So if p->x.pLeft exists, it must hold at + ** least one EP_Collate. Thus the following two ALWAYS. */ + if( p->x.pList!=0 && ALWAYS(!ExprHasProperty(p, EP_xIsSelect)) ){ + int i; + for(i=0; ALWAYS(i<p->x.pList->nExpr); i++){ + if( ExprHasProperty(p->x.pList->a[i].pExpr, EP_Collate) ){ + pNext = p->x.pList->a[i].pExpr; + break; + } + } + } + p = pNext; } }else{ break; } } @@ -76390,29 +82736,37 @@ ** Set the Expr.nHeight variable in the structure passed as an ** argument. An expression with no children, Expr.pList or ** Expr.pSelect member has a height of 1. Any other expression ** has a height equal to the maximum height of any other ** referenced Expr plus one. +** +** Also propagate EP_Propagate flags up from Expr.x.pList to Expr.flags, +** if appropriate. */ static void exprSetHeight(Expr *p){ int nHeight = 0; heightOfExpr(p->pLeft, &nHeight); heightOfExpr(p->pRight, &nHeight); if( ExprHasProperty(p, EP_xIsSelect) ){ heightOfSelect(p->x.pSelect, &nHeight); - }else{ + }else if( p->x.pList ){ heightOfExprList(p->x.pList, &nHeight); + p->flags |= EP_Propagate & sqlite3ExprListFlags(p->x.pList); } p->nHeight = nHeight + 1; } /* ** Set the Expr.nHeight variable using the exprSetHeight() function. If ** the height is greater than the maximum allowed expression depth, ** leave an error in pParse. +** +** Also propagate all EP_Propagate flags from the Expr.x.pList into +** Expr.flags. */ -SQLITE_PRIVATE void sqlite3ExprSetHeight(Parse *pParse, Expr *p){ +SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ + if( pParse->nErr ) return; exprSetHeight(p); sqlite3ExprCheckHeight(pParse, p->nHeight); } /* @@ -76422,12 +82776,21 @@ SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *p){ int nHeight = 0; heightOfSelect(p, &nHeight); return nHeight; } -#else - #define exprSetHeight(y) +#else /* ABOVE: Height enforcement enabled. BELOW: Height enforcement off */ +/* +** Propagate all EP_Propagate flags from the Expr.x.pList into +** Expr.flags. +*/ +SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ + if( p && p->x.pList && !ExprHasProperty(p, EP_xIsSelect) ){ + p->flags |= EP_Propagate & sqlite3ExprListFlags(p->x.pList); + } +} +#define exprSetHeight(y) #endif /* SQLITE_MAX_EXPR_DEPTH>0 */ /* ** This routine is the core allocator for Expr nodes. ** @@ -76525,22 +82888,22 @@ sqlite3ExprDelete(db, pLeft); sqlite3ExprDelete(db, pRight); }else{ if( pRight ){ pRoot->pRight = pRight; - pRoot->flags |= EP_Collate & pRight->flags; + pRoot->flags |= EP_Propagate & pRight->flags; } if( pLeft ){ pRoot->pLeft = pLeft; - pRoot->flags |= EP_Collate & pLeft->flags; + pRoot->flags |= EP_Propagate & pLeft->flags; } exprSetHeight(pRoot); } } /* -** Allocate a Expr node which joins as many as two subtrees. +** Allocate an Expr node which joins as many as two subtrees. ** ** One or both of the subtrees can be NULL. Return a pointer to the new ** Expr node. Or, if an OOM error occurs, set pParse->db->mallocFailed, ** free the subtrees and return NULL. */ @@ -76550,11 +82913,11 @@ Expr *pLeft, /* Left operand */ Expr *pRight, /* Right operand */ const Token *pToken /* Argument token */ ){ Expr *p; - if( op==TK_AND && pLeft && pRight ){ + if( op==TK_AND && pLeft && pRight && pParse->nErr==0 ){ /* Take advantage of short-circuit false optimization for AND */ p = sqlite3ExprAnd(pParse->db, pLeft, pRight); }else{ p = sqlite3ExprAlloc(pParse->db, op, pToken, 1); sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight); @@ -76564,20 +82927,29 @@ } return p; } /* -** Return 1 if an expression must be FALSE in all cases and 0 if the -** expression might be true. This is an optimization. If is OK to -** return 0 here even if the expression really is always false (a -** false negative). But it is a bug to return 1 if the expression -** might be true in some rare circumstances (a false positive.) +** If the expression is always either TRUE or FALSE (respectively), +** then return 1. If one cannot determine the truth value of the +** expression at compile-time return 0. +** +** This is an optimization. If is OK to return 0 here even if +** the expression really is always false or false (a false negative). +** But it is a bug to return 1 if the expression might have different +** boolean values in different circumstances (a false positive.) ** ** Note that if the expression is part of conditional for a ** LEFT JOIN, then we cannot determine at compile-time whether or not ** is it true or false, so always return 0. */ +static int exprAlwaysTrue(Expr *p){ + int v = 0; + if( ExprHasProperty(p, EP_FromJoin) ) return 0; + if( !sqlite3ExprIsInteger(p, &v) ) return 0; + return v!=0; +} static int exprAlwaysFalse(Expr *p){ int v = 0; if( ExprHasProperty(p, EP_FromJoin) ) return 0; if( !sqlite3ExprIsInteger(p, &v) ) return 0; return v==0; @@ -76620,11 +82992,11 @@ sqlite3ExprListDelete(db, pList); /* Avoid memory leak when malloc fails */ return 0; } pNew->x.pList = pList; assert( !ExprHasProperty(pNew, EP_xIsSelect) ); - sqlite3ExprSetHeight(pParse, pNew); + sqlite3ExprSetHeightAndFlags(pParse, pNew); return pNew; } /* ** Assign a variable number to an expression that encodes a wildcard @@ -76637,11 +83009,11 @@ ** sure "nnn" is not too be to avoid a denial of service attack when ** the SQL statement comes from an external source. ** ** Wildcards of the form ":aaa", "@aaa", or "$aaa" are assigned the same number ** as the previous instance of the same wildcard. Or if this is the first -** instance of the wildcard, the next sequenial variable number is +** instance of the wildcard, the next sequential variable number is ** assigned. */ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){ sqlite3 *db = pParse->db; const char *z; @@ -76772,11 +83144,11 @@ ** Note that with flags==EXPRDUP_REDUCE, this routines works on full-size ** (unreduced) Expr objects as they or originally constructed by the parser. ** During expression analysis, extra information is computed and moved into ** later parts of teh Expr object and that extra information might get chopped ** off if the expression is reduced. Note also that it does not work to -** make a EXPRDUP_REDUCE copy of a reduced expression. It is only legal +** make an EXPRDUP_REDUCE copy of a reduced expression. It is only legal ** to reduce a pristine expression tree from the parser. The implementation ** of dupedExprStructSize() contain multiple assert() statements that attempt ** to enforce this constraint. */ static int dupedExprStructSize(Expr *p, int flags){ @@ -76841,11 +83213,11 @@ /* ** This function is similar to sqlite3ExprDup(), except that if pzBuffer ** is not NULL then *pzBuffer is assumed to point to a buffer large enough ** to store the copy of expression p, the copies of p->u.zToken ** (if applicable), and the copies of the p->pLeft and p->pRight expressions, -** if any. Before returning, *pzBuffer is set to the first byte passed the +** if any. Before returning, *pzBuffer is set to the first byte past the ** portion of the buffer copied into by this function. */ static Expr *exprDup(sqlite3 *db, Expr *p, int flags, u8 **pzBuffer){ Expr *pNew = 0; /* Value to return */ if( p ){ @@ -76927,10 +83299,37 @@ } } return pNew; } +/* +** Create and return a deep copy of the object passed as the second +** argument. If an OOM condition is encountered, NULL is returned +** and the db->mallocFailed flag set. +*/ +#ifndef SQLITE_OMIT_CTE +static With *withDup(sqlite3 *db, With *p){ + With *pRet = 0; + if( p ){ + int nByte = sizeof(*p) + sizeof(p->a[0]) * (p->nCte-1); + pRet = sqlite3DbMallocZero(db, nByte); + if( pRet ){ + int i; + pRet->nCte = p->nCte; + for(i=0; i<p->nCte; i++){ + pRet->a[i].pSelect = sqlite3SelectDup(db, p->a[i].pSelect, 0); + pRet->a[i].pCols = sqlite3ExprListDup(db, p->a[i].pCols, 0); + pRet->a[i].zName = sqlite3DbStrDup(db, p->a[i].zName); + } + } + } + return pRet; +} +#else +# define withDup(x,y) 0 +#endif + /* ** The following group of routines make deep copies of expressions, ** expression lists, ID lists, and select statements. The copies can ** be deleted (by being passed to their respective ...Delete() routines) ** without effecting the originals. @@ -76954,11 +83353,10 @@ struct ExprList_item *pItem, *pOldItem; int i; if( p==0 ) return 0; pNew = sqlite3DbMallocRaw(db, sizeof(*pNew) ); if( pNew==0 ) return 0; - pNew->iECursor = 0; pNew->nExpr = i = p->nExpr; if( (flags & EXPRDUP_REDUCE)==0 ) for(i=1; i<p->nExpr; i+=i){} pNew->a = pItem = sqlite3DbMallocRaw(db, i*sizeof(p->a[0]) ); if( pItem==0 ){ sqlite3DbFree(db, pNew); @@ -76971,12 +83369,11 @@ pItem->zName = sqlite3DbStrDup(db, pOldItem->zName); pItem->zSpan = sqlite3DbStrDup(db, pOldItem->zSpan); pItem->sortOrder = pOldItem->sortOrder; pItem->done = 0; pItem->bSpanIsTab = pOldItem->bSpanIsTab; - pItem->iOrderByCol = pOldItem->iOrderByCol; - pItem->iAlias = pOldItem->iAlias; + pItem->u = pOldItem->u; } return pNew; } /* @@ -77008,10 +83405,11 @@ pNewItem->iCursor = pOldItem->iCursor; pNewItem->addrFillSub = pOldItem->addrFillSub; pNewItem->regReturn = pOldItem->regReturn; pNewItem->isCorrelated = pOldItem->isCorrelated; pNewItem->viaCoroutine = pOldItem->viaCoroutine; + pNewItem->isRecursive = pOldItem->isRecursive; pNewItem->zIndex = sqlite3DbStrDup(db, pOldItem->zIndex); pNewItem->notIndexed = pOldItem->notIndexed; pNewItem->pIndex = pOldItem->pIndex; pTab = pNewItem->pTab = pOldItem->pTab; if( pTab ){ @@ -77065,14 +83463,15 @@ pNew->pLimit = sqlite3ExprDup(db, p->pLimit, flags); pNew->pOffset = sqlite3ExprDup(db, p->pOffset, flags); pNew->iLimit = 0; pNew->iOffset = 0; pNew->selFlags = p->selFlags & ~SF_UsesEphemeral; - pNew->pRightmost = 0; pNew->addrOpenEphm[0] = -1; pNew->addrOpenEphm[1] = -1; - pNew->addrOpenEphm[2] = -1; + pNew->nSelectRow = p->nSelectRow; + pNew->pWith = withDup(db, p->pWith); + sqlite3SelectSetName(pNew, p->zSelName); return pNew; } #else SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){ assert( p==0 ); @@ -77209,102 +83608,163 @@ sqlite3DbFree(db, pList->a); sqlite3DbFree(db, pList); } /* -** These routines are Walker callbacks. Walker.u.pi is a pointer -** to an integer. These routines are checking an expression to see -** if it is a constant. Set *Walker.u.pi to 0 if the expression is -** not constant. +** Return the bitwise-OR of all Expr.flags fields in the given +** ExprList. +*/ +SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList *pList){ + int i; + u32 m = 0; + if( pList ){ + for(i=0; i<pList->nExpr; i++){ + m |= pList->a[i].pExpr->flags; + } + } + return m; +} + +/* +** These routines are Walker callbacks used to check expressions to +** see if they are "constant" for some definition of constant. The +** Walker.eCode value determines the type of "constant" we are looking +** for. ** ** These callback routines are used to implement the following: ** -** sqlite3ExprIsConstant() -** sqlite3ExprIsConstantNotJoin() -** sqlite3ExprIsConstantOrFunction() +** sqlite3ExprIsConstant() pWalker->eCode==1 +** sqlite3ExprIsConstantNotJoin() pWalker->eCode==2 +** sqlite3ExprRefOneTableOnly() pWalker->eCode==3 +** sqlite3ExprIsConstantOrFunction() pWalker->eCode==4 or 5 ** +** In all cases, the callbacks set Walker.eCode=0 and abort if the expression +** is found to not be a constant. +** +** The sqlite3ExprIsConstantOrFunction() is used for evaluating expressions +** in a CREATE TABLE statement. The Walker.eCode value is 5 when parsing +** an existing schema and 4 when processing a new statement. A bound +** parameter raises an error for new statements, but is silently converted +** to NULL for existing schemas. This allows sqlite_master tables that +** contain a bound parameter because they were generated by older versions +** of SQLite to be parsed by newer versions of SQLite without raising a +** malformed schema error. */ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ - /* If pWalker->u.i is 3 then any term of the expression that comes from - ** the ON or USING clauses of a join disqualifies the expression + /* If pWalker->eCode is 2 then any term of the expression that comes from + ** the ON or USING clauses of a left join disqualifies the expression ** from being considered constant. */ - if( pWalker->u.i==3 && ExprHasProperty(pExpr, EP_FromJoin) ){ - pWalker->u.i = 0; + if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_FromJoin) ){ + pWalker->eCode = 0; return WRC_Abort; } switch( pExpr->op ){ /* Consider functions to be constant if all their arguments are constant - ** and pWalker->u.i==2 */ + ** and either pWalker->eCode==4 or 5 or the function has the + ** SQLITE_FUNC_CONST flag. */ case TK_FUNCTION: - if( pWalker->u.i==2 ) return 0; - /* Fall through */ + if( pWalker->eCode>=4 || ExprHasProperty(pExpr,EP_ConstFunc) ){ + return WRC_Continue; + }else{ + pWalker->eCode = 0; + return WRC_Abort; + } case TK_ID: case TK_COLUMN: case TK_AGG_FUNCTION: case TK_AGG_COLUMN: testcase( pExpr->op==TK_ID ); testcase( pExpr->op==TK_COLUMN ); testcase( pExpr->op==TK_AGG_FUNCTION ); testcase( pExpr->op==TK_AGG_COLUMN ); - pWalker->u.i = 0; - return WRC_Abort; + if( pWalker->eCode==3 && pExpr->iTable==pWalker->u.iCur ){ + return WRC_Continue; + }else{ + pWalker->eCode = 0; + return WRC_Abort; + } + case TK_VARIABLE: + if( pWalker->eCode==5 ){ + /* Silently convert bound parameters that appear inside of CREATE + ** statements into a NULL when parsing the CREATE statement text out + ** of the sqlite_master table */ + pExpr->op = TK_NULL; + }else if( pWalker->eCode==4 ){ + /* A bound parameter in a CREATE statement that originates from + ** sqlite3_prepare() causes an error */ + pWalker->eCode = 0; + return WRC_Abort; + } + /* Fall through */ default: testcase( pExpr->op==TK_SELECT ); /* selectNodeIsConstant will disallow */ testcase( pExpr->op==TK_EXISTS ); /* selectNodeIsConstant will disallow */ return WRC_Continue; } } static int selectNodeIsConstant(Walker *pWalker, Select *NotUsed){ UNUSED_PARAMETER(NotUsed); - pWalker->u.i = 0; + pWalker->eCode = 0; return WRC_Abort; } -static int exprIsConst(Expr *p, int initFlag){ +static int exprIsConst(Expr *p, int initFlag, int iCur){ Walker w; memset(&w, 0, sizeof(w)); - w.u.i = initFlag; + w.eCode = initFlag; w.xExprCallback = exprNodeIsConstant; w.xSelectCallback = selectNodeIsConstant; + w.u.iCur = iCur; sqlite3WalkExpr(&w, p); - return w.u.i; + return w.eCode; } /* -** Walk an expression tree. Return 1 if the expression is constant +** Walk an expression tree. Return non-zero if the expression is constant ** and 0 if it involves variables or function calls. ** ** For the purposes of this function, a double-quoted string (ex: "abc") ** is considered a variable but a single-quoted string (ex: 'abc') is ** a constant. */ SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr *p){ - return exprIsConst(p, 1); + return exprIsConst(p, 1, 0); } /* -** Walk an expression tree. Return 1 if the expression is constant +** Walk an expression tree. Return non-zero if the expression is constant ** that does no originate from the ON or USING clauses of a join. ** Return 0 if it involves variables or function calls or terms from ** an ON or USING clause. */ SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){ - return exprIsConst(p, 3); + return exprIsConst(p, 2, 0); } /* -** Walk an expression tree. Return 1 if the expression is constant +** Walk an expression tree. Return non-zero if the expression constant +** for any single row of the table with cursor iCur. In other words, the +** expression must not refer to any non-deterministic function nor any +** table other than iCur. +*/ +SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr *p, int iCur){ + return exprIsConst(p, 3, iCur); +} + +/* +** Walk an expression tree. Return non-zero if the expression is constant ** or a function call with constant arguments. Return and 0 if there ** are any variables. ** ** For the purposes of this function, a double-quoted string (ex: "abc") ** is considered a variable but a single-quoted string (ex: 'abc') is ** a constant. */ -SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr *p){ - return exprIsConst(p, 2); +SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){ + assert( isInit==0 || isInit==1 ); + return exprIsConst(p, 4+isInit, 0); } /* ** If the expression p codes a constant integer that is small enough ** to fit in a 32-bit integer, return 1 and put the value of the integer @@ -77365,33 +83825,19 @@ case TK_INTEGER: case TK_STRING: case TK_FLOAT: case TK_BLOB: return 0; + case TK_COLUMN: + assert( p->pTab!=0 ); + return ExprHasProperty(p, EP_CanBeNull) || + (p->iColumn>=0 && p->pTab->aCol[p->iColumn].notNull==0); default: return 1; } } -/* -** Generate an OP_IsNull instruction that tests register iReg and jumps -** to location iDest if the value in iReg is NULL. The value in iReg -** was computed by pExpr. If we can look at pExpr at compile-time and -** determine that it can never generate a NULL, then the OP_IsNull operation -** can be omitted. -*/ -SQLITE_PRIVATE void sqlite3ExprCodeIsNullJump( - Vdbe *v, /* The VDBE under construction */ - const Expr *pExpr, /* Only generate OP_IsNull if this expr can be NULL */ - int iReg, /* Test the value in this register for NULL */ - int iDest /* Jump here if the value is null */ -){ - if( sqlite3ExprCanBeNull(pExpr) ){ - sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iDest); - } -} - /* ** Return TRUE if the given expression is a constant which would be ** unchanged by OP_Affinity with the affinity given in the second ** argument. ** @@ -77490,83 +83936,124 @@ SQLITE_PRIVATE int sqlite3CodeOnce(Parse *pParse){ Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */ return sqlite3VdbeAddOp1(v, OP_Once, pParse->nOnce++); } +/* +** Generate code that checks the left-most column of index table iCur to see if +** it contains any NULL entries. Cause the register at regHasNull to be set +** to a non-NULL value if iCur contains no NULLs. Cause register regHasNull +** to be set to NULL if iCur contains one or more NULL values. +*/ +static void sqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){ + int j1; + sqlite3VdbeAddOp2(v, OP_Integer, 0, regHasNull); + j1 = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_Column, iCur, 0, regHasNull); + sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); + VdbeComment((v, "first_entry_in(%d)", iCur)); + sqlite3VdbeJumpHere(v, j1); +} + + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** The argument is an IN operator with a list (not a subquery) on the +** right-hand side. Return TRUE if that list is constant. +*/ +static int sqlite3InRhsIsConstant(Expr *pIn){ + Expr *pLHS; + int res; + assert( !ExprHasProperty(pIn, EP_xIsSelect) ); + pLHS = pIn->pLeft; + pIn->pLeft = 0; + res = sqlite3ExprIsConstant(pIn); + pIn->pLeft = pLHS; + return res; +} +#endif + /* ** This function is used by the implementation of the IN (...) operator. ** The pX parameter is the expression on the RHS of the IN operator, which ** might be either a list of expressions or a subquery. ** ** The job of this routine is to find or create a b-tree object that can ** be used either to test for membership in the RHS set or to iterate through ** all members of the RHS set, skipping duplicates. ** -** A cursor is opened on the b-tree object that the RHS of the IN operator +** A cursor is opened on the b-tree object that is the RHS of the IN operator ** and pX->iTable is set to the index of that cursor. ** ** The returned value of this function indicates the b-tree type, as follows: ** ** IN_INDEX_ROWID - The cursor was opened on a database table. ** IN_INDEX_INDEX_ASC - The cursor was opened on an ascending index. ** IN_INDEX_INDEX_DESC - The cursor was opened on a descending index. ** IN_INDEX_EPH - The cursor was opened on a specially created and ** populated epheremal table. +** IN_INDEX_NOOP - No cursor was allocated. The IN operator must be +** implemented as a sequence of comparisons. ** ** An existing b-tree might be used if the RHS expression pX is a simple ** subquery such as: ** ** SELECT <column> FROM <table> ** ** If the RHS of the IN operator is a list or a more complex subquery, then ** an ephemeral table might need to be generated from the RHS and then -** pX->iTable made to point to the ephermeral table instead of an -** existing table. +** pX->iTable made to point to the ephemeral table instead of an +** existing table. ** -** If the prNotFound parameter is 0, then the b-tree will be used to iterate -** through the set members, skipping any duplicates. In this case an -** epheremal table must be used unless the selected <column> is guaranteed +** The inFlags parameter must contain exactly one of the bits +** IN_INDEX_MEMBERSHIP or IN_INDEX_LOOP. If inFlags contains +** IN_INDEX_MEMBERSHIP, then the generated table will be used for a +** fast membership test. When the IN_INDEX_LOOP bit is set, the +** IN index will be used to loop over all values of the RHS of the +** IN operator. +** +** When IN_INDEX_LOOP is used (and the b-tree will be used to iterate +** through the set members) then the b-tree must not contain duplicates. +** An epheremal table must be used unless the selected <column> is guaranteed ** to be unique - either because it is an INTEGER PRIMARY KEY or it ** has a UNIQUE constraint or UNIQUE index. ** -** If the prNotFound parameter is not 0, then the b-tree will be used -** for fast set membership tests. In this case an epheremal table must +** When IN_INDEX_MEMBERSHIP is used (and the b-tree will be used +** for fast set membership tests) then an epheremal table must ** be used unless <column> is an INTEGER PRIMARY KEY or an index can ** be found with <column> as its left-most column. +** +** If the IN_INDEX_NOOP_OK and IN_INDEX_MEMBERSHIP are both set and +** if the RHS of the IN operator is a list (not a subquery) then this +** routine might decide that creating an ephemeral b-tree for membership +** testing is too expensive and return IN_INDEX_NOOP. In that case, the +** calling routine should implement the IN operator using a sequence +** of Eq or Ne comparison operations. ** ** When the b-tree is being used for membership tests, the calling function -** needs to know whether or not the structure contains an SQL NULL -** value in order to correctly evaluate expressions like "X IN (Y, Z)". -** If there is any chance that the (...) might contain a NULL value at +** might need to know whether or not the RHS side of the IN operator +** contains a NULL. If prRhsHasNull is not a NULL pointer and +** if there is any chance that the (...) might contain a NULL value at ** runtime, then a register is allocated and the register number written -** to *prNotFound. If there is no chance that the (...) contains a -** NULL value, then *prNotFound is left unchanged. -** -** If a register is allocated and its location stored in *prNotFound, then -** its initial value is NULL. If the (...) does not remain constant -** for the duration of the query (i.e. the SELECT within the (...) -** is a correlated subquery) then the value of the allocated register is -** reset to NULL each time the subquery is rerun. This allows the -** caller to use vdbe code equivalent to the following: -** -** if( register==NULL ){ -** has_null = <test if data structure contains null> -** register = 1 -** } -** -** in order to avoid running the <test if data structure contains null> -** test more often than is necessary. +** to *prRhsHasNull. If there is no chance that the (...) contains a +** NULL value, then *prRhsHasNull is left unchanged. +** +** If a register is allocated and its location stored in *prRhsHasNull, then +** the value in that register will be NULL if the b-tree contains one or more +** NULL values, and it will be some non-NULL value if the b-tree contains no +** NULL values. */ #ifndef SQLITE_OMIT_SUBQUERY -SQLITE_PRIVATE int sqlite3FindInIndex(Parse *pParse, Expr *pX, int *prNotFound){ +SQLITE_PRIVATE int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){ Select *p; /* SELECT to the right of IN operator */ int eType = 0; /* Type of RHS table. IN_INDEX_* */ int iTab = pParse->nTab++; /* Cursor of the RHS table */ - int mustBeUnique = (prNotFound==0); /* True if RHS must be unique */ + int mustBeUnique; /* True if RHS must be unique */ Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */ assert( pX->op==TK_IN ); + mustBeUnique = (inFlags & IN_INDEX_LOOP)!=0; /* Check to see if an existing table or index can be used to ** satisfy the query. This is preferable to generating a new ** ephemeral table. */ @@ -77573,22 +84060,22 @@ p = (ExprHasProperty(pX, EP_xIsSelect) ? pX->x.pSelect : 0); if( ALWAYS(pParse->nErr==0) && isCandidateForInOpt(p) ){ sqlite3 *db = pParse->db; /* Database connection */ Table *pTab; /* Table <table>. */ Expr *pExpr; /* Expression <column> */ - int iCol; /* Index of column <column> */ - int iDb; /* Database idx for pTab */ + i16 iCol; /* Index of column <column> */ + i16 iDb; /* Database idx for pTab */ assert( p ); /* Because of isCandidateForInOpt(p) */ assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */ pTab = p->pSrc->a[0].pTab; pExpr = p->pEList->a[0].pExpr; - iCol = pExpr->iColumn; + iCol = (i16)pExpr->iColumn; - /* Code an OP_VerifyCookie and OP_TableLock for <table>. */ + /* Code an OP_Transaction and OP_TableLock for <table>. */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); sqlite3CodeVerifySchema(pParse, iDb); sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); /* This function is only called from two places. In both cases the vdbe @@ -77595,13 +84082,12 @@ ** has already been allocated. So assume sqlite3GetVdbe() is always ** successful here. */ assert(v); if( iCol<0 ){ - int iAddr; - - iAddr = sqlite3CodeOnce(pParse); + int iAddr = sqlite3CodeOnce(pParse); + VdbeCoverage(v); sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead); eType = IN_INDEX_ROWID; sqlite3VdbeJumpHere(v, iAddr); @@ -77620,50 +84106,59 @@ int affinity_ok = sqlite3IndexAffinityOk(pX, pTab->aCol[iCol].affinity); for(pIdx=pTab->pIndex; pIdx && eType==0 && affinity_ok; pIdx=pIdx->pNext){ if( (pIdx->aiColumn[0]==iCol) && sqlite3FindCollSeq(db, ENC(db), pIdx->azColl[0], 0)==pReq - && (!mustBeUnique || (pIdx->nColumn==1 && pIdx->onError!=OE_None)) + && (!mustBeUnique || (pIdx->nKeyCol==1 && IsUniqueIndex(pIdx))) ){ - int iAddr; - char *pKey; - - pKey = (char *)sqlite3IndexKeyinfo(pParse, pIdx); - iAddr = sqlite3CodeOnce(pParse); - - sqlite3VdbeAddOp4(v, OP_OpenRead, iTab, pIdx->tnum, iDb, - pKey,P4_KEYINFO_HANDOFF); + int iAddr = sqlite3CodeOnce(pParse); VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_OpenRead, iTab, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 ); eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0]; + if( prRhsHasNull && !pTab->aCol[iCol].notNull ){ + *prRhsHasNull = ++pParse->nMem; + sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull); + } sqlite3VdbeJumpHere(v, iAddr); - if( prNotFound && !pTab->aCol[iCol].notNull ){ - *prNotFound = ++pParse->nMem; - sqlite3VdbeAddOp2(v, OP_Null, 0, *prNotFound); - } } } } } + + /* If no preexisting index is available for the IN clause + ** and IN_INDEX_NOOP is an allowed reply + ** and the RHS of the IN operator is a list, not a subquery + ** and the RHS is not contant or has two or fewer terms, + ** then it is not worth creating an ephemeral table to evaluate + ** the IN operator so return IN_INDEX_NOOP. + */ + if( eType==0 + && (inFlags & IN_INDEX_NOOP_OK) + && !ExprHasProperty(pX, EP_xIsSelect) + && (!sqlite3InRhsIsConstant(pX) || pX->x.pList->nExpr<=2) + ){ + eType = IN_INDEX_NOOP; + } + if( eType==0 ){ - /* Could not found an existing table or index to use as the RHS b-tree. + /* Could not find an existing table or index to use as the RHS b-tree. ** We will have to generate an ephemeral table to do the job. */ u32 savedNQueryLoop = pParse->nQueryLoop; int rMayHaveNull = 0; eType = IN_INDEX_EPH; - if( prNotFound ){ - *prNotFound = rMayHaveNull = ++pParse->nMem; - sqlite3VdbeAddOp2(v, OP_Null, 0, *prNotFound); - }else{ - testcase( pParse->nQueryLoop>0 ); + if( inFlags & IN_INDEX_LOOP ){ pParse->nQueryLoop = 0; if( pX->pLeft->iColumn<0 && !ExprHasProperty(pX, EP_xIsSelect) ){ eType = IN_INDEX_ROWID; } + }else if( prRhsHasNull ){ + *prRhsHasNull = rMayHaveNull = ++pParse->nMem; } sqlite3CodeSubselect(pParse, pX, rMayHaveNull, eType==IN_INDEX_ROWID); pParse->nQueryLoop = savedNQueryLoop; }else{ pX->iTable = iTab; @@ -77690,31 +84185,25 @@ ** intkey B-Tree to store the set of IN(...) values instead of the usual ** (slower) variable length keys B-Tree. ** ** If rMayHaveNull is non-zero, that means that the operation is an IN ** (not a SELECT or EXISTS) and that the RHS might contains NULLs. -** Furthermore, the IN is in a WHERE clause and that we really want -** to iterate over the RHS of the IN operator in order to quickly locate -** all corresponding LHS elements. All this routine does is initialize -** the register given by rMayHaveNull to NULL. Calling routines will take -** care of changing this register value to non-NULL if the RHS is NULL-free. -** -** If rMayHaveNull is zero, that means that the subquery is being used -** for membership testing only. There is no need to initialize any -** registers to indicate the presence or absence of NULLs on the RHS. +** All this routine does is initialize the register given by rMayHaveNull +** to NULL. Calling routines will take care of changing this register +** value to non-NULL if the RHS is NULL-free. ** ** For a SELECT or EXISTS operator, return the register that holds the ** result. For IN operators or if an error occurs, the return value is 0. */ #ifndef SQLITE_OMIT_SUBQUERY SQLITE_PRIVATE int sqlite3CodeSubselect( Parse *pParse, /* Parsing context */ Expr *pExpr, /* The IN, SELECT, or EXISTS operator */ - int rMayHaveNull, /* Register that records whether NULLs exist in RHS */ + int rHasNullFlag, /* Register that records whether NULLs exist in RHS */ int isRowid /* If true, LHS of IN operator is a rowid */ ){ - int testAddr = -1; /* One-time test address */ + int jmpIfDynamic = -1; /* One-time test address */ int rReg = 0; /* Register storing resulting */ Vdbe *v = sqlite3GetVdbe(pParse); if( NEVER(v==0) ) return 0; sqlite3ExprCachePush(pParse); @@ -77727,17 +84216,17 @@ ** ** If all of the above are false, then we can run this code just once ** save the results, and reuse the same result on subsequent invocations. */ if( !ExprHasProperty(pExpr, EP_VarSelect) ){ - testAddr = sqlite3CodeOnce(pParse); + jmpIfDynamic = sqlite3CodeOnce(pParse); VdbeCoverage(v); } #ifndef SQLITE_OMIT_EXPLAIN if( pParse->explain==2 ){ char *zMsg = sqlite3MPrintf( - pParse->db, "EXECUTE %s%s SUBQUERY %d", testAddr>=0?"":"CORRELATED ", + pParse->db, "EXECUTE %s%s SUBQUERY %d", jmpIfDynamic>=0?"":"CORRELATED ", pExpr->op==TK_IN?"LIST":"SCALAR", pParse->iNextSelectId ); sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC); } #endif @@ -77747,14 +84236,10 @@ char affinity; /* Affinity of the LHS of the IN */ int addr; /* Address of OP_OpenEphemeral instruction */ Expr *pLeft = pExpr->pLeft; /* the LHS of the IN operator */ KeyInfo *pKeyInfo = 0; /* Key information */ - if( rMayHaveNull ){ - sqlite3VdbeAddOp2(v, OP_Null, 0, rMayHaveNull); - } - affinity = sqlite3ExprAffinity(pLeft); /* Whether this is an 'x IN(SELECT...)' or an 'x IN(<exprlist>)' ** expression it is handled the same way. An ephemeral table is ** filled with single-field index keys representing the results @@ -77768,36 +84253,38 @@ ** 'x' nor the SELECT... statement are columns, then numeric affinity ** is used. */ pExpr->iTable = pParse->nTab++; addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, !isRowid); - if( rMayHaveNull==0 ) sqlite3VdbeChangeP5(v, BTREE_UNORDERED); - pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, 1); + pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, 1, 1); if( ExprHasProperty(pExpr, EP_xIsSelect) ){ /* Case 1: expr IN (SELECT ...) ** ** Generate code to write the results of the select into the temporary ** table allocated and opened above. */ + Select *pSelect = pExpr->x.pSelect; SelectDest dest; ExprList *pEList; assert( !isRowid ); sqlite3SelectDestInit(&dest, SRT_Set, pExpr->iTable); dest.affSdst = (u8)affinity; assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable ); - pExpr->x.pSelect->iLimit = 0; + pSelect->iLimit = 0; + testcase( pSelect->selFlags & SF_Distinct ); testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ - if( sqlite3Select(pParse, pExpr->x.pSelect, &dest) ){ - sqlite3DbFree(pParse->db, pKeyInfo); + if( sqlite3Select(pParse, pSelect, &dest) ){ + sqlite3KeyInfoUnref(pKeyInfo); return 0; } - pEList = pExpr->x.pSelect->pEList; + pEList = pSelect->pEList; assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ assert( pEList!=0 ); assert( pEList->nExpr>0 ); + assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); pKeyInfo->aColl[0] = sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, pEList->a[0].pExpr); }else if( ALWAYS(pExpr->x.pList!=0) ){ /* Case 2: expr IN (exprlist) ** @@ -77813,29 +84300,30 @@ if( !affinity ){ affinity = SQLITE_AFF_NONE; } if( pKeyInfo ){ + assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); pKeyInfo->aColl[0] = sqlite3ExprCollSeq(pParse, pExpr->pLeft); } /* Loop through each expression in <exprlist>. */ r1 = sqlite3GetTempReg(pParse); r2 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp2(v, OP_Null, 0, r2); + if( isRowid ) sqlite3VdbeAddOp2(v, OP_Null, 0, r2); for(i=pList->nExpr, pItem=pList->a; i>0; i--, pItem++){ Expr *pE2 = pItem->pExpr; int iValToIns; /* If the expression is not constant then we will need to ** disable the test that was generated above that makes sure ** this code only executes once. Because for a non-constant ** expression we need to rerun this code each time. */ - if( testAddr>=0 && !sqlite3ExprIsConstant(pE2) ){ - sqlite3VdbeChangeToNoop(v, testAddr); - testAddr = -1; + if( jmpIfDynamic>=0 && !sqlite3ExprIsConstant(pE2) ){ + sqlite3VdbeChangeToNoop(v, jmpIfDynamic); + jmpIfDynamic = -1; } /* Evaluate the expression and insert it into the temp table */ if( isRowid && sqlite3ExprIsInteger(pE2, &iValToIns) ){ sqlite3VdbeAddOp3(v, OP_InsertInt, pExpr->iTable, r2, iValToIns); @@ -77842,10 +84330,11 @@ }else{ r3 = sqlite3ExprCodeTarget(pParse, pE2, r1); if( isRowid ){ sqlite3VdbeAddOp2(v, OP_MustBeInt, r3, sqlite3VdbeCurrentAddr(v)+2); + VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_Insert, pExpr->iTable, r2, r3); }else{ sqlite3VdbeAddOp4(v, OP_MakeRecord, r3, 1, r2, &affinity, 1); sqlite3ExprCacheAffinityChange(pParse, r3, 1); sqlite3VdbeAddOp2(v, OP_IdxInsert, pExpr->iTable, r2); @@ -77854,11 +84343,11 @@ } sqlite3ReleaseTempReg(pParse, r1); sqlite3ReleaseTempReg(pParse, r2); } if( pKeyInfo ){ - sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO_HANDOFF); + sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO); } break; } case TK_EXISTS: @@ -77880,10 +84369,11 @@ assert( ExprHasProperty(pExpr, EP_xIsSelect) ); pSel = pExpr->x.pSelect; sqlite3SelectDestInit(&dest, 0, ++pParse->nMem); if( pExpr->op==TK_SELECT ){ dest.eDest = SRT_Mem; + dest.iSdst = dest.iSDParm; sqlite3VdbeAddOp2(v, OP_Null, 0, dest.iSDParm); VdbeComment((v, "Init subquery result")); }else{ dest.eDest = SRT_Exists; sqlite3VdbeAddOp2(v, OP_Integer, 0, dest.iSDParm); @@ -77900,14 +84390,18 @@ ExprSetVVAProperty(pExpr, EP_NoReduce); break; } } - if( testAddr>=0 ){ - sqlite3VdbeJumpHere(v, testAddr); + if( rHasNullFlag ){ + sqlite3SetHasNullFlag(v, pExpr->iTable, rHasNullFlag); } - sqlite3ExprCachePop(pParse, 1); + + if( jmpIfDynamic>=0 ){ + sqlite3VdbeJumpHere(v, jmpIfDynamic); + } + sqlite3ExprCachePop(pParse); return rReg; } #endif /* SQLITE_OMIT_SUBQUERY */ @@ -77922,11 +84416,11 @@ ** is an array of zero or more values. The expression is true if the LHS is ** contained within the RHS. The value of the expression is unknown (NULL) ** if the LHS is NULL or if the LHS is not contained within the RHS and the ** RHS contains one or more NULL values. ** -** This routine generates code will jump to destIfFalse if the LHS is not +** This routine generates code that jumps to destIfFalse if the LHS is not ** contained within the RHS. If due to NULLs we cannot determine if the LHS ** is contained in the RHS then jump to destIfNull. If the LHS is contained ** within the RHS then fall through. */ static void sqlite3ExprCodeIN( @@ -77945,11 +84439,13 @@ ** pExpr->iTable will contains the values that make up the RHS. */ v = pParse->pVdbe; assert( v!=0 ); /* OOM detected prior to this routine */ VdbeNoopComment((v, "begin IN expr")); - eType = sqlite3FindInIndex(pParse, pExpr, &rRhsHasNull); + eType = sqlite3FindInIndex(pParse, pExpr, + IN_INDEX_MEMBERSHIP | IN_INDEX_NOOP_OK, + destIfFalse==destIfNull ? 0 : &rRhsHasNull); /* Figure out the affinity to use to create a key from the results ** of the expression. affinityStr stores a static string suitable for ** P4 of OP_MakeRecord. */ @@ -77959,90 +84455,122 @@ */ sqlite3ExprCachePush(pParse); r1 = sqlite3GetTempReg(pParse); sqlite3ExprCode(pParse, pExpr->pLeft, r1); - /* If the LHS is NULL, then the result is either false or NULL depending - ** on whether the RHS is empty or not, respectively. - */ - if( destIfNull==destIfFalse ){ - /* Shortcut for the common case where the false and NULL outcomes are - ** the same. */ - sqlite3VdbeAddOp2(v, OP_IsNull, r1, destIfNull); - }else{ - int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, r1); - sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse); - sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfNull); - sqlite3VdbeJumpHere(v, addr1); - } - - if( eType==IN_INDEX_ROWID ){ - /* In this case, the RHS is the ROWID of table b-tree - */ - sqlite3VdbeAddOp2(v, OP_MustBeInt, r1, destIfFalse); - sqlite3VdbeAddOp3(v, OP_NotExists, pExpr->iTable, destIfFalse, r1); - }else{ - /* In this case, the RHS is an index b-tree. - */ - sqlite3VdbeAddOp4(v, OP_Affinity, r1, 1, 0, &affinity, 1); - - /* If the set membership test fails, then the result of the - ** "x IN (...)" expression must be either 0 or NULL. If the set - ** contains no NULL values, then the result is 0. If the set - ** contains one or more NULL values, then the result of the - ** expression is also NULL. - */ - if( rRhsHasNull==0 || destIfFalse==destIfNull ){ - /* This branch runs if it is known at compile time that the RHS - ** cannot contain NULL values. This happens as the result - ** of a "NOT NULL" constraint in the database schema. - ** - ** Also run this branch if NULL is equivalent to FALSE - ** for this particular IN operator. - */ - sqlite3VdbeAddOp4Int(v, OP_NotFound, pExpr->iTable, destIfFalse, r1, 1); - - }else{ - /* In this branch, the RHS of the IN might contain a NULL and - ** the presence of a NULL on the RHS makes a difference in the - ** outcome. - */ - int j1, j2, j3; - - /* First check to see if the LHS is contained in the RHS. If so, - ** then the presence of NULLs in the RHS does not matter, so jump - ** over all of the code that follows. - */ - j1 = sqlite3VdbeAddOp4Int(v, OP_Found, pExpr->iTable, 0, r1, 1); - - /* Here we begin generating code that runs if the LHS is not - ** contained within the RHS. Generate additional code that - ** tests the RHS for NULLs. If the RHS contains a NULL then - ** jump to destIfNull. If there are no NULLs in the RHS then - ** jump to destIfFalse. - */ - j2 = sqlite3VdbeAddOp1(v, OP_NotNull, rRhsHasNull); - j3 = sqlite3VdbeAddOp4Int(v, OP_Found, pExpr->iTable, 0, rRhsHasNull, 1); - sqlite3VdbeAddOp2(v, OP_Integer, -1, rRhsHasNull); - sqlite3VdbeJumpHere(v, j3); - sqlite3VdbeAddOp2(v, OP_AddImm, rRhsHasNull, 1); - sqlite3VdbeJumpHere(v, j2); - - /* Jump to the appropriate target depending on whether or not - ** the RHS contains a NULL - */ - sqlite3VdbeAddOp2(v, OP_If, rRhsHasNull, destIfNull); + /* If sqlite3FindInIndex() did not find or create an index that is + ** suitable for evaluating the IN operator, then evaluate using a + ** sequence of comparisons. + */ + if( eType==IN_INDEX_NOOP ){ + ExprList *pList = pExpr->x.pList; + CollSeq *pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); + int labelOk = sqlite3VdbeMakeLabel(v); + int r2, regToFree; + int regCkNull = 0; + int ii; + assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + if( destIfNull!=destIfFalse ){ + regCkNull = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_BitAnd, r1, r1, regCkNull); + } + for(ii=0; ii<pList->nExpr; ii++){ + r2 = sqlite3ExprCodeTemp(pParse, pList->a[ii].pExpr, ®ToFree); + if( regCkNull && sqlite3ExprCanBeNull(pList->a[ii].pExpr) ){ + sqlite3VdbeAddOp3(v, OP_BitAnd, regCkNull, r2, regCkNull); + } + if( ii<pList->nExpr-1 || destIfNull!=destIfFalse ){ + sqlite3VdbeAddOp4(v, OP_Eq, r1, labelOk, r2, + (void*)pColl, P4_COLLSEQ); + VdbeCoverageIf(v, ii<pList->nExpr-1); + VdbeCoverageIf(v, ii==pList->nExpr-1); + sqlite3VdbeChangeP5(v, affinity); + }else{ + assert( destIfNull==destIfFalse ); + sqlite3VdbeAddOp4(v, OP_Ne, r1, destIfFalse, r2, + (void*)pColl, P4_COLLSEQ); VdbeCoverage(v); + sqlite3VdbeChangeP5(v, affinity | SQLITE_JUMPIFNULL); + } + sqlite3ReleaseTempReg(pParse, regToFree); + } + if( regCkNull ){ + sqlite3VdbeAddOp2(v, OP_IsNull, regCkNull, destIfNull); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse); - - /* The OP_Found at the top of this branch jumps here when true, - ** causing the overall IN expression evaluation to fall through. + } + sqlite3VdbeResolveLabel(v, labelOk); + sqlite3ReleaseTempReg(pParse, regCkNull); + }else{ + + /* If the LHS is NULL, then the result is either false or NULL depending + ** on whether the RHS is empty or not, respectively. + */ + if( sqlite3ExprCanBeNull(pExpr->pLeft) ){ + if( destIfNull==destIfFalse ){ + /* Shortcut for the common case where the false and NULL outcomes are + ** the same. */ + sqlite3VdbeAddOp2(v, OP_IsNull, r1, destIfNull); VdbeCoverage(v); + }else{ + int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, r1); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse); + VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfNull); + sqlite3VdbeJumpHere(v, addr1); + } + } + + if( eType==IN_INDEX_ROWID ){ + /* In this case, the RHS is the ROWID of table b-tree */ - sqlite3VdbeJumpHere(v, j1); + sqlite3VdbeAddOp2(v, OP_MustBeInt, r1, destIfFalse); VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_NotExists, pExpr->iTable, destIfFalse, r1); + VdbeCoverage(v); + }else{ + /* In this case, the RHS is an index b-tree. + */ + sqlite3VdbeAddOp4(v, OP_Affinity, r1, 1, 0, &affinity, 1); + + /* If the set membership test fails, then the result of the + ** "x IN (...)" expression must be either 0 or NULL. If the set + ** contains no NULL values, then the result is 0. If the set + ** contains one or more NULL values, then the result of the + ** expression is also NULL. + */ + assert( destIfFalse!=destIfNull || rRhsHasNull==0 ); + if( rRhsHasNull==0 ){ + /* This branch runs if it is known at compile time that the RHS + ** cannot contain NULL values. This happens as the result + ** of a "NOT NULL" constraint in the database schema. + ** + ** Also run this branch if NULL is equivalent to FALSE + ** for this particular IN operator. + */ + sqlite3VdbeAddOp4Int(v, OP_NotFound, pExpr->iTable, destIfFalse, r1, 1); + VdbeCoverage(v); + }else{ + /* In this branch, the RHS of the IN might contain a NULL and + ** the presence of a NULL on the RHS makes a difference in the + ** outcome. + */ + int j1; + + /* First check to see if the LHS is contained in the RHS. If so, + ** then the answer is TRUE the presence of NULLs in the RHS does + ** not matter. If the LHS is not contained in the RHS, then the + ** answer is NULL if the RHS contains NULLs and the answer is + ** FALSE if the RHS is NULL-free. + */ + j1 = sqlite3VdbeAddOp4Int(v, OP_Found, pExpr->iTable, 0, r1, 1); + VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_IsNull, rRhsHasNull, destIfNull); + VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse); + sqlite3VdbeJumpHere(v, j1); + } } } sqlite3ReleaseTempReg(pParse, r1); - sqlite3ExprCachePop(pParse, 1); + sqlite3ExprCachePop(pParse); VdbeComment((v, "end IN expr")); } #endif /* SQLITE_OMIT_SUBQUERY */ /* @@ -78095,21 +84623,28 @@ }else{ int c; i64 value; const char *z = pExpr->u.zToken; assert( z!=0 ); - c = sqlite3Atoi64(z, &value, sqlite3Strlen30(z), SQLITE_UTF8); + c = sqlite3DecOrHexToI64(z, &value); if( c==0 || (c==2 && negFlag) ){ char *zV; if( negFlag ){ value = c==2 ? SMALLEST_INT64 : -value; } zV = dup8bytes(v, (char*)&value); sqlite3VdbeAddOp4(v, OP_Int64, 0, iMem, 0, zV, P4_INT64); }else{ #ifdef SQLITE_OMIT_FLOATING_POINT sqlite3ErrorMsg(pParse, "oversized integer: %s%s", negFlag ? "-" : "", z); #else - codeReal(v, z, negFlag, iMem); +#ifndef SQLITE_OMIT_HEX_INTEGER + if( sqlite3_strnicmp(z,"0x",2)==0 ){ + sqlite3ErrorMsg(pParse, "hex literal too big: %s", z); + }else +#endif + { + codeReal(v, z, negFlag, iMem); + } #endif } } } @@ -78134,11 +84669,12 @@ int i; int minLru; int idxLru; struct yColCache *p; - assert( iReg>0 ); /* Register numbers are always positive */ + /* Unless an error has occurred, register numbers are always positive. */ + assert( iReg>0 || pParse->nErr || pParse->db->mallocFailed ); assert( iCol>=-1 && iCol<32768 ); /* Finite column numbers */ /* The SQLITE_ColumnCache flag disables the column cache. This is used ** for testing only - to verify that SQLite always gets the same answer ** with and without the column cache. @@ -78212,23 +84748,32 @@ ** added to the column cache after this call are removed when the ** corresponding pop occurs. */ SQLITE_PRIVATE void sqlite3ExprCachePush(Parse *pParse){ pParse->iCacheLevel++; +#ifdef SQLITE_DEBUG + if( pParse->db->flags & SQLITE_VdbeAddopTrace ){ + printf("PUSH to %d\n", pParse->iCacheLevel); + } +#endif } /* ** Remove from the column cache any entries that were added since the -** the previous N Push operations. In other words, restore the cache -** to the state it was in N Pushes ago. +** the previous sqlite3ExprCachePush operation. In other words, restore +** the cache to the state it was in prior the most recent Push. */ -SQLITE_PRIVATE void sqlite3ExprCachePop(Parse *pParse, int N){ +SQLITE_PRIVATE void sqlite3ExprCachePop(Parse *pParse){ int i; struct yColCache *p; - assert( N>0 ); - assert( pParse->iCacheLevel>=N ); - pParse->iCacheLevel -= N; + assert( pParse->iCacheLevel>=1 ); + pParse->iCacheLevel--; +#ifdef SQLITE_DEBUG + if( pParse->db->flags & SQLITE_VdbeAddopTrace ){ + printf("POP to %d\n", pParse->iCacheLevel); + } +#endif for(i=0, p=pParse->aColCache; i<SQLITE_N_COLCACHE; i++, p++){ if( p->iReg && p->iLevel>pParse->iCacheLevel ){ cacheEntryClear(pParse, p); p->iReg = 0; } @@ -78255,19 +84800,23 @@ ** Generate code to extract the value of the iCol-th column of a table. */ SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable( Vdbe *v, /* The VDBE under construction */ Table *pTab, /* The table containing the value */ - int iTabCur, /* The cursor for this table */ + int iTabCur, /* The table cursor. Or the PK cursor for WITHOUT ROWID */ int iCol, /* Index of the column to extract */ - int regOut /* Extract the valud into this register */ + int regOut /* Extract the value into this register */ ){ if( iCol<0 || iCol==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut); }else{ int op = IsVirtual(pTab) ? OP_VColumn : OP_Column; - sqlite3VdbeAddOp3(v, op, iTabCur, iCol, regOut); + int x = iCol; + if( !HasRowid(pTab) ){ + x = sqlite3ColumnOfIndex(sqlite3PrimaryKeyIndex(pTab), iCol); + } + sqlite3VdbeAddOp3(v, op, iTabCur, x, regOut); } if( iCol>=0 ){ sqlite3ColumnDefault(v, pTab, iCol, regOut); } } @@ -78315,10 +84864,15 @@ */ SQLITE_PRIVATE void sqlite3ExprCacheClear(Parse *pParse){ int i; struct yColCache *p; +#if SQLITE_DEBUG + if( pParse->db->flags & SQLITE_VdbeAddopTrace ){ + printf("CLEAR\n"); + } +#endif for(i=0, p=pParse->aColCache; i<SQLITE_N_COLCACHE; i++, p++){ if( p->iReg ){ cacheEntryClear(pParse, p); p->iReg = 0; } @@ -78336,20 +84890,13 @@ /* ** Generate code to move content from registers iFrom...iFrom+nReg-1 ** over to iTo..iTo+nReg-1. Keep the column cache up-to-date. */ SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse *pParse, int iFrom, int iTo, int nReg){ - int i; - struct yColCache *p; assert( iFrom>=iTo+nReg || iFrom+nReg<=iTo ); - sqlite3VdbeAddOp3(pParse->pVdbe, OP_Move, iFrom, iTo, nReg-1); - for(i=0, p=pParse->aColCache; i<SQLITE_N_COLCACHE; i++, p++){ - int x = p->iReg; - if( x>=iFrom && x<iFrom+nReg ){ - p->iReg += iTo-iFrom; - } - } + sqlite3VdbeAddOp3(pParse->pVdbe, OP_Move, iFrom, iTo, nReg); + sqlite3ExprCacheRemove(pParse, iFrom, nReg); } #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) /* ** Return true if any register in the range iFrom..iTo (inclusive) @@ -78396,10 +84943,11 @@ int inReg = target; /* Results stored in register inReg */ int regFree1 = 0; /* If non-zero free this temporary register */ int regFree2 = 0; /* If non-zero free this temporary register */ int r1, r2, r3, r4; /* Various register numbers */ sqlite3 *db = pParse->db; /* The database connection */ + Expr tempX; /* Temporary expression node */ assert( target>0 && target<=pParse->nMem ); if( v==0 ){ assert( pParse->db->mallocFailed ); return 0; @@ -78499,30 +85047,17 @@ break; } #ifndef SQLITE_OMIT_CAST case TK_CAST: { /* Expressions of the form: CAST(pLeft AS token) */ - int aff, to_op; inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); - assert( !ExprHasProperty(pExpr, EP_IntValue) ); - aff = sqlite3AffinityType(pExpr->u.zToken); - to_op = aff - SQLITE_AFF_TEXT + OP_ToText; - assert( to_op==OP_ToText || aff!=SQLITE_AFF_TEXT ); - assert( to_op==OP_ToBlob || aff!=SQLITE_AFF_NONE ); - assert( to_op==OP_ToNumeric || aff!=SQLITE_AFF_NUMERIC ); - assert( to_op==OP_ToInt || aff!=SQLITE_AFF_INTEGER ); - assert( to_op==OP_ToReal || aff!=SQLITE_AFF_REAL ); - testcase( to_op==OP_ToText ); - testcase( to_op==OP_ToBlob ); - testcase( to_op==OP_ToNumeric ); - testcase( to_op==OP_ToInt ); - testcase( to_op==OP_ToReal ); if( inReg!=target ){ sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target); inReg = target; } - sqlite3VdbeAddOp1(v, to_op, inReg); + sqlite3VdbeAddOp2(v, OP_Cast, target, + sqlite3AffinityType(pExpr->u.zToken, 0)); testcase( usedAsColumnCache(pParse, inReg, inReg) ); sqlite3ExprCacheAffinityChange(pParse, inReg, 1); break; } #endif /* SQLITE_OMIT_CAST */ @@ -78530,26 +85065,20 @@ case TK_LE: case TK_GT: case TK_GE: case TK_NE: case TK_EQ: { - assert( TK_LT==OP_Lt ); - assert( TK_LE==OP_Le ); - assert( TK_GT==OP_Gt ); - assert( TK_GE==OP_Ge ); - assert( TK_EQ==OP_Eq ); - assert( TK_NE==OP_Ne ); - testcase( op==TK_LT ); - testcase( op==TK_LE ); - testcase( op==TK_GT ); - testcase( op==TK_GE ); - testcase( op==TK_EQ ); - testcase( op==TK_NE ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, r1, r2, inReg, SQLITE_STOREP2); + assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); + assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); testcase( regFree1==0 ); testcase( regFree2==0 ); break; } case TK_IS: @@ -78559,10 +85088,12 @@ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); op = (op==TK_IS) ? TK_EQ : TK_NE; codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, r1, r2, inReg, SQLITE_STOREP2 | SQLITE_NULLEQ); + VdbeCoverageIf(v, op==TK_EQ); + VdbeCoverageIf(v, op==TK_NE); testcase( regFree1==0 ); testcase( regFree2==0 ); break; } case TK_AND: @@ -78575,32 +85106,21 @@ case TK_BITOR: case TK_SLASH: case TK_LSHIFT: case TK_RSHIFT: case TK_CONCAT: { - assert( TK_AND==OP_And ); - assert( TK_OR==OP_Or ); - assert( TK_PLUS==OP_Add ); - assert( TK_MINUS==OP_Subtract ); - assert( TK_REM==OP_Remainder ); - assert( TK_BITAND==OP_BitAnd ); - assert( TK_BITOR==OP_BitOr ); - assert( TK_SLASH==OP_Divide ); - assert( TK_LSHIFT==OP_ShiftLeft ); - assert( TK_RSHIFT==OP_ShiftRight ); - assert( TK_CONCAT==OP_Concat ); - testcase( op==TK_AND ); - testcase( op==TK_OR ); - testcase( op==TK_PLUS ); - testcase( op==TK_MINUS ); - testcase( op==TK_REM ); - testcase( op==TK_BITAND ); - testcase( op==TK_BITOR ); - testcase( op==TK_SLASH ); - testcase( op==TK_LSHIFT ); - testcase( op==TK_RSHIFT ); - testcase( op==TK_CONCAT ); + assert( TK_AND==OP_And ); testcase( op==TK_AND ); + assert( TK_OR==OP_Or ); testcase( op==TK_OR ); + assert( TK_PLUS==OP_Add ); testcase( op==TK_PLUS ); + assert( TK_MINUS==OP_Subtract ); testcase( op==TK_MINUS ); + assert( TK_REM==OP_Remainder ); testcase( op==TK_REM ); + assert( TK_BITAND==OP_BitAnd ); testcase( op==TK_BITAND ); + assert( TK_BITOR==OP_BitOr ); testcase( op==TK_BITOR ); + assert( TK_SLASH==OP_Divide ); testcase( op==TK_SLASH ); + assert( TK_LSHIFT==OP_ShiftLeft ); testcase( op==TK_LSHIFT ); + assert( TK_RSHIFT==OP_ShiftRight ); testcase( op==TK_RSHIFT ); + assert( TK_CONCAT==OP_Concat ); testcase( op==TK_CONCAT ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); sqlite3VdbeAddOp3(v, op, r2, r1, target); testcase( regFree1==0 ); testcase( regFree2==0 ); @@ -78615,43 +85135,43 @@ }else if( pLeft->op==TK_FLOAT ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); codeReal(v, pLeft->u.zToken, 1, target); #endif }else{ - regFree1 = r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp2(v, OP_Integer, 0, r1); + tempX.op = TK_INTEGER; + tempX.flags = EP_IntValue|EP_TokenOnly; + tempX.u.iValue = 0; + r1 = sqlite3ExprCodeTemp(pParse, &tempX, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free2); sqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target); testcase( regFree2==0 ); } inReg = target; break; } case TK_BITNOT: case TK_NOT: { - assert( TK_BITNOT==OP_BitNot ); - assert( TK_NOT==OP_Not ); - testcase( op==TK_BITNOT ); - testcase( op==TK_NOT ); + assert( TK_BITNOT==OP_BitNot ); testcase( op==TK_BITNOT ); + assert( TK_NOT==OP_Not ); testcase( op==TK_NOT ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); testcase( regFree1==0 ); inReg = target; sqlite3VdbeAddOp2(v, op, r1, inReg); break; } case TK_ISNULL: case TK_NOTNULL: { int addr; - assert( TK_ISNULL==OP_IsNull ); - assert( TK_NOTNULL==OP_NotNull ); - testcase( op==TK_ISNULL ); - testcase( op==TK_NOTNULL ); + assert( TK_ISNULL==OP_IsNull ); testcase( op==TK_ISNULL ); + assert( TK_NOTNULL==OP_NotNull ); testcase( op==TK_NOTNULL ); sqlite3VdbeAddOp2(v, OP_Integer, 1, target); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); testcase( regFree1==0 ); addr = sqlite3VdbeAddOp1(v, op, r1); - sqlite3VdbeAddOp2(v, OP_AddImm, target, -1); + VdbeCoverageIf(v, op==TK_ISNULL); + VdbeCoverageIf(v, op==TK_NOTNULL); + sqlite3VdbeAddOp2(v, OP_Integer, 0, target); sqlite3VdbeJumpHere(v, addr); break; } case TK_AGG_FUNCTION: { AggInfo *pInfo = pExpr->pAggInfo; @@ -78661,25 +85181,22 @@ }else{ inReg = pInfo->aFunc[pExpr->iAgg].iMem; } break; } - case TK_CONST_FUNC: case TK_FUNCTION: { ExprList *pFarg; /* List of function arguments */ int nFarg; /* Number of function arguments */ FuncDef *pDef; /* The function definition object */ int nId; /* Length of the function name in bytes */ const char *zId; /* The function name */ - int constMask = 0; /* Mask of function arguments that are constant */ + u32 constMask = 0; /* Mask of function arguments that are constant */ int i; /* Loop counter */ u8 enc = ENC(db); /* The text encoding used by this database */ CollSeq *pColl = 0; /* A collating sequence */ assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); - testcase( op==TK_CONST_FUNC ); - testcase( op==TK_FUNCTION ); if( ExprHasProperty(pExpr, EP_TokenOnly) ){ pFarg = 0; }else{ pFarg = pExpr->x.pList; } @@ -78686,29 +85203,30 @@ nFarg = pFarg ? pFarg->nExpr : 0; assert( !ExprHasProperty(pExpr, EP_IntValue) ); zId = pExpr->u.zToken; nId = sqlite3Strlen30(zId); pDef = sqlite3FindFunction(db, zId, nId, nFarg, enc, 0); - if( pDef==0 ){ + if( pDef==0 || pDef->xFunc==0 ){ sqlite3ErrorMsg(pParse, "unknown function: %.*s()", nId, zId); break; } /* Attempt a direct implementation of the built-in COALESCE() and - ** IFNULL() functions. This avoids unnecessary evalation of + ** IFNULL() functions. This avoids unnecessary evaluation of ** arguments past the first non-NULL argument. */ if( pDef->funcFlags & SQLITE_FUNC_COALESCE ){ int endCoalesce = sqlite3VdbeMakeLabel(v); assert( nFarg>=2 ); sqlite3ExprCode(pParse, pFarg->a[0].pExpr, target); for(i=1; i<nFarg; i++){ sqlite3VdbeAddOp2(v, OP_NotNull, target, endCoalesce); + VdbeCoverage(v); sqlite3ExprCacheRemove(pParse, target, 1); sqlite3ExprCachePush(pParse); sqlite3ExprCode(pParse, pFarg->a[i].pExpr, target); - sqlite3ExprCachePop(pParse, 1); + sqlite3ExprCachePop(pParse); } sqlite3VdbeResolveLabel(v, endCoalesce); break; } @@ -78719,12 +85237,26 @@ assert( nFarg>=1 ); sqlite3ExprCode(pParse, pFarg->a[0].pExpr, target); break; } + for(i=0; i<nFarg; i++){ + if( i<32 && sqlite3ExprIsConstant(pFarg->a[i].pExpr) ){ + testcase( i==31 ); + constMask |= MASKBIT32(i); + } + if( (pDef->funcFlags & SQLITE_FUNC_NEEDCOLL)!=0 && !pColl ){ + pColl = sqlite3ExprCollSeq(pParse, pFarg->a[i].pExpr); + } + } if( pFarg ){ - r1 = sqlite3GetTempRange(pParse, nFarg); + if( constMask ){ + r1 = pParse->nMem+1; + pParse->nMem += nFarg; + }else{ + r1 = sqlite3GetTempRange(pParse, nFarg); + } /* For length() and typeof() functions with a column argument, ** set the P5 parameter to the OP_Column opcode to OPFLAG_LENGTHARG ** or OPFLAG_TYPEOFARG respectively, to avoid unnecessary data ** loading. @@ -78735,19 +85267,20 @@ assert( pFarg->a[0].pExpr!=0 ); exprOp = pFarg->a[0].pExpr->op; if( exprOp==TK_COLUMN || exprOp==TK_AGG_COLUMN ){ assert( SQLITE_FUNC_LENGTH==OPFLAG_LENGTHARG ); assert( SQLITE_FUNC_TYPEOF==OPFLAG_TYPEOFARG ); - testcase( (pDef->funcFlags&~SQLITE_FUNC_ENCMASK) - ==SQLITE_FUNC_LENGTH ); - pFarg->a[0].pExpr->op2 = pDef->funcFlags&~SQLITE_FUNC_ENCMASK; + testcase( pDef->funcFlags & OPFLAG_LENGTHARG ); + pFarg->a[0].pExpr->op2 = + pDef->funcFlags & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG); } } sqlite3ExprCachePush(pParse); /* Ticket 2ea2425d34be */ - sqlite3ExprCodeExprList(pParse, pFarg, r1, 1); - sqlite3ExprCachePop(pParse, 1); /* Ticket 2ea2425d34be */ + sqlite3ExprCodeExprList(pParse, pFarg, r1, + SQLITE_ECEL_DUP|SQLITE_ECEL_FACTOR); + sqlite3ExprCachePop(pParse); /* Ticket 2ea2425d34be */ }else{ r1 = 0; } #ifndef SQLITE_OMIT_VIRTUALTABLE /* Possibly overload the function if the first argument is @@ -78766,26 +85299,18 @@ pDef = sqlite3VtabOverloadFunction(db, pDef, nFarg, pFarg->a[1].pExpr); }else if( nFarg>0 ){ pDef = sqlite3VtabOverloadFunction(db, pDef, nFarg, pFarg->a[0].pExpr); } #endif - for(i=0; i<nFarg; i++){ - if( i<32 && sqlite3ExprIsConstant(pFarg->a[i].pExpr) ){ - constMask |= (1<<i); - } - if( (pDef->funcFlags & SQLITE_FUNC_NEEDCOLL)!=0 && !pColl ){ - pColl = sqlite3ExprCollSeq(pParse, pFarg->a[i].pExpr); - } - } if( pDef->funcFlags & SQLITE_FUNC_NEEDCOLL ){ if( !pColl ) pColl = db->pDfltColl; sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); } sqlite3VdbeAddOp4(v, OP_Function, constMask, r1, target, (char*)pDef, P4_FUNCDEF); sqlite3VdbeChangeP5(v, (u8)nFarg); - if( nFarg ){ + if( nFarg && constMask==0 ){ sqlite3ReleaseTempRange(pParse, r1, nFarg); } break; } #ifndef SQLITE_OMIT_SUBQUERY @@ -78831,17 +85356,18 @@ testcase( regFree1==0 ); testcase( regFree2==0 ); r3 = sqlite3GetTempReg(pParse); r4 = sqlite3GetTempReg(pParse); codeCompare(pParse, pLeft, pRight, OP_Ge, - r1, r2, r3, SQLITE_STOREP2); + r1, r2, r3, SQLITE_STOREP2); VdbeCoverage(v); pLItem++; pRight = pLItem->pExpr; sqlite3ReleaseTempReg(pParse, regFree2); r2 = sqlite3ExprCodeTemp(pParse, pRight, ®Free2); testcase( regFree2==0 ); codeCompare(pParse, pLeft, pRight, OP_Le, r1, r2, r4, SQLITE_STOREP2); + VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_And, r3, r4, target); sqlite3ReleaseTempReg(pParse, r3); sqlite3ReleaseTempReg(pParse, r4); break; } @@ -78892,11 +85418,14 @@ target )); #ifndef SQLITE_OMIT_FLOATING_POINT /* If the column has REAL affinity, it may currently be stored as an - ** integer. Use OP_RealAffinity to make sure it is really real. */ + ** integer. Use OP_RealAffinity to make sure it is really real. + ** + ** EVIDENCE-OF: R-60985-57662 SQLite will convert the value back to + ** floating point when extracting it from the record. */ if( pExpr->iColumn>=0 && pTab->aCol[pExpr->iColumn].affinity==SQLITE_AFF_REAL ){ sqlite3VdbeAddOp1(v, OP_RealAffinity, target); } @@ -78932,11 +85461,10 @@ int nExpr; /* 2x number of WHEN terms */ int i; /* Loop counter */ ExprList *pEList; /* List of WHEN terms */ struct ExprList_item *aListelem; /* Array of WHEN terms */ Expr opCompare; /* The X==Ei expression */ - Expr cacheX; /* Cached expression X */ Expr *pX; /* The X expression */ Expr *pTest = 0; /* X==Ei (form A) or just Ei (form B) */ VVA_ONLY( int iCacheLevel = pParse->iCacheLevel; ) assert( !ExprHasProperty(pExpr, EP_xIsSelect) && pExpr->x.pList ); @@ -78944,17 +85472,16 @@ pEList = pExpr->x.pList; aListelem = pEList->a; nExpr = pEList->nExpr; endLabel = sqlite3VdbeMakeLabel(v); if( (pX = pExpr->pLeft)!=0 ){ - cacheX = *pX; + tempX = *pX; testcase( pX->op==TK_COLUMN ); - testcase( pX->op==TK_REGISTER ); - exprToRegister(&cacheX, sqlite3ExprCodeTemp(pParse, pX, ®Free1)); + exprToRegister(&tempX, sqlite3ExprCodeTemp(pParse, pX, ®Free1)); testcase( regFree1==0 ); opCompare.op = TK_EQ; - opCompare.pLeft = &cacheX; + opCompare.pLeft = &tempX; pTest = &opCompare; /* Ticket b351d95f9cd5ef17e9d9dbae18f5ca8611190001: ** The value in regFree1 might get SCopy-ed into the file result. ** So make sure that the regFree1 register is not reused for other ** purposes and possibly overwritten. */ @@ -78970,20 +85497,19 @@ } nextCase = sqlite3VdbeMakeLabel(v); testcase( pTest->op==TK_COLUMN ); sqlite3ExprIfFalse(pParse, pTest, nextCase, SQLITE_JUMPIFNULL); testcase( aListelem[i+1].pExpr->op==TK_COLUMN ); - testcase( aListelem[i+1].pExpr->op==TK_REGISTER ); sqlite3ExprCode(pParse, aListelem[i+1].pExpr, target); sqlite3VdbeAddOp2(v, OP_Goto, 0, endLabel); - sqlite3ExprCachePop(pParse, 1); + sqlite3ExprCachePop(pParse); sqlite3VdbeResolveLabel(v, nextCase); } if( (nExpr&1)!=0 ){ sqlite3ExprCachePush(pParse); sqlite3ExprCode(pParse, pEList->a[nExpr-1].pExpr, target); - sqlite3ExprCachePop(pParse, 1); + sqlite3ExprCachePop(pParse); }else{ sqlite3VdbeAddOp2(v, OP_Null, 0, target); } assert( db->mallocFailed || pParse->nErr>0 || pParse->iCacheLevel==iCacheLevel ); @@ -79007,13 +85533,14 @@ } assert( !ExprHasProperty(pExpr, EP_IntValue) ); if( pExpr->affinity==OE_Ignore ){ sqlite3VdbeAddOp4( v, OP_Halt, SQLITE_OK, OE_Ignore, 0, pExpr->u.zToken,0); + VdbeCoverage(v); }else{ sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_TRIGGER, - pExpr->affinity, pExpr->u.zToken, 0); + pExpr->affinity, pExpr->u.zToken, 0, 0); } break; } #endif @@ -79020,38 +85547,85 @@ } sqlite3ReleaseTempReg(pParse, regFree1); sqlite3ReleaseTempReg(pParse, regFree2); return inReg; } + +/* +** Factor out the code of the given expression to initialization time. +*/ +SQLITE_PRIVATE void sqlite3ExprCodeAtInit( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* The expression to code when the VDBE initializes */ + int regDest, /* Store the value in this register */ + u8 reusable /* True if this expression is reusable */ +){ + ExprList *p; + assert( ConstFactorOk(pParse) ); + p = pParse->pConstExpr; + pExpr = sqlite3ExprDup(pParse->db, pExpr, 0); + p = sqlite3ExprListAppend(pParse, p, pExpr); + if( p ){ + struct ExprList_item *pItem = &p->a[p->nExpr-1]; + pItem->u.iConstExprReg = regDest; + pItem->reusable = reusable; + } + pParse->pConstExpr = p; +} /* ** Generate code to evaluate an expression and store the results ** into a register. Return the register number where the results ** are stored. ** ** If the register is a temporary register that can be deallocated, ** then write its number into *pReg. If the result register is not ** a temporary, then set *pReg to zero. +** +** If pExpr is a constant, then this routine might generate this +** code to fill the register in the initialization section of the +** VDBE program, in order to factor it out of the evaluation loop. */ SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){ - int r1 = sqlite3GetTempReg(pParse); - int r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1); - if( r2==r1 ){ - *pReg = r1; + int r2; + pExpr = sqlite3ExprSkipCollate(pExpr); + if( ConstFactorOk(pParse) + && pExpr->op!=TK_REGISTER + && sqlite3ExprIsConstantNotJoin(pExpr) + ){ + ExprList *p = pParse->pConstExpr; + int i; + *pReg = 0; + if( p ){ + struct ExprList_item *pItem; + for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){ + if( pItem->reusable && sqlite3ExprCompare(pItem->pExpr,pExpr,-1)==0 ){ + return pItem->u.iConstExprReg; + } + } + } + r2 = ++pParse->nMem; + sqlite3ExprCodeAtInit(pParse, pExpr, r2, 1); }else{ - sqlite3ReleaseTempReg(pParse, r1); - *pReg = 0; + int r1 = sqlite3GetTempReg(pParse); + r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1); + if( r2==r1 ){ + *pReg = r1; + }else{ + sqlite3ReleaseTempReg(pParse, r1); + *pReg = 0; + } } return r2; } /* ** Generate code that will evaluate expression pExpr and store the ** results in register target. The results are guaranteed to appear ** in register target. */ -SQLITE_PRIVATE int sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){ +SQLITE_PRIVATE void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){ int inReg; assert( target>0 && target<=pParse->nMem ); if( pExpr && pExpr->op==TK_REGISTER ){ sqlite3VdbeAddOp2(pParse->pVdbe, OP_Copy, pExpr->iTable, target); @@ -79060,15 +85634,28 @@ assert( pParse->pVdbe || pParse->db->mallocFailed ); if( inReg!=target && pParse->pVdbe ){ sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, inReg, target); } } - return target; } /* -** Generate code that evalutes the given expression and puts the result +** Generate code that will evaluate expression pExpr and store the +** results in register target. The results are guaranteed to appear +** in register target. If the expression is constant, then this routine +** might choose to code the expression at initialization time. +*/ +SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){ + if( pParse->okConstFactor && sqlite3ExprIsConstant(pExpr) ){ + sqlite3ExprCodeAtInit(pParse, pExpr, target, 0); + }else{ + sqlite3ExprCode(pParse, pExpr, target); + } +} + +/* +** Generate code that evaluates the given expression and puts the result ** in register target. ** ** Also make a copy of the expression results into another "cache" register ** and modify the expression so that the next time it is evaluated, ** the result is a copy of the cache register. @@ -79075,114 +85662,102 @@ ** ** This routine is used for expressions that are used multiple ** times. They are evaluated once and the results of the expression ** are reused. */ -SQLITE_PRIVATE int sqlite3ExprCodeAndCache(Parse *pParse, Expr *pExpr, int target){ +SQLITE_PRIVATE void sqlite3ExprCodeAndCache(Parse *pParse, Expr *pExpr, int target){ Vdbe *v = pParse->pVdbe; - int inReg; - inReg = sqlite3ExprCode(pParse, pExpr, target); + int iMem; + assert( target>0 ); - /* This routine is called for terms to INSERT or UPDATE. And the only - ** other place where expressions can be converted into TK_REGISTER is - ** in WHERE clause processing. So as currently implemented, there is - ** no way for a TK_REGISTER to exist here. But it seems prudent to - ** keep the ALWAYS() in case the conditions above change with future - ** modifications or enhancements. */ - if( ALWAYS(pExpr->op!=TK_REGISTER) ){ - int iMem; - iMem = ++pParse->nMem; - sqlite3VdbeAddOp2(v, OP_Copy, inReg, iMem); - exprToRegister(pExpr, iMem); - } - return inReg; -} - -#if defined(SQLITE_ENABLE_TREE_EXPLAIN) + assert( pExpr->op!=TK_REGISTER ); + sqlite3ExprCode(pParse, pExpr, target); + iMem = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Copy, target, iMem); + exprToRegister(pExpr, iMem); +} + +#ifdef SQLITE_DEBUG /* ** Generate a human-readable explanation of an expression tree. */ -SQLITE_PRIVATE void sqlite3ExplainExpr(Vdbe *pOut, Expr *pExpr){ - int op; /* The opcode being coded */ +SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ const char *zBinOp = 0; /* Binary operator */ const char *zUniOp = 0; /* Unary operator */ + pView = sqlite3TreeViewPush(pView, moreToFollow); if( pExpr==0 ){ - op = TK_NULL; - }else{ - op = pExpr->op; + sqlite3TreeViewLine(pView, "nil"); + sqlite3TreeViewPop(pView); + return; } - switch( op ){ + switch( pExpr->op ){ case TK_AGG_COLUMN: { - sqlite3ExplainPrintf(pOut, "AGG{%d:%d}", + sqlite3TreeViewLine(pView, "AGG{%d:%d}", pExpr->iTable, pExpr->iColumn); break; } case TK_COLUMN: { if( pExpr->iTable<0 ){ /* This only happens when coding check constraints */ - sqlite3ExplainPrintf(pOut, "COLUMN(%d)", pExpr->iColumn); + sqlite3TreeViewLine(pView, "COLUMN(%d)", pExpr->iColumn); }else{ - sqlite3ExplainPrintf(pOut, "{%d:%d}", + sqlite3TreeViewLine(pView, "{%d:%d}", pExpr->iTable, pExpr->iColumn); } break; } case TK_INTEGER: { if( pExpr->flags & EP_IntValue ){ - sqlite3ExplainPrintf(pOut, "%d", pExpr->u.iValue); + sqlite3TreeViewLine(pView, "%d", pExpr->u.iValue); }else{ - sqlite3ExplainPrintf(pOut, "%s", pExpr->u.zToken); + sqlite3TreeViewLine(pView, "%s", pExpr->u.zToken); } break; } #ifndef SQLITE_OMIT_FLOATING_POINT case TK_FLOAT: { - sqlite3ExplainPrintf(pOut,"%s", pExpr->u.zToken); + sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken); break; } #endif case TK_STRING: { - sqlite3ExplainPrintf(pOut,"%Q", pExpr->u.zToken); + sqlite3TreeViewLine(pView,"%Q", pExpr->u.zToken); break; } case TK_NULL: { - sqlite3ExplainPrintf(pOut,"NULL"); + sqlite3TreeViewLine(pView,"NULL"); break; } #ifndef SQLITE_OMIT_BLOB_LITERAL case TK_BLOB: { - sqlite3ExplainPrintf(pOut,"%s", pExpr->u.zToken); + sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken); break; } #endif case TK_VARIABLE: { - sqlite3ExplainPrintf(pOut,"VARIABLE(%s,%d)", - pExpr->u.zToken, pExpr->iColumn); + sqlite3TreeViewLine(pView,"VARIABLE(%s,%d)", + pExpr->u.zToken, pExpr->iColumn); break; } case TK_REGISTER: { - sqlite3ExplainPrintf(pOut,"REGISTER(%d)", pExpr->iTable); + sqlite3TreeViewLine(pView,"REGISTER(%d)", pExpr->iTable); break; } case TK_AS: { - sqlite3ExplainExpr(pOut, pExpr->pLeft); + sqlite3TreeViewLine(pView,"AS %Q", pExpr->u.zToken); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); + break; + } + case TK_ID: { + sqlite3TreeViewLine(pView,"ID %Q", pExpr->u.zToken); break; } #ifndef SQLITE_OMIT_CAST case TK_CAST: { /* Expressions of the form: CAST(pLeft AS token) */ - const char *zAff = "unk"; - switch( sqlite3AffinityType(pExpr->u.zToken) ){ - case SQLITE_AFF_TEXT: zAff = "TEXT"; break; - case SQLITE_AFF_NONE: zAff = "NONE"; break; - case SQLITE_AFF_NUMERIC: zAff = "NUMERIC"; break; - case SQLITE_AFF_INTEGER: zAff = "INTEGER"; break; - case SQLITE_AFF_REAL: zAff = "REAL"; break; - } - sqlite3ExplainPrintf(pOut, "CAST-%s(", zAff); - sqlite3ExplainExpr(pOut, pExpr->pLeft); - sqlite3ExplainPrintf(pOut, ")"); + sqlite3TreeViewLine(pView,"CAST %Q", pExpr->u.zToken); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; } #endif /* SQLITE_OMIT_CAST */ case TK_LT: zBinOp = "LT"; break; case TK_LE: zBinOp = "LE"; break; @@ -79202,68 +85777,63 @@ case TK_BITOR: zBinOp = "BITOR"; break; case TK_SLASH: zBinOp = "DIV"; break; case TK_LSHIFT: zBinOp = "LSHIFT"; break; case TK_RSHIFT: zBinOp = "RSHIFT"; break; case TK_CONCAT: zBinOp = "CONCAT"; break; + case TK_DOT: zBinOp = "DOT"; break; case TK_UMINUS: zUniOp = "UMINUS"; break; case TK_UPLUS: zUniOp = "UPLUS"; break; case TK_BITNOT: zUniOp = "BITNOT"; break; case TK_NOT: zUniOp = "NOT"; break; case TK_ISNULL: zUniOp = "ISNULL"; break; case TK_NOTNULL: zUniOp = "NOTNULL"; break; case TK_COLLATE: { - sqlite3ExplainExpr(pOut, pExpr->pLeft); - sqlite3ExplainPrintf(pOut,".COLLATE(%s)",pExpr->u.zToken); + sqlite3TreeViewLine(pView, "COLLATE %Q", pExpr->u.zToken); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; } case TK_AGG_FUNCTION: - case TK_CONST_FUNC: case TK_FUNCTION: { ExprList *pFarg; /* List of function arguments */ if( ExprHasProperty(pExpr, EP_TokenOnly) ){ pFarg = 0; }else{ pFarg = pExpr->x.pList; } - if( op==TK_AGG_FUNCTION ){ - sqlite3ExplainPrintf(pOut, "AGG_FUNCTION%d:%s(", + if( pExpr->op==TK_AGG_FUNCTION ){ + sqlite3TreeViewLine(pView, "AGG_FUNCTION%d %Q", pExpr->op2, pExpr->u.zToken); }else{ - sqlite3ExplainPrintf(pOut, "FUNCTION:%s(", pExpr->u.zToken); + sqlite3TreeViewLine(pView, "FUNCTION %Q", pExpr->u.zToken); } if( pFarg ){ - sqlite3ExplainExprList(pOut, pFarg); + sqlite3TreeViewExprList(pView, pFarg, 0, 0); } - sqlite3ExplainPrintf(pOut, ")"); break; } #ifndef SQLITE_OMIT_SUBQUERY case TK_EXISTS: { - sqlite3ExplainPrintf(pOut, "EXISTS("); - sqlite3ExplainSelect(pOut, pExpr->x.pSelect); - sqlite3ExplainPrintf(pOut,")"); + sqlite3TreeViewLine(pView, "EXISTS-expr"); + sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); break; } case TK_SELECT: { - sqlite3ExplainPrintf(pOut, "("); - sqlite3ExplainSelect(pOut, pExpr->x.pSelect); - sqlite3ExplainPrintf(pOut, ")"); + sqlite3TreeViewLine(pView, "SELECT-expr"); + sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); break; } case TK_IN: { - sqlite3ExplainPrintf(pOut, "IN("); - sqlite3ExplainExpr(pOut, pExpr->pLeft); - sqlite3ExplainPrintf(pOut, ","); + sqlite3TreeViewLine(pView, "IN"); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); if( ExprHasProperty(pExpr, EP_xIsSelect) ){ - sqlite3ExplainSelect(pOut, pExpr->x.pSelect); + sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); }else{ - sqlite3ExplainExprList(pOut, pExpr->x.pList); + sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0); } - sqlite3ExplainPrintf(pOut, ")"); break; } #endif /* SQLITE_OMIT_SUBQUERY */ /* @@ -79279,17 +85849,14 @@ */ case TK_BETWEEN: { Expr *pX = pExpr->pLeft; Expr *pY = pExpr->x.pList->a[0].pExpr; Expr *pZ = pExpr->x.pList->a[1].pExpr; - sqlite3ExplainPrintf(pOut, "BETWEEN("); - sqlite3ExplainExpr(pOut, pX); - sqlite3ExplainPrintf(pOut, ","); - sqlite3ExplainExpr(pOut, pY); - sqlite3ExplainPrintf(pOut, ","); - sqlite3ExplainExpr(pOut, pZ); - sqlite3ExplainPrintf(pOut, ")"); + sqlite3TreeViewLine(pView, "BETWEEN"); + sqlite3TreeViewExpr(pView, pX, 1); + sqlite3TreeViewExpr(pView, pY, 1); + sqlite3TreeViewExpr(pView, pZ, 0); break; } case TK_TRIGGER: { /* If the opcode is TK_TRIGGER, then the expression is a reference ** to a column in the new.* or old.* pseudo-tables available to @@ -79296,19 +85863,18 @@ ** trigger programs. In this case Expr.iTable is set to 1 for the ** new.* pseudo-table, or 0 for the old.* pseudo-table. Expr.iColumn ** is set to the column of the pseudo-table to read, or to -1 to ** read the rowid field. */ - sqlite3ExplainPrintf(pOut, "%s(%d)", + sqlite3TreeViewLine(pView, "%s(%d)", pExpr->iTable ? "NEW" : "OLD", pExpr->iColumn); break; } case TK_CASE: { - sqlite3ExplainPrintf(pOut, "CASE("); - sqlite3ExplainExpr(pOut, pExpr->pLeft); - sqlite3ExplainPrintf(pOut, ","); - sqlite3ExplainExprList(pOut, pExpr->x.pList); + sqlite3TreeViewLine(pView, "CASE"); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); + sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0); break; } #ifndef SQLITE_OMIT_TRIGGER case TK_RAISE: { const char *zType = "unk"; @@ -79316,220 +85882,109 @@ case OE_Rollback: zType = "rollback"; break; case OE_Abort: zType = "abort"; break; case OE_Fail: zType = "fail"; break; case OE_Ignore: zType = "ignore"; break; } - sqlite3ExplainPrintf(pOut, "RAISE-%s(%s)", zType, pExpr->u.zToken); + sqlite3TreeViewLine(pView, "RAISE %s(%Q)", zType, pExpr->u.zToken); break; } #endif + default: { + sqlite3TreeViewLine(pView, "op=%d", pExpr->op); + break; + } } if( zBinOp ){ - sqlite3ExplainPrintf(pOut,"%s(", zBinOp); - sqlite3ExplainExpr(pOut, pExpr->pLeft); - sqlite3ExplainPrintf(pOut,","); - sqlite3ExplainExpr(pOut, pExpr->pRight); - sqlite3ExplainPrintf(pOut,")"); + sqlite3TreeViewLine(pView, "%s", zBinOp); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); + sqlite3TreeViewExpr(pView, pExpr->pRight, 0); }else if( zUniOp ){ - sqlite3ExplainPrintf(pOut,"%s(", zUniOp); - sqlite3ExplainExpr(pOut, pExpr->pLeft); - sqlite3ExplainPrintf(pOut,")"); - } -} -#endif /* defined(SQLITE_ENABLE_TREE_EXPLAIN) */ - -#if defined(SQLITE_ENABLE_TREE_EXPLAIN) + sqlite3TreeViewLine(pView, "%s", zUniOp); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); + } + sqlite3TreeViewPop(pView); +} +#endif /* SQLITE_DEBUG */ + +#ifdef SQLITE_DEBUG /* ** Generate a human-readable explanation of an expression list. */ -SQLITE_PRIVATE void sqlite3ExplainExprList(Vdbe *pOut, ExprList *pList){ +SQLITE_PRIVATE void sqlite3TreeViewExprList( + TreeView *pView, + const ExprList *pList, + u8 moreToFollow, + const char *zLabel +){ int i; - if( pList==0 || pList->nExpr==0 ){ - sqlite3ExplainPrintf(pOut, "(empty-list)"); - return; - }else if( pList->nExpr==1 ){ - sqlite3ExplainExpr(pOut, pList->a[0].pExpr); + pView = sqlite3TreeViewPush(pView, moreToFollow); + if( zLabel==0 || zLabel[0]==0 ) zLabel = "LIST"; + if( pList==0 ){ + sqlite3TreeViewLine(pView, "%s (empty)", zLabel); }else{ - sqlite3ExplainPush(pOut); + sqlite3TreeViewLine(pView, "%s", zLabel); for(i=0; i<pList->nExpr; i++){ - sqlite3ExplainPrintf(pOut, "item[%d] = ", i); - sqlite3ExplainPush(pOut); - sqlite3ExplainExpr(pOut, pList->a[i].pExpr); - sqlite3ExplainPop(pOut); - if( pList->a[i].zName ){ + sqlite3TreeViewExpr(pView, pList->a[i].pExpr, i<pList->nExpr-1); +#if 0 + if( pList->a[i].zName ){ sqlite3ExplainPrintf(pOut, " AS %s", pList->a[i].zName); } if( pList->a[i].bSpanIsTab ){ sqlite3ExplainPrintf(pOut, " (%s)", pList->a[i].zSpan); } - if( i<pList->nExpr-1 ){ - sqlite3ExplainNL(pOut); - } +#endif } - sqlite3ExplainPop(pOut); } + sqlite3TreeViewPop(pView); } #endif /* SQLITE_DEBUG */ - -/* -** Return TRUE if pExpr is an constant expression that is appropriate -** for factoring out of a loop. Appropriate expressions are: -** -** * Any expression that evaluates to two or more opcodes. -** -** * Any OP_Integer, OP_Real, OP_String, OP_Blob, OP_Null, -** or OP_Variable that does not need to be placed in a -** specific register. -** -** There is no point in factoring out single-instruction constant -** expressions that need to be placed in a particular register. -** We could factor them out, but then we would end up adding an -** OP_SCopy instruction to move the value into the correct register -** later. We might as well just use the original instruction and -** avoid the OP_SCopy. -*/ -static int isAppropriateForFactoring(Expr *p){ - if( !sqlite3ExprIsConstantNotJoin(p) ){ - return 0; /* Only constant expressions are appropriate for factoring */ - } - if( (p->flags & EP_FixedDest)==0 ){ - return 1; /* Any constant without a fixed destination is appropriate */ - } - while( p->op==TK_UPLUS ) p = p->pLeft; - switch( p->op ){ -#ifndef SQLITE_OMIT_BLOB_LITERAL - case TK_BLOB: -#endif - case TK_VARIABLE: - case TK_INTEGER: - case TK_FLOAT: - case TK_NULL: - case TK_STRING: { - testcase( p->op==TK_BLOB ); - testcase( p->op==TK_VARIABLE ); - testcase( p->op==TK_INTEGER ); - testcase( p->op==TK_FLOAT ); - testcase( p->op==TK_NULL ); - testcase( p->op==TK_STRING ); - /* Single-instruction constants with a fixed destination are - ** better done in-line. If we factor them, they will just end - ** up generating an OP_SCopy to move the value to the destination - ** register. */ - return 0; - } - case TK_UMINUS: { - if( p->pLeft->op==TK_FLOAT || p->pLeft->op==TK_INTEGER ){ - return 0; - } - break; - } - default: { - break; - } - } - return 1; -} - -/* -** If pExpr is a constant expression that is appropriate for -** factoring out of a loop, then evaluate the expression -** into a register and convert the expression into a TK_REGISTER -** expression. -*/ -static int evalConstExpr(Walker *pWalker, Expr *pExpr){ - Parse *pParse = pWalker->pParse; - switch( pExpr->op ){ - case TK_IN: - case TK_REGISTER: { - return WRC_Prune; - } - case TK_COLLATE: { - return WRC_Continue; - } - case TK_FUNCTION: - case TK_AGG_FUNCTION: - case TK_CONST_FUNC: { - /* The arguments to a function have a fixed destination. - ** Mark them this way to avoid generated unneeded OP_SCopy - ** instructions. - */ - ExprList *pList = pExpr->x.pList; - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); - if( pList ){ - int i = pList->nExpr; - struct ExprList_item *pItem = pList->a; - for(; i>0; i--, pItem++){ - if( ALWAYS(pItem->pExpr) ) pItem->pExpr->flags |= EP_FixedDest; - } - } - break; - } - } - if( isAppropriateForFactoring(pExpr) ){ - int r1 = ++pParse->nMem; - int r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1); - /* If r2!=r1, it means that register r1 is never used. That is harmless - ** but suboptimal, so we want to know about the situation to fix it. - ** Hence the following assert: */ - assert( r2==r1 ); - exprToRegister(pExpr, r2); - return WRC_Prune; - } - return WRC_Continue; -} - -/* -** Preevaluate constant subexpressions within pExpr and store the -** results in registers. Modify pExpr so that the constant subexpresions -** are TK_REGISTER opcodes that refer to the precomputed values. -** -** This routine is a no-op if the jump to the cookie-check code has -** already occur. Since the cookie-check jump is generated prior to -** any other serious processing, this check ensures that there is no -** way to accidently bypass the constant initializations. -** -** This routine is also a no-op if the SQLITE_FactorOutConst optimization -** is disabled via the sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS) -** interface. This allows test logic to verify that the same answer is -** obtained for queries regardless of whether or not constants are -** precomputed into registers or if they are inserted in-line. -*/ -SQLITE_PRIVATE void sqlite3ExprCodeConstants(Parse *pParse, Expr *pExpr){ - Walker w; - if( pParse->cookieGoto ) return; - if( OptimizationDisabled(pParse->db, SQLITE_FactorOutConst) ) return; - memset(&w, 0, sizeof(w)); - w.xExprCallback = evalConstExpr; - w.pParse = pParse; - sqlite3WalkExpr(&w, pExpr); -} - /* ** Generate code that pushes the value of every element of the given ** expression list into a sequence of registers beginning at target. ** ** Return the number of elements evaluated. +** +** The SQLITE_ECEL_DUP flag prevents the arguments from being +** filled using OP_SCopy. OP_Copy must be used instead. +** +** The SQLITE_ECEL_FACTOR argument allows constant arguments to be +** factored out into initialization code. */ SQLITE_PRIVATE int sqlite3ExprCodeExprList( Parse *pParse, /* Parsing context */ ExprList *pList, /* The expression list to be coded */ int target, /* Where to write results */ - int doHardCopy /* Make a hard copy of every element */ + u8 flags /* SQLITE_ECEL_* flags */ ){ struct ExprList_item *pItem; int i, n; + u8 copyOp = (flags & SQLITE_ECEL_DUP) ? OP_Copy : OP_SCopy; assert( pList!=0 ); assert( target>0 ); assert( pParse->pVdbe!=0 ); /* Never gets this far otherwise */ n = pList->nExpr; + if( !ConstFactorOk(pParse) ) flags &= ~SQLITE_ECEL_FACTOR; for(pItem=pList->a, i=0; i<n; i++, pItem++){ Expr *pExpr = pItem->pExpr; - int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i); - if( inReg!=target+i ){ - sqlite3VdbeAddOp2(pParse->pVdbe, doHardCopy ? OP_Copy : OP_SCopy, - inReg, target+i); + if( (flags & SQLITE_ECEL_FACTOR)!=0 && sqlite3ExprIsConstant(pExpr) ){ + sqlite3ExprCodeAtInit(pParse, pExpr, target+i, 0); + }else{ + int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i); + if( inReg!=target+i ){ + VdbeOp *pOp; + Vdbe *v = pParse->pVdbe; + if( copyOp==OP_Copy + && (pOp=sqlite3VdbeGetOp(v, -1))->opcode==OP_Copy + && pOp->p1+pOp->p3+1==inReg + && pOp->p2+pOp->p3+1==target+i + ){ + pOp->p3++; + }else{ + sqlite3VdbeAddOp2(v, copyOp, inReg, target+i); + } + } } } return n; } @@ -79541,11 +85996,11 @@ ** The above is equivalent to ** ** x>=y AND x<=z ** ** Code it as such, taking care to do the common subexpression -** elementation of x. +** elimination of x. */ static void exprCodeBetween( Parse *pParse, /* Parsing and code generating context */ Expr *pExpr, /* The BETWEEN expression */ int dest, /* Jump here if the jump is taken */ @@ -79615,21 +86070,23 @@ op = pExpr->op; switch( op ){ case TK_AND: { int d2 = sqlite3VdbeMakeLabel(v); testcase( jumpIfNull==0 ); - sqlite3ExprCachePush(pParse); sqlite3ExprIfFalse(pParse, pExpr->pLeft, d2,jumpIfNull^SQLITE_JUMPIFNULL); + sqlite3ExprCachePush(pParse); sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); sqlite3VdbeResolveLabel(v, d2); - sqlite3ExprCachePop(pParse, 1); + sqlite3ExprCachePop(pParse); break; } case TK_OR: { testcase( jumpIfNull==0 ); sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); + sqlite3ExprCachePush(pParse); sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); + sqlite3ExprCachePop(pParse); break; } case TK_NOT: { testcase( jumpIfNull==0 ); sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); @@ -79639,27 +86096,21 @@ case TK_LE: case TK_GT: case TK_GE: case TK_NE: case TK_EQ: { - assert( TK_LT==OP_Lt ); - assert( TK_LE==OP_Le ); - assert( TK_GT==OP_Gt ); - assert( TK_GE==OP_Ge ); - assert( TK_EQ==OP_Eq ); - assert( TK_NE==OP_Ne ); - testcase( op==TK_LT ); - testcase( op==TK_LE ); - testcase( op==TK_GT ); - testcase( op==TK_GE ); - testcase( op==TK_EQ ); - testcase( op==TK_NE ); testcase( jumpIfNull==0 ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, r1, r2, dest, jumpIfNull); + assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); + assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); testcase( regFree1==0 ); testcase( regFree2==0 ); break; } case TK_IS: @@ -79669,22 +86120,24 @@ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); op = (op==TK_IS) ? TK_EQ : TK_NE; codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, r1, r2, dest, SQLITE_NULLEQ); + VdbeCoverageIf(v, op==TK_EQ); + VdbeCoverageIf(v, op==TK_NE); testcase( regFree1==0 ); testcase( regFree2==0 ); break; } case TK_ISNULL: case TK_NOTNULL: { - assert( TK_ISNULL==OP_IsNull ); - assert( TK_NOTNULL==OP_NotNull ); - testcase( op==TK_ISNULL ); - testcase( op==TK_NOTNULL ); + assert( TK_ISNULL==OP_IsNull ); testcase( op==TK_ISNULL ); + assert( TK_NOTNULL==OP_NotNull ); testcase( op==TK_NOTNULL ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); sqlite3VdbeAddOp2(v, op, r1, dest); + VdbeCoverageIf(v, op==TK_ISNULL); + VdbeCoverageIf(v, op==TK_NOTNULL); testcase( regFree1==0 ); break; } case TK_BETWEEN: { testcase( jumpIfNull==0 ); @@ -79700,14 +86153,21 @@ sqlite3VdbeResolveLabel(v, destIfFalse); break; } #endif default: { - r1 = sqlite3ExprCodeTemp(pParse, pExpr, ®Free1); - sqlite3VdbeAddOp3(v, OP_If, r1, dest, jumpIfNull!=0); - testcase( regFree1==0 ); - testcase( jumpIfNull==0 ); + if( exprAlwaysTrue(pExpr) ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, dest); + }else if( exprAlwaysFalse(pExpr) ){ + /* No-op */ + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr, ®Free1); + sqlite3VdbeAddOp3(v, OP_If, r1, dest, jumpIfNull!=0); + VdbeCoverage(v); + testcase( regFree1==0 ); + testcase( jumpIfNull==0 ); + } break; } } sqlite3ReleaseTempReg(pParse, regFree1); sqlite3ReleaseTempReg(pParse, regFree2); @@ -79766,21 +86226,23 @@ switch( pExpr->op ){ case TK_AND: { testcase( jumpIfNull==0 ); sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); + sqlite3ExprCachePush(pParse); sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); + sqlite3ExprCachePop(pParse); break; } case TK_OR: { int d2 = sqlite3VdbeMakeLabel(v); testcase( jumpIfNull==0 ); - sqlite3ExprCachePush(pParse); sqlite3ExprIfTrue(pParse, pExpr->pLeft, d2, jumpIfNull^SQLITE_JUMPIFNULL); + sqlite3ExprCachePush(pParse); sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); sqlite3VdbeResolveLabel(v, d2); - sqlite3ExprCachePop(pParse, 1); + sqlite3ExprCachePop(pParse); break; } case TK_NOT: { testcase( jumpIfNull==0 ); sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); @@ -79790,21 +86252,21 @@ case TK_LE: case TK_GT: case TK_GE: case TK_NE: case TK_EQ: { - testcase( op==TK_LT ); - testcase( op==TK_LE ); - testcase( op==TK_GT ); - testcase( op==TK_GE ); - testcase( op==TK_EQ ); - testcase( op==TK_NE ); testcase( jumpIfNull==0 ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, r1, r2, dest, jumpIfNull); + assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); + assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); testcase( regFree1==0 ); testcase( regFree2==0 ); break; } case TK_IS: @@ -79814,20 +86276,22 @@ r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); op = (pExpr->op==TK_IS) ? TK_NE : TK_EQ; codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, r1, r2, dest, SQLITE_NULLEQ); + VdbeCoverageIf(v, op==TK_EQ); + VdbeCoverageIf(v, op==TK_NE); testcase( regFree1==0 ); testcase( regFree2==0 ); break; } case TK_ISNULL: case TK_NOTNULL: { - testcase( op==TK_ISNULL ); - testcase( op==TK_NOTNULL ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); sqlite3VdbeAddOp2(v, op, r1, dest); + testcase( op==TK_ISNULL ); VdbeCoverageIf(v, op==TK_ISNULL); + testcase( op==TK_NOTNULL ); VdbeCoverageIf(v, op==TK_NOTNULL); testcase( regFree1==0 ); break; } case TK_BETWEEN: { testcase( jumpIfNull==0 ); @@ -79845,14 +86309,21 @@ } break; } #endif default: { - r1 = sqlite3ExprCodeTemp(pParse, pExpr, ®Free1); - sqlite3VdbeAddOp3(v, OP_IfNot, r1, dest, jumpIfNull!=0); - testcase( regFree1==0 ); - testcase( jumpIfNull==0 ); + if( exprAlwaysFalse(pExpr) ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, dest); + }else if( exprAlwaysTrue(pExpr) ){ + /* no-op */ + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr, ®Free1); + sqlite3VdbeAddOp3(v, OP_IfNot, r1, dest, jumpIfNull!=0); + VdbeCoverage(v); + testcase( regFree1==0 ); + testcase( jumpIfNull==0 ); + } break; } } sqlite3ReleaseTempReg(pParse, regFree1); sqlite3ReleaseTempReg(pParse, regFree2); @@ -79879,44 +86350,46 @@ ** this routine is used, it does not hurt to get an extra 2 - that ** just might result in some slightly slower code. But returning ** an incorrect 0 or 1 could lead to a malfunction. */ SQLITE_PRIVATE int sqlite3ExprCompare(Expr *pA, Expr *pB, int iTab){ - if( pA==0||pB==0 ){ + u32 combinedFlags; + if( pA==0 || pB==0 ){ return pB==pA ? 0 : 2; } - assert( !ExprHasProperty(pA, EP_TokenOnly|EP_Reduced) ); - assert( !ExprHasProperty(pB, EP_TokenOnly|EP_Reduced) ); - if( ExprHasProperty(pA, EP_xIsSelect) || ExprHasProperty(pB, EP_xIsSelect) ){ + combinedFlags = pA->flags | pB->flags; + if( combinedFlags & EP_IntValue ){ + if( (pA->flags&pB->flags&EP_IntValue)!=0 && pA->u.iValue==pB->u.iValue ){ + return 0; + } return 2; } - if( (pA->flags & EP_Distinct)!=(pB->flags & EP_Distinct) ) return 2; - if( pA->op!=pB->op && (pA->op!=TK_REGISTER || pA->op2!=pB->op) ){ + if( pA->op!=pB->op ){ if( pA->op==TK_COLLATE && sqlite3ExprCompare(pA->pLeft, pB, iTab)<2 ){ return 1; } if( pB->op==TK_COLLATE && sqlite3ExprCompare(pA, pB->pLeft, iTab)<2 ){ return 1; } return 2; } - if( sqlite3ExprCompare(pA->pLeft, pB->pLeft, iTab) ) return 2; - if( sqlite3ExprCompare(pA->pRight, pB->pRight, iTab) ) return 2; - if( sqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2; - if( pA->iColumn!=pB->iColumn ) return 2; - if( pA->iTable!=pB->iTable - && pA->op!=TK_REGISTER - && (pA->iTable!=iTab || NEVER(pB->iTable>=0)) ) return 2; - if( ExprHasProperty(pA, EP_IntValue) ){ - if( !ExprHasProperty(pB, EP_IntValue) || pA->u.iValue!=pB->u.iValue ){ - return 2; - } - }else if( pA->op!=TK_COLUMN && ALWAYS(pA->op!=TK_AGG_COLUMN) && pA->u.zToken){ - if( ExprHasProperty(pB, EP_IntValue) || NEVER(pB->u.zToken==0) ) return 2; + if( pA->op!=TK_COLUMN && ALWAYS(pA->op!=TK_AGG_COLUMN) && pA->u.zToken ){ if( strcmp(pA->u.zToken,pB->u.zToken)!=0 ){ return pA->op==TK_COLLATE ? 1 : 2; } + } + if( (pA->flags & EP_Distinct)!=(pB->flags & EP_Distinct) ) return 2; + if( ALWAYS((combinedFlags & EP_TokenOnly)==0) ){ + if( combinedFlags & EP_xIsSelect ) return 2; + if( sqlite3ExprCompare(pA->pLeft, pB->pLeft, iTab) ) return 2; + if( sqlite3ExprCompare(pA->pRight, pB->pRight, iTab) ) return 2; + if( sqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2; + if( ALWAYS((combinedFlags & EP_Reduced)==0) ){ + if( pA->iColumn!=pB->iColumn ) return 2; + if( pA->iTable!=pB->iTable + && (pA->iTable!=iTab || NEVER(pB->iTable>=0)) ) return 2; + } } return 0; } /* @@ -80010,14 +86483,15 @@ ** NEVER() will need to be removed. */ if( pExpr->op==TK_COLUMN || NEVER(pExpr->op==TK_AGG_COLUMN) ){ int i; struct SrcCount *p = pWalker->u.pSrcCount; SrcList *pSrc = p->pSrc; - for(i=0; i<pSrc->nSrc; i++){ + int nSrc = pSrc ? pSrc->nSrc : 0; + for(i=0; i<nSrc; i++){ if( pExpr->iTable==pSrc->a[i].iCursor ) break; } - if( i<pSrc->nSrc ){ + if( i<nSrc ){ p->nThis++; }else{ p->nOther++; } } @@ -80260,11 +86734,11 @@ /* ** Deallocate a register, making available for reuse for some other ** purpose. ** ** If a register is currently being used by the column cache, then -** the dallocation is deferred until the column cache line that uses +** the deallocation is deferred until the column cache line that uses ** the register becomes stale. */ SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse *pParse, int iReg){ if( iReg && pParse->nTempReg<ArraySize(pParse->aTempReg) ){ int i; @@ -80390,12 +86864,12 @@ len = sqlite3GetToken(zCsr, &token); } while( token==TK_SPACE ); assert( len>0 ); } while( token!=TK_LP && token!=TK_USING ); - zRet = sqlite3MPrintf(db, "%.*s\"%w\"%s", ((u8*)tname.z) - zSql, zSql, - zTableName, tname.z+tname.n); + zRet = sqlite3MPrintf(db, "%.*s\"%w\"%s", (int)(((u8*)tname.z) - zSql), + zSql, zTableName, tname.z+tname.n); sqlite3_result_text(context, zRet, -1, SQLITE_DYNAMIC); } } /* @@ -80429,10 +86903,11 @@ unsigned const char *z; /* Pointer to token */ int n; /* Length of token z */ int token; /* Type of token */ UNUSED_PARAMETER(NotUsed); + if( zInput==0 || zOld==0 ) return; for(z=zInput; *z; z=z+n){ n = sqlite3GetToken(z, &token); if( token==TK_REFERENCES ){ char *zParent; do { @@ -80443,11 +86918,11 @@ zParent = sqlite3DbStrNDup(db, (const char *)z, n); if( zParent==0 ) break; sqlite3Dequote(zParent); if( 0==sqlite3StrICmp((const char *)zOld, zParent) ){ char *zOut = sqlite3MPrintf(db, "%s%.*s\"%w\"", - (zOutput?zOutput:""), z-zInput, zInput, (const char *)zNew + (zOutput?zOutput:""), (int)(z-zInput), zInput, (const char *)zNew ); sqlite3DbFree(db, zOutput); zOutput = zOut; zInput = &z[n]; } @@ -80486,12 +86961,12 @@ sqlite3 *db = sqlite3_context_db_handle(context); UNUSED_PARAMETER(NotUsed); /* The principle used to locate the table name in the CREATE TRIGGER - ** statement is that the table name is the first token that is immediatedly - ** preceded by either TK_ON or TK_DOT and immediatedly followed by one + ** statement is that the table name is the first token that is immediately + ** preceded by either TK_ON or TK_DOT and immediately followed by one ** of TK_WHEN, TK_BEGIN or TK_FOR. */ if( zSql ){ do { @@ -80529,12 +87004,12 @@ } while( dist!=2 || (token!=TK_WHEN && token!=TK_FOR && token!=TK_BEGIN) ); /* Variable tname now contains the token that is the old table-name ** in the CREATE TRIGGER statement. */ - zRet = sqlite3MPrintf(db, "%.*s\"%w\"%s", ((u8*)tname.z) - zSql, zSql, - zTableName, tname.z+tname.n); + zRet = sqlite3MPrintf(db, "%.*s\"%w\"%s", (int)(((u8*)tname.z) - zSql), + zSql, zTableName, tname.z+tname.n); sqlite3_result_text(context, zRet, -1, SQLITE_DYNAMIC); } } #endif /* !SQLITE_OMIT_TRIGGER */ @@ -80782,11 +87257,11 @@ pVTab = 0; } } #endif - /* Begin a transaction and code the VerifyCookie for database iDb. + /* Begin a transaction for database iDb. ** Then modify the schema cookie (since the ALTER TABLE modifies the ** schema). Open a statement transaction if the table is a virtual ** table. */ v = sqlite3GetVdbe(pParse); @@ -80918,10 +87393,11 @@ int j1; sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, r1, BTREE_FILE_FORMAT); sqlite3VdbeUsesBtree(v, iDb); sqlite3VdbeAddOp2(v, OP_Integer, minFormat, r2); j1 = sqlite3VdbeAddOp3(v, OP_Ge, r2, 0, r1); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, r2); sqlite3VdbeJumpHere(v, j1); sqlite3ReleaseTempReg(pParse, r1); sqlite3ReleaseTempReg(pParse, r2); } @@ -81001,11 +87477,14 @@ /* Ensure the default expression is something that sqlite3ValueFromExpr() ** can handle (i.e. not CURRENT_TIME etc.) */ if( pDflt ){ sqlite3_value *pVal = 0; - if( sqlite3ValueFromExpr(db, pDflt, SQLITE_UTF8, SQLITE_AFF_NONE, &pVal) ){ + int rc; + rc = sqlite3ValueFromExpr(db, pDflt, SQLITE_UTF8, SQLITE_AFF_NONE, &pVal); + assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); + if( rc!=SQLITE_OK ){ db->mallocFailed = 1; return; } if( !pVal ){ sqlite3ErrorMsg(pParse, "Cannot add a column with non-constant default"); @@ -81173,15 +87652,15 @@ ** The sqlite_stat2 table is superseded by sqlite_stat3, which is only ** created and used by SQLite versions 3.7.9 and later and with ** SQLITE_ENABLE_STAT3 defined. The functionality of sqlite_stat3 ** is a superset of sqlite_stat2. The sqlite_stat4 is an enhanced ** version of sqlite_stat3 and is only available when compiled with -** SQLITE_ENABLE_STAT4 and in SQLite versions 3.8.0 and later. It is +** SQLITE_ENABLE_STAT4 and in SQLite versions 3.8.1 and later. It is ** not possible to enable both STAT3 and STAT4 at the same time. If they ** are both enabled, then STAT4 takes precedence. ** -** For most applications, sqlite_stat1 provides all the statisics required +** For most applications, sqlite_stat1 provides all the statistics required ** for the query planner to make good choices. ** ** Format of sqlite_stat1: ** ** There is normally one row per index, with the index identified by the @@ -81249,16 +87728,16 @@ ** ** The sqlite_stat4 table contains multiple entries for each index. ** The idx column names the index and the tbl column is the table of the ** index. If the idx and tbl columns are the same, then the sample is ** of the INTEGER PRIMARY KEY. The sample column is a blob which is the -** binary encoding of a key from the index, with the trailing rowid -** omitted. The nEq column is a list of integers. The first integer -** is the approximate number of entries in the index whose left-most -** column exactly matches the left-most column of the sample. The second -** integer in nEq is the approximate number of entries in the index where -** the first two columns match the first two columns of the sample. +** binary encoding of a key from the index. The nEq column is a +** list of integers. The first integer is the approximate number +** of entries in the index whose left-most column exactly matches +** the left-most column of the sample. The second integer in nEq +** is the approximate number of entries in the index where the +** first two columns match the first two columns of the sample. ** And so forth. nLt is another list of integers that show the approximate ** number of entries that are strictly less than the sample. The first ** integer in nLt contains the number of entries in the index where the ** left-most column is less than the left-most column of the sample. ** The K-th integer in the nLt entry is the number of index entries @@ -81385,13 +87864,13 @@ } /* Open the sqlite_stat[134] tables for writing. */ for(i=0; aTable[i].zCols; i++){ assert( i<ArraySize(aTable) ); - sqlite3VdbeAddOp3(v, OP_OpenWrite, iStatCur+i, aRoot[i], iDb); - sqlite3VdbeChangeP4(v, -1, (char *)3, P4_INT32); + sqlite3VdbeAddOp4Int(v, OP_OpenWrite, iStatCur+i, aRoot[i], iDb, 3); sqlite3VdbeChangeP5(v, aCreateTbl[i]); + VdbeComment((v, aTable[i].zName)); } } /* ** Recommended number of samples for sqlite_stat4 @@ -81410,76 +87889,177 @@ struct Stat4Sample { tRowcnt *anEq; /* sqlite_stat4.nEq */ tRowcnt *anDLt; /* sqlite_stat4.nDLt */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 tRowcnt *anLt; /* sqlite_stat4.nLt */ - i64 iRowid; /* Rowid in main table of the key */ + union { + i64 iRowid; /* Rowid in main table of the key */ + u8 *aRowid; /* Key for WITHOUT ROWID tables */ + } u; + u32 nRowid; /* Sizeof aRowid[] */ u8 isPSample; /* True if a periodic sample */ int iCol; /* If !isPSample, the reason for inclusion */ u32 iHash; /* Tiebreaker hash */ #endif }; struct Stat4Accum { tRowcnt nRow; /* Number of rows in the entire table */ tRowcnt nPSample; /* How often to do a periodic sample */ - int nCol; /* Number of columns in index + rowid */ + int nCol; /* Number of columns in index + pk/rowid */ + int nKeyCol; /* Number of index columns w/o the pk/rowid */ int mxSample; /* Maximum number of samples to accumulate */ Stat4Sample current; /* Current row as a Stat4Sample */ u32 iPrn; /* Pseudo-random number used for sampling */ - Stat4Sample *aBest; /* Array of (nCol-1) best samples */ + Stat4Sample *aBest; /* Array of nCol best samples */ int iMin; /* Index in a[] of entry with minimum score */ int nSample; /* Current number of samples */ int iGet; /* Index of current sample accessed by stat_get() */ Stat4Sample *a; /* Array of mxSample Stat4Sample objects */ + sqlite3 *db; /* Database connection, for malloc() */ }; + +/* Reclaim memory used by a Stat4Sample +*/ +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +static void sampleClear(sqlite3 *db, Stat4Sample *p){ + assert( db!=0 ); + if( p->nRowid ){ + sqlite3DbFree(db, p->u.aRowid); + p->nRowid = 0; + } +} +#endif + +/* Initialize the BLOB value of a ROWID +*/ +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +static void sampleSetRowid(sqlite3 *db, Stat4Sample *p, int n, const u8 *pData){ + assert( db!=0 ); + if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid); + p->u.aRowid = sqlite3DbMallocRaw(db, n); + if( p->u.aRowid ){ + p->nRowid = n; + memcpy(p->u.aRowid, pData, n); + }else{ + p->nRowid = 0; + } +} +#endif + +/* Initialize the INTEGER value of a ROWID. +*/ +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +static void sampleSetRowidInt64(sqlite3 *db, Stat4Sample *p, i64 iRowid){ + assert( db!=0 ); + if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid); + p->nRowid = 0; + p->u.iRowid = iRowid; +} +#endif + /* -** Implementation of the stat_init(N,C) SQL function. The two parameters -** are the number of rows in the table or index (C) and the number of columns -** in the index (N). The second argument (C) is only used for STAT3 and STAT4. +** Copy the contents of object (*pFrom) into (*pTo). +*/ +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +static void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){ + pTo->isPSample = pFrom->isPSample; + pTo->iCol = pFrom->iCol; + pTo->iHash = pFrom->iHash; + memcpy(pTo->anEq, pFrom->anEq, sizeof(tRowcnt)*p->nCol); + memcpy(pTo->anLt, pFrom->anLt, sizeof(tRowcnt)*p->nCol); + memcpy(pTo->anDLt, pFrom->anDLt, sizeof(tRowcnt)*p->nCol); + if( pFrom->nRowid ){ + sampleSetRowid(p->db, pTo, pFrom->nRowid, pFrom->u.aRowid); + }else{ + sampleSetRowidInt64(p->db, pTo, pFrom->u.iRowid); + } +} +#endif + +/* +** Reclaim all memory of a Stat4Accum structure. +*/ +static void stat4Destructor(void *pOld){ + Stat4Accum *p = (Stat4Accum*)pOld; +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + int i; + for(i=0; i<p->nCol; i++) sampleClear(p->db, p->aBest+i); + for(i=0; i<p->mxSample; i++) sampleClear(p->db, p->a+i); + sampleClear(p->db, &p->current); +#endif + sqlite3DbFree(p->db, p); +} + +/* +** Implementation of the stat_init(N,K,C) SQL function. The three parameters +** are: +** N: The number of columns in the index including the rowid/pk (note 1) +** K: The number of columns in the index excluding the rowid/pk. +** C: The number of rows in the index (note 2) +** +** Note 1: In the special case of the covering index that implements a +** WITHOUT ROWID table, N is the number of PRIMARY KEY columns, not the +** total number of columns in the table. +** +** Note 2: C is only used for STAT3 and STAT4. +** +** For indexes on ordinary rowid tables, N==K+1. But for indexes on +** WITHOUT ROWID tables, N=K+P where P is the number of columns in the +** PRIMARY KEY of the table. The covering index that implements the +** original WITHOUT ROWID table as N==K as a special case. ** ** This routine allocates the Stat4Accum object in heap memory. The return -** value is a pointer to the the Stat4Accum object encoded as a blob (i.e. -** the size of the blob is sizeof(void*) bytes). +** value is a pointer to the Stat4Accum object. The datatype of the +** return value is BLOB, but it is really just a pointer to the Stat4Accum +** object. */ static void statInit( sqlite3_context *context, int argc, sqlite3_value **argv ){ Stat4Accum *p; int nCol; /* Number of columns in index being sampled */ + int nKeyCol; /* Number of key columns */ int nColUp; /* nCol rounded up for alignment */ int n; /* Bytes of space to allocate */ + sqlite3 *db; /* Database connection */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 int mxSample = SQLITE_STAT4_SAMPLES; #endif /* Decode the three function arguments */ UNUSED_PARAMETER(argc); nCol = sqlite3_value_int(argv[0]); - assert( nCol>1 ); /* >1 because it includes the rowid column */ + assert( nCol>0 ); nColUp = sizeof(tRowcnt)<8 ? (nCol+1)&~1 : nCol; + nKeyCol = sqlite3_value_int(argv[1]); + assert( nKeyCol<=nCol ); + assert( nKeyCol>0 ); /* Allocate the space required for the Stat4Accum object */ n = sizeof(*p) + sizeof(tRowcnt)*nColUp /* Stat4Accum.anEq */ + sizeof(tRowcnt)*nColUp /* Stat4Accum.anDLt */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + sizeof(tRowcnt)*nColUp /* Stat4Accum.anLt */ - + sizeof(Stat4Sample)*(nCol+mxSample) /* Stat4Accum.aBest[], a[] */ + + sizeof(Stat4Sample)*(nCol+mxSample) /* Stat4Accum.aBest[], a[] */ + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample) #endif ; - p = sqlite3MallocZero(n); + db = sqlite3_context_db_handle(context); + p = sqlite3DbMallocZero(db, n); if( p==0 ){ sqlite3_result_error_nomem(context); return; } + p->db = db; p->nRow = 0; p->nCol = nCol; + p->nKeyCol = nKeyCol; p->current.anDLt = (tRowcnt*)&p[1]; p->current.anEq = &p->current.anDLt[nColUp]; #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 { @@ -81486,13 +88066,13 @@ u8 *pSpace; /* Allocated space not yet assigned */ int i; /* Used to iterate through p->aSample[] */ p->iGet = -1; p->mxSample = mxSample; - p->nPSample = (tRowcnt)(sqlite3_value_int64(argv[1])/(mxSample/3+1) + 1); + p->nPSample = (tRowcnt)(sqlite3_value_int64(argv[2])/(mxSample/3+1) + 1); p->current.anLt = &p->current.anEq[nColUp]; - p->iPrn = nCol*0x689e962d ^ sqlite3_value_int(argv[1])*0xd0944565; + p->iPrn = 0x689e962d*(u32)nCol ^ 0xd0944565*(u32)sqlite3_value_int(argv[2]); /* Set up the Stat4Accum.a[] and aBest[] arrays */ p->a = (struct Stat4Sample*)&p->current.anLt[nColUp]; p->aBest = &p->a[mxSample]; pSpace = (u8*)(&p->a[mxSample+nCol]); @@ -81507,15 +88087,18 @@ p->aBest[i].iCol = i; } } #endif - /* Return a pointer to the allocated object to the caller */ - sqlite3_result_blob(context, p, sizeof(p), sqlite3_free); + /* Return a pointer to the allocated object to the caller. Note that + ** only the pointer (the 2nd parameter) matters. The size of the object + ** (given by the 3rd parameter) is never used and can be any positive + ** value. */ + sqlite3_result_blob(context, p, sizeof(*p), stat4Destructor); } static const FuncDef statInitFuncdef = { - 1+IsStat34, /* nArg */ + 2+IsStat34, /* nArg */ SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ statInit, /* xFunc */ 0, /* xStep */ @@ -81582,29 +88165,16 @@ #else return (nEqNew==nEqOld && pNew->iHash>pOld->iHash); #endif } -/* -** Copy the contents of object (*pFrom) into (*pTo). -*/ -void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){ - pTo->iRowid = pFrom->iRowid; - pTo->isPSample = pFrom->isPSample; - pTo->iCol = pFrom->iCol; - pTo->iHash = pFrom->iHash; - memcpy(pTo->anEq, pFrom->anEq, sizeof(tRowcnt)*p->nCol); - memcpy(pTo->anLt, pFrom->anLt, sizeof(tRowcnt)*p->nCol); - memcpy(pTo->anDLt, pFrom->anDLt, sizeof(tRowcnt)*p->nCol); -} - /* ** Copy the contents of sample *pNew into the p->a[] array. If necessary, ** remove the least desirable sample from p->a[] to make room. */ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ - Stat4Sample *pSample; + Stat4Sample *pSample = 0; int i; assert( IsStat4 || nEqZero==0 ); #ifdef SQLITE_ENABLE_STAT4 @@ -81640,12 +88210,14 @@ if( p->nSample>=p->mxSample ){ Stat4Sample *pMin = &p->a[p->iMin]; tRowcnt *anEq = pMin->anEq; tRowcnt *anLt = pMin->anLt; tRowcnt *anDLt = pMin->anDLt; + sampleClear(p->db, pMin); memmove(pMin, &pMin[1], sizeof(p->a[0])*(p->nSample-p->iMin-1)); pSample = &p->a[p->nSample-1]; + pSample->nRowid = 0; pSample->anEq = anEq; pSample->anDLt = anDLt; pSample->anLt = anLt; p->nSample = p->mxSample-1; } @@ -81730,23 +88302,32 @@ ){ sampleInsert(p, &p->current, 0); } } #endif + +#ifndef SQLITE_ENABLE_STAT3_OR_STAT4 + UNUSED_PARAMETER( p ); + UNUSED_PARAMETER( iChng ); +#endif } /* -** Implementation of the stat_push SQL function: stat_push(P,R,C) +** Implementation of the stat_push SQL function: stat_push(P,C,R) ** Arguments: ** ** P Pointer to the Stat4Accum object created by stat_init() ** C Index of left-most column to differ from previous row -** R Rowid for the current row +** R Rowid for the current row. Might be a key record for +** WITHOUT ROWID tables. ** -** The SQL function always returns NULL. +** This SQL function always returns NULL. It's purpose it to accumulate +** statistical data and/or samples in the Stat4Accum object about the +** index being analyzed. The stat_get() SQL function will later be used to +** extract relevant information for constructing the sqlite_statN tables. ** -** The R parameter is only used for STAT3 and STAT4. +** The R parameter is only used for STAT3 and STAT4 */ static void statPush( sqlite3_context *context, int argc, sqlite3_value **argv @@ -81755,11 +88336,13 @@ /* The three function arguments */ Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]); int iChng = sqlite3_value_int(argv[1]); - assert( p->nCol>1 ); /* Includes rowid field */ + UNUSED_PARAMETER( argc ); + UNUSED_PARAMETER( context ); + assert( p->nCol>0 ); assert( iChng<p->nCol ); if( p->nRow==0 ){ /* This is the first call to this function. Do initialization. */ for(i=0; i<p->nCol; i++) p->current.anEq[i] = 1; @@ -81780,11 +88363,16 @@ p->current.anEq[i] = 1; } } p->nRow++; #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - p->current.iRowid = sqlite3_value_int64(argv[2]); + if( sqlite3_value_type(argv[2])==SQLITE_INTEGER ){ + sampleSetRowidInt64(p->db, &p->current, sqlite3_value_int64(argv[2])); + }else{ + sampleSetRowid(p->db, &p->current, sqlite3_value_bytes(argv[2]), + sqlite3_value_blob(argv[2])); + } p->current.iHash = p->iPrn = p->iPrn*1103515245 + 12345; #endif #ifdef SQLITE_ENABLE_STAT4 { @@ -81827,11 +88415,14 @@ #define STAT_GET_NLT 3 /* "nlt" column of stat[34] entry */ #define STAT_GET_NDLT 4 /* "ndlt" column of stat[34] entry */ /* ** Implementation of the stat_get(P,J) SQL function. This routine is -** used to query the results. Content is returned for parameter J +** used to query statistical information that has been gathered into +** the Stat4Accum object by prior calls to stat_push(). The P parameter +** has type BLOB but it is really just a pointer to the Stat4Accum object. +** The content to returned is determined by the parameter J ** which is one of the STAT_GET_xxxx values defined above. ** ** If neither STAT3 nor STAT4 are enabled, then J is always ** STAT_GET_STAT1 and is hence omitted and this routine becomes ** a one-parameter function, stat_get(P), that always returns the @@ -81878,22 +88469,22 @@ ** I = (K+D-1)/D */ char *z; int i; - char *zRet = sqlite3MallocZero(p->nCol * 25); + char *zRet = sqlite3MallocZero( (p->nKeyCol+1)*25 ); if( zRet==0 ){ sqlite3_result_error_nomem(context); return; } - sqlite3_snprintf(24, zRet, "%lld", p->nRow); + sqlite3_snprintf(24, zRet, "%llu", (u64)p->nRow); z = zRet + sqlite3Strlen30(zRet); - for(i=0; i<(p->nCol-1); i++){ - i64 nDistinct = p->current.anDLt[i] + 1; - i64 iVal = (p->nRow + nDistinct - 1) / nDistinct; - sqlite3_snprintf(24, z, " %lld", iVal); + for(i=0; i<p->nKeyCol; i++){ + u64 nDistinct = p->current.anDLt[i] + 1; + u64 iVal = (p->nRow + nDistinct - 1) / nDistinct; + sqlite3_snprintf(24, z, " %llu", iVal); z += sqlite3Strlen30(z); assert( p->current.anEq[i] ); } assert( z[0]=='\0' && z>zRet ); @@ -81904,11 +88495,17 @@ if( p->iGet<0 ){ samplePushPrevious(p, 0); p->iGet = 0; } if( p->iGet<p->nSample ){ - sqlite3_result_int64(context, p->a[p->iGet].iRowid); + Stat4Sample *pS = p->a + p->iGet; + if( pS->nRowid==0 ){ + sqlite3_result_int64(context, pS->u.iRowid); + }else{ + sqlite3_result_blob(context, pS->u.aRowid, pS->nRowid, + SQLITE_TRANSIENT); + } } }else{ tRowcnt *aCnt = 0; assert( p->iGet<p->nSample ); @@ -81930,20 +88527,23 @@ sqlite3_result_error_nomem(context); }else{ int i; char *z = zRet; for(i=0; i<p->nCol; i++){ - sqlite3_snprintf(24, z, "%lld ", aCnt[i]); + sqlite3_snprintf(24, z, "%llu ", (u64)aCnt[i]); z += sqlite3Strlen30(z); } assert( z[0]=='\0' && z>zRet ); z[-1] = '\0'; sqlite3_result_text(context, zRet, -1, sqlite3_free); } } } #endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ +#ifndef SQLITE_DEBUG + UNUSED_PARAMETER( argc ); +#endif } static const FuncDef statGetFuncdef = { 1+IsStat34, /* nArg */ SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ @@ -81958,12 +88558,14 @@ static void callStatGet(Vdbe *v, int regStat4, int iParam, int regOut){ assert( regOut!=regStat4 && regOut!=regStat4+1 ); #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 sqlite3VdbeAddOp2(v, OP_Integer, iParam, regStat4+1); -#else +#elif SQLITE_DEBUG assert( iParam==STAT_GET_STAT1 ); +#else + UNUSED_PARAMETER( iParam ); #endif sqlite3VdbeAddOp3(v, OP_Function, 0, regStat4, regOut); sqlite3VdbeChangeP4(v, -1, (char*)&statGetFuncdef, P4_FUNCDEF); sqlite3VdbeChangeP5(v, 1 + IsStat34); } @@ -82035,27 +88637,31 @@ pParse->nTab = MAX(pParse->nTab, iTab); sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0); for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - int nCol; /* Number of columns indexed by pIdx */ - KeyInfo *pKey; /* KeyInfo structure for pIdx */ - int *aGotoChng; /* Array of jump instruction addresses */ + int nCol; /* Number of columns in pIdx. "N" */ int addrRewind; /* Address of "OP_Rewind iIdxCur" */ - int addrGotoChng0; /* Address of "Goto addr_chng_0" */ int addrNextRow; /* Address of "next_row:" */ + const char *zIdxName; /* Name of the index */ + int nColTest; /* Number of columns to test for changes */ if( pOnlyIdx && pOnlyIdx!=pIdx ) continue; if( pIdx->pPartIdxWhere==0 ) needTableCnt = 0; - VdbeNoopComment((v, "Begin analysis of %s", pIdx->zName)); - nCol = pIdx->nColumn; - aGotoChng = sqlite3DbMallocRaw(db, sizeof(int)*(nCol+1)); - if( aGotoChng==0 ) continue; - pKey = sqlite3IndexKeyinfo(pParse, pIdx); + if( !HasRowid(pTab) && IsPrimaryKeyIndex(pIdx) ){ + nCol = pIdx->nKeyCol; + zIdxName = pTab->zName; + nColTest = nCol - 1; + }else{ + nCol = pIdx->nColumn; + zIdxName = pIdx->zName; + nColTest = pIdx->uniqNotNull ? pIdx->nKeyCol-1 : nCol-1; + } /* Populate the register containing the index name. */ - sqlite3VdbeAddOp4(v, OP_String8, 0, regIdxname, 0, pIdx->zName, 0); + sqlite3VdbeAddOp4(v, OP_String8, 0, regIdxname, 0, zIdxName, 0); + VdbeComment((v, "Analysis for %s.%s", pTab->zName, zIdxName)); /* ** Pseudo-code for loop that calls stat_push(): ** ** Rewind csr @@ -82076,11 +88682,11 @@ ** regPrev(0) = idx(0) ** chng_addr_1: ** regPrev(1) = idx(1) ** ... ** - ** chng_addr_N: + ** endDistinctTest: ** regRowid = idx(rowid) ** stat_push(P, regChng, regRowid) ** Next csr ** if !eof(csr) goto next_row; ** @@ -82089,32 +88695,36 @@ /* Make sure there are enough memory cells allocated to accommodate ** the regPrev array and a trailing rowid (the rowid slot is required ** when building a record to insert into the sample column of ** the sqlite_stat4 table. */ - pParse->nMem = MAX(pParse->nMem, regPrev+nCol); + pParse->nMem = MAX(pParse->nMem, regPrev+nColTest); /* Open a read-only cursor on the index being analyzed. */ assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) ); sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum, iDb); - sqlite3VdbeChangeP4(v, -1, (char*)pKey, P4_KEYINFO_HANDOFF); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); /* Invoke the stat_init() function. The arguments are: ** - ** (1) the number of columns in the index including the rowid, - ** (2) the number of rows in the index, + ** (1) the number of columns in the index including the rowid + ** (or for a WITHOUT ROWID table, the number of PK columns), + ** (2) the number of columns in the key without the rowid/pk + ** (3) the number of rows in the index, ** - ** The second argument is only used for STAT3 and STAT4 + ** + ** The third argument is only used for STAT3 and STAT4 */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regStat4+2); + sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regStat4+3); #endif - sqlite3VdbeAddOp2(v, OP_Integer, nCol+1, regStat4+1); + sqlite3VdbeAddOp2(v, OP_Integer, nCol, regStat4+1); + sqlite3VdbeAddOp2(v, OP_Integer, pIdx->nKeyCol, regStat4+2); sqlite3VdbeAddOp3(v, OP_Function, 0, regStat4+1, regStat4); sqlite3VdbeChangeP4(v, -1, (char*)&statInitFuncdef, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, 1+IsStat34); + sqlite3VdbeChangeP5(v, 2+IsStat34); /* Implementation of the following: ** ** Rewind csr ** if eof(csr) goto end_of_scan; @@ -82121,69 +88731,102 @@ ** regChng = 0 ** goto next_push_0; ** */ addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur); + VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng); - addrGotoChng0 = sqlite3VdbeAddOp0(v, OP_Goto); - - /* - ** next_row: - ** regChng = 0 - ** if( idx(0) != regPrev(0) ) goto chng_addr_0 - ** regChng = 1 - ** if( idx(1) != regPrev(1) ) goto chng_addr_1 - ** ... - ** regChng = N - ** goto chng_addr_N - */ addrNextRow = sqlite3VdbeCurrentAddr(v); - for(i=0; i<nCol; i++){ - char *pColl = (char*)sqlite3LocateCollSeq(pParse, pIdx->azColl[i]); - sqlite3VdbeAddOp2(v, OP_Integer, i, regChng); - sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regTemp); - aGotoChng[i] = - sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0, regPrev+i, pColl, P4_COLLSEQ); - sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); - } - sqlite3VdbeAddOp2(v, OP_Integer, nCol, regChng); - aGotoChng[nCol] = sqlite3VdbeAddOp0(v, OP_Goto); - - /* - ** chng_addr_0: - ** regPrev(0) = idx(0) - ** chng_addr_1: - ** regPrev(1) = idx(1) - ** ... - */ - sqlite3VdbeJumpHere(v, addrGotoChng0); - for(i=0; i<nCol; i++){ - sqlite3VdbeJumpHere(v, aGotoChng[i]); - sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regPrev+i); - } - + + if( nColTest>0 ){ + int endDistinctTest = sqlite3VdbeMakeLabel(v); + int *aGotoChng; /* Array of jump instruction addresses */ + aGotoChng = sqlite3DbMallocRaw(db, sizeof(int)*nColTest); + if( aGotoChng==0 ) continue; + + /* + ** next_row: + ** regChng = 0 + ** if( idx(0) != regPrev(0) ) goto chng_addr_0 + ** regChng = 1 + ** if( idx(1) != regPrev(1) ) goto chng_addr_1 + ** ... + ** regChng = N + ** goto endDistinctTest + */ + sqlite3VdbeAddOp0(v, OP_Goto); + addrNextRow = sqlite3VdbeCurrentAddr(v); + if( nColTest==1 && pIdx->nKeyCol==1 && IsUniqueIndex(pIdx) ){ + /* For a single-column UNIQUE index, once we have found a non-NULL + ** row, we know that all the rest will be distinct, so skip + ** subsequent distinctness tests. */ + sqlite3VdbeAddOp2(v, OP_NotNull, regPrev, endDistinctTest); + VdbeCoverage(v); + } + for(i=0; i<nColTest; i++){ + char *pColl = (char*)sqlite3LocateCollSeq(pParse, pIdx->azColl[i]); + sqlite3VdbeAddOp2(v, OP_Integer, i, regChng); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regTemp); + aGotoChng[i] = + sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0, regPrev+i, pColl, P4_COLLSEQ); + sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); + VdbeCoverage(v); + } + sqlite3VdbeAddOp2(v, OP_Integer, nColTest, regChng); + sqlite3VdbeAddOp2(v, OP_Goto, 0, endDistinctTest); + + + /* + ** chng_addr_0: + ** regPrev(0) = idx(0) + ** chng_addr_1: + ** regPrev(1) = idx(1) + ** ... + */ + sqlite3VdbeJumpHere(v, addrNextRow-1); + for(i=0; i<nColTest; i++){ + sqlite3VdbeJumpHere(v, aGotoChng[i]); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regPrev+i); + } + sqlite3VdbeResolveLabel(v, endDistinctTest); + sqlite3DbFree(db, aGotoChng); + } + /* ** chng_addr_N: ** regRowid = idx(rowid) // STAT34 only ** stat_push(P, regChng, regRowid) // 3rd parameter STAT34 only ** Next csr ** if !eof(csr) goto next_row; */ - sqlite3VdbeJumpHere(v, aGotoChng[nCol]); #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, regRowid); assert( regRowid==(regStat4+2) ); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, regRowid); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable); + int j, k, regKey; + regKey = sqlite3GetTempRange(pParse, pPk->nKeyCol); + for(j=0; j<pPk->nKeyCol; j++){ + k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[j]); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, regKey+j); + VdbeComment((v, "%s", pTab->aCol[pPk->aiColumn[j]].zName)); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regKey, pPk->nKeyCol, regRowid); + sqlite3ReleaseTempRange(pParse, regKey, pPk->nKeyCol); + } #endif assert( regChng==(regStat4+1) ); sqlite3VdbeAddOp3(v, OP_Function, 1, regStat4, regTemp); sqlite3VdbeChangeP4(v, -1, (char*)&statPushFuncdef, P4_FUNCDEF); sqlite3VdbeChangeP5(v, 2+IsStat34); - sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow); + sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow); VdbeCoverage(v); /* Add the entry to the stat1 table. */ callStatGet(v, regStat4, STAT_GET_STAT1, regStat1); - sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "aaa", 0); + assert( "BBB"[0]==SQLITE_AFF_TEXT ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0); sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); /* Add the entries to the stat3 or stat4 table. */ @@ -82195,53 +88838,59 @@ int regSample = regStat1+3; int regCol = regStat1+4; int regSampleRowid = regCol + nCol; int addrNext; int addrIsNull; + u8 seekOp = HasRowid(pTab) ? OP_NotExists : OP_NotFound; - pParse->nMem = MAX(pParse->nMem, regCol+nCol+1); + pParse->nMem = MAX(pParse->nMem, regCol+nCol); addrNext = sqlite3VdbeCurrentAddr(v); callStatGet(v, regStat4, STAT_GET_ROWID, regSampleRowid); addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, regSampleRowid); + VdbeCoverage(v); callStatGet(v, regStat4, STAT_GET_NEQ, regEq); callStatGet(v, regStat4, STAT_GET_NLT, regLt); callStatGet(v, regStat4, STAT_GET_NDLT, regDLt); - sqlite3VdbeAddOp3(v, OP_NotExists, iTabCur, addrNext, regSampleRowid); + sqlite3VdbeAddOp4Int(v, seekOp, iTabCur, addrNext, regSampleRowid, 0); + /* We know that the regSampleRowid row exists because it was read by + ** the previous loop. Thus the not-found jump of seekOp will never + ** be taken */ + VdbeCoverageNeverTaken(v); #ifdef SQLITE_ENABLE_STAT3 sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, pIdx->aiColumn[0], regSample); #else for(i=0; i<nCol; i++){ - int iCol = pIdx->aiColumn[i]; + i16 iCol = pIdx->aiColumn[i]; sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, iCol, regCol+i); } - sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, nCol+1, regSample); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, nCol, regSample); #endif - sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 6, regTemp, "bbbbbb", 0); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regTabname, 6, regTemp); sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur+1, regNewRowid); sqlite3VdbeAddOp3(v, OP_Insert, iStatCur+1, regTemp, regNewRowid); - sqlite3VdbeAddOp2(v, OP_Goto, 0, addrNext); + sqlite3VdbeAddOp2(v, OP_Goto, 1, addrNext); /* P1==1 for end-of-loop */ sqlite3VdbeJumpHere(v, addrIsNull); } #endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ /* End of analysis */ sqlite3VdbeJumpHere(v, addrRewind); - sqlite3DbFree(db, aGotoChng); } /* Create a single sqlite_stat1 entry containing NULL as the index ** name and the row count as the content. */ if( pOnlyIdx==0 && needTableCnt ){ VdbeComment((v, "%s", pTab->zName)); sqlite3VdbeAddOp2(v, OP_Count, iTabCur, regStat1); - jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); + jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname); - sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "aaa", 0); + assert( "BBB"[0]==SQLITE_AFF_TEXT ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0); sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); sqlite3VdbeJumpHere(v, jZeroRows); } @@ -82326,10 +88975,11 @@ int i; char *z, *zDb; Table *pTab; Index *pIdx; Token *pTableName; + Vdbe *v; /* Read the database schema. If an error occurs, leave an error message ** and code in pParse and return NULL. */ assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ @@ -82373,10 +89023,12 @@ } sqlite3DbFree(db, z); } } } + v = sqlite3GetVdbe(pParse); + if( v ) sqlite3VdbeAddOp0(v, OP_Expire); } /* ** Used to pass information from the analyzer reader through to the ** callback routine. @@ -82391,38 +89043,66 @@ ** The first argument points to a nul-terminated string containing a ** list of space separated integers. Read the first nOut of these into ** the array aOut[]. */ static void decodeIntArray( - char *zIntArray, - int nOut, - tRowcnt *aOut, - int *pbUnordered + char *zIntArray, /* String containing int array to decode */ + int nOut, /* Number of slots in aOut[] */ + tRowcnt *aOut, /* Store integers here */ + LogEst *aLog, /* Or, if aOut==0, here */ + Index *pIndex /* Handle extra flags for this index, if not NULL */ ){ char *z = zIntArray; int c; int i; tRowcnt v; - assert( pbUnordered==0 || *pbUnordered==0 ); - #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 if( z==0 ) z = ""; #else - if( NEVER(z==0) ) z = ""; + assert( z!=0 ); #endif for(i=0; *z && i<nOut; i++){ v = 0; while( (c=z[0])>='0' && c<='9' ){ v = v*10 + c - '0'; z++; } - aOut[i] = v; +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + if( aOut ) aOut[i] = v; + if( aLog ) aLog[i] = sqlite3LogEst(v); +#else + assert( aOut==0 ); + UNUSED_PARAMETER(aOut); + assert( aLog!=0 ); + aLog[i] = sqlite3LogEst(v); +#endif if( *z==' ' ) z++; } - if( pbUnordered && strcmp(z, "unordered")==0 ){ - *pbUnordered = 1; +#ifndef SQLITE_ENABLE_STAT3_OR_STAT4 + assert( pIndex!=0 ); { +#else + if( pIndex ){ +#endif + pIndex->bUnordered = 0; + pIndex->noSkipScan = 0; + while( z[0] ){ + if( sqlite3_strglob("unordered*", z)==0 ){ + pIndex->bUnordered = 1; + }else if( sqlite3_strglob("sz=[0-9]*", z)==0 ){ + pIndex->szIdxRow = sqlite3LogEst(sqlite3Atoi(z+3)); + }else if( sqlite3_strglob("noskipscan*", z)==0 ){ + pIndex->noSkipScan = 1; + } +#ifdef SQLITE_ENABLE_COSTMULT + else if( sqlite3_strglob("costmult=[0-9]*",z)==0 ){ + pIndex->pTable->costMult = sqlite3LogEst(sqlite3Atoi(z+9)); + } +#endif + while( z[0]!=0 && z[0]!=' ' ) z++; + while( z[0]==' ' ) z++; + } } } /* ** This callback is invoked once for each index when reading the @@ -82449,24 +89129,40 @@ } pTable = sqlite3FindTable(pInfo->db, argv[0], pInfo->zDatabase); if( pTable==0 ){ return 0; } - if( argv[1] ){ - pIndex = sqlite3FindIndex(pInfo->db, argv[1], pInfo->zDatabase); - }else{ + if( argv[1]==0 ){ pIndex = 0; + }else if( sqlite3_stricmp(argv[0],argv[1])==0 ){ + pIndex = sqlite3PrimaryKeyIndex(pTable); + }else{ + pIndex = sqlite3FindIndex(pInfo->db, argv[1], pInfo->zDatabase); } z = argv[2]; if( pIndex ){ - int bUnordered = 0; - decodeIntArray((char*)z, pIndex->nColumn+1, pIndex->aiRowEst,&bUnordered); - if( pIndex->pPartIdxWhere==0 ) pTable->nRowEst = pIndex->aiRowEst[0]; - pIndex->bUnordered = bUnordered; + int nCol = pIndex->nKeyCol+1; +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + tRowcnt * const aiRowEst = pIndex->aiRowEst = (tRowcnt*)sqlite3MallocZero( + sizeof(tRowcnt) * nCol + ); + if( aiRowEst==0 ) pInfo->db->mallocFailed = 1; +#else + tRowcnt * const aiRowEst = 0; +#endif + pIndex->bUnordered = 0; + decodeIntArray((char*)z, nCol, aiRowEst, pIndex->aiRowLogEst, pIndex); + if( pIndex->pPartIdxWhere==0 ) pTable->nRowLogEst = pIndex->aiRowLogEst[0]; }else{ - decodeIntArray((char*)z, 1, &pTable->nRowEst, 0); + Index fakeIdx; + fakeIdx.szIdxRow = pTable->szTabRow; +#ifdef SQLITE_ENABLE_COSTMULT + fakeIdx.pTable = pTable; +#endif + decodeIntArray((char*)z, 1, 0, &pTable->nRowLogEst, &fakeIdx); + pTable->szTabRow = fakeIdx.szIdxRow; } return 0; } @@ -82502,37 +89198,76 @@ static void initAvgEq(Index *pIdx){ if( pIdx ){ IndexSample *aSample = pIdx->aSample; IndexSample *pFinal = &aSample[pIdx->nSample-1]; int iCol; - for(iCol=0; iCol<pIdx->nColumn; iCol++){ + int nCol = 1; + if( pIdx->nSampleCol>1 ){ + /* If this is stat4 data, then calculate aAvgEq[] values for all + ** sample columns except the last. The last is always set to 1, as + ** once the trailing PK fields are considered all index keys are + ** unique. */ + nCol = pIdx->nSampleCol-1; + pIdx->aAvgEq[nCol] = 1; + } + for(iCol=0; iCol<nCol; iCol++){ + int nSample = pIdx->nSample; int i; /* Used to iterate through samples */ tRowcnt sumEq = 0; /* Sum of the nEq values */ - tRowcnt nSum = 0; /* Number of terms contributing to sumEq */ tRowcnt avgEq = 0; - tRowcnt nDLt = pFinal->anDLt[iCol]; + tRowcnt nRow; /* Number of rows in index */ + i64 nSum100 = 0; /* Number of terms contributing to sumEq */ + i64 nDist100; /* Number of distinct values in index */ + + if( !pIdx->aiRowEst || iCol>=pIdx->nKeyCol || pIdx->aiRowEst[iCol+1]==0 ){ + nRow = pFinal->anLt[iCol]; + nDist100 = (i64)100 * pFinal->anDLt[iCol]; + nSample--; + }else{ + nRow = pIdx->aiRowEst[0]; + nDist100 = ((i64)100 * pIdx->aiRowEst[0]) / pIdx->aiRowEst[iCol+1]; + } + pIdx->nRowEst0 = nRow; /* Set nSum to the number of distinct (iCol+1) field prefixes that - ** occur in the stat4 table for this index before pFinal. Set - ** sumEq to the sum of the nEq values for column iCol for the same - ** set (adding the value only once where there exist dupicate - ** prefixes). */ - for(i=0; i<(pIdx->nSample-1); i++){ - if( aSample[i].anDLt[iCol]!=aSample[i+1].anDLt[iCol] ){ + ** occur in the stat4 table for this index. Set sumEq to the sum of + ** the nEq values for column iCol for the same set (adding the value + ** only once where there exist duplicate prefixes). */ + for(i=0; i<nSample; i++){ + if( i==(pIdx->nSample-1) + || aSample[i].anDLt[iCol]!=aSample[i+1].anDLt[iCol] + ){ sumEq += aSample[i].anEq[iCol]; - nSum++; + nSum100 += 100; } } - if( nDLt>nSum ){ - avgEq = (pFinal->anLt[iCol] - sumEq)/(nDLt - nSum); + + if( nDist100>nSum100 ){ + avgEq = ((i64)100 * (nRow - sumEq))/(nDist100 - nSum100); } if( avgEq==0 ) avgEq = 1; pIdx->aAvgEq[iCol] = avgEq; - if( pIdx->nSampleCol==1 ) break; } } } + +/* +** Look up an index by name. Or, if the name of a WITHOUT ROWID table +** is supplied instead, find the PRIMARY KEY index for that table. +*/ +static Index *findIndexOrPrimaryKey( + sqlite3 *db, + const char *zName, + const char *zDb +){ + Index *pIdx = sqlite3FindIndex(db, zName, zDb); + if( pIdx==0 ){ + Table *pTab = sqlite3FindTable(db, zName, zDb); + if( pTab && !HasRowid(pTab) ) pIdx = sqlite3PrimaryKeyIndex(pTab); + } + return pIdx; +} /* ** Load the content from either the sqlite_stat4 or sqlite_stat3 table ** into the relevant Index.aSample[] arrays. ** @@ -82567,11 +89302,10 @@ sqlite3DbFree(db, zSql); if( rc ) return rc; while( sqlite3_step(pStmt)==SQLITE_ROW ){ int nIdxCol = 1; /* Number of columns in stat4 records */ - int nAvgCol = 1; /* Number of entries in Index.aAvgEq */ char *zIndex; /* Index name */ Index *pIdx; /* Pointer to the index object */ int nSample; /* Number of samples */ int nByte; /* Bytes of space required */ @@ -82579,31 +89313,35 @@ tRowcnt *pSpace; zIndex = (char *)sqlite3_column_text(pStmt, 0); if( zIndex==0 ) continue; nSample = sqlite3_column_int(pStmt, 1); - pIdx = sqlite3FindIndex(db, zIndex, zDb); + pIdx = findIndexOrPrimaryKey(db, zIndex, zDb); assert( pIdx==0 || bStat3 || pIdx->nSample==0 ); /* Index.nSample is non-zero at this point if data has already been ** loaded from the stat4 table. In this case ignore stat3 data. */ if( pIdx==0 || pIdx->nSample ) continue; if( bStat3==0 ){ - nIdxCol = pIdx->nColumn+1; - nAvgCol = pIdx->nColumn; + assert( !HasRowid(pIdx->pTable) || pIdx->nColumn==pIdx->nKeyCol+1 ); + if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){ + nIdxCol = pIdx->nKeyCol; + }else{ + nIdxCol = pIdx->nColumn; + } } pIdx->nSampleCol = nIdxCol; nByte = sizeof(IndexSample) * nSample; nByte += sizeof(tRowcnt) * nIdxCol * 3 * nSample; - nByte += nAvgCol * sizeof(tRowcnt); /* Space for Index.aAvgEq[] */ + nByte += nIdxCol * sizeof(tRowcnt); /* Space for Index.aAvgEq[] */ pIdx->aSample = sqlite3DbMallocZero(db, nByte); if( pIdx->aSample==0 ){ sqlite3_finalize(pStmt); return SQLITE_NOMEM; } pSpace = (tRowcnt*)&pIdx->aSample[nSample]; - pIdx->aAvgEq = pSpace; pSpace += nAvgCol; + pIdx->aAvgEq = pSpace; pSpace += nIdxCol; for(i=0; i<nSample; i++){ pIdx->aSample[i].anEq = pSpace; pSpace += nIdxCol; pIdx->aSample[i].anLt = pSpace; pSpace += nIdxCol; pIdx->aSample[i].anDLt = pSpace; pSpace += nIdxCol; } @@ -82625,11 +89363,11 @@ Index *pIdx; /* Pointer to the index object */ int nCol = 1; /* Number of columns in index */ zIndex = (char *)sqlite3_column_text(pStmt, 0); if( zIndex==0 ) continue; - pIdx = sqlite3FindIndex(db, zIndex, zDb); + pIdx = findIndexOrPrimaryKey(db, zIndex, zDb); if( pIdx==0 ) continue; /* This next condition is true if data has already been loaded from ** the sqlite_stat4 table. In this case ignore stat3 data. */ nCol = pIdx->nSampleCol; if( bStat3 && nCol>1 ) continue; @@ -82636,13 +89374,13 @@ if( pIdx!=pPrevIdx ){ initAvgEq(pPrevIdx); pPrevIdx = pIdx; } pSample = &pIdx->aSample[pIdx->nSample]; - decodeIntArray((char*)sqlite3_column_text(pStmt,1), nCol, pSample->anEq, 0); - decodeIntArray((char*)sqlite3_column_text(pStmt,2), nCol, pSample->anLt, 0); - decodeIntArray((char*)sqlite3_column_text(pStmt,3), nCol, pSample->anDLt,0); + decodeIntArray((char*)sqlite3_column_text(pStmt,1),nCol,pSample->anEq,0,0); + decodeIntArray((char*)sqlite3_column_text(pStmt,2),nCol,pSample->anLt,0,0); + decodeIntArray((char*)sqlite3_column_text(pStmt,3),nCol,pSample->anDLt,0,0); /* Take a copy of the sample. Add two 0x00 bytes the end of the buffer. ** This is in case the sample record is corrupted. In that case, the ** sqlite3VdbeRecordCompare() may read up to two varints past the ** end of the allocated buffer before it realizes it is dealing with @@ -82748,15 +89486,20 @@ } /* Load the statistics from the sqlite_stat4 table. */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && OptimizationEnabled(db, SQLITE_Stat34) ){ int lookasideEnabled = db->lookaside.bEnabled; db->lookaside.bEnabled = 0; rc = loadStat4(db, sInfo.zDatabase); db->lookaside.bEnabled = lookasideEnabled; + } + for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){ + Index *pIdx = sqliteHashData(i); + sqlite3_free(pIdx->aiRowEst); + pIdx->aiRowEst = 0; } #endif if( rc==SQLITE_NOMEM ){ db->mallocFailed = 1; @@ -82806,14 +89549,10 @@ { int rc = SQLITE_OK; if( pExpr ){ if( pExpr->op!=TK_ID ){ rc = sqlite3ResolveExprNames(pName, pExpr); - if( rc==SQLITE_OK && !sqlite3ExprIsConstant(pExpr) ){ - sqlite3ErrorMsg(pName->pParse, "invalid name: \"%s\"", pExpr->u.zToken); - return SQLITE_ERROR; - } }else{ pExpr->op = TK_STRING; } } return rc; @@ -82922,17 +89661,19 @@ }else if( aNew->pSchema->file_format && aNew->pSchema->enc!=ENC(db) ){ zErrDyn = sqlite3MPrintf(db, "attached databases must use the same text encoding as main database"); rc = SQLITE_ERROR; } + sqlite3BtreeEnter(aNew->pBt); pPager = sqlite3BtreePager(aNew->pBt); sqlite3PagerLockingMode(pPager, db->dfltLockMode); sqlite3BtreeSecureDelete(aNew->pBt, sqlite3BtreeSecureDelete(db->aDb[0].pBt,-1) ); #ifndef SQLITE_OMIT_PAGER_PRAGMAS sqlite3BtreeSetPagerFlags(aNew->pBt, 3 | (db->flags & PAGER_FLAGS_MASK)); #endif + sqlite3BtreeLeave(aNew->pBt); } aNew->safety_level = 3; aNew->zName = sqlite3DbStrDup(db, zName); if( rc==SQLITE_OK && aNew->zName==0 ){ rc = SQLITE_NOMEM; @@ -82961,11 +89702,11 @@ break; case SQLITE_NULL: /* No key specified. Use the key from the main database */ sqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey); - if( nKey>0 || sqlite3BtreeGetReserve(db->aDb[0].pBt)>0 ){ + if( nKey>0 || sqlite3BtreeGetOptimalReserve(db->aDb[0].pBt)>0 ){ rc = sqlite3CodecAttach(db, db->nDb-1, zKey, nKey); } break; } } @@ -82979,10 +89720,19 @@ if( rc==SQLITE_OK ){ sqlite3BtreeEnterAll(db); rc = sqlite3Init(db, &zErrDyn); sqlite3BtreeLeaveAll(db); } +#ifdef SQLITE_USER_AUTHENTICATION + if( rc==SQLITE_OK ){ + u8 newAuth = 0; + rc = sqlite3UserAuthCheckLogin(db, zName, &newAuth); + if( newAuth<db->auth.authLevel ){ + rc = SQLITE_AUTH_USER; + } + } +#endif if( rc ){ int iDb = db->nDb - 1; assert( iDb>=2 ); if( db->aDb[iDb].pBt ){ sqlite3BtreeClose(db->aDb[iDb].pBt); @@ -83185,32 +89935,28 @@ #endif /* SQLITE_OMIT_ATTACH */ /* ** Initialize a DbFixer structure. This routine must be called prior ** to passing the structure to one of the sqliteFixAAAA() routines below. -** -** The return value indicates whether or not fixation is required. TRUE -** means we do need to fix the database references, FALSE means we do not. */ -SQLITE_PRIVATE int sqlite3FixInit( +SQLITE_PRIVATE void sqlite3FixInit( DbFixer *pFix, /* The fixer to be initialized */ Parse *pParse, /* Error messages will be written here */ int iDb, /* This is the database that must be used */ const char *zType, /* "view", "trigger", or "index" */ const Token *pName /* Name of the view, trigger, or index */ ){ sqlite3 *db; - if( NEVER(iDb<0) || iDb==1 ) return 0; db = pParse->db; assert( db->nDb>iDb ); pFix->pParse = pParse; pFix->zDb = db->aDb[iDb].zName; pFix->pSchema = db->aDb[iDb].pSchema; pFix->zType = zType; pFix->pName = pName; - return 1; + pFix->bVarOnly = (iDb==1); } /* ** The following set of routines walk through the parse tree and assign ** a specific database to all table references where the database name @@ -83234,19 +89980,21 @@ struct SrcList_item *pItem; if( NEVER(pList==0) ) return 0; zDb = pFix->zDb; for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){ - if( pItem->zDatabase && sqlite3StrICmp(pItem->zDatabase, zDb) ){ - sqlite3ErrorMsg(pFix->pParse, - "%s %T cannot reference objects in database %s", - pFix->zType, pFix->pName, pItem->zDatabase); - return 1; - } - sqlite3DbFree(pFix->pParse->db, pItem->zDatabase); - pItem->zDatabase = 0; - pItem->pSchema = pFix->pSchema; + if( pFix->bVarOnly==0 ){ + if( pItem->zDatabase && sqlite3StrICmp(pItem->zDatabase, zDb) ){ + sqlite3ErrorMsg(pFix->pParse, + "%s %T cannot reference objects in database %s", + pFix->zType, pFix->pName, pItem->zDatabase); + return 1; + } + sqlite3DbFree(pFix->pParse->db, pItem->zDatabase); + pItem->zDatabase = 0; + pItem->pSchema = pFix->pSchema; + } #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) if( sqlite3FixSelect(pFix, pItem->pSelect) ) return 1; if( sqlite3FixExpr(pFix, pItem->pOn) ) return 1; #endif } @@ -83264,13 +90012,25 @@ if( sqlite3FixSrcList(pFix, pSelect->pSrc) ){ return 1; } if( sqlite3FixExpr(pFix, pSelect->pWhere) ){ return 1; + } + if( sqlite3FixExprList(pFix, pSelect->pGroupBy) ){ + return 1; } if( sqlite3FixExpr(pFix, pSelect->pHaving) ){ return 1; + } + if( sqlite3FixExprList(pFix, pSelect->pOrderBy) ){ + return 1; + } + if( sqlite3FixExpr(pFix, pSelect->pLimit) ){ + return 1; + } + if( sqlite3FixExpr(pFix, pSelect->pOffset) ){ + return 1; } pSelect = pSelect->pPrior; } return 0; } @@ -83277,10 +90037,18 @@ SQLITE_PRIVATE int sqlite3FixExpr( DbFixer *pFix, /* Context of the fixation */ Expr *pExpr /* The expression to be fixed to one database */ ){ while( pExpr ){ + if( pExpr->op==TK_VARIABLE ){ + if( pFix->pParse->db->init.busy ){ + pExpr->op = TK_NULL; + }else{ + sqlite3ErrorMsg(pFix->pParse, "%s cannot use variables", pFix->zType); + return 1; + } + } if( ExprHasProperty(pExpr, EP_TokenOnly) ) break; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ if( sqlite3FixSelect(pFix, pExpr->x.pSelect) ) return 1; }else{ if( sqlite3FixExprList(pFix, pExpr->x.pList) ) return 1; @@ -83397,17 +90165,20 @@ ** and attempts to write the column will be ignored. ** ** Setting the auth function to NULL disables this hook. The default ** setting of the auth function is NULL. */ -SQLITE_API int sqlite3_set_authorizer( +SQLITE_API int SQLITE_STDCALL sqlite3_set_authorizer( sqlite3 *db, int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), void *pArg ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); - db->xAuth = xAuth; + db->xAuth = (sqlite3_xauth)xAuth; db->pAuthArg = pArg; sqlite3ExpirePreparedStatements(db); sqlite3_mutex_leave(db->mutex); return SQLITE_OK; } @@ -83438,11 +90209,15 @@ ){ sqlite3 *db = pParse->db; /* Database handle */ char *zDb = db->aDb[iDb].zName; /* Name of attached database */ int rc; /* Auth callback return code */ - rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext); + rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext +#ifdef SQLITE_USER_AUTHENTICATION + ,db->auth.zAuthUser +#endif + ); if( rc==SQLITE_DENY ){ if( db->nDb>2 || iDb!=0 ){ sqlite3ErrorMsg(pParse, "access to %s.%s.%s is prohibited",zDb,zTab,zCol); }else{ sqlite3ErrorMsg(pParse, "access to %s.%s is prohibited", zTab, zCol); @@ -83538,11 +90313,15 @@ } if( db->xAuth==0 ){ return SQLITE_OK; } - rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext); + rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext +#ifdef SQLITE_USER_AUTHENTICATION + ,db->auth.zAuthUser +#endif + ); if( rc==SQLITE_DENY ){ sqlite3ErrorMsg(pParse, "not authorized"); pParse->rc = SQLITE_AUTH; }else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){ rc = SQLITE_DENY; @@ -83694,10 +90473,23 @@ } #else #define codeTableLocks(x) #endif +/* +** Return TRUE if the given yDbMask object is empty - if it contains no +** 1 bits. This routine is used by the DbMaskAllZero() and DbMaskNotZero() +** macros when SQLITE_MAX_ATTACHED is greater than 30. +*/ +#if SQLITE_MAX_ATTACHED>30 +SQLITE_PRIVATE int sqlite3DbMaskAllZero(yDbMask m){ + int i; + for(i=0; i<sizeof(yDbMask); i++) if( m[i] ) return 0; + return 1; +} +#endif + /* ** This routine is called after a single SQL statement has been ** parsed and a VDBE program to execute that statement has been ** prepared. This routine puts the finishing touches on the ** VDBE program and resets the pParse structure for the next @@ -83721,42 +90513,54 @@ */ v = sqlite3GetVdbe(pParse); assert( !pParse->isMultiWrite || sqlite3VdbeAssertMayAbort(v, pParse->mayAbort)); if( v ){ + while( sqlite3VdbeDeletePriorOpcode(v, OP_Close) ){} sqlite3VdbeAddOp0(v, OP_Halt); + +#if SQLITE_USER_AUTHENTICATION + if( pParse->nTableLock>0 && db->init.busy==0 ){ + sqlite3UserAuthInit(db); + if( db->auth.authLevel<UAUTH_User ){ + pParse->rc = SQLITE_AUTH_USER; + sqlite3ErrorMsg(pParse, "user not authenticated"); + return; + } + } +#endif /* The cookie mask contains one bit for each database file open. ** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are ** set for each database that is used. Generate code to start a ** transaction on each used database and to verify the schema cookie ** on each used database. */ - if( pParse->cookieGoto>0 ){ - yDbMask mask; - int iDb; - sqlite3VdbeJumpHere(v, pParse->cookieGoto-1); - for(iDb=0, mask=1; iDb<db->nDb; mask<<=1, iDb++){ - if( (mask & pParse->cookieMask)==0 ) continue; + if( db->mallocFailed==0 + && (DbMaskNonZero(pParse->cookieMask) || pParse->pConstExpr) + ){ + int iDb, i; + assert( sqlite3VdbeGetOp(v, 0)->opcode==OP_Init ); + sqlite3VdbeJumpHere(v, 0); + for(iDb=0; iDb<db->nDb; iDb++){ + if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue; sqlite3VdbeUsesBtree(v, iDb); - sqlite3VdbeAddOp2(v,OP_Transaction, iDb, (mask & pParse->writeMask)!=0); - if( db->init.busy==0 ){ - assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - sqlite3VdbeAddOp3(v, OP_VerifyCookie, - iDb, pParse->cookieValue[iDb], - db->aDb[iDb].pSchema->iGeneration); - } + sqlite3VdbeAddOp4Int(v, + OP_Transaction, /* Opcode */ + iDb, /* P1 */ + DbMaskTest(pParse->writeMask,iDb), /* P2 */ + pParse->cookieValue[iDb], /* P3 */ + db->aDb[iDb].pSchema->iGeneration /* P4 */ + ); + if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1); } #ifndef SQLITE_OMIT_VIRTUALTABLE - { - int i; - for(i=0; i<pParse->nVtabLock; i++){ - char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]); - sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB); - } - pParse->nVtabLock = 0; - } + for(i=0; i<pParse->nVtabLock; i++){ + char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]); + sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB); + } + pParse->nVtabLock = 0; #endif /* Once all the cookies have been verified and transactions opened, ** obtain the required table-locks. This is a no-op unless the ** shared-cache feature is enabled. @@ -83764,24 +90568,29 @@ codeTableLocks(pParse); /* Initialize any AUTOINCREMENT data structures required. */ sqlite3AutoincrementBegin(pParse); + + /* Code constant expressions that where factored out of inner loops */ + if( pParse->pConstExpr ){ + ExprList *pEL = pParse->pConstExpr; + pParse->okConstFactor = 0; + for(i=0; i<pEL->nExpr; i++){ + sqlite3ExprCode(pParse, pEL->a[i].pExpr, pEL->a[i].u.iConstExprReg); + } + } /* Finally, jump back to the beginning of the executable code. */ - sqlite3VdbeAddOp2(v, OP_Goto, 0, pParse->cookieGoto); + sqlite3VdbeAddOp2(v, OP_Goto, 0, 1); } } /* Get the VDBE program ready for execution */ if( v && ALWAYS(pParse->nErr==0) && !db->mallocFailed ){ -#ifdef SQLITE_DEBUG - FILE *trace = (db->flags & SQLITE_VdbeTrace)!=0 ? stdout : 0; - sqlite3VdbeTrace(v, trace); -#endif assert( pParse->iCacheLevel==0 ); /* Disables and re-enables match */ /* A minimum of one cursor is required if autoincrement is used * See ticket [a696379c1f08866] */ if( pParse->pAinc!=0 && pParse->nTab==0 ) pParse->nTab = 1; sqlite3VdbeMakeReady(v, pParse); @@ -83792,12 +90601,11 @@ } pParse->nTab = 0; pParse->nMem = 0; pParse->nSet = 0; pParse->nVar = 0; - pParse->cookieMask = 0; - pParse->cookieGoto = 0; + DbMaskZero(pParse->cookieMask); } /* ** Run the parser and code generator recursively in order to generate ** code for the SQL statement given onto the end of the pParse context @@ -83834,10 +90642,20 @@ sqlite3DbFree(db, zSql); memcpy(&pParse->nVar, saveBuf, SAVE_SZ); pParse->nested--; } +#if SQLITE_USER_AUTHENTICATION +/* +** Return TRUE if zTable is the name of the system table that stores the +** list of users and their access credentials. +*/ +SQLITE_PRIVATE int sqlite3UserAuthTable(const char *zTable){ + return sqlite3_stricmp(zTable, "sqlite_user")==0; +} +#endif + /* ** Locate the in-memory structure that describes a particular database ** table given the name of that table and (optionally) the name of the ** database containing the table. Return NULL if not found. ** @@ -83849,20 +90667,25 @@ ** See also sqlite3LocateTable(). */ SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){ Table *p = 0; int i; - int nName; - assert( zName!=0 ); - nName = sqlite3Strlen30(zName); + /* All mutexes are required for schema access. Make sure we hold them. */ assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) ); +#if SQLITE_USER_AUTHENTICATION + /* Only the admin user is allowed to know that the sqlite_user table + ** exists */ + if( db->auth.authLevel<UAUTH_Admin && sqlite3UserAuthTable(zName)!=0 ){ + return 0; + } +#endif for(i=OMIT_TEMPDB; i<db->nDb; i++){ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ if( zDatabase!=0 && sqlite3StrICmp(zDatabase, db->aDb[j].zName) ) continue; assert( sqlite3SchemaMutexHeld(db, j, 0) ); - p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName, nName); + p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName); if( p ) break; } return p; } @@ -83898,10 +90721,16 @@ }else{ sqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName); } pParse->checkSchema = 1; } +#if SQLITE_USER_AUTHENICATION + else if( pParse->db->auth.authLevel<UAUTH_User ){ + sqlite3ErrorMsg(pParse, "user not authenticated"); + p = 0; + } +#endif return p; } /* ** Locate the table identified by *p. @@ -83941,20 +90770,19 @@ ** using the ATTACH command. */ SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const char *zDb){ Index *p = 0; int i; - int nName = sqlite3Strlen30(zName); /* All mutexes are required for schema access. Make sure we hold them. */ assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) ); for(i=OMIT_TEMPDB; i<db->nDb; i++){ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ Schema *pSchema = db->aDb[j].pSchema; assert( pSchema ); if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zName) ) continue; assert( sqlite3SchemaMutexHeld(db, j, 0) ); - p = sqlite3HashFind(&pSchema->idxHash, zName, nName); + p = sqlite3HashFind(&pSchema->idxHash, zName); if( p ) break; } return p; } @@ -83965,10 +90793,14 @@ #ifndef SQLITE_OMIT_ANALYZE sqlite3DeleteIndexSamples(db, p); #endif sqlite3ExprDelete(db, p->pPartIdxWhere); sqlite3DbFree(db, p->zColAff); + if( p->isResized ) sqlite3DbFree(db, p->azColl); +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + sqlite3_free(p->aiRowEst); +#endif sqlite3DbFree(db, p); } /* ** For the index called zIdxName which is found in the database iDb, @@ -83976,17 +90808,15 @@ ** the index hash table and free all memory structures associated ** with the index. */ SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char *zIdxName){ Index *pIndex; - int len; Hash *pHash; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); pHash = &db->aDb[iDb].pSchema->idxHash; - len = sqlite3Strlen30(zIdxName); - pIndex = sqlite3HashInsert(pHash, zIdxName, len, 0); + pIndex = sqlite3HashInsert(pHash, zIdxName, 0); if( ALWAYS(pIndex) ){ if( pIndex->pTable->pIndex==pIndex ){ pIndex->pTable->pIndex = pIndex->pNext; }else{ Index *p; @@ -84142,11 +90972,11 @@ pNext = pIndex->pNext; assert( pIndex->pSchema==pTable->pSchema ); if( !db || db->pnBytesFreed==0 ){ char *zName = pIndex->zName; TESTONLY ( Index *pOld = ) sqlite3HashInsert( - &pIndex->pSchema->idxHash, zName, sqlite3Strlen30(zName), 0 + &pIndex->pSchema->idxHash, zName, 0 ); assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) ); assert( pOld==pIndex || pOld==0 ); } freeIndex(db, pIndex); @@ -84185,12 +91015,11 @@ assert( iDb>=0 && iDb<db->nDb ); assert( zTabName ); assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); testcase( zTabName[0]==0 ); /* Zero-length table names are allowed */ pDb = &db->aDb[iDb]; - p = sqlite3HashInsert(&pDb->pSchema->tblHash, zTabName, - sqlite3Strlen30(zTabName),0); + p = sqlite3HashInsert(&pDb->pSchema->tblHash, zTabName, 0); sqlite3DeleteTable(db, p); db->flags |= SQLITE_InternChanges; } /* @@ -84222,12 +91051,11 @@ ** writing. The table is opened using cursor 0. */ SQLITE_PRIVATE void sqlite3OpenMasterTable(Parse *p, int iDb){ Vdbe *v = sqlite3GetVdbe(p); sqlite3TableLock(p, iDb, MASTER_ROOT, 1, SCHEMA_TABLE(iDb)); - sqlite3VdbeAddOp3(v, OP_OpenWrite, 0, MASTER_ROOT, iDb); - sqlite3VdbeChangeP4(v, -1, (char *)5, P4_INT32); /* 5 column table */ + sqlite3VdbeAddOp4Int(v, OP_OpenWrite, 0, MASTER_ROOT, iDb, 5); if( p->nTab==0 ){ p->nTab = 1; } } @@ -84327,10 +91155,31 @@ sqlite3ErrorMsg(pParse, "object name reserved for internal use: %s", zName); return SQLITE_ERROR; } return SQLITE_OK; } + +/* +** Return the PRIMARY KEY index of a table +*/ +SQLITE_PRIVATE Index *sqlite3PrimaryKeyIndex(Table *pTab){ + Index *p; + for(p=pTab->pIndex; p && !IsPrimaryKeyIndex(p); p=p->pNext){} + return p; +} + +/* +** Return the column of index pIdx that corresponds to table +** column iCol. Return -1 if not found. +*/ +SQLITE_PRIVATE i16 sqlite3ColumnOfIndex(Index *pIdx, i16 iCol){ + int i; + for(i=0; i<pIdx->nColumn; i++){ + if( iCol==pIdx->aiColumn[i] ) return i; + } + return -1; +} /* ** Begin constructing a new table representation in memory. This is ** the first of several action routines that get called in response ** to a CREATE TABLE statement. In particular, this routine is called @@ -84460,11 +91309,11 @@ } pTable->zName = zName; pTable->iPKey = -1; pTable->pSchema = db->aDb[iDb].pSchema; pTable->nRef = 1; - pTable->nRowEst = 1000000; + pTable->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); assert( pParse->pNewTable==0 ); pParse->pNewTable = pTable; /* If this is the magic sqlite_sequence table used by autoincrement, ** then record a pointer to this table in the main database structure @@ -84503,11 +91352,11 @@ reg1 = pParse->regRowid = ++pParse->nMem; reg2 = pParse->regRoot = ++pParse->nMem; reg3 = ++pParse->nMem; sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, reg3, BTREE_FILE_FORMAT); sqlite3VdbeUsesBtree(v, iDb); - j1 = sqlite3VdbeAddOp1(v, OP_If, reg3); + j1 = sqlite3VdbeAddOp1(v, OP_If, reg3); VdbeCoverage(v); fileFormat = (db->flags & SQLITE_LegacyFileFmt)!=0 ? 1 : SQLITE_MAX_FILE_FORMAT; sqlite3VdbeAddOp2(v, OP_Integer, fileFormat, reg3); sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, reg3); sqlite3VdbeAddOp2(v, OP_Integer, ENC(db), reg3); @@ -84527,11 +91376,11 @@ if( isView || isVirtual ){ sqlite3VdbeAddOp2(v, OP_Integer, 0, reg2); }else #endif { - sqlite3VdbeAddOp2(v, OP_CreateTable, iDb, reg2); + pParse->addrCrTab = sqlite3VdbeAddOp2(v, OP_CreateTable, iDb, reg2); } sqlite3OpenMasterTable(pParse, iDb); sqlite3VdbeAddOp2(v, OP_NewRowid, 0, reg1); sqlite3VdbeAddOp2(v, OP_Null, 0, reg3); sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1); @@ -84607,10 +91456,11 @@ /* If there is no type specified, columns have the default affinity ** 'NONE'. If there is a type specified, then sqlite3AddColumnType() will ** be called next to set pCol->affinity correctly. */ pCol->affinity = SQLITE_AFF_NONE; + pCol->szEst = 1; p->nCol++; } /* ** This routine is called by the parser while in the middle of @@ -84648,26 +91498,30 @@ ** 'DOUB' | SQLITE_AFF_REAL ** ** If none of the substrings in the above table are found, ** SQLITE_AFF_NUMERIC is returned. */ -SQLITE_PRIVATE char sqlite3AffinityType(const char *zIn){ +SQLITE_PRIVATE char sqlite3AffinityType(const char *zIn, u8 *pszEst){ u32 h = 0; char aff = SQLITE_AFF_NUMERIC; + const char *zChar = 0; - if( zIn ) while( zIn[0] ){ + if( zIn==0 ) return aff; + while( zIn[0] ){ h = (h<<8) + sqlite3UpperToLower[(*zIn)&0xff]; zIn++; if( h==(('c'<<24)+('h'<<16)+('a'<<8)+'r') ){ /* CHAR */ - aff = SQLITE_AFF_TEXT; + aff = SQLITE_AFF_TEXT; + zChar = zIn; }else if( h==(('c'<<24)+('l'<<16)+('o'<<8)+'b') ){ /* CLOB */ aff = SQLITE_AFF_TEXT; }else if( h==(('t'<<24)+('e'<<16)+('x'<<8)+'t') ){ /* TEXT */ aff = SQLITE_AFF_TEXT; }else if( h==(('b'<<24)+('l'<<16)+('o'<<8)+'b') /* BLOB */ && (aff==SQLITE_AFF_NUMERIC || aff==SQLITE_AFF_REAL) ){ aff = SQLITE_AFF_NONE; + if( zIn[0]=='(' ) zChar = zIn; #ifndef SQLITE_OMIT_FLOATING_POINT }else if( h==(('r'<<24)+('e'<<16)+('a'<<8)+'l') /* REAL */ && aff==SQLITE_AFF_NUMERIC ){ aff = SQLITE_AFF_REAL; }else if( h==(('f'<<24)+('l'<<16)+('o'<<8)+'a') /* FLOA */ @@ -84681,10 +91535,32 @@ aff = SQLITE_AFF_INTEGER; break; } } + /* If pszEst is not NULL, store an estimate of the field size. The + ** estimate is scaled so that the size of an integer is 1. */ + if( pszEst ){ + *pszEst = 1; /* default size is approx 4 bytes */ + if( aff<SQLITE_AFF_NUMERIC ){ + if( zChar ){ + while( zChar[0] ){ + if( sqlite3Isdigit(zChar[0]) ){ + int v = 0; + sqlite3GetInt32(zChar, &v); + v = v/4 + 1; + if( v>255 ) v = 255; + *pszEst = v; /* BLOB(k), VARCHAR(k), CHAR(k) -> r=(k/4+1) */ + break; + } + zChar++; + } + }else{ + *pszEst = 5; /* BLOB, TEXT, CLOB -> r=5 (approx 20 bytes)*/ + } + } + } return aff; } /* ** This routine is called by the parser while in the middle of @@ -84702,11 +91578,11 @@ p = pParse->pNewTable; if( p==0 || NEVER(p->nCol<1) ) return; pCol = &p->aCol[p->nCol-1]; assert( pCol->zType==0 ); pCol->zType = sqlite3NameFromToken(pParse->db, pType); - pCol->affinity = sqlite3AffinityType(pCol->zType); + pCol->affinity = sqlite3AffinityType(pCol->zType, &pCol->szEst); } /* ** The expression is the default value for the most recently added column ** of the table currently under construction. @@ -84722,11 +91598,11 @@ Column *pCol; sqlite3 *db = pParse->db; p = pParse->pNewTable; if( p!=0 ){ pCol = &(p->aCol[p->nCol-1]); - if( !sqlite3ExprIsConstantOrFunction(pSpan->pExpr) ){ + if( !sqlite3ExprIsConstantOrFunction(pSpan->pExpr, db->init.busy) ){ sqlite3ErrorMsg(pParse, "default value of column [%s] is not constant", pCol->zName); }else{ /* A copy of pExpr is used instead of the original, as pExpr contains ** tokens that point to volatile memory. The 'span' of the expression @@ -84768,10 +91644,11 @@ int sortOrder /* SQLITE_SO_ASC or SQLITE_SO_DESC */ ){ Table *pTab = pParse->pNewTable; char *zType = 0; int iCol = -1, i; + int nTerm; if( pTab==0 || IN_DECLARE_VTAB ) goto primary_key_exit; if( pTab->tabFlags & TF_HasPrimaryKey ){ sqlite3ErrorMsg(pParse, "table \"%s\" has more than one primary key", pTab->zName); goto primary_key_exit; @@ -84778,43 +91655,47 @@ } pTab->tabFlags |= TF_HasPrimaryKey; if( pList==0 ){ iCol = pTab->nCol - 1; pTab->aCol[iCol].colFlags |= COLFLAG_PRIMKEY; + zType = pTab->aCol[iCol].zType; + nTerm = 1; }else{ - for(i=0; i<pList->nExpr; i++){ + nTerm = pList->nExpr; + for(i=0; i<nTerm; i++){ for(iCol=0; iCol<pTab->nCol; iCol++){ if( sqlite3StrICmp(pList->a[i].zName, pTab->aCol[iCol].zName)==0 ){ + pTab->aCol[iCol].colFlags |= COLFLAG_PRIMKEY; + zType = pTab->aCol[iCol].zType; break; } } - if( iCol<pTab->nCol ){ - pTab->aCol[iCol].colFlags |= COLFLAG_PRIMKEY; - } - } - if( pList->nExpr>1 ) iCol = -1; - } - if( iCol>=0 && iCol<pTab->nCol ){ - zType = pTab->aCol[iCol].zType; - } - if( zType && sqlite3StrICmp(zType, "INTEGER")==0 - && sortOrder==SQLITE_SO_ASC ){ + } + } + if( nTerm==1 + && zType && sqlite3StrICmp(zType, "INTEGER")==0 + && sortOrder==SQLITE_SO_ASC + ){ pTab->iPKey = iCol; pTab->keyConf = (u8)onError; assert( autoInc==0 || autoInc==1 ); pTab->tabFlags |= autoInc*TF_Autoincrement; + if( pList ) pParse->iPkSortOrder = pList->a[0].sortOrder; }else if( autoInc ){ #ifndef SQLITE_OMIT_AUTOINCREMENT sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an " "INTEGER PRIMARY KEY"); #endif }else{ + Vdbe *v = pParse->pVdbe; Index *p; + if( v ) pParse->addrSkipPK = sqlite3VdbeAddOp0(v, OP_Noop); p = sqlite3CreateIndex(pParse, 0, 0, 0, pList, onError, 0, 0, sortOrder, 0); if( p ){ - p->autoIndex = 2; + p->idxType = SQLITE_IDXTYPE_PRIMARYKEY; + if( v ) sqlite3VdbeJumpHere(v, pParse->addrSkipPK); } pList = 0; } primary_key_exit: @@ -84829,11 +91710,14 @@ Parse *pParse, /* Parsing context */ Expr *pCheckExpr /* The check expression */ ){ #ifndef SQLITE_OMIT_CHECK Table *pTab = pParse->pNewTable; - if( pTab && !IN_DECLARE_VTAB ){ + sqlite3 *db = pParse->db; + if( pTab && !IN_DECLARE_VTAB + && !sqlite3BtreeIsReadonly(db->aDb[db->init.iDb].pBt) + ){ pTab->pCheck = sqlite3ExprListAppend(pParse, pTab->pCheck, pCheckExpr); if( pParse->constraintName.n ){ sqlite3ExprListSetName(pParse, pTab->pCheck, &pParse->constraintName, 1); } }else @@ -84867,11 +91751,11 @@ /* If the column is declared as "<name> PRIMARY KEY COLLATE <type>", ** then an index may have been created on this column before the ** collation type was added. Correct this if it is the case. */ for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){ - assert( pIdx->nColumn==1 ); + assert( pIdx->nKeyCol==1 ); if( pIdx->aiColumn[0]==i ){ pIdx->azColl[0] = p->aCol[i].zColl; } } }else{ @@ -84975,14 +91859,14 @@ i = *pIdx; for(j=0; zIdent[j]; j++){ if( !sqlite3Isalnum(zIdent[j]) && zIdent[j]!='_' ) break; } - needQuote = sqlite3Isdigit(zIdent[0]) || sqlite3KeywordCode(zIdent, j)!=TK_ID; - if( !needQuote ){ - needQuote = zIdent[j]; - } + needQuote = sqlite3Isdigit(zIdent[0]) + || sqlite3KeywordCode(zIdent, j)!=TK_ID + || zIdent[j]!=0 + || j==0; if( needQuote ) z[i++] = '"'; for(j=0; zIdent[j]; j++){ z[i++] = zIdent[j]; if( zIdent[j]=='"' ) z[i++] = '"'; @@ -85026,12 +91910,12 @@ k = sqlite3Strlen30(zStmt); identPut(zStmt, &k, p->zName); zStmt[k++] = '('; for(pCol=p->aCol, i=0; i<p->nCol; i++, pCol++){ static const char * const azType[] = { - /* SQLITE_AFF_TEXT */ " TEXT", /* SQLITE_AFF_NONE */ "", + /* SQLITE_AFF_TEXT */ " TEXT", /* SQLITE_AFF_NUMERIC */ " NUM", /* SQLITE_AFF_INTEGER */ " INT", /* SQLITE_AFF_REAL */ " REAL" }; int len; @@ -85039,29 +91923,230 @@ sqlite3_snprintf(n-k, &zStmt[k], zSep); k += sqlite3Strlen30(&zStmt[k]); zSep = zSep2; identPut(zStmt, &k, pCol->zName); - assert( pCol->affinity-SQLITE_AFF_TEXT >= 0 ); - assert( pCol->affinity-SQLITE_AFF_TEXT < ArraySize(azType) ); - testcase( pCol->affinity==SQLITE_AFF_TEXT ); + assert( pCol->affinity-SQLITE_AFF_NONE >= 0 ); + assert( pCol->affinity-SQLITE_AFF_NONE < ArraySize(azType) ); testcase( pCol->affinity==SQLITE_AFF_NONE ); + testcase( pCol->affinity==SQLITE_AFF_TEXT ); testcase( pCol->affinity==SQLITE_AFF_NUMERIC ); testcase( pCol->affinity==SQLITE_AFF_INTEGER ); testcase( pCol->affinity==SQLITE_AFF_REAL ); - zType = azType[pCol->affinity - SQLITE_AFF_TEXT]; + zType = azType[pCol->affinity - SQLITE_AFF_NONE]; len = sqlite3Strlen30(zType); assert( pCol->affinity==SQLITE_AFF_NONE - || pCol->affinity==sqlite3AffinityType(zType) ); + || pCol->affinity==sqlite3AffinityType(zType, 0) ); memcpy(&zStmt[k], zType, len); k += len; assert( k<=n ); } sqlite3_snprintf(n-k, &zStmt[k], "%s", zEnd); return zStmt; } + +/* +** Resize an Index object to hold N columns total. Return SQLITE_OK +** on success and SQLITE_NOMEM on an OOM error. +*/ +static int resizeIndexObject(sqlite3 *db, Index *pIdx, int N){ + char *zExtra; + int nByte; + if( pIdx->nColumn>=N ) return SQLITE_OK; + assert( pIdx->isResized==0 ); + nByte = (sizeof(char*) + sizeof(i16) + 1)*N; + zExtra = sqlite3DbMallocZero(db, nByte); + if( zExtra==0 ) return SQLITE_NOMEM; + memcpy(zExtra, pIdx->azColl, sizeof(char*)*pIdx->nColumn); + pIdx->azColl = (char**)zExtra; + zExtra += sizeof(char*)*N; + memcpy(zExtra, pIdx->aiColumn, sizeof(i16)*pIdx->nColumn); + pIdx->aiColumn = (i16*)zExtra; + zExtra += sizeof(i16)*N; + memcpy(zExtra, pIdx->aSortOrder, pIdx->nColumn); + pIdx->aSortOrder = (u8*)zExtra; + pIdx->nColumn = N; + pIdx->isResized = 1; + return SQLITE_OK; +} + +/* +** Estimate the total row width for a table. +*/ +static void estimateTableWidth(Table *pTab){ + unsigned wTable = 0; + const Column *pTabCol; + int i; + for(i=pTab->nCol, pTabCol=pTab->aCol; i>0; i--, pTabCol++){ + wTable += pTabCol->szEst; + } + if( pTab->iPKey<0 ) wTable++; + pTab->szTabRow = sqlite3LogEst(wTable*4); +} + +/* +** Estimate the average size of a row for an index. +*/ +static void estimateIndexWidth(Index *pIdx){ + unsigned wIndex = 0; + int i; + const Column *aCol = pIdx->pTable->aCol; + for(i=0; i<pIdx->nColumn; i++){ + i16 x = pIdx->aiColumn[i]; + assert( x<pIdx->pTable->nCol ); + wIndex += x<0 ? 1 : aCol[pIdx->aiColumn[i]].szEst; + } + pIdx->szIdxRow = sqlite3LogEst(wIndex*4); +} + +/* Return true if value x is found any of the first nCol entries of aiCol[] +*/ +static int hasColumn(const i16 *aiCol, int nCol, int x){ + while( nCol-- > 0 ) if( x==*(aiCol++) ) return 1; + return 0; +} + +/* +** This routine runs at the end of parsing a CREATE TABLE statement that +** has a WITHOUT ROWID clause. The job of this routine is to convert both +** internal schema data structures and the generated VDBE code so that they +** are appropriate for a WITHOUT ROWID table instead of a rowid table. +** Changes include: +** +** (1) Convert the OP_CreateTable into an OP_CreateIndex. There is +** no rowid btree for a WITHOUT ROWID. Instead, the canonical +** data storage is a covering index btree. +** (2) Bypass the creation of the sqlite_master table entry +** for the PRIMARY KEY as the primary key index is now +** identified by the sqlite_master table entry of the table itself. +** (3) Set the Index.tnum of the PRIMARY KEY Index object in the +** schema to the rootpage from the main table. +** (4) Set all columns of the PRIMARY KEY schema object to be NOT NULL. +** (5) Add all table columns to the PRIMARY KEY Index object +** so that the PRIMARY KEY is a covering index. The surplus +** columns are part of KeyInfo.nXField and are not used for +** sorting or lookup or uniqueness checks. +** (6) Replace the rowid tail on all automatically generated UNIQUE +** indices with the PRIMARY KEY columns. +*/ +static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ + Index *pIdx; + Index *pPk; + int nPk; + int i, j; + sqlite3 *db = pParse->db; + Vdbe *v = pParse->pVdbe; + + /* Convert the OP_CreateTable opcode that would normally create the + ** root-page for the table into an OP_CreateIndex opcode. The index + ** created will become the PRIMARY KEY index. + */ + if( pParse->addrCrTab ){ + assert( v ); + sqlite3VdbeGetOp(v, pParse->addrCrTab)->opcode = OP_CreateIndex; + } + + /* Bypass the creation of the PRIMARY KEY btree and the sqlite_master + ** table entry. + */ + if( pParse->addrSkipPK ){ + assert( v ); + sqlite3VdbeGetOp(v, pParse->addrSkipPK)->opcode = OP_Goto; + } + + /* Locate the PRIMARY KEY index. Or, if this table was originally + ** an INTEGER PRIMARY KEY table, create a new PRIMARY KEY index. + */ + if( pTab->iPKey>=0 ){ + ExprList *pList; + pList = sqlite3ExprListAppend(pParse, 0, 0); + if( pList==0 ) return; + pList->a[0].zName = sqlite3DbStrDup(pParse->db, + pTab->aCol[pTab->iPKey].zName); + pList->a[0].sortOrder = pParse->iPkSortOrder; + assert( pParse->pNewTable==pTab ); + pPk = sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0); + if( pPk==0 ) return; + pPk->idxType = SQLITE_IDXTYPE_PRIMARYKEY; + pTab->iPKey = -1; + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + /* + ** Remove all redundant columns from the PRIMARY KEY. For example, change + ** "PRIMARY KEY(a,b,a,b,c,b,c,d)" into just "PRIMARY KEY(a,b,c,d)". Later + ** code assumes the PRIMARY KEY contains no repeated columns. + */ + for(i=j=1; i<pPk->nKeyCol; i++){ + if( hasColumn(pPk->aiColumn, j, pPk->aiColumn[i]) ){ + pPk->nColumn--; + }else{ + pPk->aiColumn[j++] = pPk->aiColumn[i]; + } + } + pPk->nKeyCol = j; + } + pPk->isCovering = 1; + assert( pPk!=0 ); + nPk = pPk->nKeyCol; + + /* Make sure every column of the PRIMARY KEY is NOT NULL. (Except, + ** do not enforce this for imposter tables.) */ + if( !db->init.imposterTable ){ + for(i=0; i<nPk; i++){ + pTab->aCol[pPk->aiColumn[i]].notNull = 1; + } + pPk->uniqNotNull = 1; + } + + /* The root page of the PRIMARY KEY is the table root page */ + pPk->tnum = pTab->tnum; + + /* Update the in-memory representation of all UNIQUE indices by converting + ** the final rowid column into one or more columns of the PRIMARY KEY. + */ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + int n; + if( IsPrimaryKeyIndex(pIdx) ) continue; + for(i=n=0; i<nPk; i++){ + if( !hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ) n++; + } + if( n==0 ){ + /* This index is a superset of the primary key */ + pIdx->nColumn = pIdx->nKeyCol; + continue; + } + if( resizeIndexObject(db, pIdx, pIdx->nKeyCol+n) ) return; + for(i=0, j=pIdx->nKeyCol; i<nPk; i++){ + if( !hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ){ + pIdx->aiColumn[j] = pPk->aiColumn[i]; + pIdx->azColl[j] = pPk->azColl[i]; + j++; + } + } + assert( pIdx->nColumn>=pIdx->nKeyCol+n ); + assert( pIdx->nColumn>=j ); + } + + /* Add all table columns to the PRIMARY KEY index + */ + if( nPk<pTab->nCol ){ + if( resizeIndexObject(db, pPk, pTab->nCol) ) return; + for(i=0, j=nPk; i<pTab->nCol; i++){ + if( !hasColumn(pPk->aiColumn, j, i) ){ + assert( j<pPk->nColumn ); + pPk->aiColumn[j] = i; + pPk->azColl[j] = "BINARY"; + j++; + } + } + assert( pPk->nColumn==j ); + assert( pTab->nCol==j ); + }else{ + pPk->nColumn = pTab->nCol; + } +} /* ** This routine is called to report the final ")" that terminates ** a CREATE TABLE statement. ** @@ -85082,24 +92167,51 @@ ** the new table will match the result set of the SELECT. */ SQLITE_PRIVATE void sqlite3EndTable( Parse *pParse, /* Parse context */ Token *pCons, /* The ',' token after the last column defn. */ - Token *pEnd, /* The final ')' token in the CREATE TABLE */ + Token *pEnd, /* The ')' before options in the CREATE TABLE */ + u8 tabOpts, /* Extra table options. Usually 0. */ Select *pSelect /* Select from a "CREATE ... AS SELECT" */ ){ - Table *p; - sqlite3 *db = pParse->db; - int iDb; + Table *p; /* The new table */ + sqlite3 *db = pParse->db; /* The database connection */ + int iDb; /* Database in which the table lives */ + Index *pIdx; /* An implied index of the table */ if( (pEnd==0 && pSelect==0) || db->mallocFailed ){ return; } p = pParse->pNewTable; if( p==0 ) return; assert( !db->init.busy || !pSelect ); + + /* If the db->init.busy is 1 it means we are reading the SQL off the + ** "sqlite_master" or "sqlite_temp_master" table on the disk. + ** So do not write to the disk again. Extract the root page number + ** for the table from the db->init.newTnum field. (The page number + ** should have been put there by the sqliteOpenCb routine.) + */ + if( db->init.busy ){ + p->tnum = db->init.newTnum; + } + + /* Special processing for WITHOUT ROWID Tables */ + if( tabOpts & TF_WithoutRowid ){ + if( (p->tabFlags & TF_Autoincrement) ){ + sqlite3ErrorMsg(pParse, + "AUTOINCREMENT not allowed on WITHOUT ROWID tables"); + return; + } + if( (p->tabFlags & TF_HasPrimaryKey)==0 ){ + sqlite3ErrorMsg(pParse, "PRIMARY KEY missing on table %s", p->zName); + }else{ + p->tabFlags |= TF_WithoutRowid; + convertToWithoutRowidTable(pParse, p); + } + } iDb = sqlite3SchemaToIndex(db, p->pSchema); #ifndef SQLITE_OMIT_CHECK /* Resolve names in all CHECK constraint expressions. @@ -85107,18 +92219,14 @@ if( p->pCheck ){ sqlite3ResolveSelfReference(pParse, p, NC_IsCheck, 0, p->pCheck); } #endif /* !defined(SQLITE_OMIT_CHECK) */ - /* If the db->init.busy is 1 it means we are reading the SQL off the - ** "sqlite_master" or "sqlite_temp_master" table on the disk. - ** So do not write to the disk again. Extract the root page number - ** for the table from the db->init.newTnum field. (The page number - ** should have been put there by the sqliteOpenCb routine.) - */ - if( db->init.busy ){ - p->tnum = db->init.newTnum; + /* Estimate the average row size for the table and for all implied indices */ + estimateTableWidth(p); + for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){ + estimateIndexWidth(pIdx); } /* If not initializing, then create a record for the new table ** in the SQLITE_MASTER table of the database. ** @@ -85190,11 +92298,13 @@ /* Compute the complete text of the CREATE statement */ if( pSelect ){ zStmt = createTableStmt(db, p); }else{ - n = (int)(pEnd->z - pParse->sNameToken.z) + 1; + Token *pEnd2 = tabOpts ? &pParse->sLastToken : pEnd; + n = (int)(pEnd2->z - pParse->sNameToken.z); + if( pEnd2->z[0]!=';' ) n += pEnd2->n; zStmt = sqlite3MPrintf(db, "CREATE %s %.*s", zType2, n, pParse->sNameToken.z ); } @@ -85233,22 +92343,21 @@ } #endif /* Reparse everything to update our internal data structures */ sqlite3VdbeAddParseSchemaOp(v, iDb, - sqlite3MPrintf(db, "tbl_name='%q'", p->zName)); + sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName)); } /* Add the table to the in-memory representation of the database. */ if( db->init.busy ){ Table *pOld; Schema *pSchema = p->pSchema; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName, - sqlite3Strlen30(p->zName),p); + pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName, p); if( pOld ){ assert( p==pOld ); /* Malloc must have failed inside HashInsert() */ db->mallocFailed = 1; return; } @@ -85303,13 +92412,12 @@ sqlite3SelectDelete(db, pSelect); return; } sqlite3TwoPartName(pParse, pName1, pName2, &pName); iDb = sqlite3SchemaToIndex(db, p->pSchema); - if( sqlite3FixInit(&sFix, pParse, iDb, "view", pName) - && sqlite3FixSelect(&sFix, pSelect) - ){ + sqlite3FixInit(&sFix, pParse, iDb, "view", pName); + if( sqlite3FixSelect(&sFix, pSelect) ){ sqlite3SelectDelete(db, pSelect); return; } /* Make a copy of the entire SELECT statement that defines the view. @@ -85339,11 +92447,11 @@ while( ALWAYS(n>0) && sqlite3Isspace(z[n-1]) ){ n--; } sEnd.z = &z[n-1]; sEnd.n = 1; /* Use sqlite3EndTable() to add the view to the SQLITE_MASTER table */ - sqlite3EndTable(pParse, 0, &sEnd, 0); + sqlite3EndTable(pParse, 0, &sEnd, 0, 0); return; } #endif /* SQLITE_OMIT_VIEW */ #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) @@ -85356,11 +92464,11 @@ Table *pSelTab; /* A fake table from which we get the result set */ Select *pSel; /* Copy of the SELECT that implements the view */ int nErr = 0; /* Number of errors encountered */ int n; /* Temporarily holds the number of cursors assigned */ sqlite3 *db = pParse->db; /* Database connection for malloc errors */ - int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); + sqlite3_xauth xAuth; /* Saved xAuth pointer */ assert( pTable ); #ifndef SQLITE_OMIT_VIRTUALTABLE if( sqlite3VtabCallConnect(pParse, pTable) ){ @@ -85427,11 +92535,11 @@ pTable->aCol = pSelTab->aCol; pSelTab->nCol = 0; pSelTab->aCol = 0; sqlite3DeleteTable(db, pSelTab); assert( sqlite3SchemaMutexHeld(db, 0, pTable->pSchema) ); - pTable->pSchema->flags |= DB_UnresetViews; + pTable->pSchema->schemaFlags |= DB_UnresetViews; }else{ pTable->nCol = 0; nErr++; } sqlite3SelectDelete(db, pSel); @@ -85794,12 +92902,12 @@ /* ** This routine is called to create a new foreign key on the table ** currently under construction. pFromCol determines which columns ** in the current table point to the foreign key. If pFromCol==0 then ** connect the key to the last column inserted. pTo is the name of -** the table referred to. pToCol is a list of tables in the other -** pTo table that the foreign key points to. flags contains all +** the table referred to (a.k.a the "parent" table). pToCol is a list +** of tables in the parent pTo table. flags contains all ** information about the conflict resolution algorithms specified ** in the ON DELETE, ON UPDATE and ON INSERT clauses. ** ** An FKey structure is created and added to the table currently ** under construction in the pParse->pNewTable field. @@ -85895,11 +93003,11 @@ pFKey->aAction[0] = (u8)(flags & 0xff); /* ON DELETE action */ pFKey->aAction[1] = (u8)((flags >> 8 ) & 0xff); /* ON UPDATE action */ assert( sqlite3SchemaMutexHeld(db, 0, p->pSchema) ); pNextTo = (FKey *)sqlite3HashInsert(&p->pSchema->fkeyHash, - pFKey->zTo, sqlite3Strlen30(pFKey->zTo), (void *)pFKey + pFKey->zTo, (void *)pFKey ); if( pNextTo==pFKey ){ db->mallocFailed = 1; goto fk_end; } @@ -85958,11 +93066,11 @@ int addr2; /* Address to jump to for next iteration */ int tnum; /* Root page of index */ int iPartIdxLabel; /* Jump to this label to skip a row */ Vdbe *v; /* Generate code into this virtual machine */ KeyInfo *pKey; /* KeyInfo for index */ - int regRecord; /* Register holding assemblied index record */ + int regRecord; /* Register holding assembled index record */ sqlite3 *db = pParse->db; /* The database connection */ int iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); #ifndef SQLITE_OMIT_AUTHORIZATION if( sqlite3AuthCheck(pParse, SQLITE_REINDEX, pIndex->zName, 0, @@ -85978,55 +93086,92 @@ if( v==0 ) return; if( memRootPage>=0 ){ tnum = memRootPage; }else{ tnum = pIndex->tnum; - sqlite3VdbeAddOp2(v, OP_Clear, tnum, iDb); } - pKey = sqlite3IndexKeyinfo(pParse, pIndex); - sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, tnum, iDb, - (char *)pKey, P4_KEYINFO_HANDOFF); - sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR|((memRootPage>=0)?OPFLAG_P2ISREG:0)); + pKey = sqlite3KeyInfoOfIndex(pParse, pIndex); /* Open the sorter cursor if we are to use one. */ iSorter = pParse->nTab++; - sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, 0, (char*)pKey, P4_KEYINFO); + sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, pIndex->nKeyCol, (char*) + sqlite3KeyInfoRef(pKey), P4_KEYINFO); /* Open the table. Loop through all rows of the table, inserting index ** records into the sorter. */ sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead); - addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0); + addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0); VdbeCoverage(v); regRecord = sqlite3GetTempReg(pParse); - sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1, &iPartIdxLabel); + sqlite3GenerateIndexKey(pParse,pIndex,iTab,regRecord,0,&iPartIdxLabel,0,0); sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord); - sqlite3VdbeResolveLabel(v, iPartIdxLabel); - sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1); + sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel); + sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addr1); - addr1 = sqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0); - if( pIndex->onError!=OE_None ){ + if( memRootPage<0 ) sqlite3VdbeAddOp2(v, OP_Clear, tnum, iDb); + sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, tnum, iDb, + (char *)pKey, P4_KEYINFO); + sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR|((memRootPage>=0)?OPFLAG_P2ISREG:0)); + + addr1 = sqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0); VdbeCoverage(v); + assert( pKey!=0 || db->mallocFailed || pParse->nErr ); + if( IsUniqueIndex(pIndex) && pKey!=0 ){ int j2 = sqlite3VdbeCurrentAddr(v) + 3; sqlite3VdbeAddOp2(v, OP_Goto, 0, j2); addr2 = sqlite3VdbeCurrentAddr(v); - sqlite3VdbeAddOp3(v, OP_SorterCompare, iSorter, j2, regRecord); - sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_UNIQUE, - OE_Abort, "indexed columns are not unique", P4_STATIC - ); + sqlite3VdbeAddOp4Int(v, OP_SorterCompare, iSorter, j2, regRecord, + pIndex->nKeyCol); VdbeCoverage(v); + sqlite3UniqueConstraint(pParse, OE_Abort, pIndex); }else{ addr2 = sqlite3VdbeCurrentAddr(v); } - sqlite3VdbeAddOp2(v, OP_SorterData, iSorter, regRecord); + sqlite3VdbeAddOp3(v, OP_SorterData, iSorter, regRecord, iIdx); sqlite3VdbeAddOp3(v, OP_IdxInsert, iIdx, regRecord, 1); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); sqlite3ReleaseTempReg(pParse, regRecord); - sqlite3VdbeAddOp2(v, OP_SorterNext, iSorter, addr2); + sqlite3VdbeAddOp2(v, OP_SorterNext, iSorter, addr2); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addr1); sqlite3VdbeAddOp1(v, OP_Close, iTab); sqlite3VdbeAddOp1(v, OP_Close, iIdx); sqlite3VdbeAddOp1(v, OP_Close, iSorter); } + +/* +** Allocate heap space to hold an Index object with nCol columns. +** +** Increase the allocation size to provide an extra nExtra bytes +** of 8-byte aligned space after the Index object and return a +** pointer to this extra space in *ppExtra. +*/ +SQLITE_PRIVATE Index *sqlite3AllocateIndexObject( + sqlite3 *db, /* Database connection */ + i16 nCol, /* Total number of columns in the index */ + int nExtra, /* Number of bytes of extra space to alloc */ + char **ppExtra /* Pointer to the "extra" space */ +){ + Index *p; /* Allocated index object */ + int nByte; /* Bytes of space for Index object + arrays */ + + nByte = ROUND8(sizeof(Index)) + /* Index structure */ + ROUND8(sizeof(char*)*nCol) + /* Index.azColl */ + ROUND8(sizeof(LogEst)*(nCol+1) + /* Index.aiRowLogEst */ + sizeof(i16)*nCol + /* Index.aiColumn */ + sizeof(u8)*nCol); /* Index.aSortOrder */ + p = sqlite3DbMallocZero(db, nByte + nExtra); + if( p ){ + char *pExtra = ((char*)p)+ROUND8(sizeof(Index)); + p->azColl = (char**)pExtra; pExtra += ROUND8(sizeof(char*)*nCol); + p->aiRowLogEst = (LogEst*)pExtra; pExtra += sizeof(LogEst)*(nCol+1); + p->aiColumn = (i16*)pExtra; pExtra += sizeof(i16)*nCol; + p->aSortOrder = (u8*)pExtra; + p->nColumn = nCol; + p->nKeyCol = nCol - 1; + *ppExtra = ((char*)p) + nByte; + } + return p; +} /* ** Create a new index for an SQL table. pName1.pName2 is the name of the index ** and pTblList is the name of the table that is to be indexed. Both will ** be NULL for a primary key or an index that is created to satisfy a @@ -86038,11 +93183,11 @@ ** is a primary key or unique-constraint on the most recent column added ** to the table currently under construction. ** ** If the index is created successfully, return a pointer to the new Index ** structure. This is used by sqlite3AddPrimaryKey() to mark the index -** as the tables primary key (Index.autoIndex==2). +** as the tables primary key (Index.idxType==SQLITE_IDXTYPE_PRIMARYKEY) */ SQLITE_PRIVATE Index *sqlite3CreateIndex( Parse *pParse, /* All information about this parse */ Token *pName1, /* First part of index name. May be NULL */ Token *pName2, /* Second part of index name. May be NULL */ @@ -86058,21 +93203,22 @@ Table *pTab = 0; /* Table to be indexed */ Index *pIndex = 0; /* The index to be created */ char *zName = 0; /* Name of the index */ int nName; /* Number of characters in zName */ int i, j; - Token nullId; /* Fake token for an empty ID list */ DbFixer sFix; /* For assigning database names to pTable */ int sortOrderMask; /* 1 to honor DESC in index. 0 to ignore. */ sqlite3 *db = pParse->db; Db *pDb; /* The specific table containing the indexed database */ int iDb; /* Index of the database that is being written */ Token *pName = 0; /* Unqualified name of the index to create */ struct ExprList_item *pListItem; /* For looping over pList */ - int nCol; - int nExtra = 0; - char *zExtra; + const Column *pTabCol; /* A column in the table */ + int nExtra = 0; /* Space allocated for zExtra[] */ + int nExtraCol; /* Number of extra columns needed */ + char *zExtra = 0; /* Extra space after the Index object */ + Index *pPk = 0; /* PRIMARY KEY index for WITHOUT ROWID tables */ assert( pParse->nErr==0 ); /* Never called with prior errors */ if( db->mallocFailed || IN_DECLARE_VTAB ){ goto exit_create_index; } @@ -86105,13 +93251,12 @@ iDb = 1; } } #endif - if( sqlite3FixInit(&sFix, pParse, iDb, "index", pName) && - sqlite3FixSrcList(&sFix, pTblName) - ){ + sqlite3FixInit(&sFix, pParse, iDb, "index", pName); + if( sqlite3FixSrcList(&sFix, pTblName) ){ /* Because the parser constructs pTblName from a single identifier, ** sqlite3FixSrcList can never fail. */ assert(0); } pTab = sqlite3LocateTableItem(pParse, 0, &pTblName->a[0]); @@ -86121,10 +93266,11 @@ sqlite3ErrorMsg(pParse, "cannot create a TEMP index on non-TEMP table \"%s\"", pTab->zName); goto exit_create_index; } + if( !HasRowid(pTab) ) pPk = sqlite3PrimaryKeyIndex(pTab); }else{ assert( pName==0 ); assert( pStart==0 ); pTab = pParse->pNewTable; if( !pTab ) goto exit_create_index; @@ -86133,10 +93279,14 @@ pDb = &db->aDb[iDb]; assert( pTab!=0 ); assert( pParse->nErr==0 ); if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 + && db->init.busy==0 +#if SQLITE_USER_AUTHENTICATION + && sqlite3UserAuthTable(pTab->zName)==0 +#endif && sqlite3StrNICmp(&pTab->zName[7],"altertab_",9)!=0 ){ sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName); goto exit_create_index; } #ifndef SQLITE_OMIT_VIEW @@ -86216,15 +93366,14 @@ /* If pList==0, it means this routine was called to make a primary ** key out of the last column added to the table under construction. ** So create a fake list to simulate this. */ if( pList==0 ){ - nullId.z = pTab->aCol[pTab->nCol-1].zName; - nullId.n = sqlite3Strlen30((char*)nullId.z); pList = sqlite3ExprListAppend(pParse, 0, 0); if( pList==0 ) goto exit_create_index; - sqlite3ExprListSetName(pParse, pList, &nullId, 0); + pList->a[0].zName = sqlite3DbStrDup(pParse->db, + pTab->aCol[pTab->nCol-1].zName); pList->a[0].sortOrder = (u8)sortOrder; } /* Figure out how many bytes of space are required to store explicitly ** specified collation sequence names. @@ -86239,40 +93388,27 @@ /* ** Allocate the index structure. */ nName = sqlite3Strlen30(zName); - nCol = pList->nExpr; - pIndex = sqlite3DbMallocZero(db, - ROUND8(sizeof(Index)) + /* Index structure */ - ROUND8(sizeof(tRowcnt)*(nCol+1)) + /* Index.aiRowEst */ - sizeof(char *)*nCol + /* Index.azColl */ - sizeof(int)*nCol + /* Index.aiColumn */ - sizeof(u8)*nCol + /* Index.aSortOrder */ - nName + 1 + /* Index.zName */ - nExtra /* Collation sequence names */ - ); + nExtraCol = pPk ? pPk->nKeyCol : 1; + pIndex = sqlite3AllocateIndexObject(db, pList->nExpr + nExtraCol, + nName + nExtra + 1, &zExtra); if( db->mallocFailed ){ goto exit_create_index; } - zExtra = (char*)pIndex; - pIndex->aiRowEst = (tRowcnt*)&zExtra[ROUND8(sizeof(Index))]; - pIndex->azColl = (char**) - ((char*)pIndex->aiRowEst + ROUND8(sizeof(tRowcnt)*nCol+1)); - assert( EIGHT_BYTE_ALIGNMENT(pIndex->aiRowEst) ); + assert( EIGHT_BYTE_ALIGNMENT(pIndex->aiRowLogEst) ); assert( EIGHT_BYTE_ALIGNMENT(pIndex->azColl) ); - pIndex->aiColumn = (int *)(&pIndex->azColl[nCol]); - pIndex->aSortOrder = (u8 *)(&pIndex->aiColumn[nCol]); - pIndex->zName = (char *)(&pIndex->aSortOrder[nCol]); - zExtra = (char *)(&pIndex->zName[nName+1]); + pIndex->zName = zExtra; + zExtra += nName + 1; memcpy(pIndex->zName, zName, nName+1); pIndex->pTable = pTab; - pIndex->nColumn = pList->nExpr; pIndex->onError = (u8)onError; - pIndex->uniqNotNull = onError==OE_Abort; - pIndex->autoIndex = (u8)(pName==0); + pIndex->uniqNotNull = onError!=OE_None; + pIndex->idxType = pName ? SQLITE_IDXTYPE_APPDEF : SQLITE_IDXTYPE_UNIQUE; pIndex->pSchema = db->aDb[iDb].pSchema; + pIndex->nKeyCol = pList->nExpr; if( pPIWhere ){ sqlite3ResolveSelfReference(pParse, pTab, NC_PartIdx, pPIWhere, 0); pIndex->pPartIdxWhere = pPIWhere; pPIWhere = 0; } @@ -86296,11 +93432,10 @@ ** same column more than once cannot be an error because that would ** break backwards compatibility - it needs to be a warning. */ for(i=0, pListItem=pList->a; i<pList->nExpr; i++, pListItem++){ const char *zColName = pListItem->zName; - Column *pTabCol; int requestedSortOrder; char *zColl; /* Collation sequence name */ for(j=0, pTabCol=pTab->aCol; j<pTab->nCol; j++, pTabCol++){ if( sqlite3StrICmp(zColName, pTabCol->zName)==0 ) break; @@ -86309,11 +93444,12 @@ sqlite3ErrorMsg(pParse, "table %s has no column named %s", pTab->zName, zColName); pParse->checkSchema = 1; goto exit_create_index; } - pIndex->aiColumn[i] = j; + assert( j<=0x7fff ); + pIndex->aiColumn[i] = (i16)j; if( pListItem->pExpr ){ int nColl; assert( pListItem->pExpr->op==TK_COLLATE ); zColl = pListItem->pExpr->u.zToken; nColl = sqlite3Strlen30(zColl) + 1; @@ -86332,11 +93468,29 @@ pIndex->azColl[i] = zColl; requestedSortOrder = pListItem->sortOrder & sortOrderMask; pIndex->aSortOrder[i] = (u8)requestedSortOrder; if( pTab->aCol[j].notNull==0 ) pIndex->uniqNotNull = 0; } + if( pPk ){ + for(j=0; j<pPk->nKeyCol; j++){ + int x = pPk->aiColumn[j]; + if( hasColumn(pIndex->aiColumn, pIndex->nKeyCol, x) ){ + pIndex->nColumn--; + }else{ + pIndex->aiColumn[i] = x; + pIndex->azColl[i] = pPk->azColl[j]; + pIndex->aSortOrder[i] = pPk->aSortOrder[j]; + i++; + } + } + assert( i==pIndex->nColumn ); + }else{ + pIndex->aiColumn[i] = -1; + pIndex->azColl[i] = "BINARY"; + } sqlite3DefaultRowEst(pIndex); + if( pParse->pNewTable==0 ) estimateIndexWidth(pIndex); if( pTab==pParse->pNewTable ){ /* This routine has been called to create an automatic index as a ** result of a PRIMARY KEY or UNIQUE clause on a column definition, or ** a PRIMARY KEY or UNIQUE clause following the column definitions. @@ -86359,24 +93513,24 @@ ** considered distinct and both result in separate indices. */ Index *pIdx; for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ int k; - assert( pIdx->onError!=OE_None ); - assert( pIdx->autoIndex ); - assert( pIndex->onError!=OE_None ); + assert( IsUniqueIndex(pIdx) ); + assert( pIdx->idxType!=SQLITE_IDXTYPE_APPDEF ); + assert( IsUniqueIndex(pIndex) ); - if( pIdx->nColumn!=pIndex->nColumn ) continue; - for(k=0; k<pIdx->nColumn; k++){ + if( pIdx->nKeyCol!=pIndex->nKeyCol ) continue; + for(k=0; k<pIdx->nKeyCol; k++){ const char *z1; const char *z2; if( pIdx->aiColumn[k]!=pIndex->aiColumn[k] ) break; z1 = pIdx->azColl[k]; z2 = pIndex->azColl[k]; if( z1!=z2 && sqlite3StrICmp(z1, z2) ) break; } - if( k==pIdx->nColumn ){ + if( k==pIdx->nKeyCol ){ if( pIdx->onError!=pIndex->onError ){ /* This constraint creates the same index as a previous ** constraint specified somewhere in the CREATE TABLE statement. ** However the ON CONFLICT clauses are different. If both this ** constraint and the previous equivalent constraint have explicit @@ -86389,10 +93543,11 @@ } if( pIdx->onError==OE_Default ){ pIdx->onError = pIndex->onError; } } + pRet = pIdx; goto exit_create_index; } } } @@ -86401,12 +93556,11 @@ */ if( db->init.busy ){ Index *p; assert( sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) ); p = sqlite3HashInsert(&pIndex->pSchema->idxHash, - pIndex->zName, sqlite3Strlen30(pIndex->zName), - pIndex); + pIndex->zName, pIndex); if( p ){ assert( p==pIndex ); /* Malloc must have failed */ db->mallocFailed = 1; goto exit_create_index; } @@ -86414,26 +93568,24 @@ if( pTblName!=0 ){ pIndex->tnum = db->init.newTnum; } } - /* If the db->init.busy is 0 then create the index on disk. This - ** involves writing the index into the master table and filling in the - ** index with the current table contents. - ** - ** The db->init.busy is 0 when the user first enters a CREATE INDEX - ** command. db->init.busy is 1 when a database is opened and - ** CREATE INDEX statements are read out of the master table. In - ** the latter case the index already exists on disk, which is why - ** we don't want to recreate it. - ** - ** If pTblName==0 it means this index is generated as a primary key - ** or UNIQUE constraint of a CREATE TABLE statement. Since the table + /* If this is the initial CREATE INDEX statement (or CREATE TABLE if the + ** index is an implied index for a UNIQUE or PRIMARY KEY constraint) then + ** emit code to allocate the index rootpage on disk and make an entry for + ** the index in the sqlite_master table and populate the index with + ** content. But, do not do this if we are simply reading the sqlite_master + ** table to parse the schema, or if this index is the PRIMARY KEY index + ** of a WITHOUT ROWID table. + ** + ** If pTblName==0 it means this index is generated as an implied PRIMARY KEY + ** or UNIQUE index in a CREATE TABLE statement. Since the table ** has just been created, it contains no data and the index initialization ** step can be skipped. */ - else if( pParse->nErr==0 ){ + else if( pParse->nErr==0 && (HasRowid(pTab) || pTblName!=0) ){ Vdbe *v; char *zStmt; int iMem = ++pParse->nMem; v = sqlite3GetVdbe(pParse); @@ -86519,15 +93671,15 @@ /* ** Fill the Index.aiRowEst[] array with default information - information ** to be used when we have not run the ANALYZE command. ** -** aiRowEst[0] is suppose to contain the number of elements in the index. +** aiRowEst[0] is supposed to contain the number of elements in the index. ** Since we do not know, guess 1 million. aiRowEst[1] is an estimate of the ** number of rows in the table that match any particular value of the ** first column of the index. aiRowEst[2] is an estimate of the number -** of rows that match any particular combiniation of the first 2 columns +** of rows that match any particular combination of the first 2 columns ** of the index. And so forth. It must always be the case that * ** aiRowEst[N]<=aiRowEst[N-1] ** aiRowEst[N]>=1 ** @@ -86534,24 +93686,31 @@ ** Apart from that, we have little to go on besides intuition as to ** how aiRowEst[] should be initialized. The numbers generated here ** are based on typical values found in actual indices. */ SQLITE_PRIVATE void sqlite3DefaultRowEst(Index *pIdx){ - tRowcnt *a = pIdx->aiRowEst; + /* 10, 9, 8, 7, 6 */ + LogEst aVal[] = { 33, 32, 30, 28, 26 }; + LogEst *a = pIdx->aiRowLogEst; + int nCopy = MIN(ArraySize(aVal), pIdx->nKeyCol); int i; - tRowcnt n; - assert( a!=0 ); - a[0] = pIdx->pTable->nRowEst; - if( a[0]<10 ) a[0] = 10; - n = 10; - for(i=1; i<=pIdx->nColumn; i++){ - a[i] = n; - if( n>5 ) n--; - } - if( pIdx->onError!=OE_None ){ - a[pIdx->nColumn] = 1; - } + + /* Set the first entry (number of rows in the index) to the estimated + ** number of rows in the table. Or 10, if the estimated number of rows + ** in the table is less than that. */ + a[0] = pIdx->pTable->nRowLogEst; + if( a[0]<33 ) a[0] = 33; assert( 33==sqlite3LogEst(10) ); + + /* Estimate that a[1] is 10, a[2] is 9, a[3] is 8, a[4] is 7, a[5] is + ** 6 and each subsequent value (if any) is 5. */ + memcpy(&a[1], aVal, nCopy*sizeof(LogEst)); + for(i=nCopy+1; i<=pIdx->nKeyCol; i++){ + a[i] = 23; assert( 23==sqlite3LogEst(5) ); + } + + assert( 0==sqlite3LogEst(1) ); + if( IsUniqueIndex(pIdx) ) a[pIdx->nKeyCol] = 0; } /* ** This routine will drop an existing named index. This routine ** implements the DROP INDEX statement. @@ -86578,11 +93737,11 @@ sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); } pParse->checkSchema = 1; goto exit_drop_index; } - if( pIndex->autoIndex ){ + if( pIndex->idxType!=SQLITE_IDXTYPE_APPDEF ){ sqlite3ErrorMsg(pParse, "index associated with UNIQUE " "or PRIMARY KEY constraint cannot be dropped", 0); goto exit_drop_index; } iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); @@ -86747,11 +93906,11 @@ assert( nExtra>=1 ); assert( pSrc!=0 ); assert( iStart<=pSrc->nSrc ); /* Allocate additional space if needed */ - if( pSrc->nSrc+nExtra>pSrc->nAlloc ){ + if( (u32)pSrc->nSrc+nExtra>pSrc->nAlloc ){ SrcList *pNew; int nAlloc = pSrc->nSrc+nExtra; int nGot; pNew = sqlite3DbRealloc(db, pSrc, sizeof(*pSrc) + (nAlloc-1)*sizeof(pSrc->a[0]) ); @@ -86759,19 +93918,19 @@ assert( db->mallocFailed ); return pSrc; } pSrc = pNew; nGot = (sqlite3DbMallocSize(db, pNew) - sizeof(*pSrc))/sizeof(pSrc->a[0])+1; - pSrc->nAlloc = (u8)nGot; + pSrc->nAlloc = nGot; } /* Move existing slots that come after the newly inserted slots ** out of the way */ for(i=pSrc->nSrc-1; i>=iStart; i--){ pSrc->a[i+nExtra] = pSrc->a[i]; } - pSrc->nSrc += (i8)nExtra; + pSrc->nSrc += nExtra; /* Zero the newly allocated slots */ memset(&pSrc->a[iStart], 0, sizeof(pSrc->a[0])*nExtra); for(i=iStart; i<iStart+nExtra; i++){ pSrc->a[i].iCursor = -1; @@ -86891,11 +94050,11 @@ ** end of a growing FROM clause. The "p" parameter is the part of ** the FROM clause that has already been constructed. "p" is NULL ** if this is the first term of the FROM clause. pTable and pDatabase ** are the name of the table and database named in the FROM clause term. ** pDatabase is NULL if the database name qualifier is missing - the -** usual case. If the term has a alias, then pAlias points to the +** usual case. If the term has an alias, then pAlias points to the ** alias token. If the term is a subquery, then pSubquery is the ** SELECT statement that the subquery encodes. The pTable and ** pDatabase parameters are NULL for subqueries. The pOn and pUsing ** parameters are the content of the ON and USING clauses. ** @@ -87099,63 +94258,28 @@ } return 0; } /* -** Generate VDBE code that will verify the schema cookie and start -** a read-transaction for all named database files. -** -** It is important that all schema cookies be verified and all -** read transactions be started before anything else happens in -** the VDBE program. But this routine can be called after much other -** code has been generated. So here is what we do: -** -** The first time this routine is called, we code an OP_Goto that -** will jump to a subroutine at the end of the program. Then we -** record every database that needs its schema verified in the -** pParse->cookieMask field. Later, after all other code has been -** generated, the subroutine that does the cookie verifications and -** starts the transactions will be coded and the OP_Goto P2 value -** will be made to point to that subroutine. The generation of the -** cookie verification subroutine code happens in sqlite3FinishCoding(). -** -** If iDb<0 then code the OP_Goto only - don't set flag to verify the -** schema on any databases. This can be used to position the OP_Goto -** early in the code, before we know if any database tables will be used. +** Record the fact that the schema cookie will need to be verified +** for database iDb. The code to actually verify the schema cookie +** will occur at the end of the top-level VDBE and will be generated +** later, by sqlite3FinishCoding(). */ SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ Parse *pToplevel = sqlite3ParseToplevel(pParse); - -#ifndef SQLITE_OMIT_TRIGGER - if( pToplevel!=pParse ){ - /* This branch is taken if a trigger is currently being coded. In this - ** case, set cookieGoto to a non-zero value to show that this function - ** has been called. This is used by the sqlite3ExprCodeConstants() - ** function. */ - pParse->cookieGoto = -1; - } -#endif - if( pToplevel->cookieGoto==0 ){ - Vdbe *v = sqlite3GetVdbe(pToplevel); - if( v==0 ) return; /* This only happens if there was a prior error */ - pToplevel->cookieGoto = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0)+1; - } - if( iDb>=0 ){ - sqlite3 *db = pToplevel->db; - yDbMask mask; - - assert( iDb<db->nDb ); - assert( db->aDb[iDb].pBt!=0 || iDb==1 ); - assert( iDb<SQLITE_MAX_ATTACHED+2 ); - assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - mask = ((yDbMask)1)<<iDb; - if( (pToplevel->cookieMask & mask)==0 ){ - pToplevel->cookieMask |= mask; - pToplevel->cookieValue[iDb] = db->aDb[iDb].pSchema->schema_cookie; - if( !OMIT_TEMPDB && iDb==1 ){ - sqlite3OpenTempDatabase(pToplevel); - } + sqlite3 *db = pToplevel->db; + + assert( iDb>=0 && iDb<db->nDb ); + assert( db->aDb[iDb].pBt!=0 || iDb==1 ); + assert( iDb<SQLITE_MAX_ATTACHED+2 ); + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + if( DbMaskTest(pToplevel->cookieMask, iDb)==0 ){ + DbMaskSet(pToplevel->cookieMask, iDb); + pToplevel->cookieValue[iDb] = db->aDb[iDb].pSchema->schema_cookie; + if( !OMIT_TEMPDB && iDb==1 ){ + sqlite3OpenTempDatabase(pToplevel); } } } /* @@ -87187,11 +94311,11 @@ ** necessary to undo a write and the checkpoint should not be set. */ SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){ Parse *pToplevel = sqlite3ParseToplevel(pParse); sqlite3CodeVerifySchema(pParse, iDb); - pToplevel->writeMask |= ((yDbMask)1)<<iDb; + DbMaskSet(pToplevel->writeMask, iDb); pToplevel->isMultiWrite |= setStatement; } /* ** Indicate that the statement currently under construction might write @@ -87234,18 +94358,72 @@ SQLITE_PRIVATE void sqlite3HaltConstraint( Parse *pParse, /* Parsing context */ int errCode, /* extended error code */ int onError, /* Constraint type */ char *p4, /* Error message */ - int p4type /* P4_STATIC or P4_TRANSIENT */ + i8 p4type, /* P4_STATIC or P4_TRANSIENT */ + u8 p5Errmsg /* P5_ErrMsg type */ ){ Vdbe *v = sqlite3GetVdbe(pParse); assert( (errCode&0xff)==SQLITE_CONSTRAINT ); if( onError==OE_Abort ){ sqlite3MayAbort(pParse); } sqlite3VdbeAddOp4(v, OP_Halt, errCode, onError, 0, p4, p4type); + if( p5Errmsg ) sqlite3VdbeChangeP5(v, p5Errmsg); +} + +/* +** Code an OP_Halt due to UNIQUE or PRIMARY KEY constraint violation. +*/ +SQLITE_PRIVATE void sqlite3UniqueConstraint( + Parse *pParse, /* Parsing context */ + int onError, /* Constraint type */ + Index *pIdx /* The index that triggers the constraint */ +){ + char *zErr; + int j; + StrAccum errMsg; + Table *pTab = pIdx->pTable; + + sqlite3StrAccumInit(&errMsg, 0, 0, 200); + errMsg.db = pParse->db; + for(j=0; j<pIdx->nKeyCol; j++){ + char *zCol = pTab->aCol[pIdx->aiColumn[j]].zName; + if( j ) sqlite3StrAccumAppend(&errMsg, ", ", 2); + sqlite3StrAccumAppendAll(&errMsg, pTab->zName); + sqlite3StrAccumAppend(&errMsg, ".", 1); + sqlite3StrAccumAppendAll(&errMsg, zCol); + } + zErr = sqlite3StrAccumFinish(&errMsg); + sqlite3HaltConstraint(pParse, + IsPrimaryKeyIndex(pIdx) ? SQLITE_CONSTRAINT_PRIMARYKEY + : SQLITE_CONSTRAINT_UNIQUE, + onError, zErr, P4_DYNAMIC, P5_ConstraintUnique); +} + + +/* +** Code an OP_Halt due to non-unique rowid. +*/ +SQLITE_PRIVATE void sqlite3RowidConstraint( + Parse *pParse, /* Parsing context */ + int onError, /* Conflict resolution algorithm */ + Table *pTab /* The table with the non-unique rowid */ +){ + char *zMsg; + int rc; + if( pTab->iPKey>=0 ){ + zMsg = sqlite3MPrintf(pParse->db, "%s.%s", pTab->zName, + pTab->aCol[pTab->iPKey].zName); + rc = SQLITE_CONSTRAINT_PRIMARYKEY; + }else{ + zMsg = sqlite3MPrintf(pParse->db, "%s.rowid", pTab->zName); + rc = SQLITE_CONSTRAINT_ROWID; + } + sqlite3HaltConstraint(pParse, rc, onError, zMsg, P4_DYNAMIC, + P5_ConstraintUnique); } /* ** Check to see if pIndex uses the collating sequence pColl. Return ** true if it does and false if it does not. @@ -87254,12 +94432,12 @@ static int collationMatch(const char *zColl, Index *pIndex){ int i; assert( zColl!=0 ); for(i=0; i<pIndex->nColumn; i++){ const char *z = pIndex->azColl[i]; - assert( z!=0 ); - if( 0==sqlite3StrICmp(z, zColl) ){ + assert( z!=0 || pIndex->aiColumn[i]<0 ); + if( pIndex->aiColumn[i]>=0 && 0==sqlite3StrICmp(z, zColl) ){ return 1; } } return 0; } @@ -87374,41 +94552,117 @@ sqlite3ErrorMsg(pParse, "unable to identify the object to be reindexed"); } #endif /* -** Return a dynamicly allocated KeyInfo structure that can be used -** with OP_OpenRead or OP_OpenWrite to access database index pIdx. +** Return a KeyInfo structure that is appropriate for the given Index. ** -** If successful, a pointer to the new structure is returned. In this case -** the caller is responsible for calling sqlite3DbFree(db, ) on the returned -** pointer. If an error occurs (out of memory or missing collation -** sequence), NULL is returned and the state of pParse updated to reflect -** the error. +** The KeyInfo structure for an index is cached in the Index object. +** So there might be multiple references to the returned pointer. The +** caller should not try to modify the KeyInfo object. +** +** The caller should invoke sqlite3KeyInfoUnref() on the returned object +** when it has finished using it. */ -SQLITE_PRIVATE KeyInfo *sqlite3IndexKeyinfo(Parse *pParse, Index *pIdx){ +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){ int i; int nCol = pIdx->nColumn; + int nKey = pIdx->nKeyCol; KeyInfo *pKey; - - pKey = sqlite3KeyInfoAlloc(pParse->db, nCol); + if( pParse->nErr ) return 0; + if( pIdx->uniqNotNull ){ + pKey = sqlite3KeyInfoAlloc(pParse->db, nKey, nCol-nKey); + }else{ + pKey = sqlite3KeyInfoAlloc(pParse->db, nCol, 0); + } if( pKey ){ + assert( sqlite3KeyInfoIsWriteable(pKey) ); for(i=0; i<nCol; i++){ char *zColl = pIdx->azColl[i]; - assert( zColl ); - pKey->aColl[i] = sqlite3LocateCollSeq(pParse, zColl); + assert( zColl!=0 ); + pKey->aColl[i] = strcmp(zColl,"BINARY")==0 ? 0 : + sqlite3LocateCollSeq(pParse, zColl); pKey->aSortOrder[i] = pIdx->aSortOrder[i]; } - } - - if( pParse->nErr ){ - sqlite3DbFree(pParse->db, pKey); - pKey = 0; + if( pParse->nErr ){ + sqlite3KeyInfoUnref(pKey); + pKey = 0; + } } return pKey; } +#ifndef SQLITE_OMIT_CTE +/* +** This routine is invoked once per CTE by the parser while parsing a +** WITH clause. +*/ +SQLITE_PRIVATE With *sqlite3WithAdd( + Parse *pParse, /* Parsing context */ + With *pWith, /* Existing WITH clause, or NULL */ + Token *pName, /* Name of the common-table */ + ExprList *pArglist, /* Optional column name list for the table */ + Select *pQuery /* Query used to initialize the table */ +){ + sqlite3 *db = pParse->db; + With *pNew; + char *zName; + + /* Check that the CTE name is unique within this WITH clause. If + ** not, store an error in the Parse structure. */ + zName = sqlite3NameFromToken(pParse->db, pName); + if( zName && pWith ){ + int i; + for(i=0; i<pWith->nCte; i++){ + if( sqlite3StrICmp(zName, pWith->a[i].zName)==0 ){ + sqlite3ErrorMsg(pParse, "duplicate WITH table name: %s", zName); + } + } + } + + if( pWith ){ + int nByte = sizeof(*pWith) + (sizeof(pWith->a[1]) * pWith->nCte); + pNew = sqlite3DbRealloc(db, pWith, nByte); + }else{ + pNew = sqlite3DbMallocZero(db, sizeof(*pWith)); + } + assert( zName!=0 || pNew==0 ); + assert( db->mallocFailed==0 || pNew==0 ); + + if( pNew==0 ){ + sqlite3ExprListDelete(db, pArglist); + sqlite3SelectDelete(db, pQuery); + sqlite3DbFree(db, zName); + pNew = pWith; + }else{ + pNew->a[pNew->nCte].pSelect = pQuery; + pNew->a[pNew->nCte].pCols = pArglist; + pNew->a[pNew->nCte].zName = zName; + pNew->a[pNew->nCte].zErr = 0; + pNew->nCte++; + } + + return pNew; +} + +/* +** Free the contents of the With object passed as the second argument. +*/ +SQLITE_PRIVATE void sqlite3WithDelete(sqlite3 *db, With *pWith){ + if( pWith ){ + int i; + for(i=0; i<pWith->nCte; i++){ + struct Cte *pCte = &pWith->a[i]; + sqlite3ExprListDelete(db, pCte->pCols); + sqlite3SelectDelete(db, pCte->pSelect); + sqlite3DbFree(db, pCte->zName); + } + sqlite3DbFree(db, pWith); + } +} +#endif /* !defined(SQLITE_OMIT_CTE) */ + /************** End of build.c ***********************************************/ /************** Begin file callback.c ****************************************/ /* ** 2005 May 23 ** @@ -87550,11 +94804,11 @@ ** specified by zName and nName is not found and parameter 'create' is ** true, then create a new entry. Otherwise return NULL. ** ** Each pointer stored in the sqlite3.aCollSeq hash table contains an ** array of three CollSeq structures. The first is the collation sequence -** prefferred for UTF-8, the second UTF-16le, and the third UTF-16be. +** preferred for UTF-8, the second UTF-16le, and the third UTF-16be. ** ** Stored immediately after the three collation sequences is a copy of ** the collation sequence name. A pointer to this string is stored in ** each collation sequence structure. */ @@ -87562,15 +94816,15 @@ sqlite3 *db, /* Database connection */ const char *zName, /* Name of the collating sequence */ int create /* Create a new entry if true */ ){ CollSeq *pColl; - int nName = sqlite3Strlen30(zName); - pColl = sqlite3HashFind(&db->aCollSeq, zName, nName); + pColl = sqlite3HashFind(&db->aCollSeq, zName); if( 0==pColl && create ){ - pColl = sqlite3DbMallocZero(db, 3*sizeof(*pColl) + nName + 1 ); + int nName = sqlite3Strlen30(zName); + pColl = sqlite3DbMallocZero(db, 3*sizeof(*pColl) + nName + 1); if( pColl ){ CollSeq *pDel = 0; pColl[0].zName = (char*)&pColl[3]; pColl[0].enc = SQLITE_UTF8; pColl[1].zName = (char*)&pColl[3]; @@ -87577,11 +94831,11 @@ pColl[1].enc = SQLITE_UTF16LE; pColl[2].zName = (char*)&pColl[3]; pColl[2].enc = SQLITE_UTF16BE; memcpy(pColl[0].zName, zName, nName); pColl[0].zName[nName] = 0; - pDel = sqlite3HashInsert(&db->aCollSeq, pColl[0].zName, nName, pColl); + pDel = sqlite3HashInsert(&db->aCollSeq, pColl[0].zName, pColl); /* If a malloc() failure occurred in sqlite3HashInsert(), it will ** return the pColl pointer to be deleted (because it wasn't added ** to the hash table). */ @@ -87765,11 +95019,10 @@ int bestScore = 0; /* Score of best match */ int h; /* Hash value */ assert( nArg>=(-2) ); assert( nArg>=(-1) || createFlag==0 ); - assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); h = (sqlite3UpperToLower[(u8)zName[0]] + nName) % ArraySize(db->aFunc.a); /* First search for a match amongst the application-defined functions. */ p = functionSearch(&db->aFunc, h, zName, nName); @@ -87856,13 +95109,13 @@ sqlite3DeleteTable(0, pTab); } sqlite3HashClear(&temp1); sqlite3HashClear(&pSchema->fkeyHash); pSchema->pSeqTab = 0; - if( pSchema->flags & DB_SchemaLoaded ){ + if( pSchema->schemaFlags & DB_SchemaLoaded ){ pSchema->iGeneration++; - pSchema->flags &= ~DB_SchemaLoaded; + pSchema->schemaFlags &= ~DB_SchemaLoaded; } } /* ** Find and return the schema associated with a BTree. Create @@ -87978,32 +95231,27 @@ */ SQLITE_PRIVATE void sqlite3MaterializeView( Parse *pParse, /* Parsing context */ Table *pView, /* View definition */ Expr *pWhere, /* Optional WHERE clause to be added */ - int iCur /* Cursor number for ephemerial table */ + int iCur /* Cursor number for ephemeral table */ ){ SelectDest dest; Select *pSel; SrcList *pFrom; sqlite3 *db = pParse->db; int iDb = sqlite3SchemaToIndex(db, pView->pSchema); - pWhere = sqlite3ExprDup(db, pWhere, 0); pFrom = sqlite3SrcListAppend(db, 0, 0, 0); - if( pFrom ){ assert( pFrom->nSrc==1 ); pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName); pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zName); assert( pFrom->a[0].pOn==0 ); assert( pFrom->a[0].pUsing==0 ); } - pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0, 0, 0, 0); - if( pSel ) pSel->selFlags |= SF_Materialize; - sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur); sqlite3Select(pParse, pSel, &dest); sqlite3SelectDelete(db, pSel); } #endif /* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */ @@ -88022,11 +95270,11 @@ SrcList *pSrc, /* the FROM clause -- which tables to scan */ Expr *pWhere, /* The WHERE clause. May be null */ ExprList *pOrderBy, /* The ORDER BY clause. May be null */ Expr *pLimit, /* The LIMIT clause. May be null */ Expr *pOffset, /* The OFFSET clause. May be null */ - char *zStmtType /* Either DELETE or UPDATE. For error messages. */ + char *zStmtType /* Either DELETE or UPDATE. For err msgs. */ ){ Expr *pWhereRowid = NULL; /* WHERE rowid .. */ Expr *pInClause = NULL; /* WHERE rowid IN ( select ) */ Expr *pSelectRowid = NULL; /* SELECT rowid ... */ ExprList *pEList = NULL; /* Expression list contaning only pSelectRowid */ @@ -88082,11 +95330,11 @@ pInClause = sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0, 0); if( pInClause == 0 ) goto limit_where_cleanup_1; pInClause->x.pSelect = pSelect; pInClause->flags |= EP_xIsSelect; - sqlite3ExprSetHeight(pParse, pInClause); + sqlite3ExprSetHeightAndFlags(pParse, pInClause); return pInClause; /* something went wrong. clean up anything allocated. */ limit_where_cleanup_1: sqlite3SelectDelete(pParse->db, pSelect); @@ -88097,11 +95345,12 @@ sqlite3ExprListDelete(pParse->db, pOrderBy); sqlite3ExprDelete(pParse->db, pLimit); sqlite3ExprDelete(pParse->db, pOffset); return 0; } -#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) */ +#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */ + /* && !defined(SQLITE_OMIT_SUBQUERY) */ /* ** Generate code for a DELETE FROM statement. ** ** DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL; @@ -88114,22 +95363,38 @@ Expr *pWhere /* The WHERE clause. May be null */ ){ Vdbe *v; /* The virtual database engine */ Table *pTab; /* The table from which records will be deleted */ const char *zDb; /* Name of database holding pTab */ - int end, addr = 0; /* A couple addresses of generated code */ int i; /* Loop counter */ WhereInfo *pWInfo; /* Information about the WHERE clause */ Index *pIdx; /* For looping over indices of the table */ - int iCur; /* VDBE Cursor number for pTab */ + int iTabCur; /* Cursor number for the table */ + int iDataCur = 0; /* VDBE cursor for the canonical data source */ + int iIdxCur = 0; /* Cursor number of the first index */ + int nIdx; /* Number of indices */ sqlite3 *db; /* Main database structure */ AuthContext sContext; /* Authorization context */ NameContext sNC; /* Name context to resolve expressions in */ int iDb; /* Database number */ int memCnt = -1; /* Memory cell used for change counting */ int rcauth; /* Value returned by authorization callback */ - + int okOnePass; /* True for one-pass algorithm without the FIFO */ + int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */ + u8 *aToOpen = 0; /* Open cursor iTabCur+j if aToOpen[j] is true */ + Index *pPk; /* The PRIMARY KEY index on the table */ + int iPk = 0; /* First of nPk registers holding PRIMARY KEY value */ + i16 nPk = 1; /* Number of columns in the PRIMARY KEY */ + int iKey; /* Memory cell holding key of row to be deleted */ + i16 nKey; /* Number of memory cells in the row key */ + int iEphCur = 0; /* Ephemeral table holding all primary key values */ + int iRowSet = 0; /* Register for rowset of rows to delete */ + int addrBypass = 0; /* Address of jump over the delete logic */ + int addrLoop = 0; /* Top of the delete loop */ + int addrDelete = 0; /* Jump directly to the delete logic */ + int addrEphOpen = 0; /* Instruction to open the Ephemeral table */ + #ifndef SQLITE_OMIT_TRIGGER int isView; /* True if attempting to delete from a view */ Trigger *pTrigger; /* List of table triggers, if required */ #endif @@ -88180,15 +95445,15 @@ if( rcauth==SQLITE_DENY ){ goto delete_from_cleanup; } assert(!isView || pTrigger); - /* Assign cursor number to the table and all its indices. + /* Assign cursor numbers to the table and all its indices. */ assert( pTabList->nSrc==1 ); - iCur = pTabList->a[0].iCursor = pParse->nTab++; - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + iTabCur = pTabList->a[0].iCursor = pParse->nTab++; + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ pParse->nTab++; } /* Start the view context */ @@ -88204,15 +95469,16 @@ } if( pParse->nested==0 ) sqlite3VdbeCountChanges(v); sqlite3BeginWriteOperation(pParse, 1, iDb); /* If we are trying to delete from a view, realize that view into - ** a ephemeral table. + ** an ephemeral table. */ #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) if( isView ){ - sqlite3MaterializeView(pParse, pTab, pWhere, iCur); + sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur); + iDataCur = iIdxCur = iTabCur; } #endif /* Resolve the column names in the WHERE clause. */ @@ -88238,82 +95504,177 @@ ** API function sqlite3_count_changes) to be set incorrectly. */ if( rcauth==SQLITE_OK && pWhere==0 && !pTrigger && !IsVirtual(pTab) && 0==sqlite3FkRequired(pParse, pTab, 0, 0) ){ assert( !isView ); - sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt, - pTab->zName, P4_STATIC); + sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt, + pTab->zName, P4_STATIC); + } for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ assert( pIdx->pSchema==pTab->pSchema ); sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb); } }else #endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */ - /* The usual case: There is a WHERE clause so we have to scan through - ** the table and pick which records to delete. - */ - { - int iRowSet = ++pParse->nMem; /* Register for rowset of rows to delete */ - int iRowid = ++pParse->nMem; /* Used for storing rowid values. */ - int regRowid; /* Actual register containing rowids */ - - /* Collect rowids of every row to be deleted. - */ - sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet); - pWInfo = sqlite3WhereBegin( - pParse, pTabList, pWhere, 0, 0, WHERE_DUPLICATES_OK, 0 - ); + { + if( HasRowid(pTab) ){ + /* For a rowid table, initialize the RowSet to an empty set */ + pPk = 0; + nPk = 1; + iRowSet = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet); + }else{ + /* For a WITHOUT ROWID table, create an ephemeral table used to + ** hold all primary keys for rows to be deleted. */ + pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + nPk = pPk->nKeyCol; + iPk = pParse->nMem+1; + pParse->nMem += nPk; + iEphCur = pParse->nTab++; + addrEphOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEphCur, nPk); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + } + + /* Construct a query to find the rowid or primary key for every row + ** to be deleted, based on the WHERE clause. + */ + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, + WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK, + iTabCur+1); if( pWInfo==0 ) goto delete_from_cleanup; - regRowid = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iCur, iRowid, 0); - sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, regRowid); + okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); + + /* Keep track of the number of rows to be deleted */ if( db->flags & SQLITE_CountRows ){ sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1); } + + /* Extract the rowid or primary key for the current row */ + if( pPk ){ + for(i=0; i<nPk; i++){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, + pPk->aiColumn[i], iPk+i); + } + iKey = iPk; + }else{ + iKey = pParse->nMem + 1; + iKey = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iTabCur, iKey, 0); + if( iKey>pParse->nMem ) pParse->nMem = iKey; + } + + if( okOnePass ){ + /* For ONEPASS, no need to store the rowid/primary-key. There is only + ** one, so just keep it in its register(s) and fall through to the + ** delete code. + */ + nKey = nPk; /* OP_Found will use an unpacked key */ + aToOpen = sqlite3DbMallocRaw(db, nIdx+2); + if( aToOpen==0 ){ + sqlite3WhereEnd(pWInfo); + goto delete_from_cleanup; + } + memset(aToOpen, 1, nIdx+1); + aToOpen[nIdx+1] = 0; + if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iTabCur] = 0; + if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iTabCur] = 0; + if( addrEphOpen ) sqlite3VdbeChangeToNoop(v, addrEphOpen); + addrDelete = sqlite3VdbeAddOp0(v, OP_Goto); /* Jump to DELETE logic */ + }else if( pPk ){ + /* Construct a composite key for the row to be deleted and remember it */ + iKey = ++pParse->nMem; + nKey = 0; /* Zero tells OP_Found to use a composite key */ + sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, + sqlite3IndexAffinityStr(v, pPk), nPk); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey); + }else{ + /* Get the rowid of the row to be deleted and remember it in the RowSet */ + nKey = 1; /* OP_Seek always uses a single rowid */ + sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey); + } + + /* End of the WHERE loop */ sqlite3WhereEnd(pWInfo); - - /* Delete every item whose key was written to the list during the - ** database scan. We have to delete items after the scan is complete - ** because deleting an item can change the scan order. */ - end = sqlite3VdbeMakeLabel(v); - + if( okOnePass ){ + /* Bypass the delete logic below if the WHERE loop found zero rows */ + addrBypass = sqlite3VdbeMakeLabel(v); + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrBypass); + sqlite3VdbeJumpHere(v, addrDelete); + } + /* Unless this is a view, open cursors for the table we are ** deleting from and all its indices. If this is a view, then the ** only effect this statement has is to fire the INSTEAD OF - ** triggers. */ + ** triggers. + */ if( !isView ){ - sqlite3OpenTableAndIndices(pParse, pTab, iCur, OP_OpenWrite); + testcase( IsVirtual(pTab) ); + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, iTabCur, aToOpen, + &iDataCur, &iIdxCur); + assert( pPk || IsVirtual(pTab) || iDataCur==iTabCur ); + assert( pPk || IsVirtual(pTab) || iIdxCur==iDataCur+1 ); } - - addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, end, iRowid); - + + /* Set up a loop over the rowids/primary-keys that were found in the + ** where-clause loop above. + */ + if( okOnePass ){ + /* Just one row. Hence the top-of-loop is a no-op */ + assert( nKey==nPk ); /* OP_Found will use an unpacked key */ + assert( !IsVirtual(pTab) ); + if( aToOpen[iDataCur-iTabCur] ){ + assert( pPk!=0 || pTab->pSelect!=0 ); + sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, addrBypass, iKey, nKey); + VdbeCoverage(v); + } + }else if( pPk ){ + addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_RowKey, iEphCur, iKey); + assert( nKey==0 ); /* OP_Found will use a composite key */ + }else{ + addrLoop = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, 0, iKey); + VdbeCoverage(v); + assert( nKey==1 ); + } + /* Delete the row */ #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pTab) ){ const char *pVTab = (const char *)sqlite3GetVTable(db, pTab); sqlite3VtabMakeWritable(pParse, pTab); - sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iRowid, pVTab, P4_VTAB); + sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iKey, pVTab, P4_VTAB); sqlite3VdbeChangeP5(v, OE_Abort); sqlite3MayAbort(pParse); }else #endif { int count = (pParse->nested==0); /* True to count changes */ - sqlite3GenerateRowDelete(pParse, pTab, iCur, iRowid, count, pTrigger, OE_Default); + sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + iKey, nKey, count, OE_Default, okOnePass); } - - /* End of the delete loop */ - sqlite3VdbeAddOp2(v, OP_Goto, 0, addr); - sqlite3VdbeResolveLabel(v, end); - + + /* End of the loop over all rowids/primary-keys. */ + if( okOnePass ){ + sqlite3VdbeResolveLabel(v, addrBypass); + }else if( pPk ){ + sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrLoop); + }else{ + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrLoop); + sqlite3VdbeJumpHere(v, addrLoop); + } + /* Close the cursors open on the table and its indexes. */ if( !isView && !IsVirtual(pTab) ){ - for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - sqlite3VdbeAddOp2(v, OP_Close, iCur + i, pIdx->tnum); + if( !pPk ) sqlite3VdbeAddOp1(v, OP_Close, iDataCur); + for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + sqlite3VdbeAddOp1(v, OP_Close, iIdxCur + i); } - sqlite3VdbeAddOp1(v, OP_Close, iCur); } - } + } /* End non-truncate path */ /* Update the sqlite_sequence table by storing the content of the ** maximum rowid counter values recorded while inserting into ** autoincrement tables. */ @@ -88333,14 +95694,15 @@ delete_from_cleanup: sqlite3AuthContextPop(&sContext); sqlite3SrcListDelete(db, pTabList); sqlite3ExprDelete(db, pWhere); + sqlite3DbFree(db, aToOpen); return; } /* Make sure "isView" and other macros defined above are undefined. Otherwise -** thely may interfere with compilation of other functions in this file +** they may interfere with compilation of other functions in this file ** (or in another file, if this file becomes part of the amalgamation). */ #ifdef isView #undef isView #endif #ifdef pTrigger @@ -88347,54 +95709,67 @@ #undef pTrigger #endif /* ** This routine generates VDBE code that causes a single row of a -** single table to be deleted. +** single table to be deleted. Both the original table entry and +** all indices are removed. ** -** The VDBE must be in a particular state when this routine is called. -** These are the requirements: +** Preconditions: ** -** 1. A read/write cursor pointing to pTab, the table containing the row -** to be deleted, must be opened as cursor number $iCur. +** 1. iDataCur is an open cursor on the btree that is the canonical data +** store for the table. (This will be either the table itself, +** in the case of a rowid table, or the PRIMARY KEY index in the case +** of a WITHOUT ROWID table.) ** ** 2. Read/write cursors for all indices of pTab must be open as -** cursor number base+i for the i-th index. +** cursor number iIdxCur+i for the i-th index. ** -** 3. The record number of the row to be deleted must be stored in -** memory cell iRowid. -** -** This routine generates code to remove both the table record and all -** index entries that point to that record. +** 3. The primary key for the row to be deleted must be stored in a +** sequence of nPk memory cells starting at iPk. If nPk==0 that means +** that a search record formed from OP_MakeRecord is contained in the +** single memory location iPk. */ SQLITE_PRIVATE void sqlite3GenerateRowDelete( Parse *pParse, /* Parsing context */ Table *pTab, /* Table containing the row to be deleted */ - int iCur, /* Cursor number for the table */ - int iRowid, /* Memory cell that contains the rowid to delete */ - int count, /* If non-zero, increment the row change counter */ Trigger *pTrigger, /* List of triggers to (potentially) fire */ - int onconf /* Default ON CONFLICT policy for triggers */ + int iDataCur, /* Cursor from which column data is extracted */ + int iIdxCur, /* First index cursor */ + int iPk, /* First memory cell containing the PRIMARY KEY */ + i16 nPk, /* Number of PRIMARY KEY memory cells */ + u8 count, /* If non-zero, increment the row change counter */ + u8 onconf, /* Default ON CONFLICT policy for triggers */ + u8 bNoSeek /* iDataCur is already pointing to the row to delete */ ){ Vdbe *v = pParse->pVdbe; /* Vdbe */ int iOld = 0; /* First register in OLD.* array */ int iLabel; /* Label resolved to end of generated code */ + u8 opSeek; /* Seek opcode */ /* Vdbe is guaranteed to have been allocated by this stage. */ assert( v ); + VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)", + iDataCur, iIdxCur, iPk, (int)nPk)); /* Seek cursor iCur to the row to delete. If this row no longer exists ** (this can happen if a trigger program has already deleted it), do ** not attempt to delete it or fire any DELETE triggers. */ iLabel = sqlite3VdbeMakeLabel(v); - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, iLabel, iRowid); + opSeek = HasRowid(pTab) ? OP_NotExists : OP_NotFound; + if( !bNoSeek ){ + sqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk); + VdbeCoverageIf(v, opSeek==OP_NotExists); + VdbeCoverageIf(v, opSeek==OP_NotFound); + } /* If there are any triggers to fire, allocate a range of registers to ** use for the old.* references in the triggers. */ if( sqlite3FkRequired(pParse, pTab, 0, 0) || pTrigger ){ u32 mask; /* Mask of OLD.* columns in use */ int iCol; /* Iterator used while populating OLD.* */ + int addrStart; /* Start of BEFORE trigger programs */ /* TODO: Could use temporary registers here. Also could attempt to ** avoid copying the contents of the rowid register. */ mask = sqlite3TriggerColmask( pParse, pTrigger, 0, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onconf @@ -88403,27 +95778,35 @@ iOld = pParse->nMem+1; pParse->nMem += (1 + pTab->nCol); /* Populate the OLD.* pseudo-table register array. These values will be ** used by any BEFORE and AFTER triggers that exist. */ - sqlite3VdbeAddOp2(v, OP_Copy, iRowid, iOld); + sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld); for(iCol=0; iCol<pTab->nCol; iCol++){ - if( mask==0xffffffff || mask&(1<<iCol) ){ - sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol, iOld+iCol+1); + testcase( mask!=0xffffffff && iCol==31 ); + testcase( mask!=0xffffffff && iCol==32 ); + if( mask==0xffffffff || (iCol<=31 && (mask & MASKBIT32(iCol))!=0) ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, iCol, iOld+iCol+1); } } /* Invoke BEFORE DELETE trigger programs. */ + addrStart = sqlite3VdbeCurrentAddr(v); sqlite3CodeRowTrigger(pParse, pTrigger, TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld, onconf, iLabel ); - /* Seek the cursor to the row to be deleted again. It may be that - ** the BEFORE triggers coded above have already removed the row - ** being deleted. Do not attempt to delete the row a second time, and - ** do not fire AFTER triggers. */ - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, iLabel, iRowid); + /* If any BEFORE triggers were coded, then seek the cursor to the + ** row to be deleted again. It may be that the BEFORE triggers moved + ** the cursor or of already deleted the row that the cursor was + ** pointing to. + */ + if( addrStart<sqlite3VdbeCurrentAddr(v) ){ + sqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk); + VdbeCoverageIf(v, opSeek==OP_NotExists); + VdbeCoverageIf(v, opSeek==OP_NotFound); + } /* Do FK processing. This call checks that any FK constraints that ** refer to this table (i.e. constraints attached to other tables) ** are not violated by deleting this row. */ sqlite3FkCheck(pParse, pTab, iOld, 0, 0, 0); @@ -88431,12 +95814,12 @@ /* Delete the index and table entries. Skip this step if pTab is really ** a view (in which case the only effect of the DELETE statement is to ** fire the INSTEAD OF triggers). */ if( pTab->pSelect==0 ){ - sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, 0); - sqlite3VdbeAddOp2(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0)); + sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, 0); + sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); if( count ){ sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT); } } @@ -88452,73 +95835,102 @@ /* Jump here if the row had already been deleted before any BEFORE ** trigger programs were invoked. Or if a trigger program throws a ** RAISE(IGNORE) exception. */ sqlite3VdbeResolveLabel(v, iLabel); + VdbeModuleComment((v, "END: GenRowDel()")); } /* ** This routine generates VDBE code that causes the deletion of all -** index entries associated with a single row of a single table. +** index entries associated with a single row of a single table, pTab ** -** The VDBE must be in a particular state when this routine is called. -** These are the requirements: +** Preconditions: ** -** 1. A read/write cursor pointing to pTab, the table containing the row -** to be deleted, must be opened as cursor number "iCur". +** 1. A read/write cursor "iDataCur" must be open on the canonical storage +** btree for the table pTab. (This will be either the table itself +** for rowid tables or to the primary key index for WITHOUT ROWID +** tables.) ** ** 2. Read/write cursors for all indices of pTab must be open as -** cursor number iCur+i for the i-th index. +** cursor number iIdxCur+i for the i-th index. (The pTab->pIndex +** index is the 0-th index.) ** -** 3. The "iCur" cursor must be pointing to the row that is to be -** deleted. +** 3. The "iDataCur" cursor must be already be positioned on the row +** that is to be deleted. */ SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete( Parse *pParse, /* Parsing and code generating context */ Table *pTab, /* Table containing the row to be deleted */ - int iCur, /* Cursor number for the table */ + int iDataCur, /* Cursor of table holding data. */ + int iIdxCur, /* First index cursor */ int *aRegIdx /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */ ){ - int i; - Index *pIdx; - int r1; - int iPartIdxLabel; - Vdbe *v = pParse->pVdbe; - - for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - if( aRegIdx!=0 && aRegIdx[i-1]==0 ) continue; - r1 = sqlite3GenerateIndexKey(pParse, pIdx, iCur, 0, 0, &iPartIdxLabel); - sqlite3VdbeAddOp3(v, OP_IdxDelete, iCur+i, r1, pIdx->nColumn+1); - sqlite3VdbeResolveLabel(v, iPartIdxLabel); + int i; /* Index loop counter */ + int r1 = -1; /* Register holding an index key */ + int iPartIdxLabel; /* Jump destination for skipping partial index entries */ + Index *pIdx; /* Current index */ + Index *pPrior = 0; /* Prior index */ + Vdbe *v; /* The prepared statement under construction */ + Index *pPk; /* PRIMARY KEY index, or NULL for rowid tables */ + + v = pParse->pVdbe; + pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); + for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + assert( iIdxCur+i!=iDataCur || pPk==pIdx ); + if( aRegIdx!=0 && aRegIdx[i]==0 ) continue; + if( pIdx==pPk ) continue; + VdbeModuleComment((v, "GenRowIdxDel for %s", pIdx->zName)); + r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 1, + &iPartIdxLabel, pPrior, r1); + sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1, + pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn); + sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel); + pPrior = pIdx; } } /* -** Generate code that will assemble an index key and put it in register +** Generate code that will assemble an index key and stores it in register ** regOut. The key with be for index pIdx which is an index on pTab. ** iCur is the index of a cursor open on the pTab table and pointing to -** the entry that needs indexing. +** the entry that needs indexing. If pTab is a WITHOUT ROWID table, then +** iCur must be the cursor of the PRIMARY KEY index. ** ** Return a register number which is the first in a block of ** registers that holds the elements of the index key. The ** block of registers has already been deallocated by the time ** this routine returns. ** ** If *piPartIdxLabel is not NULL, fill it in with a label and jump ** to that label if pIdx is a partial index that should be skipped. +** The label should be resolved using sqlite3ResolvePartIdxLabel(). ** A partial index should be skipped if its WHERE clause evaluates ** to false or null. If pIdx is not a partial index, *piPartIdxLabel ** will be set to zero which is an empty label that is ignored by -** sqlite3VdbeResolveLabel(). +** sqlite3ResolvePartIdxLabel(). +** +** The pPrior and regPrior parameters are used to implement a cache to +** avoid unnecessary register loads. If pPrior is not NULL, then it is +** a pointer to a different index for which an index key has just been +** computed into register regPrior. If the current pIdx index is generating +** its key into the same sequence of registers and if pPrior and pIdx share +** a column in common, then the register corresponding to that column already +** holds the correct value and the loading of that register is skipped. +** This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK +** on a table with multiple indices, and especially with the ROWID or +** PRIMARY KEY columns of the index. */ SQLITE_PRIVATE int sqlite3GenerateIndexKey( Parse *pParse, /* Parsing context */ Index *pIdx, /* The index for which to generate a key */ - int iCur, /* Cursor number for the pIdx->pTable table */ - int regOut, /* Write the new index key to this register */ - int doMakeRec, /* Run the OP_MakeRecord instruction if true */ - int *piPartIdxLabel /* OUT: Jump to this label to skip partial index */ + int iDataCur, /* Cursor number from which to take column data */ + int regOut, /* Put the new key into this register if not 0 */ + int prefixOnly, /* Compute only a unique prefix of the key */ + int *piPartIdxLabel, /* OUT: Jump to this label to skip partial index */ + Index *pPrior, /* Previously generated index key */ + int regPrior /* Register holding previous generated key */ ){ Vdbe *v = pParse->pVdbe; int j; Table *pTab = pIdx->pTable; int regBase; @@ -88525,44 +95937,51 @@ int nCol; if( piPartIdxLabel ){ if( pIdx->pPartIdxWhere ){ *piPartIdxLabel = sqlite3VdbeMakeLabel(v); - pParse->iPartIdxTab = iCur; + pParse->iPartIdxTab = iDataCur; + sqlite3ExprCachePush(pParse); sqlite3ExprIfFalse(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel, SQLITE_JUMPIFNULL); }else{ *piPartIdxLabel = 0; } } - nCol = pIdx->nColumn; - regBase = sqlite3GetTempRange(pParse, nCol+1); - sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regBase+nCol); + nCol = (prefixOnly && pIdx->uniqNotNull) ? pIdx->nKeyCol : pIdx->nColumn; + regBase = sqlite3GetTempRange(pParse, nCol); + if( pPrior && (regBase!=regPrior || pPrior->pPartIdxWhere) ) pPrior = 0; for(j=0; j<nCol; j++){ - int idx = pIdx->aiColumn[j]; - if( idx==pTab->iPKey ){ - sqlite3VdbeAddOp2(v, OP_SCopy, regBase+nCol, regBase+j); - }else{ - sqlite3VdbeAddOp3(v, OP_Column, iCur, idx, regBase+j); - sqlite3ColumnDefault(v, pTab, idx, -1); - } - } - if( doMakeRec ){ - const char *zAff; - if( pTab->pSelect - || OptimizationDisabled(pParse->db, SQLITE_IdxRealAsInt) - ){ - zAff = 0; - }else{ - zAff = sqlite3IndexAffinityStr(v, pIdx); - } - sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol+1, regOut); - sqlite3VdbeChangeP4(v, -1, zAff, P4_TRANSIENT); - } - sqlite3ReleaseTempRange(pParse, regBase, nCol+1); + if( pPrior && pPrior->aiColumn[j]==pIdx->aiColumn[j] ) continue; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, pIdx->aiColumn[j], + regBase+j); + /* If the column affinity is REAL but the number is an integer, then it + ** might be stored in the table as an integer (using a compact + ** representation) then converted to REAL by an OP_RealAffinity opcode. + ** But we are getting ready to store this value back into an index, where + ** it should be converted by to INTEGER again. So omit the OP_RealAffinity + ** opcode if it is present */ + sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity); + } + if( regOut ){ + sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut); + } + sqlite3ReleaseTempRange(pParse, regBase, nCol); return regBase; } + +/* +** If a prior call to sqlite3GenerateIndexKey() generated a jump-over label +** because it was a partial index, then this routine should be called to +** resolve that label. +*/ +SQLITE_PRIVATE void sqlite3ResolvePartIdxLabel(Parse *pParse, int iLabel){ + if( iLabel ){ + sqlite3VdbeResolveLabel(pParse->pVdbe, iLabel); + sqlite3ExprCachePop(pParse); + } +} /************** End of delete.c **********************************************/ /************** Begin file func.c ********************************************/ /* ** 2002 February 23 @@ -88573,25 +95992,27 @@ ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file contains the C functions that implement various SQL -** functions of SQLite. -** -** There is only one exported symbol in this file - the function -** sqliteRegisterBuildinFunctions() found at the bottom of the file. -** All other code has file scope. +** This file contains the C-language implementations for many of the SQL +** functions of SQLite. (Some function, and in particular the date and +** time functions, are implemented separately.) */ /* #include <stdlib.h> */ /* #include <assert.h> */ /* ** Return the collating function associated with a function. */ static CollSeq *sqlite3GetFuncCollSeq(sqlite3_context *context){ - return context->pColl; + VdbeOp *pOp; + assert( context->pVdbe!=0 ); + pOp = &context->pVdbe->aOp[context->iOp-1]; + assert( pOp->opcode==OP_CollSeq ); + assert( pOp->p4type==P4_COLLSEQ ); + return pOp->p4.pColl; } /* ** Indicate that the accumulator load should be skipped on this ** iteration of the aggregate loop. @@ -88699,13 +96120,13 @@ UNUSED_PARAMETER(argc); switch( sqlite3_value_type(argv[0]) ){ case SQLITE_INTEGER: { i64 iVal = sqlite3_value_int64(argv[0]); if( iVal<0 ){ - if( (iVal<<1)==0 ){ - /* IMP: R-35460-15084 If X is the integer -9223372036854775807 then - ** abs(X) throws an integer overflow error since there is no + if( iVal==SMALLEST_INT64 ){ + /* IMP: R-31676-45509 If X is the integer -9223372036854775808 + ** then abs(X) throws an integer overflow error since there is no ** equivalent positive 64-bit two complement value. */ sqlite3_result_error(context, "integer overflow", -1); return; } iVal = -iVal; @@ -88719,12 +96140,12 @@ break; } default: { /* Because sqlite3_value_double() returns 0.0 if the argument is not ** something that can be converted into a number, we have: - ** IMP: R-57326-31541 Abs(X) return 0.0 if X is a string or blob that - ** cannot be converted to a numeric value. + ** IMP: R-01992-00519 Abs(X) returns 0.0 if X is a string or blob + ** that cannot be converted to a numeric value. */ double rVal = sqlite3_value_double(argv[0]); if( rVal<0 ) rVal = -rVal; sqlite3_result_double(context, rVal); break; @@ -88779,10 +96200,36 @@ }while( isText && (zHaystack[0]&0xc0)==0x80 ); } if( nNeedle>nHaystack ) N = 0; sqlite3_result_int(context, N); } + +/* +** Implementation of the printf() function. +*/ +static void printfFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + PrintfArguments x; + StrAccum str; + const char *zFormat; + int n; + + if( argc>=1 && (zFormat = (const char*)sqlite3_value_text(argv[0]))!=0 ){ + x.nArg = argc-1; + x.nUsed = 0; + x.apArg = argv+1; + sqlite3StrAccumInit(&str, 0, 0, SQLITE_MAX_LENGTH); + str.db = sqlite3_context_db_handle(context); + sqlite3XPrintf(&str, SQLITE_PRINTF_SQLFUNC, zFormat, &x); + n = str.nChar; + sqlite3_result_text(context, sqlite3StrAccumFinish(&str), n, + SQLITE_DYNAMIC); + } +} /* ** Implementation of the substr() function. ** ** substr(x,p1,p2) returns p2 characters of x[] beginning with p1. @@ -88827,10 +96274,18 @@ for(z2=z; *z2; len++){ SQLITE_SKIP_UTF8(z2); } } } +#ifdef SQLITE_SUBSTR_COMPATIBILITY + /* If SUBSTR_COMPATIBILITY is defined then substr(X,0,N) work the same as + ** as substr(X,1,N) - it returns the first N characters of X. This + ** is essentially a back-out of the bug-fix in check-in [5fc125d362df4b8] + ** from 2009-02-02 for compatibility of applications that exploited the + ** old buggy behavior. */ + if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */ +#endif if( argc==3 ){ p2 = sqlite3_value_int(argv[2]); if( p2<0 ){ p2 = -p2; negP2 = 1; @@ -88864,17 +96319,18 @@ p1--; } for(z2=z; *z2 && p2; p2--){ SQLITE_SKIP_UTF8(z2); } - sqlite3_result_text(context, (char*)z, (int)(z2-z), SQLITE_TRANSIENT); + sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT, + SQLITE_UTF8); }else{ if( p1+p2>len ){ p2 = len-p1; if( p2<0 ) p2 = 0; } - sqlite3_result_blob(context, (char*)&z[p1], (int)p2, SQLITE_TRANSIENT); + sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT); } } /* ** Implementation of the round() function @@ -88929,11 +96385,11 @@ testcase( nByte==db->aLimit[SQLITE_LIMIT_LENGTH]+1 ); if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ sqlite3_result_error_toobig(context); z = 0; }else{ - z = sqlite3Malloc((int)nByte); + z = sqlite3Malloc(nByte); if( !z ){ sqlite3_result_error_nomem(context); } } return z; @@ -89105,14 +96561,16 @@ ** character is exactly one byte in size. Also, all characters are ** able to participate in upper-case-to-lower-case mappings in EBCDIC ** whereas only characters less than 0x80 do in ASCII. */ #if defined(SQLITE_EBCDIC) -# define sqlite3Utf8Read(A) (*((*A)++)) -# define GlobUpperToLower(A) A = sqlite3UpperToLower[A] +# define sqlite3Utf8Read(A) (*((*A)++)) +# define GlobUpperToLower(A) A = sqlite3UpperToLower[A] +# define GlobUpperToLowerAscii(A) A = sqlite3UpperToLower[A] #else -# define GlobUpperToLower(A) if( !((A)&~0x7f) ){ A = sqlite3UpperToLower[A]; } +# define GlobUpperToLower(A) if( A<=0x7f ){ A = sqlite3UpperToLower[A]; } +# define GlobUpperToLowerAscii(A) A = sqlite3UpperToLower[A] #endif static const struct compareInfo globInfo = { '*', '?', '[', 0 }; /* The correct SQL-92 behavior is for the LIKE operator to ignore ** case. Thus 'a' LIKE 'A' would be true. */ @@ -89121,11 +96579,11 @@ ** is case sensitive causing 'a' LIKE 'A' to be false */ static const struct compareInfo likeInfoAlt = { '%', '_', 0, 0 }; /* ** Compare two UTF-8 strings for equality where the first string can -** potentially be a "glob" expression. Return true (1) if they +** potentially be a "glob" or "like" expression. Return true (1) if they ** are the same and false (0) if they are different. ** ** Globbing rules: ** ** '*' Matches any sequence of zero or more characters. @@ -89141,128 +96599,155 @@ ** in the list by making it the first character after '[' or '^'. A ** range of characters can be specified using '-'. Example: ** "[a-z]" matches any single lower-case letter. To match a '-', make ** it the last character in the list. ** +** Like matching rules: +** +** '%' Matches any sequence of zero or more characters +** +*** '_' Matches any one character +** +** Ec Where E is the "esc" character and c is any other +** character, including '%', '_', and esc, match exactly c. +** +** The comments through this routine usually assume glob matching. +** ** This routine is usually quick, but can be N**2 in the worst case. -** -** Hints: to match '*' or '?', put them in "[]". Like this: -** -** abc[*]xyz Matches "abc*xyz" only */ static int patternCompare( const u8 *zPattern, /* The glob pattern */ const u8 *zString, /* The string to compare against the glob */ const struct compareInfo *pInfo, /* Information about how to do the compare */ u32 esc /* The escape character */ ){ - u32 c, c2; - int invert; - int seen; - u8 matchOne = pInfo->matchOne; - u8 matchAll = pInfo->matchAll; - u8 matchSet = pInfo->matchSet; - u8 noCase = pInfo->noCase; - int prevEscape = 0; /* True if the previous character was 'escape' */ + u32 c, c2; /* Next pattern and input string chars */ + u32 matchOne = pInfo->matchOne; /* "?" or "_" */ + u32 matchAll = pInfo->matchAll; /* "*" or "%" */ + u32 matchOther; /* "[" or the escape character */ + u8 noCase = pInfo->noCase; /* True if uppercase==lowercase */ + const u8 *zEscaped = 0; /* One past the last escaped input char */ + + /* The GLOB operator does not have an ESCAPE clause. And LIKE does not + ** have the matchSet operator. So we either have to look for one or + ** the other, never both. Hence the single variable matchOther is used + ** to store the one we have to look for. + */ + matchOther = esc ? esc : pInfo->matchSet; while( (c = sqlite3Utf8Read(&zPattern))!=0 ){ - if( c==matchAll && !prevEscape ){ + if( c==matchAll ){ /* Match "*" */ + /* Skip over multiple "*" characters in the pattern. If there + ** are also "?" characters, skip those as well, but consume a + ** single character of the input string for each "?" skipped */ while( (c=sqlite3Utf8Read(&zPattern)) == matchAll || c == matchOne ){ if( c==matchOne && sqlite3Utf8Read(&zString)==0 ){ return 0; } } if( c==0 ){ - return 1; - }else if( c==esc ){ - c = sqlite3Utf8Read(&zPattern); - if( c==0 ){ - return 0; - } - }else if( c==matchSet ){ - assert( esc==0 ); /* This is GLOB, not LIKE */ - assert( matchSet<0x80 ); /* '[' is a single-byte character */ - while( *zString && patternCompare(&zPattern[-1],zString,pInfo,esc)==0 ){ - SQLITE_SKIP_UTF8(zString); - } - return *zString!=0; - } - while( (c2 = sqlite3Utf8Read(&zString))!=0 ){ - if( noCase ){ - GlobUpperToLower(c2); - GlobUpperToLower(c); - while( c2 != 0 && c2 != c ){ - c2 = sqlite3Utf8Read(&zString); - GlobUpperToLower(c2); - } - }else{ - while( c2 != 0 && c2 != c ){ - c2 = sqlite3Utf8Read(&zString); - } - } - if( c2==0 ) return 0; - if( patternCompare(zPattern,zString,pInfo,esc) ) return 1; - } - return 0; - }else if( c==matchOne && !prevEscape ){ - if( sqlite3Utf8Read(&zString)==0 ){ - return 0; - } - }else if( c==matchSet ){ - u32 prior_c = 0; - assert( esc==0 ); /* This only occurs for GLOB, not LIKE */ - seen = 0; - invert = 0; - c = sqlite3Utf8Read(&zString); - if( c==0 ) return 0; - c2 = sqlite3Utf8Read(&zPattern); - if( c2=='^' ){ - invert = 1; - c2 = sqlite3Utf8Read(&zPattern); - } - if( c2==']' ){ - if( c==']' ) seen = 1; - c2 = sqlite3Utf8Read(&zPattern); - } - while( c2 && c2!=']' ){ - if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){ - c2 = sqlite3Utf8Read(&zPattern); - if( c>=prior_c && c<=c2 ) seen = 1; - prior_c = 0; - }else{ - if( c==c2 ){ - seen = 1; - } - prior_c = c2; - } - c2 = sqlite3Utf8Read(&zPattern); - } - if( c2==0 || (seen ^ invert)==0 ){ - return 0; - } - }else if( esc==c && !prevEscape ){ - prevEscape = 1; - }else{ - c2 = sqlite3Utf8Read(&zString); - if( noCase ){ - GlobUpperToLower(c); - GlobUpperToLower(c2); - } - if( c!=c2 ){ - return 0; - } - prevEscape = 0; - } + return 1; /* "*" at the end of the pattern matches */ + }else if( c==matchOther ){ + if( esc ){ + c = sqlite3Utf8Read(&zPattern); + if( c==0 ) return 0; + }else{ + /* "[...]" immediately follows the "*". We have to do a slow + ** recursive search in this case, but it is an unusual case. */ + assert( matchOther<0x80 ); /* '[' is a single-byte character */ + while( *zString + && patternCompare(&zPattern[-1],zString,pInfo,esc)==0 ){ + SQLITE_SKIP_UTF8(zString); + } + return *zString!=0; + } + } + + /* At this point variable c contains the first character of the + ** pattern string past the "*". Search in the input string for the + ** first matching character and recursively contine the match from + ** that point. + ** + ** For a case-insensitive search, set variable cx to be the same as + ** c but in the other case and search the input string for either + ** c or cx. + */ + if( c<=0x80 ){ + u32 cx; + if( noCase ){ + cx = sqlite3Toupper(c); + c = sqlite3Tolower(c); + }else{ + cx = c; + } + while( (c2 = *(zString++))!=0 ){ + if( c2!=c && c2!=cx ) continue; + if( patternCompare(zPattern,zString,pInfo,esc) ) return 1; + } + }else{ + while( (c2 = sqlite3Utf8Read(&zString))!=0 ){ + if( c2!=c ) continue; + if( patternCompare(zPattern,zString,pInfo,esc) ) return 1; + } + } + return 0; + } + if( c==matchOther ){ + if( esc ){ + c = sqlite3Utf8Read(&zPattern); + if( c==0 ) return 0; + zEscaped = zPattern; + }else{ + u32 prior_c = 0; + int seen = 0; + int invert = 0; + c = sqlite3Utf8Read(&zString); + if( c==0 ) return 0; + c2 = sqlite3Utf8Read(&zPattern); + if( c2=='^' ){ + invert = 1; + c2 = sqlite3Utf8Read(&zPattern); + } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = sqlite3Utf8Read(&zPattern); + } + while( c2 && c2!=']' ){ + if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){ + c2 = sqlite3Utf8Read(&zPattern); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else{ + if( c==c2 ){ + seen = 1; + } + prior_c = c2; + } + c2 = sqlite3Utf8Read(&zPattern); + } + if( c2==0 || (seen ^ invert)==0 ){ + return 0; + } + continue; + } + } + c2 = sqlite3Utf8Read(&zString); + if( c==c2 ) continue; + if( noCase && c<0x80 && c2<0x80 && sqlite3Tolower(c)==sqlite3Tolower(c2) ){ + continue; + } + if( c==matchOne && zPattern!=zEscaped && c2!=0 ) continue; + return 0; } return *zString==0; } /* ** The sqlite3_strglob() interface. */ -SQLITE_API int sqlite3_strglob(const char *zGlobPattern, const char *zString){ +SQLITE_API int SQLITE_STDCALL sqlite3_strglob(const char *zGlobPattern, const char *zString){ return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, 0)==0; } /* ** Count the number of times that the LIKE operator (or GLOB which is @@ -89553,11 +97038,11 @@ int argc, sqlite3_value **argv ){ unsigned char *z, *zOut; int i; - zOut = z = sqlite3_malloc( argc*4 ); + zOut = z = sqlite3_malloc( argc*4+1 ); if( z==0 ){ sqlite3_result_error_nomem(context); return; } for(i=0; i<argc; i++){ @@ -89580,11 +97065,11 @@ *zOut++ = 0x80 + (u8)((c>>12) & 0x3F); *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); *zOut++ = 0x80 + (u8)(c & 0x3F); } \ } - sqlite3_result_text(context, (char*)z, (int)(zOut-z), sqlite3_free); + sqlite3_result_text64(context, (char*)z, zOut-z, sqlite3_free, SQLITE_UTF8); } /* ** The hex() function. Interpret the argument as a blob. Return ** a hexadecimal rendering as text. @@ -90030,10 +97515,11 @@ sqlite3VdbeMemCopy(pBest, pArg); }else{ sqlite3SkipAccumulatorLoad(context); } }else{ + pBest->db = sqlite3_context_db_handle(context); sqlite3VdbeMemCopy(pBest, pArg); } } static void minMaxFinalize(sqlite3_context *context){ sqlite3_value *pRes; @@ -90073,15 +97559,15 @@ nSep = sqlite3_value_bytes(argv[1]); }else{ zSep = ","; nSep = 1; } - sqlite3StrAccumAppend(pAccum, zSep, nSep); + if( nSep ) sqlite3StrAccumAppend(pAccum, zSep, nSep); } zVal = (char*)sqlite3_value_text(argv[0]); nVal = sqlite3_value_bytes(argv[0]); - sqlite3StrAccumAppend(pAccum, zVal, nVal); + if( zVal ) sqlite3StrAccumAppend(pAccum, zVal, nVal); } } static void groupConcatFinalize(sqlite3_context *context){ StrAccum *pAccum; pAccum = sqlite3_aggregate_context(context, 0); @@ -90147,10 +97633,15 @@ ** pExpr points to an expression which implements a function. If ** it is appropriate to apply the LIKE optimization to that function ** then set aWc[0] through aWc[2] to the wildcard characters and ** return TRUE. If the function is not a LIKE-style function then ** return FALSE. +** +** *pIsNocase is set to true if uppercase and lowercase are equivalent for +** the function (default for LIKE). If the function makes the distinction +** between uppercase and lowercase (as does GLOB) then *pIsNocase is set to +** false. */ SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){ FuncDef *pDef; if( pExpr->op!=TK_FUNCTION || !pExpr->x.pList @@ -90177,11 +97668,11 @@ *pIsNocase = (pDef->funcFlags & SQLITE_FUNC_CASE)==0; return 1; } /* -** All all of the FuncDef structures in the aBuiltinFunc[] array above +** All of the FuncDef structures in the aBuiltinFunc[] array above ** to the global function hash table. This occurs at start-time (as ** a consequence of calling sqlite3_initialize()). ** ** After this routine runs */ @@ -90201,19 +97692,22 @@ FUNCTION(rtrim, 2, 2, 0, trimFunc ), FUNCTION(trim, 1, 3, 0, trimFunc ), FUNCTION(trim, 2, 3, 0, trimFunc ), FUNCTION(min, -1, 0, 1, minmaxFunc ), FUNCTION(min, 0, 0, 1, 0 ), - AGGREGATE(min, 1, 0, 1, minmaxStep, minMaxFinalize ), + AGGREGATE2(min, 1, 0, 1, minmaxStep, minMaxFinalize, + SQLITE_FUNC_MINMAX ), FUNCTION(max, -1, 1, 1, minmaxFunc ), FUNCTION(max, 0, 1, 1, 0 ), - AGGREGATE(max, 1, 1, 1, minmaxStep, minMaxFinalize ), + AGGREGATE2(max, 1, 1, 1, minmaxStep, minMaxFinalize, + SQLITE_FUNC_MINMAX ), FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQLITE_FUNC_TYPEOF), FUNCTION2(length, 1, 0, 0, lengthFunc, SQLITE_FUNC_LENGTH), FUNCTION(instr, 2, 0, 0, instrFunc ), FUNCTION(substr, 2, 0, 0, substrFunc ), FUNCTION(substr, 3, 0, 0, substrFunc ), + FUNCTION(printf, -1, 0, 0, printfFunc ), FUNCTION(unicode, 1, 0, 0, unicodeFunc ), FUNCTION(char, -1, 0, 0, charFunc ), FUNCTION(abs, 1, 0, 0, absFunc ), #ifndef SQLITE_OMIT_FLOATING_POINT FUNCTION(round, 1, 0, 0, roundFunc ), @@ -90226,24 +97720,28 @@ FUNCTION2(coalesce, -1, 0, 0, noopFunc, SQLITE_FUNC_COALESCE), FUNCTION(hex, 1, 0, 0, hexFunc ), FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQLITE_FUNC_COALESCE), FUNCTION2(unlikely, 1, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), FUNCTION2(likelihood, 2, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), - FUNCTION(random, 0, 0, 0, randomFunc ), - FUNCTION(randomblob, 1, 0, 0, randomBlob ), + FUNCTION2(likely, 1, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), + VFUNCTION(random, 0, 0, 0, randomFunc ), + VFUNCTION(randomblob, 1, 0, 0, randomBlob ), FUNCTION(nullif, 2, 0, 1, nullifFunc ), FUNCTION(sqlite_version, 0, 0, 0, versionFunc ), FUNCTION(sqlite_source_id, 0, 0, 0, sourceidFunc ), FUNCTION(sqlite_log, 2, 0, 0, errlogFunc ), +#if SQLITE_USER_AUTHENTICATION + FUNCTION(sqlite_crypt, 2, 0, 0, sqlite3CryptFunc ), +#endif #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS FUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ), FUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ), #endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ FUNCTION(quote, 1, 0, 0, quoteFunc ), - FUNCTION(last_insert_rowid, 0, 0, 0, last_insert_rowid), - FUNCTION(changes, 0, 0, 0, changes ), - FUNCTION(total_changes, 0, 0, 0, total_changes ), + VFUNCTION(last_insert_rowid, 0, 0, 0, last_insert_rowid), + VFUNCTION(changes, 0, 0, 0, changes ), + VFUNCTION(total_changes, 0, 0, 0, total_changes ), FUNCTION(replace, 3, 0, 0, replaceFunc ), FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc ), #ifdef SQLITE_SOUNDEX FUNCTION(soundex, 1, 0, 0, soundexFunc ), #endif @@ -90252,12 +97750,12 @@ FUNCTION(load_extension, 2, 0, 0, loadExt ), #endif AGGREGATE(sum, 1, 0, 0, sumStep, sumFinalize ), AGGREGATE(total, 1, 0, 0, sumStep, totalFinalize ), AGGREGATE(avg, 1, 0, 0, sumStep, avgFinalize ), - /* AGGREGATE(count, 0, 0, 0, countStep, countFinalize ), */ - {0,SQLITE_UTF8|SQLITE_FUNC_COUNT,0,0,0,countStep,countFinalize,"count",0,0}, + AGGREGATE2(count, 0, 0, 0, countStep, countFinalize, + SQLITE_FUNC_COUNT ), AGGREGATE(count, 1, 0, 0, countStep, countFinalize ), AGGREGATE(group_concat, 1, 0, 0, groupConcatStep, groupConcatFinalize), AGGREGATE(group_concat, 2, 0, 0, groupConcatStep, groupConcatFinalize), LIKEFUNC(glob, 2, &globInfo, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), @@ -90460,11 +97958,11 @@ ** foreign key definition, and the parent table does not have a ** PRIMARY KEY, or ** ** 4) No parent key columns were provided explicitly as part of the ** foreign key definition, and the PRIMARY KEY of the parent table -** consists of a a different number of columns to the child key in +** consists of a different number of columns to the child key in ** the child table. ** ** then non-zero is returned, and a "foreign key mismatch" error loaded ** into pParse. If an OOM error occurs, non-zero is returned and the ** pParse->db->mallocFailed flag is set. @@ -90512,20 +98010,20 @@ if( !aiCol ) return 1; *paiCol = aiCol; } for(pIdx=pParent->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->nColumn==nCol && pIdx->onError!=OE_None ){ + if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) ){ /* pIdx is a UNIQUE index (or a PRIMARY KEY) and has the right number ** of columns. If each indexed column corresponds to a foreign key ** column of pFKey, then this index is a winner. */ if( zKey==0 ){ /* If zKey is NULL, then this foreign key is implicitly mapped to ** the PRIMARY KEY of table pParent. The PRIMARY KEY index may be - ** identified by the test (Index.autoIndex==2). */ - if( pIdx->autoIndex==2 ){ + ** identified by the test. */ + if( IsPrimaryKeyIndex(pIdx) ){ if( aiCol ){ int i; for(i=0; i<nCol; i++) aiCol[i] = pFKey->aCol[i].iFrom; } break; @@ -90535,11 +98033,11 @@ ** map to an explicit list of columns in table pParent. Check if this ** index matches those columns. Also, check that the index uses ** the default collation sequences for each column. */ int i, j; for(i=0; i<nCol; i++){ - int iCol = pIdx->aiColumn[i]; /* Index of column in parent tbl */ + i16 iCol = pIdx->aiColumn[i]; /* Index of column in parent tbl */ char *zDfltColl; /* Def. collation for column */ char *zIdxCol; /* Name of indexed column */ /* If the index uses a collation sequence that is different from ** the default collation sequence for the column, this index is @@ -90627,14 +98125,15 @@ ** Check if any of the key columns in the child table row are NULL. If ** any are, then the constraint is considered satisfied. No need to ** search for a matching row in the parent table. */ if( nIncr<0 ){ sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, iOk); + VdbeCoverage(v); } for(i=0; i<pFKey->nCol; i++){ int iReg = aiCol[i] + regData + 1; - sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iOk); + sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iOk); VdbeCoverage(v); } if( isIgnore==0 ){ if( pIdx==0 ){ /* If pIdx is NULL, then the parent key is the INTEGER PRIMARY KEY @@ -90647,33 +98146,34 @@ ** is no matching parent key. Before using MustBeInt, make a copy of ** the value. Otherwise, the value inserted into the child key column ** will have INTEGER affinity applied to it, which may not be correct. */ sqlite3VdbeAddOp2(v, OP_SCopy, aiCol[0]+1+regData, regTemp); iMustBeInt = sqlite3VdbeAddOp2(v, OP_MustBeInt, regTemp, 0); + VdbeCoverage(v); /* If the parent table is the same as the child table, and we are about ** to increment the constraint-counter (i.e. this is an INSERT operation), ** then check if the row being inserted matches itself. If so, do not ** increment the constraint-counter. */ if( pTab==pFKey->pFrom && nIncr==1 ){ - sqlite3VdbeAddOp3(v, OP_Eq, regData, iOk, regTemp); + sqlite3VdbeAddOp3(v, OP_Eq, regData, iOk, regTemp); VdbeCoverage(v); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); } sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenRead); - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regTemp); + sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regTemp); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Goto, 0, iOk); sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2); sqlite3VdbeJumpHere(v, iMustBeInt); sqlite3ReleaseTempReg(pParse, regTemp); }else{ int nCol = pFKey->nCol; int regTemp = sqlite3GetTempRange(pParse, nCol); int regRec = sqlite3GetTempReg(pParse); - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb); - sqlite3VdbeChangeP4(v, -1, (char*)pKey, P4_KEYINFO_HANDOFF); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); for(i=0; i<nCol; i++){ sqlite3VdbeAddOp2(v, OP_Copy, aiCol[i]+1+regData, regTemp+i); } /* If the parent table is the same as the child table, and we are about @@ -90694,19 +98194,19 @@ assert( aiCol[i]!=pTab->iPKey ); if( pIdx->aiColumn[i]==pTab->iPKey ){ /* The parent key is a composite key that includes the IPK column */ iParent = regData; } - sqlite3VdbeAddOp3(v, OP_Ne, iChild, iJump, iParent); + sqlite3VdbeAddOp3(v, OP_Ne, iChild, iJump, iParent); VdbeCoverage(v); sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL); } sqlite3VdbeAddOp2(v, OP_Goto, 0, iOk); } - sqlite3VdbeAddOp3(v, OP_MakeRecord, regTemp, nCol, regRec); - sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v,pIdx), P4_TRANSIENT); - sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regTemp, nCol, regRec, + sqlite3IndexAffinityStr(v,pIdx), nCol); + sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0); VdbeCoverage(v); sqlite3ReleaseTempReg(pParse, regRec); sqlite3ReleaseTempRange(pParse, regTemp, nCol); } } @@ -90719,59 +98219,118 @@ ** one row into the table, raise a constraint immediately instead of ** incrementing a counter. This is necessary as the VM code is being ** generated for will not open a statement transaction. */ assert( nIncr==1 ); sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY, - OE_Abort, "foreign key constraint failed", P4_STATIC - ); + OE_Abort, 0, P4_STATIC, P5_ConstraintFK); }else{ if( nIncr>0 && pFKey->isDeferred==0 ){ - sqlite3ParseToplevel(pParse)->mayAbort = 1; + sqlite3MayAbort(pParse); } sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); } sqlite3VdbeResolveLabel(v, iOk); sqlite3VdbeAddOp1(v, OP_Close, iCur); } + +/* +** Return an Expr object that refers to a memory register corresponding +** to column iCol of table pTab. +** +** regBase is the first of an array of register that contains the data +** for pTab. regBase itself holds the rowid. regBase+1 holds the first +** column. regBase+2 holds the second column, and so forth. +*/ +static Expr *exprTableRegister( + Parse *pParse, /* Parsing and code generating context */ + Table *pTab, /* The table whose content is at r[regBase]... */ + int regBase, /* Contents of table pTab */ + i16 iCol /* Which column of pTab is desired */ +){ + Expr *pExpr; + Column *pCol; + const char *zColl; + sqlite3 *db = pParse->db; + + pExpr = sqlite3Expr(db, TK_REGISTER, 0); + if( pExpr ){ + if( iCol>=0 && iCol!=pTab->iPKey ){ + pCol = &pTab->aCol[iCol]; + pExpr->iTable = regBase + iCol + 1; + pExpr->affinity = pCol->affinity; + zColl = pCol->zColl; + if( zColl==0 ) zColl = db->pDfltColl->zName; + pExpr = sqlite3ExprAddCollateString(pParse, pExpr, zColl); + }else{ + pExpr->iTable = regBase; + pExpr->affinity = SQLITE_AFF_INTEGER; + } + } + return pExpr; +} + +/* +** Return an Expr object that refers to column iCol of table pTab which +** has cursor iCur. +*/ +static Expr *exprTableColumn( + sqlite3 *db, /* The database connection */ + Table *pTab, /* The table whose column is desired */ + int iCursor, /* The open cursor on the table */ + i16 iCol /* The column that is wanted */ +){ + Expr *pExpr = sqlite3Expr(db, TK_COLUMN, 0); + if( pExpr ){ + pExpr->pTab = pTab; + pExpr->iTable = iCursor; + pExpr->iColumn = iCol; + } + return pExpr; +} + /* ** This function is called to generate code executed when a row is deleted ** from the parent table of foreign key constraint pFKey and, if pFKey is ** deferred, when a row is inserted into the same table. When generating ** code for an SQL UPDATE operation, this function may be called twice - ** once to "delete" the old row and once to "insert" the new row. +** +** Parameter nIncr is passed -1 when inserting a row (as this may decrease +** the number of FK violations in the db) or +1 when deleting one (as this +** may increase the number of FK constraint problems). ** ** The code generated by this function scans through the rows in the child ** table that correspond to the parent table row being deleted or inserted. ** For each child row found, one of the following actions is taken: ** ** Operation | FK type | Action taken ** -------------------------------------------------------------------------- ** DELETE immediate Increment the "immediate constraint counter". ** Or, if the ON (UPDATE|DELETE) action is RESTRICT, -** throw a "foreign key constraint failed" exception. +** throw a "FOREIGN KEY constraint failed" exception. ** ** INSERT immediate Decrement the "immediate constraint counter". ** ** DELETE deferred Increment the "deferred constraint counter". ** Or, if the ON (UPDATE|DELETE) action is RESTRICT, -** throw a "foreign key constraint failed" exception. +** throw a "FOREIGN KEY constraint failed" exception. ** ** INSERT deferred Decrement the "deferred constraint counter". ** ** These operations are identified in the comment at the top of this file ** (fkey.c) as "I.2" and "D.2". */ static void fkScanChildren( Parse *pParse, /* Parse context */ - SrcList *pSrc, /* SrcList containing the table to scan */ - Table *pTab, - Index *pIdx, /* Foreign key index */ - FKey *pFKey, /* Foreign key relationship */ + SrcList *pSrc, /* The child table to be scanned */ + Table *pTab, /* The parent table */ + Index *pIdx, /* Index on parent covering the foreign key */ + FKey *pFKey, /* The foreign key linking pSrc to pTab */ int *aiCol, /* Map from pIdx cols to child table cols */ - int regData, /* Referenced table data starts here */ + int regData, /* Parent row data starts here */ int nIncr /* Amount to increment deferred counter by */ ){ sqlite3 *db = pParse->db; /* Database handle */ int i; /* Iterator variable */ Expr *pWhere = 0; /* WHERE clause to scan with */ @@ -90778,14 +98337,18 @@ NameContext sNameContext; /* Context used to resolve WHERE clause */ WhereInfo *pWInfo; /* Context used by sqlite3WhereXXX() */ int iFkIfZero = 0; /* Address of OP_FkIfZero */ Vdbe *v = sqlite3GetVdbe(pParse); - assert( !pIdx || pIdx->pTable==pTab ); + assert( pIdx==0 || pIdx->pTable==pTab ); + assert( pIdx==0 || pIdx->nKeyCol==pFKey->nCol ); + assert( pIdx!=0 || pFKey->nCol==1 ); + assert( pIdx!=0 || HasRowid(pTab) ); if( nIncr<0 ){ iFkIfZero = sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, 0); + VdbeCoverage(v); } /* Create an Expr object representing an SQL expression like: ** ** <parent-key1> = <child-key1> AND <parent-key2> = <child-key2> ... @@ -90796,75 +98359,68 @@ */ for(i=0; i<pFKey->nCol; i++){ Expr *pLeft; /* Value from parent table row */ Expr *pRight; /* Column ref to child table */ Expr *pEq; /* Expression (pLeft = pRight) */ - int iCol; /* Index of column in child table */ + i16 iCol; /* Index of column in child table */ const char *zCol; /* Name of column in child table */ - pLeft = sqlite3Expr(db, TK_REGISTER, 0); - if( pLeft ){ - /* Set the collation sequence and affinity of the LHS of each TK_EQ - ** expression to the parent key column defaults. */ - if( pIdx ){ - Column *pCol; - const char *zColl; - iCol = pIdx->aiColumn[i]; - pCol = &pTab->aCol[iCol]; - if( pTab->iPKey==iCol ) iCol = -1; - pLeft->iTable = regData+iCol+1; - pLeft->affinity = pCol->affinity; - zColl = pCol->zColl; - if( zColl==0 ) zColl = db->pDfltColl->zName; - pLeft = sqlite3ExprAddCollateString(pParse, pLeft, zColl); - }else{ - pLeft->iTable = regData; - pLeft->affinity = SQLITE_AFF_INTEGER; - } - } + iCol = pIdx ? pIdx->aiColumn[i] : -1; + pLeft = exprTableRegister(pParse, pTab, regData, iCol); iCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom; assert( iCol>=0 ); zCol = pFKey->pFrom->aCol[iCol].zName; pRight = sqlite3Expr(db, TK_ID, zCol); pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0); pWhere = sqlite3ExprAnd(db, pWhere, pEq); } - /* If the child table is the same as the parent table, and this scan - ** is taking place as part of a DELETE operation (operation D.2), omit the - ** row being deleted from the scan by adding ($rowid != rowid) to the WHERE - ** clause, where $rowid is the rowid of the row being deleted. */ + /* If the child table is the same as the parent table, then add terms + ** to the WHERE clause that prevent this entry from being scanned. + ** The added WHERE clause terms are like this: + ** + ** $current_rowid!=rowid + ** NOT( $current_a==a AND $current_b==b AND ... ) + ** + ** The first form is used for rowid tables. The second form is used + ** for WITHOUT ROWID tables. In the second form, the primary key is + ** (a,b,...) + */ if( pTab==pFKey->pFrom && nIncr>0 ){ - Expr *pEq; /* Expression (pLeft = pRight) */ + Expr *pNe; /* Expression (pLeft != pRight) */ Expr *pLeft; /* Value from parent table row */ Expr *pRight; /* Column ref to child table */ - pLeft = sqlite3Expr(db, TK_REGISTER, 0); - pRight = sqlite3Expr(db, TK_COLUMN, 0); - if( pLeft && pRight ){ - pLeft->iTable = regData; - pLeft->affinity = SQLITE_AFF_INTEGER; - pRight->iTable = pSrc->a[0].iCursor; - pRight->iColumn = -1; - } - pEq = sqlite3PExpr(pParse, TK_NE, pLeft, pRight, 0); - pWhere = sqlite3ExprAnd(db, pWhere, pEq); + if( HasRowid(pTab) ){ + pLeft = exprTableRegister(pParse, pTab, regData, -1); + pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, -1); + pNe = sqlite3PExpr(pParse, TK_NE, pLeft, pRight, 0); + }else{ + Expr *pEq, *pAll = 0; + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pIdx!=0 ); + for(i=0; i<pPk->nKeyCol; i++){ + i16 iCol = pIdx->aiColumn[i]; + pLeft = exprTableRegister(pParse, pTab, regData, iCol); + pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, iCol); + pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0); + pAll = sqlite3ExprAnd(db, pAll, pEq); + } + pNe = sqlite3PExpr(pParse, TK_NOT, pAll, 0, 0); + } + pWhere = sqlite3ExprAnd(db, pWhere, pNe); } /* Resolve the references in the WHERE clause. */ memset(&sNameContext, 0, sizeof(NameContext)); sNameContext.pSrcList = pSrc; sNameContext.pParse = pParse; sqlite3ResolveExprNames(&sNameContext, pWhere); /* Create VDBE to loop through the entries in pSrc that match the WHERE - ** clause. If the constraint is not deferred, throw an exception for - ** each row found. Otherwise, for deferred constraints, increment the - ** deferred constraint counter by nIncr for each row selected. */ + ** clause. For each row found, increment either the deferred or immediate + ** foreign key constraint counter. */ pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0); - if( nIncr>0 && pFKey->isDeferred==0 ){ - sqlite3ParseToplevel(pParse)->mayAbort = 1; - } sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); if( pWInfo ){ sqlite3WhereEnd(pWInfo); } @@ -90874,12 +98430,12 @@ sqlite3VdbeJumpHere(v, iFkIfZero); } } /* -** This function returns a pointer to the head of a linked list of FK -** constraints for which table pTab is the parent table. For example, +** This function returns a linked list of FKey objects (connected by +** FKey.pNextTo) holding all children of table pTab. For example, ** given the following schema: ** ** CREATE TABLE t1(a PRIMARY KEY); ** CREATE TABLE t2(b REFERENCES t1(a); ** @@ -90888,12 +98444,11 @@ ** "t2". Calling this function with "t2" as the argument would return a ** NULL pointer (as there are no FK constraints for which t2 is the parent ** table). */ SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *pTab){ - int nName = sqlite3Strlen30(pTab->zName); - return (FKey *)sqlite3HashFind(&pTab->pSchema->fkeyHash, pTab->zName, nName); + return (FKey *)sqlite3HashFind(&pTab->pSchema->fkeyHash, pTab->zName); } /* ** The second argument is a Trigger structure allocated by the ** fkActionTrigger() routine. This function deletes the Trigger structure @@ -90943,29 +98498,36 @@ ** generating any VDBE code. If one can be found, then jump over ** the entire DELETE if there are no outstanding deferred constraints ** when this statement is run. */ FKey *p; for(p=pTab->pFKey; p; p=p->pNextFrom){ - if( p->isDeferred ) break; + if( p->isDeferred || (db->flags & SQLITE_DeferFKs) ) break; } if( !p ) return; iSkip = sqlite3VdbeMakeLabel(v); - sqlite3VdbeAddOp2(v, OP_FkIfZero, 1, iSkip); + sqlite3VdbeAddOp2(v, OP_FkIfZero, 1, iSkip); VdbeCoverage(v); } pParse->disableTriggers = 1; sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0); pParse->disableTriggers = 0; /* If the DELETE has generated immediate foreign key constraint ** violations, halt the VDBE and return an error at this point, before ** any modifications to the schema are made. This is because statement - ** transactions are not able to rollback schema changes. */ - sqlite3VdbeAddOp2(v, OP_FkIfZero, 0, sqlite3VdbeCurrentAddr(v)+2); - sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY, - OE_Abort, "foreign key constraint failed", P4_STATIC - ); + ** transactions are not able to rollback schema changes. + ** + ** If the SQLITE_DeferFKs flag is set, then this is not required, as + ** the statement transaction will not be rolled back even if FK + ** constraints are violated. + */ + if( (db->flags & SQLITE_DeferFKs)==0 ){ + sqlite3VdbeAddOp2(v, OP_FkIfZero, 0, sqlite3VdbeCurrentAddr(v)+2); + VdbeCoverage(v); + sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY, + OE_Abort, 0, P4_STATIC, P5_ConstraintFK); + } if( iSkip ){ sqlite3VdbeResolveLabel(v, iSkip); } } @@ -91032,10 +98594,28 @@ } } } return 0; } + +/* +** Return true if the parser passed as the first argument is being +** used to code a trigger that is really a "SET NULL" action belonging +** to trigger pFKey. +*/ +static int isSetNullAction(Parse *pParse, FKey *pFKey){ + Parse *pTop = sqlite3ParseToplevel(pParse); + if( pTop->pTriggerPrg ){ + Trigger *p = pTop->pTriggerPrg->pTrigger; + if( (p==pFKey->apTrigger[0] && pFKey->aAction[0]==OE_SetNull) + || (p==pFKey->apTrigger[1] && pFKey->aAction[1]==OE_SetNull) + ){ + return 1; + } + } + return 0; +} /* ** This function is called when inserting, deleting or updating a row of ** table pTab to generate VDBE code to perform foreign key constraint ** processing for the operation. @@ -91085,11 +98665,11 @@ Index *pIdx = 0; /* Index on key columns in pTo */ int *aiFree = 0; int *aiCol; int iCol; int i; - int isIgnore = 0; + int bIgnore = 0; if( aChange && sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0 && fkChildIsModified(pTab, pFKey, aChange, bChngRowid)==0 ){ @@ -91118,11 +98698,11 @@ */ Vdbe *v = sqlite3GetVdbe(pParse); int iJump = sqlite3VdbeCurrentAddr(v) + pFKey->nCol + 1; for(i=0; i<pFKey->nCol; i++){ int iReg = pFKey->aCol[i].iFrom + regOld + 1; - sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iJump); + sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iJump); VdbeCoverage(v); } sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, -1); } continue; } @@ -91144,11 +98724,11 @@ ** values read from the parent table are NULL. */ if( db->xAuth ){ int rcauth; char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zName; rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb); - isIgnore = (rcauth==SQLITE_IGNORE); + bIgnore = (rcauth==SQLITE_IGNORE); } #endif } /* Take a shared-cache advisory read-lock on the parent table. Allocate @@ -91159,22 +98739,29 @@ if( regOld!=0 ){ /* A row is being removed from the child table. Search for the parent. ** If the parent does not exist, removing the child row resolves an ** outstanding foreign key constraint violation. */ - fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1,isIgnore); + fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1, bIgnore); } - if( regNew!=0 ){ + if( regNew!=0 && !isSetNullAction(pParse, pFKey) ){ /* A row is being added to the child table. If a parent row cannot - ** be found, adding the child row has violated the FK constraint. */ - fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1,isIgnore); + ** be found, adding the child row has violated the FK constraint. + ** + ** If this operation is being performed as part of a trigger program + ** that is actually a "SET NULL" action belonging to this very + ** foreign key, then omit this scan altogether. As all child key + ** values are guaranteed to be NULL, it is not possible for adding + ** this row to cause an FK violation. */ + fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1, bIgnore); } sqlite3DbFree(db, aiFree); } - /* Loop through all the foreign key constraints that refer to this table */ + /* Loop through all the foreign key constraints that refer to this table. + ** (the "child" constraints) */ for(pFKey = sqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){ Index *pIdx = 0; /* Foreign key index for pFKey */ SrcList *pSrc; int *aiCol = 0; @@ -91184,24 +98771,23 @@ if( !pFKey->isDeferred && !(db->flags & SQLITE_DeferFKs) && !pParse->pToplevel && !pParse->isMultiWrite ){ assert( regOld==0 && regNew!=0 ); - /* Inserting a single row into a parent table cannot cause an immediate - ** foreign key violation. So do nothing in this case. */ + /* Inserting a single row into a parent table cannot cause (or fix) + ** an immediate foreign key violation. So do nothing in this case. */ continue; } if( sqlite3FkLocateIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ){ if( !isIgnoreErrors || db->mallocFailed ) return; continue; } assert( aiCol || pFKey->nCol==1 ); - /* Create a SrcList structure containing a single table (the table - ** the foreign key that refers to this table is attached to). This - ** is required for the sqlite3WhereXXX() interface. */ + /* Create a SrcList structure containing the child table. We need the + ** child table as a SrcList for sqlite3WhereBegin() */ pSrc = sqlite3SrcListAppend(db, 0, 0, 0); if( pSrc ){ struct SrcList_item *pItem = pSrc->a; pItem->pTab = pFKey->pFrom; pItem->zName = pFKey->pFrom->zName; @@ -91210,17 +98796,32 @@ if( regNew!=0 ){ fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regNew, -1); } if( regOld!=0 ){ - /* If there is a RESTRICT action configured for the current operation - ** on the parent table of this FK, then throw an exception - ** immediately if the FK constraint is violated, even if this is a - ** deferred trigger. That's what RESTRICT means. To defer checking - ** the constraint, the FK should specify NO ACTION (represented - ** using OE_None). NO ACTION is the default. */ + int eAction = pFKey->aAction[aChange!=0]; fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regOld, 1); + /* If this is a deferred FK constraint, or a CASCADE or SET NULL + ** action applies, then any foreign key violations caused by + ** removing the parent key will be rectified by the action trigger. + ** So do not set the "may-abort" flag in this case. + ** + ** Note 1: If the FK is declared "ON UPDATE CASCADE", then the + ** may-abort flag will eventually be set on this statement anyway + ** (when this function is called as part of processing the UPDATE + ** within the action trigger). + ** + ** Note 2: At first glance it may seem like SQLite could simply omit + ** all OP_FkCounter related scans when either CASCADE or SET NULL + ** applies. The trouble starts if the CASCADE or SET NULL action + ** trigger causes other triggers or action rules attached to the + ** child table to fire. In these cases the fk constraint counters + ** might be set incorrectly if any OP_FkCounter related scans are + ** omitted. */ + if( !pFKey->isDeferred && eAction!=OE_Cascade && eAction!=OE_SetNull ){ + sqlite3MayAbort(pParse); + } } pItem->zName = 0; sqlite3SrcListDelete(db, pSrc); } sqlite3DbFree(db, aiCol); @@ -91246,11 +98847,11 @@ } for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ Index *pIdx = 0; sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0); if( pIdx ){ - for(i=0; i<pIdx->nColumn; i++) mask |= COLUMN_MASK(pIdx->aiColumn[i]); + for(i=0; i<pIdx->nKeyCol; i++) mask |= COLUMN_MASK(pIdx->aiColumn[i]); } } } return mask; } @@ -91438,11 +99039,11 @@ Token tFrom; Expr *pRaise; tFrom.z = zFrom; tFrom.n = nFrom; - pRaise = sqlite3Expr(db, TK_RAISE, "foreign key constraint failed"); + pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed"); if( pRaise ){ pRaise->affinity = OE_Abort; } pSelect = sqlite3SelectNew(pParse, sqlite3ExprListAppend(pParse, 0, pRaise), @@ -91560,11 +99161,11 @@ if( pFKey->pPrevTo ){ pFKey->pPrevTo->pNextTo = pFKey->pNextTo; }else{ void *p = (void *)pFKey->pNextTo; const char *z = (p ? pFKey->pNextTo->zTo : pFKey->zTo); - sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, sqlite3Strlen30(z), p); + sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, p); } if( pFKey->pNextTo ){ pFKey->pNextTo->pPrevTo = pFKey->pPrevTo; } } @@ -91602,43 +99203,58 @@ ** This file contains C code routines that are called by the parser ** to handle INSERT statements in SQLite. */ /* -** Generate code that will open a table for reading. +** Generate code that will +** +** (1) acquire a lock for table pTab then +** (2) open pTab as cursor iCur. +** +** If pTab is a WITHOUT ROWID table, then it is the PRIMARY KEY index +** for that table that is actually opened. */ SQLITE_PRIVATE void sqlite3OpenTable( - Parse *p, /* Generate code into this VDBE */ + Parse *pParse, /* Generate code into this VDBE */ int iCur, /* The cursor number of the table */ int iDb, /* The database index in sqlite3.aDb[] */ Table *pTab, /* The table to be opened */ int opcode /* OP_OpenRead or OP_OpenWrite */ ){ Vdbe *v; assert( !IsVirtual(pTab) ); - v = sqlite3GetVdbe(p); + v = sqlite3GetVdbe(pParse); assert( opcode==OP_OpenWrite || opcode==OP_OpenRead ); - sqlite3TableLock(p, iDb, pTab->tnum, (opcode==OP_OpenWrite)?1:0, pTab->zName); - sqlite3VdbeAddOp3(v, opcode, iCur, pTab->tnum, iDb); - sqlite3VdbeChangeP4(v, -1, SQLITE_INT_TO_PTR(pTab->nCol), P4_INT32); - VdbeComment((v, "%s", pTab->zName)); + sqlite3TableLock(pParse, iDb, pTab->tnum, + (opcode==OP_OpenWrite)?1:0, pTab->zName); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp4Int(v, opcode, iCur, pTab->tnum, iDb, pTab->nCol); + VdbeComment((v, "%s", pTab->zName)); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + assert( pPk->tnum=pTab->tnum ); + sqlite3VdbeAddOp3(v, opcode, iCur, pPk->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + VdbeComment((v, "%s", pTab->zName)); + } } /* ** Return a pointer to the column affinity string associated with index ** pIdx. A column affinity string has one character for each column in ** the table, according to the affinity of the column: ** ** Character Column affinity ** ------------------------------ -** 'a' TEXT -** 'b' NONE -** 'c' NUMERIC -** 'd' INTEGER -** 'e' REAL +** 'A' NONE +** 'B' TEXT +** 'C' NUMERIC +** 'D' INTEGER +** 'F' REAL ** -** An extra 'd' is appended to the end of the string to cover the +** An extra 'D' is appended to the end of the string to cover the ** rowid that appears as the last column in every index. ** ** Memory for the buffer containing the column index affinity string ** is managed along with the rest of the Index structure. It will be ** released when sqlite3DeleteIndex() is called. @@ -91654,85 +99270,89 @@ ** up. */ int n; Table *pTab = pIdx->pTable; sqlite3 *db = sqlite3VdbeDb(v); - pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, pIdx->nColumn+2); + pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, pIdx->nColumn+1); if( !pIdx->zColAff ){ db->mallocFailed = 1; return 0; } for(n=0; n<pIdx->nColumn; n++){ - pIdx->zColAff[n] = pTab->aCol[pIdx->aiColumn[n]].affinity; + i16 x = pIdx->aiColumn[n]; + pIdx->zColAff[n] = x<0 ? SQLITE_AFF_INTEGER : pTab->aCol[x].affinity; } - pIdx->zColAff[n++] = SQLITE_AFF_INTEGER; pIdx->zColAff[n] = 0; } return pIdx->zColAff; } /* -** Set P4 of the most recently inserted opcode to a column affinity -** string for table pTab. A column affinity string has one character -** for each column indexed by the index, according to the affinity of the -** column: +** Compute the affinity string for table pTab, if it has not already been +** computed. As an optimization, omit trailing SQLITE_AFF_NONE affinities. +** +** If the affinity exists (if it is no entirely SQLITE_AFF_NONE values) and +** if iReg>0 then code an OP_Affinity opcode that will set the affinities +** for register iReg and following. Or if affinities exists and iReg==0, +** then just set the P4 operand of the previous opcode (which should be +** an OP_MakeRecord) to the affinity string. +** +** A column affinity string has one character per column: ** ** Character Column affinity ** ------------------------------ -** 'a' TEXT -** 'b' NONE -** 'c' NUMERIC -** 'd' INTEGER -** 'e' REAL -*/ -SQLITE_PRIVATE void sqlite3TableAffinityStr(Vdbe *v, Table *pTab){ - /* The first time a column affinity string for a particular table - ** is required, it is allocated and populated here. It is then - ** stored as a member of the Table structure for subsequent use. - ** - ** The column affinity string will eventually be deleted by - ** sqlite3DeleteTable() when the Table structure itself is cleaned up. - */ - if( !pTab->zColAff ){ - char *zColAff; - int i; - sqlite3 *db = sqlite3VdbeDb(v); - +** 'A' NONE +** 'B' TEXT +** 'C' NUMERIC +** 'D' INTEGER +** 'E' REAL +*/ +SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ + int i; + char *zColAff = pTab->zColAff; + if( zColAff==0 ){ + sqlite3 *db = sqlite3VdbeDb(v); zColAff = (char *)sqlite3DbMallocRaw(0, pTab->nCol+1); if( !zColAff ){ db->mallocFailed = 1; return; } for(i=0; i<pTab->nCol; i++){ zColAff[i] = pTab->aCol[i].affinity; } - zColAff[pTab->nCol] = '\0'; - + do{ + zColAff[i--] = 0; + }while( i>=0 && zColAff[i]==SQLITE_AFF_NONE ); pTab->zColAff = zColAff; } - - sqlite3VdbeChangeP4(v, -1, pTab->zColAff, P4_TRANSIENT); + i = sqlite3Strlen30(zColAff); + if( i ){ + if( iReg ){ + sqlite3VdbeAddOp4(v, OP_Affinity, iReg, i, 0, zColAff, i); + }else{ + sqlite3VdbeChangeP4(v, -1, zColAff, i); + } + } } /* ** Return non-zero if the table pTab in database iDb or any of its indices -** have been opened at any point in the VDBE program beginning at location -** iStartAddr throught the end of the program. This is used to see if +** have been opened at any point in the VDBE program. This is used to see if ** a statement of the form "INSERT INTO <iDb, pTab> SELECT ..." can -** run without using temporary table for the results of the SELECT. +** run without using a temporary table for the results of the SELECT. */ -static int readsTable(Parse *p, int iStartAddr, int iDb, Table *pTab){ +static int readsTable(Parse *p, int iDb, Table *pTab){ Vdbe *v = sqlite3GetVdbe(p); int i; int iEnd = sqlite3VdbeCurrentAddr(v); #ifndef SQLITE_OMIT_VIRTUALTABLE VTable *pVTab = IsVirtual(pTab) ? sqlite3GetVTable(p->db, pTab) : 0; #endif - for(i=iStartAddr; i<iEnd; i++){ + for(i=1; i<iEnd; i++){ VdbeOp *pOp = sqlite3VdbeGetOp(v, i); assert( pOp!=0 ); if( pOp->opcode==OP_OpenRead && pOp->p3==iDb ){ Index *pIndex; int tnum = pOp->p2; @@ -91829,18 +99449,18 @@ assert( sqlite3SchemaMutexHeld(db, 0, pDb->pSchema) ); sqlite3OpenTable(pParse, 0, p->iDb, pDb->pSchema->pSeqTab, OP_OpenRead); sqlite3VdbeAddOp3(v, OP_Null, 0, memId, memId+1); addr = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp4(v, OP_String8, 0, memId-1, 0, p->pTab->zName, 0); - sqlite3VdbeAddOp2(v, OP_Rewind, 0, addr+9); + sqlite3VdbeAddOp2(v, OP_Rewind, 0, addr+9); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_Column, 0, 0, memId); - sqlite3VdbeAddOp3(v, OP_Ne, memId-1, addr+7, memId); + sqlite3VdbeAddOp3(v, OP_Ne, memId-1, addr+7, memId); VdbeCoverage(v); sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL); sqlite3VdbeAddOp2(v, OP_Rowid, 0, memId+1); sqlite3VdbeAddOp3(v, OP_Column, 0, 1, memId); sqlite3VdbeAddOp2(v, OP_Goto, 0, addr+9); - sqlite3VdbeAddOp2(v, OP_Next, 0, addr+2); + sqlite3VdbeAddOp2(v, OP_Next, 0, addr+2); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Integer, 0, memId); sqlite3VdbeAddOp0(v, OP_Close); } } @@ -91871,29 +99491,20 @@ sqlite3 *db = pParse->db; assert( v ); for(p = pParse->pAinc; p; p = p->pNext){ Db *pDb = &db->aDb[p->iDb]; - int j1, j2, j3, j4, j5; + int j1; int iRec; int memId = p->regCtr; iRec = sqlite3GetTempReg(pParse); assert( sqlite3SchemaMutexHeld(db, 0, pDb->pSchema) ); sqlite3OpenTable(pParse, 0, p->iDb, pDb->pSchema->pSeqTab, OP_OpenWrite); - j1 = sqlite3VdbeAddOp1(v, OP_NotNull, memId+1); - j2 = sqlite3VdbeAddOp0(v, OP_Rewind); - j3 = sqlite3VdbeAddOp3(v, OP_Column, 0, 0, iRec); - j4 = sqlite3VdbeAddOp3(v, OP_Eq, memId-1, 0, iRec); - sqlite3VdbeAddOp2(v, OP_Next, 0, j3); - sqlite3VdbeJumpHere(v, j2); + j1 = sqlite3VdbeAddOp1(v, OP_NotNull, memId+1); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_NewRowid, 0, memId+1); - j5 = sqlite3VdbeAddOp0(v, OP_Goto); - sqlite3VdbeJumpHere(v, j4); - sqlite3VdbeAddOp2(v, OP_Rowid, 0, memId+1); sqlite3VdbeJumpHere(v, j1); - sqlite3VdbeJumpHere(v, j5); sqlite3VdbeAddOp3(v, OP_MakeRecord, memId-1, 2, iRec); sqlite3VdbeAddOp3(v, OP_Insert, 0, iRec, memId+1); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); sqlite3VdbeAddOp0(v, OP_Close); sqlite3ReleaseTempReg(pParse, iRec); @@ -91906,101 +99517,10 @@ */ # define autoIncBegin(A,B,C) (0) # define autoIncStep(A,B,C) #endif /* SQLITE_OMIT_AUTOINCREMENT */ - -/* -** Generate code for a co-routine that will evaluate a subquery one -** row at a time. -** -** The pSelect parameter is the subquery that the co-routine will evaluation. -** Information about the location of co-routine and the registers it will use -** is returned by filling in the pDest object. -** -** Registers are allocated as follows: -** -** pDest->iSDParm The register holding the next entry-point of the -** co-routine. Run the co-routine to its next breakpoint -** by calling "OP_Yield $X" where $X is pDest->iSDParm. -** -** pDest->iSDParm+1 The register holding the "completed" flag for the -** co-routine. This register is 0 if the previous Yield -** generated a new result row, or 1 if the subquery -** has completed. If the Yield is called again -** after this register becomes 1, then the VDBE will -** halt with an SQLITE_INTERNAL error. -** -** pDest->iSdst First result register. -** -** pDest->nSdst Number of result registers. -** -** This routine handles all of the register allocation and fills in the -** pDest structure appropriately. -** -** Here is a schematic of the generated code assuming that X is the -** co-routine entry-point register reg[pDest->iSDParm], that EOF is the -** completed flag reg[pDest->iSDParm+1], and R and S are the range of -** registers that hold the result set, reg[pDest->iSdst] through -** reg[pDest->iSdst+pDest->nSdst-1]: -** -** X <- A -** EOF <- 0 -** goto B -** A: setup for the SELECT -** loop rows in the SELECT -** load results into registers R..S -** yield X -** end loop -** cleanup after the SELECT -** EOF <- 1 -** yield X -** halt-error -** B: -** -** To use this subroutine, the caller generates code as follows: -** -** [ Co-routine generated by this subroutine, shown above ] -** S: yield X -** if EOF goto E -** if skip this row, goto C -** if terminate loop, goto E -** deal with this row -** C: goto S -** E: -*/ -SQLITE_PRIVATE int sqlite3CodeCoroutine(Parse *pParse, Select *pSelect, SelectDest *pDest){ - int regYield; /* Register holding co-routine entry-point */ - int regEof; /* Register holding co-routine completion flag */ - int addrTop; /* Top of the co-routine */ - int j1; /* Jump instruction */ - int rc; /* Result code */ - Vdbe *v; /* VDBE under construction */ - - regYield = ++pParse->nMem; - regEof = ++pParse->nMem; - v = sqlite3GetVdbe(pParse); - addrTop = sqlite3VdbeCurrentAddr(v); - sqlite3VdbeAddOp2(v, OP_Integer, addrTop+2, regYield); /* X <- A */ - VdbeComment((v, "Co-routine entry point")); - sqlite3VdbeAddOp2(v, OP_Integer, 0, regEof); /* EOF <- 0 */ - VdbeComment((v, "Co-routine completion flag")); - sqlite3SelectDestInit(pDest, SRT_Coroutine, regYield); - j1 = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0); - rc = sqlite3Select(pParse, pSelect, pDest); - assert( pParse->nErr==0 || rc ); - if( pParse->db->mallocFailed && rc==SQLITE_OK ) rc = SQLITE_NOMEM; - if( rc ) return rc; - sqlite3VdbeAddOp2(v, OP_Integer, 1, regEof); /* EOF <- 1 */ - sqlite3VdbeAddOp1(v, OP_Yield, regYield); /* yield X */ - sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_INTERNAL, OE_Abort); - VdbeComment((v, "End of coroutine")); - sqlite3VdbeJumpHere(v, j1); /* label B: */ - return rc; -} - - /* Forward declaration */ static int xferOptimization( Parse *pParse, /* Parser context */ Table *pDest, /* The table we are inserting into */ @@ -92008,11 +99528,11 @@ int onError, /* How to handle constraint errors */ int iDbDest /* The database of pDest */ ); /* -** This routine is call to handle SQL of the following forms: +** This routine is called to handle SQL of the following forms: ** ** insert into TABLE (IDLIST) values(EXPRLIST) ** insert into TABLE (IDLIST) select ** ** The IDLIST following the table name is always optional. If omitted, @@ -92023,16 +99543,16 @@ ** statement above, and pSelect is NULL. For the second form, pList is ** NULL and pSelect is a pointer to the select statement used to generate ** data for the insert. ** ** The code generated follows one of four templates. For a simple -** select with data coming from a VALUES clause, the code executes +** insert with data coming from a VALUES clause, the code executes ** once straight down through. Pseudo-code follows (we call this ** the "1st template"): ** ** open write cursor to <table> and its indices -** puts VALUES clause expressions onto the stack +** put VALUES clause expressions into registers ** write the resulting record into <table> ** cleanup ** ** The three remaining templates assume the statement is of the form ** @@ -92060,50 +99580,42 @@ ** ** The 3rd template is for when the second template does not apply ** and the SELECT clause does not read from <table> at any time. ** The generated code follows this template: ** -** EOF <- 0 ** X <- A ** goto B ** A: setup for the SELECT ** loop over the rows in the SELECT ** load values into registers R..R+n ** yield X ** end loop ** cleanup after the SELECT -** EOF <- 1 -** yield X -** goto A +** end-coroutine X ** B: open write cursor to <table> and its indices -** C: yield X -** if EOF goto D +** C: yield X, at EOF goto D ** insert the select result into <table> from R..R+n ** goto C ** D: cleanup ** ** The 4th template is used if the insert statement takes its ** values from a SELECT but the data is being inserted into a table ** that is also read as part of the SELECT. In the third form, -** we have to use a intermediate table to store the results of +** we have to use an intermediate table to store the results of ** the select. The template is like this: ** -** EOF <- 0 ** X <- A ** goto B ** A: setup for the SELECT ** loop over the tables in the SELECT ** load value into register R..R+n ** yield X ** end loop ** cleanup after the SELECT -** EOF <- 1 -** yield X -** halt-error +** end co-routine R ** B: open temp table -** L: yield X -** if EOF goto M +** L: yield X, at EOF goto M ** insert row from R..R+n into temp table ** goto L ** M: open write cursor to <table> and its indices ** rewind temp table ** C: loop over rows of intermediate table @@ -92112,11 +99624,10 @@ ** D: cleanup */ SQLITE_PRIVATE void sqlite3Insert( Parse *pParse, /* Parser context */ SrcList *pTabList, /* Name of table into which we are inserting */ - ExprList *pList, /* List of values to be inserted */ Select *pSelect, /* A SELECT statement to use as the data source */ IdList *pColumn, /* Column names corresponding to IDLIST. */ int onError /* How to handle constraint errors */ ){ sqlite3 *db; /* The main database structure */ @@ -92126,31 +99637,33 @@ int i, j, idx; /* Loop counters */ Vdbe *v; /* Generate code into this virtual machine */ Index *pIdx; /* For looping over indices of the table */ int nColumn; /* Number of columns in the data */ int nHidden = 0; /* Number of hidden columns if TABLE is virtual */ - int baseCur = 0; /* VDBE Cursor number for pTab */ - int keyColumn = -1; /* Column that is the INTEGER PRIMARY KEY */ + int iDataCur = 0; /* VDBE cursor that is the main data repository */ + int iIdxCur = 0; /* First index cursor */ + int ipkColumn = -1; /* Column that is the INTEGER PRIMARY KEY */ int endOfLoop; /* Label for the end of the insertion loop */ - int useTempTable = 0; /* Store SELECT results in intermediate table */ int srcTab = 0; /* Data comes from this temporary cursor if >=0 */ int addrInsTop = 0; /* Jump to label "D" */ int addrCont = 0; /* Top of insert loop. Label "C" in templates 3 and 4 */ - int addrSelect = 0; /* Address of coroutine that implements the SELECT */ SelectDest dest; /* Destination for SELECT on rhs of INSERT */ int iDb; /* Index of database holding TABLE */ Db *pDb; /* The database containing table being inserted into */ - int appendFlag = 0; /* True if the insert is likely to be an append */ + u8 useTempTable = 0; /* Store SELECT results in intermediate table */ + u8 appendFlag = 0; /* True if the insert is likely to be an append */ + u8 withoutRowid; /* 0 for normal table. 1 for WITHOUT ROWID table */ + u8 bIdListInOrder = 1; /* True if IDLIST is in table order */ + ExprList *pList = 0; /* List of VALUES() to be inserted */ /* Register allocations */ int regFromSelect = 0;/* Base register for data coming from SELECT */ int regAutoinc = 0; /* Register holding the AUTOINCREMENT counter */ int regRowCount = 0; /* Memory cell used for the row counter */ int regIns; /* Block of regs holding rowid+data being inserted */ int regRowid; /* registers holding insert rowid */ int regData; /* register holding first column to insert */ - int regEof = 0; /* Register recording end of SELECT data */ int *aRegIdx = 0; /* One register allocated to each index */ #ifndef SQLITE_OMIT_TRIGGER int isView; /* True if attempting to insert into a view */ Trigger *pTrigger; /* List of triggers on pTab, if required */ @@ -92160,10 +99673,21 @@ db = pParse->db; memset(&dest, 0, sizeof(dest)); if( pParse->nErr || db->mallocFailed ){ goto insert_cleanup; } + + /* If the Select object is really just a simple VALUES() list with a + ** single row values (the common case) then keep that one row of values + ** and go ahead and discard the Select object + */ + if( pSelect && (pSelect->selFlags & SF_Values)!=0 && pSelect->pPrior==0 ){ + pList = pSelect->pEList; + pSelect->pEList = 0; + sqlite3SelectDelete(db, pSelect); + pSelect = 0; + } /* Locate the table into which we will be inserting new information. */ assert( pTabList->nSrc==1 ); zTab = pTabList->a[0].zName; @@ -92177,10 +99701,11 @@ pDb = &db->aDb[iDb]; zDb = pDb->zName; if( sqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, zDb) ){ goto insert_cleanup; } + withoutRowid = !HasRowid(pTab); /* Figure out if we have any triggers and if the table being ** inserted into is a view */ #ifndef SQLITE_OMIT_TRIGGER @@ -92196,20 +99721,17 @@ # define isView 0 #endif assert( (pTrigger && tmask) || (pTrigger==0 && tmask==0) ); /* If pTab is really a view, make sure it has been initialized. - ** ViewGetColumnNames() is a no-op if pTab is not a view (or virtual - ** module table). + ** ViewGetColumnNames() is a no-op if pTab is not a view. */ if( sqlite3ViewGetColumnNames(pParse, pTab) ){ goto insert_cleanup; } - /* Ensure that: - * (a) the table is not read-only, - * (b) that if it is a view then ON INSERT triggers exist + /* Cannot insert into a read-only table. */ if( sqlite3IsReadOnly(pParse, pTab, tmask) ){ goto insert_cleanup; } @@ -92239,69 +99761,126 @@ /* If this is an AUTOINCREMENT table, look up the sequence number in the ** sqlite_sequence table and store it in memory cell regAutoinc. */ regAutoinc = autoIncBegin(pParse, iDb, pTab); + + /* Allocate registers for holding the rowid of the new row, + ** the content of the new row, and the assembled row record. + */ + regRowid = regIns = pParse->nMem+1; + pParse->nMem += pTab->nCol + 1; + if( IsVirtual(pTab) ){ + regRowid++; + pParse->nMem++; + } + regData = regRowid+1; + + /* If the INSERT statement included an IDLIST term, then make sure + ** all elements of the IDLIST really are columns of the table and + ** remember the column indices. + ** + ** If the table has an INTEGER PRIMARY KEY column and that column + ** is named in the IDLIST, then record in the ipkColumn variable + ** the index into IDLIST of the primary key column. ipkColumn is + ** the index of the primary key as it appears in IDLIST, not as + ** is appears in the original table. (The index of the INTEGER + ** PRIMARY KEY in the original table is pTab->iPKey.) + */ + if( pColumn ){ + for(i=0; i<pColumn->nId; i++){ + pColumn->a[i].idx = -1; + } + for(i=0; i<pColumn->nId; i++){ + for(j=0; j<pTab->nCol; j++){ + if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){ + pColumn->a[i].idx = j; + if( i!=j ) bIdListInOrder = 0; + if( j==pTab->iPKey ){ + ipkColumn = i; assert( !withoutRowid ); + } + break; + } + } + if( j>=pTab->nCol ){ + if( sqlite3IsRowid(pColumn->a[i].zName) && !withoutRowid ){ + ipkColumn = i; + bIdListInOrder = 0; + }else{ + sqlite3ErrorMsg(pParse, "table %S has no column named %s", + pTabList, 0, pColumn->a[i].zName); + pParse->checkSchema = 1; + goto insert_cleanup; + } + } + } + } /* Figure out how many columns of data are supplied. If the data ** is coming from a SELECT statement, then generate a co-routine that ** produces a single row of the SELECT on each invocation. The ** co-routine is the common header to the 3rd and 4th templates. */ if( pSelect ){ - /* Data is coming from a SELECT. Generate a co-routine to run that - ** SELECT. */ - int rc = sqlite3CodeCoroutine(pParse, pSelect, &dest); - if( rc ) goto insert_cleanup; + /* Data is coming from a SELECT. Generate a co-routine to run the SELECT */ + int regYield; /* Register holding co-routine entry-point */ + int addrTop; /* Top of the co-routine */ + int rc; /* Result code */ - regEof = dest.iSDParm + 1; + regYield = ++pParse->nMem; + addrTop = sqlite3VdbeCurrentAddr(v) + 1; + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); + sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); + dest.iSdst = bIdListInOrder ? regData : 0; + dest.nSdst = pTab->nCol; + rc = sqlite3Select(pParse, pSelect, &dest); regFromSelect = dest.iSdst; + assert( pParse->nErr==0 || rc ); + if( rc || db->mallocFailed ) goto insert_cleanup; + sqlite3VdbeAddOp1(v, OP_EndCoroutine, regYield); + sqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */ assert( pSelect->pEList ); nColumn = pSelect->pEList->nExpr; - assert( dest.nSdst==nColumn ); /* Set useTempTable to TRUE if the result of the SELECT statement ** should be written into a temporary table (template 4). Set to - ** FALSE if each* row of the SELECT can be written directly into + ** FALSE if each output row of the SELECT can be written directly into ** the destination table (template 3). ** ** A temp table must be used if the table being updated is also one ** of the tables being read by the SELECT statement. Also use a ** temp table in the case of row triggers. */ - if( pTrigger || readsTable(pParse, addrSelect, iDb, pTab) ){ + if( pTrigger || readsTable(pParse, iDb, pTab) ){ useTempTable = 1; } if( useTempTable ){ /* Invoke the coroutine to extract information from the SELECT ** and add it to a transient table srcTab. The code generated ** here is from the 4th template: ** ** B: open temp table - ** L: yield X - ** if EOF goto M + ** L: yield X, goto M at EOF ** insert row from R..R+n into temp table ** goto L ** M: ... */ int regRec; /* Register to hold packed record */ int regTempRowid; /* Register to hold temp table ROWID */ - int addrTop; /* Label "L" */ - int addrIf; /* Address of jump to M */ + int addrL; /* Label "L" */ srcTab = pParse->nTab++; regRec = sqlite3GetTempReg(pParse); regTempRowid = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp2(v, OP_OpenEphemeral, srcTab, nColumn); - addrTop = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); - addrIf = sqlite3VdbeAddOp1(v, OP_If, regEof); + addrL = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_MakeRecord, regFromSelect, nColumn, regRec); sqlite3VdbeAddOp2(v, OP_NewRowid, srcTab, regTempRowid); sqlite3VdbeAddOp3(v, OP_Insert, srcTab, regRec, regTempRowid); - sqlite3VdbeAddOp2(v, OP_Goto, 0, addrTop); - sqlite3VdbeJumpHere(v, addrIf); + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrL); + sqlite3VdbeJumpHere(v, addrL); sqlite3ReleaseTempReg(pParse, regRec); sqlite3ReleaseTempReg(pParse, regTempRowid); } }else{ /* This is the case if the data for the INSERT is coming from a VALUES @@ -92317,10 +99896,18 @@ if( sqlite3ResolveExprNames(&sNC, pList->a[i].pExpr) ){ goto insert_cleanup; } } } + + /* If there is no IDLIST term but the table has an integer primary + ** key, the set the ipkColumn variable to the integer primary key + ** column index in the original table definition. + */ + if( pColumn==0 && nColumn>0 ){ + ipkColumn = pTab->iPKey; + } /* Make sure the number of columns in the source data matches the number ** of columns to be inserted into the table. */ if( IsVirtual(pTab) ){ @@ -92336,56 +99923,10 @@ } if( pColumn!=0 && nColumn!=pColumn->nId ){ sqlite3ErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId); goto insert_cleanup; } - - /* If the INSERT statement included an IDLIST term, then make sure - ** all elements of the IDLIST really are columns of the table and - ** remember the column indices. - ** - ** If the table has an INTEGER PRIMARY KEY column and that column - ** is named in the IDLIST, then record in the keyColumn variable - ** the index into IDLIST of the primary key column. keyColumn is - ** the index of the primary key as it appears in IDLIST, not as - ** is appears in the original table. (The index of the primary - ** key in the original table is pTab->iPKey.) - */ - if( pColumn ){ - for(i=0; i<pColumn->nId; i++){ - pColumn->a[i].idx = -1; - } - for(i=0; i<pColumn->nId; i++){ - for(j=0; j<pTab->nCol; j++){ - if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){ - pColumn->a[i].idx = j; - if( j==pTab->iPKey ){ - keyColumn = i; - } - break; - } - } - if( j>=pTab->nCol ){ - if( sqlite3IsRowid(pColumn->a[i].zName) ){ - keyColumn = i; - }else{ - sqlite3ErrorMsg(pParse, "table %S has no column named %s", - pTabList, 0, pColumn->a[i].zName); - pParse->checkSchema = 1; - goto insert_cleanup; - } - } - } - } - - /* If there is no IDLIST term but the table has an integer primary - ** key, the set the keyColumn variable to the primary key column index - ** in the original table definition. - */ - if( pColumn==0 && nColumn>0 ){ - keyColumn = pTab->iPKey; - } /* Initialize the count of rows to be inserted */ if( db->flags & SQLITE_CountRows ){ regRowCount = ++pParse->nMem; @@ -92393,13 +99934,12 @@ } /* If this is not a view, open the table and and all indices */ if( !isView ){ int nIdx; - - baseCur = pParse->nTab; - nIdx = sqlite3OpenTableAndIndices(pParse, pTab, baseCur, OP_OpenWrite); + nIdx = sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, -1, 0, + &iDataCur, &iIdxCur); aRegIdx = sqlite3DbMallocRaw(db, sizeof(int)*(nIdx+1)); if( aRegIdx==0 ){ goto insert_cleanup; } for(i=0; i<nIdx; i++){ @@ -92410,42 +99950,30 @@ /* This is the top of the main insertion loop */ if( useTempTable ){ /* This block codes the top of loop only. The complete loop is the ** following pseudocode (template 4): ** - ** rewind temp table + ** rewind temp table, if empty goto D ** C: loop over rows of intermediate table ** transfer values form intermediate table into <table> ** end loop ** D: ... */ - addrInsTop = sqlite3VdbeAddOp1(v, OP_Rewind, srcTab); + addrInsTop = sqlite3VdbeAddOp1(v, OP_Rewind, srcTab); VdbeCoverage(v); addrCont = sqlite3VdbeCurrentAddr(v); }else if( pSelect ){ /* This block codes the top of loop only. The complete loop is the ** following pseudocode (template 3): ** - ** C: yield X - ** if EOF goto D + ** C: yield X, at EOF goto D ** insert the select result into <table> from R..R+n ** goto C ** D: ... */ - addrCont = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); - addrInsTop = sqlite3VdbeAddOp1(v, OP_If, regEof); - } - - /* Allocate registers for holding the rowid of the new row, - ** the content of the new row, and the assemblied row record. - */ - regRowid = regIns = pParse->nMem+1; - pParse->nMem += pTab->nCol + 1; - if( IsVirtual(pTab) ){ - regRowid++; - pParse->nMem++; - } - regData = regRowid+1; + addrInsTop = addrCont = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); + VdbeCoverage(v); + } /* Run the BEFORE and INSTEAD OF triggers, if there are any */ endOfLoop = sqlite3VdbeMakeLabel(v); if( tmask & TRIGGER_BEFORE ){ @@ -92455,24 +99983,25 @@ ** PRIMARY KEY into which a NULL is being inserted, that NULL will be ** translated into a unique ID for the row. But on a BEFORE trigger, ** we do not know what the unique ID will be (because the insert has ** not happened yet) so we substitute a rowid of -1 */ - if( keyColumn<0 ){ + if( ipkColumn<0 ){ sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols); }else{ int j1; + assert( !withoutRowid ); if( useTempTable ){ - sqlite3VdbeAddOp3(v, OP_Column, srcTab, keyColumn, regCols); + sqlite3VdbeAddOp3(v, OP_Column, srcTab, ipkColumn, regCols); }else{ assert( pSelect==0 ); /* Otherwise useTempTable is true */ - sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr, regCols); + sqlite3ExprCode(pParse, pList->a[ipkColumn].pExpr, regCols); } - j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regCols); + j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regCols); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols); sqlite3VdbeJumpHere(v, j1); - sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); + sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); VdbeCoverage(v); } /* Cannot have triggers on a virtual table. If it were possible, ** this block would have to account for hidden column. */ @@ -92502,44 +100031,41 @@ ** do not attempt any conversions before assembling the record. ** If this is a real table, attempt conversions as required by the ** table column affinities. */ if( !isView ){ - sqlite3VdbeAddOp2(v, OP_Affinity, regCols+1, pTab->nCol); - sqlite3TableAffinityStr(v, pTab); + sqlite3TableAffinity(v, pTab, regCols+1); } /* Fire BEFORE or INSTEAD OF triggers */ sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE, pTab, regCols-pTab->nCol-1, onError, endOfLoop); sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol+1); } - /* Push the record number for the new entry onto the stack. The - ** record number is a randomly generate integer created by NewRowid - ** except when the table has an INTEGER PRIMARY KEY column, in which - ** case the record number is the same as that column. + /* Compute the content of the next row to insert into a range of + ** registers beginning at regIns. */ if( !isView ){ if( IsVirtual(pTab) ){ /* The row that the VUpdate opcode will delete: none */ sqlite3VdbeAddOp2(v, OP_Null, 0, regIns); } - if( keyColumn>=0 ){ + if( ipkColumn>=0 ){ if( useTempTable ){ - sqlite3VdbeAddOp3(v, OP_Column, srcTab, keyColumn, regRowid); + sqlite3VdbeAddOp3(v, OP_Column, srcTab, ipkColumn, regRowid); }else if( pSelect ){ - sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+keyColumn, regRowid); + sqlite3VdbeAddOp2(v, OP_Copy, regFromSelect+ipkColumn, regRowid); }else{ VdbeOp *pOp; - sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr, regRowid); + sqlite3ExprCode(pParse, pList->a[ipkColumn].pExpr, regRowid); pOp = sqlite3VdbeGetOp(v, -1); if( ALWAYS(pOp) && pOp->opcode==OP_Null && !IsVirtual(pTab) ){ appendFlag = 1; pOp->opcode = OP_NewRowid; - pOp->p1 = baseCur; + pOp->p1 = iDataCur; pOp->p2 = regRowid; pOp->p3 = regAutoinc; } } /* If the PRIMARY KEY expression is NULL, then use OP_NewRowid @@ -92546,39 +100072,40 @@ ** to generate a unique primary key value. */ if( !appendFlag ){ int j1; if( !IsVirtual(pTab) ){ - j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regRowid); - sqlite3VdbeAddOp3(v, OP_NewRowid, baseCur, regRowid, regAutoinc); + j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regRowid); VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc); sqlite3VdbeJumpHere(v, j1); }else{ j1 = sqlite3VdbeCurrentAddr(v); - sqlite3VdbeAddOp2(v, OP_IsNull, regRowid, j1+2); + sqlite3VdbeAddOp2(v, OP_IsNull, regRowid, j1+2); VdbeCoverage(v); } - sqlite3VdbeAddOp1(v, OP_MustBeInt, regRowid); + sqlite3VdbeAddOp1(v, OP_MustBeInt, regRowid); VdbeCoverage(v); } - }else if( IsVirtual(pTab) ){ + }else if( IsVirtual(pTab) || withoutRowid ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regRowid); }else{ - sqlite3VdbeAddOp3(v, OP_NewRowid, baseCur, regRowid, regAutoinc); + sqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc); appendFlag = 1; } autoIncStep(pParse, regAutoinc, regRowid); - /* Push onto the stack, data for all columns of the new entry, beginning + /* Compute data for all columns of the new entry, beginning ** with the first column. */ nHidden = 0; for(i=0; i<pTab->nCol; i++){ int iRegStore = regRowid+1+i; if( i==pTab->iPKey ){ /* The value of the INTEGER PRIMARY KEY column is always a NULL. - ** Whenever this column is read, the record number will be substituted - ** in its place. So will fill this column with a NULL to avoid - ** taking up data space with information that will never be used. */ - sqlite3VdbeAddOp2(v, OP_Null, 0, iRegStore); + ** Whenever this column is read, the rowid will be substituted + ** in its place. Hence, fill this column with a NULL to avoid + ** taking up data space with information that will never be used. + ** As there may be shallow copies of this value, make it a soft-NULL */ + sqlite3VdbeAddOp1(v, OP_SoftNull, iRegStore); continue; } if( pColumn==0 ){ if( IsHiddenColumn(&pTab->aCol[i]) ){ assert( IsVirtual(pTab) ); @@ -92591,15 +100118,17 @@ for(j=0; j<pColumn->nId; j++){ if( pColumn->a[j].idx==i ) break; } } if( j<0 || nColumn==0 || (pColumn && j>=pColumn->nId) ){ - sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, iRegStore); + sqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore); }else if( useTempTable ){ sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, iRegStore); }else if( pSelect ){ - sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+j, iRegStore); + if( regFromSelect!=regData ){ + sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+j, iRegStore); + } }else{ sqlite3ExprCode(pParse, pList->a[j].pExpr, iRegStore); } } @@ -92615,17 +100144,16 @@ sqlite3MayAbort(pParse); }else #endif { int isReplace; /* Set to true if constraints may cause a replace */ - sqlite3GenerateConstraintChecks(pParse, pTab, baseCur, regIns, aRegIdx, - keyColumn>=0, 0, onError, endOfLoop, &isReplace + sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, + regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace ); sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); - sqlite3CompleteInsertion( - pParse, pTab, baseCur, regIns, aRegIdx, 0, appendFlag, isReplace==0 - ); + sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur, + regIns, aRegIdx, 0, appendFlag, isReplace==0); } } /* Update the count of rows that are inserted */ @@ -92642,23 +100170,23 @@ /* The bottom of the main insertion loop, if the data source ** is a SELECT statement. */ sqlite3VdbeResolveLabel(v, endOfLoop); if( useTempTable ){ - sqlite3VdbeAddOp2(v, OP_Next, srcTab, addrCont); + sqlite3VdbeAddOp2(v, OP_Next, srcTab, addrCont); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addrInsTop); sqlite3VdbeAddOp1(v, OP_Close, srcTab); }else if( pSelect ){ sqlite3VdbeAddOp2(v, OP_Goto, 0, addrCont); sqlite3VdbeJumpHere(v, addrInsTop); } if( !IsVirtual(pTab) && !isView ){ /* Close all tables opened */ - sqlite3VdbeAddOp1(v, OP_Close, baseCur); - for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ - sqlite3VdbeAddOp1(v, OP_Close, idx+baseCur); + if( iDataCur<iIdxCur ) sqlite3VdbeAddOp1(v, OP_Close, iDataCur); + for(idx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ + sqlite3VdbeAddOp1(v, OP_Close, idx+iIdxCur); } } insert_end: /* Update the sqlite_sequence table by storing the content of the @@ -92687,11 +100215,11 @@ sqlite3IdListDelete(db, pColumn); sqlite3DbFree(db, aRegIdx); } /* Make sure "isView" and other macros defined above are undefined. Otherwise -** thely may interfere with compilation of other functions in this file +** they may interfere with compilation of other functions in this file ** (or in another file, if this file becomes part of the amalgamation). */ #ifdef isView #undef isView #endif #ifdef pTrigger @@ -92699,65 +100227,78 @@ #endif #ifdef tmask #undef tmask #endif - /* -** Generate code to do constraint checks prior to an INSERT or an UPDATE. -** -** The input is a range of consecutive registers as follows: -** -** 1. The rowid of the row after the update. -** -** 2. The data in the first column of the entry after the update. -** -** i. Data from middle columns... -** -** N. The data in the last column of the entry after the update. -** -** The regRowid parameter is the index of the register containing (1). -** -** If isUpdate is true and rowidChng is non-zero, then rowidChng contains -** the address of a register containing the rowid before the update takes -** place. isUpdate is true for UPDATEs and false for INSERTs. If isUpdate -** is false, indicating an INSERT statement, then a non-zero rowidChng -** indicates that the rowid was explicitly specified as part of the -** INSERT statement. If rowidChng is false, it means that the rowid is -** computed automatically in an insert or that the rowid value is not -** modified by an update. -** -** The code generated by this routine store new index entries into +** Generate code to do constraint checks prior to an INSERT or an UPDATE +** on table pTab. +** +** The regNewData parameter is the first register in a range that contains +** the data to be inserted or the data after the update. There will be +** pTab->nCol+1 registers in this range. The first register (the one +** that regNewData points to) will contain the new rowid, or NULL in the +** case of a WITHOUT ROWID table. The second register in the range will +** contain the content of the first table column. The third register will +** contain the content of the second table column. And so forth. +** +** The regOldData parameter is similar to regNewData except that it contains +** the data prior to an UPDATE rather than afterwards. regOldData is zero +** for an INSERT. This routine can distinguish between UPDATE and INSERT by +** checking regOldData for zero. +** +** For an UPDATE, the pkChng boolean is true if the true primary key (the +** rowid for a normal table or the PRIMARY KEY for a WITHOUT ROWID table) +** might be modified by the UPDATE. If pkChng is false, then the key of +** the iDataCur content table is guaranteed to be unchanged by the UPDATE. +** +** For an INSERT, the pkChng boolean indicates whether or not the rowid +** was explicitly specified as part of the INSERT statement. If pkChng +** is zero, it means that the either rowid is computed automatically or +** that the table is a WITHOUT ROWID table and has no rowid. On an INSERT, +** pkChng will only be true if the INSERT statement provides an integer +** value for either the rowid column or its INTEGER PRIMARY KEY alias. +** +** The code generated by this routine will store new index entries into ** registers identified by aRegIdx[]. No index entry is created for ** indices where aRegIdx[i]==0. The order of indices in aRegIdx[] is ** the same as the order of indices on the linked list of indices -** attached to the table. +** at pTab->pIndex. +** +** The caller must have already opened writeable cursors on the main +** table and all applicable indices (that is to say, all indices for which +** aRegIdx[] is not zero). iDataCur is the cursor for the main table when +** inserting or updating a rowid table, or the cursor for the PRIMARY KEY +** index when operating on a WITHOUT ROWID table. iIdxCur is the cursor +** for the first index in the pTab->pIndex list. Cursors for other indices +** are at iIdxCur+N for the N-th element of the pTab->pIndex list. ** ** This routine also generates code to check constraints. NOT NULL, ** CHECK, and UNIQUE constraints are all checked. If a constraint fails, ** then the appropriate action is performed. There are five possible ** actions: ROLLBACK, ABORT, FAIL, REPLACE, and IGNORE. ** ** Constraint type Action What Happens ** --------------- ---------- ---------------------------------------- ** any ROLLBACK The current transaction is rolled back and -** sqlite3_exec() returns immediately with a +** sqlite3_step() returns immediately with a ** return code of SQLITE_CONSTRAINT. ** ** any ABORT Back out changes from the current command ** only (do not do a complete rollback) then -** cause sqlite3_exec() to return immediately +** cause sqlite3_step() to return immediately ** with SQLITE_CONSTRAINT. ** -** any FAIL Sqlite3_exec() returns immediately with a +** any FAIL Sqlite3_step() returns immediately with a ** return code of SQLITE_CONSTRAINT. The ** transaction is not rolled back and any -** prior changes are retained. +** changes to prior rows are retained. ** -** any IGNORE The record number and data is popped from -** the stack and there is an immediate jump -** to label ignoreDest. +** any IGNORE The attempt in insert or update the current +** row is skipped, without throwing an error. +** Processing continues with the next row. +** (There is an immediate jump to ignoreDest.) ** ** NOT NULL REPLACE The NULL value is replace by the default ** value for that column. If the default value ** is NULL, the action is the same as ABORT. ** @@ -92768,48 +100309,63 @@ ** ** Which action to take is determined by the overrideError parameter. ** Or if overrideError==OE_Default, then the pParse->onError parameter ** is used. Or if pParse->onError==OE_Default then the onError value ** for the constraint is used. -** -** The calling routine must open a read/write cursor for pTab with -** cursor number "baseCur". All indices of pTab must also have open -** read/write cursors with cursor number baseCur+i for the i-th cursor. -** Except, if there is no possibility of a REPLACE action then -** cursors do not need to be open for indices where aRegIdx[i]==0. */ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( - Parse *pParse, /* The parser context */ - Table *pTab, /* the table into which we are inserting */ - int baseCur, /* Index of a read/write cursor pointing at pTab */ - int regRowid, /* Index of the range of input registers */ - int *aRegIdx, /* Register used by each index. 0 for unused indices */ - int rowidChng, /* True if the rowid might collide with existing entry */ - int isUpdate, /* True for UPDATE, False for INSERT */ - int overrideError, /* Override onError to this if not OE_Default */ - int ignoreDest, /* Jump to this label on an OE_Ignore resolution */ - int *pbMayReplace /* OUT: Set to true if constraint may cause a replace */ + Parse *pParse, /* The parser context */ + Table *pTab, /* The table being inserted or updated */ + int *aRegIdx, /* Use register aRegIdx[i] for index i. 0 for unused */ + int iDataCur, /* Canonical data cursor (main table or PK index) */ + int iIdxCur, /* First index cursor */ + int regNewData, /* First register in a range holding values to insert */ + int regOldData, /* Previous content. 0 for INSERTs */ + u8 pkChng, /* Non-zero if the rowid or PRIMARY KEY changed */ + u8 overrideError, /* Override onError to this if not OE_Default */ + int ignoreDest, /* Jump to this label on an OE_Ignore resolution */ + int *pbMayReplace /* OUT: Set to true if constraint may cause a replace */ ){ - int i; /* loop counter */ - Vdbe *v; /* VDBE under constrution */ - int nCol; /* Number of columns */ - int onError; /* Conflict resolution strategy */ - int j1; /* Addresss of jump instruction */ - int j2 = 0, j3; /* Addresses of jump instructions */ - int regData; /* Register containing first data column */ - int iCur; /* Table cursor number */ + Vdbe *v; /* VDBE under constrution */ Index *pIdx; /* Pointer to one of the indices */ + Index *pPk = 0; /* The PRIMARY KEY index */ sqlite3 *db; /* Database connection */ + int i; /* loop counter */ + int ix; /* Index loop counter */ + int nCol; /* Number of columns */ + int onError; /* Conflict resolution strategy */ + int j1; /* Address of jump instruction */ int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */ - int regOldRowid = (rowidChng && isUpdate) ? rowidChng : regRowid; + int nPkField; /* Number of fields in PRIMARY KEY. 1 for ROWID tables */ + int ipkTop = 0; /* Top of the rowid change constraint check */ + int ipkBottom = 0; /* Bottom of the rowid change constraint check */ + u8 isUpdate; /* True if this is an UPDATE operation */ + u8 bAffinityDone = 0; /* True if the OP_Affinity operation has been run */ + int regRowid = -1; /* Register holding ROWID value */ + isUpdate = regOldData!=0; db = pParse->db; v = sqlite3GetVdbe(pParse); assert( v!=0 ); assert( pTab->pSelect==0 ); /* This table is not a VIEW */ nCol = pTab->nCol; - regData = regRowid + 1; + + /* pPk is the PRIMARY KEY index for WITHOUT ROWID tables and NULL for + ** normal rowid tables. nPkField is the number of key fields in the + ** pPk index or 1 for a rowid table. In other words, nPkField is the + ** number of fields in the true primary key of the table. */ + if( HasRowid(pTab) ){ + pPk = 0; + nPkField = 1; + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + nPkField = pPk->nKeyCol; + } + + /* Record that this module has started */ + VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)", + iDataCur, iIdxCur, regNewData, regOldData, pkChng)); /* Test all NOT NULL constraints. */ for(i=0; i<nCol; i++){ if( i==pTab->iPKey ){ @@ -92828,28 +100384,30 @@ assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail || onError==OE_Ignore || onError==OE_Replace ); switch( onError ){ case OE_Abort: sqlite3MayAbort(pParse); + /* Fall through */ case OE_Rollback: case OE_Fail: { - char *zMsg; - sqlite3VdbeAddOp3(v, OP_HaltIfNull, - SQLITE_CONSTRAINT_NOTNULL, onError, regData+i); - zMsg = sqlite3MPrintf(db, "%s.%s may not be NULL", - pTab->zName, pTab->aCol[i].zName); - sqlite3VdbeChangeP4(v, -1, zMsg, P4_DYNAMIC); + char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, + pTab->aCol[i].zName); + sqlite3VdbeAddOp4(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, + regNewData+1+i, zMsg, P4_DYNAMIC); + sqlite3VdbeChangeP5(v, P5_ConstraintNotNull); + VdbeCoverage(v); break; } case OE_Ignore: { - sqlite3VdbeAddOp2(v, OP_IsNull, regData+i, ignoreDest); + sqlite3VdbeAddOp2(v, OP_IsNull, regNewData+1+i, ignoreDest); + VdbeCoverage(v); break; } default: { assert( onError==OE_Replace ); - j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regData+i); - sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regData+i); + j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regNewData+1+i); VdbeCoverage(v); + sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regNewData+1+i); sqlite3VdbeJumpHere(v, j1); break; } } } @@ -92857,59 +100415,82 @@ /* Test all CHECK constraints */ #ifndef SQLITE_OMIT_CHECK if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ ExprList *pCheck = pTab->pCheck; - pParse->ckBase = regData; + pParse->ckBase = regNewData+1; onError = overrideError!=OE_Default ? overrideError : OE_Abort; for(i=0; i<pCheck->nExpr; i++){ int allOk = sqlite3VdbeMakeLabel(v); sqlite3ExprIfTrue(pParse, pCheck->a[i].pExpr, allOk, SQLITE_JUMPIFNULL); if( onError==OE_Ignore ){ sqlite3VdbeAddOp2(v, OP_Goto, 0, ignoreDest); }else{ - char *zConsName = pCheck->a[i].zName; + char *zName = pCheck->a[i].zName; + if( zName==0 ) zName = pTab->zName; if( onError==OE_Replace ) onError = OE_Abort; /* IMP: R-15569-63625 */ - if( zConsName ){ - zConsName = sqlite3MPrintf(db, "constraint %s failed", zConsName); - }else{ - zConsName = 0; - } sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_CHECK, - onError, zConsName, P4_DYNAMIC); + onError, zName, P4_TRANSIENT, + P5_ConstraintCheck); } sqlite3VdbeResolveLabel(v, allOk); } } #endif /* !defined(SQLITE_OMIT_CHECK) */ - /* If we have an INTEGER PRIMARY KEY, make sure the primary key - ** of the new record does not previously exist. Except, if this - ** is an UPDATE and the primary key is not changing, that is OK. + /* If rowid is changing, make sure the new rowid does not previously + ** exist in the table. */ - if( rowidChng ){ + if( pkChng && pPk==0 ){ + int addrRowidOk = sqlite3VdbeMakeLabel(v); + + /* Figure out what action to take in case of a rowid collision */ onError = pTab->keyConf; if( overrideError!=OE_Default ){ onError = overrideError; }else if( onError==OE_Default ){ onError = OE_Abort; } - + if( isUpdate ){ - j2 = sqlite3VdbeAddOp3(v, OP_Eq, regRowid, 0, rowidChng); + /* pkChng!=0 does not mean that the rowid has change, only that + ** it might have changed. Skip the conflict logic below if the rowid + ** is unchanged. */ + sqlite3VdbeAddOp3(v, OP_Eq, regNewData, addrRowidOk, regOldData); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); + VdbeCoverage(v); } - j3 = sqlite3VdbeAddOp3(v, OP_NotExists, baseCur, 0, regRowid); + + /* If the response to a rowid conflict is REPLACE but the response + ** to some other UNIQUE constraint is FAIL or IGNORE, then we need + ** to defer the running of the rowid conflict checking until after + ** the UNIQUE constraints have run. + */ + if( onError==OE_Replace && overrideError!=OE_Replace ){ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->onError==OE_Ignore || pIdx->onError==OE_Fail ){ + ipkTop = sqlite3VdbeAddOp0(v, OP_Goto); + break; + } + } + } + + /* Check to see if the new rowid already exists in the table. Skip + ** the following conflict logic if it does not. */ + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, addrRowidOk, regNewData); + VdbeCoverage(v); + + /* Generate code that deals with a rowid collision */ switch( onError ){ default: { onError = OE_Abort; /* Fall thru into the next case */ } case OE_Rollback: case OE_Abort: case OE_Fail: { - sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_PRIMARYKEY, - onError, "PRIMARY KEY must be unique", P4_STATIC); + sqlite3RowidConstraint(pParse, onError, pTab); break; } case OE_Replace: { /* If there are DELETE triggers on this table and the ** recursive-triggers flag is set, call GenerateRowDelete() to @@ -92937,123 +100518,174 @@ if( db->flags&SQLITE_RecTriggers ){ pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); } if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){ sqlite3MultiWrite(pParse); - sqlite3GenerateRowDelete( - pParse, pTab, baseCur, regRowid, 0, pTrigger, OE_Replace - ); + sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + regNewData, 1, 0, OE_Replace, 1); }else if( pTab->pIndex ){ sqlite3MultiWrite(pParse); - sqlite3GenerateRowIndexDelete(pParse, pTab, baseCur, 0); + sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, 0); } seenReplace = 1; break; } case OE_Ignore: { - assert( seenReplace==0 ); + /*assert( seenReplace==0 );*/ sqlite3VdbeAddOp2(v, OP_Goto, 0, ignoreDest); break; } } - sqlite3VdbeJumpHere(v, j3); - if( isUpdate ){ - sqlite3VdbeJumpHere(v, j2); + sqlite3VdbeResolveLabel(v, addrRowidOk); + if( ipkTop ){ + ipkBottom = sqlite3VdbeAddOp0(v, OP_Goto); + sqlite3VdbeJumpHere(v, ipkTop); } } /* Test all UNIQUE constraints by creating entries for each UNIQUE ** index and making sure that duplicate entries do not already exist. - ** Add the new records to the indices as we go. + ** Compute the revised record entries for indices as we go. + ** + ** This loop also handles the case of the PRIMARY KEY index for a + ** WITHOUT ROWID table. */ - for(iCur=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, iCur++){ - int regIdx; - int regR; - int addrSkipRow = 0; + for(ix=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, ix++){ + int regIdx; /* Range of registers hold conent for pIdx */ + int regR; /* Range of registers holding conflicting PK */ + int iThisCur; /* Cursor for this UNIQUE index */ + int addrUniqueOk; /* Jump here if the UNIQUE constraint is satisfied */ - if( aRegIdx[iCur]==0 ) continue; /* Skip unused indices */ + if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */ + if( bAffinityDone==0 ){ + sqlite3TableAffinity(v, pTab, regNewData+1); + bAffinityDone = 1; + } + iThisCur = iIdxCur+ix; + addrUniqueOk = sqlite3VdbeMakeLabel(v); + /* Skip partial indices for which the WHERE clause is not true */ if( pIdx->pPartIdxWhere ){ - sqlite3VdbeAddOp2(v, OP_Null, 0, aRegIdx[iCur]); - addrSkipRow = sqlite3VdbeMakeLabel(v); - pParse->ckBase = regData; - sqlite3ExprIfFalse(pParse, pIdx->pPartIdxWhere, addrSkipRow, + sqlite3VdbeAddOp2(v, OP_Null, 0, aRegIdx[ix]); + pParse->ckBase = regNewData+1; + sqlite3ExprIfFalse(pParse, pIdx->pPartIdxWhere, addrUniqueOk, SQLITE_JUMPIFNULL); pParse->ckBase = 0; } - /* Create a key for accessing the index entry */ - regIdx = sqlite3GetTempRange(pParse, pIdx->nColumn+1); + /* Create a record for this index entry as it should appear after + ** the insert or update. Store that record in the aRegIdx[ix] register + */ + regIdx = sqlite3GetTempRange(pParse, pIdx->nColumn); for(i=0; i<pIdx->nColumn; i++){ - int idx = pIdx->aiColumn[i]; - if( idx==pTab->iPKey ){ - sqlite3VdbeAddOp2(v, OP_SCopy, regRowid, regIdx+i); + int iField = pIdx->aiColumn[i]; + int x; + if( iField<0 || iField==pTab->iPKey ){ + if( regRowid==regIdx+i ) continue; /* ROWID already in regIdx+i */ + x = regNewData; + regRowid = pIdx->pPartIdxWhere ? -1 : regIdx+i; }else{ - sqlite3VdbeAddOp2(v, OP_SCopy, regData+idx, regIdx+i); - } - } - sqlite3VdbeAddOp2(v, OP_SCopy, regRowid, regIdx+i); - sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn+1, aRegIdx[iCur]); - sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v, pIdx), P4_TRANSIENT); - sqlite3ExprCacheAffinityChange(pParse, regIdx, pIdx->nColumn+1); - - /* Find out what action to take in case there is an indexing conflict */ + x = iField + regNewData + 1; + } + sqlite3VdbeAddOp2(v, OP_SCopy, x, regIdx+i); + VdbeComment((v, "%s", iField<0 ? "rowid" : pTab->aCol[iField].zName)); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn, aRegIdx[ix]); + VdbeComment((v, "for %s", pIdx->zName)); + sqlite3ExprCacheAffinityChange(pParse, regIdx, pIdx->nColumn); + + /* In an UPDATE operation, if this index is the PRIMARY KEY index + ** of a WITHOUT ROWID table and there has been no change the + ** primary key, then no collision is possible. The collision detection + ** logic below can all be skipped. */ + if( isUpdate && pPk==pIdx && pkChng==0 ){ + sqlite3VdbeResolveLabel(v, addrUniqueOk); + continue; + } + + /* Find out what action to take in case there is a uniqueness conflict */ onError = pIdx->onError; if( onError==OE_None ){ - sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn+1); - sqlite3VdbeResolveLabel(v, addrSkipRow); + sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn); + sqlite3VdbeResolveLabel(v, addrUniqueOk); continue; /* pIdx is not a UNIQUE index */ } if( overrideError!=OE_Default ){ onError = overrideError; }else if( onError==OE_Default ){ onError = OE_Abort; } - if( seenReplace ){ - if( onError==OE_Ignore ) onError = OE_Replace; - else if( onError==OE_Fail ) onError = OE_Abort; - } /* Check to see if the new index entry will be unique */ - regR = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp2(v, OP_SCopy, regOldRowid, regR); - j3 = sqlite3VdbeAddOp4(v, OP_IsUnique, baseCur+iCur+1, 0, - regR, SQLITE_INT_TO_PTR(regIdx), - P4_INT32); - sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn+1); + sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur, addrUniqueOk, + regIdx, pIdx->nKeyCol); VdbeCoverage(v); + + /* Generate code to handle collisions */ + regR = (pIdx==pPk) ? regIdx : sqlite3GetTempRange(pParse, nPkField); + if( isUpdate || onError==OE_Replace ){ + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_IdxRowid, iThisCur, regR); + /* Conflict only if the rowid of the existing index entry + ** is different from old-rowid */ + if( isUpdate ){ + sqlite3VdbeAddOp3(v, OP_Eq, regR, addrUniqueOk, regOldData); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); + VdbeCoverage(v); + } + }else{ + int x; + /* Extract the PRIMARY KEY from the end of the index entry and + ** store it in registers regR..regR+nPk-1 */ + if( pIdx!=pPk ){ + for(i=0; i<pPk->nKeyCol; i++){ + x = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[i]); + sqlite3VdbeAddOp3(v, OP_Column, iThisCur, x, regR+i); + VdbeComment((v, "%s.%s", pTab->zName, + pTab->aCol[pPk->aiColumn[i]].zName)); + } + } + if( isUpdate ){ + /* If currently processing the PRIMARY KEY of a WITHOUT ROWID + ** table, only conflict if the new PRIMARY KEY values are actually + ** different from the old. + ** + ** For a UNIQUE index, only conflict if the PRIMARY KEY values + ** of the matched index row are different from the original PRIMARY + ** KEY values of this row before the update. */ + int addrJump = sqlite3VdbeCurrentAddr(v)+pPk->nKeyCol; + int op = OP_Ne; + int regCmp = (IsPrimaryKeyIndex(pIdx) ? regIdx : regR); + + for(i=0; i<pPk->nKeyCol; i++){ + char *p4 = (char*)sqlite3LocateCollSeq(pParse, pPk->azColl[i]); + x = pPk->aiColumn[i]; + if( i==(pPk->nKeyCol-1) ){ + addrJump = addrUniqueOk; + op = OP_Eq; + } + sqlite3VdbeAddOp4(v, op, + regOldData+1+x, addrJump, regCmp+i, p4, P4_COLLSEQ + ); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); + VdbeCoverageIf(v, op==OP_Eq); + VdbeCoverageIf(v, op==OP_Ne); + } + } + } + } /* Generate code that executes if the new index entry is not unique */ assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail || onError==OE_Ignore || onError==OE_Replace ); switch( onError ){ case OE_Rollback: case OE_Abort: case OE_Fail: { - int j; - StrAccum errMsg; - const char *zSep; - char *zErr; - - sqlite3StrAccumInit(&errMsg, 0, 0, 200); - errMsg.db = db; - zSep = pIdx->nColumn>1 ? "columns " : "column "; - for(j=0; j<pIdx->nColumn; j++){ - char *zCol = pTab->aCol[pIdx->aiColumn[j]].zName; - sqlite3StrAccumAppend(&errMsg, zSep, -1); - zSep = ", "; - sqlite3StrAccumAppend(&errMsg, zCol, -1); - } - sqlite3StrAccumAppend(&errMsg, - pIdx->nColumn>1 ? " are not unique" : " is not unique", -1); - zErr = sqlite3StrAccumFinish(&errMsg); - sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_UNIQUE, - onError, zErr, 0); - sqlite3DbFree(errMsg.db, zErr); + sqlite3UniqueConstraint(pParse, onError, pIdx); break; } case OE_Ignore: { - assert( seenReplace==0 ); sqlite3VdbeAddOp2(v, OP_Goto, 0, ignoreDest); break; } default: { Trigger *pTrigger = 0; @@ -93060,70 +100692,81 @@ assert( onError==OE_Replace ); sqlite3MultiWrite(pParse); if( db->flags&SQLITE_RecTriggers ){ pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); } - sqlite3GenerateRowDelete( - pParse, pTab, baseCur, regR, 0, pTrigger, OE_Replace - ); + sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + regR, nPkField, 0, OE_Replace, pIdx==pPk); seenReplace = 1; break; } } - sqlite3VdbeJumpHere(v, j3); - sqlite3VdbeResolveLabel(v, addrSkipRow); - sqlite3ReleaseTempReg(pParse, regR); + sqlite3VdbeResolveLabel(v, addrUniqueOk); + sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn); + if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField); + } + if( ipkTop ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, ipkTop+1); + sqlite3VdbeJumpHere(v, ipkBottom); } - if( pbMayReplace ){ - *pbMayReplace = seenReplace; - } + *pbMayReplace = seenReplace; + VdbeModuleComment((v, "END: GenCnstCks(%d)", seenReplace)); } /* ** This routine generates code to finish the INSERT or UPDATE operation ** that was started by a prior call to sqlite3GenerateConstraintChecks. -** A consecutive range of registers starting at regRowid contains the +** A consecutive range of registers starting at regNewData contains the ** rowid and the content to be inserted. ** ** The arguments to this routine should be the same as the first six ** arguments to sqlite3GenerateConstraintChecks. */ SQLITE_PRIVATE void sqlite3CompleteInsertion( Parse *pParse, /* The parser context */ Table *pTab, /* the table into which we are inserting */ - int baseCur, /* Index of a read/write cursor pointing at pTab */ - int regRowid, /* Range of content */ + int iDataCur, /* Cursor of the canonical data source */ + int iIdxCur, /* First index cursor */ + int regNewData, /* Range of content */ int *aRegIdx, /* Register used by each index. 0 for unused indices */ int isUpdate, /* True for UPDATE, False for INSERT */ int appendBias, /* True if this is likely to be an append */ int useSeekResult /* True to set the USESEEKRESULT flag on OP_[Idx]Insert */ ){ - int i; - Vdbe *v; - Index *pIdx; - u8 pik_flags; - int regData; - int regRec; + Vdbe *v; /* Prepared statements under construction */ + Index *pIdx; /* An index being inserted or updated */ + u8 pik_flags; /* flag values passed to the btree insert */ + int regData; /* Content registers (after the rowid) */ + int regRec; /* Register holding assembled record for the table */ + int i; /* Loop counter */ + u8 bAffinityDone = 0; /* True if OP_Affinity has been run already */ v = sqlite3GetVdbe(pParse); assert( v!=0 ); assert( pTab->pSelect==0 ); /* This table is not a VIEW */ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ if( aRegIdx[i]==0 ) continue; + bAffinityDone = 1; if( pIdx->pPartIdxWhere ){ sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2); + VdbeCoverage(v); } - sqlite3VdbeAddOp2(v, OP_IdxInsert, baseCur+i+1, aRegIdx[i]); - if( useSeekResult ){ - sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i]); + pik_flags = 0; + if( useSeekResult ) pik_flags = OPFLAG_USESEEKRESULT; + if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ + assert( pParse->nested==0 ); + pik_flags |= OPFLAG_NCHANGE; } + if( pik_flags ) sqlite3VdbeChangeP5(v, pik_flags); } - regData = regRowid + 1; + if( !HasRowid(pTab) ) return; + regData = regNewData + 1; regRec = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec); - sqlite3TableAffinityStr(v, pTab); + if( !bAffinityDone ) sqlite3TableAffinity(v, pTab, 0); sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol); if( pParse->nested ){ pik_flags = 0; }else{ pik_flags = OPFLAG_NCHANGE; @@ -93133,60 +100776,95 @@ pik_flags |= OPFLAG_APPEND; } if( useSeekResult ){ pik_flags |= OPFLAG_USESEEKRESULT; } - sqlite3VdbeAddOp3(v, OP_Insert, baseCur, regRec, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iDataCur, regRec, regNewData); if( !pParse->nested ){ sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT); } sqlite3VdbeChangeP5(v, pik_flags); } /* -** Generate code that will open cursors for a table and for all -** indices of that table. The "baseCur" parameter is the cursor number used -** for the table. Indices are opened on subsequent cursors. +** Allocate cursors for the pTab table and all its indices and generate +** code to open and initialized those cursors. ** -** Return the number of indices on the table. +** The cursor for the object that contains the complete data (normally +** the table itself, but the PRIMARY KEY index in the case of a WITHOUT +** ROWID table) is returned in *piDataCur. The first index cursor is +** returned in *piIdxCur. The number of indices is returned. +** +** Use iBase as the first cursor (either the *piDataCur for rowid tables +** or the first index for WITHOUT ROWID tables) if it is non-negative. +** If iBase is negative, then allocate the next available cursor. +** +** For a rowid table, *piDataCur will be exactly one less than *piIdxCur. +** For a WITHOUT ROWID table, *piDataCur will be somewhere in the range +** of *piIdxCurs, depending on where the PRIMARY KEY index appears on the +** pTab->pIndex list. +** +** If pTab is a virtual table, then this routine is a no-op and the +** *piDataCur and *piIdxCur values are left uninitialized. */ SQLITE_PRIVATE int sqlite3OpenTableAndIndices( Parse *pParse, /* Parsing context */ Table *pTab, /* Table to be opened */ - int baseCur, /* Cursor number assigned to the table */ - int op /* OP_OpenRead or OP_OpenWrite */ + int op, /* OP_OpenRead or OP_OpenWrite */ + int iBase, /* Use this for the table cursor, if there is one */ + u8 *aToOpen, /* If not NULL: boolean for each table and index */ + int *piDataCur, /* Write the database source cursor number here */ + int *piIdxCur /* Write the first index cursor number here */ ){ int i; int iDb; + int iDataCur; Index *pIdx; Vdbe *v; - if( IsVirtual(pTab) ) return 0; + assert( op==OP_OpenRead || op==OP_OpenWrite ); + if( IsVirtual(pTab) ){ + /* This routine is a no-op for virtual tables. Leave the output + ** variables *piDataCur and *piIdxCur uninitialized so that valgrind + ** can detect if they are used by mistake in the caller. */ + return 0; + } iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); v = sqlite3GetVdbe(pParse); assert( v!=0 ); - sqlite3OpenTable(pParse, baseCur, iDb, pTab, op); - for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); + if( iBase<0 ) iBase = pParse->nTab; + iDataCur = iBase++; + if( piDataCur ) *piDataCur = iDataCur; + if( HasRowid(pTab) && (aToOpen==0 || aToOpen[0]) ){ + sqlite3OpenTable(pParse, iDataCur, iDb, pTab, op); + }else{ + sqlite3TableLock(pParse, iDb, pTab->tnum, op==OP_OpenWrite, pTab->zName); + } + if( piIdxCur ) *piIdxCur = iBase; + for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + int iIdxCur = iBase++; assert( pIdx->pSchema==pTab->pSchema ); - sqlite3VdbeAddOp4(v, op, i+baseCur, pIdx->tnum, iDb, - (char*)pKey, P4_KEYINFO_HANDOFF); - VdbeComment((v, "%s", pIdx->zName)); - } - if( pParse->nTab<baseCur+i ){ - pParse->nTab = baseCur+i; - } - return i-1; + if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) && piDataCur ){ + *piDataCur = iIdxCur; + } + if( aToOpen==0 || aToOpen[i+1] ){ + sqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + VdbeComment((v, "%s", pIdx->zName)); + } + } + if( iBase>pParse->nTab ) pParse->nTab = iBase; + return i; } #ifdef SQLITE_TEST /* ** The following global variable is incremented whenever the ** transfer optimization is used. This is used for testing ** purposes only - to make sure the transfer optimization really -** is happening when it is suppose to. +** is happening when it is supposed to. */ SQLITE_API int sqlite3_xferopt_count; #endif /* SQLITE_TEST */ @@ -93218,17 +100896,17 @@ */ static int xferCompatibleIndex(Index *pDest, Index *pSrc){ int i; assert( pDest && pSrc ); assert( pDest->pTable!=pSrc->pTable ); - if( pDest->nColumn!=pSrc->nColumn ){ + if( pDest->nKeyCol!=pSrc->nKeyCol ){ return 0; /* Different number of columns */ } if( pDest->onError!=pSrc->onError ){ return 0; /* Different conflict resolution strategies */ } - for(i=0; i<pSrc->nColumn; i++){ + for(i=0; i<pSrc->nKeyCol; i++){ if( pSrc->aiColumn[i]!=pDest->aiColumn[i] ){ return 0; /* Different columns indexed */ } if( pSrc->aSortOrder[i]!=pDest->aSortOrder[i] ){ return 0; /* Different sort orders */ @@ -93249,11 +100927,11 @@ ** Attempt the transfer optimization on INSERTs of the form ** ** INSERT INTO tab1 SELECT * FROM tab2; ** ** The xfer optimization transfers raw records from tab2 over to tab1. -** Columns are not decoded and reassemblied, which greatly improves +** Columns are not decoded and reassembled, which greatly improves ** performance. Raw index records are transferred in the same way. ** ** The xfer optimization is only attempted if tab1 and tab2 are compatible. ** There are lots of rules for determining compatibility - see comments ** embedded in the code for details. @@ -93283,20 +100961,25 @@ struct SrcList_item *pItem; /* An element of pSelect->pSrc */ int i; /* Loop counter */ int iDbSrc; /* The database of pSrc */ int iSrc, iDest; /* Cursors from source and destination */ int addr1, addr2; /* Loop addresses */ - int emptyDestTest; /* Address of test for empty pDest */ - int emptySrcTest; /* Address of test for empty pSrc */ + int emptyDestTest = 0; /* Address of test for empty pDest */ + int emptySrcTest = 0; /* Address of test for empty pSrc */ Vdbe *v; /* The VDBE we are building */ - KeyInfo *pKey; /* Key information for an index */ int regAutoinc; /* Memory register used by AUTOINC */ int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */ int regData, regRowid; /* Registers holding data and rowid */ if( pSelect==0 ){ return 0; /* Must be of the form INSERT INTO ... SELECT ... */ + } + if( pParse->pWith || pSelect->pWith ){ + /* Do not attempt to process this query if there are an WITH clauses + ** attached to it. Proceeding may generate a false "no such table: xxx" + ** error if pSelect reads from a CTE named "xxx". */ + return 0; } if( sqlite3TriggerList(pParse, pDest) ){ return 0; /* tab1 must not have triggers */ } #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -93356,10 +101039,13 @@ return 0; /* FROM clause does not contain a real table */ } if( pSrc==pDest ){ return 0; /* tab1 and tab2 may not be the same table */ } + if( HasRowid(pDest)!=HasRowid(pSrc) ){ + return 0; /* source and destination must both be WITHOUT ROWID or not */ + } #ifndef SQLITE_OMIT_VIRTUALTABLE if( pSrc->tabFlags & TF_Virtual ){ return 0; /* tab2 must not be a virtual table */ } #endif @@ -93371,22 +101057,31 @@ } if( pDest->iPKey!=pSrc->iPKey ){ return 0; /* Both tables must have the same INTEGER PRIMARY KEY */ } for(i=0; i<pDest->nCol; i++){ - if( pDest->aCol[i].affinity!=pSrc->aCol[i].affinity ){ + Column *pDestCol = &pDest->aCol[i]; + Column *pSrcCol = &pSrc->aCol[i]; + if( pDestCol->affinity!=pSrcCol->affinity ){ return 0; /* Affinity must be the same on all columns */ } - if( !xferCompatibleCollation(pDest->aCol[i].zColl, pSrc->aCol[i].zColl) ){ + if( !xferCompatibleCollation(pDestCol->zColl, pSrcCol->zColl) ){ return 0; /* Collating sequence must be the same on all columns */ } - if( pDest->aCol[i].notNull && !pSrc->aCol[i].notNull ){ + if( pDestCol->notNull && !pSrcCol->notNull ){ return 0; /* tab2 must be NOT NULL if tab1 is */ + } + /* Default values for second and subsequent columns need to match. */ + if( i>0 + && ((pDestCol->zDflt==0)!=(pSrcCol->zDflt==0) + || (pDestCol->zDflt && strcmp(pDestCol->zDflt, pSrcCol->zDflt)!=0)) + ){ + return 0; /* Default values must be the same for all columns */ } } for(pDestIdx=pDest->pIndex; pDestIdx; pDestIdx=pDestIdx->pNext){ - if( pDestIdx->onError!=OE_None ){ + if( IsUniqueIndex(pDestIdx) ){ destHasUniqueIdx = 1; } for(pSrcIdx=pSrc->pIndex; pSrcIdx; pSrcIdx=pSrcIdx->pNext){ if( xferCompatibleIndex(pDestIdx, pSrcIdx) ) break; } @@ -93426,11 +101121,14 @@ v = sqlite3GetVdbe(pParse); sqlite3CodeVerifySchema(pParse, iDbSrc); iSrc = pParse->nTab++; iDest = pParse->nTab++; regAutoinc = autoIncBegin(pParse, iDbDest, pDest); + regData = sqlite3GetTempReg(pParse); + regRowid = sqlite3GetTempReg(pParse); sqlite3OpenTable(pParse, iDest, iDbDest, pDest, OP_OpenWrite); + assert( HasRowid(pDest) || destHasUniqueIdx ); if( (pDest->iPKey<0 && pDest->pIndex!=0) /* (1) */ || destHasUniqueIdx /* (2) */ || (onError!=OE_Abort && onError!=OE_Rollback) /* (3) */ ){ /* In some circumstances, we are able to run the xfer optimization @@ -93445,64 +101143,64 @@ ** (2) The destination has a unique index. (The xfer optimization ** is unable to test uniqueness.) ** ** (3) onError is something other than OE_Abort and OE_Rollback. */ - addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0); + addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0); VdbeCoverage(v); emptyDestTest = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0); sqlite3VdbeJumpHere(v, addr1); - }else{ - emptyDestTest = 0; - } - sqlite3OpenTable(pParse, iSrc, iDbSrc, pSrc, OP_OpenRead); - emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); - regData = sqlite3GetTempReg(pParse); - regRowid = sqlite3GetTempReg(pParse); - if( pDest->iPKey>=0 ){ - addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); - addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid); - sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_PRIMARYKEY, - onError, "PRIMARY KEY must be unique", P4_STATIC); - sqlite3VdbeJumpHere(v, addr2); - autoIncStep(pParse, regAutoinc, regRowid); - }else if( pDest->pIndex==0 ){ - addr1 = sqlite3VdbeAddOp2(v, OP_NewRowid, iDest, regRowid); - }else{ - addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); - assert( (pDest->tabFlags & TF_Autoincrement)==0 ); - } - sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData); - sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid); - sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND); - sqlite3VdbeChangeP4(v, -1, pDest->zName, 0); - sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); + } + if( HasRowid(pSrc) ){ + sqlite3OpenTable(pParse, iSrc, iDbSrc, pSrc, OP_OpenRead); + emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v); + if( pDest->iPKey>=0 ){ + addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); + addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid); + VdbeCoverage(v); + sqlite3RowidConstraint(pParse, onError, pDest); + sqlite3VdbeJumpHere(v, addr2); + autoIncStep(pParse, regAutoinc, regRowid); + }else if( pDest->pIndex==0 ){ + addr1 = sqlite3VdbeAddOp2(v, OP_NewRowid, iDest, regRowid); + }else{ + addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); + assert( (pDest->tabFlags & TF_Autoincrement)==0 ); + } + sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData); + sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid); + sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND); + sqlite3VdbeChangeP4(v, -1, pDest->zName, 0); + sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); + sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); + }else{ + sqlite3TableLock(pParse, iDbDest, pDest->tnum, 1, pDest->zName); + sqlite3TableLock(pParse, iDbSrc, pSrc->tnum, 0, pSrc->zName); + } for(pDestIdx=pDest->pIndex; pDestIdx; pDestIdx=pDestIdx->pNext){ for(pSrcIdx=pSrc->pIndex; ALWAYS(pSrcIdx); pSrcIdx=pSrcIdx->pNext){ if( xferCompatibleIndex(pDestIdx, pSrcIdx) ) break; } assert( pSrcIdx ); - sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); - sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); - pKey = sqlite3IndexKeyinfo(pParse, pSrcIdx); - sqlite3VdbeAddOp4(v, OP_OpenRead, iSrc, pSrcIdx->tnum, iDbSrc, - (char*)pKey, P4_KEYINFO_HANDOFF); + sqlite3VdbeAddOp3(v, OP_OpenRead, iSrc, pSrcIdx->tnum, iDbSrc); + sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx); VdbeComment((v, "%s", pSrcIdx->zName)); - pKey = sqlite3IndexKeyinfo(pParse, pDestIdx); - sqlite3VdbeAddOp4(v, OP_OpenWrite, iDest, pDestIdx->tnum, iDbDest, - (char*)pKey, P4_KEYINFO_HANDOFF); + sqlite3VdbeAddOp3(v, OP_OpenWrite, iDest, pDestIdx->tnum, iDbDest); + sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx); + sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR); VdbeComment((v, "%s", pDestIdx->zName)); - addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); + addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_RowKey, iSrc, regData); sqlite3VdbeAddOp3(v, OP_IdxInsert, iDest, regData, 1); - sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1); + sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addr1); + sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); + sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); } - sqlite3VdbeJumpHere(v, emptySrcTest); + if( emptySrcTest ) sqlite3VdbeJumpHere(v, emptySrcTest); sqlite3ReleaseTempReg(pParse, regRowid); sqlite3ReleaseTempReg(pParse, regData); - sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); - sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); if( emptyDestTest ){ sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_OK, 0); sqlite3VdbeJumpHere(v, emptyDestTest); sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); return 0; @@ -93540,11 +101238,11 @@ ** If the SQL is a query, then for each row in the query result ** the xCallback() function is called. pArg becomes the first ** argument to xCallback(). If xCallback=NULL then no callback ** is invoked, even for queries. */ -SQLITE_API int sqlite3_exec( +SQLITE_API int SQLITE_STDCALL sqlite3_exec( sqlite3 *db, /* The database on which the SQL executes */ const char *zSql, /* The SQL to be executed */ sqlite3_callback xCallback, /* Invoke this callback routine */ void *pArg, /* First argument to xCallback() */ char **pzErrMsg /* Write error messages here */ @@ -93557,11 +101255,11 @@ if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; if( zSql==0 ) zSql = ""; sqlite3_mutex_enter(db->mutex); - sqlite3Error(db, SQLITE_OK, 0); + sqlite3Error(db, SQLITE_OK); while( rc==SQLITE_OK && zSql[0] ){ int nCol; char **azVals = 0; pStmt = 0; @@ -93609,14 +101307,17 @@ goto exec_out; } } } if( xCallback(pArg, nCol, azVals, azCols) ){ + /* EVIDENCE-OF: R-38229-40159 If the callback function to + ** sqlite3_exec() returns non-zero, then sqlite3_exec() will + ** return SQLITE_ABORT. */ rc = SQLITE_ABORT; sqlite3VdbeFinalize((Vdbe *)pStmt); pStmt = 0; - sqlite3Error(db, SQLITE_ABORT, 0); + sqlite3Error(db, SQLITE_ABORT); goto exec_out; } } if( rc!=SQLITE_ROW ){ @@ -93635,18 +101336,18 @@ exec_out: if( pStmt ) sqlite3VdbeFinalize((Vdbe *)pStmt); sqlite3DbFree(db, azCols); rc = sqlite3ApiExit(db, rc); - if( rc!=SQLITE_OK && ALWAYS(rc==sqlite3_errcode(db)) && pzErrMsg ){ + if( rc!=SQLITE_OK && pzErrMsg ){ int nErrMsg = 1 + sqlite3Strlen30(sqlite3_errmsg(db)); *pzErrMsg = sqlite3Malloc(nErrMsg); if( *pzErrMsg ){ memcpy(*pzErrMsg, sqlite3_errmsg(db), nErrMsg); }else{ rc = SQLITE_NOMEM; - sqlite3Error(db, SQLITE_NOMEM, 0); + sqlite3Error(db, SQLITE_NOMEM); } }else if( pzErrMsg ){ *pzErrMsg = 0; } @@ -93704,11 +101405,11 @@ ** routines. ** ** WARNING: In order to maintain backwards compatibility, add new ** interfaces to the end of this structure only. If you insert new ** interfaces in the middle of this structure, then older different -** versions of SQLite will not be able to load each others' shared +** versions of SQLite will not be able to load each other's shared ** libraries! */ struct sqlite3_api_routines { void * (*aggregate_context)(sqlite3_context*,int nBytes); int (*aggregate_count)(sqlite3_context*); @@ -93926,15 +101627,32 @@ int (*uri_boolean)(const char*,const char*,int); sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64); const char *(*uri_parameter)(const char*,const char*); char *(*vsnprintf)(int,char*,const char*,va_list); int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*); + /* Version 3.8.7 and later */ + int (*auto_extension)(void(*)(void)); + int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64, + void(*)(void*)); + int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64, + void(*)(void*),unsigned char); + int (*cancel_auto_extension)(void(*)(void)); + int (*load_extension)(sqlite3*,const char*,const char*,char**); + void *(*malloc64)(sqlite3_uint64); + sqlite3_uint64 (*msize)(void*); + void *(*realloc64)(void*,sqlite3_uint64); + void (*reset_auto_extension)(void); + void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64, + void(*)(void*)); + void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64, + void(*)(void*), unsigned char); + int (*strglob)(const char*,const char*); }; /* ** The following macros redefine the API routines so that they are -** redirected throught the global sqlite3_api structure. +** redirected through the global sqlite3_api structure. ** ** This header file is also used by the loadext.c source file ** (part of the main SQLite library - not an extension) so that ** it can get access to the sqlite3_api_routines structure ** definition. But the main library does not want to redefine @@ -94143,10 +101861,23 @@ #define sqlite3_uri_boolean sqlite3_api->uri_boolean #define sqlite3_uri_int64 sqlite3_api->uri_int64 #define sqlite3_uri_parameter sqlite3_api->uri_parameter #define sqlite3_uri_vsnprintf sqlite3_api->vsnprintf #define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2 +/* Version 3.8.7 and later */ +#define sqlite3_auto_extension sqlite3_api->auto_extension +#define sqlite3_bind_blob64 sqlite3_api->bind_blob64 +#define sqlite3_bind_text64 sqlite3_api->bind_text64 +#define sqlite3_cancel_auto_extension sqlite3_api->cancel_auto_extension +#define sqlite3_load_extension sqlite3_api->load_extension +#define sqlite3_malloc64 sqlite3_api->malloc64 +#define sqlite3_msize sqlite3_api->msize +#define sqlite3_realloc64 sqlite3_api->realloc64 +#define sqlite3_reset_auto_extension sqlite3_api->reset_auto_extension +#define sqlite3_result_blob64 sqlite3_api->result_blob64 +#define sqlite3_result_text64 sqlite3_api->result_text64 +#define sqlite3_strglob sqlite3_api->strglob #endif /* SQLITE_CORE */ #ifndef SQLITE_CORE /* This case when the file really is being compiled as a loadable ** extension */ @@ -94180,11 +101911,10 @@ # define sqlite3_column_database_name16 0 # define sqlite3_column_table_name 0 # define sqlite3_column_table_name16 0 # define sqlite3_column_origin_name 0 # define sqlite3_column_origin_name16 0 -# define sqlite3_table_column_metadata 0 #endif #ifdef SQLITE_OMIT_AUTHORIZATION # define sqlite3_set_authorizer 0 #endif @@ -94536,11 +102266,24 @@ sqlite3_stricmp, sqlite3_uri_boolean, sqlite3_uri_int64, sqlite3_uri_parameter, sqlite3_vsnprintf, - sqlite3_wal_checkpoint_v2 + sqlite3_wal_checkpoint_v2, + /* Version 3.8.7 and later */ + sqlite3_auto_extension, + sqlite3_bind_blob64, + sqlite3_bind_text64, + sqlite3_cancel_auto_extension, + sqlite3_load_extension, + sqlite3_malloc64, + sqlite3_msize, + sqlite3_realloc64, + sqlite3_reset_auto_extension, + sqlite3_result_blob64, + sqlite3_result_text64, + sqlite3_strglob }; /* ** Attempt to load an SQLite extension library contained in the file ** zFile. The entry point is zProc. zProc may be 0 in which case a @@ -94690,11 +102433,11 @@ db->aExtension = aHandle; db->aExtension[db->nExtension++] = handle; return SQLITE_OK; } -SQLITE_API int sqlite3_load_extension( +SQLITE_API int SQLITE_STDCALL sqlite3_load_extension( sqlite3 *db, /* Load the extension into this database connection */ const char *zFile, /* Name of the shared library containing extension */ const char *zProc, /* Entry point. Use "sqlite3_extension_init" if 0 */ char **pzErrMsg /* Put error message here if not 0 */ ){ @@ -94721,11 +102464,11 @@ /* ** Enable or disable extension loading. Extension loading is disabled by ** default so as not to open security holes in older applications. */ -SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff){ +SQLITE_API int SQLITE_STDCALL sqlite3_enable_load_extension(sqlite3 *db, int onoff){ sqlite3_mutex_enter(db->mutex); if( onoff ){ db->flags |= SQLITE_LoadExtension; }else{ db->flags &= ~SQLITE_LoadExtension; @@ -94778,11 +102521,11 @@ /* ** Register a statically linked extension that is automatically ** loaded by every new database connection. */ -SQLITE_API int sqlite3_auto_extension(void (*xInit)(void)){ +SQLITE_API int SQLITE_STDCALL sqlite3_auto_extension(void (*xInit)(void)){ int rc = SQLITE_OK; #ifndef SQLITE_OMIT_AUTOINIT rc = sqlite3_initialize(); if( rc ){ return rc; @@ -94823,11 +102566,11 @@ ** routine is a no-op. ** ** Return 1 if xInit was found on the list and removed. Return 0 if xInit ** was not on the list. */ -SQLITE_API int sqlite3_cancel_auto_extension(void (*xInit)(void)){ +SQLITE_API int SQLITE_STDCALL sqlite3_cancel_auto_extension(void (*xInit)(void)){ #if SQLITE_THREADSAFE sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); #endif int i; int n = 0; @@ -94846,11 +102589,11 @@ } /* ** Reset the automatic extension loading mechanism. */ -SQLITE_API void sqlite3_reset_auto_extension(void){ +SQLITE_API void SQLITE_STDCALL sqlite3_reset_auto_extension(void){ #ifndef SQLITE_OMIT_AUTOINIT if( sqlite3_initialize()==SQLITE_OK ) #endif { #if SQLITE_THREADSAFE @@ -94895,11 +102638,11 @@ wsdAutoext.aExt[i]; } sqlite3_mutex_leave(mutex); zErrmsg = 0; if( xInit && (rc = xInit(db, &zErrmsg, &sqlite3Apis))!=0 ){ - sqlite3Error(db, rc, + sqlite3ErrorWithMsg(db, rc, "automatic extension loading failed: %s", zErrmsg); go = 0; } sqlite3_free(zErrmsg); } @@ -94928,15 +102671,22 @@ # define SQLITE_ENABLE_LOCKING_STYLE 0 # endif #endif /*************************************************************************** -** The next block of code, including the PragTyp_XXXX macro definitions and -** the aPragmaName[] object is composed of generated code. DO NOT EDIT. -** -** To add new pragmas, edit the code in ../tool/mkpragmatab.tcl and rerun -** that script. Then copy/paste the output in place of the following: +** The "pragma.h" include file is an automatically generated file that +** that includes the PragType_XXXX macro definitions and the aPragmaName[] +** object. This ensures that the aPragmaName[] table is arranged in +** lexicographical order to facility a binary search of the pragma name. +** Do not edit pragma.h directly. Edit and rerun the script in at +** ../tool/mkpragmatab.tcl. */ +/************** Include pragma.h in the middle of pragma.c *******************/ +/************** Begin file pragma.h ******************************************/ +/* DO NOT EDIT! +** This file is automatically generated by the script at +** ../tool/mkpragmatab.tcl. To update the set of pragmas, edit +** that script and rerun it. */ #define PragTyp_HEADER_VALUE 0 #define PragTyp_AUTO_VACUUM 1 #define PragTyp_FLAG 2 #define PragTyp_BUSY_TIMEOUT 3 @@ -94962,195 +102712,439 @@ #define PragTyp_MMAP_SIZE 23 #define PragTyp_PAGE_SIZE 24 #define PragTyp_SECURE_DELETE 25 #define PragTyp_SHRINK_MEMORY 26 #define PragTyp_SOFT_HEAP_LIMIT 27 -#define PragTyp_SYNCHRONOUS 28 -#define PragTyp_TABLE_INFO 29 -#define PragTyp_TEMP_STORE 30 -#define PragTyp_TEMP_STORE_DIRECTORY 31 -#define PragTyp_WAL_AUTOCHECKPOINT 32 -#define PragTyp_WAL_CHECKPOINT 33 -#define PragTyp_ACTIVATE_EXTENSIONS 34 -#define PragTyp_HEXKEY 35 -#define PragTyp_KEY 36 -#define PragTyp_REKEY 37 -#define PragTyp_LOCK_STATUS 38 -#define PragTyp_PARSER_TRACE 39 +#define PragTyp_STATS 28 +#define PragTyp_SYNCHRONOUS 29 +#define PragTyp_TABLE_INFO 30 +#define PragTyp_TEMP_STORE 31 +#define PragTyp_TEMP_STORE_DIRECTORY 32 +#define PragTyp_THREADS 33 +#define PragTyp_WAL_AUTOCHECKPOINT 34 +#define PragTyp_WAL_CHECKPOINT 35 +#define PragTyp_ACTIVATE_EXTENSIONS 36 +#define PragTyp_HEXKEY 37 +#define PragTyp_KEY 38 +#define PragTyp_REKEY 39 +#define PragTyp_LOCK_STATUS 40 +#define PragTyp_PARSER_TRACE 41 +#define PragFlag_NeedSchema 0x01 +#define PragFlag_ReadOnly 0x02 static const struct sPragmaNames { const char *const zName; /* Name of pragma */ u8 ePragTyp; /* PragTyp_XXX value */ + u8 mPragFlag; /* Zero or more PragFlag_XXX values */ u32 iArg; /* Extra argument */ } aPragmaNames[] = { #if defined(SQLITE_HAS_CODEC) || defined(SQLITE_ENABLE_CEROD) - { "activate_extensions", PragTyp_ACTIVATE_EXTENSIONS, 0 }, + { /* zName: */ "activate_extensions", + /* ePragTyp: */ PragTyp_ACTIVATE_EXTENSIONS, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { "application_id", PragTyp_HEADER_VALUE, 0 }, + { /* zName: */ "application_id", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlag: */ 0, + /* iArg: */ BTREE_APPLICATION_ID }, #endif #if !defined(SQLITE_OMIT_AUTOVACUUM) - { "auto_vacuum", PragTyp_AUTO_VACUUM, 0 }, + { /* zName: */ "auto_vacuum", + /* ePragTyp: */ PragTyp_AUTO_VACUUM, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, #endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_AUTOMATIC_INDEX) - { "automatic_index", PragTyp_FLAG, - SQLITE_AutoIndex }, + { /* zName: */ "automatic_index", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_AutoIndex }, #endif - { "busy_timeout", PragTyp_BUSY_TIMEOUT, 0 }, +#endif + { /* zName: */ "busy_timeout", + /* ePragTyp: */ PragTyp_BUSY_TIMEOUT, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { "cache_size", PragTyp_CACHE_SIZE, 0 }, + { /* zName: */ "cache_size", + /* ePragTyp: */ PragTyp_CACHE_SIZE, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, #endif - { "cache_spill", PragTyp_FLAG, - SQLITE_CacheSpill }, - { "case_sensitive_like", PragTyp_CASE_SENSITIVE_LIKE, 0 }, - { "checkpoint_fullfsync", PragTyp_FLAG, - SQLITE_CkptFullFSync }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + { /* zName: */ "cache_spill", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_CacheSpill }, +#endif + { /* zName: */ "case_sensitive_like", + /* ePragTyp: */ PragTyp_CASE_SENSITIVE_LIKE, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + { /* zName: */ "checkpoint_fullfsync", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_CkptFullFSync }, +#endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { "collation_list", PragTyp_COLLATION_LIST, 0 }, + { /* zName: */ "collation_list", + /* ePragTyp: */ PragTyp_COLLATION_LIST, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS) - { "compile_options", PragTyp_COMPILE_OPTIONS, 0 }, + { /* zName: */ "compile_options", + /* ePragTyp: */ PragTyp_COMPILE_OPTIONS, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, #endif - { "count_changes", PragTyp_FLAG, - SQLITE_CountRows }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + { /* zName: */ "count_changes", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_CountRows }, +#endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_OS_WIN - { "data_store_directory", PragTyp_DATA_STORE_DIRECTORY, 0 }, + { /* zName: */ "data_store_directory", + /* ePragTyp: */ PragTyp_DATA_STORE_DIRECTORY, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) + { /* zName: */ "data_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlag: */ PragFlag_ReadOnly, + /* iArg: */ BTREE_DATA_VERSION }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { "database_list", PragTyp_DATABASE_LIST, 0 }, + { /* zName: */ "database_list", + /* ePragTyp: */ PragTyp_DATABASE_LIST, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) - { "default_cache_size", PragTyp_DEFAULT_CACHE_SIZE, 0 }, + { /* zName: */ "default_cache_size", + /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, #endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - { "defer_foreign_keys", PragTyp_FLAG, - SQLITE_DeferFKs }, + { /* zName: */ "defer_foreign_keys", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_DeferFKs }, #endif - { "empty_result_callbacks", PragTyp_FLAG, - SQLITE_NullCallback }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + { /* zName: */ "empty_result_callbacks", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_NullCallback }, +#endif #if !defined(SQLITE_OMIT_UTF16) - { "encoding", PragTyp_ENCODING, 0 }, + { /* zName: */ "encoding", + /* ePragTyp: */ PragTyp_ENCODING, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - { "foreign_key_check", PragTyp_FOREIGN_KEY_CHECK, 0 }, + { /* zName: */ "foreign_key_check", + /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FOREIGN_KEY) - { "foreign_key_list", PragTyp_FOREIGN_KEY_LIST, 0 }, + { /* zName: */ "foreign_key_list", + /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, #endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - { "foreign_keys", PragTyp_FLAG, - SQLITE_ForeignKeys }, + { /* zName: */ "foreign_keys", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_ForeignKeys }, +#endif #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { "freelist_count", PragTyp_HEADER_VALUE, 0 }, + { /* zName: */ "freelist_count", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlag: */ PragFlag_ReadOnly, + /* iArg: */ BTREE_FREE_PAGE_COUNT }, #endif - { "full_column_names", PragTyp_FLAG, - SQLITE_FullColNames }, - { "fullfsync", PragTyp_FLAG, - SQLITE_FullFSync }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + { /* zName: */ "full_column_names", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_FullColNames }, + { /* zName: */ "fullfsync", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_FullFSync }, +#endif #if defined(SQLITE_HAS_CODEC) - { "hexkey", PragTyp_HEXKEY, 0 }, + { /* zName: */ "hexkey", + /* ePragTyp: */ PragTyp_HEXKEY, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, + { /* zName: */ "hexrekey", + /* ePragTyp: */ PragTyp_HEXKEY, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, #endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_CHECK) - { "ignore_check_constraints", PragTyp_FLAG, - SQLITE_IgnoreChecks }, + { /* zName: */ "ignore_check_constraints", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_IgnoreChecks }, +#endif #endif #if !defined(SQLITE_OMIT_AUTOVACUUM) - { "incremental_vacuum", PragTyp_INCREMENTAL_VACUUM, 0 }, + { /* zName: */ "incremental_vacuum", + /* ePragTyp: */ PragTyp_INCREMENTAL_VACUUM, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { "index_info", PragTyp_INDEX_INFO, 0 }, - { "index_list", PragTyp_INDEX_LIST, 0 }, + { /* zName: */ "index_info", + /* ePragTyp: */ PragTyp_INDEX_INFO, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, + { /* zName: */ "index_list", + /* ePragTyp: */ PragTyp_INDEX_LIST, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, + { /* zName: */ "index_xinfo", + /* ePragTyp: */ PragTyp_INDEX_INFO, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 1 }, #endif #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) - { "integrity_check", PragTyp_INTEGRITY_CHECK, 0 }, + { /* zName: */ "integrity_check", + /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { "journal_mode", PragTyp_JOURNAL_MODE, 0 }, - { "journal_size_limit", PragTyp_JOURNAL_SIZE_LIMIT, 0 }, + { /* zName: */ "journal_mode", + /* ePragTyp: */ PragTyp_JOURNAL_MODE, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, + { /* zName: */ "journal_size_limit", + /* ePragTyp: */ PragTyp_JOURNAL_SIZE_LIMIT, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, #endif #if defined(SQLITE_HAS_CODEC) - { "key", PragTyp_KEY, 0 }, + { /* zName: */ "key", + /* ePragTyp: */ PragTyp_KEY, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, #endif - { "legacy_file_format", PragTyp_FLAG, - SQLITE_LegacyFileFmt }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + { /* zName: */ "legacy_file_format", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_LegacyFileFmt }, +#endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_ENABLE_LOCKING_STYLE - { "lock_proxy_file", PragTyp_LOCK_PROXY_FILE, 0 }, + { /* zName: */ "lock_proxy_file", + /* ePragTyp: */ PragTyp_LOCK_PROXY_FILE, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, #endif #if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) - { "lock_status", PragTyp_LOCK_STATUS, 0 }, -#endif -#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { "locking_mode", PragTyp_LOCKING_MODE, 0 }, - { "max_page_count", PragTyp_PAGE_COUNT, 0 }, - { "mmap_size", PragTyp_MMAP_SIZE, 0 }, - { "page_count", PragTyp_PAGE_COUNT, 0 }, - { "page_size", PragTyp_PAGE_SIZE, 0 }, -#endif -#if defined(SQLITE_DEBUG) - { "parser_trace", PragTyp_PARSER_TRACE, 0 }, -#endif - { "query_only", PragTyp_FLAG, - SQLITE_QueryOnly }, -#if !defined(SQLITE_OMIT_INTEGRITY_CHECK) - { "quick_check", PragTyp_INTEGRITY_CHECK, 0 }, -#endif - { "read_uncommitted", PragTyp_FLAG, - SQLITE_ReadUncommitted }, - { "recursive_triggers", PragTyp_FLAG, - SQLITE_RecTriggers }, -#if defined(SQLITE_HAS_CODEC) - { "rekey", PragTyp_REKEY, 0 }, -#endif - { "reverse_unordered_selects", PragTyp_FLAG, - SQLITE_ReverseOrder }, -#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { "schema_version", PragTyp_HEADER_VALUE, 0 }, -#endif -#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { "secure_delete", PragTyp_SECURE_DELETE, 0 }, -#endif - { "short_column_names", PragTyp_FLAG, - SQLITE_ShortColNames }, - { "shrink_memory", PragTyp_SHRINK_MEMORY, 0 }, - { "soft_heap_limit", PragTyp_SOFT_HEAP_LIMIT, 0 }, -#if defined(SQLITE_DEBUG) - { "sql_trace", PragTyp_FLAG, - SQLITE_SqlTrace }, -#endif -#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { "synchronous", PragTyp_SYNCHRONOUS, 0 }, + { /* zName: */ "lock_status", + /* ePragTyp: */ PragTyp_LOCK_STATUS, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + { /* zName: */ "locking_mode", + /* ePragTyp: */ PragTyp_LOCKING_MODE, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, + { /* zName: */ "max_page_count", + /* ePragTyp: */ PragTyp_PAGE_COUNT, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, + { /* zName: */ "mmap_size", + /* ePragTyp: */ PragTyp_MMAP_SIZE, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, + { /* zName: */ "page_count", + /* ePragTyp: */ PragTyp_PAGE_COUNT, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, + { /* zName: */ "page_size", + /* ePragTyp: */ PragTyp_PAGE_SIZE, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, +#endif +#if defined(SQLITE_DEBUG) + { /* zName: */ "parser_trace", + /* ePragTyp: */ PragTyp_PARSER_TRACE, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + { /* zName: */ "query_only", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_QueryOnly }, +#endif +#if !defined(SQLITE_OMIT_INTEGRITY_CHECK) + { /* zName: */ "quick_check", + /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + { /* zName: */ "read_uncommitted", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_ReadUncommitted }, + { /* zName: */ "recursive_triggers", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_RecTriggers }, +#endif +#if defined(SQLITE_HAS_CODEC) + { /* zName: */ "rekey", + /* ePragTyp: */ PragTyp_REKEY, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + { /* zName: */ "reverse_unordered_selects", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_ReverseOrder }, +#endif +#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) + { /* zName: */ "schema_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlag: */ 0, + /* iArg: */ BTREE_SCHEMA_VERSION }, +#endif +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + { /* zName: */ "secure_delete", + /* ePragTyp: */ PragTyp_SECURE_DELETE, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + { /* zName: */ "short_column_names", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_ShortColNames }, +#endif + { /* zName: */ "shrink_memory", + /* ePragTyp: */ PragTyp_SHRINK_MEMORY, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, + { /* zName: */ "soft_heap_limit", + /* ePragTyp: */ PragTyp_SOFT_HEAP_LIMIT, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) +#if defined(SQLITE_DEBUG) + { /* zName: */ "sql_trace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_SqlTrace }, +#endif +#endif +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) + { /* zName: */ "stats", + /* ePragTyp: */ PragTyp_STATS, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, +#endif +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + { /* zName: */ "synchronous", + /* ePragTyp: */ PragTyp_SYNCHRONOUS, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { "table_info", PragTyp_TABLE_INFO, 0 }, + { /* zName: */ "table_info", + /* ePragTyp: */ PragTyp_TABLE_INFO, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { "temp_store", PragTyp_TEMP_STORE, 0 }, - { "temp_store_directory", PragTyp_TEMP_STORE_DIRECTORY, 0 }, + { /* zName: */ "temp_store", + /* ePragTyp: */ PragTyp_TEMP_STORE, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, + { /* zName: */ "temp_store_directory", + /* ePragTyp: */ PragTyp_TEMP_STORE_DIRECTORY, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, #endif + { /* zName: */ "threads", + /* ePragTyp: */ PragTyp_THREADS, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { "user_version", PragTyp_HEADER_VALUE, 0 }, + { /* zName: */ "user_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlag: */ 0, + /* iArg: */ BTREE_USER_VERSION }, #endif +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if defined(SQLITE_DEBUG) - { "vdbe_addoptrace", PragTyp_FLAG, - SQLITE_VdbeAddopTrace }, - { "vdbe_debug", PragTyp_FLAG, - SQLITE_SqlTrace|SQLITE_VdbeListing|SQLITE_VdbeTrace }, - { "vdbe_listing", PragTyp_FLAG, - SQLITE_VdbeListing }, - { "vdbe_trace", PragTyp_FLAG, - SQLITE_VdbeTrace }, + { /* zName: */ "vdbe_addoptrace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_VdbeAddopTrace }, + { /* zName: */ "vdbe_debug", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_SqlTrace|SQLITE_VdbeListing|SQLITE_VdbeTrace }, + { /* zName: */ "vdbe_eqp", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_VdbeEQP }, + { /* zName: */ "vdbe_listing", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_VdbeListing }, + { /* zName: */ "vdbe_trace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_VdbeTrace }, +#endif #endif #if !defined(SQLITE_OMIT_WAL) - { "wal_autocheckpoint", PragTyp_WAL_AUTOCHECKPOINT, 0 }, - { "wal_checkpoint", PragTyp_WAL_CHECKPOINT, 0 }, + { /* zName: */ "wal_autocheckpoint", + /* ePragTyp: */ PragTyp_WAL_AUTOCHECKPOINT, + /* ePragFlag: */ 0, + /* iArg: */ 0 }, + { /* zName: */ "wal_checkpoint", + /* ePragTyp: */ PragTyp_WAL_CHECKPOINT, + /* ePragFlag: */ PragFlag_NeedSchema, + /* iArg: */ 0 }, #endif - { "writable_schema", PragTyp_FLAG, - SQLITE_WriteSchema|SQLITE_RecoveryMode }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + { /* zName: */ "writable_schema", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlag: */ 0, + /* iArg: */ SQLITE_WriteSchema|SQLITE_RecoveryMode }, +#endif }; -/* Number of pragmas: 55 on by default, 66 total. */ -/* End of the automatically generated pragma table. -***************************************************************************/ +/* Number of pragmas: 59 on by default, 72 total. */ + +/************** End of pragma.h **********************************************/ +/************** Continuing where we left off in pragma.c *********************/ /* ** Interpret the given string as a safety level. Return 0 for OFF, ** 1 for ON or NORMAL and 2 for FULL. Return 1 for an empty or ** unrecognized string argument. The FULL option is disallowed @@ -95159,11 +103153,11 @@ ** Note that the values returned are one less that the values that ** should be passed into sqlite3BtreeSetSafetyLevel(). The is done ** to support legacy SQL code. The safety level used to be boolean ** and older scripts may have used numbers 0 for OFF and 1 for ON. */ -static u8 getSafetyLevel(const char *z, int omitFull, int dflt){ +static u8 getSafetyLevel(const char *z, int omitFull, u8 dflt){ /* 123456789 123456789 */ static const char zText[] = "onoffalseyestruefull"; static const u8 iOffset[] = {0, 1, 2, 4, 9, 12, 16}; static const u8 iLength[] = {2, 2, 3, 5, 3, 4, 4}; static const u8 iValue[] = {1, 0, 0, 0, 1, 1, 2}; @@ -95181,11 +103175,11 @@ } /* ** Interpret the given string as a boolean value. */ -SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z, int dflt){ +SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z, u8 dflt){ return getSafetyLevel(z,1,dflt)!=0; } /* The sqlite3GetBoolean() function is used by other modules but the ** remainder of this file is specific to PRAGMA processing. So omit @@ -95394,15 +103388,16 @@ char *zRight = 0; /* Nul-terminated UTF-8 string <value>, or NULL */ const char *zDb = 0; /* The database name */ Token *pId; /* Pointer to <id> token */ char *aFcntl[4]; /* Argument to SQLITE_FCNTL_PRAGMA */ int iDb; /* Database index for <database> */ - int lwr, upr, mid; /* Binary search bounds */ + int lwr, upr, mid = 0; /* Binary search bounds */ int rc; /* return value form SQLITE_FCNTL_PRAGMA */ sqlite3 *db = pParse->db; /* The database connection */ Db *pDb; /* The specific database being pragmaed */ Vdbe *v = sqlite3GetVdbe(pParse); /* Prepared statement */ + const struct sPragmaNames *pPragma; if( v==0 ) return; sqlite3VdbeRunOnlyOnce(v); pParse->nMem = 2; @@ -95434,10 +103429,21 @@ } /* Send an SQLITE_FCNTL_PRAGMA file-control to the underlying VFS ** connection. If it returns SQLITE_OK, then assume that the VFS ** handled the pragma and generate a no-op prepared statement. + ** + ** IMPLEMENTATION-OF: R-12238-55120 Whenever a PRAGMA statement is parsed, + ** an SQLITE_FCNTL_PRAGMA file control is sent to the open sqlite3_file + ** object corresponding to the database file to which the pragma + ** statement refers. + ** + ** IMPLEMENTATION-OF: R-29875-31678 The argument to the SQLITE_FCNTL_PRAGMA + ** file control is an array of pointers to strings (char**) in which the + ** second element of the array is the name of the pragma and the third + ** element is the argument to the pragma or NULL if the pragma has no + ** argument. */ aFcntl[0] = 0; aFcntl[1] = zLeft; aFcntl[2] = zRight; aFcntl[3] = 0; @@ -95476,13 +103482,19 @@ }else{ lwr = mid + 1; } } if( lwr>upr ) goto pragma_out; + pPragma = &aPragmaNames[mid]; + + /* Make sure the database schema is loaded if the pragma requires that */ + if( (pPragma->mPragFlag & PragFlag_NeedSchema)!=0 ){ + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + } /* Jump to the appropriate pragma handler */ - switch( aPragmaNames[mid].ePragTyp ){ + switch( pPragma->ePragTyp ){ #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) /* ** PRAGMA [database.]default_cache_size ** PRAGMA [database.]default_cache_size=N @@ -95498,10 +103510,11 @@ ** is always on by default regardless of the sign of the default cache ** size. But continue to take the absolute value of the default cache ** size of historical compatibility. */ case PragTyp_DEFAULT_CACHE_SIZE: { + static const int iLn = VDBE_OFFSET_LINENO(2); static const VdbeOpList getCacheSize[] = { { OP_Transaction, 0, 0, 0}, /* 0 */ { OP_ReadCookie, 0, 1, BTREE_DEFAULT_CACHE_SIZE}, /* 1 */ { OP_IfPos, 1, 8, 0}, { OP_Integer, 0, 2, 0}, @@ -95510,17 +103523,16 @@ { OP_Integer, 0, 1, 0}, /* 6 */ { OP_Noop, 0, 0, 0}, { OP_ResultRow, 1, 1, 0}, }; int addr; - if( sqlite3ReadSchema(pParse) ) goto pragma_out; sqlite3VdbeUsesBtree(v, iDb); if( !zRight ){ sqlite3VdbeSetNumCols(v, 1); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "cache_size", SQLITE_STATIC); pParse->nMem += 2; - addr = sqlite3VdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize); + addr = sqlite3VdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize,iLn); sqlite3VdbeChangeP1(v, addr, iDb); sqlite3VdbeChangeP1(v, addr+1, iDb); sqlite3VdbeChangeP1(v, addr+6, SQLITE_DEFAULT_CACHE_SIZE); }else{ int size = sqlite3AbsInt32(sqlite3Atoi(zRight)); @@ -95606,11 +103618,10 @@ ** ** Return the number of pages in the specified database. */ case PragTyp_PAGE_COUNT: { int iReg; - if( sqlite3ReadSchema(pParse) ) goto pragma_out; sqlite3CodeVerifySchema(pParse, iDb); iReg = ++pParse->nMem; if( sqlite3Tolower(zLeft[0])=='p' ){ sqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg); }else{ @@ -95679,18 +103690,10 @@ */ case PragTyp_JOURNAL_MODE: { int eMode; /* One of the PAGER_JOURNALMODE_XXX symbols */ int ii; /* Loop counter */ - /* Force the schema to be loaded on all databases. This causes all - ** database files to be opened and the journal_modes set. This is - ** necessary because subsequent processing must know if the databases - ** are in WAL mode. */ - if( sqlite3ReadSchema(pParse) ){ - goto pragma_out; - } - sqlite3VdbeSetNumCols(v, 1); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "journal_mode", SQLITE_STATIC); if( zRight==0 ){ /* If there is no "=MODE" part of the pragma, do a query for the @@ -95731,11 +103734,11 @@ */ case PragTyp_JOURNAL_SIZE_LIMIT: { Pager *pPager = sqlite3BtreePager(pDb->pBt); i64 iLimit = -2; if( zRight ){ - sqlite3Atoi64(zRight, &iLimit, sqlite3Strlen30(zRight), SQLITE_UTF8); + sqlite3DecOrHexToI64(zRight, &iLimit); if( iLimit<-1 ) iLimit = -1; } iLimit = sqlite3PagerJournalSizeLimit(pPager, iLimit); returnSingleInt(pParse, "journal_size_limit", iLimit); break; @@ -95752,55 +103755,45 @@ */ #ifndef SQLITE_OMIT_AUTOVACUUM case PragTyp_AUTO_VACUUM: { Btree *pBt = pDb->pBt; assert( pBt!=0 ); - if( sqlite3ReadSchema(pParse) ){ - goto pragma_out; - } if( !zRight ){ - int auto_vacuum; - if( ALWAYS(pBt) ){ - auto_vacuum = sqlite3BtreeGetAutoVacuum(pBt); - }else{ - auto_vacuum = SQLITE_DEFAULT_AUTOVACUUM; - } - returnSingleInt(pParse, "auto_vacuum", auto_vacuum); + returnSingleInt(pParse, "auto_vacuum", sqlite3BtreeGetAutoVacuum(pBt)); }else{ int eAuto = getAutoVacuum(zRight); assert( eAuto>=0 && eAuto<=2 ); db->nextAutovac = (u8)eAuto; - if( ALWAYS(eAuto>=0) ){ - /* Call SetAutoVacuum() to set initialize the internal auto and - ** incr-vacuum flags. This is required in case this connection - ** creates the database file. It is important that it is created - ** as an auto-vacuum capable db. - */ - rc = sqlite3BtreeSetAutoVacuum(pBt, eAuto); - if( rc==SQLITE_OK && (eAuto==1 || eAuto==2) ){ - /* When setting the auto_vacuum mode to either "full" or - ** "incremental", write the value of meta[6] in the database - ** file. Before writing to meta[6], check that meta[3] indicates - ** that this really is an auto-vacuum capable database. - */ - static const VdbeOpList setMeta6[] = { - { OP_Transaction, 0, 1, 0}, /* 0 */ - { OP_ReadCookie, 0, 1, BTREE_LARGEST_ROOT_PAGE}, - { OP_If, 1, 0, 0}, /* 2 */ - { OP_Halt, SQLITE_OK, OE_Abort, 0}, /* 3 */ - { OP_Integer, 0, 1, 0}, /* 4 */ - { OP_SetCookie, 0, BTREE_INCR_VACUUM, 1}, /* 5 */ - }; - int iAddr; - iAddr = sqlite3VdbeAddOpList(v, ArraySize(setMeta6), setMeta6); - sqlite3VdbeChangeP1(v, iAddr, iDb); - sqlite3VdbeChangeP1(v, iAddr+1, iDb); - sqlite3VdbeChangeP2(v, iAddr+2, iAddr+4); - sqlite3VdbeChangeP1(v, iAddr+4, eAuto-1); - sqlite3VdbeChangeP1(v, iAddr+5, iDb); - sqlite3VdbeUsesBtree(v, iDb); - } + /* Call SetAutoVacuum() to set initialize the internal auto and + ** incr-vacuum flags. This is required in case this connection + ** creates the database file. It is important that it is created + ** as an auto-vacuum capable db. + */ + rc = sqlite3BtreeSetAutoVacuum(pBt, eAuto); + if( rc==SQLITE_OK && (eAuto==1 || eAuto==2) ){ + /* When setting the auto_vacuum mode to either "full" or + ** "incremental", write the value of meta[6] in the database + ** file. Before writing to meta[6], check that meta[3] indicates + ** that this really is an auto-vacuum capable database. + */ + static const int iLn = VDBE_OFFSET_LINENO(2); + static const VdbeOpList setMeta6[] = { + { OP_Transaction, 0, 1, 0}, /* 0 */ + { OP_ReadCookie, 0, 1, BTREE_LARGEST_ROOT_PAGE}, + { OP_If, 1, 0, 0}, /* 2 */ + { OP_Halt, SQLITE_OK, OE_Abort, 0}, /* 3 */ + { OP_Integer, 0, 1, 0}, /* 4 */ + { OP_SetCookie, 0, BTREE_INCR_VACUUM, 1}, /* 5 */ + }; + int iAddr; + iAddr = sqlite3VdbeAddOpList(v, ArraySize(setMeta6), setMeta6, iLn); + sqlite3VdbeChangeP1(v, iAddr, iDb); + sqlite3VdbeChangeP1(v, iAddr+1, iDb); + sqlite3VdbeChangeP2(v, iAddr+2, iAddr+4); + sqlite3VdbeChangeP1(v, iAddr+4, eAuto-1); + sqlite3VdbeChangeP1(v, iAddr+5, iDb); + sqlite3VdbeUsesBtree(v, iDb); } } break; } #endif @@ -95811,22 +103804,19 @@ ** Do N steps of incremental vacuuming on a database. */ #ifndef SQLITE_OMIT_AUTOVACUUM case PragTyp_INCREMENTAL_VACUUM: { int iLimit, addr; - if( sqlite3ReadSchema(pParse) ){ - goto pragma_out; - } if( zRight==0 || !sqlite3GetInt32(zRight, &iLimit) || iLimit<=0 ){ iLimit = 0x7fffffff; } sqlite3BeginWriteOperation(pParse, 0, iDb); sqlite3VdbeAddOp2(v, OP_Integer, iLimit, 1); - addr = sqlite3VdbeAddOp1(v, OP_IncrVacuum, iDb); + addr = sqlite3VdbeAddOp1(v, OP_IncrVacuum, iDb); VdbeCoverage(v); sqlite3VdbeAddOp1(v, OP_ResultRow, 1); sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); - sqlite3VdbeAddOp2(v, OP_IfPos, 1, addr); + sqlite3VdbeAddOp2(v, OP_IfPos, 1, addr); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addr); break; } #endif @@ -95841,11 +103831,10 @@ ** number of pages in the cache. If N is negative, then the ** number of pages is adjusted so that the cache uses -N kibibytes ** of memory. */ case PragTyp_CACHE_SIZE: { - if( sqlite3ReadSchema(pParse) ) goto pragma_out; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( !zRight ){ returnSingleInt(pParse, "cache_size", pDb->pSchema->cache_size); }else{ int size = sqlite3Atoi(zRight); @@ -95873,11 +103862,11 @@ sqlite3_int64 sz; #if SQLITE_MAX_MMAP_SIZE>0 assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( zRight ){ int ii; - sqlite3Atoi64(zRight, &sz, sqlite3Strlen30(zRight), SQLITE_UTF8); + sqlite3DecOrHexToI64(zRight, &sz); if( sz<0 ) sz = sqlite3GlobalConfig.szMmap; if( pId2->n==0 ) db->szMmap = sz; for(ii=db->nDb-1; ii>=0; ii--){ if( db->aDb[ii].pBt && (ii==iDb || pId2->n==0) ){ sqlite3BtreeSetMmapLimit(db->aDb[ii].pBt, sz); @@ -96062,11 +104051,10 @@ ** the local value does not make changes to the disk file and the ** default value will be restored the next time the database is ** opened. */ case PragTyp_SYNCHRONOUS: { - if( sqlite3ReadSchema(pParse) ) goto pragma_out; if( !zRight ){ returnSingleInt(pParse, "synchronous", pDb->safety_level-1); }else{ if( !db->autoCommit ){ sqlite3ErrorMsg(pParse, @@ -96081,19 +104069,24 @@ #endif /* SQLITE_OMIT_PAGER_PRAGMAS */ #ifndef SQLITE_OMIT_FLAG_PRAGMAS case PragTyp_FLAG: { if( zRight==0 ){ - returnSingleInt(pParse, aPragmaNames[mid].zName, - (db->flags & aPragmaNames[mid].iArg)!=0 ); + returnSingleInt(pParse, pPragma->zName, (db->flags & pPragma->iArg)!=0 ); }else{ - int mask = aPragmaNames[mid].iArg; /* Mask of bits to set or clear. */ + int mask = pPragma->iArg; /* Mask of bits to set or clear. */ if( db->autoCommit==0 ){ /* Foreign key support may not be enabled or disabled while not ** in auto-commit mode. */ mask &= ~(SQLITE_ForeignKeys); } +#if SQLITE_USER_AUTHENTICATION + if( db->auth.authLevel==UAUTH_User ){ + /* Do not allow non-admin users to modify the schema arbitrarily */ + mask &= ~(SQLITE_WriteSchema); + } +#endif if( sqlite3GetBoolean(zRight, 0) ){ db->flags |= mask; }else{ db->flags &= ~mask; @@ -96124,18 +104117,16 @@ ** notnull: True if 'NOT NULL' is part of column declaration ** dflt_value: The default value for the column, if any. */ case PragTyp_TABLE_INFO: if( zRight ){ Table *pTab; - if( sqlite3ReadSchema(pParse) ) goto pragma_out; pTab = sqlite3FindTable(db, zRight, zDb); if( pTab ){ int i, k; int nHidden = 0; Column *pCol; - Index *pPk; - for(pPk=pTab->pIndex; pPk && pPk->autoIndex!=2; pPk=pPk->pNext){} + Index *pPk = sqlite3PrimaryKeyIndex(pTab); sqlite3VdbeSetNumCols(v, 6); pParse->nMem = 6; sqlite3CodeVerifySchema(pParse, iDb); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "cid", SQLITE_STATIC); sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "name", SQLITE_STATIC); @@ -96170,69 +104161,120 @@ sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6); } } } break; + + case PragTyp_STATS: { + Index *pIdx; + HashElem *i; + v = sqlite3GetVdbe(pParse); + sqlite3VdbeSetNumCols(v, 4); + pParse->nMem = 4; + sqlite3CodeVerifySchema(pParse, iDb); + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "table", SQLITE_STATIC); + sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "index", SQLITE_STATIC); + sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "width", SQLITE_STATIC); + sqlite3VdbeSetColName(v, 3, COLNAME_NAME, "height", SQLITE_STATIC); + for(i=sqliteHashFirst(&pDb->pSchema->tblHash); i; i=sqliteHashNext(i)){ + Table *pTab = sqliteHashData(i); + sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, pTab->zName, 0); + sqlite3VdbeAddOp2(v, OP_Null, 0, 2); + sqlite3VdbeAddOp2(v, OP_Integer, + (int)sqlite3LogEstToInt(pTab->szTabRow), 3); + sqlite3VdbeAddOp2(v, OP_Integer, + (int)sqlite3LogEstToInt(pTab->nRowLogEst), 4); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4); + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + sqlite3VdbeAddOp4(v, OP_String8, 0, 2, 0, pIdx->zName, 0); + sqlite3VdbeAddOp2(v, OP_Integer, + (int)sqlite3LogEstToInt(pIdx->szIdxRow), 3); + sqlite3VdbeAddOp2(v, OP_Integer, + (int)sqlite3LogEstToInt(pIdx->aiRowLogEst[0]), 4); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4); + } + } + } + break; case PragTyp_INDEX_INFO: if( zRight ){ Index *pIdx; Table *pTab; - if( sqlite3ReadSchema(pParse) ) goto pragma_out; pIdx = sqlite3FindIndex(db, zRight, zDb); if( pIdx ){ int i; + int mx; + if( pPragma->iArg ){ + /* PRAGMA index_xinfo (newer version with more rows and columns) */ + mx = pIdx->nColumn; + pParse->nMem = 6; + }else{ + /* PRAGMA index_info (legacy version) */ + mx = pIdx->nKeyCol; + pParse->nMem = 3; + } pTab = pIdx->pTable; - sqlite3VdbeSetNumCols(v, 3); - pParse->nMem = 3; + sqlite3VdbeSetNumCols(v, pParse->nMem); sqlite3CodeVerifySchema(pParse, iDb); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "seqno", SQLITE_STATIC); sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "cid", SQLITE_STATIC); sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "name", SQLITE_STATIC); - for(i=0; i<pIdx->nColumn; i++){ - int cnum = pIdx->aiColumn[i]; + if( pPragma->iArg ){ + sqlite3VdbeSetColName(v, 3, COLNAME_NAME, "desc", SQLITE_STATIC); + sqlite3VdbeSetColName(v, 4, COLNAME_NAME, "coll", SQLITE_STATIC); + sqlite3VdbeSetColName(v, 5, COLNAME_NAME, "key", SQLITE_STATIC); + } + for(i=0; i<mx; i++){ + i16 cnum = pIdx->aiColumn[i]; sqlite3VdbeAddOp2(v, OP_Integer, i, 1); sqlite3VdbeAddOp2(v, OP_Integer, cnum, 2); - assert( pTab->nCol>cnum ); - sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, pTab->aCol[cnum].zName, 0); - sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); + if( cnum<0 ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, 3); + }else{ + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, pTab->aCol[cnum].zName, 0); + } + if( pPragma->iArg ){ + sqlite3VdbeAddOp2(v, OP_Integer, pIdx->aSortOrder[i], 4); + sqlite3VdbeAddOp4(v, OP_String8, 0, 5, 0, pIdx->azColl[i], 0); + sqlite3VdbeAddOp2(v, OP_Integer, i<pIdx->nKeyCol, 6); + } + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, pParse->nMem); } } } break; case PragTyp_INDEX_LIST: if( zRight ){ Index *pIdx; Table *pTab; - if( sqlite3ReadSchema(pParse) ) goto pragma_out; + int i; pTab = sqlite3FindTable(db, zRight, zDb); if( pTab ){ v = sqlite3GetVdbe(pParse); - pIdx = pTab->pIndex; - if( pIdx ){ - int i = 0; - sqlite3VdbeSetNumCols(v, 3); - pParse->nMem = 3; - sqlite3CodeVerifySchema(pParse, iDb); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "seq", SQLITE_STATIC); - sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "name", SQLITE_STATIC); - sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "unique", SQLITE_STATIC); - while(pIdx){ - sqlite3VdbeAddOp2(v, OP_Integer, i, 1); - sqlite3VdbeAddOp4(v, OP_String8, 0, 2, 0, pIdx->zName, 0); - sqlite3VdbeAddOp2(v, OP_Integer, pIdx->onError!=OE_None, 3); - sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); - ++i; - pIdx = pIdx->pNext; - } + sqlite3VdbeSetNumCols(v, 5); + pParse->nMem = 5; + sqlite3CodeVerifySchema(pParse, iDb); + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "seq", SQLITE_STATIC); + sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "name", SQLITE_STATIC); + sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "unique", SQLITE_STATIC); + sqlite3VdbeSetColName(v, 3, COLNAME_NAME, "origin", SQLITE_STATIC); + sqlite3VdbeSetColName(v, 4, COLNAME_NAME, "partial", SQLITE_STATIC); + for(pIdx=pTab->pIndex, i=0; pIdx; pIdx=pIdx->pNext, i++){ + const char *azOrigin[] = { "c", "u", "pk" }; + sqlite3VdbeAddOp2(v, OP_Integer, i, 1); + sqlite3VdbeAddOp4(v, OP_String8, 0, 2, 0, pIdx->zName, 0); + sqlite3VdbeAddOp2(v, OP_Integer, IsUniqueIndex(pIdx), 3); + sqlite3VdbeAddOp4(v, OP_String8, 0, 4, 0, azOrigin[pIdx->idxType], 0); + sqlite3VdbeAddOp2(v, OP_Integer, pIdx->pPartIdxWhere!=0, 5); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 5); } } } break; case PragTyp_DATABASE_LIST: { int i; - if( sqlite3ReadSchema(pParse) ) goto pragma_out; sqlite3VdbeSetNumCols(v, 3); pParse->nMem = 3; sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "seq", SQLITE_STATIC); sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "name", SQLITE_STATIC); sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "file", SQLITE_STATIC); @@ -96267,11 +104309,10 @@ #ifndef SQLITE_OMIT_FOREIGN_KEY case PragTyp_FOREIGN_KEY_LIST: if( zRight ){ FKey *pFK; Table *pTab; - if( sqlite3ReadSchema(pParse) ) goto pragma_out; pTab = sqlite3FindTable(db, zRight, zDb); if( pTab ){ v = sqlite3GetVdbe(pParse); pFK = pTab->pFKey; if( pFK ){ @@ -96329,11 +104370,10 @@ int regRow; /* Registers to hold a row from pTab */ int addrTop; /* Top of a loop checking foreign keys */ int addrOk; /* Jump here if the key is OK */ int *aiCols; /* child to parent column mapping */ - if( sqlite3ReadSchema(pParse) ) goto pragma_out; regResult = pParse->nMem+1; pParse->nMem += 4; regKey = ++pParse->nMem; regRow = ++pParse->nMem; v = sqlite3GetVdbe(pParse); @@ -96357,74 +104397,77 @@ if( pTab->nCol+regRow>pParse->nMem ) pParse->nMem = pTab->nCol + regRow; sqlite3OpenTable(pParse, 0, iDb, pTab, OP_OpenRead); sqlite3VdbeAddOp4(v, OP_String8, 0, regResult, 0, pTab->zName, P4_TRANSIENT); for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){ - pParent = sqlite3LocateTable(pParse, 0, pFK->zTo, zDb); - if( pParent==0 ) break; + pParent = sqlite3FindTable(db, pFK->zTo, zDb); + if( pParent==0 ) continue; pIdx = 0; sqlite3TableLock(pParse, iDb, pParent->tnum, 0, pParent->zName); x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, 0); if( x==0 ){ if( pIdx==0 ){ sqlite3OpenTable(pParse, i, iDb, pParent, OP_OpenRead); }else{ - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); sqlite3VdbeAddOp3(v, OP_OpenRead, i, pIdx->tnum, iDb); - sqlite3VdbeChangeP4(v, -1, (char*)pKey, P4_KEYINFO_HANDOFF); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); } }else{ k = 0; break; } } + assert( pParse->nErr>0 || pFK==0 ); if( pFK ) break; if( pParse->nTab<i ) pParse->nTab = i; - addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, 0); + addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, 0); VdbeCoverage(v); for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){ - pParent = sqlite3LocateTable(pParse, 0, pFK->zTo, zDb); - assert( pParent!=0 ); + pParent = sqlite3FindTable(db, pFK->zTo, zDb); pIdx = 0; aiCols = 0; - x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, &aiCols); - assert( x==0 ); + if( pParent ){ + x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, &aiCols); + assert( x==0 ); + } addrOk = sqlite3VdbeMakeLabel(v); - if( pIdx==0 ){ + if( pParent && pIdx==0 ){ int iKey = pFK->aCol[0].iFrom; assert( iKey>=0 && iKey<pTab->nCol ); if( iKey!=pTab->iPKey ){ sqlite3VdbeAddOp3(v, OP_Column, 0, iKey, regRow); sqlite3ColumnDefault(v, pTab, iKey, regRow); - sqlite3VdbeAddOp2(v, OP_IsNull, regRow, addrOk); - sqlite3VdbeAddOp2(v, OP_MustBeInt, regRow, - sqlite3VdbeCurrentAddr(v)+3); + sqlite3VdbeAddOp2(v, OP_IsNull, regRow, addrOk); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_MustBeInt, regRow, + sqlite3VdbeCurrentAddr(v)+3); VdbeCoverage(v); }else{ sqlite3VdbeAddOp2(v, OP_Rowid, 0, regRow); } - sqlite3VdbeAddOp3(v, OP_NotExists, i, 0, regRow); + sqlite3VdbeAddOp3(v, OP_NotExists, i, 0, regRow); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Goto, 0, addrOk); sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2); }else{ for(j=0; j<pFK->nCol; j++){ sqlite3ExprCodeGetColumnOfTable(v, pTab, 0, - aiCols ? aiCols[j] : pFK->aCol[0].iFrom, regRow+j); - sqlite3VdbeAddOp2(v, OP_IsNull, regRow+j, addrOk); + aiCols ? aiCols[j] : pFK->aCol[j].iFrom, regRow+j); + sqlite3VdbeAddOp2(v, OP_IsNull, regRow+j, addrOk); VdbeCoverage(v); } - sqlite3VdbeAddOp3(v, OP_MakeRecord, regRow, pFK->nCol, regKey); - sqlite3VdbeChangeP4(v, -1, - sqlite3IndexAffinityStr(v,pIdx), P4_TRANSIENT); - sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regKey, 0); + if( pParent ){ + sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, pFK->nCol, regKey, + sqlite3IndexAffinityStr(v,pIdx), pFK->nCol); + sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regKey, 0); + VdbeCoverage(v); + } } sqlite3VdbeAddOp2(v, OP_Rowid, 0, regResult+1); sqlite3VdbeAddOp4(v, OP_String8, 0, regResult+2, 0, pFK->zTo, P4_TRANSIENT); sqlite3VdbeAddOp2(v, OP_Integer, i-1, regResult+3); sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, 4); sqlite3VdbeResolveLabel(v, addrOk); sqlite3DbFree(db, aiCols); } - sqlite3VdbeAddOp2(v, OP_Next, 0, addrTop+1); + sqlite3VdbeAddOp2(v, OP_Next, 0, addrTop+1); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addrTop); } } break; #endif /* !defined(SQLITE_OMIT_TRIGGER) */ @@ -96467,14 +104510,14 @@ /* Code that appears at the end of the integrity check. If no error ** messages have been generated, output OK. Otherwise output the ** error message */ + static const int iLn = VDBE_OFFSET_LINENO(2); static const VdbeOpList endCode[] = { - { OP_AddImm, 1, 0, 0}, /* 0 */ - { OP_IfNeg, 1, 0, 0}, /* 1 */ - { OP_String8, 0, 3, 0}, /* 2 */ + { OP_IfNeg, 1, 0, 0}, /* 0 */ + { OP_String8, 0, 3, 0}, /* 1 */ { OP_ResultRow, 3, 1, 0}, }; int isQuick = (sqlite3Tolower(zLeft[0])=='q'); @@ -96490,11 +104533,10 @@ assert( iDb>=0 ); assert( iDb==0 || pId2->z ); if( pId2->z==0 ) iDb = -1; /* Initialize the VDBE program */ - if( sqlite3ReadSchema(pParse) ) goto pragma_out; pParse->nMem = 6; sqlite3VdbeSetNumCols(v, 1); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "integrity_check", SQLITE_STATIC); /* Set the maximum error count */ @@ -96516,10 +104558,11 @@ if( OMIT_TEMPDB && i==1 ) continue; if( iDb>=0 && i!=iDb ) continue; sqlite3CodeVerifySchema(pParse, i); addr = sqlite3VdbeAddOp1(v, OP_IfPos, 1); /* Halt if out of errors */ + VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Halt, 0, 0); sqlite3VdbeJumpHere(v, addr); /* Do an integrity check of the B-Tree ** @@ -96529,101 +104572,160 @@ assert( sqlite3SchemaMutexHeld(db, i, 0) ); pTbls = &db->aDb[i].pSchema->tblHash; for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ Table *pTab = sqliteHashData(x); Index *pIdx; - sqlite3VdbeAddOp2(v, OP_Integer, pTab->tnum, 2+cnt); - cnt++; + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_Integer, pTab->tnum, 2+cnt); + VdbeComment((v, "%s", pTab->zName)); + cnt++; + } for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ sqlite3VdbeAddOp2(v, OP_Integer, pIdx->tnum, 2+cnt); + VdbeComment((v, "%s", pIdx->zName)); cnt++; } } /* Make sure sufficient number of registers have been allocated */ - pParse->nMem = MAX( pParse->nMem, cnt+7 ); + pParse->nMem = MAX( pParse->nMem, cnt+8 ); /* Do the b-tree integrity checks */ sqlite3VdbeAddOp3(v, OP_IntegrityCk, 2, cnt, 1); sqlite3VdbeChangeP5(v, (u8)i); - addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); + addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zName), P4_DYNAMIC); - sqlite3VdbeAddOp2(v, OP_Move, 2, 4); + sqlite3VdbeAddOp3(v, OP_Move, 2, 4, 1); sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 2); sqlite3VdbeAddOp2(v, OP_ResultRow, 2, 1); sqlite3VdbeJumpHere(v, addr); /* Make sure all the indices are constructed correctly. */ for(x=sqliteHashFirst(pTbls); x && !isQuick; x=sqliteHashNext(x)){ Table *pTab = sqliteHashData(x); - Index *pIdx; + Index *pIdx, *pPk; + Index *pPrior = 0; int loopTop; + int iDataCur, iIdxCur; + int r1 = -1; if( pTab->pIndex==0 ) continue; + pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); addr = sqlite3VdbeAddOp1(v, OP_IfPos, 1); /* Stop if out of errors */ + VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Halt, 0, 0); sqlite3VdbeJumpHere(v, addr); sqlite3ExprCacheClear(pParse); - sqlite3OpenTableAndIndices(pParse, pTab, 1, OP_OpenRead); + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, + 1, 0, &iDataCur, &iIdxCur); + sqlite3VdbeAddOp2(v, OP_Integer, 0, 7); for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ - sqlite3VdbeAddOp2(v, OP_Integer, 0, 7+j); /* index entries counter */ + sqlite3VdbeAddOp2(v, OP_Integer, 0, 8+j); /* index entries counter */ } - pParse->nMem = MAX(pParse->nMem, 7+j); - loopTop = sqlite3VdbeAddOp2(v, OP_Rewind, 1, 0) + 1; - for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + pParse->nMem = MAX(pParse->nMem, 8+j); + sqlite3VdbeAddOp2(v, OP_Rewind, iDataCur, 0); VdbeCoverage(v); + loopTop = sqlite3VdbeAddOp2(v, OP_AddImm, 7, 1); + /* Verify that all NOT NULL columns really are NOT NULL */ + for(j=0; j<pTab->nCol; j++){ + char *zErr; int jmp2, jmp3; - int r1; - static const VdbeOpList idxErr[] = { - { OP_AddImm, 1, -1, 0}, - { OP_String8, 0, 3, 0}, /* 1 */ - { OP_Rowid, 1, 4, 0}, - { OP_String8, 0, 5, 0}, /* 3 */ - { OP_String8, 0, 6, 0}, /* 4 */ - { OP_Concat, 4, 3, 3}, - { OP_Concat, 5, 3, 3}, - { OP_Concat, 6, 3, 3}, - { OP_ResultRow, 3, 1, 0}, - { OP_IfPos, 1, 0, 0}, /* 9 */ - { OP_Halt, 0, 0, 0}, - }; - r1 = sqlite3GenerateIndexKey(pParse, pIdx, 1, 3, 0, &jmp3); - sqlite3VdbeAddOp2(v, OP_AddImm, 7+j, 1); /* increment entry count */ - jmp2 = sqlite3VdbeAddOp4Int(v, OP_Found, j+2, 0, r1, pIdx->nColumn+1); - addr = sqlite3VdbeAddOpList(v, ArraySize(idxErr), idxErr); - sqlite3VdbeChangeP4(v, addr+1, "rowid ", P4_STATIC); - sqlite3VdbeChangeP4(v, addr+3, " missing from index ", P4_STATIC); - sqlite3VdbeChangeP4(v, addr+4, pIdx->zName, P4_TRANSIENT); - sqlite3VdbeJumpHere(v, addr+9); + if( j==pTab->iPKey ) continue; + if( pTab->aCol[j].notNull==0 ) continue; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); + sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); + jmp2 = sqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */ + zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, + pTab->aCol[j].zName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + sqlite3VdbeAddOp2(v, OP_ResultRow, 3, 1); + jmp3 = sqlite3VdbeAddOp1(v, OP_IfPos, 1); VdbeCoverage(v); + sqlite3VdbeAddOp0(v, OP_Halt); + sqlite3VdbeJumpHere(v, jmp2); + sqlite3VdbeJumpHere(v, jmp3); + } + /* Validate index entries for the current row */ + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + int jmp2, jmp3, jmp4, jmp5; + int ckUniq = sqlite3VdbeMakeLabel(v); + if( pPk==pIdx ) continue; + r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 0, &jmp3, + pPrior, r1); + pPrior = pIdx; + sqlite3VdbeAddOp2(v, OP_AddImm, 8+j, 1); /* increment entry count */ + /* Verify that an index entry exists for the current table row */ + jmp2 = sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, ckUniq, r1, + pIdx->nColumn); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */ + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, "row ", P4_STATIC); + sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); + sqlite3VdbeAddOp4(v, OP_String8, 0, 4, 0, + " missing from index ", P4_STATIC); + sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); + jmp5 = sqlite3VdbeAddOp4(v, OP_String8, 0, 4, 0, + pIdx->zName, P4_TRANSIENT); + sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); + sqlite3VdbeAddOp2(v, OP_ResultRow, 3, 1); + jmp4 = sqlite3VdbeAddOp1(v, OP_IfPos, 1); VdbeCoverage(v); + sqlite3VdbeAddOp0(v, OP_Halt); sqlite3VdbeJumpHere(v, jmp2); - sqlite3VdbeResolveLabel(v, jmp3); + /* For UNIQUE indexes, verify that only one entry exists with the + ** current key. The entry is unique if (1) any column is NULL + ** or (2) the next entry has a different key */ + if( IsUniqueIndex(pIdx) ){ + int uniqOk = sqlite3VdbeMakeLabel(v); + int jmp6; + int kk; + for(kk=0; kk<pIdx->nKeyCol; kk++){ + int iCol = pIdx->aiColumn[kk]; + assert( iCol>=0 && iCol<pTab->nCol ); + if( pTab->aCol[iCol].notNull ) continue; + sqlite3VdbeAddOp2(v, OP_IsNull, r1+kk, uniqOk); + VdbeCoverage(v); + } + jmp6 = sqlite3VdbeAddOp1(v, OP_Next, iIdxCur+j); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Goto, 0, uniqOk); + sqlite3VdbeJumpHere(v, jmp6); + sqlite3VdbeAddOp4Int(v, OP_IdxGT, iIdxCur+j, uniqOk, r1, + pIdx->nKeyCol); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */ + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, + "non-unique entry in index ", P4_STATIC); + sqlite3VdbeAddOp2(v, OP_Goto, 0, jmp5); + sqlite3VdbeResolveLabel(v, uniqOk); + } + sqlite3VdbeJumpHere(v, jmp4); + sqlite3ResolvePartIdxLabel(pParse, jmp3); } - sqlite3VdbeAddOp2(v, OP_Next, 1, loopTop); + sqlite3VdbeAddOp2(v, OP_Next, iDataCur, loopTop); VdbeCoverage(v); sqlite3VdbeJumpHere(v, loopTop-1); #ifndef SQLITE_OMIT_BTREECOUNT sqlite3VdbeAddOp4(v, OP_String8, 0, 2, 0, "wrong # of entries in index ", P4_STATIC); for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + if( pPk==pIdx ) continue; addr = sqlite3VdbeCurrentAddr(v); - sqlite3VdbeAddOp2(v, OP_IfPos, 1, addr+2); + sqlite3VdbeAddOp2(v, OP_IfPos, 1, addr+2); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Halt, 0, 0); - sqlite3VdbeAddOp2(v, OP_Count, j+2, 3); - sqlite3VdbeAddOp3(v, OP_Eq, 7+j, addr+8, 3); + sqlite3VdbeAddOp2(v, OP_Count, iIdxCur+j, 3); + sqlite3VdbeAddOp3(v, OP_Eq, 8+j, addr+8, 3); VdbeCoverage(v); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, pIdx->zName, P4_TRANSIENT); sqlite3VdbeAddOp3(v, OP_Concat, 3, 2, 7); sqlite3VdbeAddOp2(v, OP_ResultRow, 7, 1); } #endif /* SQLITE_OMIT_BTREECOUNT */ } } - addr = sqlite3VdbeAddOpList(v, ArraySize(endCode), endCode); - sqlite3VdbeChangeP2(v, addr, -mxErr); - sqlite3VdbeJumpHere(v, addr+1); - sqlite3VdbeChangeP4(v, addr+2, "ok", P4_STATIC); + addr = sqlite3VdbeAddOpList(v, ArraySize(endCode), endCode, iLn); + sqlite3VdbeChangeP3(v, addr, -mxErr); + sqlite3VdbeJumpHere(v, addr); + sqlite3VdbeChangeP4(v, addr+1, "ok", P4_STATIC); } break; #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ #ifndef SQLITE_OMIT_UTF16 @@ -96685,11 +104787,12 @@ !(DbHasProperty(db, 0, DB_SchemaLoaded)) || DbHasProperty(db, 0, DB_Empty) ){ for(pEnc=&encnames[0]; pEnc->zName; pEnc++){ if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){ - ENC(pParse->db) = pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE; + SCHEMA_ENC(db) = ENC(db) = + pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE; break; } } if( !pEnc->zName ){ sqlite3ErrorMsg(pParse, "unsupported encoding: %s", zRight); @@ -96730,35 +104833,20 @@ ** ** The user-version is not used internally by SQLite. It may be used by ** applications for any purpose. */ case PragTyp_HEADER_VALUE: { - int iCookie; /* Cookie index. 1 for schema-cookie, 6 for user-cookie. */ + int iCookie = pPragma->iArg; /* Which cookie to read or write */ sqlite3VdbeUsesBtree(v, iDb); - switch( zLeft[0] ){ - case 'a': case 'A': - iCookie = BTREE_APPLICATION_ID; - break; - case 'f': case 'F': - iCookie = BTREE_FREE_PAGE_COUNT; - break; - case 's': case 'S': - iCookie = BTREE_SCHEMA_VERSION; - break; - default: - iCookie = BTREE_USER_VERSION; - break; - } - - if( zRight && iCookie!=BTREE_FREE_PAGE_COUNT ){ + if( zRight && (pPragma->mPragFlag & PragFlag_ReadOnly)==0 ){ /* Write the specified cookie value */ static const VdbeOpList setCookie[] = { { OP_Transaction, 0, 1, 0}, /* 0 */ { OP_Integer, 0, 1, 0}, /* 1 */ { OP_SetCookie, 0, 0, 1}, /* 2 */ }; - int addr = sqlite3VdbeAddOpList(v, ArraySize(setCookie), setCookie); + int addr = sqlite3VdbeAddOpList(v, ArraySize(setCookie), setCookie, 0); sqlite3VdbeChangeP1(v, addr, iDb); sqlite3VdbeChangeP1(v, addr+1, sqlite3Atoi(zRight)); sqlite3VdbeChangeP1(v, addr+2, iDb); sqlite3VdbeChangeP2(v, addr+2, iCookie); }else{ @@ -96766,11 +104854,11 @@ static const VdbeOpList readCookie[] = { { OP_Transaction, 0, 0, 0}, /* 0 */ { OP_ReadCookie, 0, 1, 0}, /* 1 */ { OP_ResultRow, 1, 1, 0} }; - int addr = sqlite3VdbeAddOpList(v, ArraySize(readCookie), readCookie); + int addr = sqlite3VdbeAddOpList(v, ArraySize(readCookie), readCookie, 0); sqlite3VdbeChangeP1(v, addr, iDb); sqlite3VdbeChangeP1(v, addr+1, iDb); sqlite3VdbeChangeP3(v, addr+1, iCookie); sqlite3VdbeSetNumCols(v, 1); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLeft, SQLITE_TRANSIENT); @@ -96800,11 +104888,11 @@ break; #endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ #ifndef SQLITE_OMIT_WAL /* - ** PRAGMA [database.]wal_checkpoint = passive|full|restart + ** PRAGMA [database.]wal_checkpoint = passive|full|restart|truncate ** ** Checkpoint the database. */ case PragTyp_WAL_CHECKPOINT: { int iBt = (pId2->z?iDb:SQLITE_MAX_ATTACHED); @@ -96812,13 +104900,14 @@ if( zRight ){ if( sqlite3StrICmp(zRight, "full")==0 ){ eMode = SQLITE_CHECKPOINT_FULL; }else if( sqlite3StrICmp(zRight, "restart")==0 ){ eMode = SQLITE_CHECKPOINT_RESTART; + }else if( sqlite3StrICmp(zRight, "truncate")==0 ){ + eMode = SQLITE_CHECKPOINT_TRUNCATE; } } - if( sqlite3ReadSchema(pParse) ) goto pragma_out; sqlite3VdbeSetNumCols(v, 3); pParse->nMem = 3; sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "busy", SQLITE_STATIC); sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "log", SQLITE_STATIC); sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "checkpointed", SQLITE_STATIC); @@ -96848,12 +104937,13 @@ #endif /* ** PRAGMA shrink_memory ** - ** This pragma attempts to free as much memory as possible from the - ** current database connection. + ** IMPLEMENTATION-OF: R-23445-46109 This pragma causes the database + ** connection on which it is invoked to free up as much memory as it + ** can, by calling sqlite3_db_release_memory(). */ case PragTyp_SHRINK_MEMORY: { sqlite3_db_release_memory(db); break; } @@ -96866,11 +104956,11 @@ ** if one is set. If no busy handler or a different busy handler is set ** then 0 is returned. Setting the busy_timeout to 0 or negative ** disables the timeout. */ /*case PragTyp_BUSY_TIMEOUT*/ default: { - assert( aPragmaNames[mid].ePragTyp==PragTyp_BUSY_TIMEOUT ); + assert( pPragma->ePragTyp==PragTyp_BUSY_TIMEOUT ); if( zRight ){ sqlite3_busy_timeout(db, sqlite3Atoi(zRight)); } returnSingleInt(pParse, "timeout", db->busyTimeout); break; @@ -96878,21 +104968,45 @@ /* ** PRAGMA soft_heap_limit ** PRAGMA soft_heap_limit = N ** - ** Call sqlite3_soft_heap_limit64(N). Return the result. If N is omitted, - ** use -1. + ** IMPLEMENTATION-OF: R-26343-45930 This pragma invokes the + ** sqlite3_soft_heap_limit64() interface with the argument N, if N is + ** specified and is a non-negative integer. + ** IMPLEMENTATION-OF: R-64451-07163 The soft_heap_limit pragma always + ** returns the same integer that would be returned by the + ** sqlite3_soft_heap_limit64(-1) C-language function. */ case PragTyp_SOFT_HEAP_LIMIT: { sqlite3_int64 N; - if( zRight && sqlite3Atoi64(zRight, &N, 1000000, SQLITE_UTF8)==SQLITE_OK ){ + if( zRight && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK ){ sqlite3_soft_heap_limit64(N); } returnSingleInt(pParse, "soft_heap_limit", sqlite3_soft_heap_limit64(-1)); break; } + + /* + ** PRAGMA threads + ** PRAGMA threads = N + ** + ** Configure the maximum number of worker threads. Return the new + ** maximum, which might be less than requested. + */ + case PragTyp_THREADS: { + sqlite3_int64 N; + if( zRight + && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK + && N>=0 + ){ + sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, (int)(N&0x7fffffff)); + } + returnSingleInt(pParse, "threads", + sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, -1)); + break; + } #if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) /* ** Report the current state of file logs for all databases */ @@ -96934,16 +105048,16 @@ if( zRight ) sqlite3_rekey_v2(db, zDb, zRight, sqlite3Strlen30(zRight)); break; } case PragTyp_HEXKEY: { if( zRight ){ - int i, h1, h2; + u8 iByte; + int i; char zKey[40]; - for(i=0; (h1 = zRight[i])!=0 && (h2 = zRight[i+1])!=0; i+=2){ - h1 += 9*(1&(h1>>6)); - h2 += 9*(1&(h2>>6)); - zKey[i/2] = (h2 & 0x0f) | ((h1 & 0xf)<<4); + for(i=0, iByte=0; i<sizeof(zKey)*2 && sqlite3Isxdigit(zRight[i]); i++){ + iByte = (iByte<<4) + sqlite3HexToInt(zRight[i]); + if( (i&1)!=0 ) zKey[i/2] = iByte; } if( (zLeft[3] & 0xf)==0xb ){ sqlite3_key_v2(db, zDb, zKey, i/2); }else{ sqlite3_rekey_v2(db, zDb, zKey, i/2); @@ -97306,11 +105420,11 @@ zSql = sqlite3MPrintf(db, "SELECT name, rootpage, sql FROM '%q'.%s ORDER BY rowid", db->aDb[iDb].zName, zMasterName); #ifndef SQLITE_OMIT_AUTHORIZATION { - int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); + sqlite3_xauth xAuth; xAuth = db->xAuth; db->xAuth = 0; #endif rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0); #ifndef SQLITE_OMIT_AUTHORIZATION @@ -97372,12 +105486,15 @@ SQLITE_PRIVATE int sqlite3Init(sqlite3 *db, char **pzErrMsg){ int i, rc; int commit_internal = !(db->flags&SQLITE_InternChanges); assert( sqlite3_mutex_held(db->mutex) ); + assert( sqlite3BtreeHoldsMutex(db->aDb[0].pBt) ); + assert( db->init.busy==0 ); rc = SQLITE_OK; db->init.busy = 1; + ENC(db) = SCHEMA_ENC(db); for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ if( DbHasProperty(db, i, DB_SchemaLoaded) || i==1 ) continue; rc = sqlite3InitOne(db, i, pzErrMsg); if( rc ){ sqlite3ResetOneSchema(db, i); @@ -97387,12 +105504,12 @@ /* Once all the other databases have been initialized, load the schema ** for the TEMP database. This is loaded last, as the TEMP database ** schema may contain references to objects in other databases. */ #ifndef SQLITE_OMIT_TEMPDB - if( rc==SQLITE_OK && ALWAYS(db->nDb>1) - && !DbHasProperty(db, 1, DB_SchemaLoaded) ){ + assert( db->nDb>1 ); + if( rc==SQLITE_OK && !DbHasProperty(db, 1, DB_SchemaLoaded) ){ rc = sqlite3InitOne(db, 1, pzErrMsg); if( rc ){ sqlite3ResetOneSchema(db, 1); } } @@ -97501,10 +105618,21 @@ } assert( i>=0 && i<db->nDb ); } return i; } + +/* +** Free all memory allocations in the pParse object +*/ +SQLITE_PRIVATE void sqlite3ParserReset(Parse *pParse){ + if( pParse ){ + sqlite3 *db = pParse->db; + sqlite3DbFree(db, pParse->aLabel); + sqlite3ExprListDelete(db, pParse->pConstExpr); + } +} /* ** Compile the UTF-8 encoded SQL statement zSql into a statement handle. */ static int sqlite3Prepare( @@ -97560,11 +105688,11 @@ if( pBt ){ assert( sqlite3BtreeHoldsMutex(pBt) ); rc = sqlite3BtreeSchemaLocked(pBt); if( rc ){ const char *zDb = db->aDb[i].zName; - sqlite3Error(db, rc, "database schema is locked: %s", zDb); + sqlite3ErrorWithMsg(db, rc, "database schema is locked: %s", zDb); testcase( db->flags & SQLITE_ReadUncommitted ); goto end_prepare; } } } @@ -97577,11 +105705,11 @@ char *zSqlCopy; int mxLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; testcase( nBytes==mxLen ); testcase( nBytes==mxLen+1 ); if( nBytes>mxLen ){ - sqlite3Error(db, SQLITE_TOOBIG, "statement too long"); + sqlite3ErrorWithMsg(db, SQLITE_TOOBIG, "statement too long"); rc = sqlite3ApiExit(db, SQLITE_TOOBIG); goto end_prepare; } zSqlCopy = sqlite3DbStrNDup(db, zSql, nBytes); if( zSqlCopy ){ @@ -97644,14 +105772,14 @@ }else{ *ppStmt = (sqlite3_stmt*)pParse->pVdbe; } if( zErrMsg ){ - sqlite3Error(db, rc, "%s", zErrMsg); + sqlite3ErrorWithMsg(db, rc, "%s", zErrMsg); sqlite3DbFree(db, zErrMsg); }else{ - sqlite3Error(db, rc, 0); + sqlite3Error(db, rc); } /* Delete any TriggerPrg structures allocated while parsing this statement. */ while( pParse->pTriggerPrg ){ TriggerPrg *pT = pParse->pTriggerPrg; @@ -97659,10 +105787,11 @@ sqlite3DbFree(db, pT); } end_prepare: + sqlite3ParserReset(pParse); sqlite3StackFree(db, pParse); rc = sqlite3ApiExit(db, rc); assert( (rc&db->errMask)==rc ); return rc; } @@ -97674,13 +105803,16 @@ Vdbe *pOld, /* VM being reprepared */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const char **pzTail /* OUT: End of parsed string */ ){ int rc; - assert( ppStmt!=0 ); + +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppStmt==0 ) return SQLITE_MISUSE_BKPT; +#endif *ppStmt = 0; - if( !sqlite3SafetyCheckOk(db) ){ + if( !sqlite3SafetyCheckOk(db)||zSql==0 ){ return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(db->mutex); sqlite3BtreeEnterAll(db); rc = sqlite3Prepare(db, zSql, nBytes, saveSqlFlag, pOld, ppStmt, pzTail); @@ -97737,11 +105869,11 @@ ** and so if a schema change occurs, SQLITE_SCHEMA is returned by ** sqlite3_step(). In the new version, the original SQL text is retained ** and the statement is automatically recompiled if an schema change ** occurs. */ -SQLITE_API int sqlite3_prepare( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare( sqlite3 *db, /* Database handle. */ const char *zSql, /* UTF-8 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const char **pzTail /* OUT: End of parsed string */ @@ -97749,11 +105881,11 @@ int rc; rc = sqlite3LockAndPrepare(db,zSql,nBytes,0,0,ppStmt,pzTail); assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */ return rc; } -SQLITE_API int sqlite3_prepare_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare_v2( sqlite3 *db, /* Database handle. */ const char *zSql, /* UTF-8 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const char **pzTail /* OUT: End of parsed string */ @@ -97783,13 +105915,15 @@ */ char *zSql8; const char *zTail8 = 0; int rc = SQLITE_OK; - assert( ppStmt ); +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppStmt==0 ) return SQLITE_MISUSE_BKPT; +#endif *ppStmt = 0; - if( !sqlite3SafetyCheckOk(db) ){ + if( !sqlite3SafetyCheckOk(db)||zSql==0 ){ return SQLITE_MISUSE_BKPT; } if( nBytes>=0 ){ int sz; const char *z = (const char*)zSql; @@ -97823,11 +105957,11 @@ ** and so if a schema change occurs, SQLITE_SCHEMA is returned by ** sqlite3_step(). In the new version, the original SQL text is retained ** and the statement is automatically recompiled if an schema change ** occurs. */ -SQLITE_API int sqlite3_prepare16( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare16( sqlite3 *db, /* Database handle. */ const void *zSql, /* UTF-16 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const void **pzTail /* OUT: End of parsed string */ @@ -97835,11 +105969,11 @@ int rc; rc = sqlite3Prepare16(db,zSql,nBytes,0,ppStmt,pzTail); assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */ return rc; } -SQLITE_API int sqlite3_prepare16_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare16_v2( sqlite3 *db, /* Database handle. */ const void *zSql, /* UTF-16 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const void **pzTail /* OUT: End of parsed string */ @@ -97867,25 +106001,73 @@ ************************************************************************* ** This file contains C code routines that are called by the parser ** to handle SELECT statements in SQLite. */ +/* +** Trace output macros +*/ +#if SELECTTRACE_ENABLED +/***/ int sqlite3SelectTrace = 0; +# define SELECTTRACE(K,P,S,X) \ + if(sqlite3SelectTrace&(K)) \ + sqlite3DebugPrintf("%*s%s.%p: ",(P)->nSelectIndent*2-2,"",(S)->zSelName,(S)),\ + sqlite3DebugPrintf X +#else +# define SELECTTRACE(K,P,S,X) +#endif + /* -** Delete all the content of a Select structure but do not deallocate -** the select structure itself. +** An instance of the following object is used to record information about +** how to process the DISTINCT keyword, to simplify passing that information +** into the selectInnerLoop() routine. */ -static void clearSelect(sqlite3 *db, Select *p){ - sqlite3ExprListDelete(db, p->pEList); - sqlite3SrcListDelete(db, p->pSrc); - sqlite3ExprDelete(db, p->pWhere); - sqlite3ExprListDelete(db, p->pGroupBy); - sqlite3ExprDelete(db, p->pHaving); - sqlite3ExprListDelete(db, p->pOrderBy); - sqlite3SelectDelete(db, p->pPrior); - sqlite3ExprDelete(db, p->pLimit); - sqlite3ExprDelete(db, p->pOffset); +typedef struct DistinctCtx DistinctCtx; +struct DistinctCtx { + u8 isTnct; /* True if the DISTINCT keyword is present */ + u8 eTnctType; /* One of the WHERE_DISTINCT_* operators */ + int tabTnct; /* Ephemeral table used for DISTINCT processing */ + int addrTnct; /* Address of OP_OpenEphemeral opcode for tabTnct */ +}; + +/* +** An instance of the following object is used to record information about +** the ORDER BY (or GROUP BY) clause of query is being coded. +*/ +typedef struct SortCtx SortCtx; +struct SortCtx { + ExprList *pOrderBy; /* The ORDER BY (or GROUP BY clause) */ + int nOBSat; /* Number of ORDER BY terms satisfied by indices */ + int iECursor; /* Cursor number for the sorter */ + int regReturn; /* Register holding block-output return address */ + int labelBkOut; /* Start label for the block-output subroutine */ + int addrSortIndex; /* Address of the OP_SorterOpen or OP_OpenEphemeral */ + u8 sortFlags; /* Zero or more SORTFLAG_* bits */ +}; +#define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */ + +/* +** Delete all the content of a Select structure. Deallocate the structure +** itself only if bFree is true. +*/ +static void clearSelect(sqlite3 *db, Select *p, int bFree){ + while( p ){ + Select *pPrior = p->pPrior; + sqlite3ExprListDelete(db, p->pEList); + sqlite3SrcListDelete(db, p->pSrc); + sqlite3ExprDelete(db, p->pWhere); + sqlite3ExprListDelete(db, p->pGroupBy); + sqlite3ExprDelete(db, p->pHaving); + sqlite3ExprListDelete(db, p->pOrderBy); + sqlite3ExprDelete(db, p->pLimit); + sqlite3ExprDelete(db, p->pOffset); + sqlite3WithDelete(db, p->pWith); + if( bFree ) sqlite3DbFree(db, p); + p = pPrior; + bFree = 1; + } } /* ** Initialize a SelectDest structure. */ @@ -97939,30 +106121,45 @@ pNew->pLimit = pLimit; pNew->pOffset = pOffset; assert( pOffset==0 || pLimit!=0 ); pNew->addrOpenEphm[0] = -1; pNew->addrOpenEphm[1] = -1; - pNew->addrOpenEphm[2] = -1; if( db->mallocFailed ) { - clearSelect(db, pNew); - if( pNew!=&standin ) sqlite3DbFree(db, pNew); + clearSelect(db, pNew, pNew!=&standin); pNew = 0; }else{ assert( pNew->pSrc!=0 || pParse->nErr>0 ); } assert( pNew!=&standin ); return pNew; } + +#if SELECTTRACE_ENABLED +/* +** Set the name of a Select object +*/ +SQLITE_PRIVATE void sqlite3SelectSetName(Select *p, const char *zName){ + if( p && zName ){ + sqlite3_snprintf(sizeof(p->zSelName), p->zSelName, "%s", zName); + } +} +#endif + /* ** Delete the given Select structure and all of its substructures. */ SQLITE_PRIVATE void sqlite3SelectDelete(sqlite3 *db, Select *p){ - if( p ){ - clearSelect(db, p); - sqlite3DbFree(db, p); - } + clearSelect(db, p, 1); +} + +/* +** Return a pointer to the right-most SELECT statement in a compound. +*/ +static Select *findRightmost(Select *p){ + while( p->pNext ) p = p->pNext; + return p; } /* ** Given 1 to 3 identifiers preceding the JOIN keyword, determine the ** type of join. Return an integer constant that expresses that type @@ -98263,68 +106460,125 @@ } } return 0; } +/* Forward reference */ +static KeyInfo *keyInfoFromExprList( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* Form the KeyInfo object from this ExprList */ + int iStart, /* Begin with this column of pList */ + int nExtra /* Add this many extra columns to the end */ +); + /* -** Insert code into "v" that will push the record on the top of the -** stack into the sorter. +** Generate code that will push the record in registers regData +** through regData+nData-1 onto the sorter. */ static void pushOntoSorter( Parse *pParse, /* Parser context */ - ExprList *pOrderBy, /* The ORDER BY clause */ + SortCtx *pSort, /* Information about the ORDER BY clause */ Select *pSelect, /* The whole SELECT statement */ - int regData /* Register holding data to be sorted */ + int regData, /* First register holding data to be sorted */ + int nData, /* Number of elements in the data array */ + int nPrefixReg /* No. of reg prior to regData available for use */ ){ - Vdbe *v = pParse->pVdbe; - int nExpr = pOrderBy->nExpr; - int regBase = sqlite3GetTempRange(pParse, nExpr+2); - int regRecord = sqlite3GetTempReg(pParse); - int op; - sqlite3ExprCacheClear(pParse); - sqlite3ExprCodeExprList(pParse, pOrderBy, regBase, 0); - sqlite3VdbeAddOp2(v, OP_Sequence, pOrderBy->iECursor, regBase+nExpr); - sqlite3ExprCodeMove(pParse, regData, regBase+nExpr+1, 1); - sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nExpr + 2, regRecord); - if( pSelect->selFlags & SF_UseSorter ){ + Vdbe *v = pParse->pVdbe; /* Stmt under construction */ + int bSeq = ((pSort->sortFlags & SORTFLAG_UseSorter)==0); + int nExpr = pSort->pOrderBy->nExpr; /* No. of ORDER BY terms */ + int nBase = nExpr + bSeq + nData; /* Fields in sorter record */ + int regBase; /* Regs for sorter record */ + int regRecord = ++pParse->nMem; /* Assembled sorter record */ + int nOBSat = pSort->nOBSat; /* ORDER BY terms to skip */ + int op; /* Opcode to add sorter record to sorter */ + + assert( bSeq==0 || bSeq==1 ); + if( nPrefixReg ){ + assert( nPrefixReg==nExpr+bSeq ); + regBase = regData - nExpr - bSeq; + }else{ + regBase = pParse->nMem + 1; + pParse->nMem += nBase; + } + sqlite3ExprCodeExprList(pParse, pSort->pOrderBy, regBase, SQLITE_ECEL_DUP); + if( bSeq ){ + sqlite3VdbeAddOp2(v, OP_Sequence, pSort->iECursor, regBase+nExpr); + } + if( nPrefixReg==0 ){ + sqlite3ExprCodeMove(pParse, regData, regBase+nExpr+bSeq, nData); + } + + sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase+nOBSat, nBase-nOBSat, regRecord); + if( nOBSat>0 ){ + int regPrevKey; /* The first nOBSat columns of the previous row */ + int addrFirst; /* Address of the OP_IfNot opcode */ + int addrJmp; /* Address of the OP_Jump opcode */ + VdbeOp *pOp; /* Opcode that opens the sorter */ + int nKey; /* Number of sorting key columns, including OP_Sequence */ + KeyInfo *pKI; /* Original KeyInfo on the sorter table */ + + regPrevKey = pParse->nMem+1; + pParse->nMem += pSort->nOBSat; + nKey = nExpr - pSort->nOBSat + bSeq; + if( bSeq ){ + addrFirst = sqlite3VdbeAddOp1(v, OP_IfNot, regBase+nExpr); + }else{ + addrFirst = sqlite3VdbeAddOp1(v, OP_SequenceTest, pSort->iECursor); + } + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_Compare, regPrevKey, regBase, pSort->nOBSat); + pOp = sqlite3VdbeGetOp(v, pSort->addrSortIndex); + if( pParse->db->mallocFailed ) return; + pOp->p2 = nKey + nData; + pKI = pOp->p4.pKeyInfo; + memset(pKI->aSortOrder, 0, pKI->nField); /* Makes OP_Jump below testable */ + sqlite3VdbeChangeP4(v, -1, (char*)pKI, P4_KEYINFO); + testcase( pKI->nXField>2 ); + pOp->p4.pKeyInfo = keyInfoFromExprList(pParse, pSort->pOrderBy, nOBSat, + pKI->nXField-1); + addrJmp = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp3(v, OP_Jump, addrJmp+1, 0, addrJmp+1); VdbeCoverage(v); + pSort->labelBkOut = sqlite3VdbeMakeLabel(v); + pSort->regReturn = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut); + sqlite3VdbeAddOp1(v, OP_ResetSorter, pSort->iECursor); + sqlite3VdbeJumpHere(v, addrFirst); + sqlite3ExprCodeMove(pParse, regBase, regPrevKey, pSort->nOBSat); + sqlite3VdbeJumpHere(v, addrJmp); + } + if( pSort->sortFlags & SORTFLAG_UseSorter ){ op = OP_SorterInsert; }else{ op = OP_IdxInsert; } - sqlite3VdbeAddOp2(v, op, pOrderBy->iECursor, regRecord); - sqlite3ReleaseTempReg(pParse, regRecord); - sqlite3ReleaseTempRange(pParse, regBase, nExpr+2); + sqlite3VdbeAddOp2(v, op, pSort->iECursor, regRecord); if( pSelect->iLimit ){ - int addr1, addr2; + int addr; int iLimit; if( pSelect->iOffset ){ iLimit = pSelect->iOffset+1; }else{ iLimit = pSelect->iLimit; } - addr1 = sqlite3VdbeAddOp1(v, OP_IfZero, iLimit); - sqlite3VdbeAddOp2(v, OP_AddImm, iLimit, -1); - addr2 = sqlite3VdbeAddOp0(v, OP_Goto); - sqlite3VdbeJumpHere(v, addr1); - sqlite3VdbeAddOp1(v, OP_Last, pOrderBy->iECursor); - sqlite3VdbeAddOp1(v, OP_Delete, pOrderBy->iECursor); - sqlite3VdbeJumpHere(v, addr2); + addr = sqlite3VdbeAddOp3(v, OP_IfNotZero, iLimit, 0, -1); VdbeCoverage(v); + sqlite3VdbeAddOp1(v, OP_Last, pSort->iECursor); + sqlite3VdbeAddOp1(v, OP_Delete, pSort->iECursor); + sqlite3VdbeJumpHere(v, addr); } } /* ** Add code to implement the OFFSET */ static void codeOffset( Vdbe *v, /* Generate code into this VM */ - Select *p, /* The SELECT statement being coded */ + int iOffset, /* Register holding the offset counter */ int iContinue /* Jump here to skip the current record */ ){ - if( p->iOffset && iContinue!=0 ){ + if( iOffset>0 ){ int addr; - sqlite3VdbeAddOp2(v, OP_AddImm, p->iOffset, -1); - addr = sqlite3VdbeAddOp1(v, OP_IfNeg, p->iOffset); + addr = sqlite3VdbeAddOp3(v, OP_IfNeg, iOffset, 0, -1); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Goto, 0, iContinue); VdbeComment((v, "skip OFFSET records")); sqlite3VdbeJumpHere(v, addr); } } @@ -98348,11 +106602,11 @@ Vdbe *v; int r1; v = pParse->pVdbe; r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, iMem, N); + sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, iMem, N); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_MakeRecord, iMem, N, r1); sqlite3VdbeAddOp2(v, OP_IdxInsert, iTab, r1); sqlite3ReleaseTempReg(pParse, r1); } @@ -98378,39 +106632,25 @@ return 0; } } #endif -/* -** An instance of the following object is used to record information about -** how to process the DISTINCT keyword, to simplify passing that information -** into the selectInnerLoop() routine. -*/ -typedef struct DistinctCtx DistinctCtx; -struct DistinctCtx { - u8 isTnct; /* True if the DISTINCT keyword is present */ - u8 eTnctType; /* One of the WHERE_DISTINCT_* operators */ - int tabTnct; /* Ephemeral table used for DISTINCT processing */ - int addrTnct; /* Address of OP_OpenEphemeral opcode for tabTnct */ -}; - /* ** This routine generates the code for the inside of the inner loop ** of a SELECT. ** -** If srcTab and nColumn are both zero, then the pEList expressions -** are evaluated in order to get the data for this row. If nColumn>0 -** then data is pulled from srcTab and pEList is used only to get the -** datatypes for each column. +** If srcTab is negative, then the pEList expressions +** are evaluated in order to get the data for this row. If srcTab is +** zero or more, then data is pulled from srcTab and pEList is used only +** to get number columns and the datatype for each column. */ static void selectInnerLoop( Parse *pParse, /* The parser context */ Select *p, /* The complete select statement being coded */ ExprList *pEList, /* List of values being extracted */ int srcTab, /* Pull data from this table */ - int nColumn, /* Number of columns in the source table */ - ExprList *pOrderBy, /* If not NULL, sort results using this key */ + SortCtx *pSort, /* If not NULL, info on how to process ORDER BY */ DistinctCtx *pDistinct, /* If not NULL, info on how to process DISTINCT */ SelectDest *pDest, /* How to dispose of the results */ int iContinue, /* Jump here to continue with next row */ int iBreak /* Jump here to break out of the inner loop */ ){ @@ -98419,63 +106659,70 @@ int hasDistinct; /* True if the DISTINCT keyword is present */ int regResult; /* Start of memory holding result set */ int eDest = pDest->eDest; /* How to dispose of results */ int iParm = pDest->iSDParm; /* First argument to disposal method */ int nResultCol; /* Number of result columns */ + int nPrefixReg = 0; /* Number of extra registers before regResult */ assert( v ); - if( NEVER(v==0) ) return; assert( pEList!=0 ); hasDistinct = pDistinct ? pDistinct->eTnctType : WHERE_DISTINCT_NOOP; - if( pOrderBy==0 && !hasDistinct ){ - codeOffset(v, p, iContinue); + if( pSort && pSort->pOrderBy==0 ) pSort = 0; + if( pSort==0 && !hasDistinct ){ + assert( iContinue!=0 ); + codeOffset(v, p->iOffset, iContinue); } /* Pull the requested columns. */ - if( nColumn>0 ){ - nResultCol = nColumn; - }else{ - nResultCol = pEList->nExpr; - } + nResultCol = pEList->nExpr; + if( pDest->iSdst==0 ){ + if( pSort ){ + nPrefixReg = pSort->pOrderBy->nExpr; + if( !(pSort->sortFlags & SORTFLAG_UseSorter) ) nPrefixReg++; + pParse->nMem += nPrefixReg; + } pDest->iSdst = pParse->nMem+1; - pDest->nSdst = nResultCol; + pParse->nMem += nResultCol; + }else if( pDest->iSdst+nResultCol > pParse->nMem ){ + /* This is an error condition that can result, for example, when a SELECT + ** on the right-hand side of an INSERT contains more result columns than + ** there are columns in the table on the left. The error will be caught + ** and reported later. But we need to make sure enough memory is allocated + ** to avoid other spurious errors in the meantime. */ pParse->nMem += nResultCol; - }else{ - assert( pDest->nSdst==nResultCol ); } + pDest->nSdst = nResultCol; regResult = pDest->iSdst; - if( nColumn>0 ){ - for(i=0; i<nColumn; i++){ + if( srcTab>=0 ){ + for(i=0; i<nResultCol; i++){ sqlite3VdbeAddOp3(v, OP_Column, srcTab, i, regResult+i); + VdbeComment((v, "%s", pEList->a[i].zName)); } }else if( eDest!=SRT_Exists ){ /* If the destination is an EXISTS(...) expression, the actual ** values returned by the SELECT are not required. */ - sqlite3ExprCacheClear(pParse); - sqlite3ExprCodeExprList(pParse, pEList, regResult, eDest==SRT_Output); + sqlite3ExprCodeExprList(pParse, pEList, regResult, + (eDest==SRT_Output||eDest==SRT_Coroutine)?SQLITE_ECEL_DUP:0); } - nColumn = nResultCol; /* If the DISTINCT keyword was present on the SELECT statement ** and this row has been seen before, then do not make this row ** part of the result. */ if( hasDistinct ){ - assert( pEList!=0 ); - assert( pEList->nExpr==nColumn ); switch( pDistinct->eTnctType ){ case WHERE_DISTINCT_ORDERED: { VdbeOp *pOp; /* No longer required OpenEphemeral instr. */ int iJump; /* Jump destination */ int regPrev; /* Previous row content */ /* Allocate space for the previous row */ regPrev = pParse->nMem+1; - pParse->nMem += nColumn; + pParse->nMem += nResultCol; /* Change the OP_OpenEphemeral coded earlier to an OP_Null ** sets the MEM_Cleared bit on the first register of the ** previous value. This will cause the OP_Ne below to always ** fail on the first iteration of the loop even if the first @@ -98485,23 +106732,25 @@ pOp = sqlite3VdbeGetOp(v, pDistinct->addrTnct); pOp->opcode = OP_Null; pOp->p1 = 1; pOp->p2 = regPrev; - iJump = sqlite3VdbeCurrentAddr(v) + nColumn; - for(i=0; i<nColumn; i++){ + iJump = sqlite3VdbeCurrentAddr(v) + nResultCol; + for(i=0; i<nResultCol; i++){ CollSeq *pColl = sqlite3ExprCollSeq(pParse, pEList->a[i].pExpr); - if( i<nColumn-1 ){ + if( i<nResultCol-1 ){ sqlite3VdbeAddOp3(v, OP_Ne, regResult+i, iJump, regPrev+i); + VdbeCoverage(v); }else{ sqlite3VdbeAddOp3(v, OP_Eq, regResult+i, iContinue, regPrev+i); - } + VdbeCoverage(v); + } sqlite3VdbeChangeP4(v, -1, (const char *)pColl, P4_COLLSEQ); sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); } - assert( sqlite3VdbeCurrentAddr(v)==iJump ); - sqlite3VdbeAddOp3(v, OP_Copy, regResult, regPrev, nColumn-1); + assert( sqlite3VdbeCurrentAddr(v)==iJump || pParse->db->mallocFailed ); + sqlite3VdbeAddOp3(v, OP_Copy, regResult, regPrev, nResultCol-1); break; } case WHERE_DISTINCT_UNIQUE: { sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct); @@ -98508,16 +106757,16 @@ break; } default: { assert( pDistinct->eTnctType==WHERE_DISTINCT_UNORDERED ); - codeDistinct(pParse, pDistinct->tabTnct, iContinue, nColumn, regResult); + codeDistinct(pParse, pDistinct->tabTnct, iContinue, nResultCol, regResult); break; } } - if( pOrderBy==0 ){ - codeOffset(v, p, iContinue); + if( pSort==0 ){ + codeOffset(v, p->iOffset, iContinue); } } switch( eDest ){ /* In this mode, write each query result to the key of the temporary @@ -98525,11 +106774,11 @@ */ #ifndef SQLITE_OMIT_COMPOUND_SELECT case SRT_Union: { int r1; r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nColumn, r1); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1); sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); sqlite3ReleaseTempReg(pParse, r1); break; } @@ -98536,51 +106785,66 @@ /* Construct a record from the query result, but instead of ** saving that record, use it as a key to delete elements from ** the temporary table iParm. */ case SRT_Except: { - sqlite3VdbeAddOp3(v, OP_IdxDelete, iParm, regResult, nColumn); + sqlite3VdbeAddOp3(v, OP_IdxDelete, iParm, regResult, nResultCol); break; } -#endif +#endif /* SQLITE_OMIT_COMPOUND_SELECT */ /* Store the result as data using a unique key. */ + case SRT_Fifo: + case SRT_DistFifo: case SRT_Table: case SRT_EphemTab: { - int r1 = sqlite3GetTempReg(pParse); + int r1 = sqlite3GetTempRange(pParse, nPrefixReg+1); testcase( eDest==SRT_Table ); testcase( eDest==SRT_EphemTab ); - sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nColumn, r1); - if( pOrderBy ){ - pushOntoSorter(pParse, pOrderBy, p, r1); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg); +#ifndef SQLITE_OMIT_CTE + if( eDest==SRT_DistFifo ){ + /* If the destination is DistFifo, then cursor (iParm+1) is open + ** on an ephemeral index. If the current row is already present + ** in the index, do not write it to the output. If not, add the + ** current row to the index and proceed with writing it to the + ** output table as well. */ + int addr = sqlite3VdbeCurrentAddr(v) + 4; + sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, addr, r1, 0); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r1); + assert( pSort==0 ); + } +#endif + if( pSort ){ + pushOntoSorter(pParse, pSort, p, r1+nPrefixReg, 1, nPrefixReg); }else{ int r2 = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, r2); sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, r2); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); sqlite3ReleaseTempReg(pParse, r2); } - sqlite3ReleaseTempReg(pParse, r1); + sqlite3ReleaseTempRange(pParse, r1, nPrefixReg+1); break; } #ifndef SQLITE_OMIT_SUBQUERY /* If we are creating a set for an "expr IN (SELECT ...)" construct, ** then there should be a single item on the stack. Write this ** item into the set table with bogus data. */ case SRT_Set: { - assert( nColumn==1 ); + assert( nResultCol==1 ); pDest->affSdst = sqlite3CompareAffinity(pEList->a[0].pExpr, pDest->affSdst); - if( pOrderBy ){ + if( pSort ){ /* At first glance you would think we could optimize out the ** ORDER BY in this case since the order of entries in the set ** does not matter. But there might be a LIMIT clause, in which ** case the order does matter */ - pushOntoSorter(pParse, pOrderBy, p, regResult); + pushOntoSorter(pParse, pSort, p, regResult, 1, nPrefixReg); }else{ int r1 = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult,1,r1, &pDest->affSdst, 1); sqlite3ExprCacheAffinityChange(pParse, regResult, 1); sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); @@ -98600,42 +106864,84 @@ /* If this is a scalar select that is part of an expression, then ** store the results in the appropriate memory cell and break out ** of the scan loop. */ case SRT_Mem: { - assert( nColumn==1 ); - if( pOrderBy ){ - pushOntoSorter(pParse, pOrderBy, p, regResult); + assert( nResultCol==1 ); + if( pSort ){ + pushOntoSorter(pParse, pSort, p, regResult, 1, nPrefixReg); }else{ - sqlite3ExprCodeMove(pParse, regResult, iParm, 1); + assert( regResult==iParm ); /* The LIMIT clause will jump out of the loop for us */ } break; } #endif /* #ifndef SQLITE_OMIT_SUBQUERY */ - /* Send the data to the callback function or to a subroutine. In the - ** case of a subroutine, the subroutine itself is responsible for - ** popping the data from the stack. - */ - case SRT_Coroutine: - case SRT_Output: { + case SRT_Coroutine: /* Send data to a co-routine */ + case SRT_Output: { /* Return the results */ testcase( eDest==SRT_Coroutine ); testcase( eDest==SRT_Output ); - if( pOrderBy ){ - int r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nColumn, r1); - pushOntoSorter(pParse, pOrderBy, p, r1); - sqlite3ReleaseTempReg(pParse, r1); + if( pSort ){ + pushOntoSorter(pParse, pSort, p, regResult, nResultCol, nPrefixReg); }else if( eDest==SRT_Coroutine ){ sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm); }else{ - sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, nColumn); - sqlite3ExprCacheAffinityChange(pParse, regResult, nColumn); + sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, nResultCol); + sqlite3ExprCacheAffinityChange(pParse, regResult, nResultCol); } break; } + +#ifndef SQLITE_OMIT_CTE + /* Write the results into a priority queue that is order according to + ** pDest->pOrderBy (in pSO). pDest->iSDParm (in iParm) is the cursor for an + ** index with pSO->nExpr+2 columns. Build a key using pSO for the first + ** pSO->nExpr columns, then make sure all keys are unique by adding a + ** final OP_Sequence column. The last column is the record as a blob. + */ + case SRT_DistQueue: + case SRT_Queue: { + int nKey; + int r1, r2, r3; + int addrTest = 0; + ExprList *pSO; + pSO = pDest->pOrderBy; + assert( pSO ); + nKey = pSO->nExpr; + r1 = sqlite3GetTempReg(pParse); + r2 = sqlite3GetTempRange(pParse, nKey+2); + r3 = r2+nKey+1; + if( eDest==SRT_DistQueue ){ + /* If the destination is DistQueue, then cursor (iParm+1) is open + ** on a second ephemeral index that holds all values every previously + ** added to the queue. */ + addrTest = sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, 0, + regResult, nResultCol); + VdbeCoverage(v); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r3); + if( eDest==SRT_DistQueue ){ + sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r3); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + } + for(i=0; i<nKey; i++){ + sqlite3VdbeAddOp2(v, OP_SCopy, + regResult + pSO->a[i].u.x.iOrderByCol - 1, + r2+i); + } + sqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2+nKey); + sqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey+2, r1); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); + if( addrTest ) sqlite3VdbeJumpHere(v, addrTest); + sqlite3ReleaseTempReg(pParse, r1); + sqlite3ReleaseTempRange(pParse, r2, nKey+2); + break; + } +#endif /* SQLITE_OMIT_CTE */ + + #if !defined(SQLITE_OMIT_TRIGGER) /* Discard the results. This is used for SELECT statements inside ** the body of a TRIGGER. The purpose of such selects is to call ** user-defined functions that have side effects. We do not care @@ -98650,34 +106956,67 @@ /* Jump to the end of the loop if the LIMIT is reached. Except, if ** there is a sorter, in which case the sorter has already limited ** the output for us. */ - if( pOrderBy==0 && p->iLimit ){ - sqlite3VdbeAddOp3(v, OP_IfZero, p->iLimit, iBreak, -1); + if( pSort==0 && p->iLimit ){ + sqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, iBreak); VdbeCoverage(v); } } /* -** Allocate a KeyInfo object sufficient for an index of N columns. -** -** Actually, always allocate one extra column for the rowid at the end -** of the index. So the KeyInfo returned will have space sufficient for -** N+1 columns. +** Allocate a KeyInfo object sufficient for an index of N key columns and +** X extra columns. */ -SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N){ - KeyInfo *p = sqlite3DbMallocZero(db, - sizeof(KeyInfo) + (N+1)*(sizeof(CollSeq*)+1)); +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ + KeyInfo *p = sqlite3DbMallocZero(0, + sizeof(KeyInfo) + (N+X)*(sizeof(CollSeq*)+1)); if( p ){ - p->aSortOrder = (u8*)&p->aColl[N+1]; + p->aSortOrder = (u8*)&p->aColl[N+X]; p->nField = (u16)N; + p->nXField = (u16)X; p->enc = ENC(db); p->db = db; + p->nRef = 1; + }else{ + db->mallocFailed = 1; + } + return p; +} + +/* +** Deallocate a KeyInfo object +*/ +SQLITE_PRIVATE void sqlite3KeyInfoUnref(KeyInfo *p){ + if( p ){ + assert( p->nRef>0 ); + p->nRef--; + if( p->nRef==0 ) sqlite3DbFree(0, p); + } +} + +/* +** Make a new pointer to a KeyInfo object +*/ +SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoRef(KeyInfo *p){ + if( p ){ + assert( p->nRef>0 ); + p->nRef++; } return p; } +#ifdef SQLITE_DEBUG +/* +** Return TRUE if a KeyInfo object can be change. The KeyInfo object +** can only be changed if this is just a single reference to the object. +** +** This routine is used only inside of assert() statements. +*/ +SQLITE_PRIVATE int sqlite3KeyInfoIsWriteable(KeyInfo *p){ return p->nRef==1; } +#endif /* SQLITE_DEBUG */ + /* ** Given an expression list, generate a KeyInfo structure that records ** the collating sequence for each expression in that expression list. ** ** If the ExprList is an ORDER BY or GROUP BY clause then the resulting @@ -98684,31 +107023,36 @@ ** KeyInfo structure is appropriate for initializing a virtual index to ** implement that clause. If the ExprList is the result set of a SELECT ** then the KeyInfo structure is appropriate for initializing a virtual ** index to implement a DISTINCT test. ** -** Space to hold the KeyInfo structure is obtain from malloc. The calling +** Space to hold the KeyInfo structure is obtained from malloc. The calling ** function is responsible for seeing that this structure is eventually -** freed. Add the KeyInfo structure to the P4 field of an opcode using -** P4_KEYINFO_HANDOFF is the usual way of dealing with this. +** freed. */ -static KeyInfo *keyInfoFromExprList(Parse *pParse, ExprList *pList){ +static KeyInfo *keyInfoFromExprList( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* Form the KeyInfo object from this ExprList */ + int iStart, /* Begin with this column of pList */ + int nExtra /* Add this many extra columns to the end */ +){ int nExpr; KeyInfo *pInfo; struct ExprList_item *pItem; sqlite3 *db = pParse->db; int i; nExpr = pList->nExpr; - pInfo = sqlite3KeyInfoAlloc(db, nExpr); + pInfo = sqlite3KeyInfoAlloc(db, nExpr-iStart, nExtra+1); if( pInfo ){ - for(i=0, pItem=pList->a; i<nExpr; i++, pItem++){ + assert( sqlite3KeyInfoIsWriteable(pInfo) ); + for(i=iStart, pItem=pList->a+iStart; i<nExpr; i++, pItem++){ CollSeq *pColl; pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr); if( !pColl ) pColl = db->pDfltColl; - pInfo->aColl[i] = pColl; - pInfo->aSortOrder[i] = pItem->sortOrder; + pInfo->aColl[i-iStart] = pColl; + pInfo->aSortOrder[i-iStart] = pItem->sortOrder; } } return pInfo; } @@ -98806,49 +107150,72 @@ ** routine generates the code needed to do that. */ static void generateSortTail( Parse *pParse, /* Parsing context */ Select *p, /* The SELECT statement */ - Vdbe *v, /* Generate code into this VDBE */ + SortCtx *pSort, /* Information on the ORDER BY clause */ int nColumn, /* Number of columns of data */ SelectDest *pDest /* Write the sorted results here */ ){ + Vdbe *v = pParse->pVdbe; /* The prepared statement */ int addrBreak = sqlite3VdbeMakeLabel(v); /* Jump here to exit loop */ int addrContinue = sqlite3VdbeMakeLabel(v); /* Jump here for next cycle */ int addr; + int addrOnce = 0; int iTab; - int pseudoTab = 0; - ExprList *pOrderBy = p->pOrderBy; - + ExprList *pOrderBy = pSort->pOrderBy; int eDest = pDest->eDest; int iParm = pDest->iSDParm; - int regRow; int regRowid; + int nKey; + int iSortTab; /* Sorter cursor to read from */ + int nSortData; /* Trailing values to read from sorter */ + int i; + int bSeq; /* True if sorter record includes seq. no. */ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + struct ExprList_item *aOutEx = p->pEList->a; +#endif - iTab = pOrderBy->iECursor; - regRow = sqlite3GetTempReg(pParse); + if( pSort->labelBkOut ){ + sqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut); + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrBreak); + sqlite3VdbeResolveLabel(v, pSort->labelBkOut); + } + iTab = pSort->iECursor; if( eDest==SRT_Output || eDest==SRT_Coroutine ){ - pseudoTab = pParse->nTab++; - sqlite3VdbeAddOp3(v, OP_OpenPseudo, pseudoTab, regRow, nColumn); regRowid = 0; + regRow = pDest->iSdst; + nSortData = nColumn; }else{ regRowid = sqlite3GetTempReg(pParse); + regRow = sqlite3GetTempReg(pParse); + nSortData = 1; } - if( p->selFlags & SF_UseSorter ){ + nKey = pOrderBy->nExpr - pSort->nOBSat; + if( pSort->sortFlags & SORTFLAG_UseSorter ){ int regSortOut = ++pParse->nMem; - int ptab2 = pParse->nTab++; - sqlite3VdbeAddOp3(v, OP_OpenPseudo, ptab2, regSortOut, pOrderBy->nExpr+2); + iSortTab = pParse->nTab++; + if( pSort->labelBkOut ){ + addrOnce = sqlite3CodeOnce(pParse); VdbeCoverage(v); + } + sqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut, nKey+1+nSortData); + if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); addr = 1 + sqlite3VdbeAddOp2(v, OP_SorterSort, iTab, addrBreak); - codeOffset(v, p, addrContinue); - sqlite3VdbeAddOp2(v, OP_SorterData, iTab, regSortOut); - sqlite3VdbeAddOp3(v, OP_Column, ptab2, pOrderBy->nExpr+1, regRow); - sqlite3VdbeChangeP5(v, OPFLAG_CLEARCACHE); + VdbeCoverage(v); + codeOffset(v, p->iOffset, addrContinue); + sqlite3VdbeAddOp3(v, OP_SorterData, iTab, regSortOut, iSortTab); + bSeq = 0; }else{ - addr = 1 + sqlite3VdbeAddOp2(v, OP_Sort, iTab, addrBreak); - codeOffset(v, p, addrContinue); - sqlite3VdbeAddOp3(v, OP_Column, iTab, pOrderBy->nExpr+1, regRow); + addr = 1 + sqlite3VdbeAddOp2(v, OP_Sort, iTab, addrBreak); VdbeCoverage(v); + codeOffset(v, p->iOffset, addrContinue); + iSortTab = iTab; + bSeq = 1; + } + for(i=0; i<nSortData; i++){ + sqlite3VdbeAddOp3(v, OP_Column, iSortTab, nKey+bSeq+i, regRow+i); + VdbeComment((v, "%s", aOutEx[i].zName ? aOutEx[i].zName : aOutEx[i].zSpan)); } switch( eDest ){ case SRT_Table: case SRT_EphemTab: { testcase( eDest==SRT_Table ); @@ -98873,50 +107240,44 @@ /* The LIMIT clause will terminate the loop for us */ break; } #endif default: { - int i; assert( eDest==SRT_Output || eDest==SRT_Coroutine ); testcase( eDest==SRT_Output ); testcase( eDest==SRT_Coroutine ); - for(i=0; i<nColumn; i++){ - assert( regRow!=pDest->iSdst+i ); - sqlite3VdbeAddOp3(v, OP_Column, pseudoTab, i, pDest->iSdst+i); - if( i==0 ){ - sqlite3VdbeChangeP5(v, OPFLAG_CLEARCACHE); - } - } if( eDest==SRT_Output ){ sqlite3VdbeAddOp2(v, OP_ResultRow, pDest->iSdst, nColumn); sqlite3ExprCacheAffinityChange(pParse, pDest->iSdst, nColumn); }else{ sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm); } break; } } - sqlite3ReleaseTempReg(pParse, regRow); - sqlite3ReleaseTempReg(pParse, regRowid); - + if( regRowid ){ + sqlite3ReleaseTempReg(pParse, regRow); + sqlite3ReleaseTempReg(pParse, regRowid); + } /* The bottom of the loop */ sqlite3VdbeResolveLabel(v, addrContinue); - if( p->selFlags & SF_UseSorter ){ - sqlite3VdbeAddOp2(v, OP_SorterNext, iTab, addr); + if( pSort->sortFlags & SORTFLAG_UseSorter ){ + sqlite3VdbeAddOp2(v, OP_SorterNext, iTab, addr); VdbeCoverage(v); }else{ - sqlite3VdbeAddOp2(v, OP_Next, iTab, addr); + sqlite3VdbeAddOp2(v, OP_Next, iTab, addr); VdbeCoverage(v); } + if( pSort->regReturn ) sqlite3VdbeAddOp1(v, OP_Return, pSort->regReturn); sqlite3VdbeResolveLabel(v, addrBreak); - if( eDest==SRT_Output || eDest==SRT_Coroutine ){ - sqlite3VdbeAddOp2(v, OP_Close, pseudoTab, 0); - } } /* ** Return a pointer to a string containing the 'declaration type' of the ** expression pExpr. The string may be treated as static by the caller. +** +** Also try to estimate the size of the returned value and return that +** result in *pEstWidth. ** ** The declaration type is the exact datatype definition extracted from the ** original CREATE TABLE statement if the expression is a column. The ** declaration type for a ROWID field is INTEGER. Exactly when an expression ** is considered a column can be complex in the presence of subqueries. The @@ -98927,25 +107288,40 @@ ** SELECT (SELECT col FROM tbl; ** SELECT (SELECT col FROM tbl); ** SELECT abc FROM (SELECT col AS abc FROM tbl); ** ** The declaration type for any expression other than a column is NULL. +** +** This routine has either 3 or 6 parameters depending on whether or not +** the SQLITE_ENABLE_COLUMN_METADATA compile-time option is used. */ -static const char *columnType( +#ifdef SQLITE_ENABLE_COLUMN_METADATA +# define columnType(A,B,C,D,E,F) columnTypeImpl(A,B,C,D,E,F) +static const char *columnTypeImpl( + NameContext *pNC, + Expr *pExpr, + const char **pzOrigDb, + const char **pzOrigTab, + const char **pzOrigCol, + u8 *pEstWidth +){ + char const *zOrigDb = 0; + char const *zOrigTab = 0; + char const *zOrigCol = 0; +#else /* if !defined(SQLITE_ENABLE_COLUMN_METADATA) */ +# define columnType(A,B,C,D,E,F) columnTypeImpl(A,B,F) +static const char *columnTypeImpl( NameContext *pNC, Expr *pExpr, - const char **pzOriginDb, - const char **pzOriginTab, - const char **pzOriginCol + u8 *pEstWidth ){ +#endif /* !defined(SQLITE_ENABLE_COLUMN_METADATA) */ char const *zType = 0; - char const *zOriginDb = 0; - char const *zOriginTab = 0; - char const *zOriginCol = 0; int j; + u8 estWidth = 1; + if( NEVER(pExpr==0) || pNC->pSrcList==0 ) return 0; - switch( pExpr->op ){ case TK_AGG_COLUMN: case TK_COLUMN: { /* The expression is a column. Locate the table the column is being ** extracted from in NameContext.pSrcList. This table may be real @@ -99002,29 +107378,39 @@ NameContext sNC; Expr *p = pS->pEList->a[iCol].pExpr; sNC.pSrcList = pS->pSrc; sNC.pNext = pNC; sNC.pParse = pNC->pParse; - zType = columnType(&sNC, p, &zOriginDb, &zOriginTab, &zOriginCol); + zType = columnType(&sNC, p,&zOrigDb,&zOrigTab,&zOrigCol, &estWidth); } - }else if( ALWAYS(pTab->pSchema) ){ + }else if( pTab->pSchema ){ /* A real table */ assert( !pS ); if( iCol<0 ) iCol = pTab->iPKey; assert( iCol==-1 || (iCol>=0 && iCol<pTab->nCol) ); +#ifdef SQLITE_ENABLE_COLUMN_METADATA if( iCol<0 ){ zType = "INTEGER"; - zOriginCol = "rowid"; + zOrigCol = "rowid"; }else{ zType = pTab->aCol[iCol].zType; - zOriginCol = pTab->aCol[iCol].zName; + zOrigCol = pTab->aCol[iCol].zName; + estWidth = pTab->aCol[iCol].szEst; } - zOriginTab = pTab->zName; + zOrigTab = pTab->zName; if( pNC->pParse ){ int iDb = sqlite3SchemaToIndex(pNC->pParse->db, pTab->pSchema); - zOriginDb = pNC->pParse->db->aDb[iDb].zName; + zOrigDb = pNC->pParse->db->aDb[iDb].zName; } +#else + if( iCol<0 ){ + zType = "INTEGER"; + }else{ + zType = pTab->aCol[iCol].zType; + estWidth = pTab->aCol[iCol].szEst; + } +#endif } break; } #ifndef SQLITE_OMIT_SUBQUERY case TK_SELECT: { @@ -99037,22 +107423,25 @@ Expr *p = pS->pEList->a[0].pExpr; assert( ExprHasProperty(pExpr, EP_xIsSelect) ); sNC.pSrcList = pS->pSrc; sNC.pNext = pNC; sNC.pParse = pNC->pParse; - zType = columnType(&sNC, p, &zOriginDb, &zOriginTab, &zOriginCol); + zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol, &estWidth); break; } #endif } - - if( pzOriginDb ){ - assert( pzOriginTab && pzOriginCol ); - *pzOriginDb = zOriginDb; - *pzOriginTab = zOriginTab; - *pzOriginCol = zOriginCol; + +#ifdef SQLITE_ENABLE_COLUMN_METADATA + if( pzOrigDb ){ + assert( pzOrigTab && pzOrigCol ); + *pzOrigDb = zOrigDb; + *pzOrigTab = zOrigTab; + *pzOrigCol = zOrigCol; } +#endif + if( pEstWidth ) *pEstWidth = estWidth; return zType; } /* ** Generate code that will tell the VDBE the declaration types of columns @@ -99074,25 +107463,25 @@ const char *zType; #ifdef SQLITE_ENABLE_COLUMN_METADATA const char *zOrigDb = 0; const char *zOrigTab = 0; const char *zOrigCol = 0; - zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol); + zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol, 0); /* The vdbe must make its own copy of the column-type and other ** column specific strings, in case the schema is reset before this ** virtual machine is deleted. */ sqlite3VdbeSetColName(v, i, COLNAME_DATABASE, zOrigDb, SQLITE_TRANSIENT); sqlite3VdbeSetColName(v, i, COLNAME_TABLE, zOrigTab, SQLITE_TRANSIENT); sqlite3VdbeSetColName(v, i, COLNAME_COLUMN, zOrigCol, SQLITE_TRANSIENT); #else - zType = columnType(&sNC, p, 0, 0, 0); + zType = columnType(&sNC, p, 0, 0, 0, 0); #endif sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, zType, SQLITE_TRANSIENT); } -#endif /* SQLITE_OMIT_DECLTYPE */ +#endif /* !defined(SQLITE_OMIT_DECLTYPE) */ } /* ** Generate code that will tell the VDBE the names of columns ** in the result set. This information is used to provide the @@ -99152,19 +107541,20 @@ sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_DYNAMIC); }else{ sqlite3VdbeSetColName(v, i, COLNAME_NAME, zCol, SQLITE_TRANSIENT); } }else{ - sqlite3VdbeSetColName(v, i, COLNAME_NAME, - sqlite3DbStrDup(db, pEList->a[i].zSpan), SQLITE_DYNAMIC); + const char *z = pEList->a[i].zSpan; + z = z==0 ? sqlite3MPrintf(db, "column%d", i+1) : sqlite3DbStrDup(db, z); + sqlite3VdbeSetColName(v, i, COLNAME_NAME, z, SQLITE_DYNAMIC); } } generateColumnTypes(pParse, pTabList, pEList); } /* -** Given a an expression list (which is really the list of expressions +** Given an expression list (which is really the list of expressions ** that form the result set of a SELECT statement) compute appropriate ** column names for a table that would hold the expression list. ** ** All column names will be unique. ** @@ -99233,19 +107623,19 @@ sqlite3DbFree(db, zName); break; } /* Make sure the column name is unique. If the name is not unique, - ** append a integer to the name so that it becomes unique. + ** append an integer to the name so that it becomes unique. */ nName = sqlite3Strlen30(zName); for(j=cnt=0; j<i; j++){ if( sqlite3StrICmp(aCol[j].zName, zName)==0 ){ char *zNewName; int k; for(k=nName-1; k>1 && sqlite3Isdigit(zName[k]); k--){} - if( zName[k]==':' ) nName = k; + if( k>=0 && zName[k]==':' ) nName = k; zName[nName] = 0; zNewName = sqlite3MPrintf(db, "%s:%d", zName, ++cnt); sqlite3DbFree(db, zName); zName = zNewName; j = -1; @@ -99277,39 +107667,41 @@ ** This routine requires that all identifiers in the SELECT ** statement be resolved. */ static void selectAddColumnTypeAndCollation( Parse *pParse, /* Parsing contexts */ - int nCol, /* Number of columns */ - Column *aCol, /* List of columns */ + Table *pTab, /* Add column type information to this table */ Select *pSelect /* SELECT used to determine types and collations */ ){ sqlite3 *db = pParse->db; NameContext sNC; Column *pCol; CollSeq *pColl; int i; Expr *p; struct ExprList_item *a; + u64 szAll = 0; assert( pSelect!=0 ); assert( (pSelect->selFlags & SF_Resolved)!=0 ); - assert( nCol==pSelect->pEList->nExpr || db->mallocFailed ); + assert( pTab->nCol==pSelect->pEList->nExpr || db->mallocFailed ); if( db->mallocFailed ) return; memset(&sNC, 0, sizeof(sNC)); sNC.pSrcList = pSelect->pSrc; a = pSelect->pEList->a; - for(i=0, pCol=aCol; i<nCol; i++, pCol++){ + for(i=0, pCol=pTab->aCol; i<pTab->nCol; i++, pCol++){ p = a[i].pExpr; - pCol->zType = sqlite3DbStrDup(db, columnType(&sNC, p, 0, 0, 0)); + pCol->zType = sqlite3DbStrDup(db, columnType(&sNC, p,0,0,0, &pCol->szEst)); + szAll += pCol->szEst; pCol->affinity = sqlite3ExprAffinity(p); if( pCol->affinity==0 ) pCol->affinity = SQLITE_AFF_NONE; pColl = sqlite3ExprCollSeq(pParse, p); if( pColl ){ pCol->zColl = sqlite3DbStrDup(db, pColl->zName); } } + pTab->szTabRow = sqlite3LogEst(szAll*4); } /* ** Given a SELECT statement, generate a Table structure that describes ** the result set of that SELECT. @@ -99333,13 +107725,13 @@ /* The sqlite3ResultSetOfSelect() is only used n contexts where lookaside ** is disabled */ assert( db->lookaside.bEnabled==0 ); pTab->nRef = 1; pTab->zName = 0; - pTab->nRowEst = 1000000; + pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); selectColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol); - selectAddColumnTypeAndCollation(pParse, pTab->nCol, pTab->aCol, pSelect); + selectAddColumnTypeAndCollation(pParse, pTab, pSelect); pTab->iPKey = -1; if( db->mallocFailed ){ sqlite3DeleteTable(db, pTab); return 0; } @@ -99351,16 +107743,18 @@ ** If an error occurs, return NULL and leave a message in pParse. */ SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse *pParse){ Vdbe *v = pParse->pVdbe; if( v==0 ){ - v = pParse->pVdbe = sqlite3VdbeCreate(pParse->db); -#ifndef SQLITE_OMIT_TRACE - if( v ){ - sqlite3VdbeAddOp0(v, OP_Trace); + v = pParse->pVdbe = sqlite3VdbeCreate(pParse); + if( v ) sqlite3VdbeAddOp0(v, OP_Init); + if( pParse->pToplevel==0 + && OptimizationEnabled(pParse->db,SQLITE_FactorOutConst) + ){ + pParse->okConstFactor = 1; } -#endif + } return v; } @@ -99373,12 +107767,17 @@ ** the limit and offset. If there is no limit and/or offset, then ** iLimit and iOffset are negative. ** ** This routine changes the values of iLimit and iOffset only if ** a limit or offset is defined by pLimit and pOffset. iLimit and -** iOffset should have been preset to appropriate default values -** (usually but not always -1) prior to calling this routine. +** iOffset should have been preset to appropriate default values (zero) +** prior to calling this routine. +** +** The iOffset register (if it exists) is initialized to the value +** of the OFFSET. The iLimit register is initialized to LIMIT. Register +** iOffset+1 is initialized to LIMIT+OFFSET. +** ** Only if pLimit!=0 or pOffset!=0 do the limit registers get ** redefined. The UNION ALL operator uses this property to force ** the reuse of the same limit and offset registers across multiple ** SELECT statements. */ @@ -99398,11 +107797,11 @@ sqlite3ExprCacheClear(pParse); assert( p->pOffset==0 || p->pLimit!=0 ); if( p->pLimit ){ p->iLimit = iLimit = ++pParse->nMem; v = sqlite3GetVdbe(pParse); - if( NEVER(v==0) ) return; /* VDBE should have already been allocated */ + assert( v!=0 ); if( sqlite3ExprIsInteger(p->pLimit, &n) ){ sqlite3VdbeAddOp2(v, OP_Integer, n, iLimit); VdbeComment((v, "LIMIT counter")); if( n==0 ){ sqlite3VdbeAddOp2(v, OP_Goto, 0, iBreak); @@ -99409,26 +107808,26 @@ }else if( n>=0 && p->nSelectRow>(u64)n ){ p->nSelectRow = n; } }else{ sqlite3ExprCode(pParse, p->pLimit, iLimit); - sqlite3VdbeAddOp1(v, OP_MustBeInt, iLimit); + sqlite3VdbeAddOp1(v, OP_MustBeInt, iLimit); VdbeCoverage(v); VdbeComment((v, "LIMIT counter")); - sqlite3VdbeAddOp2(v, OP_IfZero, iLimit, iBreak); + sqlite3VdbeAddOp2(v, OP_IfNot, iLimit, iBreak); VdbeCoverage(v); } if( p->pOffset ){ p->iOffset = iOffset = ++pParse->nMem; pParse->nMem++; /* Allocate an extra register for limit+offset */ sqlite3ExprCode(pParse, p->pOffset, iOffset); - sqlite3VdbeAddOp1(v, OP_MustBeInt, iOffset); + sqlite3VdbeAddOp1(v, OP_MustBeInt, iOffset); VdbeCoverage(v); VdbeComment((v, "OFFSET counter")); - addr1 = sqlite3VdbeAddOp1(v, OP_IfPos, iOffset); + addr1 = sqlite3VdbeAddOp1(v, OP_IfPos, iOffset); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Integer, 0, iOffset); sqlite3VdbeJumpHere(v, addr1); sqlite3VdbeAddOp3(v, OP_Add, iLimit, iOffset, iOffset+1); VdbeComment((v, "LIMIT+OFFSET")); - addr1 = sqlite3VdbeAddOp1(v, OP_IfPos, iLimit); + addr1 = sqlite3VdbeAddOp1(v, OP_IfPos, iLimit); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Integer, -1, iOffset+1); sqlite3VdbeJumpHere(v, addr1); } } } @@ -99453,21 +107852,281 @@ if( pRet==0 && iCol<p->pEList->nExpr ){ pRet = sqlite3ExprCollSeq(pParse, p->pEList->a[iCol].pExpr); } return pRet; } -#endif /* SQLITE_OMIT_COMPOUND_SELECT */ -/* Forward reference */ +/* +** The select statement passed as the second parameter is a compound SELECT +** with an ORDER BY clause. This function allocates and returns a KeyInfo +** structure suitable for implementing the ORDER BY. +** +** Space to hold the KeyInfo structure is obtained from malloc. The calling +** function is responsible for ensuring that this structure is eventually +** freed. +*/ +static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ + ExprList *pOrderBy = p->pOrderBy; + int nOrderBy = p->pOrderBy->nExpr; + sqlite3 *db = pParse->db; + KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy+nExtra, 1); + if( pRet ){ + int i; + for(i=0; i<nOrderBy; i++){ + struct ExprList_item *pItem = &pOrderBy->a[i]; + Expr *pTerm = pItem->pExpr; + CollSeq *pColl; + + if( pTerm->flags & EP_Collate ){ + pColl = sqlite3ExprCollSeq(pParse, pTerm); + }else{ + pColl = multiSelectCollSeq(pParse, p, pItem->u.x.iOrderByCol-1); + if( pColl==0 ) pColl = db->pDfltColl; + pOrderBy->a[i].pExpr = + sqlite3ExprAddCollateString(pParse, pTerm, pColl->zName); + } + assert( sqlite3KeyInfoIsWriteable(pRet) ); + pRet->aColl[i] = pColl; + pRet->aSortOrder[i] = pOrderBy->a[i].sortOrder; + } + } + + return pRet; +} + +#ifndef SQLITE_OMIT_CTE +/* +** This routine generates VDBE code to compute the content of a WITH RECURSIVE +** query of the form: +** +** <recursive-table> AS (<setup-query> UNION [ALL] <recursive-query>) +** \___________/ \_______________/ +** p->pPrior p +** +** +** There is exactly one reference to the recursive-table in the FROM clause +** of recursive-query, marked with the SrcList->a[].isRecursive flag. +** +** The setup-query runs once to generate an initial set of rows that go +** into a Queue table. Rows are extracted from the Queue table one by +** one. Each row extracted from Queue is output to pDest. Then the single +** extracted row (now in the iCurrent table) becomes the content of the +** recursive-table for a recursive-query run. The output of the recursive-query +** is added back into the Queue table. Then another row is extracted from Queue +** and the iteration continues until the Queue table is empty. +** +** If the compound query operator is UNION then no duplicate rows are ever +** inserted into the Queue table. The iDistinct table keeps a copy of all rows +** that have ever been inserted into Queue and causes duplicates to be +** discarded. If the operator is UNION ALL, then duplicates are allowed. +** +** If the query has an ORDER BY, then entries in the Queue table are kept in +** ORDER BY order and the first entry is extracted for each cycle. Without +** an ORDER BY, the Queue table is just a FIFO. +** +** If a LIMIT clause is provided, then the iteration stops after LIMIT rows +** have been output to pDest. A LIMIT of zero means to output no rows and a +** negative LIMIT means to output all rows. If there is also an OFFSET clause +** with a positive value, then the first OFFSET outputs are discarded rather +** than being sent to pDest. The LIMIT count does not begin until after OFFSET +** rows have been skipped. +*/ +static void generateWithRecursiveQuery( + Parse *pParse, /* Parsing context */ + Select *p, /* The recursive SELECT to be coded */ + SelectDest *pDest /* What to do with query results */ +){ + SrcList *pSrc = p->pSrc; /* The FROM clause of the recursive query */ + int nCol = p->pEList->nExpr; /* Number of columns in the recursive table */ + Vdbe *v = pParse->pVdbe; /* The prepared statement under construction */ + Select *pSetup = p->pPrior; /* The setup query */ + int addrTop; /* Top of the loop */ + int addrCont, addrBreak; /* CONTINUE and BREAK addresses */ + int iCurrent = 0; /* The Current table */ + int regCurrent; /* Register holding Current table */ + int iQueue; /* The Queue table */ + int iDistinct = 0; /* To ensure unique results if UNION */ + int eDest = SRT_Fifo; /* How to write to Queue */ + SelectDest destQueue; /* SelectDest targetting the Queue table */ + int i; /* Loop counter */ + int rc; /* Result code */ + ExprList *pOrderBy; /* The ORDER BY clause */ + Expr *pLimit, *pOffset; /* Saved LIMIT and OFFSET */ + int regLimit, regOffset; /* Registers used by LIMIT and OFFSET */ + + /* Obtain authorization to do a recursive query */ + if( sqlite3AuthCheck(pParse, SQLITE_RECURSIVE, 0, 0, 0) ) return; + + /* Process the LIMIT and OFFSET clauses, if they exist */ + addrBreak = sqlite3VdbeMakeLabel(v); + computeLimitRegisters(pParse, p, addrBreak); + pLimit = p->pLimit; + pOffset = p->pOffset; + regLimit = p->iLimit; + regOffset = p->iOffset; + p->pLimit = p->pOffset = 0; + p->iLimit = p->iOffset = 0; + pOrderBy = p->pOrderBy; + + /* Locate the cursor number of the Current table */ + for(i=0; ALWAYS(i<pSrc->nSrc); i++){ + if( pSrc->a[i].isRecursive ){ + iCurrent = pSrc->a[i].iCursor; + break; + } + } + + /* Allocate cursors numbers for Queue and Distinct. The cursor number for + ** the Distinct table must be exactly one greater than Queue in order + ** for the SRT_DistFifo and SRT_DistQueue destinations to work. */ + iQueue = pParse->nTab++; + if( p->op==TK_UNION ){ + eDest = pOrderBy ? SRT_DistQueue : SRT_DistFifo; + iDistinct = pParse->nTab++; + }else{ + eDest = pOrderBy ? SRT_Queue : SRT_Fifo; + } + sqlite3SelectDestInit(&destQueue, eDest, iQueue); + + /* Allocate cursors for Current, Queue, and Distinct. */ + regCurrent = ++pParse->nMem; + sqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol); + if( pOrderBy ){ + KeyInfo *pKeyInfo = multiSelectOrderByKeyInfo(pParse, p, 1); + sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iQueue, pOrderBy->nExpr+2, 0, + (char*)pKeyInfo, P4_KEYINFO); + destQueue.pOrderBy = pOrderBy; + }else{ + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iQueue, nCol); + } + VdbeComment((v, "Queue table")); + if( iDistinct ){ + p->addrOpenEphm[0] = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iDistinct, 0); + p->selFlags |= SF_UsesEphemeral; + } + + /* Detach the ORDER BY clause from the compound SELECT */ + p->pOrderBy = 0; + + /* Store the results of the setup-query in Queue. */ + pSetup->pNext = 0; + rc = sqlite3Select(pParse, pSetup, &destQueue); + pSetup->pNext = p; + if( rc ) goto end_of_recursive_query; + + /* Find the next row in the Queue and output that row */ + addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, iQueue, addrBreak); VdbeCoverage(v); + + /* Transfer the next row in Queue over to Current */ + sqlite3VdbeAddOp1(v, OP_NullRow, iCurrent); /* To reset column cache */ + if( pOrderBy ){ + sqlite3VdbeAddOp3(v, OP_Column, iQueue, pOrderBy->nExpr+1, regCurrent); + }else{ + sqlite3VdbeAddOp2(v, OP_RowData, iQueue, regCurrent); + } + sqlite3VdbeAddOp1(v, OP_Delete, iQueue); + + /* Output the single row in Current */ + addrCont = sqlite3VdbeMakeLabel(v); + codeOffset(v, regOffset, addrCont); + selectInnerLoop(pParse, p, p->pEList, iCurrent, + 0, 0, pDest, addrCont, addrBreak); + if( regLimit ){ + sqlite3VdbeAddOp2(v, OP_DecrJumpZero, regLimit, addrBreak); + VdbeCoverage(v); + } + sqlite3VdbeResolveLabel(v, addrCont); + + /* Execute the recursive SELECT taking the single row in Current as + ** the value for the recursive-table. Store the results in the Queue. + */ + p->pPrior = 0; + sqlite3Select(pParse, p, &destQueue); + assert( p->pPrior==0 ); + p->pPrior = pSetup; + + /* Keep running the loop until the Queue is empty */ + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrTop); + sqlite3VdbeResolveLabel(v, addrBreak); + +end_of_recursive_query: + sqlite3ExprListDelete(pParse->db, p->pOrderBy); + p->pOrderBy = pOrderBy; + p->pLimit = pLimit; + p->pOffset = pOffset; + return; +} +#endif /* SQLITE_OMIT_CTE */ + +/* Forward references */ static int multiSelectOrderBy( Parse *pParse, /* Parsing context */ Select *p, /* The right-most of SELECTs to be coded */ SelectDest *pDest /* What to do with query results */ ); +/* +** Error message for when two or more terms of a compound select have different +** size result sets. +*/ +static void selectWrongNumTermsError(Parse *pParse, Select *p){ + if( p->selFlags & SF_Values ){ + sqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms"); + }else{ + sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s" + " do not have the same number of result columns", selectOpName(p->op)); + } +} -#ifndef SQLITE_OMIT_COMPOUND_SELECT +/* +** Handle the special case of a compound-select that originates from a +** VALUES clause. By handling this as a special case, we avoid deep +** recursion, and thus do not need to enforce the SQLITE_LIMIT_COMPOUND_SELECT +** on a VALUES clause. +** +** Because the Select object originates from a VALUES clause: +** (1) It has no LIMIT or OFFSET +** (2) All terms are UNION ALL +** (3) There is no ORDER BY clause +*/ +static int multiSelectValues( + Parse *pParse, /* Parsing context */ + Select *p, /* The right-most of SELECTs to be coded */ + SelectDest *pDest /* What to do with query results */ +){ + Select *pPrior; + int nExpr = p->pEList->nExpr; + int nRow = 1; + int rc = 0; + assert( p->pNext==0 ); + assert( p->selFlags & SF_AllValues ); + do{ + assert( p->selFlags & SF_Values ); + assert( p->op==TK_ALL || (p->op==TK_SELECT && p->pPrior==0) ); + assert( p->pLimit==0 ); + assert( p->pOffset==0 ); + if( p->pEList->nExpr!=nExpr ){ + selectWrongNumTermsError(pParse, p); + return 1; + } + if( p->pPrior==0 ) break; + assert( p->pPrior->pNext==p ); + p = p->pPrior; + nRow++; + }while(1); + while( p ){ + pPrior = p->pPrior; + p->pPrior = 0; + rc = sqlite3Select(pParse, p, pDest); + p->pPrior = pPrior; + if( rc ) break; + p->nSelectRow = nRow; + p = p->pNext; + } + return rc; +} + /* ** This routine is called to process a compound query form from ** two or more separate queries using UNION, UNION ALL, EXCEPT, or ** INTERSECT ** @@ -99507,22 +108166,21 @@ Vdbe *v; /* Generate code to this VDBE */ SelectDest dest; /* Alternative data destination */ Select *pDelete = 0; /* Chain of simple selects to delete */ sqlite3 *db; /* Database connection */ #ifndef SQLITE_OMIT_EXPLAIN - int iSub1; /* EQP id of left-hand query */ - int iSub2; /* EQP id of right-hand query */ + int iSub1 = 0; /* EQP id of left-hand query */ + int iSub2 = 0; /* EQP id of right-hand query */ #endif /* Make sure there is no ORDER BY or LIMIT clause on prior SELECTs. Only ** the last (right-most) SELECT in the series may have an ORDER BY or LIMIT. */ assert( p && p->pPrior ); /* Calling function guarantees this much */ + assert( (p->selFlags & SF_Recursive)==0 || p->op==TK_ALL || p->op==TK_UNION ); db = pParse->db; pPrior = p->pPrior; - assert( pPrior->pRightmost!=pPrior ); - assert( pPrior->pRightmost==p->pRightmost ); dest = *pDest; if( pPrior->pOrderBy ){ sqlite3ErrorMsg(pParse,"ORDER BY clause should come after %s not before", selectOpName(p->op)); rc = 1; @@ -99544,31 +108202,39 @@ assert( p->pEList ); sqlite3VdbeAddOp2(v, OP_OpenEphemeral, dest.iSDParm, p->pEList->nExpr); sqlite3VdbeChangeP5(v, BTREE_UNORDERED); dest.eDest = SRT_Table; } + + /* Special handling for a compound-select that originates as a VALUES clause. + */ + if( p->selFlags & SF_AllValues ){ + rc = multiSelectValues(pParse, p, &dest); + goto multi_select_end; + } /* Make sure all SELECTs in the statement have the same number of elements ** in their result sets. */ assert( p->pEList && pPrior->pEList ); if( p->pEList->nExpr!=pPrior->pEList->nExpr ){ - if( p->selFlags & SF_Values ){ - sqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms"); - }else{ - sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s" - " do not have the same number of result columns", selectOpName(p->op)); - } + selectWrongNumTermsError(pParse, p); rc = 1; goto multi_select_end; } + +#ifndef SQLITE_OMIT_CTE + if( p->selFlags & SF_Recursive ){ + generateWithRecursiveQuery(pParse, p, &dest); + }else +#endif /* Compound SELECTs that have an ORDER BY clause are handled separately. */ if( p->pOrderBy ){ return multiSelectOrderBy(pParse, p, pDest); - } + }else /* Generate code for the left and right SELECT statements. */ switch( p->op ){ case TK_ALL: { @@ -99588,11 +108254,11 @@ } p->pPrior = 0; p->iLimit = pPrior->iLimit; p->iOffset = pPrior->iOffset; if( p->iLimit ){ - addr = sqlite3VdbeAddOp1(v, OP_IfZero, p->iLimit); + addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); VdbeComment((v, "Jump ahead if LIMIT reached")); } explainSetInteger(iSub2, pParse->iNextSelectId); rc = sqlite3Select(pParse, p, &dest); testcase( rc!=SQLITE_OK ); @@ -99620,16 +108286,14 @@ SelectDest uniondest; testcase( p->op==TK_EXCEPT ); testcase( p->op==TK_UNION ); priorOp = SRT_Union; - if( dest.eDest==priorOp && ALWAYS(!p->pLimit &&!p->pOffset) ){ + if( dest.eDest==priorOp ){ /* We can reuse a temporary table generated by a SELECT to our ** right. */ - assert( p->pRightmost!=p ); /* Can only happen for leftward elements - ** of a 3-way or more compound */ assert( p->pLimit==0 ); /* Not allowed on leftward elements */ assert( p->pOffset==0 ); /* Not allowed on leftward elements */ unionTab = dest.iSDParm; }else{ /* We will need to create our own temporary table to hold the @@ -99638,11 +108302,11 @@ unionTab = pParse->nTab++; assert( p->pOrderBy==0 ); addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, unionTab, 0); assert( p->addrOpenEphm[0] == -1 ); p->addrOpenEphm[0] = addr; - p->pRightmost->selFlags |= SF_UsesEphemeral; + findRightmost(p)->selFlags |= SF_UsesEphemeral; assert( p->pEList ); } /* Code the SELECT statements to our left */ @@ -99697,16 +108361,16 @@ generateColumnNames(pParse, 0, pFirst->pEList); } iBreak = sqlite3VdbeMakeLabel(v); iCont = sqlite3VdbeMakeLabel(v); computeLimitRegisters(pParse, p, iBreak); - sqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak); + sqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak); VdbeCoverage(v); iStart = sqlite3VdbeCurrentAddr(v); - selectInnerLoop(pParse, p, p->pEList, unionTab, p->pEList->nExpr, + selectInnerLoop(pParse, p, p->pEList, unionTab, 0, 0, &dest, iCont, iBreak); sqlite3VdbeResolveLabel(v, iCont); - sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); + sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v); sqlite3VdbeResolveLabel(v, iBreak); sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0); } break; } @@ -99727,11 +108391,11 @@ assert( p->pOrderBy==0 ); addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0); assert( p->addrOpenEphm[0] == -1 ); p->addrOpenEphm[0] = addr; - p->pRightmost->selFlags |= SF_UsesEphemeral; + findRightmost(p)->selFlags |= SF_UsesEphemeral; assert( p->pEList ); /* Code the SELECTs to our left into temporary table "tab1". */ sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); @@ -99772,19 +108436,19 @@ generateColumnNames(pParse, 0, pFirst->pEList); } iBreak = sqlite3VdbeMakeLabel(v); iCont = sqlite3VdbeMakeLabel(v); computeLimitRegisters(pParse, p, iBreak); - sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); + sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v); r1 = sqlite3GetTempReg(pParse); iStart = sqlite3VdbeAddOp2(v, OP_RowKey, tab1, r1); - sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); + sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); VdbeCoverage(v); sqlite3ReleaseTempReg(pParse, r1); - selectInnerLoop(pParse, p, p->pEList, tab1, p->pEList->nExpr, + selectInnerLoop(pParse, p, p->pEList, tab1, 0, 0, &dest, iCont, iBreak); sqlite3VdbeResolveLabel(v, iCont); - sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); + sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v); sqlite3VdbeResolveLabel(v, iBreak); sqlite3VdbeAddOp2(v, OP_Close, tab2, 0); sqlite3VdbeAddOp2(v, OP_Close, tab1, 0); break; } @@ -99806,13 +108470,13 @@ KeyInfo *pKeyInfo; /* Collating sequence for the result set */ Select *pLoop; /* For looping through SELECT statements */ CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */ int nCol; /* Number of columns in result set */ - assert( p->pRightmost==p ); + assert( p->pNext==0 ); nCol = p->pEList->nExpr; - pKeyInfo = sqlite3KeyInfoAlloc(db, nCol); + pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1); if( !pKeyInfo ){ rc = SQLITE_NOMEM; goto multi_select_end; } for(i=0, apColl=pKeyInfo->aColl; i<nCol; i++, apColl++){ @@ -99830,15 +108494,16 @@ ** always safely abort as soon as the first unused slot is found */ assert( pLoop->addrOpenEphm[1]<0 ); break; } sqlite3VdbeChangeP2(v, addr, nCol); - sqlite3VdbeChangeP4(v, addr, (char*)pKeyInfo, P4_KEYINFO); + sqlite3VdbeChangeP4(v, addr, (char*)sqlite3KeyInfoRef(pKeyInfo), + P4_KEYINFO); pLoop->addrOpenEphm[i] = -1; } } - sqlite3DbFree(db, pKeyInfo); + sqlite3KeyInfoUnref(pKeyInfo); } multi_select_end: pDest->iSdst = dest.iSdst; pDest->nSdst = dest.nSdst; @@ -99873,11 +108538,10 @@ SelectDest *pIn, /* Coroutine supplying data */ SelectDest *pDest, /* Where to send the data */ int regReturn, /* The return address register */ int regPrev, /* Previous result register. No uniqueness if 0 */ KeyInfo *pKeyInfo, /* For comparing with previous entry */ - int p4type, /* The p4 type for pKeyInfo */ int iBreak /* Jump here if we hit the LIMIT */ ){ Vdbe *v = pParse->pVdbe; int iContinue; int addr; @@ -99887,23 +108551,23 @@ /* Suppress duplicates for UNION, EXCEPT, and INTERSECT */ if( regPrev ){ int j1, j2; - j1 = sqlite3VdbeAddOp1(v, OP_IfNot, regPrev); + j1 = sqlite3VdbeAddOp1(v, OP_IfNot, regPrev); VdbeCoverage(v); j2 = sqlite3VdbeAddOp4(v, OP_Compare, pIn->iSdst, regPrev+1, pIn->nSdst, - (char*)pKeyInfo, p4type); - sqlite3VdbeAddOp3(v, OP_Jump, j2+2, iContinue, j2+2); + (char*)sqlite3KeyInfoRef(pKeyInfo), P4_KEYINFO); + sqlite3VdbeAddOp3(v, OP_Jump, j2+2, iContinue, j2+2); VdbeCoverage(v); sqlite3VdbeJumpHere(v, j1); sqlite3VdbeAddOp3(v, OP_Copy, pIn->iSdst, regPrev+1, pIn->nSdst-1); sqlite3VdbeAddOp2(v, OP_Integer, 1, regPrev); } if( pParse->db->mallocFailed ) return 0; /* Suppress the first OFFSET entries if there is an OFFSET clause */ - codeOffset(v, p, iContinue); + codeOffset(v, p->iOffset, iContinue); switch( pDest->eDest ){ /* Store the result as data using a unique key. */ case SRT_Table: @@ -99991,11 +108655,11 @@ } /* Jump to the end of the loop if the LIMIT is reached. */ if( p->iLimit ){ - sqlite3VdbeAddOp3(v, OP_IfZero, p->iLimit, iBreak, -1); + sqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, iBreak); VdbeCoverage(v); } /* Generate the subroutine return */ sqlite3VdbeResolveLabel(v, iContinue); @@ -100099,20 +108763,19 @@ Select *pPrior; /* Another SELECT immediately to our left */ Vdbe *v; /* Generate code to this VDBE */ SelectDest destA; /* Destination for coroutine A */ SelectDest destB; /* Destination for coroutine B */ int regAddrA; /* Address register for select-A coroutine */ - int regEofA; /* Flag to indicate when select-A is complete */ int regAddrB; /* Address register for select-B coroutine */ - int regEofB; /* Flag to indicate when select-B is complete */ int addrSelectA; /* Address of the select-A coroutine */ int addrSelectB; /* Address of the select-B coroutine */ int regOutA; /* Address register for the output-A subroutine */ int regOutB; /* Address register for the output-B subroutine */ int addrOutA; /* Address of the output-A subroutine */ int addrOutB = 0; /* Address of the output-B subroutine */ int addrEofA; /* Address of the select-A-exhausted subroutine */ + int addrEofA_noB; /* Alternate addrEofA if B is uninitialized */ int addrEofB; /* Address of the select-B-exhausted subroutine */ int addrAltB; /* Address of the A<B subroutine */ int addrAeqB; /* Address of the A==B subroutine */ int addrAgtB; /* Address of the A>B subroutine */ int regLimitA; /* Limit register for select-A */ @@ -100159,20 +108822,20 @@ */ if( op!=TK_ALL ){ for(i=1; db->mallocFailed==0 && i<=p->pEList->nExpr; i++){ struct ExprList_item *pItem; for(j=0, pItem=pOrderBy->a; j<nOrderBy; j++, pItem++){ - assert( pItem->iOrderByCol>0 ); - if( pItem->iOrderByCol==i ) break; + assert( pItem->u.x.iOrderByCol>0 ); + if( pItem->u.x.iOrderByCol==i ) break; } if( j==nOrderBy ){ Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); if( pNew==0 ) return SQLITE_NOMEM; pNew->flags |= EP_IntValue; pNew->u.iValue = i; pOrderBy = sqlite3ExprListAppend(pParse, pOrderBy, pNew); - if( pOrderBy ) pOrderBy->a[nOrderBy++].iOrderByCol = (u16)i; + if( pOrderBy ) pOrderBy->a[nOrderBy++].u.x.iOrderByCol = (u16)i; } } } /* Compute the comparison permutation and keyinfo that is used with @@ -100184,30 +108847,15 @@ */ aPermute = sqlite3DbMallocRaw(db, sizeof(int)*nOrderBy); if( aPermute ){ struct ExprList_item *pItem; for(i=0, pItem=pOrderBy->a; i<nOrderBy; i++, pItem++){ - assert( pItem->iOrderByCol>0 && pItem->iOrderByCol<=p->pEList->nExpr ); - aPermute[i] = pItem->iOrderByCol - 1; - } - pKeyMerge = sqlite3KeyInfoAlloc(db, nOrderBy); - if( pKeyMerge ){ - for(i=0; i<nOrderBy; i++){ - CollSeq *pColl; - Expr *pTerm = pOrderBy->a[i].pExpr; - if( pTerm->flags & EP_Collate ){ - pColl = sqlite3ExprCollSeq(pParse, pTerm); - }else{ - pColl = multiSelectCollSeq(pParse, p, aPermute[i]); - if( pColl==0 ) pColl = db->pDfltColl; - pOrderBy->a[i].pExpr = - sqlite3ExprAddCollateString(pParse, pTerm, pColl->zName); - } - pKeyMerge->aColl[i] = pColl; - pKeyMerge->aSortOrder[i] = pOrderBy->a[i].sortOrder; - } - } + assert( pItem->u.x.iOrderByCol>0 + && pItem->u.x.iOrderByCol<=p->pEList->nExpr ); + aPermute[i] = pItem->u.x.iOrderByCol - 1; + } + pKeyMerge = multiSelectOrderByKeyInfo(pParse, p, 1); }else{ pKeyMerge = 0; } /* Reattach the ORDER BY clause to the query. @@ -100225,12 +108873,13 @@ int nExpr = p->pEList->nExpr; assert( nOrderBy>=nExpr || db->mallocFailed ); regPrev = pParse->nMem+1; pParse->nMem += nExpr+1; sqlite3VdbeAddOp2(v, OP_Integer, 0, regPrev); - pKeyDup = sqlite3KeyInfoAlloc(db, nExpr); + pKeyDup = sqlite3KeyInfoAlloc(db, nExpr, 1); if( pKeyDup ){ + assert( sqlite3KeyInfoIsWriteable(pKeyDup) ); for(i=0; i<nExpr; i++){ pKeyDup->aColl[i] = multiSelectCollSeq(pParse, p, i); pKeyDup->aSortOrder[i] = 0; } } @@ -100237,10 +108886,11 @@ } /* Separate the left and the right query from one another */ p->pPrior = 0; + pPrior->pNext = 0; sqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER"); if( pPrior->pPrior==0 ){ sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER"); } @@ -100259,81 +108909,73 @@ p->pLimit = 0; sqlite3ExprDelete(db, p->pOffset); p->pOffset = 0; regAddrA = ++pParse->nMem; - regEofA = ++pParse->nMem; regAddrB = ++pParse->nMem; - regEofB = ++pParse->nMem; regOutA = ++pParse->nMem; regOutB = ++pParse->nMem; sqlite3SelectDestInit(&destA, SRT_Coroutine, regAddrA); sqlite3SelectDestInit(&destB, SRT_Coroutine, regAddrB); - - /* Jump past the various subroutines and coroutines to the main - ** merge loop - */ - j1 = sqlite3VdbeAddOp0(v, OP_Goto); - addrSelectA = sqlite3VdbeCurrentAddr(v); - /* Generate a coroutine to evaluate the SELECT statement to the ** left of the compound operator - the "A" select. */ - VdbeNoopComment((v, "Begin coroutine for left SELECT")); + addrSelectA = sqlite3VdbeCurrentAddr(v) + 1; + j1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrA, 0, addrSelectA); + VdbeComment((v, "left SELECT")); pPrior->iLimit = regLimitA; explainSetInteger(iSub1, pParse->iNextSelectId); sqlite3Select(pParse, pPrior, &destA); - sqlite3VdbeAddOp2(v, OP_Integer, 1, regEofA); - sqlite3VdbeAddOp1(v, OP_Yield, regAddrA); - VdbeNoopComment((v, "End coroutine for left SELECT")); + sqlite3VdbeAddOp1(v, OP_EndCoroutine, regAddrA); + sqlite3VdbeJumpHere(v, j1); /* Generate a coroutine to evaluate the SELECT statement on ** the right - the "B" select */ - addrSelectB = sqlite3VdbeCurrentAddr(v); - VdbeNoopComment((v, "Begin coroutine for right SELECT")); + addrSelectB = sqlite3VdbeCurrentAddr(v) + 1; + j1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrB, 0, addrSelectB); + VdbeComment((v, "right SELECT")); savedLimit = p->iLimit; savedOffset = p->iOffset; p->iLimit = regLimitB; p->iOffset = 0; explainSetInteger(iSub2, pParse->iNextSelectId); sqlite3Select(pParse, p, &destB); p->iLimit = savedLimit; p->iOffset = savedOffset; - sqlite3VdbeAddOp2(v, OP_Integer, 1, regEofB); - sqlite3VdbeAddOp1(v, OP_Yield, regAddrB); - VdbeNoopComment((v, "End coroutine for right SELECT")); + sqlite3VdbeAddOp1(v, OP_EndCoroutine, regAddrB); /* Generate a subroutine that outputs the current row of the A ** select as the next output row of the compound select. */ VdbeNoopComment((v, "Output routine for A")); addrOutA = generateOutputSubroutine(pParse, p, &destA, pDest, regOutA, - regPrev, pKeyDup, P4_KEYINFO_HANDOFF, labelEnd); + regPrev, pKeyDup, labelEnd); /* Generate a subroutine that outputs the current row of the B ** select as the next output row of the compound select. */ if( op==TK_ALL || op==TK_UNION ){ VdbeNoopComment((v, "Output routine for B")); addrOutB = generateOutputSubroutine(pParse, p, &destB, pDest, regOutB, - regPrev, pKeyDup, P4_KEYINFO_STATIC, labelEnd); + regPrev, pKeyDup, labelEnd); } + sqlite3KeyInfoUnref(pKeyDup); /* Generate a subroutine to run when the results from select A ** are exhausted and only data in select B remains. */ - VdbeNoopComment((v, "eof-A subroutine")); if( op==TK_EXCEPT || op==TK_INTERSECT ){ - addrEofA = sqlite3VdbeAddOp2(v, OP_Goto, 0, labelEnd); + addrEofA_noB = addrEofA = labelEnd; }else{ - addrEofA = sqlite3VdbeAddOp2(v, OP_If, regEofB, labelEnd); - sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); - sqlite3VdbeAddOp1(v, OP_Yield, regAddrB); + VdbeNoopComment((v, "eof-A subroutine")); + addrEofA = sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); + addrEofA_noB = sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, labelEnd); + VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Goto, 0, addrEofA); p->nSelectRow += pPrior->nSelectRow; } /* Generate a subroutine to run when the results from select B @@ -100342,22 +108984,20 @@ if( op==TK_INTERSECT ){ addrEofB = addrEofA; if( p->nSelectRow > pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow; }else{ VdbeNoopComment((v, "eof-B subroutine")); - addrEofB = sqlite3VdbeAddOp2(v, OP_If, regEofA, labelEnd); - sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA); - sqlite3VdbeAddOp1(v, OP_Yield, regAddrA); + addrEofB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, labelEnd); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Goto, 0, addrEofB); } /* Generate code to handle the case of A<B */ VdbeNoopComment((v, "A-lt-B subroutine")); addrAltB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA); - sqlite3VdbeAddOp1(v, OP_Yield, regAddrA); - sqlite3VdbeAddOp2(v, OP_If, regEofA, addrEofA); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Goto, 0, labelCmpr); /* Generate code to handle the case of A==B */ if( op==TK_ALL ){ @@ -100366,12 +109006,11 @@ addrAeqB = addrAltB; addrAltB++; }else{ VdbeNoopComment((v, "A-eq-B subroutine")); addrAeqB = - sqlite3VdbeAddOp1(v, OP_Yield, regAddrA); - sqlite3VdbeAddOp2(v, OP_If, regEofA, addrEofA); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Goto, 0, labelCmpr); } /* Generate code to handle the case of A>B */ @@ -100378,32 +109017,27 @@ VdbeNoopComment((v, "A-gt-B subroutine")); addrAgtB = sqlite3VdbeCurrentAddr(v); if( op==TK_ALL || op==TK_UNION ){ sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); } - sqlite3VdbeAddOp1(v, OP_Yield, regAddrB); - sqlite3VdbeAddOp2(v, OP_If, regEofB, addrEofB); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Goto, 0, labelCmpr); /* This code runs once to initialize everything. */ sqlite3VdbeJumpHere(v, j1); - sqlite3VdbeAddOp2(v, OP_Integer, 0, regEofA); - sqlite3VdbeAddOp2(v, OP_Integer, 0, regEofB); - sqlite3VdbeAddOp2(v, OP_Gosub, regAddrA, addrSelectA); - sqlite3VdbeAddOp2(v, OP_Gosub, regAddrB, addrSelectB); - sqlite3VdbeAddOp2(v, OP_If, regEofA, addrEofA); - sqlite3VdbeAddOp2(v, OP_If, regEofB, addrEofB); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA_noB); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); /* Implement the main merge loop */ sqlite3VdbeResolveLabel(v, labelCmpr); sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY); sqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy, - (char*)pKeyMerge, P4_KEYINFO_HANDOFF); + (char*)pKeyMerge, P4_KEYINFO); sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE); - sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); + sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); VdbeCoverage(v); /* Jump to the this point in order to terminate the query. */ sqlite3VdbeResolveLabel(v, labelEnd); @@ -100419,10 +109053,11 @@ ** by the calling function */ if( p->pPrior ){ sqlite3SelectDelete(db, p->pPrior); } p->pPrior = pPrior; + pPrior->pNext = p; /*** TBD: Insert subroutine calls to close cursors on incomplete **** subqueries ****/ explainComposite(pParse, p->op, iSub1, iSub2, 0); return SQLITE_OK; @@ -100534,20 +109169,23 @@ ** This routine attempts to rewrite queries such as the above into ** a single flat select, like this: ** ** SELECT x+y AS a FROM t1 WHERE z<100 AND a>5 ** -** The code generated for this simpification gives the same result +** The code generated for this simplification gives the same result ** but only has to scan the data once. And because indices might ** exist on the table t1, a complete scan of the data might be ** avoided. ** ** Flattening is only attempted if all of the following are true: ** ** (1) The subquery and the outer query do not both use aggregates. ** -** (2) The subquery is not an aggregate or the outer query is not a join. +** (2) The subquery is not an aggregate or (2a) the outer query is not a join +** and (2b) the outer query does not use subqueries other than the one +** FROM-clause subquery that is a candidate for flattening. (2b is +** due to ticket [2f7170d73bf9abf80] from 2015-02-09.) ** ** (3) The subquery is not the right operand of a left outer join ** (Originally ticket #306. Strengthened by ticket #3300) ** ** (4) The subquery is not DISTINCT. @@ -100567,12 +109205,14 @@ ** (8) The subquery does not use LIMIT or the outer query is not a join. ** ** (9) The subquery does not use LIMIT or the outer query does not use ** aggregates. ** -** (10) The subquery does not use aggregates or the outer query does not -** use LIMIT. +** (**) Restriction (10) was removed from the code on 2005-02-05 but we +** accidently carried the comment forward until 2014-09-15. Original +** text: "The subquery does not use aggregates or the outer query does not +** use LIMIT." ** ** (11) The subquery and the outer query do not both have ORDER BY clauses. ** ** (**) Not implemented. Subsumed into restriction (3). Was previously ** a separate restriction deriving from ticket #350. @@ -100623,10 +109263,23 @@ ** appear as unmodified result columns in the outer query. But we ** have other optimizations in mind to deal with that case. ** ** (21) The subquery does not use LIMIT or the outer query is not ** DISTINCT. (See ticket [752e1646fc]). +** +** (22) The subquery is not a recursive CTE. +** +** (23) The parent is not a recursive CTE, or the sub-query is not a +** compound query. This restriction is because transforming the +** parent to a compound query confuses the code that handles +** recursive queries in multiSelect(). +** +** (24) The subquery is not an aggregate that uses the built-in min() or +** or max() functions. (Without this restriction, a query like: +** "SELECT x FROM (SELECT max(y), x FROM t1)" would not necessarily +** return the value X for which Y was maximal.) +** ** ** In this routine, the "p" parameter is a pointer to the outer query. ** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query ** uses aggregates and subqueryIsAgg is true if the subquery uses aggregates. ** @@ -100665,22 +109318,31 @@ assert( pSrc && iFrom>=0 && iFrom<pSrc->nSrc ); pSubitem = &pSrc->a[iFrom]; iParent = pSubitem->iCursor; pSub = pSubitem->pSelect; assert( pSub!=0 ); - if( isAgg && subqueryIsAgg ) return 0; /* Restriction (1) */ - if( subqueryIsAgg && pSrc->nSrc>1 ) return 0; /* Restriction (2) */ + if( subqueryIsAgg ){ + if( isAgg ) return 0; /* Restriction (1) */ + if( pSrc->nSrc>1 ) return 0; /* Restriction (2a) */ + if( (p->pWhere && ExprHasProperty(p->pWhere,EP_Subquery)) + || (sqlite3ExprListFlags(p->pEList) & EP_Subquery)!=0 + || (sqlite3ExprListFlags(p->pOrderBy) & EP_Subquery)!=0 + ){ + return 0; /* Restriction (2b) */ + } + } + pSubSrc = pSub->pSrc; assert( pSubSrc ); /* Prior to version 3.1.2, when LIMIT and OFFSET had to be simple constants, - ** not arbitrary expresssions, we allowed some combining of LIMIT and OFFSET + ** not arbitrary expressions, we allowed some combining of LIMIT and OFFSET ** because they could be computed at compile-time. But when LIMIT and OFFSET ** became arbitrary expressions, we were forced to add restrictions (13) ** and (14). */ if( pSub->pLimit && p->pLimit ) return 0; /* Restriction (13) */ if( pSub->pOffset ) return 0; /* Restriction (14) */ - if( p->pRightmost && pSub->pLimit ){ + if( (p->selFlags & SF_Compound)!=0 && pSub->pLimit ){ return 0; /* Restriction (15) */ } if( pSubSrc->nSrc==0 ) return 0; /* Restriction (7) */ if( pSub->selFlags & SF_Distinct ) return 0; /* Restriction (5) */ if( pSub->pLimit && (pSrc->nSrc>1 || isAgg) ){ @@ -100694,10 +109356,18 @@ } if( isAgg && pSub->pOrderBy ) return 0; /* Restriction (16) */ if( pSub->pLimit && p->pWhere ) return 0; /* Restriction (19) */ if( pSub->pLimit && (p->selFlags & SF_Distinct)!=0 ){ return 0; /* Restriction (21) */ + } + testcase( pSub->selFlags & SF_Recursive ); + testcase( pSub->selFlags & SF_MinMaxAgg ); + if( pSub->selFlags & (SF_Recursive|SF_MinMaxAgg) ){ + return 0; /* Restrictions (22) and (24) */ + } + if( (p->selFlags & SF_Recursive) && pSub->pPrior ){ + return 0; /* Restriction (23) */ } /* OBSOLETE COMMENT 1: ** Restriction 3: If the subquery is a join, make sure the subquery is ** not used as the right operand of an outer join. Examples of why this @@ -100762,16 +109432,18 @@ /* Restriction 18. */ if( p->pOrderBy ){ int ii; for(ii=0; ii<p->pOrderBy->nExpr; ii++){ - if( p->pOrderBy->a[ii].iOrderByCol==0 ) return 0; + if( p->pOrderBy->a[ii].u.x.iOrderByCol==0 ) return 0; } } } /***** If we reach this point, flattening is permitted. *****/ + SELECTTRACE(1,pParse,p,("flatten %s.%p from term %d\n", + pSub->zSelName, pSub, iFrom)); /* Authorize the subquery */ pParse->zAuthContext = pSubitem->zName; TESTONLY(i =) sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0); testcase( i==SQLITE_DENY ); @@ -100820,23 +109492,27 @@ p->pSrc = 0; p->pPrior = 0; p->pLimit = 0; p->pOffset = 0; pNew = sqlite3SelectDup(db, p, 0); + sqlite3SelectSetName(pNew, pSub->zSelName); p->pOffset = pOffset; p->pLimit = pLimit; p->pOrderBy = pOrderBy; p->pSrc = pSrc; p->op = TK_ALL; - p->pRightmost = 0; if( pNew==0 ){ - pNew = pPrior; + p->pPrior = pPrior; }else{ pNew->pPrior = pPrior; - pNew->pRightmost = 0; + if( pPrior ) pPrior->pNext = pNew; + pNew->pNext = p; + p->pPrior = pNew; + SELECTTRACE(2,pParse,p, + ("compound-subquery flattener creates %s.%p as peer\n", + pNew->zSelName, pNew)); } - p->pPrior = pNew; if( db->mallocFailed ) return 1; } /* Begin flattening the iFrom-th entry of the FROM clause ** in the outer query. @@ -100961,12 +109637,27 @@ if( isAgg ){ substExprList(db, pParent->pGroupBy, iParent, pSub->pEList); pParent->pHaving = substExpr(db, pParent->pHaving, iParent, pSub->pEList); } if( pSub->pOrderBy ){ + /* At this point, any non-zero iOrderByCol values indicate that the + ** ORDER BY column expression is identical to the iOrderByCol'th + ** expression returned by SELECT statement pSub. Since these values + ** do not necessarily correspond to columns in SELECT statement pParent, + ** zero them before transfering the ORDER BY clause. + ** + ** Not doing this may cause an error if a subsequent call to this + ** function attempts to flatten a compound sub-query into pParent + ** (the only way this can happen is if the compound sub-query is + ** currently part of pSub->pSrc). See ticket [d11a6e908f]. */ + ExprList *pOrderBy = pSub->pOrderBy; + for(i=0; i<pOrderBy->nExpr; i++){ + pOrderBy->a[i].u.x.iOrderByCol = 0; + } assert( pParent->pOrderBy==0 ); - pParent->pOrderBy = pSub->pOrderBy; + assert( pSub->pPrior==0 ); + pParent->pOrderBy = pOrderBy; pSub->pOrderBy = 0; }else if( pParent->pOrderBy ){ substExprList(db, pParent->pOrderBy, iParent, pSub->pEList); } if( pSub->pWhere ){ @@ -101007,10 +109698,17 @@ /* Finially, delete what is left of the subquery and return ** success. */ sqlite3SelectDelete(db, pSub1); + +#if SELECTTRACE_ENABLED + if( sqlite3SelectTrace & 0x100 ){ + sqlite3DebugPrintf("After flattening:\n"); + sqlite3TreeViewSelect(0, p, 0); + } +#endif return 1; } #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ @@ -101054,11 +109752,11 @@ return eRet; } /* ** The select statement passed as the first argument is an aggregate query. -** The second argment is the associated aggregate-info object. This +** The second argument is the associated aggregate-info object. This ** function tests if the SELECT is of the form: ** ** SELECT count(*) FROM <tbl> ** ** where table is a database table, not a sub-select or view. If the query @@ -101171,14 +109869,210 @@ p->pWhere = 0; pNew->pGroupBy = 0; pNew->pHaving = 0; pNew->pOrderBy = 0; p->pPrior = 0; + p->pNext = 0; + p->selFlags &= ~SF_Compound; + assert( pNew->pPrior!=0 ); + pNew->pPrior->pNext = pNew; pNew->pLimit = 0; pNew->pOffset = 0; return WRC_Continue; } + +#ifndef SQLITE_OMIT_CTE +/* +** Argument pWith (which may be NULL) points to a linked list of nested +** WITH contexts, from inner to outermost. If the table identified by +** FROM clause element pItem is really a common-table-expression (CTE) +** then return a pointer to the CTE definition for that table. Otherwise +** return NULL. +** +** If a non-NULL value is returned, set *ppContext to point to the With +** object that the returned CTE belongs to. +*/ +static struct Cte *searchWith( + With *pWith, /* Current outermost WITH clause */ + struct SrcList_item *pItem, /* FROM clause element to resolve */ + With **ppContext /* OUT: WITH clause return value belongs to */ +){ + const char *zName; + if( pItem->zDatabase==0 && (zName = pItem->zName)!=0 ){ + With *p; + for(p=pWith; p; p=p->pOuter){ + int i; + for(i=0; i<p->nCte; i++){ + if( sqlite3StrICmp(zName, p->a[i].zName)==0 ){ + *ppContext = p; + return &p->a[i]; + } + } + } + } + return 0; +} + +/* The code generator maintains a stack of active WITH clauses +** with the inner-most WITH clause being at the top of the stack. +** +** This routine pushes the WITH clause passed as the second argument +** onto the top of the stack. If argument bFree is true, then this +** WITH clause will never be popped from the stack. In this case it +** should be freed along with the Parse object. In other cases, when +** bFree==0, the With object will be freed along with the SELECT +** statement with which it is associated. +*/ +SQLITE_PRIVATE void sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ + assert( bFree==0 || pParse->pWith==0 ); + if( pWith ){ + pWith->pOuter = pParse->pWith; + pParse->pWith = pWith; + pParse->bFreeWith = bFree; + } +} + +/* +** This function checks if argument pFrom refers to a CTE declared by +** a WITH clause on the stack currently maintained by the parser. And, +** if currently processing a CTE expression, if it is a recursive +** reference to the current CTE. +** +** If pFrom falls into either of the two categories above, pFrom->pTab +** and other fields are populated accordingly. The caller should check +** (pFrom->pTab!=0) to determine whether or not a successful match +** was found. +** +** Whether or not a match is found, SQLITE_OK is returned if no error +** occurs. If an error does occur, an error message is stored in the +** parser and some error code other than SQLITE_OK returned. +*/ +static int withExpand( + Walker *pWalker, + struct SrcList_item *pFrom +){ + Parse *pParse = pWalker->pParse; + sqlite3 *db = pParse->db; + struct Cte *pCte; /* Matched CTE (or NULL if no match) */ + With *pWith; /* WITH clause that pCte belongs to */ + + assert( pFrom->pTab==0 ); + + pCte = searchWith(pParse->pWith, pFrom, &pWith); + if( pCte ){ + Table *pTab; + ExprList *pEList; + Select *pSel; + Select *pLeft; /* Left-most SELECT statement */ + int bMayRecursive; /* True if compound joined by UNION [ALL] */ + With *pSavedWith; /* Initial value of pParse->pWith */ + + /* If pCte->zErr is non-NULL at this point, then this is an illegal + ** recursive reference to CTE pCte. Leave an error in pParse and return + ** early. If pCte->zErr is NULL, then this is not a recursive reference. + ** In this case, proceed. */ + if( pCte->zErr ){ + sqlite3ErrorMsg(pParse, pCte->zErr, pCte->zName); + return SQLITE_ERROR; + } + + assert( pFrom->pTab==0 ); + pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table)); + if( pTab==0 ) return WRC_Abort; + pTab->nRef = 1; + pTab->zName = sqlite3DbStrDup(db, pCte->zName); + pTab->iPKey = -1; + pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); + pTab->tabFlags |= TF_Ephemeral; + pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0); + if( db->mallocFailed ) return SQLITE_NOMEM; + assert( pFrom->pSelect ); + + /* Check if this is a recursive CTE. */ + pSel = pFrom->pSelect; + bMayRecursive = ( pSel->op==TK_ALL || pSel->op==TK_UNION ); + if( bMayRecursive ){ + int i; + SrcList *pSrc = pFrom->pSelect->pSrc; + for(i=0; i<pSrc->nSrc; i++){ + struct SrcList_item *pItem = &pSrc->a[i]; + if( pItem->zDatabase==0 + && pItem->zName!=0 + && 0==sqlite3StrICmp(pItem->zName, pCte->zName) + ){ + pItem->pTab = pTab; + pItem->isRecursive = 1; + pTab->nRef++; + pSel->selFlags |= SF_Recursive; + } + } + } + + /* Only one recursive reference is permitted. */ + if( pTab->nRef>2 ){ + sqlite3ErrorMsg( + pParse, "multiple references to recursive table: %s", pCte->zName + ); + return SQLITE_ERROR; + } + assert( pTab->nRef==1 || ((pSel->selFlags&SF_Recursive) && pTab->nRef==2 )); + + pCte->zErr = "circular reference: %s"; + pSavedWith = pParse->pWith; + pParse->pWith = pWith; + sqlite3WalkSelect(pWalker, bMayRecursive ? pSel->pPrior : pSel); + + for(pLeft=pSel; pLeft->pPrior; pLeft=pLeft->pPrior); + pEList = pLeft->pEList; + if( pCte->pCols ){ + if( pEList && pEList->nExpr!=pCte->pCols->nExpr ){ + sqlite3ErrorMsg(pParse, "table %s has %d values for %d columns", + pCte->zName, pEList->nExpr, pCte->pCols->nExpr + ); + pParse->pWith = pSavedWith; + return SQLITE_ERROR; + } + pEList = pCte->pCols; + } + + selectColumnsFromExprList(pParse, pEList, &pTab->nCol, &pTab->aCol); + if( bMayRecursive ){ + if( pSel->selFlags & SF_Recursive ){ + pCte->zErr = "multiple recursive references: %s"; + }else{ + pCte->zErr = "recursive reference in a subquery: %s"; + } + sqlite3WalkSelect(pWalker, pSel); + } + pCte->zErr = 0; + pParse->pWith = pSavedWith; + } + + return SQLITE_OK; +} +#endif + +#ifndef SQLITE_OMIT_CTE +/* +** If the SELECT passed as the second argument has an associated WITH +** clause, pop it from the stack stored as part of the Parse object. +** +** This function is used as the xSelectCallback2() callback by +** sqlite3SelectExpand() when walking a SELECT tree to resolve table +** names and other FROM clause elements. +*/ +static void selectPopWith(Walker *pWalker, Select *p){ + Parse *pParse = pWalker->pParse; + With *pWith = findRightmost(p)->pWith; + if( pWith!=0 ){ + assert( pParse->pWith==pWith ); + pParse->pWith = pWith->pOuter; + } +} +#else +#define selectPopWith 0 +#endif /* ** This routine is a Walker callback for "expanding" a SELECT statement. ** "Expanding" means to do the following: ** @@ -101188,14 +110082,14 @@ ** (2) Fill in the pTabList->a[].pTab fields in the SrcList that ** defines FROM clause. When views appear in the FROM clause, ** fill pTabList->a[].pSelect with a copy of the SELECT statement ** that implements the view. A copy is made of the view's SELECT ** statement so that we can freely modify or delete that statement -** without worrying about messing up the presistent representation +** without worrying about messing up the persistent representation ** of the view. ** -** (3) Add terms to the WHERE clause to accomodate the NATURAL keyword +** (3) Add terms to the WHERE clause to accommodate the NATURAL keyword ** on joins and the ON and USING clause of joins. ** ** (4) Scan the list of columns in the result set (pEList) looking ** for instances of the "*" operator or the TABLE.* operator. ** If found, expand each "*" to be every column in every table @@ -101219,10 +110113,13 @@ if( NEVER(p->pSrc==0) || (selFlags & SF_Expanded)!=0 ){ return WRC_Prune; } pTabList = p->pSrc; pEList = p->pEList; + if( pWalker->xSelectCallback2==selectPopWith ){ + sqlite3WithPush(pParse, findRightmost(p)->pWith, 0); + } /* Make sure cursor numbers have been assigned to all entries in ** the FROM clause of the SELECT statement. */ sqlite3SrcListAssignCursors(pParse, pTabList); @@ -101231,31 +110128,40 @@ ** an entry of the FROM clause is a subquery instead of a table or view, ** then create a transient table structure to describe the subquery. */ for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){ Table *pTab; + assert( pFrom->isRecursive==0 || pFrom->pTab ); + if( pFrom->isRecursive ) continue; if( pFrom->pTab!=0 ){ /* This statement has already been prepared. There is no need ** to go further. */ assert( i==0 ); +#ifndef SQLITE_OMIT_CTE + selectPopWith(pWalker, p); +#endif return WRC_Prune; } +#ifndef SQLITE_OMIT_CTE + if( withExpand(pWalker, pFrom) ) return WRC_Abort; + if( pFrom->pTab ) {} else +#endif if( pFrom->zName==0 ){ #ifndef SQLITE_OMIT_SUBQUERY Select *pSel = pFrom->pSelect; /* A sub-query in the FROM clause of a SELECT */ assert( pSel!=0 ); assert( pFrom->pTab==0 ); - sqlite3WalkSelect(pWalker, pSel); + if( sqlite3WalkSelect(pWalker, pSel) ) return WRC_Abort; pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table)); if( pTab==0 ) return WRC_Abort; pTab->nRef = 1; - pTab->zName = sqlite3MPrintf(db, "sqlite_subquery_%p_", (void*)pTab); + pTab->zName = sqlite3MPrintf(db, "sqlite_sq_%p", (void*)pTab); while( pSel->pPrior ){ pSel = pSel->pPrior; } selectColumnsFromExprList(pParse, pSel->pEList, &pTab->nCol, &pTab->aCol); pTab->iPKey = -1; - pTab->nRowEst = 1000000; + pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); pTab->tabFlags |= TF_Ephemeral; #endif }else{ /* An ordinary table or view name in the FROM clause */ assert( pFrom->pTab==0 ); @@ -101272,10 +110178,11 @@ if( pTab->pSelect || IsVirtual(pTab) ){ /* We reach here if the named table is a really a view */ if( sqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort; assert( pFrom->pSelect==0 ); pFrom->pSelect = sqlite3SelectDup(db, pTab->pSelect, 0); + sqlite3SelectSetName(pFrom->pSelect, pTab->zName); sqlite3WalkSelect(pWalker, pFrom->pSelect); } #endif } @@ -101499,10 +110406,13 @@ if( pParse->hasCompound ){ w.xSelectCallback = convertCompoundSelectToSubquery; sqlite3WalkSelect(&w, pSelect); } w.xSelectCallback = selectExpander; + if( (pSelect->selFlags & SF_AllValues)==0 ){ + w.xSelectCallback2 = selectPopWith; + } sqlite3WalkSelect(&w, pSelect); } #ifndef SQLITE_OMIT_SUBQUERY @@ -101517,11 +110427,11 @@ ** The Table structure that represents the result set was constructed ** by selectExpander() but the type and collation information was omitted ** at that point because identifiers had not yet been resolved. This ** routine is called after identifier resolution. */ -static int selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ +static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ Parse *pParse; int i; SrcList *pTabList; struct SrcList_item *pFrom; @@ -101533,17 +110443,17 @@ for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){ Table *pTab = pFrom->pTab; if( ALWAYS(pTab!=0) && (pTab->tabFlags & TF_Ephemeral)!=0 ){ /* A sub-query in the FROM clause of a SELECT */ Select *pSel = pFrom->pSelect; - assert( pSel ); - while( pSel->pPrior ) pSel = pSel->pPrior; - selectAddColumnTypeAndCollation(pParse, pTab->nCol, pTab->aCol, pSel); + if( pSel ){ + while( pSel->pPrior ) pSel = pSel->pPrior; + selectAddColumnTypeAndCollation(pParse, pTab, pSel); + } } } } - return WRC_Continue; } #endif /* @@ -101555,14 +110465,13 @@ */ static void sqlite3SelectAddTypeInfo(Parse *pParse, Select *pSelect){ #ifndef SQLITE_OMIT_SUBQUERY Walker w; memset(&w, 0, sizeof(w)); - w.xSelectCallback = selectAddSubqueryTypeInfo; + w.xSelectCallback2 = selectAddSubqueryTypeInfo; w.xExprCallback = exprWalkNoop; w.pParse = pParse; - w.bSelectDepthFirst = 1; sqlite3WalkSelect(&w, pSelect); #endif } @@ -101605,29 +110514,38 @@ */ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ Vdbe *v = pParse->pVdbe; int i; struct AggInfo_func *pFunc; - if( pAggInfo->nFunc+pAggInfo->nColumn==0 ){ - return; - } + int nReg = pAggInfo->nFunc + pAggInfo->nColumn; + if( nReg==0 ) return; +#ifdef SQLITE_DEBUG + /* Verify that all AggInfo registers are within the range specified by + ** AggInfo.mnReg..AggInfo.mxReg */ + assert( nReg==pAggInfo->mxReg-pAggInfo->mnReg+1 ); for(i=0; i<pAggInfo->nColumn; i++){ - sqlite3VdbeAddOp2(v, OP_Null, 0, pAggInfo->aCol[i].iMem); + assert( pAggInfo->aCol[i].iMem>=pAggInfo->mnReg + && pAggInfo->aCol[i].iMem<=pAggInfo->mxReg ); } + for(i=0; i<pAggInfo->nFunc; i++){ + assert( pAggInfo->aFunc[i].iMem>=pAggInfo->mnReg + && pAggInfo->aFunc[i].iMem<=pAggInfo->mxReg ); + } +#endif + sqlite3VdbeAddOp3(v, OP_Null, 0, pAggInfo->mnReg, pAggInfo->mxReg); for(pFunc=pAggInfo->aFunc, i=0; i<pAggInfo->nFunc; i++, pFunc++){ - sqlite3VdbeAddOp2(v, OP_Null, 0, pFunc->iMem); if( pFunc->iDistinct>=0 ){ Expr *pE = pFunc->pExpr; assert( !ExprHasProperty(pE, EP_xIsSelect) ); if( pE->x.pList==0 || pE->x.pList->nExpr!=1 ){ sqlite3ErrorMsg(pParse, "DISTINCT aggregates must have exactly one " "argument"); pFunc->iDistinct = -1; }else{ - KeyInfo *pKeyInfo = keyInfoFromExprList(pParse, pE->x.pList); + KeyInfo *pKeyInfo = keyInfoFromExprList(pParse, pE->x.pList, 0, 0); sqlite3VdbeAddOp4(v, OP_OpenEphemeral, pFunc->iDistinct, 0, 0, - (char*)pKeyInfo, P4_KEYINFO_HANDOFF); + (char*)pKeyInfo, P4_KEYINFO); } } } } @@ -101658,21 +110576,20 @@ int addrHitTest = 0; struct AggInfo_func *pF; struct AggInfo_col *pC; pAggInfo->directMode = 1; - sqlite3ExprCacheClear(pParse); for(i=0, pF=pAggInfo->aFunc; i<pAggInfo->nFunc; i++, pF++){ int nArg; int addrNext = 0; int regAgg; ExprList *pList = pF->pExpr->x.pList; assert( !ExprHasProperty(pF->pExpr, EP_xIsSelect) ); if( pList ){ nArg = pList->nExpr; regAgg = sqlite3GetTempRange(pParse, nArg); - sqlite3ExprCodeExprList(pParse, pList, regAgg, 1); + sqlite3ExprCodeExprList(pParse, pList, regAgg, SQLITE_ECEL_DUP); }else{ nArg = 0; regAgg = 0; } if( pF->iDistinct>=0 ){ @@ -101714,11 +110631,11 @@ ** ** Another solution would be to change the OP_SCopy used to copy cached ** values to an OP_Copy. */ if( regHit ){ - addrHitTest = sqlite3VdbeAddOp1(v, OP_If, regHit); + addrHitTest = sqlite3VdbeAddOp1(v, OP_If, regHit); VdbeCoverage(v); } sqlite3ExprCacheClear(pParse); for(i=0, pC=pAggInfo->aCol; i<pAggInfo->nAccumulator; i++, pC++){ sqlite3ExprCode(pParse, pC->pExpr, pC->iMem); } @@ -101738,14 +110655,15 @@ Parse *pParse, /* Parse context */ Table *pTab, /* Table being queried */ Index *pIdx /* Index used to optimize scan, or NULL */ ){ if( pParse->explain==2 ){ + int bCover = (pIdx!=0 && (HasRowid(pTab) || !IsPrimaryKeyIndex(pIdx))); char *zEqp = sqlite3MPrintf(pParse->db, "SCAN TABLE %s%s%s", - pTab->zName, - pIdx ? " USING COVERING INDEX " : "", - pIdx ? pIdx->zName : "" + pTab->zName, + bCover ? " USING COVERING INDEX " : "", + bCover ? pIdx->zName : "" ); sqlite3VdbeAddOp4( pParse->pVdbe, OP_Explain, pParse->iSelectId, 0, 0, zEqp, P4_DYNAMIC ); } @@ -101755,54 +110673,12 @@ #endif /* ** Generate code for the SELECT statement given in the p argument. ** -** The results are distributed in various ways depending on the -** contents of the SelectDest structure pointed to by argument pDest -** as follows: -** -** pDest->eDest Result -** ------------ ------------------------------------------- -** SRT_Output Generate a row of output (using the OP_ResultRow -** opcode) for each row in the result set. -** -** SRT_Mem Only valid if the result is a single column. -** Store the first column of the first result row -** in register pDest->iSDParm then abandon the rest -** of the query. This destination implies "LIMIT 1". -** -** SRT_Set The result must be a single column. Store each -** row of result as the key in table pDest->iSDParm. -** Apply the affinity pDest->affSdst before storing -** results. Used to implement "IN (SELECT ...)". -** -** SRT_Union Store results as a key in a temporary table -** identified by pDest->iSDParm. -** -** SRT_Except Remove results from the temporary table pDest->iSDParm. -** -** SRT_Table Store results in temporary table pDest->iSDParm. -** This is like SRT_EphemTab except that the table -** is assumed to already be open. -** -** SRT_EphemTab Create an temporary table pDest->iSDParm and store -** the result there. The cursor is left open after -** returning. This is like SRT_Table except that -** this destination uses OP_OpenEphemeral to create -** the table first. -** -** SRT_Coroutine Generate a co-routine that returns a new row of -** results each time it is invoked. The entry point -** of the co-routine is stored in register pDest->iSDParm. -** -** SRT_Exists Store a 1 in memory cell pDest->iSDParm if the result -** set is not empty. -** -** SRT_Discard Throw the results away. This is used by SELECT -** statements within triggers whose only purpose is -** the side-effects of functions. +** The results are returned according to the SelectDest structure. +** See comments in sqliteInt.h for further information. ** ** This routine returns the number of errors. If any errors are ** encountered, then an appropriate error message is left in ** pParse->zErrMsg. ** @@ -101819,16 +110695,15 @@ Vdbe *v; /* The virtual machine under construction */ int isAgg; /* True for select lists like "count(*)" */ ExprList *pEList; /* List of columns to extract. */ SrcList *pTabList; /* List of tables to select from */ Expr *pWhere; /* The WHERE clause. May be NULL */ - ExprList *pOrderBy; /* The ORDER BY clause. May be NULL */ ExprList *pGroupBy; /* The GROUP BY clause. May be NULL */ Expr *pHaving; /* The HAVING clause. May be NULL */ int rc = 1; /* Value to return from this function */ - int addrSortIndex; /* Address of an OP_OpenEphemeral instruction */ DistinctCtx sDistinct; /* Info on how to code the DISTINCT keyword */ + SortCtx sSort; /* Info on how to code the ORDER BY clause */ AggInfo sAggInfo; /* Information used by aggregate queries */ int iEnd; /* Address of the end of the query */ sqlite3 *db; /* The database connection */ #ifndef SQLITE_OMIT_EXPLAIN @@ -101840,29 +110715,50 @@ if( p==0 || db->mallocFailed || pParse->nErr ){ return 1; } if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1; memset(&sAggInfo, 0, sizeof(sAggInfo)); +#if SELECTTRACE_ENABLED + pParse->nSelectIndent++; + SELECTTRACE(1,pParse,p, ("begin processing:\n")); + if( sqlite3SelectTrace & 0x100 ){ + sqlite3TreeViewSelect(0, p, 0); + } +#endif + assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistFifo ); + assert( p->pOrderBy==0 || pDest->eDest!=SRT_Fifo ); + assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistQueue ); + assert( p->pOrderBy==0 || pDest->eDest!=SRT_Queue ); if( IgnorableOrderby(pDest) ){ assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union || - pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard); + pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard || + pDest->eDest==SRT_Queue || pDest->eDest==SRT_DistFifo || + pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_Fifo); /* If ORDER BY makes no difference in the output then neither does ** DISTINCT so it can be removed too. */ sqlite3ExprListDelete(db, p->pOrderBy); p->pOrderBy = 0; p->selFlags &= ~SF_Distinct; } sqlite3SelectPrep(pParse, p, 0); - pOrderBy = p->pOrderBy; + memset(&sSort, 0, sizeof(sSort)); + sSort.pOrderBy = p->pOrderBy; pTabList = p->pSrc; pEList = p->pEList; if( pParse->nErr || db->mallocFailed ){ goto select_end; } isAgg = (p->selFlags & SF_Aggregate)!=0; assert( pEList!=0 ); +#if SELECTTRACE_ENABLED + if( sqlite3SelectTrace & 0x100 ){ + SELECTTRACE(0x100,pParse,p, ("after name resolution:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + /* Begin generating code. */ v = sqlite3GetVdbe(pParse); if( v==0 ) goto select_end; @@ -101915,46 +110811,28 @@ if( isAggSub ){ isAgg = 1; p->selFlags |= SF_Aggregate; } i = -1; - }else if( pTabList->nSrc==1 && (p->selFlags & SF_Materialize)==0 - && OptimizationEnabled(db, SQLITE_SubqCoroutine) + }else if( pTabList->nSrc==1 + && OptimizationEnabled(db, SQLITE_SubqCoroutine) ){ /* Implement a co-routine that will return a single row of the result ** set on each invocation. */ - int addrTop; - int addrEof; + int addrTop = sqlite3VdbeCurrentAddr(v)+1; pItem->regReturn = ++pParse->nMem; - addrEof = ++pParse->nMem; - /* Before coding the OP_Goto to jump to the start of the main routine, - ** ensure that the jump to the verify-schema routine has already - ** been coded. Otherwise, the verify-schema would likely be coded as - ** part of the co-routine. If the main routine then accessed the - ** database before invoking the co-routine for the first time (for - ** example to initialize a LIMIT register from a sub-select), it would - ** be doing so without having verified the schema version and obtained - ** the required db locks. See ticket d6b36be38. */ - sqlite3CodeVerifySchema(pParse, -1); - sqlite3VdbeAddOp0(v, OP_Goto); - addrTop = sqlite3VdbeAddOp1(v, OP_OpenPseudo, pItem->iCursor); - sqlite3VdbeChangeP5(v, 1); - VdbeComment((v, "coroutine for %s", pItem->pTab->zName)); + sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop); + VdbeComment((v, "%s", pItem->pTab->zName)); pItem->addrFillSub = addrTop; - sqlite3VdbeAddOp2(v, OP_Integer, 0, addrEof); - sqlite3VdbeChangeP5(v, 1); sqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn); explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId); sqlite3Select(pParse, pSub, &dest); - pItem->pTab->nRowEst = (unsigned)pSub->nSelectRow; + pItem->pTab->nRowLogEst = sqlite3LogEst(pSub->nSelectRow); pItem->viaCoroutine = 1; - sqlite3VdbeChangeP2(v, addrTop, dest.iSdst); - sqlite3VdbeChangeP3(v, addrTop, dest.nSdst); - sqlite3VdbeAddOp2(v, OP_Integer, 1, addrEof); - sqlite3VdbeAddOp1(v, OP_Yield, pItem->regReturn); - VdbeComment((v, "end %s", pItem->pTab->zName)); + pItem->regResult = dest.iSdst; + sqlite3VdbeAddOp1(v, OP_EndCoroutine, pItem->regReturn); sqlite3VdbeJumpHere(v, addrTop-1); sqlite3ClearTempRegCache(pParse); }else{ /* Generate a subroutine that will fill an ephemeral table with ** the content of this subquery. pItem->addrFillSub will point @@ -101966,21 +110844,23 @@ int retAddr; assert( pItem->addrFillSub==0 ); pItem->regReturn = ++pParse->nMem; topAddr = sqlite3VdbeAddOp2(v, OP_Integer, 0, pItem->regReturn); pItem->addrFillSub = topAddr+1; - VdbeNoopComment((v, "materialize %s", pItem->pTab->zName)); if( pItem->isCorrelated==0 ){ /* If the subquery is not correlated and if we are not inside of ** a trigger, then we only need to compute the value of the subquery ** once. */ - onceAddr = sqlite3CodeOnce(pParse); + onceAddr = sqlite3CodeOnce(pParse); VdbeCoverage(v); + VdbeComment((v, "materialize \"%s\"", pItem->pTab->zName)); + }else{ + VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName)); } sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId); sqlite3Select(pParse, pSub, &dest); - pItem->pTab->nRowEst = (unsigned)pSub->nSelectRow; + pItem->pTab->nRowLogEst = sqlite3LogEst(pSub->nSelectRow); if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr); retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn); VdbeComment((v, "end %s", pItem->pTab->zName)); sqlite3VdbeChangeP1(v, topAddr, retAddr); sqlite3ClearTempRegCache(pParse); @@ -101989,11 +110869,11 @@ goto select_end; } pParse->nHeight -= sqlite3SelectExprHeight(p); pTabList = p->pSrc; if( !IgnorableOrderby(pDest) ){ - pOrderBy = p->pOrderBy; + sSort.pOrderBy = p->pOrderBy; } } pEList = p->pEList; #endif pWhere = p->pWhere; @@ -102003,65 +110883,41 @@ #ifndef SQLITE_OMIT_COMPOUND_SELECT /* If there is are a sequence of queries, do the earlier ones first. */ if( p->pPrior ){ - if( p->pRightmost==0 ){ - Select *pLoop, *pRight = 0; - int cnt = 0; - int mxSelect; - for(pLoop=p; pLoop; pLoop=pLoop->pPrior, cnt++){ - pLoop->pRightmost = p; - pLoop->pNext = pRight; - pRight = pLoop; - } - mxSelect = db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT]; - if( mxSelect && cnt>mxSelect ){ - sqlite3ErrorMsg(pParse, "too many terms in compound SELECT"); - goto select_end; - } - } rc = multiSelect(pParse, p, pDest); explainSetInteger(pParse->iSelectId, iRestoreSelectId); +#if SELECTTRACE_ENABLED + SELECTTRACE(1,pParse,p,("end compound-select processing\n")); + pParse->nSelectIndent--; +#endif return rc; } #endif - /* If there is both a GROUP BY and an ORDER BY clause and they are - ** identical, then disable the ORDER BY clause since the GROUP BY - ** will cause elements to come out in the correct order. This is - ** an optimization - the correct answer should result regardless. - ** Use the SQLITE_GroupByOrder flag with SQLITE_TESTCTRL_OPTIMIZER - ** to disable this optimization for testing purposes. - */ - if( sqlite3ExprListCompare(p->pGroupBy, pOrderBy, -1)==0 - && OptimizationEnabled(db, SQLITE_GroupByOrder) ){ - pOrderBy = 0; - } - /* If the query is DISTINCT with an ORDER BY but is not an aggregate, and ** if the select-list is the same as the ORDER BY list, then this query ** can be rewritten as a GROUP BY. In other words, this: ** ** SELECT DISTINCT xyz FROM ... ORDER BY xyz ** ** is transformed to: ** - ** SELECT xyz FROM ... GROUP BY xyz + ** SELECT xyz FROM ... GROUP BY xyz ORDER BY xyz ** ** The second form is preferred as a single index (or temp-table) may be ** used for both the ORDER BY and DISTINCT processing. As originally ** written the query must use a temp-table for at least one of the ORDER ** BY and DISTINCT, and an index or separate temp-table for the other. */ if( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct - && sqlite3ExprListCompare(pOrderBy, p->pEList, -1)==0 + && sqlite3ExprListCompare(sSort.pOrderBy, p->pEList, -1)==0 ){ p->selFlags &= ~SF_Distinct; p->pGroupBy = sqlite3ExprListDup(db, p->pEList, 0); pGroupBy = p->pGroupBy; - pOrderBy = 0; /* Notice that even thought SF_Distinct has been cleared from p->selFlags, ** the sDistinct.isTnct is still set. Hence, isTnct represents the ** original setting of the SF_Distinct flag, not the current setting */ assert( sDistinct.isTnct ); } @@ -102071,20 +110927,21 @@ ** extracted in pre-sorted order. If that is the case, then the ** OP_OpenEphemeral instruction will be changed to an OP_Noop once ** we figure out that the sorting index is not needed. The addrSortIndex ** variable is used to facilitate that change. */ - if( pOrderBy ){ + if( sSort.pOrderBy ){ KeyInfo *pKeyInfo; - pKeyInfo = keyInfoFromExprList(pParse, pOrderBy); - pOrderBy->iECursor = pParse->nTab++; - p->addrOpenEphm[2] = addrSortIndex = + pKeyInfo = keyInfoFromExprList(pParse, sSort.pOrderBy, 0, pEList->nExpr); + sSort.iECursor = pParse->nTab++; + sSort.addrSortIndex = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, - pOrderBy->iECursor, pOrderBy->nExpr+2, 0, - (char*)pKeyInfo, P4_KEYINFO_HANDOFF); + sSort.iECursor, sSort.pOrderBy->nExpr+1+pEList->nExpr, 0, + (char*)pKeyInfo, P4_KEYINFO + ); }else{ - addrSortIndex = -1; + sSort.addrSortIndex = -1; } /* If the output is destined for a temporary table, open that table. */ if( pDest->eDest==SRT_EphemTab ){ @@ -102094,23 +110951,23 @@ /* Set the limiter. */ iEnd = sqlite3VdbeMakeLabel(v); p->nSelectRow = LARGEST_INT64; computeLimitRegisters(pParse, p, iEnd); - if( p->iLimit==0 && addrSortIndex>=0 ){ - sqlite3VdbeGetOp(v, addrSortIndex)->opcode = OP_SorterOpen; - p->selFlags |= SF_UseSorter; + if( p->iLimit==0 && sSort.addrSortIndex>=0 ){ + sqlite3VdbeGetOp(v, sSort.addrSortIndex)->opcode = OP_SorterOpen; + sSort.sortFlags |= SORTFLAG_UseSorter; } /* Open a virtual index to use for the distinct set. */ if( p->selFlags & SF_Distinct ){ sDistinct.tabTnct = pParse->nTab++; sDistinct.addrTnct = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, sDistinct.tabTnct, 0, 0, - (char*)keyInfoFromExprList(pParse, p->pEList), - P4_KEYINFO_HANDOFF); + (char*)keyInfoFromExprList(pParse, p->pEList,0,0), + P4_KEYINFO); sqlite3VdbeChangeP5(v, BTREE_UNORDERED); sDistinct.eTnctType = WHERE_DISTINCT_UNORDERED; }else{ sDistinct.eTnctType = WHERE_DISTINCT_NOOP; } @@ -102118,32 +110975,36 @@ if( !isAgg && pGroupBy==0 ){ /* No aggregate functions and no GROUP BY clause */ u16 wctrlFlags = (sDistinct.isTnct ? WHERE_WANT_DISTINCT : 0); /* Begin the database scan. */ - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pOrderBy, p->pEList, - wctrlFlags, 0); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy, + p->pEList, wctrlFlags, 0); if( pWInfo==0 ) goto select_end; if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){ p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo); } if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){ sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo); } - if( pOrderBy && sqlite3WhereIsOrdered(pWInfo) ) pOrderBy = 0; + if( sSort.pOrderBy ){ + sSort.nOBSat = sqlite3WhereIsOrdered(pWInfo); + if( sSort.nOBSat==sSort.pOrderBy->nExpr ){ + sSort.pOrderBy = 0; + } + } /* If sorting index that was created by a prior OP_OpenEphemeral ** instruction ended up not being needed, then change the OP_OpenEphemeral ** into an OP_Noop. */ - if( addrSortIndex>=0 && pOrderBy==0 ){ - sqlite3VdbeChangeToNoop(v, addrSortIndex); - p->addrOpenEphm[2] = -1; + if( sSort.addrSortIndex>=0 && sSort.pOrderBy==0 ){ + sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex); } /* Use the standard inner loop. */ - selectInnerLoop(pParse, p, pEList, 0, 0, pOrderBy, &sDistinct, pDest, + selectInnerLoop(pParse, p, pEList, -1, &sSort, &sDistinct, pDest, sqlite3WhereContinueLabel(pWInfo), sqlite3WhereBreakLabel(pWInfo)); /* End the database scan loop. */ @@ -102160,29 +111021,42 @@ int iAbortFlag; /* Mem address which causes query abort if positive */ int groupBySort; /* Rows come from source in GROUP BY order */ int addrEnd; /* End of processing for this SELECT */ int sortPTab = 0; /* Pseudotable used to decode sorting results */ int sortOut = 0; /* Output register from the sorter */ + int orderByGrp = 0; /* True if the GROUP BY and ORDER BY are the same */ /* Remove any and all aliases between the result set and the ** GROUP BY clause. */ if( pGroupBy ){ int k; /* Loop counter */ struct ExprList_item *pItem; /* For looping over expression in a list */ for(k=p->pEList->nExpr, pItem=p->pEList->a; k>0; k--, pItem++){ - pItem->iAlias = 0; + pItem->u.x.iAlias = 0; } for(k=pGroupBy->nExpr, pItem=pGroupBy->a; k>0; k--, pItem++){ - pItem->iAlias = 0; + pItem->u.x.iAlias = 0; } if( p->nSelectRow>100 ) p->nSelectRow = 100; }else{ p->nSelectRow = 1; } + + /* If there is both a GROUP BY and an ORDER BY clause and they are + ** identical, then it may be possible to disable the ORDER BY clause + ** on the grounds that the GROUP BY will cause elements to come out + ** in the correct order. It also may not - the GROUP BY may use a + ** database index that causes rows to be grouped together as required + ** but not actually sorted. Either way, record the fact that the + ** ORDER BY and GROUP BY clauses are the same by setting the orderByGrp + ** variable. */ + if( sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 ){ + orderByGrp = 1; + } /* Create a label to jump to when we want to abort the query */ addrEnd = sqlite3VdbeMakeLabel(v); /* Convert TK_COLUMN nodes into TK_AGG_COLUMN and make entries in @@ -102191,14 +111065,15 @@ */ memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; sNC.pSrcList = pTabList; sNC.pAggInfo = &sAggInfo; - sAggInfo.nSortingColumn = pGroupBy ? pGroupBy->nExpr+1 : 0; + sAggInfo.mnReg = pParse->nMem+1; + sAggInfo.nSortingColumn = pGroupBy ? pGroupBy->nExpr : 0; sAggInfo.pGroupBy = pGroupBy; sqlite3ExprAnalyzeAggList(&sNC, pEList); - sqlite3ExprAnalyzeAggList(&sNC, pOrderBy); + sqlite3ExprAnalyzeAggList(&sNC, sSort.pOrderBy); if( pHaving ){ sqlite3ExprAnalyzeAggregates(&sNC, pHaving); } sAggInfo.nAccumulator = sAggInfo.nColumn; for(i=0; i<sAggInfo.nFunc; i++){ @@ -102205,10 +111080,11 @@ assert( !ExprHasProperty(sAggInfo.aFunc[i].pExpr, EP_xIsSelect) ); sNC.ncFlags |= NC_InAggFunc; sqlite3ExprAnalyzeAggList(&sNC, sAggInfo.aFunc[i].pExpr->x.pList); sNC.ncFlags &= ~NC_InAggFunc; } + sAggInfo.mxReg = pParse->nMem; if( db->mallocFailed ) goto select_end; /* Processing for aggregates with GROUP BY is very different and ** much more complex than aggregates without a GROUP BY. */ @@ -102227,14 +111103,14 @@ ** implement it. Allocate that sorting index now. If it turns out ** that we do not need it after all, the OP_SorterOpen instruction ** will be converted into a Noop. */ sAggInfo.sortingIdx = pParse->nTab++; - pKeyInfo = keyInfoFromExprList(pParse, pGroupBy); + pKeyInfo = keyInfoFromExprList(pParse, pGroupBy, 0, sAggInfo.nColumn); addrSortingIdx = sqlite3VdbeAddOp4(v, OP_SorterOpen, sAggInfo.sortingIdx, sAggInfo.nSortingColumn, - 0, (char*)pKeyInfo, P4_KEYINFO_HANDOFF); + 0, (char*)pKeyInfo, P4_KEYINFO); /* Initialize memory locations used by GROUP BY aggregate processing */ iUseFlag = ++pParse->nMem; iAbortFlag = ++pParse->nMem; @@ -102256,14 +111132,15 @@ ** This might involve two separate loops with an OP_Sort in between, or ** it might be a single loop that uses an index to extract information ** in the right order to begin with. */ sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, 0, - WHERE_GROUPBY, 0); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, 0, + WHERE_GROUPBY | (orderByGrp ? WHERE_SORTBYGROUP : 0), 0 + ); if( pWInfo==0 ) goto select_end; - if( sqlite3WhereIsOrdered(pWInfo) ){ + if( sqlite3WhereIsOrdered(pWInfo)==pGroupBy->nExpr ){ /* The optimizer is able to deliver rows in group by order so ** we do not have to sort. The OP_OpenEphemeral table will be ** cancelled later because we still need to use the pKeyInfo */ groupBySort = 0; @@ -102282,23 +111159,22 @@ (sDistinct.isTnct && (p->selFlags&SF_Distinct)==0) ? "DISTINCT" : "GROUP BY"); groupBySort = 1; nGroupBy = pGroupBy->nExpr; - nCol = nGroupBy + 1; - j = nGroupBy+1; + nCol = nGroupBy; + j = nGroupBy; for(i=0; i<sAggInfo.nColumn; i++){ if( sAggInfo.aCol[i].iSorterColumn>=j ){ nCol++; j++; } } regBase = sqlite3GetTempRange(pParse, nCol); sqlite3ExprCacheClear(pParse); sqlite3ExprCodeExprList(pParse, pGroupBy, regBase, 0); - sqlite3VdbeAddOp2(v, OP_Sequence, sAggInfo.sortingIdx,regBase+nGroupBy); - j = nGroupBy+1; + j = nGroupBy; for(i=0; i<sAggInfo.nColumn; i++){ struct AggInfo_col *pCol = &sAggInfo.aCol[i]; if( pCol->iSorterColumn>=j ){ int r1 = j + regBase; int r2; @@ -102319,13 +111195,28 @@ sqlite3WhereEnd(pWInfo); sAggInfo.sortingIdxPTab = sortPTab = pParse->nTab++; sortOut = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_OpenPseudo, sortPTab, sortOut, nCol); sqlite3VdbeAddOp2(v, OP_SorterSort, sAggInfo.sortingIdx, addrEnd); - VdbeComment((v, "GROUP BY sort")); + VdbeComment((v, "GROUP BY sort")); VdbeCoverage(v); sAggInfo.useSortingIdx = 1; sqlite3ExprCacheClear(pParse); + + } + + /* If the index or temporary table used by the GROUP BY sort + ** will naturally deliver rows in the order required by the ORDER BY + ** clause, cancel the ephemeral table open coded earlier. + ** + ** This is an optimization - the correct answer should result regardless. + ** Use the SQLITE_GroupByOrder flag with SQLITE_TESTCTRL_OPTIMIZER to + ** disable this optimization for testing purposes. */ + if( orderByGrp && OptimizationEnabled(db, SQLITE_GroupByOrder) + && (groupBySort || sqlite3WhereIsSorted(pWInfo)) + ){ + sSort.pOrderBy = 0; + sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex); } /* Evaluate the current GROUP BY terms and store in b0, b1, b2... ** (b0 is memory location iBMem+0, b1 is iBMem+1, and so forth) ** Then compare the current GROUP BY terms against the GROUP BY terms @@ -102332,25 +111223,24 @@ ** from the previous row currently stored in a0, a1, a2... */ addrTopOfLoop = sqlite3VdbeCurrentAddr(v); sqlite3ExprCacheClear(pParse); if( groupBySort ){ - sqlite3VdbeAddOp2(v, OP_SorterData, sAggInfo.sortingIdx, sortOut); + sqlite3VdbeAddOp3(v, OP_SorterData, sAggInfo.sortingIdx, sortOut,sortPTab); } for(j=0; j<pGroupBy->nExpr; j++){ if( groupBySort ){ sqlite3VdbeAddOp3(v, OP_Column, sortPTab, j, iBMem+j); - if( j==0 ) sqlite3VdbeChangeP5(v, OPFLAG_CLEARCACHE); }else{ sAggInfo.directMode = 1; sqlite3ExprCode(pParse, pGroupBy->a[j].pExpr, iBMem+j); } } sqlite3VdbeAddOp4(v, OP_Compare, iAMem, iBMem, pGroupBy->nExpr, - (char*)pKeyInfo, P4_KEYINFO); + (char*)sqlite3KeyInfoRef(pKeyInfo), P4_KEYINFO); j1 = sqlite3VdbeCurrentAddr(v); - sqlite3VdbeAddOp3(v, OP_Jump, j1+1, 0, j1+1); + sqlite3VdbeAddOp3(v, OP_Jump, j1+1, 0, j1+1); VdbeCoverage(v); /* Generate code that runs whenever the GROUP BY changes. ** Changes in the GROUP BY are detected by the previous code ** block. If there were no changes, this block is skipped. ** @@ -102360,11 +111250,11 @@ ** for the next GROUP BY batch. */ sqlite3ExprCodeMove(pParse, iBMem, iAMem, pGroupBy->nExpr); sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow); VdbeComment((v, "output one row")); - sqlite3VdbeAddOp2(v, OP_IfPos, iAbortFlag, addrEnd); + sqlite3VdbeAddOp2(v, OP_IfPos, iAbortFlag, addrEnd); VdbeCoverage(v); VdbeComment((v, "check abort flag")); sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); VdbeComment((v, "reset accumulator")); /* Update the aggregate accumulators based on the content of @@ -102377,10 +111267,11 @@ /* End of the loop */ if( groupBySort ){ sqlite3VdbeAddOp2(v, OP_SorterNext, sAggInfo.sortingIdx, addrTopOfLoop); + VdbeCoverage(v); }else{ sqlite3WhereEnd(pWInfo); sqlite3VdbeChangeToNoop(v, addrSortingIdx); } @@ -102404,16 +111295,16 @@ sqlite3VdbeAddOp2(v, OP_Integer, 1, iAbortFlag); VdbeComment((v, "set abort flag")); sqlite3VdbeAddOp1(v, OP_Return, regOutputRow); sqlite3VdbeResolveLabel(v, addrOutputRow); addrOutputRow = sqlite3VdbeCurrentAddr(v); - sqlite3VdbeAddOp2(v, OP_IfPos, iUseFlag, addrOutputRow+2); + sqlite3VdbeAddOp2(v, OP_IfPos, iUseFlag, addrOutputRow+2); VdbeCoverage(v); VdbeComment((v, "Groupby result generator entry point")); sqlite3VdbeAddOp1(v, OP_Return, regOutputRow); finalizeAggFunctions(pParse, &sAggInfo); sqlite3ExprIfFalse(pParse, pHaving, addrOutputRow+1, SQLITE_JUMPIFNULL); - selectInnerLoop(pParse, p, p->pEList, 0, 0, pOrderBy, + selectInnerLoop(pParse, p, p->pEList, -1, &sSort, &sDistinct, pDest, addrOutputRow+1, addrSetAbort); sqlite3VdbeAddOp1(v, OP_Return, regOutputRow); VdbeComment((v, "end groupby result generator")); @@ -102450,37 +111341,38 @@ int iRoot = pTab->tnum; /* Root page of scanned b-tree */ sqlite3CodeVerifySchema(pParse, iDb); sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); - /* Search for the index that has the least amount of columns. If - ** there is such an index, and it has less columns than the table - ** does, then we can assume that it consumes less space on disk and - ** will therefore be cheaper to scan to determine the query result. - ** In this case set iRoot to the root page number of the index b-tree - ** and pKeyInfo to the KeyInfo structure required to navigate the - ** index. + /* Search for the index that has the lowest scan cost. ** ** (2011-04-15) Do not do a full scan of an unordered index. + ** + ** (2013-10-03) Do not count the entries in a partial index. ** ** In practice the KeyInfo structure will not be used. It is only ** passed to keep OP_OpenRead happy. */ + if( !HasRowid(pTab) ) pBest = sqlite3PrimaryKeyIndex(pTab); for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->bUnordered==0 && (!pBest || pIdx->nColumn<pBest->nColumn) ){ + if( pIdx->bUnordered==0 + && pIdx->szIdxRow<pTab->szTabRow + && pIdx->pPartIdxWhere==0 + && (!pBest || pIdx->szIdxRow<pBest->szIdxRow) + ){ pBest = pIdx; } } - if( pBest && pBest->nColumn<pTab->nCol ){ + if( pBest ){ iRoot = pBest->tnum; - pKeyInfo = sqlite3IndexKeyinfo(pParse, pBest); + pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pBest); } /* Open a read-only cursor, execute the OP_Count, close the cursor. */ - sqlite3VdbeAddOp3(v, OP_OpenRead, iCsr, iRoot, iDb); + sqlite3VdbeAddOp4Int(v, OP_OpenRead, iCsr, iRoot, iDb, 1); if( pKeyInfo ){ - sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO_HANDOFF); + sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO); } sqlite3VdbeAddOp2(v, OP_Count, iCsr, sAggInfo.aFunc[0].iMem); sqlite3VdbeAddOp1(v, OP_Close, iCsr); explainSimpleCount(pParse, pTab, pBest); }else @@ -102540,22 +111432,22 @@ sqlite3ExprListDelete(db, pDel); goto select_end; } updateAccumulator(pParse, &sAggInfo); assert( pMinMax==0 || pMinMax->nExpr==1 ); - if( sqlite3WhereIsOrdered(pWInfo) ){ + if( sqlite3WhereIsOrdered(pWInfo)>0 ){ sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3WhereBreakLabel(pWInfo)); VdbeComment((v, "%s() by index", (flag==WHERE_ORDERBY_MIN?"min":"max"))); } sqlite3WhereEnd(pWInfo); finalizeAggFunctions(pParse, &sAggInfo); } - pOrderBy = 0; + sSort.pOrderBy = 0; sqlite3ExprIfFalse(pParse, pHaving, addrEnd, SQLITE_JUMPIFNULL); - selectInnerLoop(pParse, p, p->pEList, 0, 0, 0, 0, + selectInnerLoop(pParse, p, p->pEList, -1, 0, 0, pDest, addrEnd, addrEnd); sqlite3ExprListDelete(db, pDel); } sqlite3VdbeResolveLabel(v, addrEnd); @@ -102566,13 +111458,13 @@ } /* If there is an ORDER BY clause, then we need to sort the results ** and send them to the callback one by one. */ - if( pOrderBy ){ - explainTempTable(pParse, "ORDER BY"); - generateSortTail(pParse, p, v, pEList->nExpr, pDest); + if( sSort.pOrderBy ){ + explainTempTable(pParse, sSort.nOBSat>0 ? "RIGHT PART OF ORDER BY":"ORDER BY"); + generateSortTail(pParse, p, &sSort, pEList->nExpr, pDest); } /* Jump here to skip this query */ sqlite3VdbeResolveLabel(v, iEnd); @@ -102594,111 +111486,110 @@ generateColumnNames(pParse, pTabList, pEList); } sqlite3DbFree(db, sAggInfo.aCol); sqlite3DbFree(db, sAggInfo.aFunc); +#if SELECTTRACE_ENABLED + SELECTTRACE(1,pParse,p,("end processing\n")); + pParse->nSelectIndent--; +#endif return rc; } -#if defined(SQLITE_ENABLE_TREE_EXPLAIN) +#ifdef SQLITE_DEBUG /* ** Generate a human-readable description of a the Select object. */ -static void explainOneSelect(Vdbe *pVdbe, Select *p){ - sqlite3ExplainPrintf(pVdbe, "SELECT "); - if( p->selFlags & (SF_Distinct|SF_Aggregate) ){ - if( p->selFlags & SF_Distinct ){ - sqlite3ExplainPrintf(pVdbe, "DISTINCT "); - } - if( p->selFlags & SF_Aggregate ){ - sqlite3ExplainPrintf(pVdbe, "agg_flag "); - } - sqlite3ExplainNL(pVdbe); - sqlite3ExplainPrintf(pVdbe, " "); - } - sqlite3ExplainExprList(pVdbe, p->pEList); - sqlite3ExplainNL(pVdbe); +SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ + int n = 0; + pView = sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewLine(pView, "SELECT%s%s (0x%p)", + ((p->selFlags & SF_Distinct) ? " DISTINCT" : ""), + ((p->selFlags & SF_Aggregate) ? " agg_flag" : ""), p + ); + if( p->pSrc && p->pSrc->nSrc ) n++; + if( p->pWhere ) n++; + if( p->pGroupBy ) n++; + if( p->pHaving ) n++; + if( p->pOrderBy ) n++; + if( p->pLimit ) n++; + if( p->pOffset ) n++; + if( p->pPrior ) n++; + sqlite3TreeViewExprList(pView, p->pEList, (n--)>0, "result-set"); if( p->pSrc && p->pSrc->nSrc ){ int i; - sqlite3ExplainPrintf(pVdbe, "FROM "); - sqlite3ExplainPush(pVdbe); + pView = sqlite3TreeViewPush(pView, (n--)>0); + sqlite3TreeViewLine(pView, "FROM"); for(i=0; i<p->pSrc->nSrc; i++){ struct SrcList_item *pItem = &p->pSrc->a[i]; - sqlite3ExplainPrintf(pVdbe, "{%d,*} = ", pItem->iCursor); - if( pItem->pSelect ){ - sqlite3ExplainSelect(pVdbe, pItem->pSelect); - if( pItem->pTab ){ - sqlite3ExplainPrintf(pVdbe, " (tabname=%s)", pItem->pTab->zName); - } + StrAccum x; + char zLine[100]; + sqlite3StrAccumInit(&x, zLine, sizeof(zLine), 0); + sqlite3XPrintf(&x, 0, "{%d,*}", pItem->iCursor); + if( pItem->zDatabase ){ + sqlite3XPrintf(&x, 0, " %s.%s", pItem->zDatabase, pItem->zName); }else if( pItem->zName ){ - sqlite3ExplainPrintf(pVdbe, "%s", pItem->zName); + sqlite3XPrintf(&x, 0, " %s", pItem->zName); + } + if( pItem->pTab ){ + sqlite3XPrintf(&x, 0, " tabname=%Q", pItem->pTab->zName); } if( pItem->zAlias ){ - sqlite3ExplainPrintf(pVdbe, " (AS %s)", pItem->zAlias); + sqlite3XPrintf(&x, 0, " (AS %s)", pItem->zAlias); } if( pItem->jointype & JT_LEFT ){ - sqlite3ExplainPrintf(pVdbe, " LEFT-JOIN"); + sqlite3XPrintf(&x, 0, " LEFT-JOIN"); } - sqlite3ExplainNL(pVdbe); + sqlite3StrAccumFinish(&x); + sqlite3TreeViewItem(pView, zLine, i<p->pSrc->nSrc-1); + if( pItem->pSelect ){ + sqlite3TreeViewSelect(pView, pItem->pSelect, 0); + } + sqlite3TreeViewPop(pView); } - sqlite3ExplainPop(pVdbe); + sqlite3TreeViewPop(pView); } if( p->pWhere ){ - sqlite3ExplainPrintf(pVdbe, "WHERE "); - sqlite3ExplainExpr(pVdbe, p->pWhere); - sqlite3ExplainNL(pVdbe); + sqlite3TreeViewItem(pView, "WHERE", (n--)>0); + sqlite3TreeViewExpr(pView, p->pWhere, 0); + sqlite3TreeViewPop(pView); } if( p->pGroupBy ){ - sqlite3ExplainPrintf(pVdbe, "GROUPBY "); - sqlite3ExplainExprList(pVdbe, p->pGroupBy); - sqlite3ExplainNL(pVdbe); + sqlite3TreeViewExprList(pView, p->pGroupBy, (n--)>0, "GROUPBY"); } if( p->pHaving ){ - sqlite3ExplainPrintf(pVdbe, "HAVING "); - sqlite3ExplainExpr(pVdbe, p->pHaving); - sqlite3ExplainNL(pVdbe); + sqlite3TreeViewItem(pView, "HAVING", (n--)>0); + sqlite3TreeViewExpr(pView, p->pHaving, 0); + sqlite3TreeViewPop(pView); } if( p->pOrderBy ){ - sqlite3ExplainPrintf(pVdbe, "ORDERBY "); - sqlite3ExplainExprList(pVdbe, p->pOrderBy); - sqlite3ExplainNL(pVdbe); + sqlite3TreeViewExprList(pView, p->pOrderBy, (n--)>0, "ORDERBY"); } if( p->pLimit ){ - sqlite3ExplainPrintf(pVdbe, "LIMIT "); - sqlite3ExplainExpr(pVdbe, p->pLimit); - sqlite3ExplainNL(pVdbe); + sqlite3TreeViewItem(pView, "LIMIT", (n--)>0); + sqlite3TreeViewExpr(pView, p->pLimit, 0); + sqlite3TreeViewPop(pView); } if( p->pOffset ){ - sqlite3ExplainPrintf(pVdbe, "OFFSET "); - sqlite3ExplainExpr(pVdbe, p->pOffset); - sqlite3ExplainNL(pVdbe); - } -} -SQLITE_PRIVATE void sqlite3ExplainSelect(Vdbe *pVdbe, Select *p){ - if( p==0 ){ - sqlite3ExplainPrintf(pVdbe, "(null-select)"); - return; - } - while( p->pPrior ){ - p->pPrior->pNext = p; - p = p->pPrior; - } - sqlite3ExplainPush(pVdbe); - while( p ){ - explainOneSelect(pVdbe, p); - p = p->pNext; - if( p==0 ) break; - sqlite3ExplainNL(pVdbe); - sqlite3ExplainPrintf(pVdbe, "%s\n", selectOpName(p->op)); - } - sqlite3ExplainPrintf(pVdbe, "END"); - sqlite3ExplainPop(pVdbe); -} - -/* End of the structure debug printing code -*****************************************************************************/ -#endif /* defined(SQLITE_ENABLE_TREE_EXPLAIN) */ + sqlite3TreeViewItem(pView, "OFFSET", (n--)>0); + sqlite3TreeViewExpr(pView, p->pOffset, 0); + sqlite3TreeViewPop(pView); + } + if( p->pPrior ){ + const char *zOp = "UNION"; + switch( p->op ){ + case TK_ALL: zOp = "UNION ALL"; break; + case TK_INTERSECT: zOp = "INTERSECT"; break; + case TK_EXCEPT: zOp = "EXCEPT"; break; + } + sqlite3TreeViewItem(pView, zOp, (n--)>0); + sqlite3TreeViewSelect(pView, p->pPrior, 0); + sqlite3TreeViewPop(pView); + } + sqlite3TreeViewPop(pView); +} +#endif /* SQLITE_DEBUG */ /************** End of select.c **********************************************/ /************** Begin file table.c *******************************************/ /* ** 2001 September 15 @@ -102728,14 +111619,14 @@ ** to the callback function is uses to build the result. */ typedef struct TabResult { char **azResult; /* Accumulated output */ char *zErrMsg; /* Error message text, if an error occurs */ - int nAlloc; /* Slots allocated for azResult[] */ - int nRow; /* Number of rows in the result */ - int nColumn; /* Number of columns in the result */ - int nData; /* Slots used in azResult[]. (nRow+1)*nColumn */ + u32 nAlloc; /* Slots allocated for azResult[] */ + u32 nRow; /* Number of rows in the result */ + u32 nColumn; /* Number of columns in the result */ + u32 nData; /* Slots used in azResult[]. (nRow+1)*nColumn */ int rc; /* Return code from sqlite3_exec() */ } TabResult; /* ** This routine is called once for each row in the result table. Its job @@ -102757,11 +111648,11 @@ need = nCol; } if( p->nData + need > p->nAlloc ){ char **azNew; p->nAlloc = p->nAlloc*2 + need; - azNew = sqlite3_realloc( p->azResult, sizeof(char*)*p->nAlloc ); + azNew = sqlite3_realloc64( p->azResult, sizeof(char*)*p->nAlloc ); if( azNew==0 ) goto malloc_failed; p->azResult = azNew; } /* If this is the first row, then generate an extra row containing @@ -102772,11 +111663,11 @@ for(i=0; i<nCol; i++){ z = sqlite3_mprintf("%s", colv[i]); if( z==0 ) goto malloc_failed; p->azResult[p->nData++] = z; } - }else if( p->nColumn!=nCol ){ + }else if( (int)p->nColumn!=nCol ){ sqlite3_free(p->zErrMsg); p->zErrMsg = sqlite3_mprintf( "sqlite3_get_table() called with two or more incompatible queries" ); p->rc = SQLITE_ERROR; @@ -102814,11 +111705,11 @@ ** The result that is written to ***pazResult is held in memory obtained ** from malloc(). But the caller cannot free this memory directly. ** Instead, the entire table should be passed to sqlite3_free_table() when ** the calling procedure is finished using it. */ -SQLITE_API int sqlite3_get_table( +SQLITE_API int SQLITE_STDCALL sqlite3_get_table( sqlite3 *db, /* The database on which the SQL executes */ const char *zSql, /* The SQL to be executed */ char ***pazResult, /* Write the result table here */ int *pnRow, /* Write the number of rows in the result here */ int *pnColumn, /* Write the number of columns of result here */ @@ -102825,10 +111716,13 @@ char **pzErrMsg /* Write error messages here */ ){ int rc; TabResult res; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || pazResult==0 ) return SQLITE_MISUSE_BKPT; +#endif *pazResult = 0; if( pnColumn ) *pnColumn = 0; if( pnRow ) *pnRow = 0; if( pzErrMsg ) *pzErrMsg = 0; res.zErrMsg = 0; @@ -102880,12 +111774,12 @@ } /* ** This routine frees the space the sqlite3_get_table() malloced. */ -SQLITE_API void sqlite3_free_table( - char **azResult /* Result returned from from sqlite3_get_table() */ +SQLITE_API void SQLITE_STDCALL sqlite3_free_table( + char **azResult /* Result returned from sqlite3_get_table() */ ){ if( azResult ){ int i, n; azResult--; assert( azResult!=0 ); @@ -103025,11 +111919,11 @@ ** ** CREATE TRIGGER attached.demo AFTER INSERT ON attached.tab .... ** ^^^^^^^^ ** ** To maintain backwards compatibility, ignore the database - ** name on pTableName if we are reparsing our of SQLITE_MASTER. + ** name on pTableName if we are reparsing out of SQLITE_MASTER. */ if( db->init.busy && iDb!=1 ){ sqlite3DbFree(db, pTableName->a[0].zDatabase); pTableName->a[0].zDatabase = 0; } @@ -103046,12 +111940,12 @@ } /* Ensure the table name matches database name and that the table exists */ if( db->mallocFailed ) goto trigger_cleanup; assert( pTableName->nSrc==1 ); - if( sqlite3FixInit(&sFix, pParse, iDb, "trigger", pName) && - sqlite3FixSrcList(&sFix, pTableName) ){ + sqlite3FixInit(&sFix, pParse, iDb, "trigger", pName); + if( sqlite3FixSrcList(&sFix, pTableName) ){ goto trigger_cleanup; } pTab = sqlite3SrcListLookup(pParse, pTableName); if( !pTab ){ /* The table does not exist. */ @@ -103078,12 +111972,11 @@ zName = sqlite3NameFromToken(db, pName); if( !zName || SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ goto trigger_cleanup; } assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - if( sqlite3HashFind(&(db->aDb[iDb].pSchema->trigHash), - zName, sqlite3Strlen30(zName)) ){ + if( sqlite3HashFind(&(db->aDb[iDb].pSchema->trigHash),zName) ){ if( !noErr ){ sqlite3ErrorMsg(pParse, "trigger %T already exists", pName); }else{ assert( !db->init.busy ); sqlite3CodeVerifySchema(pParse, iDb); @@ -103189,12 +112082,14 @@ pStepList->pTrig = pTrig; pStepList = pStepList->pNext; } nameToken.z = pTrig->zName; nameToken.n = sqlite3Strlen30(nameToken.z); - if( sqlite3FixInit(&sFix, pParse, iDb, "trigger", &nameToken) - && sqlite3FixTriggerStep(&sFix, pTrig->step_list) ){ + sqlite3FixInit(&sFix, pParse, iDb, "trigger", &nameToken); + if( sqlite3FixTriggerStep(&sFix, pTrig->step_list) + || sqlite3FixExpr(&sFix, pTrig->pWhen) + ){ goto triggerfinish_cleanup; } /* if we are not initializing, ** build the sqlite_master entry @@ -103220,17 +112115,16 @@ if( db->init.busy ){ Trigger *pLink = pTrig; Hash *pHash = &db->aDb[iDb].pSchema->trigHash; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - pTrig = sqlite3HashInsert(pHash, zName, sqlite3Strlen30(zName), pTrig); + pTrig = sqlite3HashInsert(pHash, zName, pTrig); if( pTrig ){ db->mallocFailed = 1; }else if( pLink->pSchema==pLink->pTabSchema ){ Table *pTab; - int n = sqlite3Strlen30(pLink->table); - pTab = sqlite3HashFind(&pLink->pTabSchema->tblHash, pLink->table, n); + pTab = sqlite3HashFind(&pLink->pTabSchema->tblHash, pLink->table); assert( pTab!=0 ); pLink->pNext = pTab->pTrigger; pTab->pTrigger = pLink; } } @@ -103293,29 +112187,25 @@ */ SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep( sqlite3 *db, /* The database connection */ Token *pTableName, /* Name of the table into which we insert */ IdList *pColumn, /* List of columns in pTableName to insert into */ - ExprList *pEList, /* The VALUE clause: a list of values to be inserted */ Select *pSelect, /* A SELECT statement that supplies values */ u8 orconf /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */ ){ TriggerStep *pTriggerStep; - assert(pEList == 0 || pSelect == 0); - assert(pEList != 0 || pSelect != 0 || db->mallocFailed); + assert(pSelect != 0 || db->mallocFailed); pTriggerStep = triggerStepAllocate(db, TK_INSERT, pTableName); if( pTriggerStep ){ pTriggerStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); pTriggerStep->pIdList = pColumn; - pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE); pTriggerStep->orconf = orconf; }else{ sqlite3IdListDelete(db, pColumn); } - sqlite3ExprListDelete(db, pEList); sqlite3SelectDelete(db, pSelect); return pTriggerStep; } @@ -103389,11 +112279,10 @@ SQLITE_PRIVATE void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr){ Trigger *pTrigger = 0; int i; const char *zDb; const char *zName; - int nName; sqlite3 *db = pParse->db; if( db->mallocFailed ) goto drop_trigger_cleanup; if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ goto drop_trigger_cleanup; @@ -103400,17 +112289,16 @@ } assert( pName->nSrc==1 ); zDb = pName->a[0].zDatabase; zName = pName->a[0].zName; - nName = sqlite3Strlen30(zName); assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) ); for(i=OMIT_TEMPDB; i<db->nDb; i++){ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ if( zDb && sqlite3StrICmp(db->aDb[j].zName, zDb) ) continue; assert( sqlite3SchemaMutexHeld(db, j, 0) ); - pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName, nName); + pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName); if( pTrigger ) break; } if( !pTrigger ){ if( !noErr ){ sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0); @@ -103429,12 +112317,11 @@ /* ** Return a pointer to the Table structure for the table that a trigger ** is set on. */ static Table *tableOfTrigger(Trigger *pTrigger){ - int n = sqlite3Strlen30(pTrigger->table); - return sqlite3HashFind(&pTrigger->pTabSchema->tblHash, pTrigger->table, n); + return sqlite3HashFind(&pTrigger->pTabSchema->tblHash, pTrigger->table); } /* ** Drop a trigger given a pointer to that trigger. @@ -103466,10 +112353,11 @@ /* Generate code to destroy the database record of the trigger. */ assert( pTable!=0 ); if( (v = sqlite3GetVdbe(pParse))!=0 ){ int base; + static const int iLn = VDBE_OFFSET_LINENO(2); static const VdbeOpList dropTrigger[] = { { OP_Rewind, 0, ADDR(9), 0}, { OP_String8, 0, 1, 0}, /* 1 */ { OP_Column, 0, 1, 2}, { OP_Ne, 2, ADDR(8), 1}, @@ -103480,11 +112368,11 @@ { OP_Next, 0, ADDR(1), 0}, /* 8 */ }; sqlite3BeginWriteOperation(pParse, 0, iDb); sqlite3OpenMasterTable(pParse, iDb); - base = sqlite3VdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger); + base = sqlite3VdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger, iLn); sqlite3VdbeChangeP4(v, base+1, pTrigger->zName, P4_TRANSIENT); sqlite3VdbeChangeP4(v, base+4, "trigger", P4_STATIC); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddOp2(v, OP_Close, 0, 0); sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0); @@ -103501,11 +112389,11 @@ Trigger *pTrigger; Hash *pHash; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); pHash = &(db->aDb[iDb].pSchema->trigHash); - pTrigger = sqlite3HashInsert(pHash, zName, sqlite3Strlen30(zName), 0); + pTrigger = sqlite3HashInsert(pHash, zName, 0); if( ALWAYS(pTrigger) ){ if( pTrigger->pSchema==pTrigger->pTabSchema ){ Table *pTab = tableOfTrigger(pTrigger); Trigger **pp; for(pp=&pTab->pTrigger; *pp!=pTrigger; pp=&((*pp)->pNext)); @@ -103626,19 +112514,11 @@ ** ** INSERT INTO t1 ... ; -- insert into t2 uses REPLACE policy ** INSERT OR IGNORE INTO t1 ... ; -- insert into t2 uses IGNORE policy */ pParse->eOrconf = (orconf==OE_Default)?pStep->orconf:(u8)orconf; - - /* Clear the cookieGoto flag. When coding triggers, the cookieGoto - ** variable is used as a flag to indicate to sqlite3ExprCodeConstants() - ** that it is not safe to refactor constants (this happens after the - ** start of the first loop in the SQL statement is coded - at that - ** point code may be conditionally executed, so it is no longer safe to - ** initialize constant register values). */ - assert( pParse->cookieGoto==0 || pParse->cookieGoto==-1 ); - pParse->cookieGoto = 0; + assert( pParse->okConstFactor==0 ); switch( pStep->op ){ case TK_UPDATE: { sqlite3Update(pParse, targetSrcList(pParse, pStep), @@ -103649,11 +112529,10 @@ break; } case TK_INSERT: { sqlite3Insert(pParse, targetSrcList(pParse, pStep), - sqlite3ExprListDup(db, pStep->pExprList, 0), sqlite3SelectDup(db, pStep->pSelect, 0), sqlite3IdListDup(db, pStep->pIdList), pParse->eOrconf ); break; @@ -103680,11 +112559,11 @@ } return 0; } -#ifdef SQLITE_DEBUG +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS /* ** This function is used to add VdbeComment() annotations to a VDBE ** program. It is not used in production code, only for debugging. */ static const char *onErrorText(int onError){ @@ -103820,10 +112699,11 @@ sqlite3VdbeDelete(v); } assert( !pSubParse->pAinc && !pSubParse->pZombieTab ); assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg ); + sqlite3ParserReset(pSubParse); sqlite3StackFree(db, pSubParse); return pPrg; } @@ -104105,11 +112985,11 @@ pCol->affinity, &pValue); if( pValue ){ sqlite3VdbeChangeP4(v, -1, (const char *)pValue, P4_MEM); } #ifndef SQLITE_OMIT_FLOATING_POINT - if( iReg>=0 && pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ + if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg); } #endif } } @@ -104128,44 +113008,55 @@ Expr *pWhere, /* The WHERE clause. May be null */ int onError /* How to handle constraint errors */ ){ int i, j; /* Loop counters */ Table *pTab; /* The table to be updated */ - int addr = 0; /* VDBE instruction address of the start of the loop */ + int addrTop = 0; /* VDBE instruction address of the start of the loop */ WhereInfo *pWInfo; /* Information about the WHERE clause */ Vdbe *v; /* The virtual database engine */ Index *pIdx; /* For looping over indices */ + Index *pPk; /* The PRIMARY KEY index for WITHOUT ROWID tables */ int nIdx; /* Number of indices that need updating */ - int iCur; /* VDBE Cursor number of pTab */ + int iBaseCur; /* Base cursor number */ + int iDataCur; /* Cursor for the canonical data btree */ + int iIdxCur; /* Cursor for the first index */ sqlite3 *db; /* The database structure */ int *aRegIdx = 0; /* One register assigned to each index to be updated */ int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the ** an expression for the i-th column of the table. ** aXRef[i]==-1 if the i-th column is not changed. */ - int chngRowid; /* True if the record number is being changed */ + u8 *aToOpen; /* 1 for tables and indices to be opened */ + u8 chngPk; /* PRIMARY KEY changed in a WITHOUT ROWID table */ + u8 chngRowid; /* Rowid changed in a normal table */ + u8 chngKey; /* Either chngPk or chngRowid */ Expr *pRowidExpr = 0; /* Expression defining the new record number */ - int openAll = 0; /* True if all indices need to be opened */ AuthContext sContext; /* The authorization context */ NameContext sNC; /* The name-context to resolve expressions in */ int iDb; /* Database containing the table being updated */ int okOnePass; /* True for one-pass algorithm without the FIFO */ int hasFK; /* True if foreign key processing is required */ + int labelBreak; /* Jump here to break out of UPDATE loop */ + int labelContinue; /* Jump here to continue next step of UPDATE loop */ #ifndef SQLITE_OMIT_TRIGGER int isView; /* True when updating a view (INSTEAD OF trigger) */ Trigger *pTrigger; /* List of triggers on pTab, if required */ int tmask; /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ #endif int newmask; /* Mask of NEW.* columns accessed by BEFORE triggers */ + int iEph = 0; /* Ephemeral table holding all primary key values */ + int nKey = 0; /* Number of elements in regKey for WITHOUT ROWID */ + int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */ /* Register Allocations */ int regRowCount = 0; /* A count of rows changed */ int regOldRowid; /* The old rowid */ int regNewRowid; /* The new rowid */ int regNew; /* Content of the NEW.* table in triggers */ int regOld = 0; /* Content of OLD.* table in triggers */ int regRowSet = 0; /* Rowset of rows to be updated */ + int regKey = 0; /* composite PRIMARY KEY value */ memset(&sContext, 0, sizeof(sContext)); db = pParse->db; if( pParse->nErr || db->mallocFailed ){ goto update_cleanup; @@ -104199,23 +113090,37 @@ goto update_cleanup; } if( sqlite3IsReadOnly(pParse, pTab, tmask) ){ goto update_cleanup; } - aXRef = sqlite3DbMallocRaw(db, sizeof(int) * pTab->nCol ); - if( aXRef==0 ) goto update_cleanup; - for(i=0; i<pTab->nCol; i++) aXRef[i] = -1; /* Allocate a cursors for the main database table and for all indices. ** The index cursors might not be used, but if they are used they ** need to occur right after the database cursor. So go ahead and ** allocate enough space, just in case. */ - pTabList->a[0].iCursor = iCur = pParse->nTab++; - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + pTabList->a[0].iCursor = iBaseCur = iDataCur = pParse->nTab++; + iIdxCur = iDataCur+1; + pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ + if( IsPrimaryKeyIndex(pIdx) && pPk!=0 ){ + iDataCur = pParse->nTab; + pTabList->a[0].iCursor = iDataCur; + } pParse->nTab++; } + + /* Allocate space for aXRef[], aRegIdx[], and aToOpen[]. + ** Initialize aXRef[] and aToOpen[] to their default values. + */ + aXRef = sqlite3DbMallocRaw(db, sizeof(int) * (pTab->nCol+nIdx) + nIdx+2 ); + if( aXRef==0 ) goto update_cleanup; + aRegIdx = aXRef+pTab->nCol; + aToOpen = (u8*)(aRegIdx+nIdx); + memset(aToOpen, 1, nIdx+1); + aToOpen[nIdx+1] = 0; + for(i=0; i<pTab->nCol; i++) aXRef[i] = -1; /* Initialize the name-context */ memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; sNC.pSrcList = pTabList; @@ -104224,27 +113129,29 @@ ** of the UPDATE statement. Also find the column index ** for each column to be updated in the pChanges array. For each ** column to be updated, make sure we have authorization to change ** that column. */ - chngRowid = 0; + chngRowid = chngPk = 0; for(i=0; i<pChanges->nExpr; i++){ if( sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){ goto update_cleanup; } for(j=0; j<pTab->nCol; j++){ if( sqlite3StrICmp(pTab->aCol[j].zName, pChanges->a[i].zName)==0 ){ if( j==pTab->iPKey ){ chngRowid = 1; pRowidExpr = pChanges->a[i].pExpr; + }else if( pPk && (pTab->aCol[j].colFlags & COLFLAG_PRIMKEY)!=0 ){ + chngPk = 1; } aXRef[j] = i; break; } } if( j>=pTab->nCol ){ - if( sqlite3IsRowid(pChanges->a[i].zName) ){ + if( pPk==0 && sqlite3IsRowid(pChanges->a[i].zName) ){ j = -1; chngRowid = 1; pRowidExpr = pChanges->a[i].pExpr; }else{ sqlite3ErrorMsg(pParse, "no such column: %s", pChanges->a[i].zName); @@ -104264,36 +113171,40 @@ aXRef[j] = -1; } } #endif } - - hasFK = sqlite3FkRequired(pParse, pTab, aXRef, chngRowid); - - /* Allocate memory for the array aRegIdx[]. There is one entry in the - ** array for each index associated with table being updated. Fill in - ** the value with a register number for indices that are to be used - ** and with zero for unused indices. - */ - for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){} - if( nIdx>0 ){ - aRegIdx = sqlite3DbMallocRaw(db, sizeof(Index*) * nIdx ); - if( aRegIdx==0 ) goto update_cleanup; - } + assert( (chngRowid & chngPk)==0 ); + assert( chngRowid==0 || chngRowid==1 ); + assert( chngPk==0 || chngPk==1 ); + chngKey = chngRowid + chngPk; + + /* The SET expressions are not actually used inside the WHERE loop. + ** So reset the colUsed mask + */ + pTabList->a[0].colUsed = 0; + + hasFK = sqlite3FkRequired(pParse, pTab, aXRef, chngKey); + + /* There is one entry in the aRegIdx[] array for each index on the table + ** being updated. Fill in aRegIdx[] with a register number that will hold + ** the key for accessing each index. + */ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ int reg; - if( hasFK || chngRowid || pIdx->pPartIdxWhere ){ + if( chngKey || hasFK || pIdx->pPartIdxWhere || pIdx==pPk ){ reg = ++pParse->nMem; }else{ reg = 0; - for(i=0; i<pIdx->nColumn; i++){ + for(i=0; i<pIdx->nKeyCol; i++){ if( aXRef[pIdx->aiColumn[i]]>=0 ){ reg = ++pParse->nMem; break; } } } + if( reg==0 ) aToOpen[j+1] = 0; aRegIdx[j] = reg; } /* Begin generating code. */ v = sqlite3GetVdbe(pParse); @@ -104313,15 +113224,15 @@ #endif /* Allocate required registers. */ regRowSet = ++pParse->nMem; regOldRowid = regNewRowid = ++pParse->nMem; - if( pTrigger || hasFK ){ + if( chngPk || pTrigger || hasFK ){ regOld = pParse->nMem + 1; pParse->nMem += pTab->nCol; } - if( chngRowid || pTrigger || hasFK ){ + if( chngKey || pTrigger || hasFK ){ regNewRowid = ++pParse->nMem; } regNew = pParse->nMem + 1; pParse->nMem += pTab->nCol; @@ -104329,15 +113240,15 @@ if( isView ){ sqlite3AuthContextPush(pParse, &sContext, pTab->zName); } /* If we are trying to update a view, realize that view into - ** a ephemeral table. + ** an ephemeral table. */ #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) if( isView ){ - sqlite3MaterializeView(pParse, pTab, pWhere, iCur); + sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur); } #endif /* Resolve the column names in all the expressions in the ** WHERE clause. @@ -104346,110 +113257,155 @@ goto update_cleanup; } /* Begin the database scan */ - sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid); - pWInfo = sqlite3WhereBegin( - pParse, pTabList, pWhere, 0, 0, WHERE_ONEPASS_DESIRED, 0 - ); - if( pWInfo==0 ) goto update_cleanup; - okOnePass = sqlite3WhereOkOnePass(pWInfo); - - /* Remember the rowid of every item to be updated. - */ - sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regOldRowid); - if( !okOnePass ){ - sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid); - } - - /* End the database scan loop. - */ - sqlite3WhereEnd(pWInfo); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid); + pWInfo = sqlite3WhereBegin( + pParse, pTabList, pWhere, 0, 0, WHERE_ONEPASS_DESIRED, iIdxCur + ); + if( pWInfo==0 ) goto update_cleanup; + okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); + + /* Remember the rowid of every item to be updated. + */ + sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid); + if( !okOnePass ){ + sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid); + } + + /* End the database scan loop. + */ + sqlite3WhereEnd(pWInfo); + }else{ + int iPk; /* First of nPk memory cells holding PRIMARY KEY value */ + i16 nPk; /* Number of components of the PRIMARY KEY */ + int addrOpen; /* Address of the OpenEphemeral instruction */ + + assert( pPk!=0 ); + nPk = pPk->nKeyCol; + iPk = pParse->nMem+1; + pParse->nMem += nPk; + regKey = ++pParse->nMem; + iEph = pParse->nTab++; + sqlite3VdbeAddOp2(v, OP_Null, 0, iPk); + addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, + WHERE_ONEPASS_DESIRED, iIdxCur); + if( pWInfo==0 ) goto update_cleanup; + okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); + for(i=0; i<nPk; i++){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, pPk->aiColumn[i], + iPk+i); + } + if( okOnePass ){ + sqlite3VdbeChangeToNoop(v, addrOpen); + nKey = nPk; + regKey = iPk; + }else{ + sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey, + sqlite3IndexAffinityStr(v, pPk), nPk); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iEph, regKey); + } + sqlite3WhereEnd(pWInfo); + } /* Initialize the count of updated rows */ if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab ){ regRowCount = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount); } + labelBreak = sqlite3VdbeMakeLabel(v); if( !isView ){ /* ** Open every index that needs updating. Note that if any ** index could potentially invoke a REPLACE conflict resolution ** action, then we need to open all indices because we might need ** to be deleting some records. */ - if( !okOnePass ) sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenWrite); if( onError==OE_Replace ){ - openAll = 1; + memset(aToOpen, 1, nIdx+1); }else{ - openAll = 0; for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ if( pIdx->onError==OE_Replace ){ - openAll = 1; + memset(aToOpen, 1, nIdx+1); break; } } } - for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ - assert( aRegIdx ); - if( openAll || aRegIdx[i]>0 ){ - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); - sqlite3VdbeAddOp4(v, OP_OpenWrite, iCur+i+1, pIdx->tnum, iDb, - (char*)pKey, P4_KEYINFO_HANDOFF); - assert( pParse->nTab>iCur+i+1 ); - } - } + if( okOnePass ){ + if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0; + if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0; + } + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, iBaseCur, aToOpen, + 0, 0); } /* Top of the update loop */ if( okOnePass ){ - int a1 = sqlite3VdbeAddOp1(v, OP_NotNull, regOldRowid); - addr = sqlite3VdbeAddOp0(v, OP_Goto); - sqlite3VdbeJumpHere(v, a1); + if( aToOpen[iDataCur-iBaseCur] && !isView ){ + assert( pPk ); + sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey, nKey); + VdbeCoverageNeverTaken(v); + } + labelContinue = labelBreak; + sqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak); + VdbeCoverageIf(v, pPk==0); + VdbeCoverageIf(v, pPk!=0); + }else if( pPk ){ + labelContinue = sqlite3VdbeMakeLabel(v); + sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v); + addrTop = sqlite3VdbeAddOp2(v, OP_RowKey, iEph, regKey); + sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0); + VdbeCoverage(v); }else{ - addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet, 0, regOldRowid); + labelContinue = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet, labelBreak, + regOldRowid); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid); + VdbeCoverage(v); } - /* Make cursor iCur point to the record that is being updated. If - ** this record does not exist for some reason (deleted by a trigger, - ** for example, then jump to the next iteration of the RowSet loop. */ - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid); - /* If the record number will change, set register regNewRowid to ** contain the new value. If the record number is not being modified, ** then regNewRowid is the same register as regOldRowid, which is ** already populated. */ - assert( chngRowid || pTrigger || hasFK || regOldRowid==regNewRowid ); + assert( chngKey || pTrigger || hasFK || regOldRowid==regNewRowid ); if( chngRowid ){ sqlite3ExprCode(pParse, pRowidExpr, regNewRowid); - sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid); + sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid); VdbeCoverage(v); } - /* If there are triggers on this table, populate an array of registers - ** with the required old.* column data. */ - if( hasFK || pTrigger ){ + /* Compute the old pre-UPDATE content of the row being changed, if that + ** information is needed */ + if( chngPk || hasFK || pTrigger ){ u32 oldmask = (hasFK ? sqlite3FkOldmask(pParse, pTab) : 0); oldmask |= sqlite3TriggerColmask(pParse, pTrigger, pChanges, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onError ); for(i=0; i<pTab->nCol; i++){ - if( aXRef[i]<0 || oldmask==0xffffffff || (i<32 && (oldmask & (1<<i))) ){ - sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOld+i); + if( oldmask==0xffffffff + || (i<32 && (oldmask & MASKBIT32(i))!=0) + || (pTab->aCol[i].colFlags & COLFLAG_PRIMKEY)!=0 + ){ + testcase( oldmask!=0xffffffff && i==31 ); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regOld+i); }else{ sqlite3VdbeAddOp2(v, OP_Null, 0, regOld+i); } } - if( chngRowid==0 ){ + if( chngRowid==0 && pPk==0 ){ sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid); } } /* Populate the array of registers beginning at regNew with the new - ** row data. This array is used to check constaints, create the new + ** row data. This array is used to check constants, create the new ** table and index records, and as the values for any new.* references ** made by triggers. ** ** If there are one or more BEFORE triggers, then do not populate the ** registers associated with columns that are (a) not modified by @@ -104460,96 +113416,113 @@ ** be used eliminates some redundant opcodes. */ newmask = sqlite3TriggerColmask( pParse, pTrigger, pChanges, 1, TRIGGER_BEFORE, pTab, onError ); - sqlite3VdbeAddOp3(v, OP_Null, 0, regNew, regNew+pTab->nCol-1); + /*sqlite3VdbeAddOp3(v, OP_Null, 0, regNew, regNew+pTab->nCol-1);*/ for(i=0; i<pTab->nCol; i++){ if( i==pTab->iPKey ){ - /*sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i);*/ + sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i); }else{ j = aXRef[i]; if( j>=0 ){ sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regNew+i); - }else if( 0==(tmask&TRIGGER_BEFORE) || i>31 || (newmask&(1<<i)) ){ + }else if( 0==(tmask&TRIGGER_BEFORE) || i>31 || (newmask & MASKBIT32(i)) ){ /* This branch loads the value of a column that will not be changed ** into a register. This is done if there are no BEFORE triggers, or ** if there are one or more BEFORE triggers that use this value via ** a new.* reference in a trigger program. */ testcase( i==31 ); testcase( i==32 ); - sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regNew+i); - sqlite3ColumnDefault(v, pTab, i, regNew+i); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regNew+i); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i); } } } /* Fire any BEFORE UPDATE triggers. This happens before constraints are ** verified. One could argue that this is wrong. */ if( tmask&TRIGGER_BEFORE ){ - sqlite3VdbeAddOp2(v, OP_Affinity, regNew, pTab->nCol); - sqlite3TableAffinityStr(v, pTab); + sqlite3TableAffinity(v, pTab, regNew); sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, - TRIGGER_BEFORE, pTab, regOldRowid, onError, addr); + TRIGGER_BEFORE, pTab, regOldRowid, onError, labelContinue); /* The row-trigger may have deleted the row being updated. In this ** case, jump to the next row. No updates or AFTER triggers are ** required. This behavior - what happens when the row being updated ** is deleted or renamed by a BEFORE trigger - is left undefined in the ** documentation. */ - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid); + if( pPk ){ + sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue,regKey,nKey); + VdbeCoverage(v); + }else{ + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid); + VdbeCoverage(v); + } /* If it did not delete it, the row-trigger may still have modified ** some of the columns of the row being updated. Load the values for ** all columns not modified by the update statement into their ** registers in case this has happened. */ for(i=0; i<pTab->nCol; i++){ if( aXRef[i]<0 && i!=pTab->iPKey ){ - sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regNew+i); - sqlite3ColumnDefault(v, pTab, i, regNew+i); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regNew+i); } } } if( !isView ){ - int j1; /* Address of jump instruction */ + int j1 = 0; /* Address of jump instruction */ + int bReplace = 0; /* True if REPLACE conflict resolution might happen */ /* Do constraint checks. */ - sqlite3GenerateConstraintChecks(pParse, pTab, iCur, regNewRowid, - aRegIdx, (chngRowid?regOldRowid:0), 1, onError, addr, 0); + assert( regOldRowid>0 ); + sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, + regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace); /* Do FK constraint checks. */ if( hasFK ){ - sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngRowid); + sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngKey); } /* Delete the index entries associated with the current record. */ - j1 = sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regOldRowid); - sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, aRegIdx); + if( bReplace || chngKey ){ + if( pPk ){ + j1 = sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, 0, regKey, nKey); + }else{ + j1 = sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, 0, regOldRowid); + } + VdbeCoverageNeverTaken(v); + } + sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, aRegIdx); /* If changing the record number, delete the old record. */ - if( hasFK || chngRowid ){ - sqlite3VdbeAddOp2(v, OP_Delete, iCur, 0); + if( hasFK || chngKey || pPk!=0 ){ + sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0); } - sqlite3VdbeJumpHere(v, j1); + if( bReplace || chngKey ){ + sqlite3VdbeJumpHere(v, j1); + } if( hasFK ){ - sqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngRowid); + sqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngKey); } /* Insert the new index entries and the new record. */ - sqlite3CompleteInsertion(pParse, pTab, iCur, regNewRowid, aRegIdx, 1, 0, 0); + sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur, + regNewRowid, aRegIdx, 1, 0, 0); /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key ** to the row just updated. */ if( hasFK ){ - sqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngRowid); + sqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngKey); } } /* Increment the row counter */ @@ -104556,26 +113529,33 @@ if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab){ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); } sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, - TRIGGER_AFTER, pTab, regOldRowid, onError, addr); + TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue); /* Repeat the above with the next record to be updated, until ** all record selected by the WHERE clause have been updated. */ - sqlite3VdbeAddOp2(v, OP_Goto, 0, addr); - sqlite3VdbeJumpHere(v, addr); + if( okOnePass ){ + /* Nothing to do at end-of-loop for a single-pass */ + }else if( pPk ){ + sqlite3VdbeResolveLabel(v, labelContinue); + sqlite3VdbeAddOp2(v, OP_Next, iEph, addrTop); VdbeCoverage(v); + }else{ + sqlite3VdbeAddOp2(v, OP_Goto, 0, labelContinue); + } + sqlite3VdbeResolveLabel(v, labelBreak); /* Close all tables */ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ assert( aRegIdx ); - if( openAll || aRegIdx[i]>0 ){ - sqlite3VdbeAddOp2(v, OP_Close, iCur+i+1, 0); + if( aToOpen[i+1] ){ + sqlite3VdbeAddOp2(v, OP_Close, iIdxCur+i, 0); } } - sqlite3VdbeAddOp2(v, OP_Close, iCur, 0); + if( iDataCur<iIdxCur ) sqlite3VdbeAddOp2(v, OP_Close, iDataCur, 0); /* Update the sqlite_sequence table by storing the content of the ** maximum rowid counter values recorded while inserting into ** autoincrement tables. */ @@ -104594,19 +113574,18 @@ sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC); } update_cleanup: sqlite3AuthContextPop(&sContext); - sqlite3DbFree(db, aRegIdx); - sqlite3DbFree(db, aXRef); + sqlite3DbFree(db, aXRef); /* Also frees aRegIdx[] and aToOpen[] */ sqlite3SrcListDelete(db, pTabList); sqlite3ExprListDelete(db, pChanges); sqlite3ExprDelete(db, pWhere); return; } /* Make sure "isView" and other macros defined above are undefined. Otherwise -** thely may interfere with compilation of other functions in this file +** they may interfere with compilation of other functions in this file ** (or in another file, if this file becomes part of the amalgamation). */ #ifdef isView #undef isView #endif #ifdef pTrigger @@ -104615,19 +113594,19 @@ #ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Generate code for an UPDATE of a virtual table. ** -** The strategy is that we create an ephemerial table that contains +** The strategy is that we create an ephemeral table that contains ** for each row to be changed: ** ** (A) The original rowid of that row. ** (B) The revised rowid for the row. (note1) ** (C) The content of every column in the row. ** ** Then we loop over this ephemeral table and for each row in -** the ephermeral table call VUpdate. +** the ephemeral table call VUpdate. ** ** When finished, drop the ephemeral table. ** ** (note1) Actually, if we know in advance that (A) is always the same ** as (B) we only store (A), then duplicate (A) when pulling @@ -104688,21 +113667,21 @@ sqlite3Select(pParse, pSelect, &dest); /* Generate code to scan the ephemeral table and call VUpdate. */ iReg = ++pParse->nMem; pParse->nMem += pTab->nCol+1; - addr = sqlite3VdbeAddOp2(v, OP_Rewind, ephemTab, 0); + addr = sqlite3VdbeAddOp2(v, OP_Rewind, ephemTab, 0); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_Column, ephemTab, 0, iReg); sqlite3VdbeAddOp3(v, OP_Column, ephemTab, (pRowid?1:0), iReg+1); for(i=0; i<pTab->nCol; i++){ sqlite3VdbeAddOp3(v, OP_Column, ephemTab, i+1+(pRowid!=0), iReg+2+i); } sqlite3VtabMakeWritable(pParse, pTab); sqlite3VdbeAddOp4(v, OP_VUpdate, 0, pTab->nCol+2, iReg, pVTab, P4_VTAB); sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError); sqlite3MayAbort(pParse); - sqlite3VdbeAddOp2(v, OP_Next, ephemTab, addr+1); + sqlite3VdbeAddOp2(v, OP_Next, ephemTab, addr+1); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addr); sqlite3VdbeAddOp2(v, OP_Close, ephemTab, 0); /* Cleanup */ sqlite3SelectDelete(db, pSelect); @@ -104781,18 +113760,38 @@ return vacuumFinalize(db, pStmt, pzErrMsg); } /* -** The non-standard VACUUM command is used to clean up the database, +** The VACUUM command is used to clean up the database, ** collapse free space, etc. It is modelled after the VACUUM command -** in PostgreSQL. +** in PostgreSQL. The VACUUM command works as follows: ** -** In version 1.0.x of SQLite, the VACUUM command would call -** gdbm_reorganize() on all the database tables. But beginning -** with 2.0.0, SQLite no longer uses GDBM so this command has -** become a no-op. +** (1) Create a new transient database file +** (2) Copy all content from the database being vacuumed into +** the new transient database file +** (3) Copy content from the transient database back into the +** original database. +** +** The transient database requires temporary disk space approximately +** equal to the size of the original database. The copy operation of +** step (3) requires additional temporary disk space approximately equal +** to the size of the original database for the rollback journal. +** Hence, temporary disk space that is approximately 2x the size of the +** original database is required. Every page of the database is written +** approximately 3 times: Once for step (2) and twice for step (3). +** Two writes per page are required in step (3) because the original +** database content must be written into the rollback journal prior to +** overwriting the database with the vacuumed content. +** +** Only 1x temporary space and only 1x writes would be required if +** the copy of step (3) were replaced by deleting the original database +** and renaming the transient database as the original. But that will +** not work if other processes are attached to the original database. +** And a power loss in between deleting the original and renaming the +** transient would cause the database file to appear to be deleted +** following reboot. */ SQLITE_PRIVATE void sqlite3Vacuum(Parse *pParse){ Vdbe *v = sqlite3GetVdbe(pParse); if( v ){ sqlite3VdbeAddOp2(v, OP_Vacuum, 0, 0); @@ -104873,11 +113872,11 @@ ** locked (as there was more than one active statement when the transaction ** to read the schema was concluded. Unlock it here so that this doesn't ** cause problems for the call to BtreeSetPageSize() below. */ sqlite3BtreeCommit(pTemp); - nRes = sqlite3BtreeGetReserve(pMain); + nRes = sqlite3BtreeGetOptimalReserve(pMain); /* A VACUUM cannot change the pagesize of an encrypted database. */ #ifdef SQLITE_HAS_CODEC if( db->nextPagesize ){ extern void sqlite3CodecGetKey(sqlite3*, int, void**, int*); @@ -104923,11 +113922,11 @@ ** in the temporary database. */ rc = execExecSql(db, pzErrMsg, "SELECT 'CREATE TABLE vacuum_db.' || substr(sql,14) " " FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'" - " AND rootpage>0" + " AND coalesce(rootpage,1)>0" ); if( rc!=SQLITE_OK ) goto end_of_vacuum; rc = execExecSql(db, pzErrMsg, "SELECT 'CREATE INDEX vacuum_db.' || substr(sql,14)" " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %' "); @@ -104944,11 +113943,11 @@ rc = execExecSql(db, pzErrMsg, "SELECT 'INSERT INTO vacuum_db.' || quote(name) " "|| ' SELECT * FROM main.' || quote(name) || ';'" "FROM main.sqlite_master " "WHERE type = 'table' AND name!='sqlite_sequence' " - " AND rootpage>0" + " AND coalesce(rootpage,1)>0" ); if( rc!=SQLITE_OK ) goto end_of_vacuum; /* Copy over the sequence table */ @@ -105103,11 +114102,11 @@ int rc = SQLITE_OK; int nName; sqlite3_mutex_enter(db->mutex); nName = sqlite3Strlen30(zName); - if( sqlite3HashFind(&db->aModule, zName, nName) ){ + if( sqlite3HashFind(&db->aModule, zName) ){ rc = SQLITE_MISUSE_BKPT; }else{ Module *pMod; pMod = (Module *)sqlite3DbMallocRaw(db, sizeof(Module) + nName + 1); if( pMod ){ @@ -105116,11 +114115,11 @@ memcpy(zCopy, zName, nName+1); pMod->zName = zCopy; pMod->pModule = pModule; pMod->pAux = pAux; pMod->xDestroy = xDestroy; - pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,nName,(void*)pMod); + pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,(void*)pMod); assert( pDel==0 || pDel==pMod ); if( pDel ){ db->mallocFailed = 1; sqlite3DbFree(db, pDel); } @@ -105135,29 +114134,35 @@ /* ** External API function used to create a new virtual-table module. */ -SQLITE_API int sqlite3_create_module( +SQLITE_API int SQLITE_STDCALL sqlite3_create_module( sqlite3 *db, /* Database in which module is registered */ const char *zName, /* Name assigned to this module */ const sqlite3_module *pModule, /* The definition of the module */ void *pAux /* Context pointer for xCreate/xConnect */ ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif return createModule(db, zName, pModule, pAux, 0); } /* ** External API function used to create a new virtual-table module. */ -SQLITE_API int sqlite3_create_module_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_create_module_v2( sqlite3 *db, /* Database in which module is registered */ const char *zName, /* Name assigned to this module */ const sqlite3_module *pModule, /* The definition of the module */ void *pAux, /* Context pointer for xCreate/xConnect */ void (*xDestroy)(void *) /* Module destructor function */ ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif return createModule(db, zName, pModule, pAux, xDestroy); } /* ** Lock the virtual table so that it cannot be disconnected. @@ -105386,11 +114391,16 @@ pTable->tabFlags |= TF_Virtual; pTable->nModuleArg = 0; addModuleArgument(db, pTable, sqlite3NameFromToken(db, pModuleName)); addModuleArgument(db, pTable, 0); addModuleArgument(db, pTable, sqlite3DbStrDup(db, pTable->zName)); - pParse->sNameToken.n = (int)(&pModuleName->z[pModuleName->n] - pName1->z); + assert( (pParse->sNameToken.z==pName2->z && pName2->z!=0) + || (pParse->sNameToken.z==pName1->z && pName2->z==0) + ); + pParse->sNameToken.n = (int)( + &pModuleName->z[pModuleName->n] - pParse->sNameToken.z + ); #ifndef SQLITE_OMIT_AUTHORIZATION /* Creating a virtual table invokes the authorization callback twice. ** The first invocation, to obtain permission to INSERT a row into the ** sqlite_master table, has already been made by sqlite3StartTable(). @@ -105438,10 +114448,11 @@ */ if( !db->init.busy ){ char *zStmt; char *zWhere; int iDb; + int iReg; Vdbe *v; /* Compute the complete text of the CREATE VIRTUAL TABLE statement */ if( pEnd ){ pParse->sNameToken.n = (int)(pEnd->z - pParse->sNameToken.z) + pEnd->n; @@ -105472,12 +114483,14 @@ sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddOp2(v, OP_Expire, 0, 0); zWhere = sqlite3MPrintf(db, "name='%q' AND type='table'", pTab->zName); sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere); - sqlite3VdbeAddOp4(v, OP_VCreate, iDb, 0, 0, - pTab->zName, sqlite3Strlen30(pTab->zName) + 1); + + iReg = ++pParse->nMem; + sqlite3VdbeAddOp4(v, OP_String8, 0, iReg, 0, pTab->zName, 0); + sqlite3VdbeAddOp2(v, OP_VCreate, iDb, iReg); } /* If we are rereading the sqlite_master table create the in-memory ** record of the table. The xConnect() method is not called until ** the first time the virtual table is used in an SQL statement. This @@ -105485,13 +114498,12 @@ ** the required virtual table implementations are registered. */ else { Table *pOld; Schema *pSchema = pTab->pSchema; const char *zName = pTab->zName; - int nName = sqlite3Strlen30(zName); assert( sqlite3SchemaMutexHeld(db, 0, pSchema) ); - pOld = sqlite3HashInsert(&pSchema->tblHash, zName, nName, pTab); + pOld = sqlite3HashInsert(&pSchema->tblHash, zName, pTab); if( pOld ){ db->mallocFailed = 1; assert( pTab==pOld ); /* Malloc must have failed inside HashInsert() */ return; } @@ -105580,10 +114592,11 @@ } sqlite3DbFree(db, pVTable); }else if( ALWAYS(pVTable->pVtab) ){ /* Justification of ALWAYS(): A correct vtab constructor must allocate ** the sqlite3_vtab object if successful. */ + memset(pVTable->pVtab, 0, sizeof(pVTable->pVtab[0])); pVTable->pVtab->pModule = pMod->pModule; pVTable->nRef = 1; if( sCtx.pTab ){ const char *zFormat = "vtable constructor did not declare schema: %s"; *pzErr = sqlite3MPrintf(db, zFormat, pTab->zName); @@ -105653,11 +114666,11 @@ return SQLITE_OK; } /* Locate the required virtual table module */ zMod = pTab->azModuleArg[0]; - pMod = (Module*)sqlite3HashFind(&db->aModule, zMod, sqlite3Strlen30(zMod)); + pMod = (Module*)sqlite3HashFind(&db->aModule, zMod); if( !pMod ){ const char *zModule = pTab->azModuleArg[0]; sqlite3ErrorMsg(pParse, "no such module: %s", zModule); rc = SQLITE_ERROR; @@ -105721,11 +114734,11 @@ pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zName); assert( pTab && (pTab->tabFlags & TF_Virtual)!=0 && !pTab->pVTable ); /* Locate the required virtual table module */ zMod = pTab->azModuleArg[0]; - pMod = (Module*)sqlite3HashFind(&db->aModule, zMod, sqlite3Strlen30(zMod)); + pMod = (Module*)sqlite3HashFind(&db->aModule, zMod); /* If the module has been registered and includes a Create method, ** invoke it now. If the module has not been registered, return an ** error. Otherwise, do nothing. */ @@ -105751,20 +114764,25 @@ /* ** This function is used to set the schema of a virtual table. It is only ** valid to call this function from within the xCreate() or xConnect() of a ** virtual table module. */ -SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ +SQLITE_API int SQLITE_STDCALL sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ Parse *pParse; int rc = SQLITE_OK; Table *pTab; char *zErr = 0; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif sqlite3_mutex_enter(db->mutex); if( !db->pVtabCtx || !(pTab = db->pVtabCtx->pTab) ){ - sqlite3Error(db, SQLITE_MISUSE, 0); + sqlite3Error(db, SQLITE_MISUSE); sqlite3_mutex_leave(db->mutex); return SQLITE_MISUSE_BKPT; } assert( (pTab->tabFlags & TF_Virtual)!=0 ); @@ -105788,20 +114806,21 @@ pParse->pNewTable->nCol = 0; pParse->pNewTable->aCol = 0; } db->pVtabCtx->pTab = 0; }else{ - sqlite3Error(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr); + sqlite3ErrorWithMsg(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr); sqlite3DbFree(db, zErr); rc = SQLITE_ERROR; } pParse->declareVtab = 0; if( pParse->pVdbe ){ sqlite3VdbeFinalize(pParse->pVdbe); } sqlite3DeleteTable(db, pParse->pNewTable); + sqlite3ParserReset(pParse); sqlite3StackFree(db, pParse); } assert( (rc&0xff)==rc ); rc = sqlite3ApiExit(db, rc); @@ -105820,15 +114839,19 @@ int rc = SQLITE_OK; Table *pTab; pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zName); if( ALWAYS(pTab!=0 && pTab->pVTable!=0) ){ - VTable *p = vtabDisconnectAll(db, pTab); - - assert( rc==SQLITE_OK ); + VTable *p; + for(p=pTab->pVTable; p; p=p->pNext){ + assert( p->pVtab ); + if( p->pVtab->nRef>0 ){ + return SQLITE_LOCKED; + } + } + p = vtabDisconnectAll(db, pTab); rc = p->pMod->pModule->xDestroy(p->pVtab); - /* Remove the sqlite3_vtab* from the aVTrans[] array, if applicable */ if( rc==SQLITE_OK ){ assert( pTab->pVTable==p && p->pNext==0 ); p->pVtab = 0; pTab->pVTable = 0; @@ -106109,14 +115132,17 @@ ** table update operation currently in progress. ** ** The results of this routine are undefined unless it is called from ** within an xUpdate method. */ -SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *db){ +SQLITE_API int SQLITE_STDCALL sqlite3_vtab_on_conflict(sqlite3 *db){ static const unsigned char aMap[] = { SQLITE_ROLLBACK, SQLITE_ABORT, SQLITE_FAIL, SQLITE_IGNORE, SQLITE_REPLACE }; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif assert( OE_Rollback==1 && OE_Abort==2 && OE_Fail==3 ); assert( OE_Ignore==4 && OE_Replace==5 ); assert( db->vtabOnConflict>=1 && db->vtabOnConflict<=5 ); return (int)aMap[db->vtabOnConflict-1]; } @@ -106124,16 +115150,18 @@ /* ** Call from within the xCreate() or xConnect() methods to provide ** the SQLite core with additional information about the behavior ** of the virtual table being implemented. */ -SQLITE_API int sqlite3_vtab_config(sqlite3 *db, int op, ...){ +SQLITE_API int SQLITE_CDECL sqlite3_vtab_config(sqlite3 *db, int op, ...){ va_list ap; int rc = SQLITE_OK; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); - va_start(ap, op); switch( op ){ case SQLITE_VTAB_CONSTRAINT_SUPPORT: { VtabCtx *p = db->pVtabCtx; if( !p ){ @@ -106148,11 +115176,11 @@ rc = SQLITE_MISUSE_BKPT; break; } va_end(ap); - if( rc!=SQLITE_OK ) sqlite3Error(db, rc, 0); + if( rc!=SQLITE_OK ) sqlite3Error(db, rc); sqlite3_mutex_leave(db->mutex); return rc; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -106175,11 +115203,28 @@ ** generating the code that loops through a table looking for applicable ** rows. Indices are selected and used to speed the search when doing ** so is applicable. Because this module is responsible for selecting ** indices, you might also think of this module as the "query optimizer". */ - +/************** Include whereInt.h in the middle of where.c ******************/ +/************** Begin file whereInt.h ****************************************/ +/* +** 2013-11-12 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains structure and macro definitions for the query +** planner logic in "where.c". These definitions are broken out into +** a separate source file for easier editing. +*/ /* ** Trace output macros */ #if defined(SQLITE_TEST) || defined(SQLITE_DEBUG) @@ -106206,30 +115251,10 @@ typedef struct WhereLoopBuilder WhereLoopBuilder; typedef struct WhereScan WhereScan; typedef struct WhereOrCost WhereOrCost; typedef struct WhereOrSet WhereOrSet; -/* -** Cost X is tracked as 10*log2(X) stored in a 16-bit integer. The -** maximum cost for ordinary tables is 64*(2**63) which becomes 6900. -** (Virtual tables can return a larger cost, but let's assume they do not.) -** So all costs can be stored in a 16-bit integer without risk -** of overflow. -** -** Costs are estimates, so no effort is made to compute 10*log2(X) exactly. -** Instead, a close estimate is used. Any value of X=1 is stored as 0. -** X=2 is 10. X=3 is 16. X=1000 is 99. etc. Negative values are allowed. -** A WhereCost of -10 means 0.5. WhereCost of -20 means 0.25. And so forth. -** -** The tool/wherecosttest.c source file implements a command-line program -** that will convert WhereCosts to integers, convert integers to WhereCosts -** and do addition and multiplication on WhereCost values. The wherecosttest -** command-line program is a useful utility to have around when working with -** this module. -*/ -typedef short int WhereCost; - /* ** This object contains information needed to implement a single nested ** loop in WHERE clause. ** ** Contrast this object with WhereLoop. This object describes the @@ -106247,15 +115272,18 @@ int iLeftJoin; /* Memory cell used to implement LEFT OUTER JOIN */ int iTabCur; /* The VDBE cursor used to access the table */ int iIdxCur; /* The VDBE cursor used to access pIdx */ int addrBrk; /* Jump here to break out of the loop */ int addrNxt; /* Jump here to start the next IN combination */ + int addrSkip; /* Jump here for next iteration of skip-scan */ int addrCont; /* Jump here to continue with the next loop cycle */ int addrFirst; /* First instruction of interior of the loop */ int addrBody; /* Beginning of the body of this loop */ + int iLikeRepCntr; /* LIKE range processing counter register */ + int addrLikeRep; /* LIKE range processing address */ u8 iFrom; /* Which entry in the FROM clause */ - u8 op, p5; /* Opcode and P5 of the opcode that ends the loop */ + u8 op, p3, p5; /* Opcode, P3 & P5 of the opcode that ends the loop */ int p1, p2; /* Operands of the opcode used to ends the loop */ union { /* Information that depends on pWLoop->wsFlags */ struct { int nIn; /* Number of entries in aInLoop[] */ struct InLoop { @@ -106266,10 +115294,13 @@ } in; /* Used when pWLoop->wsFlags&WHERE_IN_ABLE */ Index *pCovidx; /* Possible covering index for WHERE_MULTI_OR */ } u; struct WhereLoop *pWLoop; /* The selected WhereLoop object */ Bitmask notReady; /* FROM entries not usable at this level */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrVisit; /* Address at which row is visited */ +#endif }; /* ** Each instance of this object represents an algorithm for evaluating one ** term of a join. Every term of the FROM clause will have at least @@ -106290,44 +115321,45 @@ #ifdef SQLITE_DEBUG char cId; /* Symbolic ID of this loop for debugging use */ #endif u8 iTab; /* Position in FROM clause of table for this loop */ u8 iSortIdx; /* Sorting index number. 0==None */ - WhereCost rSetup; /* One-time setup cost (ex: create transient index) */ - WhereCost rRun; /* Cost of running each loop */ - WhereCost nOut; /* Estimated number of output rows */ + LogEst rSetup; /* One-time setup cost (ex: create transient index) */ + LogEst rRun; /* Cost of running each loop */ + LogEst nOut; /* Estimated number of output rows */ union { struct { /* Information for internal btree tables */ - int nEq; /* Number of equality constraints */ + u16 nEq; /* Number of equality constraints */ Index *pIndex; /* Index used, or NULL */ } btree; struct { /* Information for virtual tables */ int idxNum; /* Index number */ u8 needFree; /* True if sqlite3_free(idxStr) is needed */ - u8 isOrdered; /* True if satisfies ORDER BY */ + i8 isOrdered; /* True if satisfies ORDER BY */ u16 omitMask; /* Terms that may be omitted */ char *idxStr; /* Index identifier string */ } vtab; } u; u32 wsFlags; /* WHERE_* flags describing the plan */ u16 nLTerm; /* Number of entries in aLTerm[] */ + u16 nSkip; /* Number of NULL aLTerm[] entries */ /**** whereLoopXfer() copies fields above ***********************/ # define WHERE_LOOP_XFER_SZ offsetof(WhereLoop,nLSlot) u16 nLSlot; /* Number of slots allocated for aLTerm[] */ WhereTerm **aLTerm; /* WhereTerms used */ WhereLoop *pNextLoop; /* Next WhereLoop object in the WhereClause */ - WhereTerm *aLTermSpace[4]; /* Initial aLTerm[] space */ + WhereTerm *aLTermSpace[3]; /* Initial aLTerm[] space */ }; /* This object holds the prerequisites and the cost of running a ** subquery on one operand of an OR operator in the WHERE clause. ** See WhereOrSet for additional information */ struct WhereOrCost { Bitmask prereq; /* Prerequisites */ - WhereCost rRun; /* Cost of running this subquery */ - WhereCost nOut; /* Number of outputs for this subquery */ + LogEst rRun; /* Cost of running this subquery */ + LogEst nOut; /* Number of outputs for this subquery */ }; /* The WhereOrSet object holds a set of possible WhereOrCosts that ** correspond to the subquery(s) of OR-clause processing. Only the ** best N_OR_COST elements are retained. @@ -106356,19 +115388,19 @@ ** ** The "solver" works by creating the N best WherePath objects of length ** 1. Then using those as a basis to compute the N best WherePath objects ** of length 2. And so forth until the length of WherePaths equals the ** number of nodes in the FROM clause. The best (lowest cost) WherePath -** at the end is the choosen query plan. +** at the end is the chosen query plan. */ struct WherePath { Bitmask maskLoop; /* Bitmask of all WhereLoop objects in this path */ Bitmask revLoop; /* aLoop[]s that should be reversed for ORDER BY */ - WhereCost nRow; /* Estimated number of rows generated by this path */ - WhereCost rCost; /* Total cost of this path */ - u8 isOrdered; /* True if this path satisfies ORDER BY */ - u8 isOrderedValid; /* True if the isOrdered field is valid */ + LogEst nRow; /* Estimated number of rows generated by this path */ + LogEst rCost; /* Total cost of this path */ + LogEst rUnsorted; /* Total cost of this path ignoring sorting costs */ + i8 isOrdered; /* No. of ORDER BY terms satisfied. -1 for unknown */ WhereLoop **aLoop; /* Array of WhereLoop objects implementing this path */ }; /* ** The query generator uses an array of instances of this structure to @@ -106428,13 +115460,13 @@ union { int leftColumn; /* Column number of X in "X <op> <expr>" */ WhereOrInfo *pOrInfo; /* Extra information if (eOperator & WO_OR)!=0 */ WhereAndInfo *pAndInfo; /* Extra information if (eOperator& WO_AND)!=0 */ } u; - WhereCost truthProb; /* Probability of truth for this expression */ + LogEst truthProb; /* Probability of truth for this expression */ u16 eOperator; /* A WO_xx value describing <op> */ - u8 wtFlags; /* TERM_xxx bit flags. See below */ + u16 wtFlags; /* TERM_xxx bit flags. See below */ u8 nChild; /* Number of children that must disable us */ WhereClause *pWC; /* The clause this term is part of */ Bitmask prereqRight; /* Bitmask of tables used by pExpr->pRight */ Bitmask prereqAll; /* Bitmask of tables referenced by pExpr */ }; @@ -106452,10 +115484,13 @@ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 # define TERM_VNULL 0x80 /* Manufactured x>NULL or x<=NULL term */ #else # define TERM_VNULL 0x00 /* Disabled if not using stat3 */ #endif +#define TERM_LIKEOPT 0x100 /* Virtual terms from the LIKE optimization */ +#define TERM_LIKECOND 0x200 /* Conditionally this LIKE operator term */ +#define TERM_LIKE 0x400 /* The original LIKE operator */ /* ** An instance of the WhereScan object is used as an iterator for locating ** terms in the WHERE clause that are useful to the query planner. */ @@ -106576,21 +115611,23 @@ SrcList *pTabList; /* List of tables in the join */ ExprList *pOrderBy; /* The ORDER BY clause or NULL */ ExprList *pResultSet; /* Result set. DISTINCT operates on these */ WhereLoop *pLoops; /* List of all WhereLoop objects */ Bitmask revMask; /* Mask of ORDER BY terms that need reversing */ - WhereCost nRowOut; /* Estimated number of output rows */ + LogEst nRowOut; /* Estimated number of output rows */ u16 wctrlFlags; /* Flags originally passed to sqlite3WhereBegin() */ - u8 bOBSat; /* ORDER BY satisfied by indices */ + i8 nOBSat; /* Number of ORDER BY terms satisfied by indices */ + u8 sorted; /* True if really sorted (not just grouped) */ u8 okOnePass; /* Ok to use one-pass algorithm for UPDATE/DELETE */ u8 untestedTerms; /* Not all WHERE terms resolved by outer loop */ u8 eDistinct; /* One of the WHERE_DISTINCT_* values below */ u8 nLevel; /* Number of nested loop */ int iTop; /* The very beginning of the WHERE loop */ int iContinue; /* Jump here to continue with next record */ int iBreak; /* Jump here to break out of the loop */ int savedNQueryLoop; /* pParse->nQueryLoop outside the WHERE loop */ + int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */ WhereMaskSet sMaskSet; /* Map cursor numbers to bitmasks */ WhereClause sWC; /* Decomposition of the WHERE clause */ WhereLevel a[1]; /* Information about each nest loop in WHERE */ }; @@ -106635,31 +115672,22 @@ #define WHERE_VIRTUALTABLE 0x00000400 /* WhereLoop.u.vtab is valid */ #define WHERE_IN_ABLE 0x00000800 /* Able to support an IN operator */ #define WHERE_ONEROW 0x00001000 /* Selects no more than one row */ #define WHERE_MULTI_OR 0x00002000 /* OR using multiple indices */ #define WHERE_AUTO_INDEX 0x00004000 /* Uses an ephemeral index */ - - -/* Convert a WhereCost value (10 times log2(X)) into its integer value X. -** A rough approximation is used. The value returned is not exact. -*/ -static u64 whereCostToInt(WhereCost x){ - u64 n; - if( x<10 ) return 1; - n = x%10; - x /= 10; - if( n>=5 ) n -= 2; - else if( n>=1 ) n -= 1; - if( x>=3 ) return (n+8)<<(x-3); - return (n+8)>>(3-x); -} +#define WHERE_SKIPSCAN 0x00008000 /* Uses the skip-scan algorithm */ +#define WHERE_UNQ_WANTED 0x00010000 /* WHERE_ONEROW would have been helpful*/ +#define WHERE_PARTIALIDX 0x00020000 /* The automatic index is partial */ + +/************** End of whereInt.h ********************************************/ +/************** Continuing where we left off in where.c **********************/ /* ** Return the estimated number of output rows from a WHERE clause */ SQLITE_PRIVATE u64 sqlite3WhereOutputRowCount(WhereInfo *pWInfo){ - return whereCostToInt(pWInfo->nRowOut); + return sqlite3LogEstToInt(pWInfo->nRowOut); } /* ** Return one of the WHERE_DISTINCT_xxxxx values to indicate how this ** WHERE clause returns outputs for DISTINCT processing. @@ -106671,18 +115699,19 @@ /* ** Return TRUE if the WHERE clause returns rows in ORDER BY order. ** Return FALSE if the output needs to be sorted. */ SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo *pWInfo){ - return pWInfo->bOBSat!=0; + return pWInfo->nOBSat; } /* ** Return the VDBE address or label to jump to in order to continue ** immediately with the next row of a WHERE clause. */ SQLITE_PRIVATE int sqlite3WhereContinueLabel(WhereInfo *pWInfo){ + assert( pWInfo->iContinue!=0 ); return pWInfo->iContinue; } /* ** Return the VDBE address or label to jump to in order to break @@ -106694,12 +115723,23 @@ /* ** Return TRUE if an UPDATE or DELETE statement can operate directly on ** the rowids returned by a WHERE clause. Return FALSE if doing an ** UPDATE or DELETE might change subsequent WHERE clause results. +** +** If the ONEPASS optimization is used (if this routine returns true) +** then also write the indices of open cursors used by ONEPASS +** into aiCur[0] and aiCur[1]. iaCur[0] gets the cursor of the data +** table and iaCur[1] gets the cursor used by an auxiliary index. +** Either value may be -1, indicating that cursor is not used. +** Any cursors returned will have been opened for writing. +** +** aiCur[0] and aiCur[1] both get -1 if the where-clause logic is +** unable to use the ONEPASS optimization. */ -SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo *pWInfo){ +SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo *pWInfo, int *aiCur){ + memcpy(aiCur, pWInfo->aiCurOnePass, sizeof(int)*2); return pWInfo->okOnePass; } /* ** Move the content of pSrc into pDest @@ -106717,12 +115757,12 @@ ** so that pSet keeps the N_OR_COST best entries seen so far. */ static int whereOrInsert( WhereOrSet *pSet, /* The WhereOrSet to be updated */ Bitmask prereq, /* Prerequisites of the new entry */ - WhereCost rRun, /* Run-cost of the new entry */ - WhereCost nOut /* Number of outputs for the new entry */ + LogEst rRun, /* Run-cost of the new entry */ + LogEst nOut /* Number of outputs for the new entry */ ){ u16 i; WhereOrCost *p; for(i=pSet->n, p=pSet->a; i>0; i--, p++){ if( rRun<=p->rRun && (prereq & p->prereq)==prereq ){ @@ -106803,13 +115843,10 @@ if( pWC->a!=pWC->aStatic ){ sqlite3DbFree(db, pWC->a); } } -/* Forward declaration */ -static WhereCost whereCost(tRowcnt x); - /* ** Add a single new WhereTerm entry to the WhereClause object pWC. ** The new WhereTerm object is constructed from Expr p and with wtFlags. ** The index in pWC->a[] of the new WhereTerm is returned on success. ** 0 is returned if the new WhereTerm could not be added due to a memory @@ -106825,11 +115862,11 @@ ** WARNING: This routine might reallocate the space used to store ** WhereTerms. All pointers to WhereTerms should be invalidated after ** calling this routine. Such pointers may be reinitialized by referencing ** the pWC->a[] array. */ -static int whereClauseInsert(WhereClause *pWC, Expr *p, u8 wtFlags){ +static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ WhereTerm *pTerm; int idx; testcase( wtFlags & TERM_VIRTUAL ); if( pWC->nTerm>=pWC->nSlot ){ WhereTerm *pOld = pWC->a; @@ -106845,16 +115882,17 @@ memcpy(pWC->a, pOld, sizeof(pWC->a[0])*pWC->nTerm); if( pOld!=pWC->aStatic ){ sqlite3DbFree(db, pOld); } pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]); + memset(&pWC->a[pWC->nTerm], 0, sizeof(pWC->a[0])*(pWC->nSlot-pWC->nTerm)); } pTerm = &pWC->a[idx = pWC->nTerm++]; if( p && ExprHasProperty(p, EP_Unlikely) ){ - pTerm->truthProb = whereCost(p->iTable) - 99; + pTerm->truthProb = sqlite3LogEst(p->iTable) - 270; }else{ - pTerm->truthProb = -1; + pTerm->truthProb = 1; } pTerm->pExpr = sqlite3ExprSkipCollate(p); pTerm->wtFlags = wtFlags; pTerm->pWC = pWC; pTerm->iParent = -1; @@ -106987,15 +116025,10 @@ assert( TK_LE>TK_EQ && TK_LE<TK_GE ); assert( TK_GE==TK_EQ+4 ); return op==TK_IN || (op>=TK_EQ && op<=TK_GE) || op==TK_ISNULL; } -/* -** Swap two objects of type TYPE. -*/ -#define SWAP(TYPE,A,B) {TYPE t=A; A=B; B=t;} - /* ** Commute a comparison operator. Expressions of the form "X op Y" ** are converted into "Y op X". ** ** If left/right precedence rules come into play when determining the @@ -107074,11 +116107,14 @@ while( pScan->iEquiv<=pScan->nEquiv ){ iCur = pScan->aEquiv[pScan->iEquiv-2]; iColumn = pScan->aEquiv[pScan->iEquiv-1]; while( (pWC = pScan->pWC)!=0 ){ for(pTerm=pWC->a+k; k<pWC->nTerm; k++, pTerm++){ - if( pTerm->leftCursor==iCur && pTerm->u.leftColumn==iColumn ){ + if( pTerm->leftCursor==iCur + && pTerm->u.leftColumn==iColumn + && (pScan->iEquiv<=2 || !ExprHasProperty(pTerm->pExpr, EP_FromJoin)) + ){ if( (pTerm->eOperator & WO_EQUIV)!=0 && pScan->nEquiv<ArraySize(pScan->aEquiv) ){ int j; pX = sqlite3ExprSkipCollate(pTerm->pExpr->pRight); @@ -107164,11 +116200,11 @@ pScan->pOrigWC = pWC; pScan->pWC = pWC; if( pIdx && iColumn>=0 ){ pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; for(j=0; pIdx->aiColumn[j]!=iColumn; j++){ - if( NEVER(j>=pIdx->nColumn) ) return 0; + if( NEVER(j>pIdx->nColumn) ) return 0; } pScan->zCollName = pIdx->azColl[j]; }else{ pScan->idxaff = 0; pScan->zCollName = 0; @@ -107251,11 +116287,15 @@ ** Check to see if the given expression is a LIKE or GLOB operator that ** can be optimized using inequality constraints. Return TRUE if it is ** so and false if not. ** ** In order for the operator to be optimizible, the RHS must be a string -** literal that does not begin with a wildcard. +** literal that does not begin with a wildcard. The LHS must be a column +** that may only be NULL, a string, or a BLOB, never a number. (This means +** that virtual tables cannot participate in the LIKE optimization.) If the +** collating sequence for the column on the LHS must be appropriate for +** the operator. */ static int isLikeOrGlob( Parse *pParse, /* Parsing and code generating context */ Expr *pExpr, /* Test this expression */ Expr **ppPrefix, /* Pointer to TK_STRING expression with pattern prefix */ @@ -107280,23 +116320,20 @@ #endif pList = pExpr->x.pList; pLeft = pList->a[1].pExpr; if( pLeft->op!=TK_COLUMN || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT - || IsVirtual(pLeft->pTab) + || IsVirtual(pLeft->pTab) /* Value might be numeric */ ){ /* IMP: R-02065-49465 The left-hand side of the LIKE or GLOB operator must ** be the name of an indexed column with TEXT affinity. */ return 0; } assert( pLeft->iColumn!=(-1) ); /* Because IPK never has AFF_TEXT */ - pRight = pList->a[0].pExpr; + pRight = sqlite3ExprSkipCollate(pList->a[0].pExpr); op = pRight->op; - if( op==TK_REGISTER ){ - op = pRight->op2; - } if( op==TK_VARIABLE ){ Vdbe *pReprepare = pParse->pReprepare; int iCol = pRight->iColumn; pVal = sqlite3VdbeGetBoundValue(pReprepare, iCol, SQLITE_AFF_NONE); if( pVal && sqlite3_value_type(pVal)==SQLITE_TEXT ){ @@ -107324,11 +116361,11 @@ if( *pisComplete && pRight->u.zToken[1] ){ /* If the rhs of the LIKE expression is a variable, and the current ** value of the variable means there is no need to invoke the LIKE ** function, then no OP_Variable will be added to the program. ** This causes problems for the sqlite3_bind_parameter_name() - ** API. To workaround them, add a dummy OP_Variable here. + ** API. To work around them, add a dummy OP_Variable here. */ int r1 = sqlite3GetTempReg(pParse); sqlite3ExprCodeTarget(pParse, pRight, r1); sqlite3VdbeChangeP3(v, sqlite3VdbeCurrentAddr(v)-1, 0); sqlite3ReleaseTempReg(pParse, r1); @@ -107383,10 +116420,92 @@ if( pDerived ){ pDerived->flags |= pBase->flags & EP_FromJoin; pDerived->iRightJoinTable = pBase->iRightJoinTable; } } + +/* +** Mark term iChild as being a child of term iParent +*/ +static void markTermAsChild(WhereClause *pWC, int iChild, int iParent){ + pWC->a[iChild].iParent = iParent; + pWC->a[iChild].truthProb = pWC->a[iParent].truthProb; + pWC->a[iParent].nChild++; +} + +/* +** Return the N-th AND-connected subterm of pTerm. Or if pTerm is not +** a conjunction, then return just pTerm when N==0. If N is exceeds +** the number of available subterms, return NULL. +*/ +static WhereTerm *whereNthSubterm(WhereTerm *pTerm, int N){ + if( pTerm->eOperator!=WO_AND ){ + return N==0 ? pTerm : 0; + } + if( N<pTerm->u.pAndInfo->wc.nTerm ){ + return &pTerm->u.pAndInfo->wc.a[N]; + } + return 0; +} + +/* +** Subterms pOne and pTwo are contained within WHERE clause pWC. The +** two subterms are in disjunction - they are OR-ed together. +** +** If these two terms are both of the form: "A op B" with the same +** A and B values but different operators and if the operators are +** compatible (if one is = and the other is <, for example) then +** add a new virtual AND term to pWC that is the combination of the +** two. +** +** Some examples: +** +** x<y OR x=y --> x<=y +** x=y OR x=y --> x=y +** x<=y OR x<y --> x<=y +** +** The following is NOT generated: +** +** x<y OR x>y --> x!=y +*/ +static void whereCombineDisjuncts( + SrcList *pSrc, /* the FROM clause */ + WhereClause *pWC, /* The complete WHERE clause */ + WhereTerm *pOne, /* First disjunct */ + WhereTerm *pTwo /* Second disjunct */ +){ + u16 eOp = pOne->eOperator | pTwo->eOperator; + sqlite3 *db; /* Database connection (for malloc) */ + Expr *pNew; /* New virtual expression */ + int op; /* Operator for the combined expression */ + int idxNew; /* Index in pWC of the next virtual term */ + + if( (pOne->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; + if( (pTwo->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; + if( (eOp & (WO_EQ|WO_LT|WO_LE))!=eOp + && (eOp & (WO_EQ|WO_GT|WO_GE))!=eOp ) return; + assert( pOne->pExpr->pLeft!=0 && pOne->pExpr->pRight!=0 ); + assert( pTwo->pExpr->pLeft!=0 && pTwo->pExpr->pRight!=0 ); + if( sqlite3ExprCompare(pOne->pExpr->pLeft, pTwo->pExpr->pLeft, -1) ) return; + if( sqlite3ExprCompare(pOne->pExpr->pRight, pTwo->pExpr->pRight, -1) )return; + /* If we reach this point, it means the two subterms can be combined */ + if( (eOp & (eOp-1))!=0 ){ + if( eOp & (WO_LT|WO_LE) ){ + eOp = WO_LE; + }else{ + assert( eOp & (WO_GT|WO_GE) ); + eOp = WO_GE; + } + } + db = pWC->pWInfo->pParse->db; + pNew = sqlite3ExprDup(db, pOne->pExpr, 0); + if( pNew==0 ) return; + for(op=TK_EQ; eOp!=(WO_EQ<<(op-TK_EQ)); op++){ assert( op<TK_GE ); } + pNew->op = op; + idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC); + exprAnalyze(pSrc, pWC, idxNew); +} #if !defined(SQLITE_OMIT_OR_OPTIMIZATION) && !defined(SQLITE_OMIT_SUBQUERY) /* ** Analyze a term that consists of two or more OR-connected ** subterms. So in: @@ -107408,10 +116527,11 @@ ** (A) t1.x=t2.y OR t1.x=t2.z OR t1.y=15 OR t1.z=t3.a+5 ** (B) x=expr1 OR expr2=x OR x=expr3 ** (C) t1.x=t2.y OR (t1.x=t2.z AND t1.y=15) ** (D) x=expr1 OR (y>11 AND y<22 AND z LIKE '*hello*') ** (E) (p.a=1 AND q.b=2 AND r.c=3) OR (p.x=4 AND q.y=5 AND r.z=6) +** (F) x>A OR (x=A AND y>=B) ** ** CASE 1: ** ** If all subterms are of the form T.C=expr for some single column of C and ** a single table T (as shown in example B above) then create a new virtual @@ -107423,10 +116543,20 @@ ** then create a new virtual term like this: ** ** x IN (expr1,expr2,expr3) ** ** CASE 2: +** +** If there are exactly two disjuncts one side has x>A and the other side +** has x=A (for the same x and A) then add a new virtual conjunct term to the +** WHERE clause of the form "x>=A". Example: +** +** x>A OR (x=A AND y>B) adds: x>=A +** +** The added conjunct can sometimes be helpful in query planning. +** +** CASE 3: ** ** If all subterms are indexable by a single table T, then set ** ** WhereTerm.eOperator = WO_OR ** WhereTerm.u.pOrInfo->indexable |= the cursor number for table T @@ -107444,11 +116574,11 @@ ** This analysis does not consider whether or not the index exists; that ** is decided elsewhere. This analysis only looks at whether subterms ** appropriate for indexing exist. ** ** All examples A through E above satisfy case 2. But if a term -** also statisfies case 1 (such as B) we know that the optimizer will +** also satisfies case 1 (such as B) we know that the optimizer will ** always prefer case 1, so in that case we pretend that case 2 is not ** satisfied. ** ** It might be the case that multiple tables are indexable. For example, ** (E) above is indexable on tables P, Q, and R. @@ -107550,15 +116680,29 @@ } } } /* - ** Record the set of tables that satisfy case 2. The set might be + ** Record the set of tables that satisfy case 3. The set might be ** empty. */ pOrInfo->indexable = indexable; pTerm->eOperator = indexable==0 ? 0 : WO_OR; + + /* For a two-way OR, attempt to implementation case 2. + */ + if( indexable && pOrWc->nTerm==2 ){ + int iOne = 0; + WhereTerm *pOne; + while( (pOne = whereNthSubterm(&pOrWc->a[0],iOne++))!=0 ){ + int iTwo = 0; + WhereTerm *pTwo; + while( (pTwo = whereNthSubterm(&pOrWc->a[1],iTwo++))!=0 ){ + whereCombineDisjuncts(pSrc, pWC, pOne, pTwo); + } + } + } /* ** chngToIN holds a set of tables that *might* satisfy case 1. But ** we have to do some additional checking to see if case 1 really ** is satisfied. @@ -107602,11 +116746,11 @@ assert( j==1 ); continue; } if( (chngToIN & getMask(&pWInfo->sMaskSet, pOrTerm->leftCursor))==0 ){ /* This term must be of the form t1.a==t2.b where t2 is in the - ** chngToIN set but t1 is not. This term will be either preceeded + ** chngToIN set but t1 is not. This term will be either preceded ** or follwed by an inverted copy (t2.b==t1.a). Skip this term ** and use its inversion. */ testcase( pOrTerm->wtFlags & TERM_COPIED ); testcase( pOrTerm->wtFlags & TERM_VIRTUAL ); assert( pOrTerm->wtFlags & (TERM_COPIED|TERM_VIRTUAL) ); @@ -107681,16 +116825,15 @@ pNew->x.pList = pList; idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); exprAnalyze(pSrc, pWC, idxNew); pTerm = &pWC->a[idxTerm]; - pWC->a[idxNew].iParent = idxTerm; - pTerm->nChild = 1; + markTermAsChild(pWC, idxNew, idxTerm); }else{ sqlite3ExprListDelete(db, pList); } - pTerm->eOperator = WO_NOOP; /* case 1 trumps case 2 */ + pTerm->eOperator = WO_NOOP; /* case 1 trumps case 3 */ } } } #endif /* !SQLITE_OMIT_OR_OPTIMIZATION && !SQLITE_OMIT_SUBQUERY */ @@ -107724,11 +116867,11 @@ Bitmask prereqLeft; /* Prerequesites of the pExpr->pLeft */ Bitmask prereqAll; /* Prerequesites of pExpr */ Bitmask extraRight = 0; /* Extra dependencies on LEFT JOIN */ Expr *pStr1 = 0; /* RHS of LIKE/GLOB operator */ int isComplete = 0; /* RHS of LIKE/GLOB ends with wildcard */ - int noCase = 0; /* LIKE/GLOB distinguishes case */ + int noCase = 0; /* uppercase equivalent to lowercase */ int op; /* Top-level operator. pExpr->op */ Parse *pParse = pWInfo->pParse; /* Parsing context */ sqlite3 *db = pParse->db; /* Database connection */ if( db->mallocFailed ){ @@ -107784,13 +116927,12 @@ return; } idxNew = whereClauseInsert(pWC, pDup, TERM_VIRTUAL|TERM_DYNAMIC); if( idxNew==0 ) return; pNew = &pWC->a[idxNew]; - pNew->iParent = idxTerm; + markTermAsChild(pWC, idxNew, idxTerm); pTerm = &pWC->a[idxTerm]; - pTerm->nChild = 1; pTerm->wtFlags |= TERM_COPIED; if( pExpr->op==TK_EQ && !ExprHasProperty(pExpr, EP_FromJoin) && OptimizationEnabled(db, SQLITE_Transitive) ){ @@ -107843,13 +116985,12 @@ transferJoinMarkings(pNewExpr, pExpr); idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); exprAnalyze(pSrc, pWC, idxNew); pTerm = &pWC->a[idxTerm]; - pWC->a[idxNew].iParent = idxTerm; + markTermAsChild(pWC, idxNew, idxTerm); } - pTerm->nChild = 2; } #endif /* SQLITE_OMIT_BETWEEN_OPTIMIZATION */ #if !defined(SQLITE_OMIT_OR_OPTIMIZATION) && !defined(SQLITE_OMIT_SUBQUERY) /* Analyze a term that is composed of two or more subterms connected by @@ -107864,16 +117005,19 @@ #ifndef SQLITE_OMIT_LIKE_OPTIMIZATION /* Add constraints to reduce the search space on a LIKE or GLOB ** operator. ** - ** A like pattern of the form "x LIKE 'abc%'" is changed into constraints + ** A like pattern of the form "x LIKE 'aBc%'" is changed into constraints ** - ** x>='abc' AND x<'abd' AND x LIKE 'abc%' + ** x>='ABC' AND x<'abd' AND x LIKE 'aBc%' ** ** The last character of the prefix "abc" is incremented to form the - ** termination condition "abd". + ** termination condition "abd". If case is not significant (the default + ** for LIKE) then the lower-bound is made all uppercase and the upper- + ** bound is made all lowercase so that the bounds also work when comparing + ** BLOBs. */ if( pWC->op==TK_AND && isLikeOrGlob(pParse, pExpr, &pStr1, &isComplete, &noCase) ){ Expr *pLeft; /* LHS of LIKE/GLOB operator */ @@ -107880,14 +117024,30 @@ Expr *pStr2; /* Copy of pStr1 - RHS of LIKE/GLOB operator */ Expr *pNewExpr1; Expr *pNewExpr2; int idxNew1; int idxNew2; - Token sCollSeqName; /* Name of collating sequence */ + const char *zCollSeqName; /* Name of collating sequence */ + const u16 wtFlags = TERM_LIKEOPT | TERM_VIRTUAL | TERM_DYNAMIC; pLeft = pExpr->x.pList->a[1].pExpr; pStr2 = sqlite3ExprDup(db, pStr1, 0); + + /* Convert the lower bound to upper-case and the upper bound to + ** lower-case (upper-case is less than lower-case in ASCII) so that + ** the range constraints also work for BLOBs + */ + if( noCase && !pParse->db->mallocFailed ){ + int i; + char c; + pTerm->wtFlags |= TERM_LIKE; + for(i=0; (c = pStr1->u.zToken[i])!=0; i++){ + pStr1->u.zToken[i] = sqlite3Toupper(c); + pStr2->u.zToken[i] = sqlite3Tolower(c); + } + } + if( !db->mallocFailed ){ u8 c, *pC; /* Last character before the first wildcard */ pC = (u8*)&pStr2->u.zToken[sqlite3Strlen30(pStr2->u.zToken)-1]; c = *pC; if( noCase ){ @@ -107900,33 +117060,31 @@ if( c=='A'-1 ) isComplete = 0; c = sqlite3UpperToLower[c]; } *pC = c + 1; } - sCollSeqName.z = noCase ? "NOCASE" : "BINARY"; - sCollSeqName.n = 6; + zCollSeqName = noCase ? "NOCASE" : "BINARY"; pNewExpr1 = sqlite3ExprDup(db, pLeft, 0); - pNewExpr1 = sqlite3PExpr(pParse, TK_GE, - sqlite3ExprAddCollateToken(pParse,pNewExpr1,&sCollSeqName), + pNewExpr1 = sqlite3PExpr(pParse, TK_GE, + sqlite3ExprAddCollateString(pParse,pNewExpr1,zCollSeqName), pStr1, 0); transferJoinMarkings(pNewExpr1, pExpr); - idxNew1 = whereClauseInsert(pWC, pNewExpr1, TERM_VIRTUAL|TERM_DYNAMIC); + idxNew1 = whereClauseInsert(pWC, pNewExpr1, wtFlags); testcase( idxNew1==0 ); exprAnalyze(pSrc, pWC, idxNew1); pNewExpr2 = sqlite3ExprDup(db, pLeft, 0); pNewExpr2 = sqlite3PExpr(pParse, TK_LT, - sqlite3ExprAddCollateToken(pParse,pNewExpr2,&sCollSeqName), + sqlite3ExprAddCollateString(pParse,pNewExpr2,zCollSeqName), pStr2, 0); transferJoinMarkings(pNewExpr2, pExpr); - idxNew2 = whereClauseInsert(pWC, pNewExpr2, TERM_VIRTUAL|TERM_DYNAMIC); + idxNew2 = whereClauseInsert(pWC, pNewExpr2, wtFlags); testcase( idxNew2==0 ); exprAnalyze(pSrc, pWC, idxNew2); pTerm = &pWC->a[idxTerm]; if( isComplete ){ - pWC->a[idxNew1].iParent = idxTerm; - pWC->a[idxNew2].iParent = idxTerm; - pTerm->nChild = 2; + markTermAsChild(pWC, idxNew1, idxTerm); + markTermAsChild(pWC, idxNew2, idxTerm); } } #endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */ #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -107955,13 +117113,12 @@ pNewTerm = &pWC->a[idxNew]; pNewTerm->prereqRight = prereqExpr; pNewTerm->leftCursor = pLeft->iTable; pNewTerm->u.leftColumn = pLeft->iColumn; pNewTerm->eOperator = WO_MATCH; - pNewTerm->iParent = idxTerm; + markTermAsChild(pWC, idxNew, idxTerm); pTerm = &pWC->a[idxTerm]; - pTerm->nChild = 1; pTerm->wtFlags |= TERM_COPIED; pNewTerm->prereqAll = pTerm->prereqAll; } } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -107978,11 +117135,11 @@ ** the start of the loop will prevent any results from being returned. */ if( pExpr->op==TK_NOTNULL && pExpr->pLeft->op==TK_COLUMN && pExpr->pLeft->iColumn>=0 - && OptimizationEnabled(db, SQLITE_Stat3) + && OptimizationEnabled(db, SQLITE_Stat34) ){ Expr *pNewExpr; Expr *pLeft = pExpr->pLeft; int idxNew; WhereTerm *pNewTerm; @@ -107997,13 +117154,12 @@ pNewTerm = &pWC->a[idxNew]; pNewTerm->prereqRight = 0; pNewTerm->leftCursor = pLeft->iTable; pNewTerm->u.leftColumn = pLeft->iColumn; pNewTerm->eOperator = WO_GT; - pNewTerm->iParent = idxTerm; + markTermAsChild(pWC, idxNew, idxTerm); pTerm = &pWC->a[idxTerm]; - pTerm->nChild = 1; pTerm->wtFlags |= TERM_COPIED; pNewTerm->prereqAll = pTerm->prereqAll; } } #endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ @@ -108013,11 +117169,11 @@ */ pTerm->prereqRight |= extraRight; } /* -** This function searches pList for a entry that matches the iCol-th column +** This function searches pList for an entry that matches the iCol-th column ** of index pIdx. ** ** If such an expression is found, its index in pList->a[] is returned. If ** no expression is found, -1 is returned. */ @@ -108093,99 +117249,35 @@ ** ** 3. All of those index columns for which the WHERE clause does not ** contain a "col=X" term are subject to a NOT NULL constraint. */ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->onError==OE_None ) continue; - for(i=0; i<pIdx->nColumn; i++){ - int iCol = pIdx->aiColumn[i]; + if( !IsUniqueIndex(pIdx) ) continue; + for(i=0; i<pIdx->nKeyCol; i++){ + i16 iCol = pIdx->aiColumn[i]; if( 0==findTerm(pWC, iBase, iCol, ~(Bitmask)0, WO_EQ, pIdx) ){ int iIdxCol = findIndexCol(pParse, pDistinct, iBase, pIdx, i); - if( iIdxCol<0 || pTab->aCol[pIdx->aiColumn[i]].notNull==0 ){ + if( iIdxCol<0 || pTab->aCol[iCol].notNull==0 ){ break; } } } - if( i==pIdx->nColumn ){ + if( i==pIdx->nKeyCol ){ /* This index implies that the DISTINCT qualifier is redundant. */ return 1; } } return 0; } -/* -** Find (an approximate) sum of two WhereCosts. This computation is -** not a simple "+" operator because WhereCost is stored as a logarithmic -** value. -** -*/ -static WhereCost whereCostAdd(WhereCost a, WhereCost b){ - static const unsigned char x[] = { - 10, 10, /* 0,1 */ - 9, 9, /* 2,3 */ - 8, 8, /* 4,5 */ - 7, 7, 7, /* 6,7,8 */ - 6, 6, 6, /* 9,10,11 */ - 5, 5, 5, /* 12-14 */ - 4, 4, 4, 4, /* 15-18 */ - 3, 3, 3, 3, 3, 3, /* 19-24 */ - 2, 2, 2, 2, 2, 2, 2, /* 25-31 */ - }; - if( a>=b ){ - if( a>b+49 ) return a; - if( a>b+31 ) return a+1; - return a+x[a-b]; - }else{ - if( b>a+49 ) return b; - if( b>a+31 ) return b+1; - return b+x[b-a]; - } -} - -/* -** Convert an integer into a WhereCost. In other words, compute a -** good approximatation for 10*log2(x). -*/ -static WhereCost whereCost(tRowcnt x){ - static WhereCost a[] = { 0, 2, 3, 5, 6, 7, 8, 9 }; - WhereCost y = 40; - if( x<8 ){ - if( x<2 ) return 0; - while( x<8 ){ y -= 10; x <<= 1; } - }else{ - while( x>255 ){ y += 40; x >>= 4; } - while( x>15 ){ y += 10; x >>= 1; } - } - return a[x&7] + y - 10; -} - -#ifndef SQLITE_OMIT_VIRTUALTABLE -/* -** Convert a double (as received from xBestIndex of a virtual table) -** into a WhereCost. In other words, compute an approximation for -** 10*log2(x). -*/ -static WhereCost whereCostFromDouble(double x){ - u64 a; - WhereCost e; - assert( sizeof(x)==8 && sizeof(a)==8 ); - if( x<=1 ) return 0; - if( x<=2000000000 ) return whereCost((tRowcnt)x); - memcpy(&a, &x, 8); - e = (a>>52) - 1022; - return e*10; -} -#endif /* SQLITE_OMIT_VIRTUALTABLE */ /* ** Estimate the logarithm of the input value to base 2. */ -static WhereCost estLog(WhereCost N){ - WhereCost x = whereCost(N); - return x>33 ? x - 33 : 0; +static LogEst estLog(LogEst N){ + return N<=10 ? 0 : sqlite3LogEst(N) - 33; } /* ** Two routines for printing the content of an sqlite3_index_info ** structure. Used for testing and debugging only. If neither @@ -108222,10 +117314,11 @@ } sqlite3DebugPrintf(" idxNum=%d\n", p->idxNum); sqlite3DebugPrintf(" idxStr=%s\n", p->idxStr); sqlite3DebugPrintf(" orderByConsumed=%d\n", p->orderByConsumed); sqlite3DebugPrintf(" estimatedCost=%g\n", p->estimatedCost); + sqlite3DebugPrintf(" estimatedRows=%lld\n", p->estimatedRows); } #else #define TRACE_IDX_INPUTS(A) #define TRACE_IDX_OUTPUTS(A) #endif @@ -108264,44 +117357,56 @@ WhereClause *pWC, /* The WHERE clause */ struct SrcList_item *pSrc, /* The FROM clause term to get the next index */ Bitmask notReady, /* Mask of cursors that are not available */ WhereLevel *pLevel /* Write new index here */ ){ - int nColumn; /* Number of columns in the constructed index */ + int nKeyCol; /* Number of columns in the constructed index */ WhereTerm *pTerm; /* A single term of the WHERE clause */ WhereTerm *pWCEnd; /* End of pWC->a[] */ - int nByte; /* Byte of memory needed for pIdx */ Index *pIdx; /* Object describing the transient index */ Vdbe *v; /* Prepared statement under construction */ int addrInit; /* Address of the initialization bypass jump */ Table *pTable; /* The table being indexed */ - KeyInfo *pKeyinfo; /* Key information for the index */ int addrTop; /* Top of the index fill loop */ int regRecord; /* Register holding an index record */ int n; /* Column counter */ int i; /* Loop counter */ int mxBitCol; /* Maximum column in pSrc->colUsed */ CollSeq *pColl; /* Collating sequence to on a column */ WhereLoop *pLoop; /* The Loop object */ + char *zNotUsed; /* Extra space on the end of pIdx */ Bitmask idxCols; /* Bitmap of columns used for indexing */ Bitmask extraCols; /* Bitmap of additional columns */ u8 sentWarning = 0; /* True if a warnning has been issued */ + Expr *pPartial = 0; /* Partial Index Expression */ + int iContinue = 0; /* Jump here to skip excluded rows */ /* Generate code to skip over the creation and initialization of the ** transient index on 2nd and subsequent iterations of the loop. */ v = pParse->pVdbe; assert( v!=0 ); - addrInit = sqlite3CodeOnce(pParse); + addrInit = sqlite3CodeOnce(pParse); VdbeCoverage(v); /* Count the number of columns that will be added to the index ** and used to match WHERE clause constraints */ - nColumn = 0; + nKeyCol = 0; pTable = pSrc->pTab; pWCEnd = &pWC->a[pWC->nTerm]; pLoop = pLevel->pWLoop; idxCols = 0; for(pTerm=pWC->a; pTerm<pWCEnd; pTerm++){ + Expr *pExpr = pTerm->pExpr; + assert( !ExprHasProperty(pExpr, EP_FromJoin) /* prereq always non-zero */ + || pExpr->iRightJoinTable!=pSrc->iCursor /* for the right-hand */ + || pLoop->prereq!=0 ); /* table of a LEFT JOIN */ + if( pLoop->prereq==0 + && (pTerm->wtFlags & TERM_VIRTUAL)==0 + && !ExprHasProperty(pExpr, EP_FromJoin) + && sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor) ){ + pPartial = sqlite3ExprAnd(pParse->db, pPartial, + sqlite3ExprDup(pParse->db, pExpr, 0)); + } if( termCanDriveIndex(pTerm, pSrc, notReady) ){ int iCol = pTerm->u.leftColumn; Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); testcase( iCol==BMS ); testcase( iCol==BMS-1 ); @@ -108310,18 +117415,20 @@ "automatic index on %s(%s)", pTable->zName, pTable->aCol[iCol].zName); sentWarning = 1; } if( (idxCols & cMask)==0 ){ - if( whereLoopResize(pParse->db, pLoop, nColumn+1) ) return; - pLoop->aLTerm[nColumn++] = pTerm; + if( whereLoopResize(pParse->db, pLoop, nKeyCol+1) ){ + goto end_auto_index_create; + } + pLoop->aLTerm[nKeyCol++] = pTerm; idxCols |= cMask; } } } - assert( nColumn>0 ); - pLoop->u.btree.nEq = pLoop->nLTerm = nColumn; + assert( nKeyCol>0 ); + pLoop->u.btree.nEq = pLoop->nLTerm = nKeyCol; pLoop->wsFlags = WHERE_COLUMN_EQ | WHERE_IDX_ONLY | WHERE_INDEXED | WHERE_AUTO_INDEX; /* Count the number of additional columns needed to create a ** covering index. A "covering index" is an index that contains all @@ -108330,34 +117437,25 @@ ** be a covering index because the index will not be updated if the ** original table changes and the index and table cannot both be used ** if they go out of sync. */ extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1)); - mxBitCol = (pTable->nCol >= BMS-1) ? BMS-1 : pTable->nCol; + mxBitCol = MIN(BMS-1,pTable->nCol); testcase( pTable->nCol==BMS-1 ); testcase( pTable->nCol==BMS-2 ); for(i=0; i<mxBitCol; i++){ - if( extraCols & MASKBIT(i) ) nColumn++; + if( extraCols & MASKBIT(i) ) nKeyCol++; } if( pSrc->colUsed & MASKBIT(BMS-1) ){ - nColumn += pTable->nCol - BMS + 1; + nKeyCol += pTable->nCol - BMS + 1; } - pLoop->wsFlags |= WHERE_COLUMN_EQ | WHERE_IDX_ONLY; /* Construct the Index object to describe this index */ - nByte = sizeof(Index); - nByte += nColumn*sizeof(int); /* Index.aiColumn */ - nByte += nColumn*sizeof(char*); /* Index.azColl */ - nByte += nColumn; /* Index.aSortOrder */ - pIdx = sqlite3DbMallocZero(pParse->db, nByte); - if( pIdx==0 ) return; + pIdx = sqlite3AllocateIndexObject(pParse->db, nKeyCol+1, 0, &zNotUsed); + if( pIdx==0 ) goto end_auto_index_create; pLoop->u.btree.pIndex = pIdx; - pIdx->azColl = (char**)&pIdx[1]; - pIdx->aiColumn = (int*)&pIdx->azColl[nColumn]; - pIdx->aSortOrder = (u8*)&pIdx->aiColumn[nColumn]; pIdx->zName = "auto-index"; - pIdx->nColumn = nColumn; pIdx->pTable = pTable; n = 0; idxCols = 0; for(pTerm=pWC->a; pTerm<pWCEnd; pTerm++){ if( termCanDriveIndex(pTerm, pSrc, notReady) ){ @@ -108391,33 +117489,45 @@ pIdx->aiColumn[n] = i; pIdx->azColl[n] = "BINARY"; n++; } } - assert( n==nColumn ); + assert( n==nKeyCol ); + pIdx->aiColumn[n] = -1; + pIdx->azColl[n] = "BINARY"; /* Create the automatic index */ - pKeyinfo = sqlite3IndexKeyinfo(pParse, pIdx); assert( pLevel->iIdxCur>=0 ); pLevel->iIdxCur = pParse->nTab++; - sqlite3VdbeAddOp4(v, OP_OpenAutoindex, pLevel->iIdxCur, nColumn+1, 0, - (char*)pKeyinfo, P4_KEYINFO_HANDOFF); + sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "for %s", pTable->zName)); /* Fill the automatic index with content */ - addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); + sqlite3ExprCachePush(pParse); + addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v); + if( pPartial ){ + iContinue = sqlite3VdbeMakeLabel(v); + sqlite3ExprIfFalse(pParse, pPartial, iContinue, SQLITE_JUMPIFNULL); + pLoop->wsFlags |= WHERE_PARTIALIDX; + } regRecord = sqlite3GetTempReg(pParse); - sqlite3GenerateIndexKey(pParse, pIdx, pLevel->iTabCur, regRecord, 1, 0); + sqlite3GenerateIndexKey(pParse, pIdx, pLevel->iTabCur, regRecord, 0, 0, 0, 0); sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); - sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); + if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue); + sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v); sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX); sqlite3VdbeJumpHere(v, addrTop); sqlite3ReleaseTempReg(pParse, regRecord); + sqlite3ExprCachePop(pParse); /* Jump here when skipping the initialization */ sqlite3VdbeJumpHere(v, addrInit); + +end_auto_index_create: + sqlite3ExprDelete(pParse->db, pPartial); } #endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ #ifndef SQLITE_OMIT_VIRTUALTABLE /* @@ -108445,11 +117555,12 @@ for(i=nTerm=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){ if( pTerm->leftCursor != pSrc->iCursor ) continue; assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); testcase( pTerm->eOperator & WO_IN ); testcase( pTerm->eOperator & WO_ISNULL ); - if( pTerm->eOperator & (WO_ISNULL) ) continue; + testcase( pTerm->eOperator & WO_ALL ); + if( (pTerm->eOperator & ~(WO_ISNULL|WO_EQUIV))==0 ) continue; if( pTerm->wtFlags & TERM_VNULL ) continue; nTerm++; } /* If the ORDER BY clause contains only columns in the current @@ -108497,11 +117608,12 @@ u8 op; if( pTerm->leftCursor != pSrc->iCursor ) continue; assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); testcase( pTerm->eOperator & WO_IN ); testcase( pTerm->eOperator & WO_ISNULL ); - if( pTerm->eOperator & (WO_ISNULL) ) continue; + testcase( pTerm->eOperator & WO_ALL ); + if( (pTerm->eOperator & ~(WO_ISNULL|WO_EQUIV))==0 ) continue; if( pTerm->wtFlags & TERM_VNULL ) continue; pIdxCons[j].iColumn = pTerm->u.leftColumn; pIdxCons[j].iTermOffset = i; op = (u8)pTerm->eOperator & WO_ALL; if( op==WO_IN ) op = WO_EQ; @@ -108571,88 +117683,185 @@ return pParse->nErr; } #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */ - #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 /* ** Estimate the location of a particular key among all keys in an ** index. Store the results in aStat as follows: ** -** aStat[0] Est. number of rows less than pVal -** aStat[1] Est. number of rows equal to pVal +** aStat[0] Est. number of rows less than pRec +** aStat[1] Est. number of rows equal to pRec ** -** Return SQLITE_OK on success. +** Return the index of the sample that is the smallest sample that +** is greater than or equal to pRec. Note that this index is not an index +** into the aSample[] array - it is an index into a virtual set of samples +** based on the contents of aSample[] and the number of fields in record +** pRec. */ -static void whereKeyStats( +static int whereKeyStats( Parse *pParse, /* Database connection */ Index *pIdx, /* Index to consider domain of */ UnpackedRecord *pRec, /* Vector of values to consider */ int roundUp, /* Round up if true. Round down if false */ tRowcnt *aStat /* OUT: stats written here */ ){ IndexSample *aSample = pIdx->aSample; int iCol; /* Index of required stats in anEq[] etc. */ + int i; /* Index of first sample >= pRec */ + int iSample; /* Smallest sample larger than or equal to pRec */ int iMin = 0; /* Smallest sample not yet tested */ - int i = pIdx->nSample; /* Smallest sample larger than or equal to pRec */ int iTest; /* Next sample to test */ int res; /* Result of comparison operation */ + int nField; /* Number of fields in pRec */ + tRowcnt iLower = 0; /* anLt[] + anEq[] of largest sample pRec is > */ - assert( pRec!=0 || pParse->db->mallocFailed ); - if( pRec==0 ) return; - iCol = pRec->nField - 1; +#ifndef SQLITE_DEBUG + UNUSED_PARAMETER( pParse ); +#endif + assert( pRec!=0 ); assert( pIdx->nSample>0 ); - assert( pRec->nField>0 && iCol<pIdx->nSampleCol ); + assert( pRec->nField>0 && pRec->nField<=pIdx->nSampleCol ); + + /* Do a binary search to find the first sample greater than or equal + ** to pRec. If pRec contains a single field, the set of samples to search + ** is simply the aSample[] array. If the samples in aSample[] contain more + ** than one fields, all fields following the first are ignored. + ** + ** If pRec contains N fields, where N is more than one, then as well as the + ** samples in aSample[] (truncated to N fields), the search also has to + ** consider prefixes of those samples. For example, if the set of samples + ** in aSample is: + ** + ** aSample[0] = (a, 5) + ** aSample[1] = (a, 10) + ** aSample[2] = (b, 5) + ** aSample[3] = (c, 100) + ** aSample[4] = (c, 105) + ** + ** Then the search space should ideally be the samples above and the + ** unique prefixes [a], [b] and [c]. But since that is hard to organize, + ** the code actually searches this set: + ** + ** 0: (a) + ** 1: (a, 5) + ** 2: (a, 10) + ** 3: (a, 10) + ** 4: (b) + ** 5: (b, 5) + ** 6: (c) + ** 7: (c, 100) + ** 8: (c, 105) + ** 9: (c, 105) + ** + ** For each sample in the aSample[] array, N samples are present in the + ** effective sample array. In the above, samples 0 and 1 are based on + ** sample aSample[0]. Samples 2 and 3 on aSample[1] etc. + ** + ** Often, sample i of each block of N effective samples has (i+1) fields. + ** Except, each sample may be extended to ensure that it is greater than or + ** equal to the previous sample in the array. For example, in the above, + ** sample 2 is the first sample of a block of N samples, so at first it + ** appears that it should be 1 field in size. However, that would make it + ** smaller than sample 1, so the binary search would not work. As a result, + ** it is extended to two fields. The duplicates that this creates do not + ** cause any problems. + */ + nField = pRec->nField; + iCol = 0; + iSample = pIdx->nSample * nField; do{ - iTest = (iMin+i)/2; - res = sqlite3VdbeRecordCompare(aSample[iTest].n, aSample[iTest].p, pRec); + int iSamp; /* Index in aSample[] of test sample */ + int n; /* Number of fields in test sample */ + + iTest = (iMin+iSample)/2; + iSamp = iTest / nField; + if( iSamp>0 ){ + /* The proposed effective sample is a prefix of sample aSample[iSamp]. + ** Specifically, the shortest prefix of at least (1 + iTest%nField) + ** fields that is greater than the previous effective sample. */ + for(n=(iTest % nField) + 1; n<nField; n++){ + if( aSample[iSamp-1].anLt[n-1]!=aSample[iSamp].anLt[n-1] ) break; + } + }else{ + n = iTest + 1; + } + + pRec->nField = n; + res = sqlite3VdbeRecordCompare(aSample[iSamp].n, aSample[iSamp].p, pRec); if( res<0 ){ + iLower = aSample[iSamp].anLt[n-1] + aSample[iSamp].anEq[n-1]; + iMin = iTest+1; + }else if( res==0 && n<nField ){ + iLower = aSample[iSamp].anLt[n-1]; iMin = iTest+1; + res = -1; }else{ - i = iTest; + iSample = iTest; + iCol = n-1; } - }while( res && iMin<i ); + }while( res && iMin<iSample ); + i = iSample / nField; #ifdef SQLITE_DEBUG /* The following assert statements check that the binary search code ** above found the right answer. This block serves no purpose other ** than to invoke the asserts. */ - if( res==0 ){ - /* If (res==0) is true, then sample $i must be equal to pRec */ - assert( i<pIdx->nSample ); - assert( 0==sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec) - || pParse->db->mallocFailed ); - }else{ - /* Otherwise, pRec must be smaller than sample $i and larger than - ** sample ($i-1). */ - assert( i==pIdx->nSample - || sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)>0 - || pParse->db->mallocFailed ); - assert( i==0 - || sqlite3VdbeRecordCompare(aSample[i-1].n, aSample[i-1].p, pRec)<0 - || pParse->db->mallocFailed ); + if( pParse->db->mallocFailed==0 ){ + if( res==0 ){ + /* If (res==0) is true, then pRec must be equal to sample i. */ + assert( i<pIdx->nSample ); + assert( iCol==nField-1 ); + pRec->nField = nField; + assert( 0==sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec) + || pParse->db->mallocFailed + ); + }else{ + /* Unless i==pIdx->nSample, indicating that pRec is larger than + ** all samples in the aSample[] array, pRec must be smaller than the + ** (iCol+1) field prefix of sample i. */ + assert( i<=pIdx->nSample && i>=0 ); + pRec->nField = iCol+1; + assert( i==pIdx->nSample + || sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)>0 + || pParse->db->mallocFailed ); + + /* if i==0 and iCol==0, then record pRec is smaller than all samples + ** in the aSample[] array. Otherwise, if (iCol>0) then pRec must + ** be greater than or equal to the (iCol) field prefix of sample i. + ** If (i>0), then pRec must also be greater than sample (i-1). */ + if( iCol>0 ){ + pRec->nField = iCol; + assert( sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)<=0 + || pParse->db->mallocFailed ); + } + if( i>0 ){ + pRec->nField = nField; + assert( sqlite3VdbeRecordCompare(aSample[i-1].n, aSample[i-1].p, pRec)<0 + || pParse->db->mallocFailed ); + } + } } #endif /* ifdef SQLITE_DEBUG */ - /* At this point, aSample[i] is the first sample that is greater than - ** or equal to pVal. Or if i==pIdx->nSample, then all samples are less - ** than pVal. If aSample[i]==pVal, then res==0. - */ if( res==0 ){ + /* Record pRec is equal to sample i */ + assert( iCol==nField-1 ); aStat[0] = aSample[i].anLt[iCol]; aStat[1] = aSample[i].anEq[iCol]; }else{ - tRowcnt iLower, iUpper, iGap; - if( i==0 ){ - iLower = 0; - iUpper = aSample[0].anLt[iCol]; + /* At this point, the (iCol+1) field prefix of aSample[i] is the first + ** sample that is greater than pRec. Or, if i==pIdx->nSample then pRec + ** is larger than all samples in the array. */ + tRowcnt iUpper, iGap; + if( i>=pIdx->nSample ){ + iUpper = sqlite3LogEstToInt(pIdx->aiRowLogEst[0]); }else{ - iUpper = i>=pIdx->nSample ? pIdx->aiRowEst[0] : aSample[i].anLt[iCol]; - iLower = aSample[i-1].anEq[iCol] + aSample[i-1].anLt[iCol]; + iUpper = aSample[i].anLt[iCol]; } - aStat[1] = (pIdx->nColumn>iCol ? pIdx->aAvgEq[iCol] : 1); + if( iLower>=iUpper ){ iGap = 0; }else{ iGap = iUpper - iLower; } @@ -108660,11 +117869,148 @@ iGap = (iGap*2)/3; }else{ iGap = iGap/3; } aStat[0] = iLower + iGap; + aStat[1] = pIdx->aAvgEq[iCol]; } + + /* Restore the pRec->nField value before returning. */ + pRec->nField = nField; + return i; +} +#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ + +/* +** If it is not NULL, pTerm is a term that provides an upper or lower +** bound on a range scan. Without considering pTerm, it is estimated +** that the scan will visit nNew rows. This function returns the number +** estimated to be visited after taking pTerm into account. +** +** If the user explicitly specified a likelihood() value for this term, +** then the return value is the likelihood multiplied by the number of +** input rows. Otherwise, this function assumes that an "IS NOT NULL" term +** has a likelihood of 0.50, and any other term a likelihood of 0.25. +*/ +static LogEst whereRangeAdjust(WhereTerm *pTerm, LogEst nNew){ + LogEst nRet = nNew; + if( pTerm ){ + if( pTerm->truthProb<=0 ){ + nRet += pTerm->truthProb; + }else if( (pTerm->wtFlags & TERM_VNULL)==0 ){ + nRet -= 20; assert( 20==sqlite3LogEst(4) ); + } + } + return nRet; +} + +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +/* +** This function is called to estimate the number of rows visited by a +** range-scan on a skip-scan index. For example: +** +** CREATE INDEX i1 ON t1(a, b, c); +** SELECT * FROM t1 WHERE a=? AND c BETWEEN ? AND ?; +** +** Value pLoop->nOut is currently set to the estimated number of rows +** visited for scanning (a=? AND b=?). This function reduces that estimate +** by some factor to account for the (c BETWEEN ? AND ?) expression based +** on the stat4 data for the index. this scan will be peformed multiple +** times (once for each (a,b) combination that matches a=?) is dealt with +** by the caller. +** +** It does this by scanning through all stat4 samples, comparing values +** extracted from pLower and pUpper with the corresponding column in each +** sample. If L and U are the number of samples found to be less than or +** equal to the values extracted from pLower and pUpper respectively, and +** N is the total number of samples, the pLoop->nOut value is adjusted +** as follows: +** +** nOut = nOut * ( min(U - L, 1) / N ) +** +** If pLower is NULL, or a value cannot be extracted from the term, L is +** set to zero. If pUpper is NULL, or a value cannot be extracted from it, +** U is set to N. +** +** Normally, this function sets *pbDone to 1 before returning. However, +** if no value can be extracted from either pLower or pUpper (and so the +** estimate of the number of rows delivered remains unchanged), *pbDone +** is left as is. +** +** If an error occurs, an SQLite error code is returned. Otherwise, +** SQLITE_OK. +*/ +static int whereRangeSkipScanEst( + Parse *pParse, /* Parsing & code generating context */ + WhereTerm *pLower, /* Lower bound on the range. ex: "x>123" Might be NULL */ + WhereTerm *pUpper, /* Upper bound on the range. ex: "x<455" Might be NULL */ + WhereLoop *pLoop, /* Update the .nOut value of this loop */ + int *pbDone /* Set to true if at least one expr. value extracted */ +){ + Index *p = pLoop->u.btree.pIndex; + int nEq = pLoop->u.btree.nEq; + sqlite3 *db = pParse->db; + int nLower = -1; + int nUpper = p->nSample+1; + int rc = SQLITE_OK; + int iCol = p->aiColumn[nEq]; + u8 aff = iCol>=0 ? p->pTable->aCol[iCol].affinity : SQLITE_AFF_INTEGER; + CollSeq *pColl; + + sqlite3_value *p1 = 0; /* Value extracted from pLower */ + sqlite3_value *p2 = 0; /* Value extracted from pUpper */ + sqlite3_value *pVal = 0; /* Value extracted from record */ + + pColl = sqlite3LocateCollSeq(pParse, p->azColl[nEq]); + if( pLower ){ + rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight, aff, &p1); + nLower = 0; + } + if( pUpper && rc==SQLITE_OK ){ + rc = sqlite3Stat4ValueFromExpr(pParse, pUpper->pExpr->pRight, aff, &p2); + nUpper = p2 ? 0 : p->nSample; + } + + if( p1 || p2 ){ + int i; + int nDiff; + for(i=0; rc==SQLITE_OK && i<p->nSample; i++){ + rc = sqlite3Stat4Column(db, p->aSample[i].p, p->aSample[i].n, nEq, &pVal); + if( rc==SQLITE_OK && p1 ){ + int res = sqlite3MemCompare(p1, pVal, pColl); + if( res>=0 ) nLower++; + } + if( rc==SQLITE_OK && p2 ){ + int res = sqlite3MemCompare(p2, pVal, pColl); + if( res>=0 ) nUpper++; + } + } + nDiff = (nUpper - nLower); + if( nDiff<=0 ) nDiff = 1; + + /* If there is both an upper and lower bound specified, and the + ** comparisons indicate that they are close together, use the fallback + ** method (assume that the scan visits 1/64 of the rows) for estimating + ** the number of rows visited. Otherwise, estimate the number of rows + ** using the method described in the header comment for this function. */ + if( nDiff!=1 || pUpper==0 || pLower==0 ){ + int nAdjust = (sqlite3LogEst(p->nSample) - sqlite3LogEst(nDiff)); + pLoop->nOut -= nAdjust; + *pbDone = 1; + WHERETRACE(0x10, ("range skip-scan regions: %u..%u adjust=%d est=%d\n", + nLower, nUpper, nAdjust*-1, pLoop->nOut)); + } + + }else{ + assert( *pbDone==0 ); + } + + sqlite3ValueFree(p1); + sqlite3ValueFree(p2); + sqlite3ValueFree(pVal); + + return rc; } #endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ /* ** This function is used to estimate the number of rows that will be visited @@ -108679,11 +118025,11 @@ ** pLower pUpper ** ** If either of the upper or lower bound is not present, then NULL is passed in ** place of the corresponding WhereTerm. ** -** The value in (pBuilder->pNew->u.btree.nEq) is the index of the index +** The value in (pBuilder->pNew->u.btree.nEq) is the number of the index ** column subject to the range constraint. Or, equivalently, the number of ** equality constraints optimized by the proposed index scan. For example, ** assuming index p is on t1(a, b), and the SQL query is: ** ** ... FROM t1 WHERE a = ? AND b > ? AND b < ? ... @@ -108693,147 +118039,178 @@ ** ** ... FROM t1 WHERE a > ? AND a < ? ... ** ** then nEq is set to 0. ** -** When this function is called, *pnOut is set to the whereCost() of the +** When this function is called, *pnOut is set to the sqlite3LogEst() of the ** number of rows that the index scan is expected to visit without -** considering the range constraints. If nEq is 0, this is the number of +** considering the range constraints. If nEq is 0, then *pnOut is the number of ** rows in the index. Assuming no error occurs, *pnOut is adjusted (reduced) -** to account for the range contraints pLower and pUpper. +** to account for the range constraints pLower and pUpper. ** ** In the absence of sqlite_stat4 ANALYZE data, or if such data cannot be -** used, each range inequality reduces the search space by a factor of 4. -** Hence a pair of constraints (x>? AND x<?) reduces the expected number of -** rows visited by a factor of 16. +** used, a single range inequality reduces the search space by a factor of 4. +** and a pair of constraints (x>? AND x<?) reduces the expected number of +** rows visited by a factor of 64. */ static int whereRangeScanEst( Parse *pParse, /* Parsing & code generating context */ WhereLoopBuilder *pBuilder, WhereTerm *pLower, /* Lower bound on the range. ex: "x>123" Might be NULL */ WhereTerm *pUpper, /* Upper bound on the range. ex: "x<455" Might be NULL */ - WhereCost *pnOut /* IN/OUT: Number of rows visited */ -){ - int rc = SQLITE_OK; - int nOut = (int)*pnOut; - WhereCost nNew; - -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - Index *p = pBuilder->pNew->u.btree.pIndex; - int nEq = pBuilder->pNew->u.btree.nEq; - - if( p->nSample>0 - && nEq==pBuilder->nRecValid - && nEq<p->nSampleCol - && OptimizationEnabled(pParse->db, SQLITE_Stat3) - ){ - UnpackedRecord *pRec = pBuilder->pRec; - tRowcnt a[2]; - u8 aff; - - /* Variable iLower will be set to the estimate of the number of rows in - ** the index that are less than the lower bound of the range query. The - ** lower bound being the concatenation of $P and $L, where $P is the - ** key-prefix formed by the nEq values matched against the nEq left-most - ** columns of the index, and $L is the value in pLower. - ** - ** Or, if pLower is NULL or $L cannot be extracted from it (because it - ** is not a simple variable or literal value), the lower bound of the - ** range is $P. Due to a quirk in the way whereKeyStats() works, even - ** if $L is available, whereKeyStats() is called for both ($P) and - ** ($P:$L) and the larger of the two returned values used. - ** - ** Similarly, iUpper is to be set to the estimate of the number of rows - ** less than the upper bound of the range query. Where the upper bound - ** is either ($P) or ($P:$U). Again, even if $U is available, both values - ** of iUpper are requested of whereKeyStats() and the smaller used. - */ - tRowcnt iLower; - tRowcnt iUpper; - - if( nEq==p->nColumn ){ - aff = SQLITE_AFF_INTEGER; - }else{ - aff = p->pTable->aCol[p->aiColumn[nEq]].affinity; - } - /* Determine iLower and iUpper using ($P) only. */ - if( nEq==0 ){ - iLower = 0; - iUpper = p->aiRowEst[0]; - }else{ - /* Note: this call could be optimized away - since the same values must - ** have been requested when testing key $P in whereEqualScanEst(). */ - whereKeyStats(pParse, p, pRec, 0, a); - iLower = a[0]; - iUpper = a[0] + a[1]; - } - - /* If possible, improve on the iLower estimate using ($P:$L). */ - if( pLower ){ - int bOk; /* True if value is extracted from pExpr */ - Expr *pExpr = pLower->pExpr->pRight; - assert( (pLower->eOperator & (WO_GT|WO_GE))!=0 ); - rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk); - if( rc==SQLITE_OK && bOk ){ - tRowcnt iNew; - whereKeyStats(pParse, p, pRec, 0, a); - iNew = a[0] + ((pLower->eOperator & WO_GT) ? a[1] : 0); - if( iNew>iLower ) iLower = iNew; - nOut--; - } - } - - /* If possible, improve on the iUpper estimate using ($P:$U). */ - if( pUpper ){ - int bOk; /* True if value is extracted from pExpr */ - Expr *pExpr = pUpper->pExpr->pRight; - assert( (pUpper->eOperator & (WO_LT|WO_LE))!=0 ); - rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk); - if( rc==SQLITE_OK && bOk ){ - tRowcnt iNew; - whereKeyStats(pParse, p, pRec, 1, a); - iNew = a[0] + ((pUpper->eOperator & WO_LE) ? a[1] : 0); - if( iNew<iUpper ) iUpper = iNew; - nOut--; - } - } - - pBuilder->pRec = pRec; - if( rc==SQLITE_OK ){ - if( iUpper>iLower ){ - nNew = whereCost(iUpper - iLower); - }else{ - nNew = 10; assert( 10==whereCost(2) ); - } - if( nNew<nOut ){ - nOut = nNew; - } - *pnOut = (WhereCost)nOut; - WHERETRACE(0x100, ("range scan regions: %u..%u est=%d\n", - (u32)iLower, (u32)iUpper, nOut)); - return SQLITE_OK; + WhereLoop *pLoop /* Modify the .nOut and maybe .rRun fields */ +){ + int rc = SQLITE_OK; + int nOut = pLoop->nOut; + LogEst nNew; + +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + Index *p = pLoop->u.btree.pIndex; + int nEq = pLoop->u.btree.nEq; + + if( p->nSample>0 && nEq<p->nSampleCol ){ + if( nEq==pBuilder->nRecValid ){ + UnpackedRecord *pRec = pBuilder->pRec; + tRowcnt a[2]; + u8 aff; + + /* Variable iLower will be set to the estimate of the number of rows in + ** the index that are less than the lower bound of the range query. The + ** lower bound being the concatenation of $P and $L, where $P is the + ** key-prefix formed by the nEq values matched against the nEq left-most + ** columns of the index, and $L is the value in pLower. + ** + ** Or, if pLower is NULL or $L cannot be extracted from it (because it + ** is not a simple variable or literal value), the lower bound of the + ** range is $P. Due to a quirk in the way whereKeyStats() works, even + ** if $L is available, whereKeyStats() is called for both ($P) and + ** ($P:$L) and the larger of the two returned values is used. + ** + ** Similarly, iUpper is to be set to the estimate of the number of rows + ** less than the upper bound of the range query. Where the upper bound + ** is either ($P) or ($P:$U). Again, even if $U is available, both values + ** of iUpper are requested of whereKeyStats() and the smaller used. + ** + ** The number of rows between the two bounds is then just iUpper-iLower. + */ + tRowcnt iLower; /* Rows less than the lower bound */ + tRowcnt iUpper; /* Rows less than the upper bound */ + int iLwrIdx = -2; /* aSample[] for the lower bound */ + int iUprIdx = -1; /* aSample[] for the upper bound */ + + if( pRec ){ + testcase( pRec->nField!=pBuilder->nRecValid ); + pRec->nField = pBuilder->nRecValid; + } + if( nEq==p->nKeyCol ){ + aff = SQLITE_AFF_INTEGER; + }else{ + aff = p->pTable->aCol[p->aiColumn[nEq]].affinity; + } + /* Determine iLower and iUpper using ($P) only. */ + if( nEq==0 ){ + iLower = 0; + iUpper = p->nRowEst0; + }else{ + /* Note: this call could be optimized away - since the same values must + ** have been requested when testing key $P in whereEqualScanEst(). */ + whereKeyStats(pParse, p, pRec, 0, a); + iLower = a[0]; + iUpper = a[0] + a[1]; + } + + assert( pLower==0 || (pLower->eOperator & (WO_GT|WO_GE))!=0 ); + assert( pUpper==0 || (pUpper->eOperator & (WO_LT|WO_LE))!=0 ); + assert( p->aSortOrder!=0 ); + if( p->aSortOrder[nEq] ){ + /* The roles of pLower and pUpper are swapped for a DESC index */ + SWAP(WhereTerm*, pLower, pUpper); + } + + /* If possible, improve on the iLower estimate using ($P:$L). */ + if( pLower ){ + int bOk; /* True if value is extracted from pExpr */ + Expr *pExpr = pLower->pExpr->pRight; + rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk); + if( rc==SQLITE_OK && bOk ){ + tRowcnt iNew; + iLwrIdx = whereKeyStats(pParse, p, pRec, 0, a); + iNew = a[0] + ((pLower->eOperator & (WO_GT|WO_LE)) ? a[1] : 0); + if( iNew>iLower ) iLower = iNew; + nOut--; + pLower = 0; + } + } + + /* If possible, improve on the iUpper estimate using ($P:$U). */ + if( pUpper ){ + int bOk; /* True if value is extracted from pExpr */ + Expr *pExpr = pUpper->pExpr->pRight; + rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk); + if( rc==SQLITE_OK && bOk ){ + tRowcnt iNew; + iUprIdx = whereKeyStats(pParse, p, pRec, 1, a); + iNew = a[0] + ((pUpper->eOperator & (WO_GT|WO_LE)) ? a[1] : 0); + if( iNew<iUpper ) iUpper = iNew; + nOut--; + pUpper = 0; + } + } + + pBuilder->pRec = pRec; + if( rc==SQLITE_OK ){ + if( iUpper>iLower ){ + nNew = sqlite3LogEst(iUpper - iLower); + /* TUNING: If both iUpper and iLower are derived from the same + ** sample, then assume they are 4x more selective. This brings + ** the estimated selectivity more in line with what it would be + ** if estimated without the use of STAT3/4 tables. */ + if( iLwrIdx==iUprIdx ) nNew -= 20; assert( 20==sqlite3LogEst(4) ); + }else{ + nNew = 10; assert( 10==sqlite3LogEst(2) ); + } + if( nNew<nOut ){ + nOut = nNew; + } + WHERETRACE(0x10, ("STAT4 range scan: %u..%u est=%d\n", + (u32)iLower, (u32)iUpper, nOut)); + } + }else{ + int bDone = 0; + rc = whereRangeSkipScanEst(pParse, pLower, pUpper, pLoop, &bDone); + if( bDone ) return rc; } } #else UNUSED_PARAMETER(pParse); UNUSED_PARAMETER(pBuilder); -#endif assert( pLower || pUpper ); - /* TUNING: Each inequality constraint reduces the search space 4-fold. - ** A BETWEEN operator, therefore, reduces the search space 16-fold */ - nNew = nOut; - if( pLower && (pLower->wtFlags & TERM_VNULL)==0 ){ - nNew -= 20; assert( 20==whereCost(4) ); - nOut--; - } - if( pUpper ){ - nNew -= 20; assert( 20==whereCost(4) ); - nOut--; - } +#endif + assert( pUpper==0 || (pUpper->wtFlags & TERM_VNULL)==0 ); + nNew = whereRangeAdjust(pLower, nOut); + nNew = whereRangeAdjust(pUpper, nNew); + + /* TUNING: If there is both an upper and lower limit and neither limit + ** has an application-defined likelihood(), assume the range is + ** reduced by an additional 75%. This means that, by default, an open-ended + ** range query (e.g. col > ?) is assumed to match 1/4 of the rows in the + ** index. While a closed range (e.g. col BETWEEN ? AND ?) is estimated to + ** match 1/64 of the index. */ + if( pLower && pLower->truthProb>0 && pUpper && pUpper->truthProb>0 ){ + nNew -= 20; + } + + nOut -= (pLower!=0) + (pUpper!=0); if( nNew<10 ) nNew = 10; if( nNew<nOut ) nOut = nNew; - *pnOut = (WhereCost)nOut; +#if defined(WHERETRACE_ENABLED) + if( pLoop->nOut>nOut ){ + WHERETRACE(0x10,("Range scan lowers nOut from %d to %d\n", + pLoop->nOut, nOut)); + } +#endif + pLoop->nOut = (LogEst)nOut; return rc; } #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 /* @@ -108866,11 +118243,11 @@ int rc; /* Subfunction return code */ tRowcnt a[2]; /* Statistics */ int bOk; assert( nEq>=1 ); - assert( nEq<=(p->nColumn+1) ); + assert( nEq<=p->nColumn ); assert( p->aSample!=0 ); assert( p->nSample>0 ); assert( pBuilder->nRecValid<nEq ); /* If values are not available for all fields of the index to the left @@ -108879,11 +118256,11 @@ return SQLITE_NOTFOUND; } /* This is an optimization only. The call to sqlite3Stat4ProbeSetValue() ** below would return the same value. */ - if( nEq>p->nColumn ){ + if( nEq>=p->nColumn ){ *pnRow = 1; return SQLITE_OK; } aff = p->pTable->aCol[p->aiColumn[nEq-1]].affinity; @@ -108892,11 +118269,11 @@ if( rc!=SQLITE_OK ) return rc; if( bOk==0 ) return SQLITE_NOTFOUND; pBuilder->nRecValid = nEq; whereKeyStats(pParse, p, pRec, 0, a); - WHERETRACE(0x100,("equality scan regions: %d\n", (int)a[1])); + WHERETRACE(0x10,("equality scan regions: %d\n", (int)a[1])); *pnRow = a[1]; return rc; } #endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ @@ -108923,28 +118300,29 @@ WhereLoopBuilder *pBuilder, ExprList *pList, /* The value list on the RHS of "x IN (v1,v2,v3,...)" */ tRowcnt *pnRow /* Write the revised row estimate here */ ){ Index *p = pBuilder->pNew->u.btree.pIndex; + i64 nRow0 = sqlite3LogEstToInt(p->aiRowLogEst[0]); int nRecValid = pBuilder->nRecValid; int rc = SQLITE_OK; /* Subfunction return code */ tRowcnt nEst; /* Number of rows for a single term */ tRowcnt nRowEst = 0; /* New estimate of the number of rows */ int i; /* Loop counter */ assert( p->aSample!=0 ); for(i=0; rc==SQLITE_OK && i<pList->nExpr; i++){ - nEst = p->aiRowEst[0]; + nEst = nRow0; rc = whereEqualScanEst(pParse, pBuilder, pList->a[i].pExpr, &nEst); nRowEst += nEst; pBuilder->nRecValid = nRecValid; } if( rc==SQLITE_OK ){ - if( nRowEst > p->aiRowEst[0] ) nRowEst = p->aiRowEst[0]; + if( nRowEst > nRow0 ) nRowEst = nRow0; *pnRow = nRowEst; - WHERETRACE(0x100,("IN row estimate: est=%g\n", nRowEst)); + WHERETRACE(0x10,("IN row estimate: est=%d\n", nRowEst)); } assert( pBuilder->nRecValid==nRecValid ); return rc; } #endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ @@ -108969,24 +118347,47 @@ ** by indices, we disable them to prevent redundant tests in the inner ** loop. We would get the correct results if nothing were ever disabled, ** but joins might run a little slower. The trick is to disable as much ** as we can without disabling too much. If we disabled in (1), we'd get ** the wrong answer. See ticket #813. +** +** If all the children of a term are disabled, then that term is also +** automatically disabled. In this way, terms get disabled if derived +** virtual terms are tested first. For example: +** +** x GLOB 'abc*' AND x>='abc' AND x<'acd' +** \___________/ \______/ \_____/ +** parent child1 child2 +** +** Only the parent term was in the original WHERE clause. The child1 +** and child2 terms were added by the LIKE optimization. If both of +** the virtual child terms are valid, then testing of the parent can be +** skipped. +** +** Usually the parent term is marked as TERM_CODED. But if the parent +** term was originally TERM_LIKE, then the parent gets TERM_LIKECOND instead. +** The TERM_LIKECOND marking indicates that the term should be coded inside +** a conditional such that is only evaluated on the second pass of a +** LIKE-optimization loop, when scanning BLOBs instead of strings. */ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ - if( pTerm + int nLoop = 0; + while( pTerm && (pTerm->wtFlags & TERM_CODED)==0 && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_FromJoin)) && (pLevel->notReady & pTerm->prereqAll)==0 ){ - pTerm->wtFlags |= TERM_CODED; - if( pTerm->iParent>=0 ){ - WhereTerm *pOther = &pTerm->pWC->a[pTerm->iParent]; - if( (--pOther->nChild)==0 ){ - disableTerm(pLevel, pOther); - } + if( nLoop && (pTerm->wtFlags & TERM_LIKE)!=0 ){ + pTerm->wtFlags |= TERM_LIKECOND; + }else{ + pTerm->wtFlags |= TERM_CODED; } + if( pTerm->iParent<0 ) break; + pTerm = &pTerm->pWC->a[pTerm->iParent]; + pTerm->nChild--; + if( pTerm->nChild!=0 ) break; + nLoop++; } } /* ** Code an OP_Affinity opcode to apply the column affinity string zAff @@ -109072,17 +118473,19 @@ testcase( bRev ); bRev = !bRev; } assert( pX->op==TK_IN ); iReg = iTarget; - eType = sqlite3FindInIndex(pParse, pX, 0); + eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0); if( eType==IN_INDEX_INDEX_DESC ){ testcase( bRev ); bRev = !bRev; } iTab = pX->iTable; sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iTab, 0); + VdbeCoverageIf(v, bRev); + VdbeCoverageIf(v, !bRev); assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); pLoop->wsFlags |= WHERE_IN_ABLE; if( pLevel->u.in.nIn==0 ){ pLevel->addrNxt = sqlite3VdbeMakeLabel(v); } @@ -109097,12 +118500,12 @@ if( eType==IN_INDEX_ROWID ){ pIn->addrInTop = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iReg); }else{ pIn->addrInTop = sqlite3VdbeAddOp3(v, OP_Column, iTab, 0, iReg); } - pIn->eEndLoopOp = bRev ? OP_Prev : OP_Next; - sqlite3VdbeAddOp1(v, OP_IsNull, iReg); + pIn->eEndLoopOp = bRev ? OP_PrevIfOpen : OP_NextIfOpen; + sqlite3VdbeAddOp1(v, OP_IsNull, iReg); VdbeCoverage(v); }else{ pLevel->u.in.nIn = 0; } #endif } @@ -109110,11 +118513,11 @@ return iReg; } /* ** Generate code that will evaluate all == and IN constraints for an -** index. +** index scan. ** ** For example, consider table t1(a,b,c,d,e,f) with index i1(a,b,c). ** Suppose the WHERE clause is this: a==5 AND b IN (1,2,3) AND c>5 AND c<10 ** The index has as many as three equality constraints, but in this ** example, the third "c" value is an inequality. So only two @@ -109125,13 +118528,19 @@ ** In the example above nEq==2. But this subroutine works for any value ** of nEq including 0. If nEq==0, this routine is nearly a no-op. ** The only thing it does is allocate the pLevel->iMem memory cell and ** compute the affinity string. ** -** This routine always allocates at least one memory cell and returns -** the index of that memory cell. The code that -** calls this routine will use that memory cell to store the termination +** The nExtraReg parameter is 0 or 1. It is 0 if all WHERE clause constraints +** are == or IN and are covered by the nEq. nExtraReg is 1 if there is +** an inequality constraint (such as the "c>=5 AND c<10" in the example) that +** occurs after the nEq quality constraints. +** +** This routine allocates a range of nEq+nExtraReg memory cells and returns +** the index of the first memory cell in that range. The code that +** calls this routine will use that memory range to store keys for +** start and termination conditions of the loop. ** key value of the loop. If one or more IN operators appear, then ** this routine allocates an additional nEq memory cells for internal ** use. ** ** Before returning, *pzAff is set to point to a buffer containing a @@ -109154,11 +118563,12 @@ WhereLevel *pLevel, /* Which nested loop of the FROM we are coding */ int bRev, /* Reverse the order of IN operators */ int nExtraReg, /* Number of extra registers to allocate */ char **pzAff /* OUT: Set to point to affinity string */ ){ - int nEq; /* The number of == or IN constraints to code */ + u16 nEq; /* The number of == or IN constraints to code */ + u16 nSkip; /* Number of left-most columns to skip */ Vdbe *v = pParse->pVdbe; /* The vm under construction */ Index *pIdx; /* The index being used for this loop */ WhereTerm *pTerm; /* A single constraint term */ WhereLoop *pLoop; /* The WhereLoop object */ int j; /* Loop counter */ @@ -109168,10 +118578,11 @@ /* This module is only called on query plans that use an index. */ pLoop = pLevel->pWLoop; assert( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ); nEq = pLoop->u.btree.nEq; + nSkip = pLoop->nSkip; pIdx = pLoop->u.btree.pIndex; assert( pIdx!=0 ); /* Figure out how many memory cells we will need then allocate them. */ @@ -109181,19 +118592,38 @@ zAff = sqlite3DbStrDup(pParse->db, sqlite3IndexAffinityStr(v, pIdx)); if( !zAff ){ pParse->db->mallocFailed = 1; } + + if( nSkip ){ + int iIdxCur = pLevel->iIdxCur; + sqlite3VdbeAddOp1(v, (bRev?OP_Last:OP_Rewind), iIdxCur); + VdbeCoverageIf(v, bRev==0); + VdbeCoverageIf(v, bRev!=0); + VdbeComment((v, "begin skip-scan on %s", pIdx->zName)); + j = sqlite3VdbeAddOp0(v, OP_Goto); + pLevel->addrSkip = sqlite3VdbeAddOp4Int(v, (bRev?OP_SeekLT:OP_SeekGT), + iIdxCur, 0, regBase, nSkip); + VdbeCoverageIf(v, bRev==0); + VdbeCoverageIf(v, bRev!=0); + sqlite3VdbeJumpHere(v, j); + for(j=0; j<nSkip; j++){ + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, j, regBase+j); + assert( pIdx->aiColumn[j]>=0 ); + VdbeComment((v, "%s", pIdx->pTable->aCol[pIdx->aiColumn[j]].zName)); + } + } /* Evaluate the equality constraints */ assert( zAff==0 || (int)strlen(zAff)>=nEq ); - for(j=0; j<nEq; j++){ + for(j=nSkip; j<nEq; j++){ int r1; pTerm = pLoop->aLTerm[j]; assert( pTerm!=0 ); - /* The following true for indices with redundant columns. + /* The following testcase is true for indices with redundant columns. ** Ex: CREATE INDEX i1 ON t1(a,b,a); SELECT * FROM t1 WHERE a=0 AND b=0; */ testcase( (pTerm->wtFlags & TERM_CODED)!=0 ); testcase( pTerm->wtFlags & TERM_VIRTUAL ); r1 = codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, regBase+j); if( r1!=regBase+j ){ @@ -109206,11 +118636,14 @@ } testcase( pTerm->eOperator & WO_ISNULL ); testcase( pTerm->eOperator & WO_IN ); if( (pTerm->eOperator & (WO_ISNULL|WO_IN))==0 ){ Expr *pRight = pTerm->pExpr->pRight; - sqlite3ExprCodeIsNullJump(v, pRight, regBase+j, pLevel->addrBrk); + if( sqlite3ExprCanBeNull(pRight) ){ + sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk); + VdbeCoverage(v); + } if( zAff ){ if( sqlite3CompareAffinity(pRight, zAff[j])==SQLITE_AFF_NONE ){ zAff[j] = SQLITE_AFF_NONE; } if( sqlite3ExprNeedsNoAffinityChange(pRight, zAff[j]) ){ @@ -109237,146 +118670,235 @@ int iTerm, /* Index of this term. First is zero */ const char *zColumn, /* Name of the column */ const char *zOp /* Name of the operator */ ){ if( iTerm ) sqlite3StrAccumAppend(pStr, " AND ", 5); - sqlite3StrAccumAppend(pStr, zColumn, -1); + sqlite3StrAccumAppendAll(pStr, zColumn); sqlite3StrAccumAppend(pStr, zOp, 1); sqlite3StrAccumAppend(pStr, "?", 1); } /* ** Argument pLevel describes a strategy for scanning table pTab. This -** function returns a pointer to a string buffer containing a description -** of the subset of table rows scanned by the strategy in the form of an -** SQL expression. Or, if all rows are scanned, NULL is returned. +** function appends text to pStr that describes the subset of table +** rows scanned by the strategy in the form of an SQL expression. ** ** For example, if the query: ** ** SELECT * FROM t1 WHERE a=1 AND b>2; ** ** is run and there is an index on (a, b), then this function returns a ** string similar to: ** ** "a=? AND b>?" -** -** The returned pointer points to memory obtained from sqlite3DbMalloc(). -** It is the responsibility of the caller to free the buffer when it is -** no longer required. */ -static char *explainIndexRange(sqlite3 *db, WhereLoop *pLoop, Table *pTab){ +static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop, Table *pTab){ Index *pIndex = pLoop->u.btree.pIndex; - int nEq = pLoop->u.btree.nEq; + u16 nEq = pLoop->u.btree.nEq; + u16 nSkip = pLoop->nSkip; int i, j; Column *aCol = pTab->aCol; - int *aiColumn = pIndex->aiColumn; - StrAccum txt; - - if( nEq==0 && (pLoop->wsFlags & (WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))==0 ){ - return 0; - } - sqlite3StrAccumInit(&txt, 0, 0, SQLITE_MAX_LENGTH); - txt.db = db; - sqlite3StrAccumAppend(&txt, " (", 2); + i16 *aiColumn = pIndex->aiColumn; + + if( nEq==0 && (pLoop->wsFlags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))==0 ) return; + sqlite3StrAccumAppend(pStr, " (", 2); for(i=0; i<nEq; i++){ - char *z = (i==pIndex->nColumn ) ? "rowid" : aCol[aiColumn[i]].zName; - explainAppendTerm(&txt, i, z, "="); + char *z = aiColumn[i] < 0 ? "rowid" : aCol[aiColumn[i]].zName; + if( i>=nSkip ){ + explainAppendTerm(pStr, i, z, "="); + }else{ + if( i ) sqlite3StrAccumAppend(pStr, " AND ", 5); + sqlite3XPrintf(pStr, 0, "ANY(%s)", z); + } } j = i; if( pLoop->wsFlags&WHERE_BTM_LIMIT ){ - char *z = (j==pIndex->nColumn ) ? "rowid" : aCol[aiColumn[j]].zName; - explainAppendTerm(&txt, i++, z, ">"); + char *z = aiColumn[j] < 0 ? "rowid" : aCol[aiColumn[j]].zName; + explainAppendTerm(pStr, i++, z, ">"); } if( pLoop->wsFlags&WHERE_TOP_LIMIT ){ - char *z = (j==pIndex->nColumn ) ? "rowid" : aCol[aiColumn[j]].zName; - explainAppendTerm(&txt, i, z, "<"); + char *z = aiColumn[j] < 0 ? "rowid" : aCol[aiColumn[j]].zName; + explainAppendTerm(pStr, i, z, "<"); } - sqlite3StrAccumAppend(&txt, ")", 1); - return sqlite3StrAccumFinish(&txt); + sqlite3StrAccumAppend(pStr, ")", 1); } /* ** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN -** command. If the query being compiled is an EXPLAIN QUERY PLAN, a single -** record is added to the output to describe the table scan strategy in -** pLevel. +** command, or if either SQLITE_DEBUG or SQLITE_ENABLE_STMT_SCANSTATUS was +** defined at compile-time. If it is not a no-op, a single OP_Explain opcode +** is added to the output to describe the table scan strategy in pLevel. +** +** If an OP_Explain opcode is added to the VM, its address is returned. +** Otherwise, if no OP_Explain is coded, zero is returned. */ -static void explainOneScan( +static int explainOneScan( Parse *pParse, /* Parse context */ SrcList *pTabList, /* Table list this loop refers to */ WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ int iLevel, /* Value for "level" column of output */ int iFrom, /* Value for "from" column of output */ u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ ){ - if( pParse->explain==2 ){ + int ret = 0; +#if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS) + if( pParse->explain==2 ) +#endif + { struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom]; Vdbe *v = pParse->pVdbe; /* VM being constructed */ sqlite3 *db = pParse->db; /* Database handle */ - char *zMsg; /* Text to add to EQP output */ int iId = pParse->iSelectId; /* Select id (left-most output column) */ int isSearch; /* True for a SEARCH. False for SCAN. */ WhereLoop *pLoop; /* The controlling WhereLoop object */ u32 flags; /* Flags that describe this loop */ + char *zMsg; /* Text to add to EQP output */ + StrAccum str; /* EQP output string */ + char zBuf[100]; /* Initial space for EQP output string */ pLoop = pLevel->pWLoop; flags = pLoop->wsFlags; - if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_ONETABLE_ONLY) ) return; + if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_ONETABLE_ONLY) ) return 0; isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0 || ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0)) || (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX)); - zMsg = sqlite3MPrintf(db, "%s", isSearch?"SEARCH":"SCAN"); + sqlite3StrAccumInit(&str, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); + str.db = db; + sqlite3StrAccumAppendAll(&str, isSearch ? "SEARCH" : "SCAN"); if( pItem->pSelect ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s SUBQUERY %d", zMsg,pItem->iSelectId); + sqlite3XPrintf(&str, 0, " SUBQUERY %d", pItem->iSelectId); }else{ - zMsg = sqlite3MAppendf(db, zMsg, "%s TABLE %s", zMsg, pItem->zName); + sqlite3XPrintf(&str, 0, " TABLE %s", pItem->zName); } if( pItem->zAlias ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s AS %s", zMsg, pItem->zAlias); - } - if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 - && ALWAYS(pLoop->u.btree.pIndex!=0) - ){ - char *zWhere = explainIndexRange(db, pLoop, pItem->pTab); - zMsg = sqlite3MAppendf(db, zMsg, - ((flags & WHERE_AUTO_INDEX) ? - "%s USING AUTOMATIC %sINDEX%.0s%s" : - "%s USING %sINDEX %s%s"), - zMsg, ((flags & WHERE_IDX_ONLY) ? "COVERING " : ""), - pLoop->u.btree.pIndex->zName, zWhere); - sqlite3DbFree(db, zWhere); + sqlite3XPrintf(&str, 0, " AS %s", pItem->zAlias); + } + if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){ + const char *zFmt = 0; + Index *pIdx; + + assert( pLoop->u.btree.pIndex!=0 ); + pIdx = pLoop->u.btree.pIndex; + assert( !(flags&WHERE_AUTO_INDEX) || (flags&WHERE_IDX_ONLY) ); + if( !HasRowid(pItem->pTab) && IsPrimaryKeyIndex(pIdx) ){ + if( isSearch ){ + zFmt = "PRIMARY KEY"; + } + }else if( flags & WHERE_PARTIALIDX ){ + zFmt = "AUTOMATIC PARTIAL COVERING INDEX"; + }else if( flags & WHERE_AUTO_INDEX ){ + zFmt = "AUTOMATIC COVERING INDEX"; + }else if( flags & WHERE_IDX_ONLY ){ + zFmt = "COVERING INDEX %s"; + }else{ + zFmt = "INDEX %s"; + } + if( zFmt ){ + sqlite3StrAccumAppend(&str, " USING ", 7); + sqlite3XPrintf(&str, 0, zFmt, pIdx->zName); + explainIndexRange(&str, pLoop, pItem->pTab); + } }else if( (flags & WHERE_IPK)!=0 && (flags & WHERE_CONSTRAINT)!=0 ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s USING INTEGER PRIMARY KEY", zMsg); - + const char *zRange; if( flags&(WHERE_COLUMN_EQ|WHERE_COLUMN_IN) ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid=?)", zMsg); + zRange = "(rowid=?)"; }else if( (flags&WHERE_BOTH_LIMIT)==WHERE_BOTH_LIMIT ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid>? AND rowid<?)", zMsg); + zRange = "(rowid>? AND rowid<?)"; }else if( flags&WHERE_BTM_LIMIT ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid>?)", zMsg); - }else if( ALWAYS(flags&WHERE_TOP_LIMIT) ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid<?)", zMsg); + zRange = "(rowid>?)"; + }else{ + assert( flags&WHERE_TOP_LIMIT); + zRange = "(rowid<?)"; } + sqlite3StrAccumAppendAll(&str, " USING INTEGER PRIMARY KEY "); + sqlite3StrAccumAppendAll(&str, zRange); } #ifndef SQLITE_OMIT_VIRTUALTABLE else if( (flags & WHERE_VIRTUALTABLE)!=0 ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s VIRTUAL TABLE INDEX %d:%s", zMsg, + sqlite3XPrintf(&str, 0, " VIRTUAL TABLE INDEX %d:%s", pLoop->u.vtab.idxNum, pLoop->u.vtab.idxStr); } #endif - zMsg = sqlite3MAppendf(db, zMsg, "%s", zMsg); - sqlite3VdbeAddOp4(v, OP_Explain, iId, iLevel, iFrom, zMsg, P4_DYNAMIC); +#ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS + if( pLoop->nOut>=10 ){ + sqlite3XPrintf(&str, 0, " (~%llu rows)", sqlite3LogEstToInt(pLoop->nOut)); + }else{ + sqlite3StrAccumAppend(&str, " (~1 row)", 9); + } +#endif + zMsg = sqlite3StrAccumFinish(&str); + ret = sqlite3VdbeAddOp4(v, OP_Explain, iId, iLevel, iFrom, zMsg,P4_DYNAMIC); } + return ret; } #else -# define explainOneScan(u,v,w,x,y,z) +# define explainOneScan(u,v,w,x,y,z) 0 #endif /* SQLITE_OMIT_EXPLAIN */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +/* +** Configure the VM passed as the first argument with an +** sqlite3_stmt_scanstatus() entry corresponding to the scan used to +** implement level pLvl. Argument pSrclist is a pointer to the FROM +** clause that the scan reads data from. +** +** If argument addrExplain is not 0, it must be the address of an +** OP_Explain instruction that describes the same loop. +*/ +static void addScanStatus( + Vdbe *v, /* Vdbe to add scanstatus entry to */ + SrcList *pSrclist, /* FROM clause pLvl reads data from */ + WhereLevel *pLvl, /* Level to add scanstatus() entry for */ + int addrExplain /* Address of OP_Explain (or 0) */ +){ + const char *zObj = 0; + WhereLoop *pLoop = pLvl->pWLoop; + if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){ + zObj = pLoop->u.btree.pIndex->zName; + }else{ + zObj = pSrclist->a[pLvl->iFrom].zName; + } + sqlite3VdbeScanStatus( + v, addrExplain, pLvl->addrBody, pLvl->addrVisit, pLoop->nOut, zObj + ); +} +#else +# define addScanStatus(a, b, c, d) ((void)d) +#endif + +/* +** If the most recently coded instruction is a constant range contraint +** that originated from the LIKE optimization, then change the P3 to be +** pLoop->iLikeRepCntr and set P5. +** +** The LIKE optimization trys to evaluate "x LIKE 'abc%'" as a range +** expression: "x>='ABC' AND x<'abd'". But this requires that the range +** scan loop run twice, once for strings and a second time for BLOBs. +** The OP_String opcodes on the second pass convert the upper and lower +** bound string contants to blobs. This routine makes the necessary changes +** to the OP_String opcodes for that to happen. +*/ +static void whereLikeOptimizationStringFixup( + Vdbe *v, /* prepared statement under construction */ + WhereLevel *pLevel, /* The loop that contains the LIKE operator */ + WhereTerm *pTerm /* The upper or lower bound just coded */ +){ + if( pTerm->wtFlags & TERM_LIKEOPT ){ + VdbeOp *pOp; + assert( pLevel->iLikeRepCntr>0 ); + pOp = sqlite3VdbeGetOp(v, -1); + assert( pOp!=0 ); + assert( pOp->opcode==OP_String8 + || pTerm->pWC->pWInfo->pParse->db->mallocFailed ); + pOp->p3 = pLevel->iLikeRepCntr; + pOp->p5 = 1; + } +} /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. */ @@ -109413,11 +118935,11 @@ iCur = pTabItem->iCursor; pLevel->notReady = notReady & ~getMask(&pWInfo->sMaskSet, iCur); bRev = (pWInfo->revMask>>iLevel)&1; omitTable = (pLoop->wsFlags & WHERE_IDX_ONLY)!=0 && (pWInfo->wctrlFlags & WHERE_FORCE_TABLE)==0; - VdbeNoopComment((v, "Begin Join Loop %d", iLevel)); + VdbeModuleComment((v, "Begin WHERE-loop%d: %s",iLevel,pTabItem->pTab->zName)); /* Create labels for the "break" and "continue" instructions ** for the current loop. Jump to addrBrk to break out of a loop. ** Jump to cont to go immediately to the next iteration of the ** loop. @@ -109441,14 +118963,14 @@ } /* Special case of a FROM clause subquery implemented as a co-routine */ if( pTabItem->viaCoroutine ){ int regYield = pTabItem->regReturn; - sqlite3VdbeAddOp2(v, OP_Integer, pTabItem->addrFillSub-1, regYield); - pLevel->p2 = sqlite3VdbeAddOp1(v, OP_Yield, regYield); - VdbeComment((v, "next row of co-routine %s", pTabItem->pTab->zName)); - sqlite3VdbeAddOp2(v, OP_If, regYield+1, addrBrk); + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pTabItem->addrFillSub); + pLevel->p2 = sqlite3VdbeAddOp2(v, OP_Yield, regYield, addrBrk); + VdbeCoverage(v); + VdbeComment((v, "next row of \"%s\"", pTabItem->pTab->zName)); pLevel->op = OP_Goto; }else #ifndef SQLITE_OMIT_VIRTUALTABLE if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)!=0 ){ @@ -109476,10 +118998,11 @@ sqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg); sqlite3VdbeAddOp2(v, OP_Integer, nConstraint, iReg+1); sqlite3VdbeAddOp4(v, OP_VFilter, iCur, addrNotFound, iReg, pLoop->u.vtab.idxStr, pLoop->u.vtab.needFree ? P4_MPRINTF : P4_STATIC); + VdbeCoverage(v); pLoop->u.vtab.needFree = 0; for(j=0; j<nConstraint && j<16; j++){ if( (pLoop->u.vtab.omitMask>>j)&1 ){ disableTerm(pLevel, pLoop->aLTerm[j]); } @@ -109486,11 +119009,11 @@ } pLevel->op = OP_VNext; pLevel->p1 = iCur; pLevel->p2 = sqlite3VdbeCurrentAddr(v); sqlite3ReleaseTempRange(pParse, iReg, nConstraint+2); - sqlite3ExprCachePop(pParse, 1); + sqlite3ExprCachePop(pParse); }else #endif /* SQLITE_OMIT_VIRTUALTABLE */ if( (pLoop->wsFlags & WHERE_IPK)!=0 && (pLoop->wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_EQ))!=0 @@ -109499,20 +119022,22 @@ ** equality comparison against the ROWID field. Or ** we reference multiple rows using a "rowid IN (...)" ** construct. */ assert( pLoop->u.btree.nEq==1 ); - iReleaseReg = sqlite3GetTempReg(pParse); pTerm = pLoop->aLTerm[0]; assert( pTerm!=0 ); assert( pTerm->pExpr!=0 ); assert( omitTable==0 ); testcase( pTerm->wtFlags & TERM_VIRTUAL ); + iReleaseReg = ++pParse->nMem; iRowidReg = codeEqualityTerm(pParse, pTerm, pLevel, 0, bRev, iReleaseReg); + if( iRowidReg!=iReleaseReg ) sqlite3ReleaseTempReg(pParse, iReleaseReg); addrNxt = pLevel->addrNxt; - sqlite3VdbeAddOp2(v, OP_MustBeInt, iRowidReg, addrNxt); + sqlite3VdbeAddOp2(v, OP_MustBeInt, iRowidReg, addrNxt); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addrNxt, iRowidReg); + VdbeCoverage(v); sqlite3ExprCacheAffinityChange(pParse, iRowidReg, 1); sqlite3ExprCacheStore(pParse, iCur, -1, iRowidReg); VdbeComment((v, "pk")); pLevel->op = OP_Noop; }else if( (pLoop->wsFlags & WHERE_IPK)!=0 @@ -109542,14 +119067,14 @@ /* The following constant maps TK_xx codes into corresponding ** seek opcodes. It depends on a particular ordering of TK_xx */ const u8 aMoveOp[] = { - /* TK_GT */ OP_SeekGt, - /* TK_LE */ OP_SeekLe, - /* TK_LT */ OP_SeekLt, - /* TK_GE */ OP_SeekGe + /* TK_GT */ OP_SeekGT, + /* TK_LE */ OP_SeekLE, + /* TK_LT */ OP_SeekLT, + /* TK_GE */ OP_SeekGE }; assert( TK_LE==TK_GT+1 ); /* Make sure the ordering.. */ assert( TK_LT==TK_GT+2 ); /* ... of the TK_xx values... */ assert( TK_GE==TK_GT+3 ); /* ... is correcct. */ @@ -109559,15 +119084,21 @@ assert( pX!=0 ); testcase( pStart->leftCursor!=iCur ); /* transitive constraints */ r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp); sqlite3VdbeAddOp3(v, aMoveOp[pX->op-TK_GT], iCur, addrBrk, r1); VdbeComment((v, "pk")); + VdbeCoverageIf(v, pX->op==TK_GT); + VdbeCoverageIf(v, pX->op==TK_LE); + VdbeCoverageIf(v, pX->op==TK_LT); + VdbeCoverageIf(v, pX->op==TK_GE); sqlite3ExprCacheAffinityChange(pParse, r1, 1); sqlite3ReleaseTempReg(pParse, rTemp); disableTerm(pLevel, pStart); }else{ sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrBrk); + VdbeCoverageIf(v, bRev==0); + VdbeCoverageIf(v, bRev!=0); } if( pEnd ){ Expr *pX; pX = pEnd->pExpr; assert( pX!=0 ); @@ -109587,14 +119118,18 @@ pLevel->op = bRev ? OP_Prev : OP_Next; pLevel->p1 = iCur; pLevel->p2 = start; assert( pLevel->p5==0 ); if( testOp!=OP_Noop ){ - iRowidReg = iReleaseReg = sqlite3GetTempReg(pParse); + iRowidReg = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Rowid, iCur, iRowidReg); sqlite3ExprCacheStore(pParse, iCur, -1, iRowidReg); sqlite3VdbeAddOp3(v, testOp, memEndValue, addrBrk, iRowidReg); + VdbeCoverageIf(v, testOp==OP_Le); + VdbeCoverageIf(v, testOp==OP_Lt); + VdbeCoverageIf(v, testOp==OP_Ge); + VdbeCoverageIf(v, testOp==OP_Gt); sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC | SQLITE_JUMPIFNULL); } }else if( pLoop->wsFlags & WHERE_INDEXED ){ /* Case 4: A scan using an index. ** @@ -109630,24 +119165,23 @@ static const u8 aStartOp[] = { 0, 0, OP_Rewind, /* 2: (!start_constraints && startEq && !bRev) */ OP_Last, /* 3: (!start_constraints && startEq && bRev) */ - OP_SeekGt, /* 4: (start_constraints && !startEq && !bRev) */ - OP_SeekLt, /* 5: (start_constraints && !startEq && bRev) */ - OP_SeekGe, /* 6: (start_constraints && startEq && !bRev) */ - OP_SeekLe /* 7: (start_constraints && startEq && bRev) */ + OP_SeekGT, /* 4: (start_constraints && !startEq && !bRev) */ + OP_SeekLT, /* 5: (start_constraints && !startEq && bRev) */ + OP_SeekGE, /* 6: (start_constraints && startEq && !bRev) */ + OP_SeekLE /* 7: (start_constraints && startEq && bRev) */ }; static const u8 aEndOp[] = { - OP_Noop, /* 0: (!end_constraints) */ - OP_IdxGE, /* 1: (end_constraints && !bRev) */ - OP_IdxLT /* 2: (end_constraints && bRev) */ + OP_IdxGE, /* 0: (end_constraints && !bRev && !endEq) */ + OP_IdxGT, /* 1: (end_constraints && !bRev && endEq) */ + OP_IdxLE, /* 2: (end_constraints && bRev && !endEq) */ + OP_IdxLT, /* 3: (end_constraints && bRev && endEq) */ }; - int nEq = pLoop->u.btree.nEq; /* Number of == or IN terms */ - int isMinQuery = 0; /* If this is an optimized SELECT min(x).. */ + u16 nEq = pLoop->u.btree.nEq; /* Number of == or IN terms */ int regBase; /* Base register holding constraint values */ - int r1; /* Temp register */ WhereTerm *pRangeStart = 0; /* Inequality constraint at range start */ WhereTerm *pRangeEnd = 0; /* Inequality constraint at range end */ int startEq; /* True if range start uses ==, >= or <= */ int endEq; /* True if range end uses ==, >= or <= */ int start_constraints; /* Start of range is constrained */ @@ -109655,30 +119189,35 @@ Index *pIdx; /* The index we will be using */ int iIdxCur; /* The VDBE cursor for the index */ int nExtraReg = 0; /* Number of extra registers needed */ int op; /* Instruction opcode */ char *zStartAff; /* Affinity for start of range constraint */ - char *zEndAff; /* Affinity for end of range constraint */ + char cEndAff = 0; /* Affinity for end of range constraint */ + u8 bSeekPastNull = 0; /* True to seek past initial nulls */ + u8 bStopAtNull = 0; /* Add condition to terminate at NULLs */ pIdx = pLoop->u.btree.pIndex; iIdxCur = pLevel->iIdxCur; + assert( nEq>=pLoop->nSkip ); /* If this loop satisfies a sort order (pOrderBy) request that ** was passed to this function to implement a "SELECT min(x) ..." ** query, then the caller will only allow the loop to run for ** a single iteration. This means that the first row returned ** should not have a NULL value stored in 'x'. If column 'x' is ** the first one after the nEq equality constraints in the index, ** this requires some special handling. */ + assert( pWInfo->pOrderBy==0 + || pWInfo->pOrderBy->nExpr==1 + || (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0 ); if( (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)!=0 - && (pWInfo->bOBSat!=0) - && (pIdx->nColumn>nEq) + && pWInfo->nOBSat>0 + && (pIdx->nKeyCol>nEq) ){ - /* assert( pOrderBy->nExpr==1 ); */ - /* assert( pOrderBy->a[0].pExpr->iColumn==pIdx->aiColumn[nEq] ); */ - isMinQuery = 1; + assert( pLoop->nSkip==0 ); + bSeekPastNull = 1; nExtraReg = 1; } /* Find any inequality constraint terms for the start and end ** of the range. @@ -109685,32 +119224,56 @@ */ j = nEq; if( pLoop->wsFlags & WHERE_BTM_LIMIT ){ pRangeStart = pLoop->aLTerm[j++]; nExtraReg = 1; + /* Like optimization range constraints always occur in pairs */ + assert( (pRangeStart->wtFlags & TERM_LIKEOPT)==0 || + (pLoop->wsFlags & WHERE_TOP_LIMIT)!=0 ); } if( pLoop->wsFlags & WHERE_TOP_LIMIT ){ pRangeEnd = pLoop->aLTerm[j++]; nExtraReg = 1; + if( (pRangeEnd->wtFlags & TERM_LIKEOPT)!=0 ){ + assert( pRangeStart!=0 ); /* LIKE opt constraints */ + assert( pRangeStart->wtFlags & TERM_LIKEOPT ); /* occur in pairs */ + pLevel->iLikeRepCntr = ++pParse->nMem; + testcase( bRev ); + testcase( pIdx->aSortOrder[nEq]==SQLITE_SO_DESC ); + sqlite3VdbeAddOp2(v, OP_Integer, + bRev ^ (pIdx->aSortOrder[nEq]==SQLITE_SO_DESC), + pLevel->iLikeRepCntr); + VdbeComment((v, "LIKE loop counter")); + pLevel->addrLikeRep = sqlite3VdbeCurrentAddr(v); + } + if( pRangeStart==0 + && (j = pIdx->aiColumn[nEq])>=0 + && pIdx->pTable->aCol[j].notNull==0 + ){ + bSeekPastNull = 1; + } } + assert( pRangeEnd==0 || (pRangeEnd->wtFlags & TERM_VNULL)==0 ); /* Generate code to evaluate all constraint terms using == or IN ** and store the values of those terms in an array of registers ** starting at regBase. */ regBase = codeAllEqualityTerms(pParse,pLevel,bRev,nExtraReg,&zStartAff); - zEndAff = sqlite3DbStrDup(db, zStartAff); + assert( zStartAff==0 || sqlite3Strlen30(zStartAff)>=nEq ); + if( zStartAff ) cEndAff = zStartAff[nEq]; addrNxt = pLevel->addrNxt; /* If we are doing a reverse order scan on an ascending index, or ** a forward order scan on a descending index, interchange the ** start and end terms (pRangeStart and pRangeEnd). */ - if( (nEq<pIdx->nColumn && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) - || (bRev && pIdx->nColumn==nEq) + if( (nEq<pIdx->nKeyCol && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) + || (bRev && pIdx->nKeyCol==nEq) ){ SWAP(WhereTerm *, pRangeEnd, pRangeStart); + SWAP(u8, bSeekPastNull, bStopAtNull); } testcase( pRangeStart && (pRangeStart->eOperator & WO_LE)!=0 ); testcase( pRangeStart && (pRangeStart->eOperator & WO_GE)!=0 ); testcase( pRangeEnd && (pRangeEnd->eOperator & WO_LE)!=0 ); @@ -109722,12 +119285,16 @@ /* Seek the index cursor to the start of the range. */ nConstraint = nEq; if( pRangeStart ){ Expr *pRight = pRangeStart->pExpr->pRight; sqlite3ExprCode(pParse, pRight, regBase+nEq); - if( (pRangeStart->wtFlags & TERM_VNULL)==0 ){ - sqlite3ExprCodeIsNullJump(v, pRight, regBase+nEq, addrNxt); + whereLikeOptimizationStringFixup(v, pLevel, pRangeStart); + if( (pRangeStart->wtFlags & TERM_VNULL)==0 + && sqlite3ExprCanBeNull(pRight) + ){ + sqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, addrNxt); + VdbeCoverage(v); } if( zStartAff ){ if( sqlite3CompareAffinity(pRight, zStartAff[nEq])==SQLITE_AFF_NONE){ /* Since the comparison is to be performed with no conversions ** applied to the operands, set the affinity to apply to pRight to @@ -109738,90 +119305,89 @@ zStartAff[nEq] = SQLITE_AFF_NONE; } } nConstraint++; testcase( pRangeStart->wtFlags & TERM_VIRTUAL ); - }else if( isMinQuery ){ + }else if( bSeekPastNull ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); nConstraint++; startEq = 0; start_constraints = 1; } - codeApplyAffinity(pParse, regBase, nConstraint, zStartAff); + codeApplyAffinity(pParse, regBase, nConstraint - bSeekPastNull, zStartAff); op = aStartOp[(start_constraints<<2) + (startEq<<1) + bRev]; assert( op!=0 ); - testcase( op==OP_Rewind ); - testcase( op==OP_Last ); - testcase( op==OP_SeekGt ); - testcase( op==OP_SeekGe ); - testcase( op==OP_SeekLe ); - testcase( op==OP_SeekLt ); sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint); + VdbeCoverage(v); + VdbeCoverageIf(v, op==OP_Rewind); testcase( op==OP_Rewind ); + VdbeCoverageIf(v, op==OP_Last); testcase( op==OP_Last ); + VdbeCoverageIf(v, op==OP_SeekGT); testcase( op==OP_SeekGT ); + VdbeCoverageIf(v, op==OP_SeekGE); testcase( op==OP_SeekGE ); + VdbeCoverageIf(v, op==OP_SeekLE); testcase( op==OP_SeekLE ); + VdbeCoverageIf(v, op==OP_SeekLT); testcase( op==OP_SeekLT ); /* Load the value for the inequality constraint at the end of the ** range (if any). */ nConstraint = nEq; if( pRangeEnd ){ Expr *pRight = pRangeEnd->pExpr->pRight; sqlite3ExprCacheRemove(pParse, regBase+nEq, 1); sqlite3ExprCode(pParse, pRight, regBase+nEq); - if( (pRangeEnd->wtFlags & TERM_VNULL)==0 ){ - sqlite3ExprCodeIsNullJump(v, pRight, regBase+nEq, addrNxt); - } - if( zEndAff ){ - if( sqlite3CompareAffinity(pRight, zEndAff[nEq])==SQLITE_AFF_NONE){ - /* Since the comparison is to be performed with no conversions - ** applied to the operands, set the affinity to apply to pRight to - ** SQLITE_AFF_NONE. */ - zEndAff[nEq] = SQLITE_AFF_NONE; - } - if( sqlite3ExprNeedsNoAffinityChange(pRight, zEndAff[nEq]) ){ - zEndAff[nEq] = SQLITE_AFF_NONE; - } - } - codeApplyAffinity(pParse, regBase, nEq+1, zEndAff); + whereLikeOptimizationStringFixup(v, pLevel, pRangeEnd); + if( (pRangeEnd->wtFlags & TERM_VNULL)==0 + && sqlite3ExprCanBeNull(pRight) + ){ + sqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, addrNxt); + VdbeCoverage(v); + } + if( sqlite3CompareAffinity(pRight, cEndAff)!=SQLITE_AFF_NONE + && !sqlite3ExprNeedsNoAffinityChange(pRight, cEndAff) + ){ + codeApplyAffinity(pParse, regBase+nEq, 1, &cEndAff); + } nConstraint++; testcase( pRangeEnd->wtFlags & TERM_VIRTUAL ); + }else if( bStopAtNull ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); + endEq = 0; + nConstraint++; } sqlite3DbFree(db, zStartAff); - sqlite3DbFree(db, zEndAff); /* Top of the loop body */ pLevel->p2 = sqlite3VdbeCurrentAddr(v); /* Check if the index cursor is past the end of the range. */ - op = aEndOp[(pRangeEnd || nEq) * (1 + bRev)]; - testcase( op==OP_Noop ); - testcase( op==OP_IdxGE ); - testcase( op==OP_IdxLT ); - if( op!=OP_Noop ){ + if( nConstraint ){ + op = aEndOp[bRev*2 + endEq]; sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint); - sqlite3VdbeChangeP5(v, endEq!=bRev ?1:0); - } - - /* If there are inequality constraints, check that the value - ** of the table column that the inequality contrains is not NULL. - ** If it is, jump to the next iteration of the loop. - */ - r1 = sqlite3GetTempReg(pParse); - testcase( pLoop->wsFlags & WHERE_BTM_LIMIT ); - testcase( pLoop->wsFlags & WHERE_TOP_LIMIT ); - if( (pLoop->wsFlags & (WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0 ){ - sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, nEq, r1); - sqlite3VdbeAddOp2(v, OP_IsNull, r1, addrCont); - } - sqlite3ReleaseTempReg(pParse, r1); + testcase( op==OP_IdxGT ); VdbeCoverageIf(v, op==OP_IdxGT ); + testcase( op==OP_IdxGE ); VdbeCoverageIf(v, op==OP_IdxGE ); + testcase( op==OP_IdxLT ); VdbeCoverageIf(v, op==OP_IdxLT ); + testcase( op==OP_IdxLE ); VdbeCoverageIf(v, op==OP_IdxLE ); + } /* Seek the table cursor, if required */ disableTerm(pLevel, pRangeStart); disableTerm(pLevel, pRangeEnd); - if( !omitTable ){ - iRowidReg = iReleaseReg = sqlite3GetTempReg(pParse); + if( omitTable ){ + /* pIdx is a covering index. No need to access the main table. */ + }else if( HasRowid(pIdx->pTable) ){ + iRowidReg = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, iRowidReg); sqlite3ExprCacheStore(pParse, iCur, -1, iRowidReg); sqlite3VdbeAddOp2(v, OP_Seek, iCur, iRowidReg); /* Deferred seek */ + }else if( iCur!=iIdxCur ){ + Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable); + iRowidReg = sqlite3GetTempRange(pParse, pPk->nKeyCol); + for(j=0; j<pPk->nKeyCol; j++){ + k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[j]); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, iRowidReg+j); + } + sqlite3VdbeAddOp4Int(v, OP_NotFound, iCur, addrCont, + iRowidReg, pPk->nKeyCol); VdbeCoverage(v); } /* Record the instruction used to terminate the loop. Disable ** WHERE clause terms made redundant by the index range scan. */ @@ -109831,10 +119397,11 @@ pLevel->op = OP_Prev; }else{ pLevel->op = OP_Next; } pLevel->p1 = iIdxCur; + pLevel->p3 = (pLoop->wsFlags&WHERE_UNQ_WANTED)!=0 ? 1:0; if( (pLoop->wsFlags & WHERE_CONSTRAINT)==0 ){ pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP; }else{ assert( pLevel->p5==0 ); } @@ -109878,10 +119445,14 @@ ** ** Return 2 # Jump back to the Gosub ** ** B: <after the loop> ** + ** Added 2014-05-26: If the table is a WITHOUT ROWID table, then + ** use an ephemeral index instead of a RowSet to record the primary + ** keys of the rows we have already seen. + ** */ WhereClause *pOrWc; /* The OR-clause broken out into subterms */ SrcList *pOrTab; /* Shortened table list or OR-clause generation */ Index *pCov = 0; /* Potential covering index (or NULL) */ int iCovCur = pParse->nTab++; /* Cursor used for index scans (if any) */ @@ -109891,11 +119462,13 @@ int regRowid = 0; /* Register holding rowid */ int iLoopBody = sqlite3VdbeMakeLabel(v); /* Start of loop body */ int iRetInit; /* Address of regReturn init */ int untestedTerms = 0; /* Some terms not completely tested */ int ii; /* Loop counter */ + u16 wctrlFlags; /* Flags for sub-WHERE clause */ Expr *pAndExpr = 0; /* An ".. AND (...)" expression */ + Table *pTab = pTabItem->pTab; pTerm = pLoop->aLTerm[0]; assert( pTerm!=0 ); assert( pTerm->eOperator & WO_OR ); assert( (pTerm->wtFlags & TERM_ORINFO)!=0 ); @@ -109924,11 +119497,12 @@ }else{ pOrTab = pWInfo->pTabList; } /* Initialize the rowset register to contain NULL. An SQL NULL is - ** equivalent to an empty rowset. + ** equivalent to an empty rowset. Or, create an ephemeral index + ** capable of holding primary keys in the case of a WITHOUT ROWID. ** ** Also initialize regReturn to contain the address of the instruction ** immediately following the OP_Return at the bottom of the loop. This ** is required in a few obscure LEFT JOIN cases where control jumps ** over the top of the loop into the body of it. In this case the @@ -109935,13 +119509,20 @@ ** correct response for the end-of-loop code (the OP_Return) is to ** fall through to the next instruction, just as an OP_Next does if ** called on an uninitialized cursor. */ if( (pWInfo->wctrlFlags & WHERE_DUPLICATES_OK)==0 ){ - regRowset = ++pParse->nMem; + if( HasRowid(pTab) ){ + regRowset = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Null, 0, regRowset); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + regRowset = pParse->nTab++; + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, regRowset, pPk->nKeyCol); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + } regRowid = ++pParse->nMem; - sqlite3VdbeAddOp2(v, OP_Null, 0, regRowset); } iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn); /* If the original WHERE clause is z of the form: (x1 OR x2 OR ...) AND y ** Then for every term xN, evaluate as the subexpression: xN AND z @@ -109961,48 +119542,107 @@ int iTerm; for(iTerm=0; iTerm<pWC->nTerm; iTerm++){ Expr *pExpr = pWC->a[iTerm].pExpr; if( &pWC->a[iTerm] == pTerm ) continue; if( ExprHasProperty(pExpr, EP_FromJoin) ) continue; - if( pWC->a[iTerm].wtFlags & (TERM_ORINFO) ) continue; + if( (pWC->a[iTerm].wtFlags & TERM_VIRTUAL)!=0 ) continue; if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue; + testcase( pWC->a[iTerm].wtFlags & TERM_ORINFO ); pExpr = sqlite3ExprDup(db, pExpr, 0); pAndExpr = sqlite3ExprAnd(db, pAndExpr, pExpr); } if( pAndExpr ){ pAndExpr = sqlite3PExpr(pParse, TK_AND, 0, pAndExpr, 0); } } + /* Run a separate WHERE clause for each term of the OR clause. After + ** eliminating duplicates from other WHERE clauses, the action for each + ** sub-WHERE clause is to to invoke the main loop body as a subroutine. + */ + wctrlFlags = WHERE_OMIT_OPEN_CLOSE + | WHERE_FORCE_TABLE + | WHERE_ONETABLE_ONLY + | WHERE_NO_AUTOINDEX; for(ii=0; ii<pOrWc->nTerm; ii++){ WhereTerm *pOrTerm = &pOrWc->a[ii]; if( pOrTerm->leftCursor==iCur || (pOrTerm->eOperator & WO_AND)!=0 ){ - WhereInfo *pSubWInfo; /* Info for single OR-term scan */ - Expr *pOrExpr = pOrTerm->pExpr; + WhereInfo *pSubWInfo; /* Info for single OR-term scan */ + Expr *pOrExpr = pOrTerm->pExpr; /* Current OR clause term */ + int j1 = 0; /* Address of jump operation */ if( pAndExpr && !ExprHasProperty(pOrExpr, EP_FromJoin) ){ pAndExpr->pLeft = pOrExpr; pOrExpr = pAndExpr; } /* Loop through table entries that match term pOrTerm. */ + WHERETRACE(0xffff, ("Subplan for OR-clause:\n")); pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, - WHERE_OMIT_OPEN_CLOSE | WHERE_AND_ONLY | - WHERE_FORCE_TABLE | WHERE_ONETABLE_ONLY, iCovCur); + wctrlFlags, iCovCur); assert( pSubWInfo || pParse->nErr || db->mallocFailed ); if( pSubWInfo ){ WhereLoop *pSubLoop; - explainOneScan( + int addrExplain = explainOneScan( pParse, pOrTab, &pSubWInfo->a[0], iLevel, pLevel->iFrom, 0 ); + addScanStatus(v, pOrTab, &pSubWInfo->a[0], addrExplain); + + /* This is the sub-WHERE clause body. First skip over + ** duplicate rows from prior sub-WHERE clauses, and record the + ** rowid (or PRIMARY KEY) for the current row so that the same + ** row will be skipped in subsequent sub-WHERE clauses. + */ if( (pWInfo->wctrlFlags & WHERE_DUPLICATES_OK)==0 ){ - int iSet = ((ii==pOrWc->nTerm-1)?-1:ii); int r; - r = sqlite3ExprCodeGetColumn(pParse, pTabItem->pTab, -1, iCur, - regRowid, 0); - sqlite3VdbeAddOp4Int(v, OP_RowSetTest, regRowset, - sqlite3VdbeCurrentAddr(v)+2, r, iSet); + int iSet = ((ii==pOrWc->nTerm-1)?-1:ii); + if( HasRowid(pTab) ){ + r = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iCur, regRowid, 0); + j1 = sqlite3VdbeAddOp4Int(v, OP_RowSetTest, regRowset, 0, r,iSet); + VdbeCoverage(v); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + int nPk = pPk->nKeyCol; + int iPk; + + /* Read the PK into an array of temp registers. */ + r = sqlite3GetTempRange(pParse, nPk); + for(iPk=0; iPk<nPk; iPk++){ + int iCol = pPk->aiColumn[iPk]; + sqlite3ExprCodeGetColumn(pParse, pTab, iCol, iCur, r+iPk, 0); + } + + /* Check if the temp table already contains this key. If so, + ** the row has already been included in the result set and + ** can be ignored (by jumping past the Gosub below). Otherwise, + ** insert the key into the temp table and proceed with processing + ** the row. + ** + ** Use some of the same optimizations as OP_RowSetTest: If iSet + ** is zero, assume that the key cannot already be present in + ** the temp table. And if iSet is -1, assume that there is no + ** need to insert the key into the temp table, as it will never + ** be tested for. */ + if( iSet ){ + j1 = sqlite3VdbeAddOp4Int(v, OP_Found, regRowset, 0, r, nPk); + VdbeCoverage(v); + } + if( iSet>=0 ){ + sqlite3VdbeAddOp3(v, OP_MakeRecord, r, nPk, regRowid); + sqlite3VdbeAddOp3(v, OP_IdxInsert, regRowset, regRowid, 0); + if( iSet ) sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + } + + /* Release the array of temp registers */ + sqlite3ReleaseTempRange(pParse, r, nPk); + } } + + /* Invoke the main loop body as a subroutine */ sqlite3VdbeAddOp2(v, OP_Gosub, regReturn, iLoopBody); + + /* Jump here (skipping the main loop body subroutine) if the + ** current sub-WHERE row is a duplicate from prior sub-WHEREs. */ + if( j1 ) sqlite3VdbeJumpHere(v, j1); /* The pSubWInfo->untestedTerms flag means that this OR term ** contained one or more AND term from a notReady table. The ** terms from the notReady table could not be tested and will ** need to be tested later. @@ -110023,13 +119663,15 @@ */ pSubLoop = pSubWInfo->a[0].pWLoop; assert( (pSubLoop->wsFlags & WHERE_AUTO_INDEX)==0 ); if( (pSubLoop->wsFlags & WHERE_INDEXED)!=0 && (ii==0 || pSubLoop->u.btree.pIndex==pCov) + && (HasRowid(pTab) || !IsPrimaryKeyIndex(pSubLoop->u.btree.pIndex)) ){ assert( pSubWInfo->a[0].iIdxCur==iCovCur ); pCov = pSubLoop->u.btree.pIndex; + wctrlFlags |= WHERE_REOPEN_IDX; }else{ pCov = 0; } /* Finish the loop through table entries that match term pOrTerm. */ @@ -110057,21 +119699,34 @@ ** scan of the entire table. */ static const u8 aStep[] = { OP_Next, OP_Prev }; static const u8 aStart[] = { OP_Rewind, OP_Last }; assert( bRev==0 || bRev==1 ); - pLevel->op = aStep[bRev]; - pLevel->p1 = iCur; - pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrBrk); - pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP; + if( pTabItem->isRecursive ){ + /* Tables marked isRecursive have only a single row that is stored in + ** a pseudo-cursor. No need to Rewind or Next such cursors. */ + pLevel->op = OP_Noop; + }else{ + pLevel->op = aStep[bRev]; + pLevel->p1 = iCur; + pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrBrk); + VdbeCoverageIf(v, bRev==0); + VdbeCoverageIf(v, bRev!=0); + pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP; + } } + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + pLevel->addrVisit = sqlite3VdbeCurrentAddr(v); +#endif /* Insert code to test every subexpression that can be completely ** computed using the current set of tables. */ for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){ Expr *pE; + int skipLikeAddr = 0; testcase( pTerm->wtFlags & TERM_VIRTUAL ); testcase( pTerm->wtFlags & TERM_CODED ); if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; if( (pTerm->prereqAll & pLevel->notReady)!=0 ){ testcase( pWInfo->untestedTerms==0 @@ -110081,12 +119736,18 @@ } pE = pTerm->pExpr; assert( pE!=0 ); if( pLevel->iLeftJoin && !ExprHasProperty(pE, EP_FromJoin) ){ continue; + } + if( pTerm->wtFlags & TERM_LIKECOND ){ + assert( pLevel->iLikeRepCntr>0 ); + skipLikeAddr = sqlite3VdbeAddOp1(v, OP_IfNot, pLevel->iLikeRepCntr); + VdbeCoverage(v); } sqlite3ExprIfFalse(pParse, pE, addrCont, SQLITE_JUMPIFNULL); + if( skipLikeAddr ) sqlite3VdbeJumpHere(v, skipLikeAddr); pTerm->wtFlags |= TERM_CODED; } /* Insert code to test for implied constraints based on transitivity ** of the "==" operator. @@ -110109,11 +119770,11 @@ pAlt = findTerm(pWC, iCur, pTerm->u.leftColumn, notReady, WO_EQ|WO_IN, 0); if( pAlt==0 ) continue; if( pAlt->wtFlags & (TERM_CODED) ) continue; testcase( pAlt->eOperator & WO_EQ ); testcase( pAlt->eOperator & WO_IN ); - VdbeNoopComment((v, "begin transitive constraint")); + VdbeModuleComment((v, "begin transitive constraint")); pEAlt = sqlite3StackAllocRaw(db, sizeof(*pEAlt)); if( pEAlt ){ *pEAlt = *pAlt->pExpr; pEAlt->pLeft = pE->pLeft; sqlite3ExprIfFalse(pParse, pEAlt, addrCont, SQLITE_JUMPIFNULL); @@ -110140,31 +119801,51 @@ assert( pTerm->pExpr ); sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); pTerm->wtFlags |= TERM_CODED; } } - sqlite3ReleaseTempReg(pParse, iReleaseReg); return pLevel->notReady; } + +#ifdef WHERETRACE_ENABLED +/* +** Print the content of a WhereTerm object +*/ +static void whereTermPrint(WhereTerm *pTerm, int iTerm){ + if( pTerm==0 ){ + sqlite3DebugPrintf("TERM-%-3d NULL\n", iTerm); + }else{ + char zType[4]; + memcpy(zType, "...", 4); + if( pTerm->wtFlags & TERM_VIRTUAL ) zType[0] = 'V'; + if( pTerm->eOperator & WO_EQUIV ) zType[1] = 'E'; + if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) zType[2] = 'L'; + sqlite3DebugPrintf("TERM-%-3d %p %s cursor=%-3d prob=%-3d op=0x%03x\n", + iTerm, pTerm, zType, pTerm->leftCursor, pTerm->truthProb, + pTerm->eOperator); + sqlite3TreeViewExpr(0, pTerm->pExpr, 0); + } +} +#endif #ifdef WHERETRACE_ENABLED /* ** Print a WhereLoop object for debugging purposes */ -static void whereLoopPrint(WhereLoop *p, SrcList *pTabList){ - int nb = 1+(pTabList->nSrc+7)/8; - struct SrcList_item *pItem = pTabList->a + p->iTab; +static void whereLoopPrint(WhereLoop *p, WhereClause *pWC){ + WhereInfo *pWInfo = pWC->pWInfo; + int nb = 1+(pWInfo->pTabList->nSrc+7)/8; + struct SrcList_item *pItem = pWInfo->pTabList->a + p->iTab; Table *pTab = pItem->pTab; sqlite3DebugPrintf("%c%2d.%0*llx.%0*llx", p->cId, p->iTab, nb, p->maskSelf, nb, p->prereq); sqlite3DebugPrintf(" %12s", pItem->zAlias ? pItem->zAlias : pTab->zName); if( (p->wsFlags & WHERE_VIRTUALTABLE)==0 ){ - if( p->u.btree.pIndex ){ - const char *zName = p->u.btree.pIndex->zName; - if( zName==0 ) zName = "ipk"; + const char *zName; + if( p->u.btree.pIndex && (zName = p->u.btree.pIndex->zName)!=0 ){ if( strncmp(zName, "sqlite_autoindex_", 17)==0 ){ int i = sqlite3Strlen30(zName) - 1; while( zName[i]!='_' ) i--; zName += i; } @@ -110181,12 +119862,22 @@ z = sqlite3_mprintf("(%d,%x)", p->u.vtab.idxNum, p->u.vtab.omitMask); } sqlite3DebugPrintf(" %-19s", z); sqlite3_free(z); } - sqlite3DebugPrintf(" f %04x N %d", p->wsFlags, p->nLTerm); + if( p->wsFlags & WHERE_SKIPSCAN ){ + sqlite3DebugPrintf(" f %05x %d-%d", p->wsFlags, p->nLTerm,p->nSkip); + }else{ + sqlite3DebugPrintf(" f %05x N %d", p->wsFlags, p->nLTerm); + } sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut); + if( p->nLTerm && (sqlite3WhereTrace & 0x100)!=0 ){ + int i; + for(i=0; i<p->nLTerm; i++){ + whereTermPrint(p->aLTerm[i], i); + } + } } #endif /* ** Convert bulk memory into a valid WhereLoop that can be passed @@ -110282,63 +119973,105 @@ sqlite3DbFree(db, pWInfo); } } /* -** Insert or replace a WhereLoop entry using the template supplied. -** -** An existing WhereLoop entry might be overwritten if the new template -** is better and has fewer dependencies. Or the template will be ignored -** and no insert will occur if an existing WhereLoop is faster and has -** fewer dependencies than the template. Otherwise a new WhereLoop is -** added based on the template. -** -** If pBuilder->pOrSet is not NULL then we only care about only the -** prerequisites and rRun and nOut costs of the N best loops. That -** information is gathered in the pBuilder->pOrSet object. This special -** processing mode is used only for OR clause processing. -** -** When accumulating multiple loops (when pBuilder->pOrSet is NULL) we -** still might overwrite similar loops with the new template if the -** template is better. Loops may be overwritten if the following -** conditions are met: -** -** (1) They have the same iTab. -** (2) They have the same iSortIdx. -** (3) The template has same or fewer dependencies than the current loop -** (4) The template has the same or lower cost than the current loop -** (5) The template uses more terms of the same index but has no additional -** dependencies -*/ -static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ - WhereLoop **ppPrev, *p, *pNext = 0; - WhereInfo *pWInfo = pBuilder->pWInfo; - sqlite3 *db = pWInfo->pParse->db; - - /* If pBuilder->pOrSet is defined, then only keep track of the costs - ** and prereqs. - */ - if( pBuilder->pOrSet!=0 ){ -#if WHERETRACE_ENABLED - u16 n = pBuilder->pOrSet->n; - int x = -#endif - whereOrInsert(pBuilder->pOrSet, pTemplate->prereq, pTemplate->rRun, - pTemplate->nOut); -#if WHERETRACE_ENABLED - if( sqlite3WhereTrace & 0x8 ){ - sqlite3DebugPrintf(x?" or-%d: ":" or-X: ", n); - whereLoopPrint(pTemplate, pWInfo->pTabList); - } -#endif - return SQLITE_OK; - } - - /* Search for an existing WhereLoop to overwrite, or which takes - ** priority over pTemplate. - */ - for(ppPrev=&pWInfo->pLoops, p=*ppPrev; p; ppPrev=&p->pNextLoop, p=*ppPrev){ +** Return TRUE if all of the following are true: +** +** (1) X has the same or lower cost that Y +** (2) X is a proper subset of Y +** (3) X skips at least as many columns as Y +** +** By "proper subset" we mean that X uses fewer WHERE clause terms +** than Y and that every WHERE clause term used by X is also used +** by Y. +** +** If X is a proper subset of Y then Y is a better choice and ought +** to have a lower cost. This routine returns TRUE when that cost +** relationship is inverted and needs to be adjusted. The third rule +** was added because if X uses skip-scan less than Y it still might +** deserve a lower cost even if it is a proper subset of Y. +*/ +static int whereLoopCheaperProperSubset( + const WhereLoop *pX, /* First WhereLoop to compare */ + const WhereLoop *pY /* Compare against this WhereLoop */ +){ + int i, j; + if( pX->nLTerm-pX->nSkip >= pY->nLTerm-pY->nSkip ){ + return 0; /* X is not a subset of Y */ + } + if( pY->nSkip > pX->nSkip ) return 0; + if( pX->rRun >= pY->rRun ){ + if( pX->rRun > pY->rRun ) return 0; /* X costs more than Y */ + if( pX->nOut > pY->nOut ) return 0; /* X costs more than Y */ + } + for(i=pX->nLTerm-1; i>=0; i--){ + if( pX->aLTerm[i]==0 ) continue; + for(j=pY->nLTerm-1; j>=0; j--){ + if( pY->aLTerm[j]==pX->aLTerm[i] ) break; + } + if( j<0 ) return 0; /* X not a subset of Y since term X[i] not used by Y */ + } + return 1; /* All conditions meet */ +} + +/* +** Try to adjust the cost of WhereLoop pTemplate upwards or downwards so +** that: +** +** (1) pTemplate costs less than any other WhereLoops that are a proper +** subset of pTemplate +** +** (2) pTemplate costs more than any other WhereLoops for which pTemplate +** is a proper subset. +** +** To say "WhereLoop X is a proper subset of Y" means that X uses fewer +** WHERE clause terms than Y and that every WHERE clause term used by X is +** also used by Y. +*/ +static void whereLoopAdjustCost(const WhereLoop *p, WhereLoop *pTemplate){ + if( (pTemplate->wsFlags & WHERE_INDEXED)==0 ) return; + for(; p; p=p->pNextLoop){ + if( p->iTab!=pTemplate->iTab ) continue; + if( (p->wsFlags & WHERE_INDEXED)==0 ) continue; + if( whereLoopCheaperProperSubset(p, pTemplate) ){ + /* Adjust pTemplate cost downward so that it is cheaper than its + ** subset p. */ + WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", + pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut-1)); + pTemplate->rRun = p->rRun; + pTemplate->nOut = p->nOut - 1; + }else if( whereLoopCheaperProperSubset(pTemplate, p) ){ + /* Adjust pTemplate cost upward so that it is costlier than p since + ** pTemplate is a proper subset of p */ + WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", + pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut+1)); + pTemplate->rRun = p->rRun; + pTemplate->nOut = p->nOut + 1; + } + } +} + +/* +** Search the list of WhereLoops in *ppPrev looking for one that can be +** supplanted by pTemplate. +** +** Return NULL if the WhereLoop list contains an entry that can supplant +** pTemplate, in other words if pTemplate does not belong on the list. +** +** If pX is a WhereLoop that pTemplate can supplant, then return the +** link that points to pX. +** +** If pTemplate cannot supplant any existing element of the list but needs +** to be added to the list, then return a pointer to the tail of the list. +*/ +static WhereLoop **whereLoopFindLesser( + WhereLoop **ppPrev, + const WhereLoop *pTemplate +){ + WhereLoop *p; + for(p=(*ppPrev); p; ppPrev=&p->pNextLoop, p=*ppPrev){ if( p->iTab!=pTemplate->iTab || p->iSortIdx!=pTemplate->iSortIdx ){ /* If either the iTab or iSortIdx values for two WhereLoop are different ** then those WhereLoops need to be considered separately. Neither is ** a candidate to replace the other. */ continue; @@ -110352,132 +120085,270 @@ /* whereLoopAddBtree() always generates and inserts the automatic index ** case first. Hence compatible candidate WhereLoops never have a larger ** rSetup. Call this SETUP-INVARIANT */ assert( p->rSetup>=pTemplate->rSetup ); - if( (p->prereq & pTemplate->prereq)==p->prereq - && p->rSetup<=pTemplate->rSetup - && p->rRun<=pTemplate->rRun - && p->nOut<=pTemplate->nOut - ){ - /* This branch taken when p is equal or better than pTemplate in - ** all of (1) dependencies (2) setup-cost, (3) run-cost, and - ** (4) number of output rows. */ - assert( p->rSetup==pTemplate->rSetup ); - if( p->prereq==pTemplate->prereq - && p->nLTerm<pTemplate->nLTerm - && (p->wsFlags & pTemplate->wsFlags & WHERE_INDEXED)!=0 - && (p->u.btree.pIndex==pTemplate->u.btree.pIndex - || pTemplate->rRun+p->nLTerm<=p->rRun+pTemplate->nLTerm) - ){ - /* Overwrite an existing WhereLoop with an similar one that uses - ** more terms of the index */ - pNext = p->pNextLoop; - break; - }else{ - /* pTemplate is not helpful. - ** Return without changing or adding anything */ - goto whereLoopInsert_noop; - } - } - if( (p->prereq & pTemplate->prereq)==pTemplate->prereq - && p->rRun>=pTemplate->rRun - && p->nOut>=pTemplate->nOut - ){ - /* Overwrite an existing WhereLoop with a better one: one that is - ** better at one of (1) dependencies, (2) setup-cost, (3) run-cost - ** or (4) number of output rows, and is no worse in any of those - ** categories. */ + /* Any loop using an appliation-defined index (or PRIMARY KEY or + ** UNIQUE constraint) with one or more == constraints is better + ** than an automatic index. Unless it is a skip-scan. */ + if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 + && (pTemplate->nSkip)==0 + && (pTemplate->wsFlags & WHERE_INDEXED)!=0 + && (pTemplate->wsFlags & WHERE_COLUMN_EQ)!=0 + && (p->prereq & pTemplate->prereq)==pTemplate->prereq + ){ + break; + } + + /* If existing WhereLoop p is better than pTemplate, pTemplate can be + ** discarded. WhereLoop p is better if: + ** (1) p has no more dependencies than pTemplate, and + ** (2) p has an equal or lower cost than pTemplate + */ + if( (p->prereq & pTemplate->prereq)==p->prereq /* (1) */ + && p->rSetup<=pTemplate->rSetup /* (2a) */ + && p->rRun<=pTemplate->rRun /* (2b) */ + && p->nOut<=pTemplate->nOut /* (2c) */ + ){ + return 0; /* Discard pTemplate */ + } + + /* If pTemplate is always better than p, then cause p to be overwritten + ** with pTemplate. pTemplate is better than p if: + ** (1) pTemplate has no more dependences than p, and + ** (2) pTemplate has an equal or lower cost than p. + */ + if( (p->prereq & pTemplate->prereq)==pTemplate->prereq /* (1) */ + && p->rRun>=pTemplate->rRun /* (2a) */ + && p->nOut>=pTemplate->nOut /* (2b) */ + ){ assert( p->rSetup>=pTemplate->rSetup ); /* SETUP-INVARIANT above */ - pNext = p->pNextLoop; - break; + break; /* Cause p to be overwritten by pTemplate */ } + } + return ppPrev; +} + +/* +** Insert or replace a WhereLoop entry using the template supplied. +** +** An existing WhereLoop entry might be overwritten if the new template +** is better and has fewer dependencies. Or the template will be ignored +** and no insert will occur if an existing WhereLoop is faster and has +** fewer dependencies than the template. Otherwise a new WhereLoop is +** added based on the template. +** +** If pBuilder->pOrSet is not NULL then we care about only the +** prerequisites and rRun and nOut costs of the N best loops. That +** information is gathered in the pBuilder->pOrSet object. This special +** processing mode is used only for OR clause processing. +** +** When accumulating multiple loops (when pBuilder->pOrSet is NULL) we +** still might overwrite similar loops with the new template if the +** new template is better. Loops may be overwritten if the following +** conditions are met: +** +** (1) They have the same iTab. +** (2) They have the same iSortIdx. +** (3) The template has same or fewer dependencies than the current loop +** (4) The template has the same or lower cost than the current loop +*/ +static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ + WhereLoop **ppPrev, *p; + WhereInfo *pWInfo = pBuilder->pWInfo; + sqlite3 *db = pWInfo->pParse->db; + + /* If pBuilder->pOrSet is defined, then only keep track of the costs + ** and prereqs. + */ + if( pBuilder->pOrSet!=0 ){ +#if WHERETRACE_ENABLED + u16 n = pBuilder->pOrSet->n; + int x = +#endif + whereOrInsert(pBuilder->pOrSet, pTemplate->prereq, pTemplate->rRun, + pTemplate->nOut); +#if WHERETRACE_ENABLED /* 0x8 */ + if( sqlite3WhereTrace & 0x8 ){ + sqlite3DebugPrintf(x?" or-%d: ":" or-X: ", n); + whereLoopPrint(pTemplate, pBuilder->pWC); + } +#endif + return SQLITE_OK; + } + + /* Look for an existing WhereLoop to replace with pTemplate + */ + whereLoopAdjustCost(pWInfo->pLoops, pTemplate); + ppPrev = whereLoopFindLesser(&pWInfo->pLoops, pTemplate); + + if( ppPrev==0 ){ + /* There already exists a WhereLoop on the list that is better + ** than pTemplate, so just ignore pTemplate */ +#if WHERETRACE_ENABLED /* 0x8 */ + if( sqlite3WhereTrace & 0x8 ){ + sqlite3DebugPrintf(" skip: "); + whereLoopPrint(pTemplate, pBuilder->pWC); + } +#endif + return SQLITE_OK; + }else{ + p = *ppPrev; } /* If we reach this point it means that either p[] should be overwritten ** with pTemplate[] if p[] exists, or if p==NULL then allocate a new ** WhereLoop and insert it. */ -#if WHERETRACE_ENABLED +#if WHERETRACE_ENABLED /* 0x8 */ if( sqlite3WhereTrace & 0x8 ){ if( p!=0 ){ - sqlite3DebugPrintf("ins-del: "); - whereLoopPrint(p, pWInfo->pTabList); + sqlite3DebugPrintf("replace: "); + whereLoopPrint(p, pBuilder->pWC); } - sqlite3DebugPrintf("ins-new: "); - whereLoopPrint(pTemplate, pWInfo->pTabList); + sqlite3DebugPrintf(" add: "); + whereLoopPrint(pTemplate, pBuilder->pWC); } #endif if( p==0 ){ - p = sqlite3DbMallocRaw(db, sizeof(WhereLoop)); + /* Allocate a new WhereLoop to add to the end of the list */ + *ppPrev = p = sqlite3DbMallocRaw(db, sizeof(WhereLoop)); if( p==0 ) return SQLITE_NOMEM; whereLoopInit(p); + p->pNextLoop = 0; + }else{ + /* We will be overwriting WhereLoop p[]. But before we do, first + ** go through the rest of the list and delete any other entries besides + ** p[] that are also supplated by pTemplate */ + WhereLoop **ppTail = &p->pNextLoop; + WhereLoop *pToDel; + while( *ppTail ){ + ppTail = whereLoopFindLesser(ppTail, pTemplate); + if( ppTail==0 ) break; + pToDel = *ppTail; + if( pToDel==0 ) break; + *ppTail = pToDel->pNextLoop; +#if WHERETRACE_ENABLED /* 0x8 */ + if( sqlite3WhereTrace & 0x8 ){ + sqlite3DebugPrintf(" delete: "); + whereLoopPrint(pToDel, pBuilder->pWC); + } +#endif + whereLoopDelete(db, pToDel); + } } whereLoopXfer(db, p, pTemplate); - p->pNextLoop = pNext; - *ppPrev = p; if( (p->wsFlags & WHERE_VIRTUALTABLE)==0 ){ Index *pIndex = p->u.btree.pIndex; if( pIndex && pIndex->tnum==0 ){ p->u.btree.pIndex = 0; } } return SQLITE_OK; - - /* Jump here if the insert is a no-op */ -whereLoopInsert_noop: -#if WHERETRACE_ENABLED - if( sqlite3WhereTrace & 0x8 ){ - sqlite3DebugPrintf("ins-noop: "); - whereLoopPrint(pTemplate, pWInfo->pTabList); - } -#endif - return SQLITE_OK; } /* ** Adjust the WhereLoop.nOut value downward to account for terms of the ** WHERE clause that reference the loop but which are not used by an ** index. +* +** For every WHERE clause term that is not used by the index +** and which has a truth probability assigned by one of the likelihood(), +** likely(), or unlikely() SQL functions, reduce the estimated number +** of output rows by the probability specified. ** -** In the current implementation, the first extra WHERE clause term reduces -** the number of output rows by a factor of 10 and each additional term -** reduces the number of output rows by sqrt(2). +** TUNING: For every WHERE clause term that is not used by the index +** and which does not have an assigned truth probability, heuristics +** described below are used to try to estimate the truth probability. +** TODO --> Perhaps this is something that could be improved by better +** table statistics. +** +** Heuristic 1: Estimate the truth probability as 93.75%. The 93.75% +** value corresponds to -1 in LogEst notation, so this means decrement +** the WhereLoop.nOut field for every such WHERE clause term. +** +** Heuristic 2: If there exists one or more WHERE clause terms of the +** form "x==EXPR" and EXPR is not a constant 0 or 1, then make sure the +** final output row estimate is no greater than 1/4 of the total number +** of rows in the table. In other words, assume that x==EXPR will filter +** out at least 3 out of 4 rows. If EXPR is -1 or 0 or 1, then maybe the +** "x" column is boolean or else -1 or 0 or 1 is a common default value +** on the "x" column and so in that case only cap the output row estimate +** at 1/2 instead of 1/4. */ -static void whereLoopOutputAdjust(WhereClause *pWC, WhereLoop *pLoop, int iCur){ +static void whereLoopOutputAdjust( + WhereClause *pWC, /* The WHERE clause */ + WhereLoop *pLoop, /* The loop to adjust downward */ + LogEst nRow /* Number of rows in the entire table */ +){ WhereTerm *pTerm, *pX; Bitmask notAllowed = ~(pLoop->prereq|pLoop->maskSelf); - int i, j; + int i, j, k; + LogEst iReduce = 0; /* pLoop->nOut should not exceed nRow-iReduce */ - if( !OptimizationEnabled(pWC->pWInfo->pParse->db, SQLITE_AdjustOutEst) ){ - return; - } + assert( (pLoop->wsFlags & WHERE_AUTO_INDEX)==0 ); for(i=pWC->nTerm, pTerm=pWC->a; i>0; i--, pTerm++){ if( (pTerm->wtFlags & TERM_VIRTUAL)!=0 ) break; if( (pTerm->prereqAll & pLoop->maskSelf)==0 ) continue; if( (pTerm->prereqAll & notAllowed)!=0 ) continue; for(j=pLoop->nLTerm-1; j>=0; j--){ pX = pLoop->aLTerm[j]; + if( pX==0 ) continue; if( pX==pTerm ) break; if( pX->iParent>=0 && (&pWC->a[pX->iParent])==pTerm ) break; } - if( j<0 ) pLoop->nOut += pTerm->truthProb; + if( j<0 ){ + if( pTerm->truthProb<=0 ){ + /* If a truth probability is specified using the likelihood() hints, + ** then use the probability provided by the application. */ + pLoop->nOut += pTerm->truthProb; + }else{ + /* In the absence of explicit truth probabilities, use heuristics to + ** guess a reasonable truth probability. */ + pLoop->nOut--; + if( pTerm->eOperator&WO_EQ ){ + Expr *pRight = pTerm->pExpr->pRight; + if( sqlite3ExprIsInteger(pRight, &k) && k>=(-1) && k<=1 ){ + k = 10; + }else{ + k = 20; + } + if( iReduce<k ) iReduce = k; + } + } + } } + if( pLoop->nOut > nRow-iReduce ) pLoop->nOut = nRow - iReduce; } /* -** We have so far matched pBuilder->pNew->u.btree.nEq terms of the index pIndex. -** Try to match one more. +** Adjust the cost C by the costMult facter T. This only occurs if +** compiled with -DSQLITE_ENABLE_COSTMULT +*/ +#ifdef SQLITE_ENABLE_COSTMULT +# define ApplyCostMultiplier(C,T) C += T +#else +# define ApplyCostMultiplier(C,T) +#endif + +/* +** We have so far matched pBuilder->pNew->u.btree.nEq terms of the +** index pIndex. Try to match one more. +** +** When this function is called, pBuilder->pNew->nOut contains the +** number of rows expected to be visited by filtering using the nEq +** terms only. If it is modified, this value is restored before this +** function returns. ** ** If pProbe->tnum==0, that means pIndex is a fake index used for the ** INTEGER PRIMARY KEY. */ static int whereLoopAddBtreeIndex( WhereLoopBuilder *pBuilder, /* The WhereLoop factory */ struct SrcList_item *pSrc, /* FROM clause term being analyzed */ Index *pProbe, /* An index on pSrc */ - WhereCost nInMul /* log(Number of iterations due to IN) */ + LogEst nInMul /* log(Number of iterations due to IN) */ ){ WhereInfo *pWInfo = pBuilder->pWInfo; /* WHERE analyse context */ Parse *pParse = pWInfo->pParse; /* Parsing context */ sqlite3 *db = pParse->db; /* Database connection malloc context */ WhereLoop *pNew; /* Template WhereLoop under construction */ @@ -110484,17 +120355,18 @@ WhereTerm *pTerm; /* A WhereTerm under consideration */ int opMask; /* Valid operators for constraints */ WhereScan scan; /* Iterator for WHERE terms */ Bitmask saved_prereq; /* Original value of pNew->prereq */ u16 saved_nLTerm; /* Original value of pNew->nLTerm */ - int saved_nEq; /* Original value of pNew->u.btree.nEq */ + u16 saved_nEq; /* Original value of pNew->u.btree.nEq */ + u16 saved_nSkip; /* Original value of pNew->nSkip */ u32 saved_wsFlags; /* Original value of pNew->wsFlags */ - WhereCost saved_nOut; /* Original value of pNew->nOut */ + LogEst saved_nOut; /* Original value of pNew->nOut */ int iCol; /* Index of the column in the table */ int rc = SQLITE_OK; /* Return code */ - WhereCost nRowEst; /* Estimated index selectivity */ - WhereCost rLogSize; /* Logarithm of table size */ + LogEst rSize; /* Number of rows in the table */ + LogEst rLogSize; /* Logarithm of table size */ WhereTerm *pTop = 0, *pBtm = 0; /* Top and bottom range constraints */ pNew = pBuilder->pNew; if( db->mallocFailed ) return SQLITE_NOMEM; @@ -110507,135 +120379,190 @@ }else{ opMask = WO_EQ|WO_IN|WO_ISNULL|WO_GT|WO_GE|WO_LT|WO_LE; } if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); - assert( pNew->u.btree.nEq<=pProbe->nColumn ); - if( pNew->u.btree.nEq < pProbe->nColumn ){ - iCol = pProbe->aiColumn[pNew->u.btree.nEq]; - nRowEst = whereCost(pProbe->aiRowEst[pNew->u.btree.nEq+1]); - if( nRowEst==0 && pProbe->onError==OE_None ) nRowEst = 1; - }else{ - iCol = -1; - nRowEst = 0; - } + assert( pNew->u.btree.nEq<pProbe->nColumn ); + iCol = pProbe->aiColumn[pNew->u.btree.nEq]; + pTerm = whereScanInit(&scan, pBuilder->pWC, pSrc->iCursor, iCol, opMask, pProbe); saved_nEq = pNew->u.btree.nEq; + saved_nSkip = pNew->nSkip; saved_nLTerm = pNew->nLTerm; saved_wsFlags = pNew->wsFlags; saved_prereq = pNew->prereq; saved_nOut = pNew->nOut; pNew->rSetup = 0; - rLogSize = estLog(whereCost(pProbe->aiRowEst[0])); + rSize = pProbe->aiRowLogEst[0]; + rLogSize = estLog(rSize); for(; rc==SQLITE_OK && pTerm!=0; pTerm = whereScanNext(&scan)){ + u16 eOp = pTerm->eOperator; /* Shorthand for pTerm->eOperator */ + LogEst rCostIdx; + LogEst nOutUnadjusted; /* nOut before IN() and WHERE adjustments */ int nIn = 0; #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 int nRecValid = pBuilder->nRecValid; #endif - if( (pTerm->eOperator==WO_ISNULL || (pTerm->wtFlags&TERM_VNULL)!=0) + if( (eOp==WO_ISNULL || (pTerm->wtFlags&TERM_VNULL)!=0) && (iCol<0 || pSrc->pTab->aCol[iCol].notNull) ){ continue; /* ignore IS [NOT] NULL constraints on NOT NULL columns */ } if( pTerm->prereqRight & pNew->maskSelf ) continue; - assert( pNew->nOut==saved_nOut ); + /* Do not allow the upper bound of a LIKE optimization range constraint + ** to mix with a lower range bound from some other source */ + if( pTerm->wtFlags & TERM_LIKEOPT && pTerm->eOperator==WO_LT ) continue; pNew->wsFlags = saved_wsFlags; pNew->u.btree.nEq = saved_nEq; pNew->nLTerm = saved_nLTerm; if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ pNew->aLTerm[pNew->nLTerm++] = pTerm; pNew->prereq = (saved_prereq | pTerm->prereqRight) & ~pNew->maskSelf; - pNew->rRun = rLogSize; /* Baseline cost is log2(N). Adjustments below */ - if( pTerm->eOperator & WO_IN ){ + + assert( nInMul==0 + || (pNew->wsFlags & WHERE_COLUMN_NULL)!=0 + || (pNew->wsFlags & WHERE_COLUMN_IN)!=0 + || (pNew->wsFlags & WHERE_SKIPSCAN)!=0 + ); + + if( eOp & WO_IN ){ Expr *pExpr = pTerm->pExpr; pNew->wsFlags |= WHERE_COLUMN_IN; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ /* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */ - nIn = 46; assert( 46==whereCost(25) ); + nIn = 46; assert( 46==sqlite3LogEst(25) ); }else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){ /* "x IN (value, value, ...)" */ - nIn = whereCost(pExpr->x.pList->nExpr); - } - pNew->rRun += nIn; - pNew->u.btree.nEq++; - pNew->nOut = nRowEst + nInMul + nIn; - }else if( pTerm->eOperator & (WO_EQ) ){ - assert( (pNew->wsFlags & (WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0 - || nInMul==0 ); + nIn = sqlite3LogEst(pExpr->x.pList->nExpr); + } + assert( nIn>0 ); /* RHS always has 2 or more terms... The parser + ** changes "x IN (?)" into "x=?". */ + + }else if( eOp & (WO_EQ) ){ pNew->wsFlags |= WHERE_COLUMN_EQ; - if( iCol<0 - || (pProbe->onError!=OE_None && nInMul==0 - && pNew->u.btree.nEq==pProbe->nColumn-1) - ){ - assert( (pNew->wsFlags & WHERE_COLUMN_IN)==0 || iCol<0 ); - pNew->wsFlags |= WHERE_ONEROW; - } - pNew->u.btree.nEq++; - pNew->nOut = nRowEst + nInMul; - }else if( pTerm->eOperator & (WO_ISNULL) ){ + if( iCol<0 || (nInMul==0 && pNew->u.btree.nEq==pProbe->nKeyCol-1) ){ + if( iCol>=0 && !IsUniqueIndex(pProbe) ){ + pNew->wsFlags |= WHERE_UNQ_WANTED; + }else{ + pNew->wsFlags |= WHERE_ONEROW; + } + } + }else if( eOp & WO_ISNULL ){ pNew->wsFlags |= WHERE_COLUMN_NULL; - pNew->u.btree.nEq++; - /* TUNING: IS NULL selects 2 rows */ - nIn = 10; assert( 10==whereCost(2) ); - pNew->nOut = nRowEst + nInMul + nIn; - }else if( pTerm->eOperator & (WO_GT|WO_GE) ){ - testcase( pTerm->eOperator & WO_GT ); - testcase( pTerm->eOperator & WO_GE ); + }else if( eOp & (WO_GT|WO_GE) ){ + testcase( eOp & WO_GT ); + testcase( eOp & WO_GE ); pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT; pBtm = pTerm; pTop = 0; + if( pTerm->wtFlags & TERM_LIKEOPT ){ + /* Range contraints that come from the LIKE optimization are + ** always used in pairs. */ + pTop = &pTerm[1]; + assert( (pTop-(pTerm->pWC->a))<pTerm->pWC->nTerm ); + assert( pTop->wtFlags & TERM_LIKEOPT ); + assert( pTop->eOperator==WO_LT ); + if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ + pNew->aLTerm[pNew->nLTerm++] = pTop; + pNew->wsFlags |= WHERE_TOP_LIMIT; + } }else{ - assert( pTerm->eOperator & (WO_LT|WO_LE) ); - testcase( pTerm->eOperator & WO_LT ); - testcase( pTerm->eOperator & WO_LE ); + assert( eOp & (WO_LT|WO_LE) ); + testcase( eOp & WO_LT ); + testcase( eOp & WO_LE ); pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT; pTop = pTerm; pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ? pNew->aLTerm[pNew->nLTerm-2] : 0; } + + /* At this point pNew->nOut is set to the number of rows expected to + ** be visited by the index scan before considering term pTerm, or the + ** values of nIn and nInMul. In other words, assuming that all + ** "x IN(...)" terms are replaced with "x = ?". This block updates + ** the value of pNew->nOut to account for pTerm (but not nIn/nInMul). */ + assert( pNew->nOut==saved_nOut ); if( pNew->wsFlags & WHERE_COLUMN_RANGE ){ - /* Adjust nOut and rRun for STAT3 range values */ + /* Adjust nOut using stat3/stat4 data. Or, if there is no stat3/stat4 + ** data, using some other estimate. */ + whereRangeScanEst(pParse, pBuilder, pBtm, pTop, pNew); + }else{ + int nEq = ++pNew->u.btree.nEq; + assert( eOp & (WO_ISNULL|WO_EQ|WO_IN) ); + assert( pNew->nOut==saved_nOut ); - whereRangeScanEst(pParse, pBuilder, pBtm, pTop, &pNew->nOut); - } + if( pTerm->truthProb<=0 && iCol>=0 ){ + assert( (eOp & WO_IN) || nIn==0 ); + testcase( eOp & WO_IN ); + pNew->nOut += pTerm->truthProb; + pNew->nOut -= nIn; + }else{ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - if( nInMul==0 - && pProbe->nSample - && pNew->u.btree.nEq<=pProbe->nSampleCol - && OptimizationEnabled(db, SQLITE_Stat3) - ){ - Expr *pExpr = pTerm->pExpr; - tRowcnt nOut = 0; - if( (pTerm->eOperator & (WO_EQ|WO_ISNULL))!=0 ){ - testcase( pTerm->eOperator & WO_EQ ); - testcase( pTerm->eOperator & WO_ISNULL ); - rc = whereEqualScanEst(pParse, pBuilder, pExpr->pRight, &nOut); - }else if( (pTerm->eOperator & WO_IN) - && !ExprHasProperty(pExpr, EP_xIsSelect) ){ - rc = whereInScanEst(pParse, pBuilder, pExpr->x.pList, &nOut); - } - assert( nOut==0 || rc==SQLITE_OK ); - if( nOut ){ - nOut = whereCost(nOut); - pNew->nOut = MIN(nOut, saved_nOut); - } - } -#endif + tRowcnt nOut = 0; + if( nInMul==0 + && pProbe->nSample + && pNew->u.btree.nEq<=pProbe->nSampleCol + && ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect)) + ){ + Expr *pExpr = pTerm->pExpr; + if( (eOp & (WO_EQ|WO_ISNULL))!=0 ){ + testcase( eOp & WO_EQ ); + testcase( eOp & WO_ISNULL ); + rc = whereEqualScanEst(pParse, pBuilder, pExpr->pRight, &nOut); + }else{ + rc = whereInScanEst(pParse, pBuilder, pExpr->x.pList, &nOut); + } + if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK; + if( rc!=SQLITE_OK ) break; /* Jump out of the pTerm loop */ + if( nOut ){ + pNew->nOut = sqlite3LogEst(nOut); + if( pNew->nOut>saved_nOut ) pNew->nOut = saved_nOut; + pNew->nOut -= nIn; + } + } + if( nOut==0 ) +#endif + { + pNew->nOut += (pProbe->aiRowLogEst[nEq] - pProbe->aiRowLogEst[nEq-1]); + if( eOp & WO_ISNULL ){ + /* TUNING: If there is no likelihood() value, assume that a + ** "col IS NULL" expression matches twice as many rows + ** as (col=?). */ + pNew->nOut += 10; + } + } + } + } + + /* Set rCostIdx to the cost of visiting selected rows in index. Add + ** it to pNew->rRun, which is currently set to the cost of the index + ** seek only. Then, if this is a non-covering index, add the cost of + ** visiting the rows in the main table. */ + rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow; + pNew->rRun = sqlite3LogEstAdd(rLogSize, rCostIdx); if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK))==0 ){ - /* Each row involves a step of the index, then a binary search of - ** the main table */ - pNew->rRun = whereCostAdd(pNew->rRun, rLogSize>27 ? rLogSize-17 : 10); + pNew->rRun = sqlite3LogEstAdd(pNew->rRun, pNew->nOut + 16); } - /* Step cost for each output row */ - pNew->rRun = whereCostAdd(pNew->rRun, pNew->nOut); - whereLoopOutputAdjust(pBuilder->pWC, pNew, pSrc->iCursor); + ApplyCostMultiplier(pNew->rRun, pProbe->pTable->costMult); + + nOutUnadjusted = pNew->nOut; + pNew->rRun += nInMul + nIn; + pNew->nOut += nInMul + nIn; + whereLoopOutputAdjust(pBuilder->pWC, pNew, rSize); rc = whereLoopInsert(pBuilder, pNew); + + if( pNew->wsFlags & WHERE_COLUMN_RANGE ){ + pNew->nOut = saved_nOut; + }else{ + pNew->nOut = nOutUnadjusted; + } + if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 - && pNew->u.btree.nEq<(pProbe->nColumn + (pProbe->zName!=0)) + && pNew->u.btree.nEq<pProbe->nColumn ){ whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn); } pNew->nOut = saved_nOut; #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 @@ -110642,13 +120569,49 @@ pBuilder->nRecValid = nRecValid; #endif } pNew->prereq = saved_prereq; pNew->u.btree.nEq = saved_nEq; + pNew->nSkip = saved_nSkip; pNew->wsFlags = saved_wsFlags; pNew->nOut = saved_nOut; pNew->nLTerm = saved_nLTerm; + + /* Consider using a skip-scan if there are no WHERE clause constraints + ** available for the left-most terms of the index, and if the average + ** number of repeats in the left-most terms is at least 18. + ** + ** The magic number 18 is selected on the basis that scanning 17 rows + ** is almost always quicker than an index seek (even though if the index + ** contains fewer than 2^17 rows we assume otherwise in other parts of + ** the code). And, even if it is not, it should not be too much slower. + ** On the other hand, the extra seeks could end up being significantly + ** more expensive. */ + assert( 42==sqlite3LogEst(18) ); + if( saved_nEq==saved_nSkip + && saved_nEq+1<pProbe->nKeyCol + && pProbe->noSkipScan==0 + && pProbe->aiRowLogEst[saved_nEq+1]>=42 /* TUNING: Minimum for skip-scan */ + && (rc = whereLoopResize(db, pNew, pNew->nLTerm+1))==SQLITE_OK + ){ + LogEst nIter; + pNew->u.btree.nEq++; + pNew->nSkip++; + pNew->aLTerm[pNew->nLTerm++] = 0; + pNew->wsFlags |= WHERE_SKIPSCAN; + nIter = pProbe->aiRowLogEst[saved_nEq] - pProbe->aiRowLogEst[saved_nEq+1]; + pNew->nOut -= nIter; + /* TUNING: Because uncertainties in the estimates for skip-scan queries, + ** add a 1.375 fudge factor to make skip-scan slightly less likely. */ + nIter += 5; + whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nIter + nInMul); + pNew->nOut = saved_nOut; + pNew->u.btree.nEq = saved_nEq; + pNew->nSkip = saved_nSkip; + pNew->wsFlags = saved_wsFlags; + } + return rc; } /* ** Return True if it is possible that pIndex might be useful in @@ -110670,11 +120633,12 @@ if( (pOB = pBuilder->pWInfo->pOrderBy)==0 ) return 0; for(ii=0; ii<pOB->nExpr; ii++){ Expr *pExpr = sqlite3ExprSkipCollate(pOB->a[ii].pExpr); if( pExpr->op!=TK_COLUMN ) return 0; if( pExpr->iTable==iCursor ){ - for(jj=0; jj<pIndex->nColumn; jj++){ + if( pExpr->iColumn<0 ) return 1; + for(jj=0; jj<pIndex->nKeyCol; jj++){ if( pExpr->iColumn==pIndex->aiColumn[jj] ) return 1; } } } return 0; @@ -110687,14 +120651,15 @@ static Bitmask columnsInIndex(Index *pIdx){ Bitmask m = 0; int j; for(j=pIdx->nColumn-1; j>=0; j--){ int x = pIdx->aiColumn[j]; - assert( x>=0 ); - testcase( x==BMS-1 ); - testcase( x==BMS-2 ); - if( x<BMS-1 ) m |= MASKBIT(x); + if( x>=0 ){ + testcase( x==BMS-1 ); + testcase( x==BMS-2 ); + if( x<BMS-1 ) m |= MASKBIT(x); + } } return m; } /* Check to see if a partial index with pPartIndexWhere can be used @@ -110702,103 +120667,158 @@ */ static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){ int i; WhereTerm *pTerm; for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){ - if( sqlite3ExprImpliesExpr(pTerm->pExpr, pWhere, iTab) ) return 1; + Expr *pExpr = pTerm->pExpr; + if( sqlite3ExprImpliesExpr(pExpr, pWhere, iTab) + && (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->iRightJoinTable==iTab) + ){ + return 1; + } } return 0; } /* ** Add all WhereLoop objects for a single table of the join where the table ** is idenfied by pBuilder->pNew->iTab. That table is guaranteed to be ** a b-tree table, not a virtual table. +** +** The costs (WhereLoop.rRun) of the b-tree loops added by this function +** are calculated as follows: +** +** For a full scan, assuming the table (or index) contains nRow rows: +** +** cost = nRow * 3.0 // full-table scan +** cost = nRow * K // scan of covering index +** cost = nRow * (K+3.0) // scan of non-covering index +** +** where K is a value between 1.1 and 3.0 set based on the relative +** estimated average size of the index and table records. +** +** For an index scan, where nVisit is the number of index rows visited +** by the scan, and nSeek is the number of seek operations required on +** the index b-tree: +** +** cost = nSeek * (log(nRow) + K * nVisit) // covering index +** cost = nSeek * (log(nRow) + (K+3.0) * nVisit) // non-covering index +** +** Normally, nSeek is 1. nSeek values greater than 1 come about if the +** WHERE clause includes "x IN (....)" terms used in place of "x=?". Or when +** implicit "x IN (SELECT x FROM tbl)" terms are added for skip-scans. +** +** The estimated values (nRow, nVisit, nSeek) often contain a large amount +** of uncertainty. For this reason, scoring is designed to pick plans that +** "do the least harm" if the estimates are inaccurate. For example, a +** log(nRow) factor is omitted from a non-covering index scan in order to +** bias the scoring in favor of using an index, since the worst-case +** performance of using an index is far better than the worst-case performance +** of a full table scan. */ static int whereLoopAddBtree( WhereLoopBuilder *pBuilder, /* WHERE clause information */ Bitmask mExtra /* Extra prerequesites for using this table */ ){ WhereInfo *pWInfo; /* WHERE analysis context */ Index *pProbe; /* An index we are evaluating */ Index sPk; /* A fake index object for the primary key */ - tRowcnt aiRowEstPk[2]; /* The aiRowEst[] value for the sPk index */ - int aiColumnPk = -1; /* The aColumn[] value for the sPk index */ + LogEst aiRowEstPk[2]; /* The aiRowLogEst[] value for the sPk index */ + i16 aiColumnPk = -1; /* The aColumn[] value for the sPk index */ SrcList *pTabList; /* The FROM clause */ struct SrcList_item *pSrc; /* The FROM clause btree term to add */ WhereLoop *pNew; /* Template WhereLoop object */ int rc = SQLITE_OK; /* Return code */ int iSortIdx = 1; /* Index number */ int b; /* A boolean value */ - WhereCost rSize; /* number of rows in the table */ - WhereCost rLogSize; /* Logarithm of the number of rows in the table */ + LogEst rSize; /* number of rows in the table */ + LogEst rLogSize; /* Logarithm of the number of rows in the table */ WhereClause *pWC; /* The parsed WHERE clause */ + Table *pTab; /* Table being queried */ pNew = pBuilder->pNew; pWInfo = pBuilder->pWInfo; pTabList = pWInfo->pTabList; pSrc = pTabList->a + pNew->iTab; + pTab = pSrc->pTab; pWC = pBuilder->pWC; assert( !IsVirtual(pSrc->pTab) ); if( pSrc->pIndex ){ /* An INDEXED BY clause specifies a particular index to use */ pProbe = pSrc->pIndex; + }else if( !HasRowid(pTab) ){ + pProbe = pTab->pIndex; }else{ /* There is no INDEXED BY clause. Create a fake Index object in local ** variable sPk to represent the rowid primary key index. Make this ** fake index the first in a chain of Index objects with all of the real ** indices to follow */ Index *pFirst; /* First of real indices on the table */ memset(&sPk, 0, sizeof(Index)); + sPk.nKeyCol = 1; sPk.nColumn = 1; sPk.aiColumn = &aiColumnPk; - sPk.aiRowEst = aiRowEstPk; + sPk.aiRowLogEst = aiRowEstPk; sPk.onError = OE_Replace; - sPk.pTable = pSrc->pTab; - aiRowEstPk[0] = pSrc->pTab->nRowEst; - aiRowEstPk[1] = 1; + sPk.pTable = pTab; + sPk.szIdxRow = pTab->szTabRow; + aiRowEstPk[0] = pTab->nRowLogEst; + aiRowEstPk[1] = 0; pFirst = pSrc->pTab->pIndex; if( pSrc->notIndexed==0 ){ /* The real indices of the table are only considered if the ** NOT INDEXED qualifier is omitted from the FROM clause */ sPk.pNext = pFirst; } pProbe = &sPk; } - rSize = whereCost(pSrc->pTab->nRowEst); + rSize = pTab->nRowLogEst; rLogSize = estLog(rSize); #ifndef SQLITE_OMIT_AUTOMATIC_INDEX /* Automatic indexes */ if( !pBuilder->pOrSet + && (pWInfo->wctrlFlags & WHERE_NO_AUTOINDEX)==0 && (pWInfo->pParse->db->flags & SQLITE_AutoIndex)!=0 && pSrc->pIndex==0 && !pSrc->viaCoroutine && !pSrc->notIndexed + && HasRowid(pTab) && !pSrc->isCorrelated + && !pSrc->isRecursive ){ /* Generate auto-index WhereLoops */ WhereTerm *pTerm; WhereTerm *pWCEnd = pWC->a + pWC->nTerm; for(pTerm=pWC->a; rc==SQLITE_OK && pTerm<pWCEnd; pTerm++){ if( pTerm->prereqRight & pNew->maskSelf ) continue; if( termCanDriveIndex(pTerm, pSrc, 0) ){ pNew->u.btree.nEq = 1; + pNew->nSkip = 0; pNew->u.btree.pIndex = 0; pNew->nLTerm = 1; pNew->aLTerm[0] = pTerm; /* TUNING: One-time cost for computing the automatic index is - ** approximately 7*N*log2(N) where N is the number of rows in - ** the table being indexed. */ - pNew->rSetup = rLogSize + rSize + 28; assert( 28==whereCost(7) ); + ** estimated to be X*N*log2(N) where N is the number of rows in + ** the table being indexed and where X is 7 (LogEst=28) for normal + ** tables or 1.375 (LogEst=4) for views and subqueries. The value + ** of X is smaller for views and subqueries so that the query planner + ** will be more aggressive about generating automatic indexes for + ** those objects, since there is no opportunity to add schema + ** indexes on subqueries and views. */ + pNew->rSetup = rLogSize + rSize + 4; + if( pTab->pSelect==0 && (pTab->tabFlags & TF_Ephemeral)==0 ){ + pNew->rSetup += 24; + } + ApplyCostMultiplier(pNew->rSetup, pTab->costMult); /* TUNING: Each index lookup yields 20 rows in the table. This ** is more than the usual guess of 10 rows, since we have no way - ** of knowning how selective the index will ultimately be. It would + ** of knowing how selective the index will ultimately be. It would ** not be unreasonable to make this value much larger. */ - pNew->nOut = 43; assert( 43==whereCost(20) ); - pNew->rRun = whereCostAdd(rLogSize,pNew->nOut); + pNew->nOut = 43; assert( 43==sqlite3LogEst(20) ); + pNew->rRun = sqlite3LogEstAdd(rLogSize,pNew->nOut); pNew->wsFlags = WHERE_AUTO_INDEX; pNew->prereq = mExtra | pTerm->prereqRight; rc = whereLoopInsert(pBuilder, pNew); } } @@ -110807,14 +120827,17 @@ /* Loop over all indices */ for(; rc==SQLITE_OK && pProbe; pProbe=pProbe->pNext, iSortIdx++){ if( pProbe->pPartIdxWhere!=0 - && !whereUsablePartialIndex(pNew->iTab, pWC, pProbe->pPartIdxWhere) ){ + && !whereUsablePartialIndex(pSrc->iCursor, pWC, pProbe->pPartIdxWhere) ){ + testcase( pNew->iTab!=pSrc->iCursor ); /* See ticket [98d973b8f5] */ continue; /* Partial index inappropriate for this query */ } + rSize = pProbe->aiRowLogEst[0]; pNew->u.btree.nEq = 0; + pNew->nSkip = 0; pNew->nLTerm = 0; pNew->iSortIdx = 0; pNew->rSetup = 0; pNew->prereq = mExtra; pNew->nOut = rSize; @@ -110826,51 +120849,50 @@ /* Integer primary key index */ pNew->wsFlags = WHERE_IPK; /* Full table scan */ pNew->iSortIdx = b ? iSortIdx : 0; - /* TUNING: Cost of full table scan is 3*(N + log2(N)). - ** + The extra 3 factor is to encourage the use of indexed lookups - ** over full scans. A smaller constant 2 is used for covering - ** index scans so that a covering index scan will be favored over - ** a table scan. */ - pNew->rRun = whereCostAdd(rSize,rLogSize) + 16; - whereLoopOutputAdjust(pWC, pNew, pSrc->iCursor); + /* TUNING: Cost of full table scan is (N*3.0). */ + pNew->rRun = rSize + 16; + ApplyCostMultiplier(pNew->rRun, pTab->costMult); + whereLoopOutputAdjust(pWC, pNew, rSize); rc = whereLoopInsert(pBuilder, pNew); pNew->nOut = rSize; if( rc ) break; }else{ - Bitmask m = pSrc->colUsed & ~columnsInIndex(pProbe); - pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED; + Bitmask m; + if( pProbe->isCovering ){ + pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; + m = 0; + }else{ + m = pSrc->colUsed & ~columnsInIndex(pProbe); + pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED; + } /* Full scan via index */ if( b + || !HasRowid(pTab) || ( m==0 && pProbe->bUnordered==0 + && (pProbe->szIdxRow<pTab->szTabRow) && (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 && sqlite3GlobalConfig.bUseCis && OptimizationEnabled(pWInfo->pParse->db, SQLITE_CoverIdxScan) ) ){ pNew->iSortIdx = b ? iSortIdx : 0; - if( m==0 ){ - /* TUNING: Cost of a covering index scan is 2*(N + log2(N)). - ** + The extra 2 factor is to encourage the use of indexed lookups - ** over index scans. A table scan uses a factor of 3 so that - ** index scans are favored over table scans. - ** + If this covering index might also help satisfy the ORDER BY - ** clause, then the cost is fudged down slightly so that this - ** index is favored above other indices that have no hope of - ** helping with the ORDER BY. */ - pNew->rRun = 10 + whereCostAdd(rSize,rLogSize) - b; - }else{ - assert( b!=0 ); - /* TUNING: Cost of scanning a non-covering index is (N+1)*log2(N) - ** which we will simplify to just N*log2(N) */ - pNew->rRun = rSize + rLogSize; - } - whereLoopOutputAdjust(pWC, pNew, pSrc->iCursor); + + /* The cost of visiting the index rows is N*K, where K is + ** between 1.1 and 3.0, depending on the relative sizes of the + ** index and table rows. If this is a non-covering index scan, + ** also add the cost of visiting table rows (N*3.0). */ + pNew->rRun = rSize + 1 + (15*pProbe->szIdxRow)/pTab->szTabRow; + if( m!=0 ){ + pNew->rRun = sqlite3LogEstAdd(pNew->rRun, rSize+16); + } + ApplyCostMultiplier(pNew->rRun, pTab->costMult); + whereLoopOutputAdjust(pWC, pNew, rSize); rc = whereLoopInsert(pBuilder, pNew); pNew->nOut = rSize; if( rc ) break; } } @@ -110893,11 +120915,12 @@ /* ** Add all WhereLoop objects for a table of the join identified by ** pBuilder->pNew->iTab. That table is guaranteed to be a virtual table. */ static int whereLoopAddVirtual( - WhereLoopBuilder *pBuilder /* WHERE clause information */ + WhereLoopBuilder *pBuilder, /* WHERE clause information */ + Bitmask mExtra ){ WhereInfo *pWInfo; /* WHERE analysis context */ Parse *pParse; /* The parsing context */ WhereClause *pWC; /* The WHERE clause */ struct SrcList_item *pSrc; /* The FROM clause term to search */ @@ -110979,14 +121002,15 @@ pIdxInfo->idxStr = 0; pIdxInfo->idxNum = 0; pIdxInfo->needToFreeIdxStr = 0; pIdxInfo->orderByConsumed = 0; pIdxInfo->estimatedCost = SQLITE_BIG_DBL / (double)2; + pIdxInfo->estimatedRows = 25; rc = vtabBestIndex(pParse, pTab, pIdxInfo); if( rc ) goto whereLoopAddVtab_exit; pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; - pNew->prereq = 0; + pNew->prereq = mExtra; mxTerm = -1; assert( pNew->nLSlot>=nConstraint ); for(i=0; i<nConstraint; i++) pNew->aLTerm[i] = 0; pNew->u.vtab.omitMask = 0; for(i=0; i<nConstraint; i++, pIdxCons++){ @@ -111034,16 +121058,15 @@ assert( pNew->nLTerm<=pNew->nLSlot ); pNew->u.vtab.idxNum = pIdxInfo->idxNum; pNew->u.vtab.needFree = pIdxInfo->needToFreeIdxStr; pIdxInfo->needToFreeIdxStr = 0; pNew->u.vtab.idxStr = pIdxInfo->idxStr; - pNew->u.vtab.isOrdered = (u8)((pIdxInfo->nOrderBy!=0) - && pIdxInfo->orderByConsumed); + pNew->u.vtab.isOrdered = (i8)(pIdxInfo->orderByConsumed ? + pIdxInfo->nOrderBy : 0); pNew->rSetup = 0; - pNew->rRun = whereCostFromDouble(pIdxInfo->estimatedCost); - /* TUNING: Every virtual table query returns 25 rows */ - pNew->nOut = 46; assert( 46==whereCost(25) ); + pNew->rRun = sqlite3LogEstFromDouble(pIdxInfo->estimatedCost); + pNew->nOut = sqlite3LogEst(pIdxInfo->estimatedRows); whereLoopInsert(pBuilder, pNew); if( pNew->u.vtab.needFree ){ sqlite3_free(pNew->u.vtab.idxStr); pNew->u.vtab.needFree = 0; } @@ -111068,18 +121091,19 @@ WhereTerm *pTerm, *pWCEnd; int rc = SQLITE_OK; int iCur; WhereClause tempWC; WhereLoopBuilder sSubBuild; - WhereOrSet sSum, sCur, sPrev; + WhereOrSet sSum, sCur; struct SrcList_item *pItem; pWC = pBuilder->pWC; - if( pWInfo->wctrlFlags & WHERE_AND_ONLY ) return SQLITE_OK; pWCEnd = pWC->a + pWC->nTerm; pNew = pBuilder->pNew; memset(&sSum, 0, sizeof(sSum)); + pItem = pWInfo->pTabList->a + pNew->iTab; + iCur = pItem->iCursor; for(pTerm=pWC->a; pTerm<pWCEnd && rc==SQLITE_OK; pTerm++){ if( (pTerm->eOperator & WO_OR)!=0 && (pTerm->u.pOrInfo->indexable & pNew->maskSelf)!=0 ){ @@ -111087,16 +121111,15 @@ WhereTerm * const pOrWCEnd = &pOrWC->a[pOrWC->nTerm]; WhereTerm *pOrTerm; int once = 1; int i, j; - pItem = pWInfo->pTabList->a + pNew->iTab; - iCur = pItem->iCursor; sSubBuild = *pBuilder; sSubBuild.pOrderBy = 0; sSubBuild.pOrSet = &sCur; + WHERETRACE(0x200, ("Begin processing OR-clause %p\n", pTerm)); for(pOrTerm=pOrWC->a; pOrTerm<pOrWCEnd; pOrTerm++){ if( (pOrTerm->eOperator & WO_AND)!=0 ){ sSubBuild.pWC = &pOrTerm->u.pAndInfo->wc; }else if( pOrTerm->leftCursor==iCur ){ tempWC.pWInfo = pWC->pWInfo; @@ -111107,34 +121130,46 @@ sSubBuild.pWC = &tempWC; }else{ continue; } sCur.n = 0; +#ifdef WHERETRACE_ENABLED + WHERETRACE(0x200, ("OR-term %d of %p has %d subterms:\n", + (int)(pOrTerm-pOrWC->a), pTerm, sSubBuild.pWC->nTerm)); + if( sqlite3WhereTrace & 0x400 ){ + for(i=0; i<sSubBuild.pWC->nTerm; i++){ + whereTermPrint(&sSubBuild.pWC->a[i], i); + } + } +#endif #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pItem->pTab) ){ - rc = whereLoopAddVirtual(&sSubBuild); - for(i=0; i<sCur.n; i++) sCur.a[i].prereq |= mExtra; + rc = whereLoopAddVirtual(&sSubBuild, mExtra); }else #endif { rc = whereLoopAddBtree(&sSubBuild, mExtra); } + if( rc==SQLITE_OK ){ + rc = whereLoopAddOr(&sSubBuild, mExtra); + } assert( rc==SQLITE_OK || sCur.n==0 ); if( sCur.n==0 ){ sSum.n = 0; break; }else if( once ){ whereOrMove(&sSum, &sCur); once = 0; }else{ + WhereOrSet sPrev; whereOrMove(&sPrev, &sSum); sSum.n = 0; for(i=0; i<sPrev.n; i++){ for(j=0; j<sCur.n; j++){ whereOrInsert(&sSum, sPrev.a[i].prereq | sCur.a[j].prereq, - whereCostAdd(sPrev.a[i].rRun, sCur.a[j].rRun), - whereCostAdd(sPrev.a[i].nOut, sCur.a[j].nOut)); + sqlite3LogEstAdd(sPrev.a[i].rRun, sCur.a[j].rRun), + sqlite3LogEstAdd(sPrev.a[i].nOut, sCur.a[j].nOut)); } } } } pNew->nLTerm = 1; @@ -111142,16 +121177,28 @@ pNew->wsFlags = WHERE_MULTI_OR; pNew->rSetup = 0; pNew->iSortIdx = 0; memset(&pNew->u, 0, sizeof(pNew->u)); for(i=0; rc==SQLITE_OK && i<sSum.n; i++){ - /* TUNING: Multiple by 3.5 for the secondary table lookup */ - pNew->rRun = sSum.a[i].rRun + 18; + /* TUNING: Currently sSum.a[i].rRun is set to the sum of the costs + ** of all sub-scans required by the OR-scan. However, due to rounding + ** errors, it may be that the cost of the OR-scan is equal to its + ** most expensive sub-scan. Add the smallest possible penalty + ** (equivalent to multiplying the cost by 1.07) to ensure that + ** this does not happen. Otherwise, for WHERE clauses such as the + ** following where there is an index on "y": + ** + ** WHERE likelihood(x=?, 0.99) OR y=? + ** + ** the planner may elect to "OR" together a full-table scan and an + ** index lookup. And other similarly odd results. */ + pNew->rRun = sSum.a[i].rRun + 1; pNew->nOut = sSum.a[i].nOut; pNew->prereq = sSum.a[i].prereq; rc = whereLoopInsert(pBuilder, pNew); } + WHERETRACE(0x200, ("End processing OR-clause %p\n", pTerm)); } } return rc; } @@ -111180,11 +121227,11 @@ if( ((pItem->jointype|priorJoinType) & (JT_LEFT|JT_CROSS))!=0 ){ mExtra = mPrior; } priorJoinType = pItem->jointype; if( IsVirtual(pItem->pTab) ){ - rc = whereLoopAddVirtual(pBuilder); + rc = whereLoopAddVirtual(pBuilder, mExtra); }else{ rc = whereLoopAddBtree(pBuilder, mExtra); } if( rc==SQLITE_OK ){ rc = whereLoopAddOr(pBuilder, mExtra); @@ -111197,25 +121244,25 @@ } /* ** Examine a WherePath (with the addition of the extra WhereLoop of the 5th ** parameters) to see if it outputs rows in the requested ORDER BY -** (or GROUP BY) without requiring a separate sort operation. Return: +** (or GROUP BY) without requiring a separate sort operation. Return N: ** -** 0: ORDER BY is not satisfied. Sorting required -** 1: ORDER BY is satisfied. Omit sorting -** -1: Unknown at this time +** N>0: N terms of the ORDER BY clause are satisfied +** N==0: No terms of the ORDER BY clause are satisfied +** N<0: Unknown yet how many terms of ORDER BY might be satisfied. ** ** Note that processing for WHERE_GROUPBY and WHERE_DISTINCTBY is not as ** strict. With GROUP BY and DISTINCT the only requirement is that ** equivalent rows appear immediately adjacent to one another. GROUP BY -** and DISTINT do not require rows to appear in any particular order as long -** as equivelent rows are grouped together. Thus for GROUP BY and DISTINCT +** and DISTINCT do not require rows to appear in any particular order as long +** as equivalent rows are grouped together. Thus for GROUP BY and DISTINCT ** the pOrderBy terms can be matched in any order. With ORDER BY, the ** pOrderBy terms must be matched in strict left-to-right order. */ -static int wherePathSatisfiesOrderBy( +static i8 wherePathSatisfiesOrderBy( WhereInfo *pWInfo, /* The WHERE clause */ ExprList *pOrderBy, /* ORDER BY or GROUP BY or DISTINCT clause to check */ WherePath *pPath, /* The WherePath to check */ u16 wctrlFlags, /* Might contain WHERE_GROUPBY or WHERE_DISTINCTBY */ u16 nLoop, /* Number of entries in pPath->aLoop[] */ @@ -111226,11 +121273,12 @@ u8 rev; /* Composite sort order */ u8 revIdx; /* Index sort order */ u8 isOrderDistinct; /* All prior WhereLoops are order-distinct */ u8 distinctColumns; /* True if the loop has UNIQUE NOT NULL columns */ u8 isMatch; /* iColumn matches a term of the ORDER BY clause */ - u16 nColumn; /* Number of columns in pIndex */ + u16 nKeyCol; /* Number of key columns in pIndex */ + u16 nColumn; /* Total number of ordered columns in the index */ u16 nOrderBy; /* Number terms in the ORDER BY clause */ int iLoop; /* Index of WhereLoop in pPath being processed */ int i, j; /* Loop counters */ int iCur; /* Cursor number for current WhereLoop */ int iColumn; /* A column number within table iCur */ @@ -111266,18 +121314,10 @@ ** rowid appears in the ORDER BY clause, the corresponding WhereLoop is ** automatically order-distinct. */ assert( pOrderBy!=0 ); - - /* Sortability of virtual tables is determined by the xBestIndex method - ** of the virtual table itself */ - if( pLast->wsFlags & WHERE_VIRTUALTABLE ){ - testcase( nLoop>0 ); /* True when outer loops are one-row and match - ** no ORDER BY terms */ - return pLast->u.vtab.isOrdered; - } if( nLoop && OptimizationDisabled(db, SQLITE_OrderByIdxJoin) ) return 0; nOrderBy = pOrderBy->nExpr; testcase( nOrderBy==BMS-1 ); if( nOrderBy>BMS-1 ) return 0; /* Cannot optimize overly large ORDER BYs */ @@ -111286,11 +121326,14 @@ orderDistinctMask = 0; ready = 0; for(iLoop=0; isOrderDistinct && obSat<obDone && iLoop<=nLoop; iLoop++){ if( iLoop>0 ) ready |= pLoop->maskSelf; pLoop = iLoop<nLoop ? pPath->aLoop[iLoop] : pLast; - assert( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ); + if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ + if( pLoop->u.vtab.isOrdered ) obSat = obDone; + break; + } iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor; /* Mark off any ORDER BY term X that is a column in the table of ** the current loop for which there is term in the WHERE ** clause of the form X IS NULL or X=? that reference only outer @@ -111318,28 +121361,33 @@ } if( (pLoop->wsFlags & WHERE_ONEROW)==0 ){ if( pLoop->wsFlags & WHERE_IPK ){ pIndex = 0; - nColumn = 0; + nKeyCol = 0; + nColumn = 1; }else if( (pIndex = pLoop->u.btree.pIndex)==0 || pIndex->bUnordered ){ return 0; }else{ + nKeyCol = pIndex->nKeyCol; nColumn = pIndex->nColumn; - isOrderDistinct = pIndex->onError!=OE_None; + assert( nColumn==nKeyCol+1 || !HasRowid(pIndex->pTable) ); + assert( pIndex->aiColumn[nColumn-1]==(-1) || !HasRowid(pIndex->pTable)); + isOrderDistinct = IsUniqueIndex(pIndex); } /* Loop through all columns of the index and deal with the ones ** that are not constrained by == or IN. */ rev = revSet = 0; distinctColumns = 0; - for(j=0; j<=nColumn; j++){ + for(j=0; j<nColumn; j++){ u8 bOnce; /* True to run the ORDER BY search loop */ /* Skip over == and IS NULL terms */ if( j<pLoop->u.btree.nEq + && pLoop->nSkip==0 && ((i = pLoop->aLTerm[j]->eOperator) & (WO_EQ|WO_ISNULL))!=0 ){ if( i & WO_ISNULL ){ testcase( isOrderDistinct ); isOrderDistinct = 0; @@ -111348,24 +121396,21 @@ } /* Get the column number in the table (iColumn) and sort order ** (revIdx) for the j-th column of the index. */ - if( j<nColumn ){ - /* Normal index columns */ + if( pIndex ){ iColumn = pIndex->aiColumn[j]; revIdx = pIndex->aSortOrder[j]; if( iColumn==pIndex->pTable->iPKey ) iColumn = -1; }else{ - /* The ROWID column at the end */ - assert( j==nColumn ); iColumn = -1; revIdx = 0; } /* An unconstrained column that might be NULL means that this - ** WhereLoop is not well-ordered + ** WhereLoop is not well-ordered */ if( isOrderDistinct && iColumn>=0 && j>=pLoop->u.btree.nEq && pIndex->pTable->aCol[iColumn].notNull==0 @@ -111372,11 +121417,11 @@ ){ isOrderDistinct = 0; } /* Find the ORDER BY term that corresponds to the j-th column - ** of the index and and mark that ORDER BY term off + ** of the index and mark that ORDER BY term off */ bOnce = 1; isMatch = 0; for(i=0; bOnce && i<nOrderBy; i++){ if( MASKBIT(i) & obSat ) continue; @@ -111392,31 +121437,31 @@ if( !pColl ) pColl = db->pDfltColl; if( sqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ) continue; } isMatch = 1; break; + } + if( isMatch && (wctrlFlags & WHERE_GROUPBY)==0 ){ + /* Make sure the sort order is compatible in an ORDER BY clause. + ** Sort order is irrelevant for a GROUP BY clause. */ + if( revSet ){ + if( (rev ^ revIdx)!=pOrderBy->a[i].sortOrder ) isMatch = 0; + }else{ + rev = revIdx ^ pOrderBy->a[i].sortOrder; + if( rev ) *pRevMask |= MASKBIT(iLoop); + revSet = 1; + } } if( isMatch ){ if( iColumn<0 ){ testcase( distinctColumns==0 ); distinctColumns = 1; } obSat |= MASKBIT(i); - if( (pWInfo->wctrlFlags & WHERE_GROUPBY)==0 ){ - /* Make sure the sort order is compatible in an ORDER BY clause. - ** Sort order is irrelevant for a GROUP BY clause. */ - if( revSet ){ - if( (rev ^ revIdx)!=pOrderBy->a[i].sortOrder ) return 0; - }else{ - rev = revIdx ^ pOrderBy->a[i].sortOrder; - if( rev ) *pRevMask |= MASKBIT(iLoop); - revSet = 1; - } - } }else{ /* No match found */ - if( j==0 || j<nColumn ){ + if( j==0 || j<nKeyCol ){ testcase( isOrderDistinct!=0 ); isOrderDistinct = 0; } break; } @@ -111430,23 +121475,62 @@ /* Mark off any other ORDER BY terms that reference pLoop */ if( isOrderDistinct ){ orderDistinctMask |= pLoop->maskSelf; for(i=0; i<nOrderBy; i++){ Expr *p; + Bitmask mTerm; if( MASKBIT(i) & obSat ) continue; p = pOrderBy->a[i].pExpr; - if( (exprTableUsage(&pWInfo->sMaskSet, p)&~orderDistinctMask)==0 ){ + mTerm = exprTableUsage(&pWInfo->sMaskSet,p); + if( mTerm==0 && !sqlite3ExprIsConstant(p) ) continue; + if( (mTerm&~orderDistinctMask)==0 ){ obSat |= MASKBIT(i); } } } } /* End the loop over all WhereLoops from outer-most down to inner-most */ - if( obSat==obDone ) return 1; - if( !isOrderDistinct ) return 0; + if( obSat==obDone ) return (i8)nOrderBy; + if( !isOrderDistinct ){ + for(i=nOrderBy-1; i>0; i--){ + Bitmask m = MASKBIT(i) - 1; + if( (obSat&m)==m ) return i; + } + return 0; + } return -1; } + +/* +** If the WHERE_GROUPBY flag is set in the mask passed to sqlite3WhereBegin(), +** the planner assumes that the specified pOrderBy list is actually a GROUP +** BY clause - and so any order that groups rows as required satisfies the +** request. +** +** Normally, in this case it is not possible for the caller to determine +** whether or not the rows are really being delivered in sorted order, or +** just in some other order that provides the required grouping. However, +** if the WHERE_SORTBYGROUP flag is also passed to sqlite3WhereBegin(), then +** this function may be called on the returned WhereInfo object. It returns +** true if the rows really will be sorted in the specified order, or false +** otherwise. +** +** For example, assuming: +** +** CREATE INDEX i1 ON t1(x, Y); +** +** then +** +** SELECT * FROM t1 GROUP BY x,y ORDER BY x,y; -- IsSorted()==1 +** SELECT * FROM t1 GROUP BY y,x ORDER BY y,x; -- IsSorted()==0 +*/ +SQLITE_PRIVATE int sqlite3WhereIsSorted(WhereInfo *pWInfo){ + assert( pWInfo->wctrlFlags & WHERE_GROUPBY ); + assert( pWInfo->wctrlFlags & WHERE_SORTBYGROUP ); + return pWInfo->sorted; +} + #ifdef WHERETRACE_ENABLED /* For debugging use only: */ static const char *wherePathName(WherePath *pPath, int nLoop, WhereLoop *pLast){ static char zName[65]; int i; @@ -111455,10 +121539,48 @@ zName[i] = 0; return zName; } #endif +/* +** Return the cost of sorting nRow rows, assuming that the keys have +** nOrderby columns and that the first nSorted columns are already in +** order. +*/ +static LogEst whereSortingCost( + WhereInfo *pWInfo, + LogEst nRow, + int nOrderBy, + int nSorted +){ + /* TUNING: Estimated cost of a full external sort, where N is + ** the number of rows to sort is: + ** + ** cost = (3.0 * N * log(N)). + ** + ** Or, if the order-by clause has X terms but only the last Y + ** terms are out of order, then block-sorting will reduce the + ** sorting cost to: + ** + ** cost = (3.0 * N * log(N)) * (Y/X) + ** + ** The (Y/X) term is implemented using stack variable rScale + ** below. */ + LogEst rScale, rSortCost; + assert( nOrderBy>0 && 66==sqlite3LogEst(100) ); + rScale = sqlite3LogEst((nOrderBy-nSorted)*100/nOrderBy) - 66; + rSortCost = nRow + estLog(nRow) + rScale + 16; + + /* TUNING: The cost of implementing DISTINCT using a B-TREE is + ** similar but with a larger constant of proportionality. + ** Multiply by an additional factor of 3.0. */ + if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){ + rSortCost += 16; + } + + return rSortCost; +} /* ** Given the list of WhereLoop objects at pWInfo->pLoops, this routine ** attempts to find the lowest cost path that visits each WhereLoop ** once. This path is then loaded into the pWInfo->a[].pWLoop fields. @@ -111468,212 +121590,264 @@ ** costs if nRowEst==0. ** ** Return SQLITE_OK on success or SQLITE_NOMEM of a memory allocation ** error occurs. */ -static int wherePathSolver(WhereInfo *pWInfo, WhereCost nRowEst){ +static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ int mxChoice; /* Maximum number of simultaneous paths tracked */ int nLoop; /* Number of terms in the join */ Parse *pParse; /* Parsing context */ sqlite3 *db; /* The database connection */ int iLoop; /* Loop counter over the terms of the join */ int ii, jj; /* Loop counters */ int mxI = 0; /* Index of next entry to replace */ - WhereCost rCost; /* Cost of a path */ - WhereCost nOut; /* Number of outputs */ - WhereCost mxCost = 0; /* Maximum cost of a set of paths */ - WhereCost mxOut = 0; /* Maximum nOut value on the set of paths */ - WhereCost rSortCost; /* Cost to do a sort */ + int nOrderBy; /* Number of ORDER BY clause terms */ + LogEst mxCost = 0; /* Maximum cost of a set of paths */ + LogEst mxUnsorted = 0; /* Maximum unsorted cost of a set of path */ int nTo, nFrom; /* Number of valid entries in aTo[] and aFrom[] */ WherePath *aFrom; /* All nFrom paths at the previous level */ WherePath *aTo; /* The nTo best paths at the current level */ WherePath *pFrom; /* An element of aFrom[] that we are working on */ WherePath *pTo; /* An element of aTo[] that we are working on */ WhereLoop *pWLoop; /* One of the WhereLoop objects */ WhereLoop **pX; /* Used to divy up the pSpace memory */ + LogEst *aSortCost = 0; /* Sorting and partial sorting costs */ char *pSpace; /* Temporary memory used by this routine */ + int nSpace; /* Bytes of space allocated at pSpace */ pParse = pWInfo->pParse; db = pParse->db; nLoop = pWInfo->nLevel; /* TUNING: For simple queries, only the best path is tracked. ** For 2-way joins, the 5 best paths are followed. ** For joins of 3 or more tables, track the 10 best paths */ - mxChoice = (nLoop==1) ? 1 : (nLoop==2 ? 5 : 10); + mxChoice = (nLoop<=1) ? 1 : (nLoop==2 ? 5 : 10); assert( nLoop<=pWInfo->pTabList->nSrc ); - WHERETRACE(0x002, ("---- begin solver\n")); + WHERETRACE(0x002, ("---- begin solver. (nRowEst=%d)\n", nRowEst)); - /* Allocate and initialize space for aTo and aFrom */ - ii = (sizeof(WherePath)+sizeof(WhereLoop*)*nLoop)*mxChoice*2; - pSpace = sqlite3DbMallocRaw(db, ii); + /* If nRowEst is zero and there is an ORDER BY clause, ignore it. In this + ** case the purpose of this call is to estimate the number of rows returned + ** by the overall query. Once this estimate has been obtained, the caller + ** will invoke this function a second time, passing the estimate as the + ** nRowEst parameter. */ + if( pWInfo->pOrderBy==0 || nRowEst==0 ){ + nOrderBy = 0; + }else{ + nOrderBy = pWInfo->pOrderBy->nExpr; + } + + /* Allocate and initialize space for aTo, aFrom and aSortCost[] */ + nSpace = (sizeof(WherePath)+sizeof(WhereLoop*)*nLoop)*mxChoice*2; + nSpace += sizeof(LogEst) * nOrderBy; + pSpace = sqlite3DbMallocRaw(db, nSpace); if( pSpace==0 ) return SQLITE_NOMEM; aTo = (WherePath*)pSpace; aFrom = aTo+mxChoice; memset(aFrom, 0, sizeof(aFrom[0])); pX = (WhereLoop**)(aFrom+mxChoice); for(ii=mxChoice*2, pFrom=aTo; ii>0; ii--, pFrom++, pX += nLoop){ pFrom->aLoop = pX; } + if( nOrderBy ){ + /* If there is an ORDER BY clause and it is not being ignored, set up + ** space for the aSortCost[] array. Each element of the aSortCost array + ** is either zero - meaning it has not yet been initialized - or the + ** cost of sorting nRowEst rows of data where the first X terms of + ** the ORDER BY clause are already in order, where X is the array + ** index. */ + aSortCost = (LogEst*)pX; + memset(aSortCost, 0, sizeof(LogEst) * nOrderBy); + } + assert( aSortCost==0 || &pSpace[nSpace]==(char*)&aSortCost[nOrderBy] ); + assert( aSortCost!=0 || &pSpace[nSpace]==(char*)pX ); /* Seed the search with a single WherePath containing zero WhereLoops. ** - ** TUNING: Do not let the number of iterations go above 25. If the cost - ** of computing an automatic index is not paid back within the first 25 + ** TUNING: Do not let the number of iterations go above 28. If the cost + ** of computing an automatic index is not paid back within the first 28 ** rows, then do not use the automatic index. */ - aFrom[0].nRow = MIN(pParse->nQueryLoop, 46); assert( 46==whereCost(25) ); + aFrom[0].nRow = MIN(pParse->nQueryLoop, 48); assert( 48==sqlite3LogEst(28) ); nFrom = 1; - - /* Precompute the cost of sorting the final result set, if the caller - ** to sqlite3WhereBegin() was concerned about sorting */ - rSortCost = 0; - if( pWInfo->pOrderBy==0 || nRowEst==0 ){ - aFrom[0].isOrderedValid = 1; - }else{ - /* TUNING: Estimated cost of sorting is N*log2(N) where N is the - ** number of output rows. */ - rSortCost = nRowEst + estLog(nRowEst); - WHERETRACE(0x002,("---- sort cost=%-3d\n", rSortCost)); + assert( aFrom[0].isOrdered==0 ); + if( nOrderBy ){ + /* If nLoop is zero, then there are no FROM terms in the query. Since + ** in this case the query may return a maximum of one row, the results + ** are already in the requested order. Set isOrdered to nOrderBy to + ** indicate this. Or, if nLoop is greater than zero, set isOrdered to + ** -1, indicating that the result set may or may not be ordered, + ** depending on the loops added to the current plan. */ + aFrom[0].isOrdered = nLoop>0 ? -1 : nOrderBy; } /* Compute successively longer WherePaths using the previous generation ** of WherePaths as the basis for the next. Keep track of the mxChoice ** best paths at each generation */ for(iLoop=0; iLoop<nLoop; iLoop++){ nTo = 0; for(ii=0, pFrom=aFrom; ii<nFrom; ii++, pFrom++){ for(pWLoop=pWInfo->pLoops; pWLoop; pWLoop=pWLoop->pNextLoop){ - Bitmask maskNew; - Bitmask revMask = 0; - u8 isOrderedValid = pFrom->isOrderedValid; - u8 isOrdered = pFrom->isOrdered; + LogEst nOut; /* Rows visited by (pFrom+pWLoop) */ + LogEst rCost; /* Cost of path (pFrom+pWLoop) */ + LogEst rUnsorted; /* Unsorted cost of (pFrom+pWLoop) */ + i8 isOrdered = pFrom->isOrdered; /* isOrdered for (pFrom+pWLoop) */ + Bitmask maskNew; /* Mask of src visited by (..) */ + Bitmask revMask = 0; /* Mask of rev-order loops for (..) */ + if( (pWLoop->prereq & ~pFrom->maskLoop)!=0 ) continue; if( (pWLoop->maskSelf & pFrom->maskLoop)!=0 ) continue; /* At this point, pWLoop is a candidate to be the next loop. ** Compute its cost */ - rCost = whereCostAdd(pWLoop->rSetup,pWLoop->rRun + pFrom->nRow); - rCost = whereCostAdd(rCost, pFrom->rCost); + rUnsorted = sqlite3LogEstAdd(pWLoop->rSetup,pWLoop->rRun + pFrom->nRow); + rUnsorted = sqlite3LogEstAdd(rUnsorted, pFrom->rUnsorted); nOut = pFrom->nRow + pWLoop->nOut; maskNew = pFrom->maskLoop | pWLoop->maskSelf; - if( !isOrderedValid ){ - switch( wherePathSatisfiesOrderBy(pWInfo, + if( isOrdered<0 ){ + isOrdered = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, pFrom, pWInfo->wctrlFlags, - iLoop, pWLoop, &revMask) ){ - case 1: /* Yes. pFrom+pWLoop does satisfy the ORDER BY clause */ - isOrdered = 1; - isOrderedValid = 1; - break; - case 0: /* No. pFrom+pWLoop will require a separate sort */ - isOrdered = 0; - isOrderedValid = 1; - rCost = whereCostAdd(rCost, rSortCost); - break; - default: /* Cannot tell yet. Try again on the next iteration */ - break; - } + iLoop, pWLoop, &revMask); }else{ revMask = pFrom->revLoop; } - /* Check to see if pWLoop should be added to the mxChoice best so far */ + if( isOrdered>=0 && isOrdered<nOrderBy ){ + if( aSortCost[isOrdered]==0 ){ + aSortCost[isOrdered] = whereSortingCost( + pWInfo, nRowEst, nOrderBy, isOrdered + ); + } + rCost = sqlite3LogEstAdd(rUnsorted, aSortCost[isOrdered]); + + WHERETRACE(0x002, + ("---- sort cost=%-3d (%d/%d) increases cost %3d to %-3d\n", + aSortCost[isOrdered], (nOrderBy-isOrdered), nOrderBy, + rUnsorted, rCost)); + }else{ + rCost = rUnsorted; + } + + /* Check to see if pWLoop should be added to the set of + ** mxChoice best-so-far paths. + ** + ** First look for an existing path among best-so-far paths + ** that covers the same set of loops and has the same isOrdered + ** setting as the current path candidate. + ** + ** The term "((pTo->isOrdered^isOrdered)&0x80)==0" is equivalent + ** to (pTo->isOrdered==(-1))==(isOrdered==(-1))" for the range + ** of legal values for isOrdered, -1..64. + */ for(jj=0, pTo=aTo; jj<nTo; jj++, pTo++){ if( pTo->maskLoop==maskNew - && pTo->isOrderedValid==isOrderedValid - && ((pTo->rCost<=rCost && pTo->nRow<=nOut) || - (pTo->rCost>=rCost && pTo->nRow>=nOut)) + && ((pTo->isOrdered^isOrdered)&0x80)==0 ){ testcase( jj==nTo-1 ); break; } } if( jj>=nTo ){ - if( nTo>=mxChoice && rCost>=mxCost ){ -#ifdef WHERETRACE_ENABLED + /* None of the existing best-so-far paths match the candidate. */ + if( nTo>=mxChoice + && (rCost>mxCost || (rCost==mxCost && rUnsorted>=mxUnsorted)) + ){ + /* The current candidate is no better than any of the mxChoice + ** paths currently in the best-so-far buffer. So discard + ** this candidate as not viable. */ +#ifdef WHERETRACE_ENABLED /* 0x4 */ if( sqlite3WhereTrace&0x4 ){ sqlite3DebugPrintf("Skip %s cost=%-3d,%3d order=%c\n", wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, - isOrderedValid ? (isOrdered ? 'Y' : 'N') : '?'); + isOrdered>=0 ? isOrdered+'0' : '?'); } #endif continue; } - /* Add a new Path to the aTo[] set */ + /* If we reach this points it means that the new candidate path + ** needs to be added to the set of best-so-far paths. */ if( nTo<mxChoice ){ /* Increase the size of the aTo set by one */ jj = nTo++; }else{ /* New path replaces the prior worst to keep count below mxChoice */ jj = mxI; } pTo = &aTo[jj]; -#ifdef WHERETRACE_ENABLED +#ifdef WHERETRACE_ENABLED /* 0x4 */ if( sqlite3WhereTrace&0x4 ){ sqlite3DebugPrintf("New %s cost=%-3d,%3d order=%c\n", wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, - isOrderedValid ? (isOrdered ? 'Y' : 'N') : '?'); + isOrdered>=0 ? isOrdered+'0' : '?'); } #endif }else{ - if( pTo->rCost<=rCost && pTo->nRow<=nOut ){ -#ifdef WHERETRACE_ENABLED + /* Control reaches here if best-so-far path pTo=aTo[jj] covers the + ** same set of loops and has the sam isOrdered setting as the + ** candidate path. Check to see if the candidate should replace + ** pTo or if the candidate should be skipped */ + if( pTo->rCost<rCost || (pTo->rCost==rCost && pTo->nRow<=nOut) ){ +#ifdef WHERETRACE_ENABLED /* 0x4 */ if( sqlite3WhereTrace&0x4 ){ sqlite3DebugPrintf( "Skip %s cost=%-3d,%3d order=%c", wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, - isOrderedValid ? (isOrdered ? 'Y' : 'N') : '?'); + isOrdered>=0 ? isOrdered+'0' : '?'); sqlite3DebugPrintf(" vs %s cost=%-3d,%d order=%c\n", wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow, - pTo->isOrderedValid ? (pTo->isOrdered ? 'Y' : 'N') : '?'); + pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); } #endif + /* Discard the candidate path from further consideration */ testcase( pTo->rCost==rCost ); continue; } testcase( pTo->rCost==rCost+1 ); - /* A new and better score for a previously created equivalent path */ -#ifdef WHERETRACE_ENABLED + /* Control reaches here if the candidate path is better than the + ** pTo path. Replace pTo with the candidate. */ +#ifdef WHERETRACE_ENABLED /* 0x4 */ if( sqlite3WhereTrace&0x4 ){ sqlite3DebugPrintf( "Update %s cost=%-3d,%3d order=%c", wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, - isOrderedValid ? (isOrdered ? 'Y' : 'N') : '?'); + isOrdered>=0 ? isOrdered+'0' : '?'); sqlite3DebugPrintf(" was %s cost=%-3d,%3d order=%c\n", wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow, - pTo->isOrderedValid ? (pTo->isOrdered ? 'Y' : 'N') : '?'); + pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); } #endif } /* pWLoop is a winner. Add it to the set of best so far */ pTo->maskLoop = pFrom->maskLoop | pWLoop->maskSelf; pTo->revLoop = revMask; pTo->nRow = nOut; pTo->rCost = rCost; - pTo->isOrderedValid = isOrderedValid; + pTo->rUnsorted = rUnsorted; pTo->isOrdered = isOrdered; memcpy(pTo->aLoop, pFrom->aLoop, sizeof(WhereLoop*)*iLoop); pTo->aLoop[iLoop] = pWLoop; if( nTo>=mxChoice ){ mxI = 0; mxCost = aTo[0].rCost; - mxOut = aTo[0].nRow; + mxUnsorted = aTo[0].nRow; for(jj=1, pTo=&aTo[1]; jj<mxChoice; jj++, pTo++){ - if( pTo->rCost>mxCost || (pTo->rCost==mxCost && pTo->nRow>mxOut) ){ + if( pTo->rCost>mxCost + || (pTo->rCost==mxCost && pTo->rUnsorted>mxUnsorted) + ){ mxCost = pTo->rCost; - mxOut = pTo->nRow; + mxUnsorted = pTo->rUnsorted; mxI = jj; } } } } } -#ifdef WHERETRACE_ENABLED - if( sqlite3WhereTrace>=2 ){ +#ifdef WHERETRACE_ENABLED /* >=2 */ + if( sqlite3WhereTrace & 0x02 ){ sqlite3DebugPrintf("---- after round %d ----\n", iLoop); for(ii=0, pTo=aTo; ii<nTo; ii++, pTo++){ sqlite3DebugPrintf(" %s cost=%-3d nrow=%-3d order=%c", wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow, - pTo->isOrderedValid ? (pTo->isOrdered ? 'Y' : 'N') : '?'); - if( pTo->isOrderedValid && pTo->isOrdered ){ + pTo->isOrdered>=0 ? (pTo->isOrdered+'0') : '?'); + if( pTo->isOrdered>0 ){ sqlite3DebugPrintf(" rev=0x%llx\n", pTo->revLoop); }else{ sqlite3DebugPrintf("\n"); } } @@ -111712,20 +121886,40 @@ && nRowEst ){ Bitmask notUsed; int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pResultSet, pFrom, WHERE_DISTINCTBY, nLoop-1, pFrom->aLoop[nLoop-1], ¬Used); - if( rc==1 ) pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; + if( rc==pWInfo->pResultSet->nExpr ){ + pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; + } } - if( pFrom->isOrdered ){ + if( pWInfo->pOrderBy ){ if( pWInfo->wctrlFlags & WHERE_DISTINCTBY ){ - pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; + if( pFrom->isOrdered==pWInfo->pOrderBy->nExpr ){ + pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; + } }else{ - pWInfo->bOBSat = 1; + pWInfo->nOBSat = pFrom->isOrdered; + if( pWInfo->nOBSat<0 ) pWInfo->nOBSat = 0; pWInfo->revMask = pFrom->revLoop; } + if( (pWInfo->wctrlFlags & WHERE_SORTBYGROUP) + && pWInfo->nOBSat==pWInfo->pOrderBy->nExpr + ){ + Bitmask revMask = 0; + int nOrder = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, + pFrom, 0, nLoop-1, pFrom->aLoop[nLoop-1], &revMask + ); + assert( pWInfo->sorted==0 ); + if( nOrder==pWInfo->pOrderBy->nExpr ){ + pWInfo->sorted = 1; + pWInfo->revMask = revMask; + } + } } + + pWInfo->nRowOut = pFrom->nRow; /* Free temporary memory and return success */ sqlite3DbFree(db, pSpace); return SQLITE_OK; @@ -111762,51 +121956,51 @@ if( pItem->zIndex ) return 0; iCur = pItem->iCursor; pWC = &pWInfo->sWC; pLoop = pBuilder->pNew; pLoop->wsFlags = 0; + pLoop->nSkip = 0; pTerm = findTerm(pWC, iCur, -1, 0, WO_EQ, 0); if( pTerm ){ pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW; pLoop->aLTerm[0] = pTerm; pLoop->nLTerm = 1; pLoop->u.btree.nEq = 1; /* TUNING: Cost of a rowid lookup is 10 */ - pLoop->rRun = 33; /* 33==whereCost(10) */ + pLoop->rRun = 33; /* 33==sqlite3LogEst(10) */ }else{ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ assert( pLoop->aLTermSpace==pLoop->aLTerm ); - assert( ArraySize(pLoop->aLTermSpace)==4 ); - if( pIdx->onError==OE_None + if( !IsUniqueIndex(pIdx) || pIdx->pPartIdxWhere!=0 - || pIdx->nColumn>ArraySize(pLoop->aLTermSpace) + || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace) ) continue; - for(j=0; j<pIdx->nColumn; j++){ + for(j=0; j<pIdx->nKeyCol; j++){ pTerm = findTerm(pWC, iCur, pIdx->aiColumn[j], 0, WO_EQ, pIdx); if( pTerm==0 ) break; pLoop->aLTerm[j] = pTerm; } - if( j!=pIdx->nColumn ) continue; + if( j!=pIdx->nKeyCol ) continue; pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_ONEROW|WHERE_INDEXED; - if( (pItem->colUsed & ~columnsInIndex(pIdx))==0 ){ + if( pIdx->isCovering || (pItem->colUsed & ~columnsInIndex(pIdx))==0 ){ pLoop->wsFlags |= WHERE_IDX_ONLY; } pLoop->nLTerm = j; pLoop->u.btree.nEq = j; pLoop->u.btree.pIndex = pIdx; /* TUNING: Cost of a unique index lookup is 15 */ - pLoop->rRun = 39; /* 39==whereCost(15) */ + pLoop->rRun = 39; /* 39==sqlite3LogEst(15) */ break; } } if( pLoop->wsFlags ){ - pLoop->nOut = (WhereCost)1; + pLoop->nOut = (LogEst)1; pWInfo->a[0].pWLoop = pLoop; pLoop->maskSelf = getMask(&pWInfo->sMaskSet, iCur); pWInfo->a[0].iTabCur = iCur; pWInfo->nRowOut = 1; - if( pWInfo->pOrderBy ) pWInfo->bOBSat = 1; + if( pWInfo->pOrderBy ) pWInfo->nOBSat = pWInfo->pOrderBy->nExpr; if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } #ifdef SQLITE_DEBUG pLoop->cId = '0'; @@ -111893,16 +122087,24 @@ ** ** pOrderBy is a pointer to the ORDER BY clause (or the GROUP BY clause ** if the WHERE_GROUPBY flag is set in wctrlFlags) of a SELECT statement ** if there is one. If there is no ORDER BY clause or if this routine ** is called from an UPDATE or DELETE statement, then pOrderBy is NULL. +** +** The iIdxCur parameter is the cursor number of an index. If +** WHERE_ONETABLE_ONLY is set, iIdxCur is the cursor number of an index +** to use for OR clause processing. The WHERE clause should use this +** specific cursor. If WHERE_ONEPASS_DESIRED is set, then iIdxCur is +** the first cursor in an array of cursors for all indices. iIdxCur should +** be used to compute the appropriate cursor depending on which index is +** used. */ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( Parse *pParse, /* The parser context */ SrcList *pTabList, /* FROM clause: A list of all tables to be scanned */ Expr *pWhere, /* The WHERE clause */ - ExprList *pOrderBy, /* An ORDER BY clause, or NULL */ + ExprList *pOrderBy, /* An ORDER BY (or GROUP BY) clause, or NULL */ ExprList *pResultSet, /* Result set of the query */ u16 wctrlFlags, /* One of the WHERE_* flags defined in sqliteInt.h */ int iIdxCur /* If WHERE_ONETABLE_ONLY is set, index cursor number */ ){ int nByteWInfo; /* Num. bytes allocated for WhereInfo struct */ @@ -111920,10 +122122,14 @@ /* Variable initialization */ db = pParse->db; memset(&sWLB, 0, sizeof(sWLB)); + + /* An ORDER/GROUP BY clause of more than 63 terms cannot be optimized */ + testcase( pOrderBy && pOrderBy->nExpr==BMS-1 ); + if( pOrderBy && pOrderBy->nExpr>=BMS ) pOrderBy = 0; sWLB.pOrderBy = pOrderBy; /* Disable the DISTINCT optimization if SQLITE_DistinctOpt is set via ** sqlite3_test_ctrl(SQLITE_TESTCTRL_OPTIMIZATIONS,...) */ if( OptimizationDisabled(db, SQLITE_DistinctOpt) ){ @@ -111958,16 +122164,17 @@ if( db->mallocFailed ){ sqlite3DbFree(db, pWInfo); pWInfo = 0; goto whereBeginError; } + pWInfo->aiCurOnePass[0] = pWInfo->aiCurOnePass[1] = -1; pWInfo->nLevel = nTabList; pWInfo->pParse = pParse; pWInfo->pTabList = pTabList; pWInfo->pOrderBy = pOrderBy; pWInfo->pResultSet = pResultSet; - pWInfo->iBreak = sqlite3VdbeMakeLabel(v); + pWInfo->iBreak = pWInfo->iContinue = sqlite3VdbeMakeLabel(v); pWInfo->wctrlFlags = wctrlFlags; pWInfo->savedNQueryLoop = pParse->nQueryLoop; pMaskSet = &pWInfo->sMaskSet; sWLB.pWInfo = pWInfo; sWLB.pWC = &pWInfo->sWC; @@ -111981,26 +122188,27 @@ /* Split the WHERE clause into separate subexpressions where each ** subexpression is separated by an AND operator. */ initMaskSet(pMaskSet); whereClauseInit(&pWInfo->sWC, pWInfo); - sqlite3ExprCodeConstants(pParse, pWhere); whereSplit(&pWInfo->sWC, pWhere, TK_AND); - sqlite3CodeVerifySchema(pParse, -1); /* Insert the cookie verifier Goto */ /* Special case: a WHERE clause that is constant. Evaluate the ** expression and either jump over all of the code or fall thru. */ - if( pWhere && (nTabList==0 || sqlite3ExprIsConstantNotJoin(pWhere)) ){ - sqlite3ExprIfFalse(pParse, pWhere, pWInfo->iBreak, SQLITE_JUMPIFNULL); - pWhere = 0; + for(ii=0; ii<sWLB.pWC->nTerm; ii++){ + if( nTabList==0 || sqlite3ExprIsConstantNotJoin(sWLB.pWC->a[ii].pExpr) ){ + sqlite3ExprIfFalse(pParse, sWLB.pWC->a[ii].pExpr, pWInfo->iBreak, + SQLITE_JUMPIFNULL); + sWLB.pWC->a[ii].wtFlags |= TERM_CODED; + } } /* Special case: No FROM clause */ if( nTabList==0 ){ - if( pOrderBy ) pWInfo->bOBSat = 1; + if( pOrderBy ) pWInfo->nOBSat = pOrderBy->nExpr; if( wctrlFlags & WHERE_WANT_DISTINCT ){ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } } @@ -112042,26 +122250,10 @@ exprAnalyzeAll(pTabList, &pWInfo->sWC); if( db->mallocFailed ){ goto whereBeginError; } - /* If the ORDER BY (or GROUP BY) clause contains references to general - ** expressions, then we won't be able to satisfy it using indices, so - ** go ahead and disable it now. - */ - if( pOrderBy && (wctrlFlags & WHERE_WANT_DISTINCT)!=0 ){ - for(ii=0; ii<pOrderBy->nExpr; ii++){ - Expr *pExpr = sqlite3ExprSkipCollate(pOrderBy->a[ii].pExpr); - if( pExpr->op!=TK_COLUMN ){ - pWInfo->pOrderBy = pOrderBy = 0; - break; - }else if( pExpr->iColumn<0 ){ - break; - } - } - } - if( wctrlFlags & WHERE_WANT_DISTINCT ){ if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ /* The DISTINCT marking is pointless. Ignore it. */ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; }else if( pOrderBy==0 ){ @@ -112071,24 +122263,34 @@ } } /* Construct the WhereLoop objects */ WHERETRACE(0xffff,("*** Optimizer Start ***\n")); +#if defined(WHERETRACE_ENABLED) + /* Display all terms of the WHERE clause */ + if( sqlite3WhereTrace & 0x100 ){ + int i; + for(i=0; i<sWLB.pWC->nTerm; i++){ + whereTermPrint(&sWLB.pWC->a[i], i); + } + } +#endif + if( nTabList!=1 || whereShortCut(&sWLB)==0 ){ rc = whereLoopAddAll(&sWLB); if( rc ) goto whereBeginError; /* Display all of the WhereLoop objects if wheretrace is enabled */ -#ifdef WHERETRACE_ENABLED +#ifdef WHERETRACE_ENABLED /* !=0 */ if( sqlite3WhereTrace ){ WhereLoop *p; int i; static char zLabel[] = "0123456789abcdefghijklmnopqrstuvwyxz" "ABCDEFGHIJKLMNOPQRSTUVWYXZ"; for(p=pWInfo->pLoops, i=0; p; p=p->pNextLoop, i++){ p->cId = zLabel[i%sizeof(zLabel)]; - whereLoopPrint(p, pTabList); + whereLoopPrint(p, sWLB.pWC); } } #endif wherePathSolver(pWInfo, 0); @@ -112102,16 +122304,16 @@ pWInfo->revMask = (Bitmask)(-1); } if( pParse->nErr || NEVER(db->mallocFailed) ){ goto whereBeginError; } -#ifdef WHERETRACE_ENABLED +#ifdef WHERETRACE_ENABLED /* !=0 */ if( sqlite3WhereTrace ){ int ii; sqlite3DebugPrintf("---- Solution nRow=%d", pWInfo->nRowOut); - if( pWInfo->bOBSat ){ - sqlite3DebugPrintf(" ORDERBY=0x%llx", pWInfo->revMask); + if( pWInfo->nOBSat>0 ){ + sqlite3DebugPrintf(" ORDERBY=%d,0x%llx", pWInfo->nOBSat, pWInfo->revMask); } switch( pWInfo->eDistinct ){ case WHERE_DISTINCT_UNIQUE: { sqlite3DebugPrintf(" DISTINCT=unique"); break; @@ -112125,11 +122327,11 @@ break; } } sqlite3DebugPrintf("\n"); for(ii=0; ii<pWInfo->nLevel; ii++){ - whereLoopPrint(pWInfo->a[ii].pWLoop, pTabList); + whereLoopPrint(pWInfo->a[ii].pWLoop, sWLB.pWC); } } #endif /* Attempt to omit tables from the join that do not effect the result */ if( pWInfo->nLevel>=2 @@ -112165,18 +122367,20 @@ WHERETRACE(0xffff,("*** Optimizer Finished ***\n")); pWInfo->pParse->nQueryLoop += pWInfo->nRowOut; /* If the caller is an UPDATE or DELETE statement that is requesting ** to use a one-pass algorithm, determine if this is appropriate. - ** The one-pass algorithm only works if the WHERE clause constraints + ** The one-pass algorithm only works if the WHERE clause constrains ** the statement to update a single row. */ assert( (wctrlFlags & WHERE_ONEPASS_DESIRED)==0 || pWInfo->nLevel==1 ); if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 && (pWInfo->a[0].pWLoop->wsFlags & WHERE_ONEROW)!=0 ){ pWInfo->okOnePass = 1; - pWInfo->a[0].pWLoop->wsFlags &= ~WHERE_IDX_ONLY; + if( HasRowid(pTabList->a[0].pTab) ){ + pWInfo->a[0].pWLoop->wsFlags &= ~WHERE_IDX_ONLY; + } } /* Open all tables in the pTabList and any indices selected for ** searching those tables. */ @@ -112202,15 +122406,20 @@ /* noop */ }else #endif if( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 && (wctrlFlags & WHERE_OMIT_OPEN_CLOSE)==0 ){ - int op = pWInfo->okOnePass ? OP_OpenWrite : OP_OpenRead; + int op = OP_OpenRead; + if( pWInfo->okOnePass ){ + op = OP_OpenWrite; + pWInfo->aiCurOnePass[0] = pTabItem->iCursor; + }; sqlite3OpenTable(pParse, pTabItem->iCursor, iDb, pTab, op); + assert( pTabItem->iCursor==pLevel->iTabCur ); testcase( !pWInfo->okOnePass && pTab->nCol==BMS-1 ); testcase( !pWInfo->okOnePass && pTab->nCol==BMS ); - if( !pWInfo->okOnePass && pTab->nCol<BMS ){ + if( !pWInfo->okOnePass && pTab->nCol<BMS && HasRowid(pTab) ){ Bitmask b = pTabItem->colUsed; int n = 0; for(; b; b=b>>1, n++){} sqlite3VdbeChangeP4(v, sqlite3VdbeCurrentAddr(v)-1, SQLITE_INT_TO_PTR(n), P4_INT32); @@ -112219,20 +122428,53 @@ }else{ sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); } if( pLoop->wsFlags & WHERE_INDEXED ){ Index *pIx = pLoop->u.btree.pIndex; - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIx); - /* FIXME: As an optimization use pTabItem->iCursor if WHERE_IDX_ONLY */ - int iIndexCur = pLevel->iIdxCur = iIdxCur ? iIdxCur : pParse->nTab++; + int iIndexCur; + int op = OP_OpenRead; + /* iIdxCur is always set if to a positive value if ONEPASS is possible */ + assert( iIdxCur!=0 || (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 ); + if( !HasRowid(pTab) && IsPrimaryKeyIndex(pIx) + && (wctrlFlags & WHERE_ONETABLE_ONLY)!=0 + ){ + /* This is one term of an OR-optimization using the PRIMARY KEY of a + ** WITHOUT ROWID table. No need for a separate index */ + iIndexCur = pLevel->iTabCur; + op = 0; + }else if( pWInfo->okOnePass ){ + Index *pJ = pTabItem->pTab->pIndex; + iIndexCur = iIdxCur; + assert( wctrlFlags & WHERE_ONEPASS_DESIRED ); + while( ALWAYS(pJ) && pJ!=pIx ){ + iIndexCur++; + pJ = pJ->pNext; + } + op = OP_OpenWrite; + pWInfo->aiCurOnePass[1] = iIndexCur; + }else if( iIdxCur && (wctrlFlags & WHERE_ONETABLE_ONLY)!=0 ){ + iIndexCur = iIdxCur; + if( wctrlFlags & WHERE_REOPEN_IDX ) op = OP_ReopenIdx; + }else{ + iIndexCur = pParse->nTab++; + } + pLevel->iIdxCur = iIndexCur; assert( pIx->pSchema==pTab->pSchema ); assert( iIndexCur>=0 ); - sqlite3VdbeAddOp4(v, OP_OpenRead, iIndexCur, pIx->tnum, iDb, - (char*)pKey, P4_KEYINFO_HANDOFF); - VdbeComment((v, "%s", pIx->zName)); + if( op ){ + sqlite3VdbeAddOp3(v, op, iIndexCur, pIx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIx); + if( (pLoop->wsFlags & WHERE_CONSTRAINT)!=0 + && (pLoop->wsFlags & (WHERE_COLUMN_RANGE|WHERE_SKIPSCAN))==0 + && (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0 + ){ + sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ); /* Hint to COMDB2 */ + } + VdbeComment((v, "%s", pIx->zName)); + } } - sqlite3CodeVerifySchema(pParse, iDb); + if( iDb>=0 ) sqlite3CodeVerifySchema(pParse, iDb); notReady &= ~getMask(&pWInfo->sMaskSet, pTabItem->iCursor); } pWInfo->iTop = sqlite3VdbeCurrentAddr(v); if( db->mallocFailed ) goto whereBeginError; @@ -112240,25 +122482,34 @@ ** loop below generates code for a single nested loop of the VM ** program. */ notReady = ~(Bitmask)0; for(ii=0; ii<nTabList; ii++){ + int addrExplain; + int wsFlags; pLevel = &pWInfo->a[ii]; + wsFlags = pLevel->pWLoop->wsFlags; #ifndef SQLITE_OMIT_AUTOMATIC_INDEX if( (pLevel->pWLoop->wsFlags & WHERE_AUTO_INDEX)!=0 ){ constructAutomaticIndex(pParse, &pWInfo->sWC, &pTabList->a[pLevel->iFrom], notReady, pLevel); if( db->mallocFailed ) goto whereBeginError; } #endif - explainOneScan(pParse, pTabList, pLevel, ii, pLevel->iFrom, wctrlFlags); + addrExplain = explainOneScan( + pParse, pTabList, pLevel, ii, pLevel->iFrom, wctrlFlags + ); pLevel->addrBody = sqlite3VdbeCurrentAddr(v); notReady = codeOneLoopStart(pWInfo, ii, notReady); pWInfo->iContinue = pLevel->addrCont; + if( (wsFlags&WHERE_MULTI_OR)==0 && (wctrlFlags&WHERE_ONETABLE_ONLY)==0 ){ + addScanStatus(v, pTabList, pLevel, addrExplain); + } } /* Done. */ + VdbeModuleComment((v, "Begin WHERE-core")); return pWInfo; /* Jump here if malloc fails */ whereBeginError: if( pWInfo ){ @@ -112281,34 +122532,58 @@ SrcList *pTabList = pWInfo->pTabList; sqlite3 *db = pParse->db; /* Generate loop termination code. */ + VdbeModuleComment((v, "End WHERE-core")); sqlite3ExprCacheClear(pParse); for(i=pWInfo->nLevel-1; i>=0; i--){ + int addr; pLevel = &pWInfo->a[i]; pLoop = pLevel->pWLoop; sqlite3VdbeResolveLabel(v, pLevel->addrCont); if( pLevel->op!=OP_Noop ){ - sqlite3VdbeAddOp2(v, pLevel->op, pLevel->p1, pLevel->p2); + sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3); sqlite3VdbeChangeP5(v, pLevel->p5); + VdbeCoverage(v); + VdbeCoverageIf(v, pLevel->op==OP_Next); + VdbeCoverageIf(v, pLevel->op==OP_Prev); + VdbeCoverageIf(v, pLevel->op==OP_VNext); } if( pLoop->wsFlags & WHERE_IN_ABLE && pLevel->u.in.nIn>0 ){ struct InLoop *pIn; int j; sqlite3VdbeResolveLabel(v, pLevel->addrNxt); for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){ sqlite3VdbeJumpHere(v, pIn->addrInTop+1); sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop); + VdbeCoverage(v); + VdbeCoverageIf(v, pIn->eEndLoopOp==OP_PrevIfOpen); + VdbeCoverageIf(v, pIn->eEndLoopOp==OP_NextIfOpen); sqlite3VdbeJumpHere(v, pIn->addrInTop-1); } sqlite3DbFree(db, pLevel->u.in.aInLoop); } sqlite3VdbeResolveLabel(v, pLevel->addrBrk); + if( pLevel->addrSkip ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel->addrSkip); + VdbeComment((v, "next skip-scan on %s", pLoop->u.btree.pIndex->zName)); + sqlite3VdbeJumpHere(v, pLevel->addrSkip); + sqlite3VdbeJumpHere(v, pLevel->addrSkip-2); + } + if( pLevel->addrLikeRep ){ + int op; + if( sqlite3VdbeGetOp(v, pLevel->addrLikeRep-1)->p1 ){ + op = OP_DecrJumpZero; + }else{ + op = OP_JumpZeroIncr; + } + sqlite3VdbeAddOp2(v, op, pLevel->iLikeRepCntr, pLevel->addrLikeRep); + VdbeCoverage(v); + } if( pLevel->iLeftJoin ){ - int addr; - addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); + addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v); assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || (pLoop->wsFlags & WHERE_INDEXED)!=0 ); if( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 ){ sqlite3VdbeAddOp1(v, OP_NullRow, pTabList->a[i].iCursor); } @@ -112320,35 +122595,70 @@ }else{ sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel->addrFirst); } sqlite3VdbeJumpHere(v, addr); } + VdbeModuleComment((v, "End WHERE-loop%d: %s", i, + pWInfo->pTabList->a[pLevel->iFrom].pTab->zName)); } /* The "break" point is here, just past the end of the outer loop. ** Set it. */ sqlite3VdbeResolveLabel(v, pWInfo->iBreak); - /* Close all of the cursors that were opened by sqlite3WhereBegin. - */ assert( pWInfo->nLevel<=pTabList->nSrc ); for(i=0, pLevel=pWInfo->a; i<pWInfo->nLevel; i++, pLevel++){ + int k, last; + VdbeOp *pOp; Index *pIdx = 0; struct SrcList_item *pTabItem = &pTabList->a[pLevel->iFrom]; Table *pTab = pTabItem->pTab; assert( pTab!=0 ); pLoop = pLevel->pWLoop; + + /* For a co-routine, change all OP_Column references to the table of + ** the co-routine into OP_SCopy of result contained in a register. + ** OP_Rowid becomes OP_Null. + */ + if( pTabItem->viaCoroutine && !db->mallocFailed ){ + last = sqlite3VdbeCurrentAddr(v); + k = pLevel->addrBody; + pOp = sqlite3VdbeGetOp(v, k); + for(; k<last; k++, pOp++){ + if( pOp->p1!=pLevel->iTabCur ) continue; + if( pOp->opcode==OP_Column ){ + pOp->opcode = OP_Copy; + pOp->p1 = pOp->p2 + pTabItem->regResult; + pOp->p2 = pOp->p3; + pOp->p3 = 0; + }else if( pOp->opcode==OP_Rowid ){ + pOp->opcode = OP_Null; + pOp->p1 = 0; + pOp->p3 = 0; + } + } + continue; + } + + /* Close all of the cursors that were opened by sqlite3WhereBegin. + ** Except, do not close cursors that will be reused by the OR optimization + ** (WHERE_OMIT_OPEN_CLOSE). And do not close the OP_OpenWrite cursors + ** created for the ONEPASS optimization. + */ if( (pTab->tabFlags & TF_Ephemeral)==0 && pTab->pSelect==0 && (pWInfo->wctrlFlags & WHERE_OMIT_OPEN_CLOSE)==0 ){ int ws = pLoop->wsFlags; if( !pWInfo->okOnePass && (ws & WHERE_IDX_ONLY)==0 ){ sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor); } - if( (ws & WHERE_INDEXED)!=0 && (ws & (WHERE_IPK|WHERE_AUTO_INDEX))==0 ){ + if( (ws & WHERE_INDEXED)!=0 + && (ws & (WHERE_IPK|WHERE_AUTO_INDEX))==0 + && pLevel->iIdxCur!=pWInfo->aiCurOnePass[1] + ){ sqlite3VdbeAddOp1(v, OP_Close, pLevel->iIdxCur); } } /* If this scan uses an index, make VDBE code substitutions to read data @@ -112366,27 +122676,28 @@ pIdx = pLoop->u.btree.pIndex; }else if( pLoop->wsFlags & WHERE_MULTI_OR ){ pIdx = pLevel->u.pCovidx; } if( pIdx && !db->mallocFailed ){ - int k, j, last; - VdbeOp *pOp; - last = sqlite3VdbeCurrentAddr(v); k = pLevel->addrBody; pOp = sqlite3VdbeGetOp(v, k); for(; k<last; k++, pOp++){ if( pOp->p1!=pLevel->iTabCur ) continue; if( pOp->opcode==OP_Column ){ - for(j=0; j<pIdx->nColumn; j++){ - if( pOp->p2==pIdx->aiColumn[j] ){ - pOp->p2 = j; - pOp->p1 = pLevel->iIdxCur; - break; - } - } - assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || j<pIdx->nColumn ); + int x = pOp->p2; + assert( pIdx->pTable==pTab ); + if( !HasRowid(pTab) ){ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + x = pPk->aiColumn[x]; + } + x = sqlite3ColumnOfIndex(pIdx, x); + if( x>=0 ){ + pOp->p2 = x; + pOp->p1 = pLevel->iIdxCur; + } + assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || x>=0 ); }else if( pOp->opcode==OP_Rowid ){ pOp->p1 = pLevel->iIdxCur; pOp->opcode = OP_IdxRowid; } } @@ -112459,18 +122770,10 @@ /* ** An instance of this structure holds the ATTACH key and the key type. */ struct AttachKey { int type; Token key; }; -/* -** One or more VALUES claues -*/ -struct ValueList { - ExprList *pList; - Select *pSelect; -}; - /* This is a utility routine used to set the ExprSpan.zStart and ** ExprSpan.zEnd values of pOut so that the span covers the complete ** range of text beginning with pStart and going to the end of pEnd. */ @@ -112520,11 +122823,11 @@ /* A routine to convert a binary TK_IS or TK_ISNOT expression into a ** unary TK_ISNULL or TK_NOTNULL expression. */ static void binaryToUnaryIfNull(Parse *pParse, Expr *pY, Expr *pA, int op){ sqlite3 *db = pParse->db; - if( db->mallocFailed==0 && pY->op==TK_NULL ){ + if( pY && pA && pY->op==TK_NULL ){ pA->op = (u8)op; sqlite3ExprDelete(db, pA->pRight); pA->pRight = 0; } } @@ -112590,41 +122893,41 @@ ** YYNRULE the number of rules in the grammar ** YYERRORSYMBOL is the code number of the error symbol. If not ** defined, then do no error processing. */ #define YYCODETYPE unsigned char -#define YYNOCODE 251 +#define YYNOCODE 254 #define YYACTIONTYPE unsigned short int -#define YYWILDCARD 67 +#define YYWILDCARD 70 #define sqlite3ParserTOKENTYPE Token typedef union { int yyinit; sqlite3ParserTOKENTYPE yy0; - struct LimitVal yy64; - Expr* yy122; - Select* yy159; - IdList* yy180; - struct {int value; int mask;} yy207; - u8 yy258; - u16 yy305; - struct LikeOp yy318; - TriggerStep* yy327; - ExprSpan yy342; - SrcList* yy347; - int yy392; - struct TrigEvent yy410; - ExprList* yy442; - struct ValueList yy487; + Select* yy3; + ExprList* yy14; + With* yy59; + SrcList* yy65; + struct LikeOp yy96; + Expr* yy132; + u8 yy186; + int yy328; + ExprSpan yy346; + struct TrigEvent yy378; + u16 yy381; + IdList* yy408; + struct {int value; int mask;} yy429; + TriggerStep* yy473; + struct LimitVal yy476; } YYMINORTYPE; #ifndef YYSTACKDEPTH #define YYSTACKDEPTH 100 #endif #define sqlite3ParserARG_SDECL Parse *pParse; #define sqlite3ParserARG_PDECL ,Parse *pParse #define sqlite3ParserARG_FETCH Parse *pParse = yypParser->pParse #define sqlite3ParserARG_STORE yypParser->pParse = pParse -#define YYNSTATE 628 +#define YYNSTATE 642 #define YYNRULE 327 #define YYFALLBACK 1 #define YY_NO_ACTION (YYNSTATE+YYNRULE+2) #define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) #define YY_ERROR_ACTION (YYNSTATE+YYNRULE) @@ -112691,478 +122994,467 @@ ** shifting terminals. ** yy_reduce_ofst[] For each state, the offset into yy_action for ** shifting non-terminals after a reduce. ** yy_default[] Default action for each state. */ -#define YY_ACTTAB_COUNT (1564) +#define YY_ACTTAB_COUNT (1497) static const YYACTIONTYPE yy_action[] = { - /* 0 */ 310, 956, 184, 418, 2, 171, 625, 595, 56, 56, - /* 10 */ 56, 56, 49, 54, 54, 54, 54, 53, 53, 52, - /* 20 */ 52, 52, 51, 233, 621, 620, 299, 621, 620, 234, - /* 30 */ 588, 582, 56, 56, 56, 56, 19, 54, 54, 54, - /* 40 */ 54, 53, 53, 52, 52, 52, 51, 233, 606, 57, - /* 50 */ 58, 48, 580, 579, 581, 581, 55, 55, 56, 56, - /* 60 */ 56, 56, 542, 54, 54, 54, 54, 53, 53, 52, - /* 70 */ 52, 52, 51, 233, 310, 595, 326, 196, 195, 194, - /* 80 */ 33, 54, 54, 54, 54, 53, 53, 52, 52, 52, - /* 90 */ 51, 233, 618, 617, 165, 618, 617, 381, 378, 377, - /* 100 */ 408, 533, 577, 577, 588, 582, 304, 423, 376, 59, - /* 110 */ 53, 53, 52, 52, 52, 51, 233, 50, 47, 146, - /* 120 */ 575, 546, 65, 57, 58, 48, 580, 579, 581, 581, - /* 130 */ 55, 55, 56, 56, 56, 56, 213, 54, 54, 54, - /* 140 */ 54, 53, 53, 52, 52, 52, 51, 233, 310, 223, - /* 150 */ 540, 421, 170, 176, 138, 281, 384, 276, 383, 168, - /* 160 */ 490, 552, 410, 669, 621, 620, 272, 439, 410, 439, - /* 170 */ 551, 605, 67, 483, 508, 619, 600, 413, 588, 582, - /* 180 */ 601, 484, 619, 413, 619, 599, 91, 440, 441, 440, - /* 190 */ 336, 599, 73, 670, 222, 267, 481, 57, 58, 48, - /* 200 */ 580, 579, 581, 581, 55, 55, 56, 56, 56, 56, - /* 210 */ 671, 54, 54, 54, 54, 53, 53, 52, 52, 52, - /* 220 */ 51, 233, 310, 280, 232, 231, 1, 132, 200, 386, - /* 230 */ 621, 620, 618, 617, 279, 436, 290, 564, 175, 263, - /* 240 */ 410, 265, 438, 498, 437, 166, 442, 569, 337, 569, - /* 250 */ 201, 538, 588, 582, 600, 413, 165, 595, 601, 381, - /* 260 */ 378, 377, 598, 599, 92, 524, 619, 570, 570, 593, - /* 270 */ 376, 57, 58, 48, 580, 579, 581, 581, 55, 55, - /* 280 */ 56, 56, 56, 56, 598, 54, 54, 54, 54, 53, - /* 290 */ 53, 52, 52, 52, 51, 233, 310, 464, 618, 617, - /* 300 */ 591, 591, 591, 174, 273, 397, 410, 273, 410, 549, - /* 310 */ 398, 621, 620, 68, 327, 621, 620, 621, 620, 619, - /* 320 */ 547, 413, 619, 413, 472, 595, 588, 582, 473, 599, - /* 330 */ 92, 599, 92, 52, 52, 52, 51, 233, 514, 513, - /* 340 */ 206, 323, 364, 465, 221, 57, 58, 48, 580, 579, - /* 350 */ 581, 581, 55, 55, 56, 56, 56, 56, 530, 54, - /* 360 */ 54, 54, 54, 53, 53, 52, 52, 52, 51, 233, - /* 370 */ 310, 397, 410, 397, 598, 373, 387, 531, 348, 618, - /* 380 */ 617, 576, 202, 618, 617, 618, 617, 413, 621, 620, - /* 390 */ 145, 255, 347, 254, 578, 599, 74, 352, 45, 490, - /* 400 */ 588, 582, 235, 189, 465, 545, 167, 297, 187, 470, - /* 410 */ 480, 67, 62, 39, 619, 547, 598, 346, 574, 57, - /* 420 */ 58, 48, 580, 579, 581, 581, 55, 55, 56, 56, - /* 430 */ 56, 56, 6, 54, 54, 54, 54, 53, 53, 52, - /* 440 */ 52, 52, 51, 233, 310, 563, 559, 408, 529, 577, - /* 450 */ 577, 345, 255, 347, 254, 182, 618, 617, 504, 505, - /* 460 */ 315, 410, 558, 235, 166, 272, 410, 353, 565, 181, - /* 470 */ 408, 547, 577, 577, 588, 582, 413, 538, 557, 562, - /* 480 */ 518, 413, 619, 249, 599, 16, 7, 36, 468, 599, - /* 490 */ 92, 517, 619, 57, 58, 48, 580, 579, 581, 581, - /* 500 */ 55, 55, 56, 56, 56, 56, 542, 54, 54, 54, - /* 510 */ 54, 53, 53, 52, 52, 52, 51, 233, 310, 328, - /* 520 */ 573, 572, 526, 559, 561, 395, 872, 246, 410, 248, - /* 530 */ 171, 393, 595, 219, 408, 410, 577, 577, 503, 558, - /* 540 */ 365, 145, 511, 413, 408, 229, 577, 577, 588, 582, - /* 550 */ 413, 599, 92, 382, 270, 557, 166, 401, 599, 69, - /* 560 */ 502, 420, 946, 199, 946, 198, 547, 57, 58, 48, - /* 570 */ 580, 579, 581, 581, 55, 55, 56, 56, 56, 56, - /* 580 */ 569, 54, 54, 54, 54, 53, 53, 52, 52, 52, - /* 590 */ 51, 233, 310, 318, 420, 945, 509, 945, 309, 598, - /* 600 */ 595, 566, 491, 212, 173, 247, 424, 616, 615, 614, - /* 610 */ 324, 197, 143, 406, 573, 572, 490, 66, 50, 47, - /* 620 */ 146, 595, 588, 582, 232, 231, 560, 428, 67, 556, - /* 630 */ 15, 619, 186, 544, 304, 422, 35, 206, 433, 424, - /* 640 */ 553, 57, 58, 48, 580, 579, 581, 581, 55, 55, - /* 650 */ 56, 56, 56, 56, 205, 54, 54, 54, 54, 53, - /* 660 */ 53, 52, 52, 52, 51, 233, 310, 570, 570, 261, - /* 670 */ 269, 598, 12, 374, 569, 166, 410, 314, 410, 421, - /* 680 */ 410, 474, 474, 366, 619, 50, 47, 146, 598, 595, - /* 690 */ 256, 413, 166, 413, 352, 413, 588, 582, 32, 599, - /* 700 */ 94, 599, 97, 599, 95, 628, 626, 330, 142, 50, - /* 710 */ 47, 146, 334, 350, 359, 57, 58, 48, 580, 579, - /* 720 */ 581, 581, 55, 55, 56, 56, 56, 56, 410, 54, - /* 730 */ 54, 54, 54, 53, 53, 52, 52, 52, 51, 233, - /* 740 */ 310, 410, 389, 413, 410, 22, 566, 405, 212, 363, - /* 750 */ 390, 599, 104, 360, 410, 156, 413, 410, 604, 413, - /* 760 */ 538, 332, 570, 570, 599, 103, 494, 599, 105, 413, - /* 770 */ 588, 582, 413, 261, 550, 619, 11, 599, 106, 522, - /* 780 */ 599, 133, 169, 458, 457, 170, 35, 602, 619, 57, - /* 790 */ 58, 48, 580, 579, 581, 581, 55, 55, 56, 56, - /* 800 */ 56, 56, 410, 54, 54, 54, 54, 53, 53, 52, - /* 810 */ 52, 52, 51, 233, 310, 410, 260, 413, 410, 50, - /* 820 */ 47, 146, 358, 319, 356, 599, 134, 528, 353, 338, - /* 830 */ 413, 410, 357, 413, 358, 410, 358, 619, 599, 98, - /* 840 */ 129, 599, 102, 619, 588, 582, 413, 21, 235, 619, - /* 850 */ 413, 619, 211, 143, 599, 101, 30, 167, 599, 93, - /* 860 */ 351, 536, 203, 57, 58, 48, 580, 579, 581, 581, - /* 870 */ 55, 55, 56, 56, 56, 56, 410, 54, 54, 54, - /* 880 */ 54, 53, 53, 52, 52, 52, 51, 233, 310, 410, - /* 890 */ 527, 413, 410, 426, 215, 306, 598, 552, 141, 599, - /* 900 */ 100, 40, 410, 38, 413, 410, 551, 413, 410, 228, - /* 910 */ 220, 315, 599, 77, 501, 599, 96, 413, 588, 582, - /* 920 */ 413, 339, 253, 413, 218, 599, 137, 380, 599, 136, - /* 930 */ 28, 599, 135, 271, 716, 210, 482, 57, 58, 48, - /* 940 */ 580, 579, 581, 581, 55, 55, 56, 56, 56, 56, - /* 950 */ 410, 54, 54, 54, 54, 53, 53, 52, 52, 52, - /* 960 */ 51, 233, 310, 410, 273, 413, 410, 316, 147, 598, - /* 970 */ 273, 627, 2, 599, 76, 209, 410, 127, 413, 619, - /* 980 */ 126, 413, 410, 622, 235, 619, 599, 90, 375, 599, - /* 990 */ 89, 413, 588, 582, 27, 261, 351, 413, 619, 599, - /* 1000 */ 75, 322, 542, 542, 125, 599, 88, 321, 279, 598, - /* 1010 */ 619, 57, 46, 48, 580, 579, 581, 581, 55, 55, - /* 1020 */ 56, 56, 56, 56, 410, 54, 54, 54, 54, 53, - /* 1030 */ 53, 52, 52, 52, 51, 233, 310, 410, 451, 413, - /* 1040 */ 164, 285, 283, 273, 610, 425, 305, 599, 87, 371, - /* 1050 */ 410, 478, 413, 410, 609, 410, 608, 603, 619, 619, - /* 1060 */ 599, 99, 587, 586, 122, 413, 588, 582, 413, 619, - /* 1070 */ 413, 619, 619, 599, 86, 367, 599, 17, 599, 85, - /* 1080 */ 320, 185, 520, 519, 584, 583, 58, 48, 580, 579, - /* 1090 */ 581, 581, 55, 55, 56, 56, 56, 56, 410, 54, - /* 1100 */ 54, 54, 54, 53, 53, 52, 52, 52, 51, 233, - /* 1110 */ 310, 585, 410, 413, 410, 261, 261, 261, 409, 592, - /* 1120 */ 475, 599, 84, 170, 410, 467, 519, 413, 121, 413, - /* 1130 */ 619, 619, 619, 619, 619, 599, 83, 599, 72, 413, - /* 1140 */ 588, 582, 51, 233, 626, 330, 471, 599, 71, 258, - /* 1150 */ 159, 120, 14, 463, 157, 158, 117, 261, 449, 448, - /* 1160 */ 447, 48, 580, 579, 581, 581, 55, 55, 56, 56, - /* 1170 */ 56, 56, 619, 54, 54, 54, 54, 53, 53, 52, - /* 1180 */ 52, 52, 51, 233, 44, 404, 261, 3, 410, 460, - /* 1190 */ 261, 414, 620, 118, 399, 10, 25, 24, 555, 349, - /* 1200 */ 217, 619, 407, 413, 410, 619, 4, 44, 404, 619, - /* 1210 */ 3, 599, 82, 619, 414, 620, 456, 543, 115, 413, - /* 1220 */ 539, 402, 537, 275, 507, 407, 251, 599, 81, 216, - /* 1230 */ 274, 564, 619, 243, 454, 619, 154, 619, 619, 619, - /* 1240 */ 450, 417, 624, 110, 402, 619, 410, 236, 64, 123, - /* 1250 */ 488, 41, 42, 532, 564, 204, 410, 268, 43, 412, - /* 1260 */ 411, 413, 266, 593, 108, 619, 107, 435, 333, 599, - /* 1270 */ 80, 413, 619, 264, 41, 42, 444, 619, 410, 599, - /* 1280 */ 70, 43, 412, 411, 434, 262, 593, 149, 619, 598, - /* 1290 */ 257, 237, 188, 413, 591, 591, 591, 590, 589, 13, - /* 1300 */ 619, 599, 18, 329, 235, 619, 44, 404, 361, 3, - /* 1310 */ 419, 462, 340, 414, 620, 227, 124, 591, 591, 591, - /* 1320 */ 590, 589, 13, 619, 407, 410, 619, 410, 139, 34, - /* 1330 */ 404, 388, 3, 148, 623, 313, 414, 620, 312, 331, - /* 1340 */ 413, 461, 413, 402, 180, 354, 413, 407, 599, 79, - /* 1350 */ 599, 78, 250, 564, 599, 9, 619, 613, 612, 611, - /* 1360 */ 619, 8, 453, 443, 242, 416, 402, 619, 239, 235, - /* 1370 */ 179, 238, 429, 41, 42, 289, 564, 619, 619, 619, - /* 1380 */ 43, 412, 411, 619, 144, 593, 619, 619, 177, 61, - /* 1390 */ 619, 597, 392, 621, 620, 288, 41, 42, 415, 619, - /* 1400 */ 294, 30, 394, 43, 412, 411, 293, 619, 593, 31, - /* 1410 */ 619, 396, 292, 60, 230, 37, 591, 591, 591, 590, - /* 1420 */ 589, 13, 214, 554, 183, 291, 172, 302, 301, 300, - /* 1430 */ 178, 298, 596, 564, 452, 29, 286, 391, 541, 591, - /* 1440 */ 591, 591, 590, 589, 13, 284, 521, 535, 150, 534, - /* 1450 */ 241, 282, 385, 192, 191, 325, 516, 515, 277, 240, - /* 1460 */ 511, 524, 308, 512, 128, 593, 510, 225, 226, 487, - /* 1470 */ 486, 224, 152, 492, 465, 307, 485, 163, 153, 372, - /* 1480 */ 479, 151, 162, 259, 370, 161, 368, 208, 476, 477, - /* 1490 */ 26, 160, 469, 466, 362, 140, 591, 591, 591, 116, - /* 1500 */ 119, 455, 344, 155, 114, 343, 113, 112, 446, 111, - /* 1510 */ 131, 109, 432, 317, 130, 431, 23, 20, 430, 427, - /* 1520 */ 190, 63, 255, 342, 244, 607, 295, 287, 311, 594, - /* 1530 */ 278, 508, 496, 235, 493, 571, 497, 568, 495, 403, - /* 1540 */ 459, 379, 355, 245, 193, 303, 567, 296, 341, 5, - /* 1550 */ 445, 548, 506, 207, 525, 500, 335, 489, 252, 369, - /* 1560 */ 400, 499, 523, 233, + /* 0 */ 306, 212, 432, 955, 639, 191, 955, 295, 559, 88, + /* 10 */ 88, 88, 88, 81, 86, 86, 86, 86, 85, 85, + /* 20 */ 84, 84, 84, 83, 330, 185, 184, 183, 635, 635, + /* 30 */ 292, 606, 606, 88, 88, 88, 88, 683, 86, 86, + /* 40 */ 86, 86, 85, 85, 84, 84, 84, 83, 330, 16, + /* 50 */ 436, 597, 89, 90, 80, 600, 599, 601, 601, 87, + /* 60 */ 87, 88, 88, 88, 88, 684, 86, 86, 86, 86, + /* 70 */ 85, 85, 84, 84, 84, 83, 330, 306, 559, 84, + /* 80 */ 84, 84, 83, 330, 65, 86, 86, 86, 86, 85, + /* 90 */ 85, 84, 84, 84, 83, 330, 635, 635, 634, 633, + /* 100 */ 182, 682, 550, 379, 376, 375, 17, 322, 606, 606, + /* 110 */ 371, 198, 479, 91, 374, 82, 79, 165, 85, 85, + /* 120 */ 84, 84, 84, 83, 330, 598, 635, 635, 107, 89, + /* 130 */ 90, 80, 600, 599, 601, 601, 87, 87, 88, 88, + /* 140 */ 88, 88, 186, 86, 86, 86, 86, 85, 85, 84, + /* 150 */ 84, 84, 83, 330, 306, 594, 594, 142, 328, 327, + /* 160 */ 484, 249, 344, 238, 635, 635, 634, 633, 585, 448, + /* 170 */ 526, 525, 229, 388, 1, 394, 450, 584, 449, 635, + /* 180 */ 635, 635, 635, 319, 395, 606, 606, 199, 157, 273, + /* 190 */ 382, 268, 381, 187, 635, 635, 634, 633, 311, 555, + /* 200 */ 266, 593, 593, 266, 347, 588, 89, 90, 80, 600, + /* 210 */ 599, 601, 601, 87, 87, 88, 88, 88, 88, 478, + /* 220 */ 86, 86, 86, 86, 85, 85, 84, 84, 84, 83, + /* 230 */ 330, 306, 272, 536, 634, 633, 146, 610, 197, 310, + /* 240 */ 575, 182, 482, 271, 379, 376, 375, 506, 21, 634, + /* 250 */ 633, 634, 633, 635, 635, 374, 611, 574, 548, 440, + /* 260 */ 111, 563, 606, 606, 634, 633, 324, 479, 608, 608, + /* 270 */ 608, 300, 435, 573, 119, 407, 210, 162, 562, 883, + /* 280 */ 592, 592, 306, 89, 90, 80, 600, 599, 601, 601, + /* 290 */ 87, 87, 88, 88, 88, 88, 506, 86, 86, 86, + /* 300 */ 86, 85, 85, 84, 84, 84, 83, 330, 620, 111, + /* 310 */ 635, 635, 361, 606, 606, 358, 249, 349, 248, 433, + /* 320 */ 243, 479, 586, 634, 633, 195, 611, 93, 119, 221, + /* 330 */ 575, 497, 534, 534, 89, 90, 80, 600, 599, 601, + /* 340 */ 601, 87, 87, 88, 88, 88, 88, 574, 86, 86, + /* 350 */ 86, 86, 85, 85, 84, 84, 84, 83, 330, 306, + /* 360 */ 77, 429, 638, 573, 589, 530, 240, 230, 242, 105, + /* 370 */ 249, 349, 248, 515, 588, 208, 460, 529, 564, 173, + /* 380 */ 634, 633, 970, 144, 430, 2, 424, 228, 380, 557, + /* 390 */ 606, 606, 190, 153, 159, 158, 514, 51, 632, 631, + /* 400 */ 630, 71, 536, 432, 954, 196, 610, 954, 614, 45, + /* 410 */ 18, 89, 90, 80, 600, 599, 601, 601, 87, 87, + /* 420 */ 88, 88, 88, 88, 261, 86, 86, 86, 86, 85, + /* 430 */ 85, 84, 84, 84, 83, 330, 306, 608, 608, 608, + /* 440 */ 542, 424, 402, 385, 241, 506, 451, 320, 211, 543, + /* 450 */ 164, 436, 386, 293, 451, 587, 108, 496, 111, 334, + /* 460 */ 391, 591, 424, 614, 27, 452, 453, 606, 606, 72, + /* 470 */ 257, 70, 259, 452, 339, 342, 564, 582, 68, 415, + /* 480 */ 469, 328, 327, 62, 614, 45, 110, 393, 89, 90, + /* 490 */ 80, 600, 599, 601, 601, 87, 87, 88, 88, 88, + /* 500 */ 88, 152, 86, 86, 86, 86, 85, 85, 84, 84, + /* 510 */ 84, 83, 330, 306, 110, 499, 520, 538, 402, 389, + /* 520 */ 424, 110, 566, 500, 593, 593, 454, 82, 79, 165, + /* 530 */ 424, 591, 384, 564, 340, 615, 188, 162, 424, 350, + /* 540 */ 616, 424, 614, 44, 606, 606, 445, 582, 300, 434, + /* 550 */ 151, 19, 614, 9, 568, 580, 348, 615, 469, 567, + /* 560 */ 614, 26, 616, 614, 45, 89, 90, 80, 600, 599, + /* 570 */ 601, 601, 87, 87, 88, 88, 88, 88, 411, 86, + /* 580 */ 86, 86, 86, 85, 85, 84, 84, 84, 83, 330, + /* 590 */ 306, 579, 110, 578, 521, 282, 433, 398, 400, 255, + /* 600 */ 486, 82, 79, 165, 487, 164, 82, 79, 165, 488, + /* 610 */ 488, 364, 387, 424, 544, 544, 509, 350, 362, 155, + /* 620 */ 191, 606, 606, 559, 642, 640, 333, 82, 79, 165, + /* 630 */ 305, 564, 507, 312, 357, 614, 45, 329, 596, 595, + /* 640 */ 194, 337, 89, 90, 80, 600, 599, 601, 601, 87, + /* 650 */ 87, 88, 88, 88, 88, 424, 86, 86, 86, 86, + /* 660 */ 85, 85, 84, 84, 84, 83, 330, 306, 20, 323, + /* 670 */ 150, 263, 211, 543, 421, 596, 595, 614, 22, 424, + /* 680 */ 193, 424, 284, 424, 391, 424, 509, 424, 577, 424, + /* 690 */ 186, 335, 424, 559, 424, 313, 120, 546, 606, 606, + /* 700 */ 67, 614, 47, 614, 50, 614, 48, 614, 100, 614, + /* 710 */ 99, 614, 101, 576, 614, 102, 614, 109, 326, 89, + /* 720 */ 90, 80, 600, 599, 601, 601, 87, 87, 88, 88, + /* 730 */ 88, 88, 424, 86, 86, 86, 86, 85, 85, 84, + /* 740 */ 84, 84, 83, 330, 306, 424, 311, 424, 585, 54, + /* 750 */ 424, 516, 517, 590, 614, 112, 424, 584, 424, 572, + /* 760 */ 424, 195, 424, 571, 424, 67, 424, 614, 94, 614, + /* 770 */ 98, 424, 614, 97, 264, 606, 606, 195, 614, 46, + /* 780 */ 614, 96, 614, 30, 614, 49, 614, 115, 614, 114, + /* 790 */ 418, 229, 388, 614, 113, 306, 89, 90, 80, 600, + /* 800 */ 599, 601, 601, 87, 87, 88, 88, 88, 88, 424, + /* 810 */ 86, 86, 86, 86, 85, 85, 84, 84, 84, 83, + /* 820 */ 330, 119, 424, 590, 110, 372, 606, 606, 195, 53, + /* 830 */ 250, 614, 29, 195, 472, 438, 729, 190, 302, 498, + /* 840 */ 14, 523, 641, 2, 614, 43, 306, 89, 90, 80, + /* 850 */ 600, 599, 601, 601, 87, 87, 88, 88, 88, 88, + /* 860 */ 424, 86, 86, 86, 86, 85, 85, 84, 84, 84, + /* 870 */ 83, 330, 424, 613, 964, 964, 354, 606, 606, 420, + /* 880 */ 312, 64, 614, 42, 391, 355, 283, 437, 301, 255, + /* 890 */ 414, 410, 495, 492, 614, 28, 471, 306, 89, 90, + /* 900 */ 80, 600, 599, 601, 601, 87, 87, 88, 88, 88, + /* 910 */ 88, 424, 86, 86, 86, 86, 85, 85, 84, 84, + /* 920 */ 84, 83, 330, 424, 110, 110, 110, 110, 606, 606, + /* 930 */ 110, 254, 13, 614, 41, 532, 531, 283, 481, 531, + /* 940 */ 457, 284, 119, 561, 356, 614, 40, 284, 306, 89, + /* 950 */ 78, 80, 600, 599, 601, 601, 87, 87, 88, 88, + /* 960 */ 88, 88, 424, 86, 86, 86, 86, 85, 85, 84, + /* 970 */ 84, 84, 83, 330, 110, 424, 341, 220, 555, 606, + /* 980 */ 606, 351, 555, 318, 614, 95, 413, 255, 83, 330, + /* 990 */ 284, 284, 255, 640, 333, 356, 255, 614, 39, 306, + /* 1000 */ 356, 90, 80, 600, 599, 601, 601, 87, 87, 88, + /* 1010 */ 88, 88, 88, 424, 86, 86, 86, 86, 85, 85, + /* 1020 */ 84, 84, 84, 83, 330, 424, 317, 316, 141, 465, + /* 1030 */ 606, 606, 219, 619, 463, 614, 10, 417, 462, 255, + /* 1040 */ 189, 510, 553, 351, 207, 363, 161, 614, 38, 315, + /* 1050 */ 218, 255, 255, 80, 600, 599, 601, 601, 87, 87, + /* 1060 */ 88, 88, 88, 88, 424, 86, 86, 86, 86, 85, + /* 1070 */ 85, 84, 84, 84, 83, 330, 76, 419, 255, 3, + /* 1080 */ 878, 461, 424, 247, 331, 331, 614, 37, 217, 76, + /* 1090 */ 419, 390, 3, 216, 215, 422, 4, 331, 331, 424, + /* 1100 */ 547, 12, 424, 545, 614, 36, 424, 541, 422, 424, + /* 1110 */ 540, 424, 214, 424, 408, 424, 539, 403, 605, 605, + /* 1120 */ 237, 614, 25, 119, 614, 24, 588, 408, 614, 45, + /* 1130 */ 118, 614, 35, 614, 34, 614, 33, 614, 23, 588, + /* 1140 */ 60, 223, 603, 602, 513, 378, 73, 74, 140, 139, + /* 1150 */ 424, 110, 265, 75, 426, 425, 59, 424, 610, 73, + /* 1160 */ 74, 549, 402, 404, 424, 373, 75, 426, 425, 604, + /* 1170 */ 138, 610, 614, 11, 392, 76, 419, 181, 3, 614, + /* 1180 */ 32, 271, 369, 331, 331, 493, 614, 31, 149, 608, + /* 1190 */ 608, 608, 607, 15, 422, 365, 614, 8, 137, 489, + /* 1200 */ 136, 190, 608, 608, 608, 607, 15, 485, 176, 135, + /* 1210 */ 7, 252, 477, 408, 174, 133, 175, 474, 57, 56, + /* 1220 */ 132, 130, 119, 76, 419, 588, 3, 468, 245, 464, + /* 1230 */ 171, 331, 331, 125, 123, 456, 447, 122, 446, 104, + /* 1240 */ 336, 231, 422, 166, 154, 73, 74, 332, 116, 431, + /* 1250 */ 121, 309, 75, 426, 425, 222, 106, 610, 308, 637, + /* 1260 */ 204, 408, 629, 627, 628, 6, 200, 428, 427, 290, + /* 1270 */ 203, 622, 201, 588, 62, 63, 289, 66, 419, 399, + /* 1280 */ 3, 401, 288, 92, 143, 331, 331, 287, 608, 608, + /* 1290 */ 608, 607, 15, 73, 74, 227, 422, 325, 69, 416, + /* 1300 */ 75, 426, 425, 612, 412, 610, 192, 61, 569, 209, + /* 1310 */ 396, 226, 278, 225, 383, 408, 527, 558, 276, 533, + /* 1320 */ 552, 528, 321, 523, 370, 508, 180, 588, 494, 179, + /* 1330 */ 366, 117, 253, 269, 522, 503, 608, 608, 608, 607, + /* 1340 */ 15, 551, 502, 58, 274, 524, 178, 73, 74, 304, + /* 1350 */ 501, 368, 303, 206, 75, 426, 425, 491, 360, 610, + /* 1360 */ 213, 177, 483, 131, 345, 298, 297, 296, 202, 294, + /* 1370 */ 480, 490, 466, 134, 172, 129, 444, 346, 470, 128, + /* 1380 */ 314, 459, 103, 127, 126, 148, 124, 167, 443, 235, + /* 1390 */ 608, 608, 608, 607, 15, 442, 439, 623, 234, 299, + /* 1400 */ 145, 583, 291, 377, 581, 160, 119, 156, 270, 636, + /* 1410 */ 971, 169, 279, 626, 520, 625, 473, 624, 170, 621, + /* 1420 */ 618, 119, 168, 55, 409, 423, 537, 609, 286, 285, + /* 1430 */ 405, 570, 560, 556, 5, 52, 458, 554, 147, 267, + /* 1440 */ 519, 504, 518, 406, 262, 239, 260, 512, 343, 511, + /* 1450 */ 258, 353, 565, 256, 224, 251, 359, 277, 275, 476, + /* 1460 */ 475, 246, 352, 244, 467, 455, 236, 233, 232, 307, + /* 1470 */ 441, 281, 205, 163, 397, 280, 535, 505, 330, 617, + /* 1480 */ 971, 971, 971, 971, 367, 971, 971, 971, 971, 971, + /* 1490 */ 971, 971, 971, 971, 971, 971, 338, }; static const YYCODETYPE yy_lookahead[] = { - /* 0 */ 19, 142, 143, 144, 145, 24, 1, 26, 77, 78, - /* 10 */ 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, - /* 20 */ 89, 90, 91, 92, 26, 27, 15, 26, 27, 197, - /* 30 */ 49, 50, 77, 78, 79, 80, 204, 82, 83, 84, - /* 40 */ 85, 86, 87, 88, 89, 90, 91, 92, 23, 68, - /* 50 */ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, - /* 60 */ 79, 80, 166, 82, 83, 84, 85, 86, 87, 88, - /* 70 */ 89, 90, 91, 92, 19, 94, 19, 105, 106, 107, - /* 80 */ 25, 82, 83, 84, 85, 86, 87, 88, 89, 90, - /* 90 */ 91, 92, 94, 95, 96, 94, 95, 99, 100, 101, - /* 100 */ 112, 205, 114, 115, 49, 50, 22, 23, 110, 54, - /* 110 */ 86, 87, 88, 89, 90, 91, 92, 221, 222, 223, - /* 120 */ 23, 120, 25, 68, 69, 70, 71, 72, 73, 74, - /* 130 */ 75, 76, 77, 78, 79, 80, 22, 82, 83, 84, - /* 140 */ 85, 86, 87, 88, 89, 90, 91, 92, 19, 92, - /* 150 */ 23, 67, 25, 96, 97, 98, 99, 100, 101, 102, - /* 160 */ 150, 32, 150, 118, 26, 27, 109, 150, 150, 150, - /* 170 */ 41, 161, 162, 180, 181, 165, 113, 165, 49, 50, - /* 180 */ 117, 188, 165, 165, 165, 173, 174, 170, 171, 170, - /* 190 */ 171, 173, 174, 118, 184, 16, 186, 68, 69, 70, - /* 200 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - /* 210 */ 118, 82, 83, 84, 85, 86, 87, 88, 89, 90, - /* 220 */ 91, 92, 19, 98, 86, 87, 22, 24, 160, 88, - /* 230 */ 26, 27, 94, 95, 109, 97, 224, 66, 118, 60, - /* 240 */ 150, 62, 104, 23, 106, 25, 229, 230, 229, 230, - /* 250 */ 160, 150, 49, 50, 113, 165, 96, 26, 117, 99, - /* 260 */ 100, 101, 194, 173, 174, 94, 165, 129, 130, 98, - /* 270 */ 110, 68, 69, 70, 71, 72, 73, 74, 75, 76, - /* 280 */ 77, 78, 79, 80, 194, 82, 83, 84, 85, 86, - /* 290 */ 87, 88, 89, 90, 91, 92, 19, 11, 94, 95, - /* 300 */ 129, 130, 131, 118, 150, 215, 150, 150, 150, 25, - /* 310 */ 220, 26, 27, 22, 213, 26, 27, 26, 27, 165, - /* 320 */ 25, 165, 165, 165, 30, 94, 49, 50, 34, 173, - /* 330 */ 174, 173, 174, 88, 89, 90, 91, 92, 7, 8, - /* 340 */ 160, 187, 48, 57, 187, 68, 69, 70, 71, 72, - /* 350 */ 73, 74, 75, 76, 77, 78, 79, 80, 23, 82, - /* 360 */ 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, - /* 370 */ 19, 215, 150, 215, 194, 19, 220, 88, 220, 94, - /* 380 */ 95, 23, 160, 94, 95, 94, 95, 165, 26, 27, - /* 390 */ 95, 105, 106, 107, 113, 173, 174, 217, 22, 150, - /* 400 */ 49, 50, 116, 119, 57, 120, 50, 158, 22, 21, - /* 410 */ 161, 162, 232, 136, 165, 120, 194, 237, 23, 68, - /* 420 */ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, - /* 430 */ 79, 80, 22, 82, 83, 84, 85, 86, 87, 88, - /* 440 */ 89, 90, 91, 92, 19, 23, 12, 112, 23, 114, - /* 450 */ 115, 63, 105, 106, 107, 23, 94, 95, 97, 98, - /* 460 */ 104, 150, 28, 116, 25, 109, 150, 150, 23, 23, - /* 470 */ 112, 25, 114, 115, 49, 50, 165, 150, 44, 11, - /* 480 */ 46, 165, 165, 16, 173, 174, 76, 136, 100, 173, - /* 490 */ 174, 57, 165, 68, 69, 70, 71, 72, 73, 74, - /* 500 */ 75, 76, 77, 78, 79, 80, 166, 82, 83, 84, - /* 510 */ 85, 86, 87, 88, 89, 90, 91, 92, 19, 169, - /* 520 */ 170, 171, 23, 12, 23, 214, 138, 60, 150, 62, - /* 530 */ 24, 215, 26, 216, 112, 150, 114, 115, 36, 28, - /* 540 */ 213, 95, 103, 165, 112, 205, 114, 115, 49, 50, - /* 550 */ 165, 173, 174, 51, 23, 44, 25, 46, 173, 174, - /* 560 */ 58, 22, 23, 22, 25, 160, 120, 68, 69, 70, - /* 570 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - /* 580 */ 230, 82, 83, 84, 85, 86, 87, 88, 89, 90, - /* 590 */ 91, 92, 19, 215, 22, 23, 23, 25, 163, 194, - /* 600 */ 94, 166, 167, 168, 25, 138, 67, 7, 8, 9, - /* 610 */ 108, 206, 207, 169, 170, 171, 150, 22, 221, 222, - /* 620 */ 223, 26, 49, 50, 86, 87, 23, 161, 162, 23, - /* 630 */ 22, 165, 24, 120, 22, 23, 25, 160, 241, 67, - /* 640 */ 176, 68, 69, 70, 71, 72, 73, 74, 75, 76, - /* 650 */ 77, 78, 79, 80, 160, 82, 83, 84, 85, 86, - /* 660 */ 87, 88, 89, 90, 91, 92, 19, 129, 130, 150, - /* 670 */ 23, 194, 35, 23, 230, 25, 150, 155, 150, 67, - /* 680 */ 150, 105, 106, 107, 165, 221, 222, 223, 194, 94, - /* 690 */ 23, 165, 25, 165, 217, 165, 49, 50, 25, 173, - /* 700 */ 174, 173, 174, 173, 174, 0, 1, 2, 118, 221, - /* 710 */ 222, 223, 193, 219, 237, 68, 69, 70, 71, 72, - /* 720 */ 73, 74, 75, 76, 77, 78, 79, 80, 150, 82, - /* 730 */ 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, - /* 740 */ 19, 150, 19, 165, 150, 24, 166, 167, 168, 227, - /* 750 */ 27, 173, 174, 231, 150, 25, 165, 150, 172, 165, - /* 760 */ 150, 242, 129, 130, 173, 174, 180, 173, 174, 165, - /* 770 */ 49, 50, 165, 150, 176, 165, 35, 173, 174, 165, - /* 780 */ 173, 174, 35, 23, 23, 25, 25, 173, 165, 68, - /* 790 */ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, - /* 800 */ 79, 80, 150, 82, 83, 84, 85, 86, 87, 88, - /* 810 */ 89, 90, 91, 92, 19, 150, 193, 165, 150, 221, - /* 820 */ 222, 223, 150, 213, 19, 173, 174, 23, 150, 97, - /* 830 */ 165, 150, 27, 165, 150, 150, 150, 165, 173, 174, - /* 840 */ 22, 173, 174, 165, 49, 50, 165, 52, 116, 165, - /* 850 */ 165, 165, 206, 207, 173, 174, 126, 50, 173, 174, - /* 860 */ 128, 27, 160, 68, 69, 70, 71, 72, 73, 74, - /* 870 */ 75, 76, 77, 78, 79, 80, 150, 82, 83, 84, - /* 880 */ 85, 86, 87, 88, 89, 90, 91, 92, 19, 150, - /* 890 */ 23, 165, 150, 23, 216, 25, 194, 32, 39, 173, - /* 900 */ 174, 135, 150, 137, 165, 150, 41, 165, 150, 52, - /* 910 */ 238, 104, 173, 174, 29, 173, 174, 165, 49, 50, - /* 920 */ 165, 219, 238, 165, 238, 173, 174, 52, 173, 174, - /* 930 */ 22, 173, 174, 23, 23, 160, 25, 68, 69, 70, - /* 940 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - /* 950 */ 150, 82, 83, 84, 85, 86, 87, 88, 89, 90, - /* 960 */ 91, 92, 19, 150, 150, 165, 150, 245, 246, 194, - /* 970 */ 150, 144, 145, 173, 174, 160, 150, 22, 165, 165, - /* 980 */ 22, 165, 150, 150, 116, 165, 173, 174, 52, 173, - /* 990 */ 174, 165, 49, 50, 22, 150, 128, 165, 165, 173, - /* 1000 */ 174, 187, 166, 166, 22, 173, 174, 187, 109, 194, - /* 1010 */ 165, 68, 69, 70, 71, 72, 73, 74, 75, 76, - /* 1020 */ 77, 78, 79, 80, 150, 82, 83, 84, 85, 86, - /* 1030 */ 87, 88, 89, 90, 91, 92, 19, 150, 193, 165, - /* 1040 */ 102, 205, 205, 150, 150, 247, 248, 173, 174, 19, - /* 1050 */ 150, 20, 165, 150, 150, 150, 150, 150, 165, 165, - /* 1060 */ 173, 174, 49, 50, 104, 165, 49, 50, 165, 165, - /* 1070 */ 165, 165, 165, 173, 174, 43, 173, 174, 173, 174, - /* 1080 */ 187, 24, 190, 191, 71, 72, 69, 70, 71, 72, - /* 1090 */ 73, 74, 75, 76, 77, 78, 79, 80, 150, 82, - /* 1100 */ 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, - /* 1110 */ 19, 98, 150, 165, 150, 150, 150, 150, 150, 150, - /* 1120 */ 59, 173, 174, 25, 150, 190, 191, 165, 53, 165, - /* 1130 */ 165, 165, 165, 165, 165, 173, 174, 173, 174, 165, - /* 1140 */ 49, 50, 91, 92, 1, 2, 53, 173, 174, 138, - /* 1150 */ 104, 22, 5, 1, 35, 118, 127, 150, 193, 193, - /* 1160 */ 193, 70, 71, 72, 73, 74, 75, 76, 77, 78, - /* 1170 */ 79, 80, 165, 82, 83, 84, 85, 86, 87, 88, - /* 1180 */ 89, 90, 91, 92, 19, 20, 150, 22, 150, 27, - /* 1190 */ 150, 26, 27, 108, 150, 22, 76, 76, 150, 25, - /* 1200 */ 193, 165, 37, 165, 150, 165, 22, 19, 20, 165, - /* 1210 */ 22, 173, 174, 165, 26, 27, 23, 150, 119, 165, - /* 1220 */ 150, 56, 150, 150, 150, 37, 16, 173, 174, 193, - /* 1230 */ 150, 66, 165, 193, 1, 165, 121, 165, 165, 165, - /* 1240 */ 20, 146, 147, 119, 56, 165, 150, 152, 16, 154, - /* 1250 */ 150, 86, 87, 88, 66, 160, 150, 150, 93, 94, - /* 1260 */ 95, 165, 150, 98, 108, 165, 127, 23, 65, 173, - /* 1270 */ 174, 165, 165, 150, 86, 87, 128, 165, 150, 173, - /* 1280 */ 174, 93, 94, 95, 23, 150, 98, 15, 165, 194, - /* 1290 */ 150, 140, 22, 165, 129, 130, 131, 132, 133, 134, - /* 1300 */ 165, 173, 174, 3, 116, 165, 19, 20, 150, 22, - /* 1310 */ 4, 150, 217, 26, 27, 179, 179, 129, 130, 131, - /* 1320 */ 132, 133, 134, 165, 37, 150, 165, 150, 164, 19, - /* 1330 */ 20, 150, 22, 246, 149, 249, 26, 27, 249, 244, - /* 1340 */ 165, 150, 165, 56, 6, 150, 165, 37, 173, 174, - /* 1350 */ 173, 174, 150, 66, 173, 174, 165, 149, 149, 13, - /* 1360 */ 165, 25, 150, 150, 150, 149, 56, 165, 150, 116, - /* 1370 */ 151, 150, 150, 86, 87, 150, 66, 165, 165, 165, - /* 1380 */ 93, 94, 95, 165, 150, 98, 165, 165, 151, 22, - /* 1390 */ 165, 194, 150, 26, 27, 150, 86, 87, 159, 165, - /* 1400 */ 199, 126, 123, 93, 94, 95, 200, 165, 98, 124, - /* 1410 */ 165, 122, 201, 125, 225, 135, 129, 130, 131, 132, - /* 1420 */ 133, 134, 5, 157, 157, 202, 118, 10, 11, 12, - /* 1430 */ 13, 14, 203, 66, 17, 104, 210, 121, 211, 129, - /* 1440 */ 130, 131, 132, 133, 134, 210, 175, 211, 31, 211, - /* 1450 */ 33, 210, 104, 86, 87, 47, 175, 183, 175, 42, - /* 1460 */ 103, 94, 178, 177, 22, 98, 175, 92, 228, 175, - /* 1470 */ 175, 228, 55, 183, 57, 178, 175, 156, 61, 18, - /* 1480 */ 157, 64, 156, 235, 157, 156, 45, 157, 236, 157, - /* 1490 */ 135, 156, 199, 189, 157, 68, 129, 130, 131, 22, - /* 1500 */ 189, 199, 157, 156, 192, 18, 192, 192, 199, 192, - /* 1510 */ 218, 189, 40, 157, 218, 157, 240, 240, 157, 38, - /* 1520 */ 196, 243, 105, 106, 107, 153, 198, 209, 111, 166, - /* 1530 */ 176, 181, 166, 116, 166, 230, 176, 230, 176, 226, - /* 1540 */ 199, 177, 239, 209, 185, 148, 166, 195, 209, 196, - /* 1550 */ 199, 208, 182, 233, 173, 182, 139, 186, 239, 234, - /* 1560 */ 191, 182, 173, 92, -}; -#define YY_SHIFT_USE_DFLT (-70) -#define YY_SHIFT_COUNT (417) -#define YY_SHIFT_MIN (-69) -#define YY_SHIFT_MAX (1487) + /* 0 */ 19, 22, 22, 23, 1, 24, 26, 15, 27, 80, + /* 10 */ 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + /* 20 */ 91, 92, 93, 94, 95, 108, 109, 110, 27, 28, + /* 30 */ 23, 50, 51, 80, 81, 82, 83, 122, 85, 86, + /* 40 */ 87, 88, 89, 90, 91, 92, 93, 94, 95, 22, + /* 50 */ 70, 23, 71, 72, 73, 74, 75, 76, 77, 78, + /* 60 */ 79, 80, 81, 82, 83, 122, 85, 86, 87, 88, + /* 70 */ 89, 90, 91, 92, 93, 94, 95, 19, 97, 91, + /* 80 */ 92, 93, 94, 95, 26, 85, 86, 87, 88, 89, + /* 90 */ 90, 91, 92, 93, 94, 95, 27, 28, 97, 98, + /* 100 */ 99, 122, 211, 102, 103, 104, 79, 19, 50, 51, + /* 110 */ 19, 122, 59, 55, 113, 224, 225, 226, 89, 90, + /* 120 */ 91, 92, 93, 94, 95, 23, 27, 28, 26, 71, + /* 130 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + /* 140 */ 82, 83, 51, 85, 86, 87, 88, 89, 90, 91, + /* 150 */ 92, 93, 94, 95, 19, 132, 133, 58, 89, 90, + /* 160 */ 21, 108, 109, 110, 27, 28, 97, 98, 33, 100, + /* 170 */ 7, 8, 119, 120, 22, 19, 107, 42, 109, 27, + /* 180 */ 28, 27, 28, 95, 28, 50, 51, 99, 100, 101, + /* 190 */ 102, 103, 104, 105, 27, 28, 97, 98, 107, 152, + /* 200 */ 112, 132, 133, 112, 65, 69, 71, 72, 73, 74, + /* 210 */ 75, 76, 77, 78, 79, 80, 81, 82, 83, 11, + /* 220 */ 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + /* 230 */ 95, 19, 101, 97, 97, 98, 24, 101, 122, 157, + /* 240 */ 12, 99, 103, 112, 102, 103, 104, 152, 22, 97, + /* 250 */ 98, 97, 98, 27, 28, 113, 27, 29, 91, 164, + /* 260 */ 165, 124, 50, 51, 97, 98, 219, 59, 132, 133, + /* 270 */ 134, 22, 23, 45, 66, 47, 212, 213, 124, 140, + /* 280 */ 132, 133, 19, 71, 72, 73, 74, 75, 76, 77, + /* 290 */ 78, 79, 80, 81, 82, 83, 152, 85, 86, 87, + /* 300 */ 88, 89, 90, 91, 92, 93, 94, 95, 164, 165, + /* 310 */ 27, 28, 230, 50, 51, 233, 108, 109, 110, 70, + /* 320 */ 16, 59, 23, 97, 98, 26, 97, 22, 66, 185, + /* 330 */ 12, 187, 27, 28, 71, 72, 73, 74, 75, 76, + /* 340 */ 77, 78, 79, 80, 81, 82, 83, 29, 85, 86, + /* 350 */ 87, 88, 89, 90, 91, 92, 93, 94, 95, 19, + /* 360 */ 22, 148, 149, 45, 23, 47, 62, 154, 64, 156, + /* 370 */ 108, 109, 110, 37, 69, 23, 163, 59, 26, 26, + /* 380 */ 97, 98, 144, 145, 146, 147, 152, 200, 52, 23, + /* 390 */ 50, 51, 26, 22, 89, 90, 60, 210, 7, 8, + /* 400 */ 9, 138, 97, 22, 23, 26, 101, 26, 174, 175, + /* 410 */ 197, 71, 72, 73, 74, 75, 76, 77, 78, 79, + /* 420 */ 80, 81, 82, 83, 16, 85, 86, 87, 88, 89, + /* 430 */ 90, 91, 92, 93, 94, 95, 19, 132, 133, 134, + /* 440 */ 23, 152, 208, 209, 140, 152, 152, 111, 195, 196, + /* 450 */ 98, 70, 163, 160, 152, 23, 22, 164, 165, 246, + /* 460 */ 207, 27, 152, 174, 175, 171, 172, 50, 51, 137, + /* 470 */ 62, 139, 64, 171, 172, 222, 124, 27, 138, 24, + /* 480 */ 163, 89, 90, 130, 174, 175, 197, 163, 71, 72, + /* 490 */ 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + /* 500 */ 83, 22, 85, 86, 87, 88, 89, 90, 91, 92, + /* 510 */ 93, 94, 95, 19, 197, 181, 182, 23, 208, 209, + /* 520 */ 152, 197, 26, 189, 132, 133, 232, 224, 225, 226, + /* 530 */ 152, 97, 91, 26, 232, 116, 212, 213, 152, 222, + /* 540 */ 121, 152, 174, 175, 50, 51, 243, 97, 22, 23, + /* 550 */ 22, 234, 174, 175, 177, 23, 239, 116, 163, 177, + /* 560 */ 174, 175, 121, 174, 175, 71, 72, 73, 74, 75, + /* 570 */ 76, 77, 78, 79, 80, 81, 82, 83, 24, 85, + /* 580 */ 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + /* 590 */ 19, 23, 197, 11, 23, 227, 70, 208, 220, 152, + /* 600 */ 31, 224, 225, 226, 35, 98, 224, 225, 226, 108, + /* 610 */ 109, 110, 115, 152, 117, 118, 27, 222, 49, 123, + /* 620 */ 24, 50, 51, 27, 0, 1, 2, 224, 225, 226, + /* 630 */ 166, 124, 168, 169, 239, 174, 175, 170, 171, 172, + /* 640 */ 22, 194, 71, 72, 73, 74, 75, 76, 77, 78, + /* 650 */ 79, 80, 81, 82, 83, 152, 85, 86, 87, 88, + /* 660 */ 89, 90, 91, 92, 93, 94, 95, 19, 22, 208, + /* 670 */ 24, 23, 195, 196, 170, 171, 172, 174, 175, 152, + /* 680 */ 26, 152, 152, 152, 207, 152, 97, 152, 23, 152, + /* 690 */ 51, 244, 152, 97, 152, 247, 248, 23, 50, 51, + /* 700 */ 26, 174, 175, 174, 175, 174, 175, 174, 175, 174, + /* 710 */ 175, 174, 175, 23, 174, 175, 174, 175, 188, 71, + /* 720 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + /* 730 */ 82, 83, 152, 85, 86, 87, 88, 89, 90, 91, + /* 740 */ 92, 93, 94, 95, 19, 152, 107, 152, 33, 24, + /* 750 */ 152, 100, 101, 27, 174, 175, 152, 42, 152, 23, + /* 760 */ 152, 26, 152, 23, 152, 26, 152, 174, 175, 174, + /* 770 */ 175, 152, 174, 175, 23, 50, 51, 26, 174, 175, + /* 780 */ 174, 175, 174, 175, 174, 175, 174, 175, 174, 175, + /* 790 */ 163, 119, 120, 174, 175, 19, 71, 72, 73, 74, + /* 800 */ 75, 76, 77, 78, 79, 80, 81, 82, 83, 152, + /* 810 */ 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + /* 820 */ 95, 66, 152, 97, 197, 23, 50, 51, 26, 53, + /* 830 */ 23, 174, 175, 26, 23, 23, 23, 26, 26, 26, + /* 840 */ 36, 106, 146, 147, 174, 175, 19, 71, 72, 73, + /* 850 */ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + /* 860 */ 152, 85, 86, 87, 88, 89, 90, 91, 92, 93, + /* 870 */ 94, 95, 152, 196, 119, 120, 19, 50, 51, 168, + /* 880 */ 169, 26, 174, 175, 207, 28, 152, 249, 250, 152, + /* 890 */ 163, 163, 163, 163, 174, 175, 163, 19, 71, 72, + /* 900 */ 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + /* 910 */ 83, 152, 85, 86, 87, 88, 89, 90, 91, 92, + /* 920 */ 93, 94, 95, 152, 197, 197, 197, 197, 50, 51, + /* 930 */ 197, 194, 36, 174, 175, 191, 192, 152, 191, 192, + /* 940 */ 163, 152, 66, 124, 152, 174, 175, 152, 19, 71, + /* 950 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + /* 960 */ 82, 83, 152, 85, 86, 87, 88, 89, 90, 91, + /* 970 */ 92, 93, 94, 95, 197, 152, 100, 188, 152, 50, + /* 980 */ 51, 152, 152, 188, 174, 175, 252, 152, 94, 95, + /* 990 */ 152, 152, 152, 1, 2, 152, 152, 174, 175, 19, + /* 1000 */ 152, 72, 73, 74, 75, 76, 77, 78, 79, 80, + /* 1010 */ 81, 82, 83, 152, 85, 86, 87, 88, 89, 90, + /* 1020 */ 91, 92, 93, 94, 95, 152, 188, 188, 22, 194, + /* 1030 */ 50, 51, 240, 173, 194, 174, 175, 252, 194, 152, + /* 1040 */ 36, 181, 28, 152, 23, 219, 122, 174, 175, 219, + /* 1050 */ 221, 152, 152, 73, 74, 75, 76, 77, 78, 79, + /* 1060 */ 80, 81, 82, 83, 152, 85, 86, 87, 88, 89, + /* 1070 */ 90, 91, 92, 93, 94, 95, 19, 20, 152, 22, + /* 1080 */ 23, 194, 152, 240, 27, 28, 174, 175, 240, 19, + /* 1090 */ 20, 26, 22, 194, 194, 38, 22, 27, 28, 152, + /* 1100 */ 23, 22, 152, 116, 174, 175, 152, 23, 38, 152, + /* 1110 */ 23, 152, 221, 152, 57, 152, 23, 163, 50, 51, + /* 1120 */ 194, 174, 175, 66, 174, 175, 69, 57, 174, 175, + /* 1130 */ 40, 174, 175, 174, 175, 174, 175, 174, 175, 69, + /* 1140 */ 22, 53, 74, 75, 30, 53, 89, 90, 22, 22, + /* 1150 */ 152, 197, 23, 96, 97, 98, 22, 152, 101, 89, + /* 1160 */ 90, 91, 208, 209, 152, 53, 96, 97, 98, 101, + /* 1170 */ 22, 101, 174, 175, 152, 19, 20, 105, 22, 174, + /* 1180 */ 175, 112, 19, 27, 28, 20, 174, 175, 24, 132, + /* 1190 */ 133, 134, 135, 136, 38, 44, 174, 175, 107, 61, + /* 1200 */ 54, 26, 132, 133, 134, 135, 136, 54, 107, 22, + /* 1210 */ 5, 140, 1, 57, 36, 111, 122, 28, 79, 79, + /* 1220 */ 131, 123, 66, 19, 20, 69, 22, 1, 16, 20, + /* 1230 */ 125, 27, 28, 123, 111, 120, 23, 131, 23, 16, + /* 1240 */ 68, 142, 38, 15, 22, 89, 90, 3, 167, 4, + /* 1250 */ 248, 251, 96, 97, 98, 180, 180, 101, 251, 151, + /* 1260 */ 6, 57, 151, 13, 151, 26, 25, 151, 161, 202, + /* 1270 */ 153, 162, 153, 69, 130, 128, 203, 19, 20, 127, + /* 1280 */ 22, 126, 204, 129, 22, 27, 28, 205, 132, 133, + /* 1290 */ 134, 135, 136, 89, 90, 231, 38, 95, 137, 179, + /* 1300 */ 96, 97, 98, 206, 179, 101, 122, 107, 159, 159, + /* 1310 */ 125, 231, 216, 228, 107, 57, 184, 217, 216, 176, + /* 1320 */ 217, 176, 48, 106, 18, 184, 158, 69, 159, 158, + /* 1330 */ 46, 71, 237, 176, 176, 176, 132, 133, 134, 135, + /* 1340 */ 136, 217, 176, 137, 216, 178, 158, 89, 90, 179, + /* 1350 */ 176, 159, 179, 159, 96, 97, 98, 159, 159, 101, + /* 1360 */ 5, 158, 202, 22, 18, 10, 11, 12, 13, 14, + /* 1370 */ 190, 238, 17, 190, 158, 193, 41, 159, 202, 193, + /* 1380 */ 159, 202, 245, 193, 193, 223, 190, 32, 159, 34, + /* 1390 */ 132, 133, 134, 135, 136, 159, 39, 155, 43, 150, + /* 1400 */ 223, 177, 201, 178, 177, 186, 66, 199, 177, 152, + /* 1410 */ 253, 56, 215, 152, 182, 152, 202, 152, 63, 152, + /* 1420 */ 152, 66, 67, 242, 229, 152, 174, 152, 152, 152, + /* 1430 */ 152, 152, 152, 152, 199, 242, 202, 152, 198, 152, + /* 1440 */ 152, 152, 183, 192, 152, 215, 152, 183, 215, 183, + /* 1450 */ 152, 241, 214, 152, 211, 152, 152, 211, 211, 152, + /* 1460 */ 152, 241, 152, 152, 152, 152, 152, 152, 152, 114, + /* 1470 */ 152, 152, 235, 152, 152, 152, 174, 187, 95, 174, + /* 1480 */ 253, 253, 253, 253, 236, 253, 253, 253, 253, 253, + /* 1490 */ 253, 253, 253, 253, 253, 253, 141, +}; +#define YY_SHIFT_USE_DFLT (-86) +#define YY_SHIFT_COUNT (429) +#define YY_SHIFT_MIN (-85) +#define YY_SHIFT_MAX (1383) static const short yy_shift_ofst[] = { - /* 0 */ 1143, 1188, 1417, 1188, 1287, 1287, 138, 138, -2, -19, - /* 10 */ 1287, 1287, 1287, 1287, 347, 362, 129, 129, 795, 1165, - /* 20 */ 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, - /* 30 */ 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, - /* 40 */ 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1310, 1287, - /* 50 */ 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, - /* 60 */ 1287, 1287, 286, 362, 362, 538, 538, 231, 1253, 55, - /* 70 */ 721, 647, 573, 499, 425, 351, 277, 203, 869, 869, - /* 80 */ 869, 869, 869, 869, 869, 869, 869, 869, 869, 869, - /* 90 */ 869, 869, 869, 943, 869, 1017, 1091, 1091, -69, -45, - /* 100 */ -45, -45, -45, -45, -1, 24, 245, 362, 362, 362, - /* 110 */ 362, 362, 362, 362, 362, 362, 362, 362, 362, 362, - /* 120 */ 362, 362, 362, 388, 356, 362, 362, 362, 362, 362, - /* 130 */ 732, 868, 231, 1051, 1471, -70, -70, -70, 1367, 57, - /* 140 */ 434, 434, 289, 291, 285, 1, 204, 572, 539, 362, - /* 150 */ 362, 362, 362, 362, 362, 362, 362, 362, 362, 362, - /* 160 */ 362, 362, 362, 362, 362, 362, 362, 362, 362, 362, - /* 170 */ 362, 362, 362, 362, 362, 362, 362, 362, 362, 362, - /* 180 */ 362, 506, 506, 506, 705, 1253, 1253, 1253, -70, -70, - /* 190 */ -70, 171, 171, 160, 502, 502, 502, 446, 432, 511, - /* 200 */ 422, 358, 335, -12, -12, -12, -12, 576, 294, -12, - /* 210 */ -12, 295, 595, 141, 600, 730, 723, 723, 805, 730, - /* 220 */ 805, 439, 911, 231, 865, 231, 865, 807, 865, 723, - /* 230 */ 766, 633, 633, 231, 284, 63, 608, 1481, 1308, 1308, - /* 240 */ 1472, 1472, 1308, 1477, 1427, 1275, 1487, 1487, 1487, 1487, - /* 250 */ 1308, 1461, 1275, 1477, 1427, 1427, 1275, 1308, 1461, 1355, - /* 260 */ 1441, 1308, 1308, 1461, 1308, 1461, 1308, 1461, 1442, 1348, - /* 270 */ 1348, 1348, 1408, 1375, 1375, 1442, 1348, 1357, 1348, 1408, - /* 280 */ 1348, 1348, 1316, 1331, 1316, 1331, 1316, 1331, 1308, 1308, - /* 290 */ 1280, 1288, 1289, 1285, 1279, 1275, 1253, 1336, 1346, 1346, - /* 300 */ 1338, 1338, 1338, 1338, -70, -70, -70, -70, -70, -70, - /* 310 */ 1013, 467, 612, 84, 179, -28, 870, 410, 761, 760, - /* 320 */ 667, 650, 531, 220, 361, 331, 125, 127, 97, 1306, - /* 330 */ 1300, 1270, 1151, 1272, 1203, 1232, 1261, 1244, 1148, 1174, - /* 340 */ 1139, 1156, 1124, 1220, 1115, 1210, 1233, 1099, 1193, 1184, - /* 350 */ 1174, 1173, 1029, 1121, 1120, 1085, 1162, 1119, 1037, 1152, - /* 360 */ 1147, 1129, 1046, 1011, 1093, 1098, 1075, 1061, 1032, 960, - /* 370 */ 1057, 1031, 1030, 899, 938, 982, 936, 972, 958, 910, - /* 380 */ 955, 875, 885, 908, 857, 859, 867, 804, 590, 834, - /* 390 */ 747, 818, 513, 611, 741, 673, 637, 611, 606, 603, - /* 400 */ 579, 501, 541, 468, 386, 445, 395, 376, 281, 185, - /* 410 */ 120, 92, 75, 45, 114, 25, 11, 5, + /* 0 */ 992, 1057, 1355, 1156, 1204, 1204, 1, 262, -19, 135, + /* 10 */ 135, 776, 1204, 1204, 1204, 1204, 69, 69, 53, 208, + /* 20 */ 283, 755, 58, 725, 648, 571, 494, 417, 340, 263, + /* 30 */ 212, 827, 827, 827, 827, 827, 827, 827, 827, 827, + /* 40 */ 827, 827, 827, 827, 827, 827, 878, 827, 929, 980, + /* 50 */ 980, 1070, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, + /* 60 */ 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, + /* 70 */ 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, + /* 80 */ 1258, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, + /* 90 */ 1204, 1204, 1204, 1204, -71, -47, -47, -47, -47, -47, + /* 100 */ 0, 29, -12, 283, 283, 139, 91, 392, 392, 894, + /* 110 */ 672, 726, 1383, -86, -86, -86, 88, 318, 318, 99, + /* 120 */ 381, -20, 283, 283, 283, 283, 283, 283, 283, 283, + /* 130 */ 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, + /* 140 */ 283, 283, 283, 283, 624, 876, 726, 672, 1340, 1340, + /* 150 */ 1340, 1340, 1340, 1340, -86, -86, -86, 305, 136, 136, + /* 160 */ 142, 167, 226, 154, 137, 152, 283, 283, 283, 283, + /* 170 */ 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, + /* 180 */ 283, 283, 283, 336, 336, 336, 283, 283, 352, 283, + /* 190 */ 283, 283, 283, 283, 228, 283, 283, 283, 283, 283, + /* 200 */ 283, 283, 283, 283, 283, 501, 569, 596, 596, 596, + /* 210 */ 507, 497, 441, 391, 353, 156, 156, 857, 353, 857, + /* 220 */ 735, 813, 639, 715, 156, 332, 715, 715, 496, 419, + /* 230 */ 646, 1357, 1184, 1184, 1335, 1335, 1184, 1341, 1260, 1144, + /* 240 */ 1346, 1346, 1346, 1346, 1184, 1306, 1144, 1341, 1260, 1260, + /* 250 */ 1144, 1184, 1306, 1206, 1284, 1184, 1184, 1306, 1184, 1306, + /* 260 */ 1184, 1306, 1262, 1207, 1207, 1207, 1274, 1262, 1207, 1217, + /* 270 */ 1207, 1274, 1207, 1207, 1185, 1200, 1185, 1200, 1185, 1200, + /* 280 */ 1184, 1184, 1161, 1262, 1202, 1202, 1262, 1154, 1155, 1147, + /* 290 */ 1152, 1144, 1241, 1239, 1250, 1250, 1254, 1254, 1254, 1254, + /* 300 */ -86, -86, -86, -86, -86, -86, 1068, 304, 526, 249, + /* 310 */ 408, -83, 434, 812, 27, 811, 807, 802, 751, 589, + /* 320 */ 651, 163, 131, 674, 366, 450, 299, 148, 23, 102, + /* 330 */ 229, -21, 1245, 1244, 1222, 1099, 1228, 1172, 1223, 1215, + /* 340 */ 1213, 1115, 1106, 1123, 1110, 1209, 1105, 1212, 1226, 1098, + /* 350 */ 1089, 1140, 1139, 1104, 1189, 1178, 1094, 1211, 1205, 1187, + /* 360 */ 1101, 1071, 1153, 1175, 1146, 1138, 1151, 1091, 1164, 1165, + /* 370 */ 1163, 1069, 1072, 1148, 1112, 1134, 1127, 1129, 1126, 1092, + /* 380 */ 1114, 1118, 1088, 1090, 1093, 1087, 1084, 987, 1079, 1077, + /* 390 */ 1074, 1065, 924, 1021, 1014, 1004, 1006, 819, 739, 896, + /* 400 */ 855, 804, 739, 740, 736, 690, 654, 665, 618, 582, + /* 410 */ 568, 528, 554, 379, 532, 479, 455, 379, 432, 371, + /* 420 */ 341, 28, 338, 116, -11, -57, -85, 7, -8, 3, }; -#define YY_REDUCE_USE_DFLT (-169) -#define YY_REDUCE_COUNT (309) -#define YY_REDUCE_MIN (-168) -#define YY_REDUCE_MAX (1397) +#define YY_REDUCE_USE_DFLT (-110) +#define YY_REDUCE_COUNT (305) +#define YY_REDUCE_MIN (-109) +#define YY_REDUCE_MAX (1323) static const short yy_reduce_ofst[] = { - /* 0 */ -141, 90, 1095, 222, 158, 156, 19, 17, 10, -104, - /* 10 */ 378, 316, 311, 12, 180, 249, 598, 464, 397, 1181, - /* 20 */ 1177, 1175, 1128, 1106, 1096, 1054, 1038, 974, 964, 962, - /* 30 */ 948, 905, 903, 900, 887, 874, 832, 826, 816, 813, - /* 40 */ 800, 758, 755, 752, 742, 739, 726, 685, 681, 668, - /* 50 */ 665, 652, 607, 604, 594, 591, 578, 530, 528, 526, - /* 60 */ 385, 18, 477, 466, 519, 444, 350, 435, 405, 488, - /* 70 */ 488, 488, 488, 488, 488, 488, 488, 488, 488, 488, - /* 80 */ 488, 488, 488, 488, 488, 488, 488, 488, 488, 488, - /* 90 */ 488, 488, 488, 488, 488, 488, 488, 488, 488, 488, - /* 100 */ 488, 488, 488, 488, 488, 488, 488, 1040, 678, 1036, - /* 110 */ 1007, 967, 966, 965, 845, 686, 610, 684, 317, 672, - /* 120 */ 893, 327, 623, 522, -7, 820, 814, 157, 154, 101, - /* 130 */ 702, 494, 580, 488, 488, 488, 488, 488, 614, 586, - /* 140 */ 935, 892, 968, 1245, 1242, 1234, 1225, 798, 798, 1222, - /* 150 */ 1221, 1218, 1214, 1213, 1212, 1202, 1195, 1191, 1161, 1158, - /* 160 */ 1140, 1135, 1123, 1112, 1107, 1100, 1080, 1074, 1073, 1072, - /* 170 */ 1070, 1067, 1048, 1044, 969, 968, 907, 906, 904, 894, - /* 180 */ 833, 837, 836, 340, 827, 815, 775, 68, 722, 646, - /* 190 */ -168, 1389, 1381, 1371, 1379, 1373, 1370, 1343, 1352, 1369, - /* 200 */ 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1325, 1320, 1352, - /* 210 */ 1352, 1343, 1380, 1353, 1397, 1351, 1339, 1334, 1319, 1341, - /* 220 */ 1303, 1364, 1359, 1368, 1362, 1366, 1360, 1350, 1354, 1318, - /* 230 */ 1313, 1307, 1305, 1363, 1328, 1324, 1372, 1278, 1361, 1358, - /* 240 */ 1277, 1276, 1356, 1296, 1322, 1309, 1317, 1315, 1314, 1312, - /* 250 */ 1345, 1347, 1302, 1292, 1311, 1304, 1293, 1337, 1335, 1252, - /* 260 */ 1248, 1332, 1330, 1329, 1327, 1326, 1323, 1321, 1297, 1301, - /* 270 */ 1295, 1294, 1290, 1243, 1240, 1284, 1291, 1286, 1283, 1274, - /* 280 */ 1281, 1271, 1238, 1241, 1236, 1235, 1227, 1226, 1267, 1266, - /* 290 */ 1189, 1229, 1223, 1211, 1206, 1201, 1197, 1239, 1237, 1219, - /* 300 */ 1216, 1209, 1208, 1185, 1089, 1086, 1087, 1137, 1136, 1164, + /* 0 */ 238, 954, 213, 289, 310, 234, 144, 317, -109, 382, + /* 10 */ 377, 303, 461, 389, 378, 368, 302, 294, 253, 395, + /* 20 */ 293, 324, 403, 403, 403, 403, 403, 403, 403, 403, + /* 30 */ 403, 403, 403, 403, 403, 403, 403, 403, 403, 403, + /* 40 */ 403, 403, 403, 403, 403, 403, 403, 403, 403, 403, + /* 50 */ 403, 1022, 1012, 1005, 998, 963, 961, 959, 957, 950, + /* 60 */ 947, 930, 912, 873, 861, 823, 810, 771, 759, 720, + /* 70 */ 708, 670, 657, 619, 614, 612, 610, 608, 606, 604, + /* 80 */ 598, 595, 593, 580, 542, 540, 537, 535, 533, 531, + /* 90 */ 529, 527, 503, 386, 403, 403, 403, 403, 403, 403, + /* 100 */ 403, 403, 403, 95, 447, 82, 334, 504, 467, 403, + /* 110 */ 477, 464, 403, 403, 403, 403, 860, 747, 744, 785, + /* 120 */ 638, 638, 926, 891, 900, 899, 887, 844, 840, 835, + /* 130 */ 848, 830, 843, 829, 792, 839, 826, 737, 838, 795, + /* 140 */ 789, 47, 734, 530, 696, 777, 711, 677, 733, 730, + /* 150 */ 729, 728, 727, 627, 448, 64, 187, 1305, 1302, 1252, + /* 160 */ 1290, 1273, 1323, 1322, 1321, 1319, 1318, 1316, 1315, 1314, + /* 170 */ 1313, 1312, 1311, 1310, 1308, 1307, 1304, 1303, 1301, 1298, + /* 180 */ 1294, 1292, 1289, 1266, 1264, 1259, 1288, 1287, 1238, 1285, + /* 190 */ 1281, 1280, 1279, 1278, 1251, 1277, 1276, 1275, 1273, 1268, + /* 200 */ 1267, 1265, 1263, 1261, 1257, 1248, 1237, 1247, 1246, 1243, + /* 210 */ 1238, 1240, 1235, 1249, 1234, 1233, 1230, 1220, 1214, 1210, + /* 220 */ 1225, 1219, 1232, 1231, 1197, 1195, 1227, 1224, 1201, 1208, + /* 230 */ 1242, 1137, 1236, 1229, 1193, 1181, 1221, 1177, 1196, 1179, + /* 240 */ 1191, 1190, 1186, 1182, 1218, 1216, 1176, 1162, 1183, 1180, + /* 250 */ 1160, 1199, 1203, 1133, 1095, 1198, 1194, 1188, 1192, 1171, + /* 260 */ 1169, 1168, 1173, 1174, 1166, 1159, 1141, 1170, 1158, 1167, + /* 270 */ 1157, 1132, 1145, 1143, 1124, 1128, 1103, 1102, 1100, 1096, + /* 280 */ 1150, 1149, 1085, 1125, 1080, 1064, 1120, 1097, 1082, 1078, + /* 290 */ 1073, 1067, 1109, 1107, 1119, 1117, 1116, 1113, 1111, 1108, + /* 300 */ 1007, 1000, 1002, 1076, 1075, 1081, }; static const YYACTIONTYPE yy_default[] = { - /* 0 */ 633, 867, 955, 955, 867, 867, 955, 955, 955, 757, - /* 10 */ 955, 955, 955, 865, 955, 955, 785, 785, 929, 955, - /* 20 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 30 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 40 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 50 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 60 */ 955, 955, 955, 955, 955, 955, 955, 672, 761, 791, - /* 70 */ 955, 955, 955, 955, 955, 955, 955, 955, 928, 930, - /* 80 */ 799, 798, 908, 772, 796, 789, 793, 868, 861, 862, - /* 90 */ 860, 864, 869, 955, 792, 828, 845, 827, 839, 844, - /* 100 */ 851, 843, 840, 830, 829, 831, 832, 955, 955, 955, - /* 110 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 120 */ 955, 955, 955, 659, 726, 955, 955, 955, 955, 955, - /* 130 */ 955, 955, 955, 833, 834, 848, 847, 846, 955, 664, - /* 140 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 150 */ 935, 933, 955, 880, 955, 955, 955, 955, 955, 955, - /* 160 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 170 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 180 */ 639, 757, 757, 757, 633, 955, 955, 955, 947, 761, - /* 190 */ 751, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 200 */ 955, 955, 955, 801, 740, 918, 920, 955, 901, 738, - /* 210 */ 661, 759, 674, 749, 641, 795, 774, 774, 913, 795, - /* 220 */ 913, 697, 720, 955, 785, 955, 785, 694, 785, 774, - /* 230 */ 863, 955, 955, 955, 758, 749, 955, 940, 765, 765, - /* 240 */ 932, 932, 765, 807, 730, 795, 737, 737, 737, 737, - /* 250 */ 765, 656, 795, 807, 730, 730, 795, 765, 656, 907, - /* 260 */ 905, 765, 765, 656, 765, 656, 765, 656, 873, 728, - /* 270 */ 728, 728, 712, 877, 877, 873, 728, 697, 728, 712, - /* 280 */ 728, 728, 778, 773, 778, 773, 778, 773, 765, 765, - /* 290 */ 955, 790, 779, 788, 786, 795, 955, 715, 649, 649, - /* 300 */ 638, 638, 638, 638, 952, 952, 947, 699, 699, 682, - /* 310 */ 955, 955, 955, 955, 955, 955, 955, 882, 955, 955, - /* 320 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 330 */ 634, 942, 955, 955, 939, 955, 955, 955, 955, 800, - /* 340 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 350 */ 917, 955, 955, 955, 955, 955, 955, 955, 911, 955, - /* 360 */ 955, 955, 955, 955, 955, 904, 903, 955, 955, 955, - /* 370 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 380 */ 955, 955, 955, 955, 955, 955, 955, 955, 955, 955, - /* 390 */ 955, 955, 955, 787, 955, 780, 955, 866, 955, 955, - /* 400 */ 955, 955, 955, 955, 955, 955, 955, 955, 743, 816, - /* 410 */ 955, 815, 819, 814, 666, 955, 647, 955, 630, 635, - /* 420 */ 951, 954, 953, 950, 949, 948, 943, 941, 938, 937, - /* 430 */ 936, 934, 931, 927, 886, 884, 891, 890, 889, 888, - /* 440 */ 887, 885, 883, 881, 802, 797, 794, 926, 879, 739, - /* 450 */ 736, 735, 655, 944, 910, 919, 806, 805, 808, 916, - /* 460 */ 915, 914, 912, 909, 896, 804, 803, 731, 871, 870, - /* 470 */ 658, 900, 899, 898, 902, 906, 897, 767, 657, 654, - /* 480 */ 663, 718, 719, 727, 725, 724, 723, 722, 721, 717, - /* 490 */ 665, 673, 711, 696, 695, 876, 878, 875, 874, 704, - /* 500 */ 703, 709, 708, 707, 706, 705, 702, 701, 700, 693, - /* 510 */ 692, 698, 691, 714, 713, 710, 690, 734, 733, 732, - /* 520 */ 729, 689, 688, 687, 819, 686, 685, 825, 824, 812, - /* 530 */ 855, 754, 753, 752, 764, 763, 776, 775, 810, 809, - /* 540 */ 777, 762, 756, 755, 771, 770, 769, 768, 760, 750, - /* 550 */ 782, 784, 783, 781, 857, 766, 854, 925, 924, 923, - /* 560 */ 922, 921, 859, 858, 826, 823, 677, 678, 894, 893, - /* 570 */ 895, 892, 680, 679, 676, 675, 856, 745, 744, 852, - /* 580 */ 849, 841, 837, 853, 850, 842, 838, 836, 835, 821, - /* 590 */ 820, 818, 817, 813, 822, 668, 746, 742, 741, 811, - /* 600 */ 748, 747, 684, 683, 681, 662, 660, 653, 651, 650, - /* 610 */ 652, 648, 646, 645, 644, 643, 642, 671, 670, 669, - /* 620 */ 667, 666, 640, 637, 636, 632, 631, 629, + /* 0 */ 647, 964, 964, 964, 878, 878, 969, 964, 774, 802, + /* 10 */ 802, 938, 969, 969, 969, 876, 969, 969, 969, 964, + /* 20 */ 969, 778, 808, 969, 969, 969, 969, 969, 969, 969, + /* 30 */ 969, 937, 939, 816, 815, 918, 789, 813, 806, 810, + /* 40 */ 879, 872, 873, 871, 875, 880, 969, 809, 841, 856, + /* 50 */ 840, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 60 */ 969, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 70 */ 969, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 80 */ 969, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 90 */ 969, 969, 969, 969, 850, 855, 862, 854, 851, 843, + /* 100 */ 842, 844, 845, 969, 969, 673, 739, 969, 969, 846, + /* 110 */ 969, 685, 847, 859, 858, 857, 680, 969, 969, 969, + /* 120 */ 969, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 130 */ 969, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 140 */ 969, 969, 969, 969, 647, 964, 969, 969, 964, 964, + /* 150 */ 964, 964, 964, 964, 956, 778, 768, 969, 969, 969, + /* 160 */ 969, 969, 969, 969, 969, 969, 969, 944, 942, 969, + /* 170 */ 891, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 180 */ 969, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 190 */ 969, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 200 */ 969, 969, 969, 969, 653, 969, 911, 774, 774, 774, + /* 210 */ 776, 754, 766, 655, 812, 791, 791, 923, 812, 923, + /* 220 */ 710, 733, 707, 802, 791, 874, 802, 802, 775, 766, + /* 230 */ 969, 949, 782, 782, 941, 941, 782, 821, 743, 812, + /* 240 */ 750, 750, 750, 750, 782, 670, 812, 821, 743, 743, + /* 250 */ 812, 782, 670, 917, 915, 782, 782, 670, 782, 670, + /* 260 */ 782, 670, 884, 741, 741, 741, 725, 884, 741, 710, + /* 270 */ 741, 725, 741, 741, 795, 790, 795, 790, 795, 790, + /* 280 */ 782, 782, 969, 884, 888, 888, 884, 807, 796, 805, + /* 290 */ 803, 812, 676, 728, 663, 663, 652, 652, 652, 652, + /* 300 */ 961, 961, 956, 712, 712, 695, 969, 969, 969, 969, + /* 310 */ 969, 969, 687, 969, 893, 969, 969, 969, 969, 969, + /* 320 */ 969, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 330 */ 969, 828, 969, 648, 951, 969, 969, 948, 969, 969, + /* 340 */ 969, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 350 */ 969, 969, 969, 969, 969, 969, 921, 969, 969, 969, + /* 360 */ 969, 969, 969, 914, 913, 969, 969, 969, 969, 969, + /* 370 */ 969, 969, 969, 969, 969, 969, 969, 969, 969, 969, + /* 380 */ 969, 969, 969, 969, 969, 969, 969, 757, 969, 969, + /* 390 */ 969, 761, 969, 969, 969, 969, 969, 969, 804, 969, + /* 400 */ 797, 969, 877, 969, 969, 969, 969, 969, 969, 969, + /* 410 */ 969, 969, 969, 966, 969, 969, 969, 965, 969, 969, + /* 420 */ 969, 969, 969, 830, 969, 829, 833, 969, 661, 969, + /* 430 */ 644, 649, 960, 963, 962, 959, 958, 957, 952, 950, + /* 440 */ 947, 946, 945, 943, 940, 936, 897, 895, 902, 901, + /* 450 */ 900, 899, 898, 896, 894, 892, 818, 817, 814, 811, + /* 460 */ 753, 935, 890, 752, 749, 748, 669, 953, 920, 929, + /* 470 */ 928, 927, 822, 926, 925, 924, 922, 919, 906, 820, + /* 480 */ 819, 744, 882, 881, 672, 910, 909, 908, 912, 916, + /* 490 */ 907, 784, 751, 671, 668, 675, 679, 731, 732, 740, + /* 500 */ 738, 737, 736, 735, 734, 730, 681, 686, 724, 709, + /* 510 */ 708, 717, 716, 722, 721, 720, 719, 718, 715, 714, + /* 520 */ 713, 706, 705, 711, 704, 727, 726, 723, 703, 747, + /* 530 */ 746, 745, 742, 702, 701, 700, 833, 699, 698, 838, + /* 540 */ 837, 866, 826, 755, 759, 758, 762, 763, 771, 770, + /* 550 */ 769, 780, 781, 793, 792, 824, 823, 794, 779, 773, + /* 560 */ 772, 788, 787, 786, 785, 777, 767, 799, 798, 868, + /* 570 */ 783, 867, 865, 934, 933, 932, 931, 930, 870, 967, + /* 580 */ 968, 887, 889, 886, 801, 800, 885, 869, 839, 836, + /* 590 */ 690, 691, 905, 904, 903, 693, 692, 689, 688, 863, + /* 600 */ 860, 852, 864, 861, 853, 849, 848, 834, 832, 831, + /* 610 */ 827, 835, 760, 756, 825, 765, 764, 697, 696, 694, + /* 620 */ 678, 677, 674, 667, 665, 664, 666, 662, 660, 659, + /* 630 */ 658, 657, 656, 684, 683, 682, 654, 651, 650, 646, + /* 640 */ 645, 643, }; /* The next table maps tokens into fallback tokens. If a construct ** like the following: ** @@ -113175,75 +123467,78 @@ */ #ifdef YYFALLBACK static const YYCODETYPE yyFallback[] = { 0, /* $ => nothing */ 0, /* SEMI => nothing */ - 26, /* EXPLAIN => ID */ - 26, /* QUERY => ID */ - 26, /* PLAN => ID */ - 26, /* BEGIN => ID */ + 27, /* EXPLAIN => ID */ + 27, /* QUERY => ID */ + 27, /* PLAN => ID */ + 27, /* BEGIN => ID */ 0, /* TRANSACTION => nothing */ - 26, /* DEFERRED => ID */ - 26, /* IMMEDIATE => ID */ - 26, /* EXCLUSIVE => ID */ + 27, /* DEFERRED => ID */ + 27, /* IMMEDIATE => ID */ + 27, /* EXCLUSIVE => ID */ 0, /* COMMIT => nothing */ - 26, /* END => ID */ - 26, /* ROLLBACK => ID */ - 26, /* SAVEPOINT => ID */ - 26, /* RELEASE => ID */ + 27, /* END => ID */ + 27, /* ROLLBACK => ID */ + 27, /* SAVEPOINT => ID */ + 27, /* RELEASE => ID */ 0, /* TO => nothing */ 0, /* TABLE => nothing */ 0, /* CREATE => nothing */ - 26, /* IF => ID */ + 27, /* IF => ID */ 0, /* NOT => nothing */ 0, /* EXISTS => nothing */ - 26, /* TEMP => ID */ + 27, /* TEMP => ID */ 0, /* LP => nothing */ 0, /* RP => nothing */ 0, /* AS => nothing */ + 27, /* WITHOUT => ID */ 0, /* COMMA => nothing */ 0, /* ID => nothing */ 0, /* INDEXED => nothing */ - 26, /* ABORT => ID */ - 26, /* ACTION => ID */ - 26, /* AFTER => ID */ - 26, /* ANALYZE => ID */ - 26, /* ASC => ID */ - 26, /* ATTACH => ID */ - 26, /* BEFORE => ID */ - 26, /* BY => ID */ - 26, /* CASCADE => ID */ - 26, /* CAST => ID */ - 26, /* COLUMNKW => ID */ - 26, /* CONFLICT => ID */ - 26, /* DATABASE => ID */ - 26, /* DESC => ID */ - 26, /* DETACH => ID */ - 26, /* EACH => ID */ - 26, /* FAIL => ID */ - 26, /* FOR => ID */ - 26, /* IGNORE => ID */ - 26, /* INITIALLY => ID */ - 26, /* INSTEAD => ID */ - 26, /* LIKE_KW => ID */ - 26, /* MATCH => ID */ - 26, /* NO => ID */ - 26, /* KEY => ID */ - 26, /* OF => ID */ - 26, /* OFFSET => ID */ - 26, /* PRAGMA => ID */ - 26, /* RAISE => ID */ - 26, /* REPLACE => ID */ - 26, /* RESTRICT => ID */ - 26, /* ROW => ID */ - 26, /* TRIGGER => ID */ - 26, /* VACUUM => ID */ - 26, /* VIEW => ID */ - 26, /* VIRTUAL => ID */ - 26, /* REINDEX => ID */ - 26, /* RENAME => ID */ - 26, /* CTIME_KW => ID */ + 27, /* ABORT => ID */ + 27, /* ACTION => ID */ + 27, /* AFTER => ID */ + 27, /* ANALYZE => ID */ + 27, /* ASC => ID */ + 27, /* ATTACH => ID */ + 27, /* BEFORE => ID */ + 27, /* BY => ID */ + 27, /* CASCADE => ID */ + 27, /* CAST => ID */ + 27, /* COLUMNKW => ID */ + 27, /* CONFLICT => ID */ + 27, /* DATABASE => ID */ + 27, /* DESC => ID */ + 27, /* DETACH => ID */ + 27, /* EACH => ID */ + 27, /* FAIL => ID */ + 27, /* FOR => ID */ + 27, /* IGNORE => ID */ + 27, /* INITIALLY => ID */ + 27, /* INSTEAD => ID */ + 27, /* LIKE_KW => ID */ + 27, /* MATCH => ID */ + 27, /* NO => ID */ + 27, /* KEY => ID */ + 27, /* OF => ID */ + 27, /* OFFSET => ID */ + 27, /* PRAGMA => ID */ + 27, /* RAISE => ID */ + 27, /* RECURSIVE => ID */ + 27, /* REPLACE => ID */ + 27, /* RESTRICT => ID */ + 27, /* ROW => ID */ + 27, /* TRIGGER => ID */ + 27, /* VACUUM => ID */ + 27, /* VIEW => ID */ + 27, /* VIRTUAL => ID */ + 27, /* WITH => ID */ + 27, /* REINDEX => ID */ + 27, /* RENAME => ID */ + 27, /* CTIME_KW => ID */ }; #endif /* YYFALLBACK */ /* The following structure represents a single element of the ** parser's stack. Information stored includes: @@ -113324,67 +123619,68 @@ "PLAN", "BEGIN", "TRANSACTION", "DEFERRED", "IMMEDIATE", "EXCLUSIVE", "COMMIT", "END", "ROLLBACK", "SAVEPOINT", "RELEASE", "TO", "TABLE", "CREATE", "IF", "NOT", "EXISTS", "TEMP", "LP", "RP", - "AS", "COMMA", "ID", "INDEXED", - "ABORT", "ACTION", "AFTER", "ANALYZE", - "ASC", "ATTACH", "BEFORE", "BY", - "CASCADE", "CAST", "COLUMNKW", "CONFLICT", - "DATABASE", "DESC", "DETACH", "EACH", - "FAIL", "FOR", "IGNORE", "INITIALLY", - "INSTEAD", "LIKE_KW", "MATCH", "NO", - "KEY", "OF", "OFFSET", "PRAGMA", - "RAISE", "REPLACE", "RESTRICT", "ROW", - "TRIGGER", "VACUUM", "VIEW", "VIRTUAL", - "REINDEX", "RENAME", "CTIME_KW", "ANY", - "OR", "AND", "IS", "BETWEEN", - "IN", "ISNULL", "NOTNULL", "NE", - "EQ", "GT", "LE", "LT", - "GE", "ESCAPE", "BITAND", "BITOR", - "LSHIFT", "RSHIFT", "PLUS", "MINUS", - "STAR", "SLASH", "REM", "CONCAT", - "COLLATE", "BITNOT", "STRING", "JOIN_KW", - "CONSTRAINT", "DEFAULT", "NULL", "PRIMARY", - "UNIQUE", "CHECK", "REFERENCES", "AUTOINCR", - "ON", "INSERT", "DELETE", "UPDATE", - "SET", "DEFERRABLE", "FOREIGN", "DROP", - "UNION", "ALL", "EXCEPT", "INTERSECT", - "SELECT", "DISTINCT", "DOT", "FROM", + "AS", "WITHOUT", "COMMA", "ID", + "INDEXED", "ABORT", "ACTION", "AFTER", + "ANALYZE", "ASC", "ATTACH", "BEFORE", + "BY", "CASCADE", "CAST", "COLUMNKW", + "CONFLICT", "DATABASE", "DESC", "DETACH", + "EACH", "FAIL", "FOR", "IGNORE", + "INITIALLY", "INSTEAD", "LIKE_KW", "MATCH", + "NO", "KEY", "OF", "OFFSET", + "PRAGMA", "RAISE", "RECURSIVE", "REPLACE", + "RESTRICT", "ROW", "TRIGGER", "VACUUM", + "VIEW", "VIRTUAL", "WITH", "REINDEX", + "RENAME", "CTIME_KW", "ANY", "OR", + "AND", "IS", "BETWEEN", "IN", + "ISNULL", "NOTNULL", "NE", "EQ", + "GT", "LE", "LT", "GE", + "ESCAPE", "BITAND", "BITOR", "LSHIFT", + "RSHIFT", "PLUS", "MINUS", "STAR", + "SLASH", "REM", "CONCAT", "COLLATE", + "BITNOT", "STRING", "JOIN_KW", "CONSTRAINT", + "DEFAULT", "NULL", "PRIMARY", "UNIQUE", + "CHECK", "REFERENCES", "AUTOINCR", "ON", + "INSERT", "DELETE", "UPDATE", "SET", + "DEFERRABLE", "FOREIGN", "DROP", "UNION", + "ALL", "EXCEPT", "INTERSECT", "SELECT", + "VALUES", "DISTINCT", "DOT", "FROM", "JOIN", "USING", "ORDER", "GROUP", "HAVING", "LIMIT", "WHERE", "INTO", - "VALUES", "INTEGER", "FLOAT", "BLOB", - "REGISTER", "VARIABLE", "CASE", "WHEN", - "THEN", "ELSE", "INDEX", "ALTER", - "ADD", "error", "input", "cmdlist", - "ecmd", "explain", "cmdx", "cmd", - "transtype", "trans_opt", "nm", "savepoint_opt", - "create_table", "create_table_args", "createkw", "temp", - "ifnotexists", "dbnm", "columnlist", "conslist_opt", - "select", "column", "columnid", "type", - "carglist", "id", "ids", "typetoken", - "typename", "signed", "plus_num", "minus_num", - "ccons", "term", "expr", "onconf", - "sortorder", "autoinc", "idxlist_opt", "refargs", - "defer_subclause", "refarg", "refact", "init_deferred_pred_opt", - "conslist", "tconscomma", "tcons", "idxlist", - "defer_subclause_opt", "orconf", "resolvetype", "raisetype", - "ifexists", "fullname", "oneselect", "multiselect_op", - "distinct", "selcollist", "from", "where_opt", - "groupby_opt", "having_opt", "orderby_opt", "limit_opt", - "sclp", "as", "seltablist", "stl_prefix", - "joinop", "indexed_opt", "on_opt", "using_opt", - "joinop2", "inscollist", "sortlist", "nexprlist", - "setlist", "insert_cmd", "inscollist_opt", "valuelist", - "exprlist", "likeop", "between_op", "in_op", - "case_operand", "case_exprlist", "case_else", "uniqueflag", - "collate", "nmnum", "number", "trigger_decl", - "trigger_cmd_list", "trigger_time", "trigger_event", "foreach_clause", - "when_clause", "trigger_cmd", "trnm", "tridxby", - "database_kw_opt", "key_opt", "add_column_fullname", "kwcolumn_opt", - "create_vtab", "vtabarglist", "vtabarg", "vtabargtoken", - "lp", "anylist", + "INTEGER", "FLOAT", "BLOB", "VARIABLE", + "CASE", "WHEN", "THEN", "ELSE", + "INDEX", "ALTER", "ADD", "error", + "input", "cmdlist", "ecmd", "explain", + "cmdx", "cmd", "transtype", "trans_opt", + "nm", "savepoint_opt", "create_table", "create_table_args", + "createkw", "temp", "ifnotexists", "dbnm", + "columnlist", "conslist_opt", "table_options", "select", + "column", "columnid", "type", "carglist", + "typetoken", "typename", "signed", "plus_num", + "minus_num", "ccons", "term", "expr", + "onconf", "sortorder", "autoinc", "idxlist_opt", + "refargs", "defer_subclause", "refarg", "refact", + "init_deferred_pred_opt", "conslist", "tconscomma", "tcons", + "idxlist", "defer_subclause_opt", "orconf", "resolvetype", + "raisetype", "ifexists", "fullname", "selectnowith", + "oneselect", "with", "multiselect_op", "distinct", + "selcollist", "from", "where_opt", "groupby_opt", + "having_opt", "orderby_opt", "limit_opt", "values", + "nexprlist", "exprlist", "sclp", "as", + "seltablist", "stl_prefix", "joinop", "indexed_opt", + "on_opt", "using_opt", "joinop2", "idlist", + "sortlist", "setlist", "insert_cmd", "inscollist_opt", + "likeop", "between_op", "in_op", "case_operand", + "case_exprlist", "case_else", "uniqueflag", "collate", + "nmnum", "trigger_decl", "trigger_cmd_list", "trigger_time", + "trigger_event", "foreach_clause", "when_clause", "trigger_cmd", + "trnm", "tridxby", "database_kw_opt", "key_opt", + "add_column_fullname", "kwcolumn_opt", "create_vtab", "vtabarglist", + "vtabarg", "vtabargtoken", "lp", "anylist", + "wqlist", }; #endif /* NDEBUG */ #ifndef NDEBUG /* For tracing reduce actions, the names of all rules are required. @@ -113420,305 +123716,305 @@ /* 27 */ "createkw ::= CREATE", /* 28 */ "ifnotexists ::=", /* 29 */ "ifnotexists ::= IF NOT EXISTS", /* 30 */ "temp ::= TEMP", /* 31 */ "temp ::=", - /* 32 */ "create_table_args ::= LP columnlist conslist_opt RP", + /* 32 */ "create_table_args ::= LP columnlist conslist_opt RP table_options", /* 33 */ "create_table_args ::= AS select", - /* 34 */ "columnlist ::= columnlist COMMA column", - /* 35 */ "columnlist ::= column", - /* 36 */ "column ::= columnid type carglist", - /* 37 */ "columnid ::= nm", - /* 38 */ "id ::= ID", - /* 39 */ "id ::= INDEXED", - /* 40 */ "ids ::= ID|STRING", - /* 41 */ "nm ::= id", - /* 42 */ "nm ::= STRING", - /* 43 */ "nm ::= JOIN_KW", - /* 44 */ "type ::=", - /* 45 */ "type ::= typetoken", - /* 46 */ "typetoken ::= typename", - /* 47 */ "typetoken ::= typename LP signed RP", - /* 48 */ "typetoken ::= typename LP signed COMMA signed RP", - /* 49 */ "typename ::= ids", - /* 50 */ "typename ::= typename ids", - /* 51 */ "signed ::= plus_num", - /* 52 */ "signed ::= minus_num", - /* 53 */ "carglist ::= carglist ccons", - /* 54 */ "carglist ::=", - /* 55 */ "ccons ::= CONSTRAINT nm", - /* 56 */ "ccons ::= DEFAULT term", - /* 57 */ "ccons ::= DEFAULT LP expr RP", - /* 58 */ "ccons ::= DEFAULT PLUS term", - /* 59 */ "ccons ::= DEFAULT MINUS term", - /* 60 */ "ccons ::= DEFAULT id", - /* 61 */ "ccons ::= NULL onconf", - /* 62 */ "ccons ::= NOT NULL onconf", - /* 63 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", - /* 64 */ "ccons ::= UNIQUE onconf", - /* 65 */ "ccons ::= CHECK LP expr RP", - /* 66 */ "ccons ::= REFERENCES nm idxlist_opt refargs", - /* 67 */ "ccons ::= defer_subclause", - /* 68 */ "ccons ::= COLLATE ids", - /* 69 */ "autoinc ::=", - /* 70 */ "autoinc ::= AUTOINCR", - /* 71 */ "refargs ::=", - /* 72 */ "refargs ::= refargs refarg", - /* 73 */ "refarg ::= MATCH nm", - /* 74 */ "refarg ::= ON INSERT refact", - /* 75 */ "refarg ::= ON DELETE refact", - /* 76 */ "refarg ::= ON UPDATE refact", - /* 77 */ "refact ::= SET NULL", - /* 78 */ "refact ::= SET DEFAULT", - /* 79 */ "refact ::= CASCADE", - /* 80 */ "refact ::= RESTRICT", - /* 81 */ "refact ::= NO ACTION", - /* 82 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", - /* 83 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", - /* 84 */ "init_deferred_pred_opt ::=", - /* 85 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", - /* 86 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", - /* 87 */ "conslist_opt ::=", - /* 88 */ "conslist_opt ::= COMMA conslist", - /* 89 */ "conslist ::= conslist tconscomma tcons", - /* 90 */ "conslist ::= tcons", - /* 91 */ "tconscomma ::= COMMA", - /* 92 */ "tconscomma ::=", - /* 93 */ "tcons ::= CONSTRAINT nm", - /* 94 */ "tcons ::= PRIMARY KEY LP idxlist autoinc RP onconf", - /* 95 */ "tcons ::= UNIQUE LP idxlist RP onconf", - /* 96 */ "tcons ::= CHECK LP expr RP onconf", - /* 97 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt", - /* 98 */ "defer_subclause_opt ::=", - /* 99 */ "defer_subclause_opt ::= defer_subclause", - /* 100 */ "onconf ::=", - /* 101 */ "onconf ::= ON CONFLICT resolvetype", - /* 102 */ "orconf ::=", - /* 103 */ "orconf ::= OR resolvetype", - /* 104 */ "resolvetype ::= raisetype", - /* 105 */ "resolvetype ::= IGNORE", - /* 106 */ "resolvetype ::= REPLACE", - /* 107 */ "cmd ::= DROP TABLE ifexists fullname", - /* 108 */ "ifexists ::= IF EXISTS", - /* 109 */ "ifexists ::=", - /* 110 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm AS select", - /* 111 */ "cmd ::= DROP VIEW ifexists fullname", - /* 112 */ "cmd ::= select", - /* 113 */ "select ::= oneselect", - /* 114 */ "select ::= select multiselect_op oneselect", + /* 34 */ "table_options ::=", + /* 35 */ "table_options ::= WITHOUT nm", + /* 36 */ "columnlist ::= columnlist COMMA column", + /* 37 */ "columnlist ::= column", + /* 38 */ "column ::= columnid type carglist", + /* 39 */ "columnid ::= nm", + /* 40 */ "nm ::= ID|INDEXED", + /* 41 */ "nm ::= STRING", + /* 42 */ "nm ::= JOIN_KW", + /* 43 */ "type ::=", + /* 44 */ "type ::= typetoken", + /* 45 */ "typetoken ::= typename", + /* 46 */ "typetoken ::= typename LP signed RP", + /* 47 */ "typetoken ::= typename LP signed COMMA signed RP", + /* 48 */ "typename ::= ID|STRING", + /* 49 */ "typename ::= typename ID|STRING", + /* 50 */ "signed ::= plus_num", + /* 51 */ "signed ::= minus_num", + /* 52 */ "carglist ::= carglist ccons", + /* 53 */ "carglist ::=", + /* 54 */ "ccons ::= CONSTRAINT nm", + /* 55 */ "ccons ::= DEFAULT term", + /* 56 */ "ccons ::= DEFAULT LP expr RP", + /* 57 */ "ccons ::= DEFAULT PLUS term", + /* 58 */ "ccons ::= DEFAULT MINUS term", + /* 59 */ "ccons ::= DEFAULT ID|INDEXED", + /* 60 */ "ccons ::= NULL onconf", + /* 61 */ "ccons ::= NOT NULL onconf", + /* 62 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", + /* 63 */ "ccons ::= UNIQUE onconf", + /* 64 */ "ccons ::= CHECK LP expr RP", + /* 65 */ "ccons ::= REFERENCES nm idxlist_opt refargs", + /* 66 */ "ccons ::= defer_subclause", + /* 67 */ "ccons ::= COLLATE ID|STRING", + /* 68 */ "autoinc ::=", + /* 69 */ "autoinc ::= AUTOINCR", + /* 70 */ "refargs ::=", + /* 71 */ "refargs ::= refargs refarg", + /* 72 */ "refarg ::= MATCH nm", + /* 73 */ "refarg ::= ON INSERT refact", + /* 74 */ "refarg ::= ON DELETE refact", + /* 75 */ "refarg ::= ON UPDATE refact", + /* 76 */ "refact ::= SET NULL", + /* 77 */ "refact ::= SET DEFAULT", + /* 78 */ "refact ::= CASCADE", + /* 79 */ "refact ::= RESTRICT", + /* 80 */ "refact ::= NO ACTION", + /* 81 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", + /* 82 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", + /* 83 */ "init_deferred_pred_opt ::=", + /* 84 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", + /* 85 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", + /* 86 */ "conslist_opt ::=", + /* 87 */ "conslist_opt ::= COMMA conslist", + /* 88 */ "conslist ::= conslist tconscomma tcons", + /* 89 */ "conslist ::= tcons", + /* 90 */ "tconscomma ::= COMMA", + /* 91 */ "tconscomma ::=", + /* 92 */ "tcons ::= CONSTRAINT nm", + /* 93 */ "tcons ::= PRIMARY KEY LP idxlist autoinc RP onconf", + /* 94 */ "tcons ::= UNIQUE LP idxlist RP onconf", + /* 95 */ "tcons ::= CHECK LP expr RP onconf", + /* 96 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt", + /* 97 */ "defer_subclause_opt ::=", + /* 98 */ "defer_subclause_opt ::= defer_subclause", + /* 99 */ "onconf ::=", + /* 100 */ "onconf ::= ON CONFLICT resolvetype", + /* 101 */ "orconf ::=", + /* 102 */ "orconf ::= OR resolvetype", + /* 103 */ "resolvetype ::= raisetype", + /* 104 */ "resolvetype ::= IGNORE", + /* 105 */ "resolvetype ::= REPLACE", + /* 106 */ "cmd ::= DROP TABLE ifexists fullname", + /* 107 */ "ifexists ::= IF EXISTS", + /* 108 */ "ifexists ::=", + /* 109 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm AS select", + /* 110 */ "cmd ::= DROP VIEW ifexists fullname", + /* 111 */ "cmd ::= select", + /* 112 */ "select ::= with selectnowith", + /* 113 */ "selectnowith ::= oneselect", + /* 114 */ "selectnowith ::= selectnowith multiselect_op oneselect", /* 115 */ "multiselect_op ::= UNION", /* 116 */ "multiselect_op ::= UNION ALL", /* 117 */ "multiselect_op ::= EXCEPT|INTERSECT", /* 118 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", - /* 119 */ "distinct ::= DISTINCT", - /* 120 */ "distinct ::= ALL", - /* 121 */ "distinct ::=", - /* 122 */ "sclp ::= selcollist COMMA", - /* 123 */ "sclp ::=", - /* 124 */ "selcollist ::= sclp expr as", - /* 125 */ "selcollist ::= sclp STAR", - /* 126 */ "selcollist ::= sclp nm DOT STAR", - /* 127 */ "as ::= AS nm", - /* 128 */ "as ::= ids", - /* 129 */ "as ::=", - /* 130 */ "from ::=", - /* 131 */ "from ::= FROM seltablist", - /* 132 */ "stl_prefix ::= seltablist joinop", - /* 133 */ "stl_prefix ::=", - /* 134 */ "seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt", - /* 135 */ "seltablist ::= stl_prefix LP select RP as on_opt using_opt", - /* 136 */ "seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt", - /* 137 */ "dbnm ::=", - /* 138 */ "dbnm ::= DOT nm", - /* 139 */ "fullname ::= nm dbnm", - /* 140 */ "joinop ::= COMMA|JOIN", - /* 141 */ "joinop ::= JOIN_KW JOIN", - /* 142 */ "joinop ::= JOIN_KW nm JOIN", - /* 143 */ "joinop ::= JOIN_KW nm nm JOIN", - /* 144 */ "on_opt ::= ON expr", - /* 145 */ "on_opt ::=", - /* 146 */ "indexed_opt ::=", - /* 147 */ "indexed_opt ::= INDEXED BY nm", - /* 148 */ "indexed_opt ::= NOT INDEXED", - /* 149 */ "using_opt ::= USING LP inscollist RP", - /* 150 */ "using_opt ::=", - /* 151 */ "orderby_opt ::=", - /* 152 */ "orderby_opt ::= ORDER BY sortlist", - /* 153 */ "sortlist ::= sortlist COMMA expr sortorder", - /* 154 */ "sortlist ::= expr sortorder", - /* 155 */ "sortorder ::= ASC", - /* 156 */ "sortorder ::= DESC", - /* 157 */ "sortorder ::=", - /* 158 */ "groupby_opt ::=", - /* 159 */ "groupby_opt ::= GROUP BY nexprlist", - /* 160 */ "having_opt ::=", - /* 161 */ "having_opt ::= HAVING expr", - /* 162 */ "limit_opt ::=", - /* 163 */ "limit_opt ::= LIMIT expr", - /* 164 */ "limit_opt ::= LIMIT expr OFFSET expr", - /* 165 */ "limit_opt ::= LIMIT expr COMMA expr", - /* 166 */ "cmd ::= DELETE FROM fullname indexed_opt where_opt", - /* 167 */ "where_opt ::=", - /* 168 */ "where_opt ::= WHERE expr", - /* 169 */ "cmd ::= UPDATE orconf fullname indexed_opt SET setlist where_opt", - /* 170 */ "setlist ::= setlist COMMA nm EQ expr", - /* 171 */ "setlist ::= nm EQ expr", - /* 172 */ "cmd ::= insert_cmd INTO fullname inscollist_opt valuelist", - /* 173 */ "cmd ::= insert_cmd INTO fullname inscollist_opt select", - /* 174 */ "cmd ::= insert_cmd INTO fullname inscollist_opt DEFAULT VALUES", - /* 175 */ "insert_cmd ::= INSERT orconf", - /* 176 */ "insert_cmd ::= REPLACE", - /* 177 */ "valuelist ::= VALUES LP nexprlist RP", - /* 178 */ "valuelist ::= valuelist COMMA LP exprlist RP", + /* 119 */ "oneselect ::= values", + /* 120 */ "values ::= VALUES LP nexprlist RP", + /* 121 */ "values ::= values COMMA LP exprlist RP", + /* 122 */ "distinct ::= DISTINCT", + /* 123 */ "distinct ::= ALL", + /* 124 */ "distinct ::=", + /* 125 */ "sclp ::= selcollist COMMA", + /* 126 */ "sclp ::=", + /* 127 */ "selcollist ::= sclp expr as", + /* 128 */ "selcollist ::= sclp STAR", + /* 129 */ "selcollist ::= sclp nm DOT STAR", + /* 130 */ "as ::= AS nm", + /* 131 */ "as ::= ID|STRING", + /* 132 */ "as ::=", + /* 133 */ "from ::=", + /* 134 */ "from ::= FROM seltablist", + /* 135 */ "stl_prefix ::= seltablist joinop", + /* 136 */ "stl_prefix ::=", + /* 137 */ "seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt", + /* 138 */ "seltablist ::= stl_prefix LP select RP as on_opt using_opt", + /* 139 */ "seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt", + /* 140 */ "dbnm ::=", + /* 141 */ "dbnm ::= DOT nm", + /* 142 */ "fullname ::= nm dbnm", + /* 143 */ "joinop ::= COMMA|JOIN", + /* 144 */ "joinop ::= JOIN_KW JOIN", + /* 145 */ "joinop ::= JOIN_KW nm JOIN", + /* 146 */ "joinop ::= JOIN_KW nm nm JOIN", + /* 147 */ "on_opt ::= ON expr", + /* 148 */ "on_opt ::=", + /* 149 */ "indexed_opt ::=", + /* 150 */ "indexed_opt ::= INDEXED BY nm", + /* 151 */ "indexed_opt ::= NOT INDEXED", + /* 152 */ "using_opt ::= USING LP idlist RP", + /* 153 */ "using_opt ::=", + /* 154 */ "orderby_opt ::=", + /* 155 */ "orderby_opt ::= ORDER BY sortlist", + /* 156 */ "sortlist ::= sortlist COMMA expr sortorder", + /* 157 */ "sortlist ::= expr sortorder", + /* 158 */ "sortorder ::= ASC", + /* 159 */ "sortorder ::= DESC", + /* 160 */ "sortorder ::=", + /* 161 */ "groupby_opt ::=", + /* 162 */ "groupby_opt ::= GROUP BY nexprlist", + /* 163 */ "having_opt ::=", + /* 164 */ "having_opt ::= HAVING expr", + /* 165 */ "limit_opt ::=", + /* 166 */ "limit_opt ::= LIMIT expr", + /* 167 */ "limit_opt ::= LIMIT expr OFFSET expr", + /* 168 */ "limit_opt ::= LIMIT expr COMMA expr", + /* 169 */ "cmd ::= with DELETE FROM fullname indexed_opt where_opt", + /* 170 */ "where_opt ::=", + /* 171 */ "where_opt ::= WHERE expr", + /* 172 */ "cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt", + /* 173 */ "setlist ::= setlist COMMA nm EQ expr", + /* 174 */ "setlist ::= nm EQ expr", + /* 175 */ "cmd ::= with insert_cmd INTO fullname inscollist_opt select", + /* 176 */ "cmd ::= with insert_cmd INTO fullname inscollist_opt DEFAULT VALUES", + /* 177 */ "insert_cmd ::= INSERT orconf", + /* 178 */ "insert_cmd ::= REPLACE", /* 179 */ "inscollist_opt ::=", - /* 180 */ "inscollist_opt ::= LP inscollist RP", - /* 181 */ "inscollist ::= inscollist COMMA nm", - /* 182 */ "inscollist ::= nm", + /* 180 */ "inscollist_opt ::= LP idlist RP", + /* 181 */ "idlist ::= idlist COMMA nm", + /* 182 */ "idlist ::= nm", /* 183 */ "expr ::= term", /* 184 */ "expr ::= LP expr RP", /* 185 */ "term ::= NULL", - /* 186 */ "expr ::= id", + /* 186 */ "expr ::= ID|INDEXED", /* 187 */ "expr ::= JOIN_KW", /* 188 */ "expr ::= nm DOT nm", /* 189 */ "expr ::= nm DOT nm DOT nm", /* 190 */ "term ::= INTEGER|FLOAT|BLOB", /* 191 */ "term ::= STRING", - /* 192 */ "expr ::= REGISTER", - /* 193 */ "expr ::= VARIABLE", - /* 194 */ "expr ::= expr COLLATE ids", - /* 195 */ "expr ::= CAST LP expr AS typetoken RP", - /* 196 */ "expr ::= ID LP distinct exprlist RP", - /* 197 */ "expr ::= ID LP STAR RP", - /* 198 */ "term ::= CTIME_KW", - /* 199 */ "expr ::= expr AND expr", - /* 200 */ "expr ::= expr OR expr", - /* 201 */ "expr ::= expr LT|GT|GE|LE expr", - /* 202 */ "expr ::= expr EQ|NE expr", - /* 203 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", - /* 204 */ "expr ::= expr PLUS|MINUS expr", - /* 205 */ "expr ::= expr STAR|SLASH|REM expr", - /* 206 */ "expr ::= expr CONCAT expr", - /* 207 */ "likeop ::= LIKE_KW", - /* 208 */ "likeop ::= NOT LIKE_KW", - /* 209 */ "likeop ::= MATCH", - /* 210 */ "likeop ::= NOT MATCH", - /* 211 */ "expr ::= expr likeop expr", - /* 212 */ "expr ::= expr likeop expr ESCAPE expr", - /* 213 */ "expr ::= expr ISNULL|NOTNULL", - /* 214 */ "expr ::= expr NOT NULL", - /* 215 */ "expr ::= expr IS expr", - /* 216 */ "expr ::= expr IS NOT expr", - /* 217 */ "expr ::= NOT expr", - /* 218 */ "expr ::= BITNOT expr", - /* 219 */ "expr ::= MINUS expr", - /* 220 */ "expr ::= PLUS expr", - /* 221 */ "between_op ::= BETWEEN", - /* 222 */ "between_op ::= NOT BETWEEN", - /* 223 */ "expr ::= expr between_op expr AND expr", - /* 224 */ "in_op ::= IN", - /* 225 */ "in_op ::= NOT IN", - /* 226 */ "expr ::= expr in_op LP exprlist RP", - /* 227 */ "expr ::= LP select RP", - /* 228 */ "expr ::= expr in_op LP select RP", - /* 229 */ "expr ::= expr in_op nm dbnm", - /* 230 */ "expr ::= EXISTS LP select RP", - /* 231 */ "expr ::= CASE case_operand case_exprlist case_else END", - /* 232 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", - /* 233 */ "case_exprlist ::= WHEN expr THEN expr", - /* 234 */ "case_else ::= ELSE expr", - /* 235 */ "case_else ::=", - /* 236 */ "case_operand ::= expr", - /* 237 */ "case_operand ::=", - /* 238 */ "exprlist ::= nexprlist", - /* 239 */ "exprlist ::=", - /* 240 */ "nexprlist ::= nexprlist COMMA expr", - /* 241 */ "nexprlist ::= expr", - /* 242 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP idxlist RP where_opt", - /* 243 */ "uniqueflag ::= UNIQUE", - /* 244 */ "uniqueflag ::=", - /* 245 */ "idxlist_opt ::=", - /* 246 */ "idxlist_opt ::= LP idxlist RP", - /* 247 */ "idxlist ::= idxlist COMMA nm collate sortorder", - /* 248 */ "idxlist ::= nm collate sortorder", - /* 249 */ "collate ::=", - /* 250 */ "collate ::= COLLATE ids", - /* 251 */ "cmd ::= DROP INDEX ifexists fullname", - /* 252 */ "cmd ::= VACUUM", - /* 253 */ "cmd ::= VACUUM nm", - /* 254 */ "cmd ::= PRAGMA nm dbnm", - /* 255 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", - /* 256 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", - /* 257 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", - /* 258 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", - /* 259 */ "nmnum ::= plus_num", - /* 260 */ "nmnum ::= nm", - /* 261 */ "nmnum ::= ON", - /* 262 */ "nmnum ::= DELETE", - /* 263 */ "nmnum ::= DEFAULT", - /* 264 */ "plus_num ::= PLUS number", - /* 265 */ "plus_num ::= number", - /* 266 */ "minus_num ::= MINUS number", - /* 267 */ "number ::= INTEGER|FLOAT", - /* 268 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", - /* 269 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", - /* 270 */ "trigger_time ::= BEFORE", - /* 271 */ "trigger_time ::= AFTER", - /* 272 */ "trigger_time ::= INSTEAD OF", - /* 273 */ "trigger_time ::=", - /* 274 */ "trigger_event ::= DELETE|INSERT", - /* 275 */ "trigger_event ::= UPDATE", - /* 276 */ "trigger_event ::= UPDATE OF inscollist", - /* 277 */ "foreach_clause ::=", - /* 278 */ "foreach_clause ::= FOR EACH ROW", - /* 279 */ "when_clause ::=", - /* 280 */ "when_clause ::= WHEN expr", - /* 281 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", - /* 282 */ "trigger_cmd_list ::= trigger_cmd SEMI", - /* 283 */ "trnm ::= nm", - /* 284 */ "trnm ::= nm DOT nm", - /* 285 */ "tridxby ::=", - /* 286 */ "tridxby ::= INDEXED BY nm", - /* 287 */ "tridxby ::= NOT INDEXED", - /* 288 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt", - /* 289 */ "trigger_cmd ::= insert_cmd INTO trnm inscollist_opt valuelist", - /* 290 */ "trigger_cmd ::= insert_cmd INTO trnm inscollist_opt select", - /* 291 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt", - /* 292 */ "trigger_cmd ::= select", - /* 293 */ "expr ::= RAISE LP IGNORE RP", - /* 294 */ "expr ::= RAISE LP raisetype COMMA nm RP", - /* 295 */ "raisetype ::= ROLLBACK", - /* 296 */ "raisetype ::= ABORT", - /* 297 */ "raisetype ::= FAIL", - /* 298 */ "cmd ::= DROP TRIGGER ifexists fullname", - /* 299 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", - /* 300 */ "cmd ::= DETACH database_kw_opt expr", - /* 301 */ "key_opt ::=", - /* 302 */ "key_opt ::= KEY expr", - /* 303 */ "database_kw_opt ::= DATABASE", - /* 304 */ "database_kw_opt ::=", - /* 305 */ "cmd ::= REINDEX", - /* 306 */ "cmd ::= REINDEX nm dbnm", - /* 307 */ "cmd ::= ANALYZE", - /* 308 */ "cmd ::= ANALYZE nm dbnm", - /* 309 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", - /* 310 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt column", - /* 311 */ "add_column_fullname ::= fullname", - /* 312 */ "kwcolumn_opt ::=", - /* 313 */ "kwcolumn_opt ::= COLUMNKW", - /* 314 */ "cmd ::= create_vtab", - /* 315 */ "cmd ::= create_vtab LP vtabarglist RP", - /* 316 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", - /* 317 */ "vtabarglist ::= vtabarg", - /* 318 */ "vtabarglist ::= vtabarglist COMMA vtabarg", - /* 319 */ "vtabarg ::=", - /* 320 */ "vtabarg ::= vtabarg vtabargtoken", - /* 321 */ "vtabargtoken ::= ANY", - /* 322 */ "vtabargtoken ::= lp anylist RP", - /* 323 */ "lp ::= LP", - /* 324 */ "anylist ::=", - /* 325 */ "anylist ::= anylist LP anylist RP", - /* 326 */ "anylist ::= anylist ANY", + /* 192 */ "expr ::= VARIABLE", + /* 193 */ "expr ::= expr COLLATE ID|STRING", + /* 194 */ "expr ::= CAST LP expr AS typetoken RP", + /* 195 */ "expr ::= ID|INDEXED LP distinct exprlist RP", + /* 196 */ "expr ::= ID|INDEXED LP STAR RP", + /* 197 */ "term ::= CTIME_KW", + /* 198 */ "expr ::= expr AND expr", + /* 199 */ "expr ::= expr OR expr", + /* 200 */ "expr ::= expr LT|GT|GE|LE expr", + /* 201 */ "expr ::= expr EQ|NE expr", + /* 202 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", + /* 203 */ "expr ::= expr PLUS|MINUS expr", + /* 204 */ "expr ::= expr STAR|SLASH|REM expr", + /* 205 */ "expr ::= expr CONCAT expr", + /* 206 */ "likeop ::= LIKE_KW|MATCH", + /* 207 */ "likeop ::= NOT LIKE_KW|MATCH", + /* 208 */ "expr ::= expr likeop expr", + /* 209 */ "expr ::= expr likeop expr ESCAPE expr", + /* 210 */ "expr ::= expr ISNULL|NOTNULL", + /* 211 */ "expr ::= expr NOT NULL", + /* 212 */ "expr ::= expr IS expr", + /* 213 */ "expr ::= expr IS NOT expr", + /* 214 */ "expr ::= NOT expr", + /* 215 */ "expr ::= BITNOT expr", + /* 216 */ "expr ::= MINUS expr", + /* 217 */ "expr ::= PLUS expr", + /* 218 */ "between_op ::= BETWEEN", + /* 219 */ "between_op ::= NOT BETWEEN", + /* 220 */ "expr ::= expr between_op expr AND expr", + /* 221 */ "in_op ::= IN", + /* 222 */ "in_op ::= NOT IN", + /* 223 */ "expr ::= expr in_op LP exprlist RP", + /* 224 */ "expr ::= LP select RP", + /* 225 */ "expr ::= expr in_op LP select RP", + /* 226 */ "expr ::= expr in_op nm dbnm", + /* 227 */ "expr ::= EXISTS LP select RP", + /* 228 */ "expr ::= CASE case_operand case_exprlist case_else END", + /* 229 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 230 */ "case_exprlist ::= WHEN expr THEN expr", + /* 231 */ "case_else ::= ELSE expr", + /* 232 */ "case_else ::=", + /* 233 */ "case_operand ::= expr", + /* 234 */ "case_operand ::=", + /* 235 */ "exprlist ::= nexprlist", + /* 236 */ "exprlist ::=", + /* 237 */ "nexprlist ::= nexprlist COMMA expr", + /* 238 */ "nexprlist ::= expr", + /* 239 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP idxlist RP where_opt", + /* 240 */ "uniqueflag ::= UNIQUE", + /* 241 */ "uniqueflag ::=", + /* 242 */ "idxlist_opt ::=", + /* 243 */ "idxlist_opt ::= LP idxlist RP", + /* 244 */ "idxlist ::= idxlist COMMA nm collate sortorder", + /* 245 */ "idxlist ::= nm collate sortorder", + /* 246 */ "collate ::=", + /* 247 */ "collate ::= COLLATE ID|STRING", + /* 248 */ "cmd ::= DROP INDEX ifexists fullname", + /* 249 */ "cmd ::= VACUUM", + /* 250 */ "cmd ::= VACUUM nm", + /* 251 */ "cmd ::= PRAGMA nm dbnm", + /* 252 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", + /* 253 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", + /* 254 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", + /* 255 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", + /* 256 */ "nmnum ::= plus_num", + /* 257 */ "nmnum ::= nm", + /* 258 */ "nmnum ::= ON", + /* 259 */ "nmnum ::= DELETE", + /* 260 */ "nmnum ::= DEFAULT", + /* 261 */ "plus_num ::= PLUS INTEGER|FLOAT", + /* 262 */ "plus_num ::= INTEGER|FLOAT", + /* 263 */ "minus_num ::= MINUS INTEGER|FLOAT", + /* 264 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", + /* 265 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", + /* 266 */ "trigger_time ::= BEFORE", + /* 267 */ "trigger_time ::= AFTER", + /* 268 */ "trigger_time ::= INSTEAD OF", + /* 269 */ "trigger_time ::=", + /* 270 */ "trigger_event ::= DELETE|INSERT", + /* 271 */ "trigger_event ::= UPDATE", + /* 272 */ "trigger_event ::= UPDATE OF idlist", + /* 273 */ "foreach_clause ::=", + /* 274 */ "foreach_clause ::= FOR EACH ROW", + /* 275 */ "when_clause ::=", + /* 276 */ "when_clause ::= WHEN expr", + /* 277 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", + /* 278 */ "trigger_cmd_list ::= trigger_cmd SEMI", + /* 279 */ "trnm ::= nm", + /* 280 */ "trnm ::= nm DOT nm", + /* 281 */ "tridxby ::=", + /* 282 */ "tridxby ::= INDEXED BY nm", + /* 283 */ "tridxby ::= NOT INDEXED", + /* 284 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt", + /* 285 */ "trigger_cmd ::= insert_cmd INTO trnm inscollist_opt select", + /* 286 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt", + /* 287 */ "trigger_cmd ::= select", + /* 288 */ "expr ::= RAISE LP IGNORE RP", + /* 289 */ "expr ::= RAISE LP raisetype COMMA nm RP", + /* 290 */ "raisetype ::= ROLLBACK", + /* 291 */ "raisetype ::= ABORT", + /* 292 */ "raisetype ::= FAIL", + /* 293 */ "cmd ::= DROP TRIGGER ifexists fullname", + /* 294 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", + /* 295 */ "cmd ::= DETACH database_kw_opt expr", + /* 296 */ "key_opt ::=", + /* 297 */ "key_opt ::= KEY expr", + /* 298 */ "database_kw_opt ::= DATABASE", + /* 299 */ "database_kw_opt ::=", + /* 300 */ "cmd ::= REINDEX", + /* 301 */ "cmd ::= REINDEX nm dbnm", + /* 302 */ "cmd ::= ANALYZE", + /* 303 */ "cmd ::= ANALYZE nm dbnm", + /* 304 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", + /* 305 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt column", + /* 306 */ "add_column_fullname ::= fullname", + /* 307 */ "kwcolumn_opt ::=", + /* 308 */ "kwcolumn_opt ::= COLUMNKW", + /* 309 */ "cmd ::= create_vtab", + /* 310 */ "cmd ::= create_vtab LP vtabarglist RP", + /* 311 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", + /* 312 */ "vtabarglist ::= vtabarg", + /* 313 */ "vtabarglist ::= vtabarglist COMMA vtabarg", + /* 314 */ "vtabarg ::=", + /* 315 */ "vtabarg ::= vtabarg vtabargtoken", + /* 316 */ "vtabargtoken ::= ANY", + /* 317 */ "vtabargtoken ::= lp anylist RP", + /* 318 */ "lp ::= LP", + /* 319 */ "anylist ::=", + /* 320 */ "anylist ::= anylist LP anylist RP", + /* 321 */ "anylist ::= anylist ANY", + /* 322 */ "with ::=", + /* 323 */ "with ::= WITH wqlist", + /* 324 */ "with ::= WITH RECURSIVE wqlist", + /* 325 */ "wqlist ::= nm idxlist_opt AS LP select RP", + /* 326 */ "wqlist ::= wqlist COMMA nm idxlist_opt AS LP select RP", }; #endif /* NDEBUG */ #if YYSTACKDEPTH<=0 @@ -113754,13 +124050,13 @@ ** ** Outputs: ** A pointer to a parser. This pointer is used in subsequent calls ** to sqlite3Parser and sqlite3ParserFree. */ -SQLITE_PRIVATE void *sqlite3ParserAlloc(void *(*mallocProc)(size_t)){ +SQLITE_PRIVATE void *sqlite3ParserAlloc(void *(*mallocProc)(u64)){ yyParser *pParser; - pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + pParser = (yyParser*)(*mallocProc)( (u64)sizeof(yyParser) ); if( pParser ){ pParser->yyidx = -1; #ifdef YYTRACKMAXSTACKDEPTH pParser->yyidxMax = 0; #endif @@ -113793,80 +124089,80 @@ ** ** Note: during a reduce, the only symbols destroyed are those ** which appear on the RHS of the rule, but which are not used ** inside the C code. */ - case 160: /* select */ - case 194: /* oneselect */ -{ -sqlite3SelectDelete(pParse->db, (yypminor->yy159)); -} - break; - case 173: /* term */ - case 174: /* expr */ -{ -sqlite3ExprDelete(pParse->db, (yypminor->yy342).pExpr); -} - break; - case 178: /* idxlist_opt */ - case 187: /* idxlist */ - case 197: /* selcollist */ - case 200: /* groupby_opt */ - case 202: /* orderby_opt */ - case 204: /* sclp */ - case 214: /* sortlist */ - case 215: /* nexprlist */ - case 216: /* setlist */ - case 220: /* exprlist */ - case 225: /* case_exprlist */ -{ -sqlite3ExprListDelete(pParse->db, (yypminor->yy442)); -} - break; - case 193: /* fullname */ - case 198: /* from */ - case 206: /* seltablist */ - case 207: /* stl_prefix */ -{ -sqlite3SrcListDelete(pParse->db, (yypminor->yy347)); -} - break; - case 199: /* where_opt */ - case 201: /* having_opt */ - case 210: /* on_opt */ - case 224: /* case_operand */ - case 226: /* case_else */ - case 236: /* when_clause */ - case 241: /* key_opt */ -{ -sqlite3ExprDelete(pParse->db, (yypminor->yy122)); -} - break; - case 211: /* using_opt */ - case 213: /* inscollist */ - case 218: /* inscollist_opt */ -{ -sqlite3IdListDelete(pParse->db, (yypminor->yy180)); -} - break; - case 219: /* valuelist */ -{ - - sqlite3ExprListDelete(pParse->db, (yypminor->yy487).pList); - sqlite3SelectDelete(pParse->db, (yypminor->yy487).pSelect); - -} - break; - case 232: /* trigger_cmd_list */ - case 237: /* trigger_cmd */ -{ -sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy327)); -} - break; - case 234: /* trigger_event */ -{ -sqlite3IdListDelete(pParse->db, (yypminor->yy410).b); + case 163: /* select */ + case 195: /* selectnowith */ + case 196: /* oneselect */ + case 207: /* values */ +{ +sqlite3SelectDelete(pParse->db, (yypminor->yy3)); +} + break; + case 174: /* term */ + case 175: /* expr */ +{ +sqlite3ExprDelete(pParse->db, (yypminor->yy346).pExpr); +} + break; + case 179: /* idxlist_opt */ + case 188: /* idxlist */ + case 200: /* selcollist */ + case 203: /* groupby_opt */ + case 205: /* orderby_opt */ + case 208: /* nexprlist */ + case 209: /* exprlist */ + case 210: /* sclp */ + case 220: /* sortlist */ + case 221: /* setlist */ + case 228: /* case_exprlist */ +{ +sqlite3ExprListDelete(pParse->db, (yypminor->yy14)); +} + break; + case 194: /* fullname */ + case 201: /* from */ + case 212: /* seltablist */ + case 213: /* stl_prefix */ +{ +sqlite3SrcListDelete(pParse->db, (yypminor->yy65)); +} + break; + case 197: /* with */ + case 252: /* wqlist */ +{ +sqlite3WithDelete(pParse->db, (yypminor->yy59)); +} + break; + case 202: /* where_opt */ + case 204: /* having_opt */ + case 216: /* on_opt */ + case 227: /* case_operand */ + case 229: /* case_else */ + case 238: /* when_clause */ + case 243: /* key_opt */ +{ +sqlite3ExprDelete(pParse->db, (yypminor->yy132)); +} + break; + case 217: /* using_opt */ + case 219: /* idlist */ + case 223: /* inscollist_opt */ +{ +sqlite3IdListDelete(pParse->db, (yypminor->yy408)); +} + break; + case 234: /* trigger_cmd_list */ + case 239: /* trigger_cmd */ +{ +sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy473)); +} + break; + case 236: /* trigger_event */ +{ +sqlite3IdListDelete(pParse->db, (yypminor->yy378).b); } break; default: break; /* If no destructor action specified: do nothing */ } } @@ -114107,337 +124403,337 @@ */ static const struct { YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ unsigned char nrhs; /* Number of right-hand side symbols in the rule */ } yyRuleInfo[] = { - { 142, 1 }, - { 143, 2 }, - { 143, 1 }, { 144, 1 }, - { 144, 3 }, - { 145, 0 }, + { 145, 2 }, { 145, 1 }, - { 145, 3 }, { 146, 1 }, + { 146, 3 }, + { 147, 0 }, + { 147, 1 }, { 147, 3 }, - { 149, 0 }, - { 149, 1 }, - { 149, 2 }, - { 148, 0 }, - { 148, 1 }, - { 148, 1 }, - { 148, 1 }, - { 147, 2 }, - { 147, 2 }, - { 147, 2 }, + { 148, 1 }, + { 149, 3 }, + { 151, 0 }, { 151, 1 }, - { 151, 0 }, - { 147, 2 }, - { 147, 3 }, - { 147, 5 }, - { 147, 2 }, - { 152, 6 }, - { 154, 1 }, - { 156, 0 }, - { 156, 3 }, - { 155, 1 }, - { 155, 0 }, - { 153, 4 }, - { 153, 2 }, - { 158, 3 }, - { 158, 1 }, - { 161, 3 }, - { 162, 1 }, - { 165, 1 }, - { 165, 1 }, - { 166, 1 }, + { 151, 2 }, + { 150, 0 }, { 150, 1 }, { 150, 1 }, { 150, 1 }, - { 163, 0 }, - { 163, 1 }, - { 167, 1 }, - { 167, 4 }, - { 167, 6 }, - { 168, 1 }, - { 168, 2 }, - { 169, 1 }, - { 169, 1 }, - { 164, 2 }, - { 164, 0 }, - { 172, 2 }, - { 172, 2 }, - { 172, 4 }, - { 172, 3 }, - { 172, 3 }, - { 172, 2 }, - { 172, 2 }, - { 172, 3 }, - { 172, 5 }, - { 172, 2 }, - { 172, 4 }, - { 172, 4 }, - { 172, 1 }, - { 172, 2 }, - { 177, 0 }, - { 177, 1 }, - { 179, 0 }, - { 179, 2 }, - { 181, 2 }, - { 181, 3 }, - { 181, 3 }, - { 181, 3 }, - { 182, 2 }, - { 182, 2 }, - { 182, 1 }, - { 182, 1 }, - { 182, 2 }, - { 180, 3 }, + { 149, 2 }, + { 149, 2 }, + { 149, 2 }, + { 153, 1 }, + { 153, 0 }, + { 149, 2 }, + { 149, 3 }, + { 149, 5 }, + { 149, 2 }, + { 154, 6 }, + { 156, 1 }, + { 158, 0 }, + { 158, 3 }, + { 157, 1 }, + { 157, 0 }, + { 155, 5 }, + { 155, 2 }, + { 162, 0 }, + { 162, 2 }, + { 160, 3 }, + { 160, 1 }, + { 164, 3 }, + { 165, 1 }, + { 152, 1 }, + { 152, 1 }, + { 152, 1 }, + { 166, 0 }, + { 166, 1 }, + { 168, 1 }, + { 168, 4 }, + { 168, 6 }, + { 169, 1 }, + { 169, 2 }, + { 170, 1 }, + { 170, 1 }, + { 167, 2 }, + { 167, 0 }, + { 173, 2 }, + { 173, 2 }, + { 173, 4 }, + { 173, 3 }, + { 173, 3 }, + { 173, 2 }, + { 173, 2 }, + { 173, 3 }, + { 173, 5 }, + { 173, 2 }, + { 173, 4 }, + { 173, 4 }, + { 173, 1 }, + { 173, 2 }, + { 178, 0 }, + { 178, 1 }, + { 180, 0 }, { 180, 2 }, - { 183, 0 }, + { 182, 2 }, + { 182, 3 }, + { 182, 3 }, + { 182, 3 }, + { 183, 2 }, { 183, 2 }, + { 183, 1 }, + { 183, 1 }, { 183, 2 }, - { 159, 0 }, - { 159, 2 }, - { 184, 3 }, - { 184, 1 }, + { 181, 3 }, + { 181, 2 }, + { 184, 0 }, + { 184, 2 }, + { 184, 2 }, + { 161, 0 }, + { 161, 2 }, + { 185, 3 }, { 185, 1 }, - { 185, 0 }, - { 186, 2 }, - { 186, 7 }, - { 186, 5 }, - { 186, 5 }, - { 186, 10 }, - { 188, 0 }, - { 188, 1 }, - { 175, 0 }, - { 175, 3 }, + { 186, 1 }, + { 186, 0 }, + { 187, 2 }, + { 187, 7 }, + { 187, 5 }, + { 187, 5 }, + { 187, 10 }, { 189, 0 }, - { 189, 2 }, - { 190, 1 }, - { 190, 1 }, - { 190, 1 }, - { 147, 4 }, - { 192, 2 }, - { 192, 0 }, - { 147, 8 }, - { 147, 4 }, - { 147, 1 }, - { 160, 1 }, - { 160, 3 }, - { 195, 1 }, - { 195, 2 }, - { 195, 1 }, - { 194, 9 }, - { 196, 1 }, - { 196, 1 }, - { 196, 0 }, - { 204, 2 }, - { 204, 0 }, - { 197, 3 }, - { 197, 2 }, - { 197, 4 }, - { 205, 2 }, - { 205, 1 }, - { 205, 0 }, - { 198, 0 }, - { 198, 2 }, - { 207, 2 }, - { 207, 0 }, - { 206, 7 }, - { 206, 7 }, - { 206, 7 }, - { 157, 0 }, - { 157, 2 }, - { 193, 2 }, - { 208, 1 }, - { 208, 2 }, - { 208, 3 }, - { 208, 4 }, - { 210, 2 }, - { 210, 0 }, - { 209, 0 }, - { 209, 3 }, - { 209, 2 }, - { 211, 4 }, - { 211, 0 }, - { 202, 0 }, - { 202, 3 }, - { 214, 4 }, - { 214, 2 }, - { 176, 1 }, - { 176, 1 }, + { 189, 1 }, { 176, 0 }, - { 200, 0 }, - { 200, 3 }, - { 201, 0 }, - { 201, 2 }, - { 203, 0 }, - { 203, 2 }, - { 203, 4 }, - { 203, 4 }, - { 147, 5 }, - { 199, 0 }, - { 199, 2 }, - { 147, 7 }, - { 216, 5 }, - { 216, 3 }, - { 147, 5 }, - { 147, 5 }, - { 147, 6 }, - { 217, 2 }, - { 217, 1 }, - { 219, 4 }, - { 219, 5 }, - { 218, 0 }, - { 218, 3 }, - { 213, 3 }, - { 213, 1 }, - { 174, 1 }, - { 174, 3 }, - { 173, 1 }, - { 174, 1 }, - { 174, 1 }, - { 174, 3 }, - { 174, 5 }, - { 173, 1 }, - { 173, 1 }, - { 174, 1 }, - { 174, 1 }, - { 174, 3 }, - { 174, 6 }, - { 174, 5 }, - { 174, 4 }, - { 173, 1 }, - { 174, 3 }, - { 174, 3 }, - { 174, 3 }, - { 174, 3 }, - { 174, 3 }, - { 174, 3 }, - { 174, 3 }, - { 174, 3 }, - { 221, 1 }, - { 221, 2 }, - { 221, 1 }, - { 221, 2 }, - { 174, 3 }, - { 174, 5 }, - { 174, 2 }, - { 174, 3 }, - { 174, 3 }, - { 174, 4 }, - { 174, 2 }, - { 174, 2 }, - { 174, 2 }, - { 174, 2 }, - { 222, 1 }, - { 222, 2 }, - { 174, 5 }, - { 223, 1 }, - { 223, 2 }, - { 174, 5 }, - { 174, 3 }, - { 174, 5 }, - { 174, 4 }, - { 174, 4 }, - { 174, 5 }, - { 225, 5 }, - { 225, 4 }, - { 226, 2 }, - { 226, 0 }, - { 224, 1 }, - { 224, 0 }, - { 220, 1 }, - { 220, 0 }, - { 215, 3 }, - { 215, 1 }, - { 147, 12 }, - { 227, 1 }, - { 227, 0 }, - { 178, 0 }, - { 178, 3 }, - { 187, 5 }, - { 187, 3 }, - { 228, 0 }, - { 228, 2 }, - { 147, 4 }, - { 147, 1 }, - { 147, 2 }, - { 147, 3 }, - { 147, 5 }, - { 147, 6 }, - { 147, 5 }, - { 147, 6 }, - { 229, 1 }, - { 229, 1 }, - { 229, 1 }, - { 229, 1 }, - { 229, 1 }, - { 170, 2 }, - { 170, 1 }, - { 171, 2 }, - { 230, 1 }, - { 147, 5 }, - { 231, 11 }, - { 233, 1 }, - { 233, 1 }, - { 233, 2 }, - { 233, 0 }, - { 234, 1 }, - { 234, 1 }, - { 234, 3 }, - { 235, 0 }, - { 235, 3 }, - { 236, 0 }, - { 236, 2 }, - { 232, 3 }, - { 232, 2 }, - { 238, 1 }, - { 238, 3 }, - { 239, 0 }, - { 239, 3 }, - { 239, 2 }, - { 237, 7 }, - { 237, 5 }, - { 237, 5 }, - { 237, 5 }, - { 237, 1 }, - { 174, 4 }, - { 174, 6 }, + { 176, 3 }, + { 190, 0 }, + { 190, 2 }, { 191, 1 }, { 191, 1 }, { 191, 1 }, - { 147, 4 }, - { 147, 6 }, - { 147, 3 }, - { 241, 0 }, - { 241, 2 }, + { 149, 4 }, + { 193, 2 }, + { 193, 0 }, + { 149, 8 }, + { 149, 4 }, + { 149, 1 }, + { 163, 2 }, + { 195, 1 }, + { 195, 3 }, + { 198, 1 }, + { 198, 2 }, + { 198, 1 }, + { 196, 9 }, + { 196, 1 }, + { 207, 4 }, + { 207, 5 }, + { 199, 1 }, + { 199, 1 }, + { 199, 0 }, + { 210, 2 }, + { 210, 0 }, + { 200, 3 }, + { 200, 2 }, + { 200, 4 }, + { 211, 2 }, + { 211, 1 }, + { 211, 0 }, + { 201, 0 }, + { 201, 2 }, + { 213, 2 }, + { 213, 0 }, + { 212, 7 }, + { 212, 7 }, + { 212, 7 }, + { 159, 0 }, + { 159, 2 }, + { 194, 2 }, + { 214, 1 }, + { 214, 2 }, + { 214, 3 }, + { 214, 4 }, + { 216, 2 }, + { 216, 0 }, + { 215, 0 }, + { 215, 3 }, + { 215, 2 }, + { 217, 4 }, + { 217, 0 }, + { 205, 0 }, + { 205, 3 }, + { 220, 4 }, + { 220, 2 }, + { 177, 1 }, + { 177, 1 }, + { 177, 0 }, + { 203, 0 }, + { 203, 3 }, + { 204, 0 }, + { 204, 2 }, + { 206, 0 }, + { 206, 2 }, + { 206, 4 }, + { 206, 4 }, + { 149, 6 }, + { 202, 0 }, + { 202, 2 }, + { 149, 8 }, + { 221, 5 }, + { 221, 3 }, + { 149, 6 }, + { 149, 7 }, + { 222, 2 }, + { 222, 1 }, + { 223, 0 }, + { 223, 3 }, + { 219, 3 }, + { 219, 1 }, + { 175, 1 }, + { 175, 3 }, + { 174, 1 }, + { 175, 1 }, + { 175, 1 }, + { 175, 3 }, + { 175, 5 }, + { 174, 1 }, + { 174, 1 }, + { 175, 1 }, + { 175, 3 }, + { 175, 6 }, + { 175, 5 }, + { 175, 4 }, + { 174, 1 }, + { 175, 3 }, + { 175, 3 }, + { 175, 3 }, + { 175, 3 }, + { 175, 3 }, + { 175, 3 }, + { 175, 3 }, + { 175, 3 }, + { 224, 1 }, + { 224, 2 }, + { 175, 3 }, + { 175, 5 }, + { 175, 2 }, + { 175, 3 }, + { 175, 3 }, + { 175, 4 }, + { 175, 2 }, + { 175, 2 }, + { 175, 2 }, + { 175, 2 }, + { 225, 1 }, + { 225, 2 }, + { 175, 5 }, + { 226, 1 }, + { 226, 2 }, + { 175, 5 }, + { 175, 3 }, + { 175, 5 }, + { 175, 4 }, + { 175, 4 }, + { 175, 5 }, + { 228, 5 }, + { 228, 4 }, + { 229, 2 }, + { 229, 0 }, + { 227, 1 }, + { 227, 0 }, + { 209, 1 }, + { 209, 0 }, + { 208, 3 }, + { 208, 1 }, + { 149, 12 }, + { 230, 1 }, + { 230, 0 }, + { 179, 0 }, + { 179, 3 }, + { 188, 5 }, + { 188, 3 }, + { 231, 0 }, + { 231, 2 }, + { 149, 4 }, + { 149, 1 }, + { 149, 2 }, + { 149, 3 }, + { 149, 5 }, + { 149, 6 }, + { 149, 5 }, + { 149, 6 }, + { 232, 1 }, + { 232, 1 }, + { 232, 1 }, + { 232, 1 }, + { 232, 1 }, + { 171, 2 }, + { 171, 1 }, + { 172, 2 }, + { 149, 5 }, + { 233, 11 }, + { 235, 1 }, + { 235, 1 }, + { 235, 2 }, + { 235, 0 }, + { 236, 1 }, + { 236, 1 }, + { 236, 3 }, + { 237, 0 }, + { 237, 3 }, + { 238, 0 }, + { 238, 2 }, + { 234, 3 }, + { 234, 2 }, { 240, 1 }, - { 240, 0 }, - { 147, 1 }, - { 147, 3 }, - { 147, 1 }, - { 147, 3 }, - { 147, 6 }, - { 147, 6 }, - { 242, 1 }, + { 240, 3 }, + { 241, 0 }, + { 241, 3 }, + { 241, 2 }, + { 239, 7 }, + { 239, 5 }, + { 239, 5 }, + { 239, 1 }, + { 175, 4 }, + { 175, 6 }, + { 192, 1 }, + { 192, 1 }, + { 192, 1 }, + { 149, 4 }, + { 149, 6 }, + { 149, 3 }, { 243, 0 }, - { 243, 1 }, - { 147, 1 }, - { 147, 4 }, - { 244, 8 }, + { 243, 2 }, + { 242, 1 }, + { 242, 0 }, + { 149, 1 }, + { 149, 3 }, + { 149, 1 }, + { 149, 3 }, + { 149, 6 }, + { 149, 6 }, + { 244, 1 }, + { 245, 0 }, { 245, 1 }, - { 245, 3 }, - { 246, 0 }, - { 246, 2 }, + { 149, 1 }, + { 149, 4 }, + { 246, 8 }, { 247, 1 }, { 247, 3 }, - { 248, 1 }, - { 249, 0 }, - { 249, 4 }, - { 249, 2 }, + { 248, 0 }, + { 248, 2 }, + { 249, 1 }, + { 249, 3 }, + { 250, 1 }, + { 251, 0 }, + { 251, 4 }, + { 251, 2 }, + { 197, 0 }, + { 197, 2 }, + { 197, 3 }, + { 252, 6 }, + { 252, 8 }, }; static void yy_accept(yyParser*); /* Forward Declaration */ /* @@ -114501,21 +124797,21 @@ break; case 8: /* cmdx ::= cmd */ { sqlite3FinishCoding(pParse); } break; case 9: /* cmd ::= BEGIN transtype trans_opt */ -{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy392);} +{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy328);} break; case 13: /* transtype ::= */ -{yygotominor.yy392 = TK_DEFERRED;} +{yygotominor.yy328 = TK_DEFERRED;} break; case 14: /* transtype ::= DEFERRED */ case 15: /* transtype ::= IMMEDIATE */ yytestcase(yyruleno==15); case 16: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==16); case 115: /* multiselect_op ::= UNION */ yytestcase(yyruleno==115); case 117: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==117); -{yygotominor.yy392 = yymsp[0].major;} +{yygotominor.yy328 = yymsp[0].major;} break; case 17: /* cmd ::= COMMIT trans_opt */ case 18: /* cmd ::= END trans_opt */ yytestcase(yyruleno==18); {sqlite3CommitTransaction(pParse);} break; @@ -114537,11 +124833,11 @@ sqlite3Savepoint(pParse, SAVEPOINT_ROLLBACK, &yymsp[0].minor.yy0); } break; case 26: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */ { - sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy392,0,0,yymsp[-2].minor.yy392); + sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy328,0,0,yymsp[-2].minor.yy328); } break; case 27: /* createkw ::= CREATE */ { pParse->db->lookaside.bEnabled = 0; @@ -114548,1052 +124844,1147 @@ yygotominor.yy0 = yymsp[0].minor.yy0; } break; case 28: /* ifnotexists ::= */ case 31: /* temp ::= */ yytestcase(yyruleno==31); - case 69: /* autoinc ::= */ yytestcase(yyruleno==69); - case 82: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ yytestcase(yyruleno==82); - case 84: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==84); - case 86: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ yytestcase(yyruleno==86); - case 98: /* defer_subclause_opt ::= */ yytestcase(yyruleno==98); - case 109: /* ifexists ::= */ yytestcase(yyruleno==109); - case 221: /* between_op ::= BETWEEN */ yytestcase(yyruleno==221); - case 224: /* in_op ::= IN */ yytestcase(yyruleno==224); -{yygotominor.yy392 = 0;} + case 68: /* autoinc ::= */ yytestcase(yyruleno==68); + case 81: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ yytestcase(yyruleno==81); + case 83: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==83); + case 85: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ yytestcase(yyruleno==85); + case 97: /* defer_subclause_opt ::= */ yytestcase(yyruleno==97); + case 108: /* ifexists ::= */ yytestcase(yyruleno==108); + case 218: /* between_op ::= BETWEEN */ yytestcase(yyruleno==218); + case 221: /* in_op ::= IN */ yytestcase(yyruleno==221); +{yygotominor.yy328 = 0;} break; case 29: /* ifnotexists ::= IF NOT EXISTS */ case 30: /* temp ::= TEMP */ yytestcase(yyruleno==30); - case 70: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==70); - case 85: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ yytestcase(yyruleno==85); - case 108: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==108); - case 222: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==222); - case 225: /* in_op ::= NOT IN */ yytestcase(yyruleno==225); -{yygotominor.yy392 = 1;} + case 69: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==69); + case 84: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ yytestcase(yyruleno==84); + case 107: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==107); + case 219: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==219); + case 222: /* in_op ::= NOT IN */ yytestcase(yyruleno==222); +{yygotominor.yy328 = 1;} break; - case 32: /* create_table_args ::= LP columnlist conslist_opt RP */ + case 32: /* create_table_args ::= LP columnlist conslist_opt RP table_options */ { - sqlite3EndTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0); + sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy186,0); } break; case 33: /* create_table_args ::= AS select */ { - sqlite3EndTable(pParse,0,0,yymsp[0].minor.yy159); - sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy159); + sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy3); + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy3); } break; - case 36: /* column ::= columnid type carglist */ + case 34: /* table_options ::= */ +{yygotominor.yy186 = 0;} + break; + case 35: /* table_options ::= WITHOUT nm */ +{ + if( yymsp[0].minor.yy0.n==5 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"rowid",5)==0 ){ + yygotominor.yy186 = TF_WithoutRowid; + }else{ + yygotominor.yy186 = 0; + sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z); + } +} + break; + case 38: /* column ::= columnid type carglist */ { yygotominor.yy0.z = yymsp[-2].minor.yy0.z; yygotominor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-2].minor.yy0.z) + pParse->sLastToken.n; } break; - case 37: /* columnid ::= nm */ + case 39: /* columnid ::= nm */ { sqlite3AddColumn(pParse,&yymsp[0].minor.yy0); yygotominor.yy0 = yymsp[0].minor.yy0; pParse->constraintName.n = 0; } break; - case 38: /* id ::= ID */ - case 39: /* id ::= INDEXED */ yytestcase(yyruleno==39); - case 40: /* ids ::= ID|STRING */ yytestcase(yyruleno==40); - case 41: /* nm ::= id */ yytestcase(yyruleno==41); - case 42: /* nm ::= STRING */ yytestcase(yyruleno==42); - case 43: /* nm ::= JOIN_KW */ yytestcase(yyruleno==43); - case 46: /* typetoken ::= typename */ yytestcase(yyruleno==46); - case 49: /* typename ::= ids */ yytestcase(yyruleno==49); - case 127: /* as ::= AS nm */ yytestcase(yyruleno==127); - case 128: /* as ::= ids */ yytestcase(yyruleno==128); - case 138: /* dbnm ::= DOT nm */ yytestcase(yyruleno==138); - case 147: /* indexed_opt ::= INDEXED BY nm */ yytestcase(yyruleno==147); - case 250: /* collate ::= COLLATE ids */ yytestcase(yyruleno==250); - case 259: /* nmnum ::= plus_num */ yytestcase(yyruleno==259); - case 260: /* nmnum ::= nm */ yytestcase(yyruleno==260); - case 261: /* nmnum ::= ON */ yytestcase(yyruleno==261); - case 262: /* nmnum ::= DELETE */ yytestcase(yyruleno==262); - case 263: /* nmnum ::= DEFAULT */ yytestcase(yyruleno==263); - case 264: /* plus_num ::= PLUS number */ yytestcase(yyruleno==264); - case 265: /* plus_num ::= number */ yytestcase(yyruleno==265); - case 266: /* minus_num ::= MINUS number */ yytestcase(yyruleno==266); - case 267: /* number ::= INTEGER|FLOAT */ yytestcase(yyruleno==267); - case 283: /* trnm ::= nm */ yytestcase(yyruleno==283); + case 40: /* nm ::= ID|INDEXED */ + case 41: /* nm ::= STRING */ yytestcase(yyruleno==41); + case 42: /* nm ::= JOIN_KW */ yytestcase(yyruleno==42); + case 45: /* typetoken ::= typename */ yytestcase(yyruleno==45); + case 48: /* typename ::= ID|STRING */ yytestcase(yyruleno==48); + case 130: /* as ::= AS nm */ yytestcase(yyruleno==130); + case 131: /* as ::= ID|STRING */ yytestcase(yyruleno==131); + case 141: /* dbnm ::= DOT nm */ yytestcase(yyruleno==141); + case 150: /* indexed_opt ::= INDEXED BY nm */ yytestcase(yyruleno==150); + case 247: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==247); + case 256: /* nmnum ::= plus_num */ yytestcase(yyruleno==256); + case 257: /* nmnum ::= nm */ yytestcase(yyruleno==257); + case 258: /* nmnum ::= ON */ yytestcase(yyruleno==258); + case 259: /* nmnum ::= DELETE */ yytestcase(yyruleno==259); + case 260: /* nmnum ::= DEFAULT */ yytestcase(yyruleno==260); + case 261: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==261); + case 262: /* plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==262); + case 263: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==263); + case 279: /* trnm ::= nm */ yytestcase(yyruleno==279); {yygotominor.yy0 = yymsp[0].minor.yy0;} break; - case 45: /* type ::= typetoken */ + case 44: /* type ::= typetoken */ {sqlite3AddColumnType(pParse,&yymsp[0].minor.yy0);} break; - case 47: /* typetoken ::= typename LP signed RP */ + case 46: /* typetoken ::= typename LP signed RP */ { yygotominor.yy0.z = yymsp[-3].minor.yy0.z; yygotominor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-3].minor.yy0.z); } break; - case 48: /* typetoken ::= typename LP signed COMMA signed RP */ + case 47: /* typetoken ::= typename LP signed COMMA signed RP */ { yygotominor.yy0.z = yymsp[-5].minor.yy0.z; yygotominor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-5].minor.yy0.z); } break; - case 50: /* typename ::= typename ids */ + case 49: /* typename ::= typename ID|STRING */ {yygotominor.yy0.z=yymsp[-1].minor.yy0.z; yygotominor.yy0.n=yymsp[0].minor.yy0.n+(int)(yymsp[0].minor.yy0.z-yymsp[-1].minor.yy0.z);} break; - case 55: /* ccons ::= CONSTRAINT nm */ - case 93: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==93); + case 54: /* ccons ::= CONSTRAINT nm */ + case 92: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==92); {pParse->constraintName = yymsp[0].minor.yy0;} break; - case 56: /* ccons ::= DEFAULT term */ - case 58: /* ccons ::= DEFAULT PLUS term */ yytestcase(yyruleno==58); -{sqlite3AddDefaultValue(pParse,&yymsp[0].minor.yy342);} - break; - case 57: /* ccons ::= DEFAULT LP expr RP */ -{sqlite3AddDefaultValue(pParse,&yymsp[-1].minor.yy342);} - break; - case 59: /* ccons ::= DEFAULT MINUS term */ + case 55: /* ccons ::= DEFAULT term */ + case 57: /* ccons ::= DEFAULT PLUS term */ yytestcase(yyruleno==57); +{sqlite3AddDefaultValue(pParse,&yymsp[0].minor.yy346);} + break; + case 56: /* ccons ::= DEFAULT LP expr RP */ +{sqlite3AddDefaultValue(pParse,&yymsp[-1].minor.yy346);} + break; + case 58: /* ccons ::= DEFAULT MINUS term */ { ExprSpan v; - v.pExpr = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy342.pExpr, 0, 0); + v.pExpr = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy346.pExpr, 0, 0); v.zStart = yymsp[-1].minor.yy0.z; - v.zEnd = yymsp[0].minor.yy342.zEnd; + v.zEnd = yymsp[0].minor.yy346.zEnd; sqlite3AddDefaultValue(pParse,&v); } break; - case 60: /* ccons ::= DEFAULT id */ + case 59: /* ccons ::= DEFAULT ID|INDEXED */ { ExprSpan v; spanExpr(&v, pParse, TK_STRING, &yymsp[0].minor.yy0); sqlite3AddDefaultValue(pParse,&v); } break; - case 62: /* ccons ::= NOT NULL onconf */ -{sqlite3AddNotNull(pParse, yymsp[0].minor.yy392);} - break; - case 63: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ -{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy392,yymsp[0].minor.yy392,yymsp[-2].minor.yy392);} - break; - case 64: /* ccons ::= UNIQUE onconf */ -{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy392,0,0,0,0);} - break; - case 65: /* ccons ::= CHECK LP expr RP */ -{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy342.pExpr);} - break; - case 66: /* ccons ::= REFERENCES nm idxlist_opt refargs */ -{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy442,yymsp[0].minor.yy392);} - break; - case 67: /* ccons ::= defer_subclause */ -{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy392);} - break; - case 68: /* ccons ::= COLLATE ids */ + case 61: /* ccons ::= NOT NULL onconf */ +{sqlite3AddNotNull(pParse, yymsp[0].minor.yy328);} + break; + case 62: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ +{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy328,yymsp[0].minor.yy328,yymsp[-2].minor.yy328);} + break; + case 63: /* ccons ::= UNIQUE onconf */ +{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy328,0,0,0,0);} + break; + case 64: /* ccons ::= CHECK LP expr RP */ +{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy346.pExpr);} + break; + case 65: /* ccons ::= REFERENCES nm idxlist_opt refargs */ +{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy14,yymsp[0].minor.yy328);} + break; + case 66: /* ccons ::= defer_subclause */ +{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy328);} + break; + case 67: /* ccons ::= COLLATE ID|STRING */ {sqlite3AddCollateType(pParse, &yymsp[0].minor.yy0);} break; - case 71: /* refargs ::= */ -{ yygotominor.yy392 = OE_None*0x0101; /* EV: R-19803-45884 */} - break; - case 72: /* refargs ::= refargs refarg */ -{ yygotominor.yy392 = (yymsp[-1].minor.yy392 & ~yymsp[0].minor.yy207.mask) | yymsp[0].minor.yy207.value; } - break; - case 73: /* refarg ::= MATCH nm */ - case 74: /* refarg ::= ON INSERT refact */ yytestcase(yyruleno==74); -{ yygotominor.yy207.value = 0; yygotominor.yy207.mask = 0x000000; } - break; - case 75: /* refarg ::= ON DELETE refact */ -{ yygotominor.yy207.value = yymsp[0].minor.yy392; yygotominor.yy207.mask = 0x0000ff; } - break; - case 76: /* refarg ::= ON UPDATE refact */ -{ yygotominor.yy207.value = yymsp[0].minor.yy392<<8; yygotominor.yy207.mask = 0x00ff00; } - break; - case 77: /* refact ::= SET NULL */ -{ yygotominor.yy392 = OE_SetNull; /* EV: R-33326-45252 */} - break; - case 78: /* refact ::= SET DEFAULT */ -{ yygotominor.yy392 = OE_SetDflt; /* EV: R-33326-45252 */} - break; - case 79: /* refact ::= CASCADE */ -{ yygotominor.yy392 = OE_Cascade; /* EV: R-33326-45252 */} - break; - case 80: /* refact ::= RESTRICT */ -{ yygotominor.yy392 = OE_Restrict; /* EV: R-33326-45252 */} - break; - case 81: /* refact ::= NO ACTION */ -{ yygotominor.yy392 = OE_None; /* EV: R-33326-45252 */} - break; - case 83: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ - case 99: /* defer_subclause_opt ::= defer_subclause */ yytestcase(yyruleno==99); - case 101: /* onconf ::= ON CONFLICT resolvetype */ yytestcase(yyruleno==101); - case 104: /* resolvetype ::= raisetype */ yytestcase(yyruleno==104); -{yygotominor.yy392 = yymsp[0].minor.yy392;} - break; - case 87: /* conslist_opt ::= */ + case 70: /* refargs ::= */ +{ yygotominor.yy328 = OE_None*0x0101; /* EV: R-19803-45884 */} + break; + case 71: /* refargs ::= refargs refarg */ +{ yygotominor.yy328 = (yymsp[-1].minor.yy328 & ~yymsp[0].minor.yy429.mask) | yymsp[0].minor.yy429.value; } + break; + case 72: /* refarg ::= MATCH nm */ + case 73: /* refarg ::= ON INSERT refact */ yytestcase(yyruleno==73); +{ yygotominor.yy429.value = 0; yygotominor.yy429.mask = 0x000000; } + break; + case 74: /* refarg ::= ON DELETE refact */ +{ yygotominor.yy429.value = yymsp[0].minor.yy328; yygotominor.yy429.mask = 0x0000ff; } + break; + case 75: /* refarg ::= ON UPDATE refact */ +{ yygotominor.yy429.value = yymsp[0].minor.yy328<<8; yygotominor.yy429.mask = 0x00ff00; } + break; + case 76: /* refact ::= SET NULL */ +{ yygotominor.yy328 = OE_SetNull; /* EV: R-33326-45252 */} + break; + case 77: /* refact ::= SET DEFAULT */ +{ yygotominor.yy328 = OE_SetDflt; /* EV: R-33326-45252 */} + break; + case 78: /* refact ::= CASCADE */ +{ yygotominor.yy328 = OE_Cascade; /* EV: R-33326-45252 */} + break; + case 79: /* refact ::= RESTRICT */ +{ yygotominor.yy328 = OE_Restrict; /* EV: R-33326-45252 */} + break; + case 80: /* refact ::= NO ACTION */ +{ yygotominor.yy328 = OE_None; /* EV: R-33326-45252 */} + break; + case 82: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + case 98: /* defer_subclause_opt ::= defer_subclause */ yytestcase(yyruleno==98); + case 100: /* onconf ::= ON CONFLICT resolvetype */ yytestcase(yyruleno==100); + case 103: /* resolvetype ::= raisetype */ yytestcase(yyruleno==103); +{yygotominor.yy328 = yymsp[0].minor.yy328;} + break; + case 86: /* conslist_opt ::= */ {yygotominor.yy0.n = 0; yygotominor.yy0.z = 0;} break; - case 88: /* conslist_opt ::= COMMA conslist */ + case 87: /* conslist_opt ::= COMMA conslist */ {yygotominor.yy0 = yymsp[-1].minor.yy0;} break; - case 91: /* tconscomma ::= COMMA */ + case 90: /* tconscomma ::= COMMA */ {pParse->constraintName.n = 0;} break; - case 94: /* tcons ::= PRIMARY KEY LP idxlist autoinc RP onconf */ -{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy442,yymsp[0].minor.yy392,yymsp[-2].minor.yy392,0);} - break; - case 95: /* tcons ::= UNIQUE LP idxlist RP onconf */ -{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy442,yymsp[0].minor.yy392,0,0,0,0);} - break; - case 96: /* tcons ::= CHECK LP expr RP onconf */ -{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy342.pExpr);} - break; - case 97: /* tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt */ -{ - sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy442, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy442, yymsp[-1].minor.yy392); - sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy392); -} - break; - case 100: /* onconf ::= */ -{yygotominor.yy392 = OE_Default;} - break; - case 102: /* orconf ::= */ -{yygotominor.yy258 = OE_Default;} - break; - case 103: /* orconf ::= OR resolvetype */ -{yygotominor.yy258 = (u8)yymsp[0].minor.yy392;} - break; - case 105: /* resolvetype ::= IGNORE */ -{yygotominor.yy392 = OE_Ignore;} - break; - case 106: /* resolvetype ::= REPLACE */ -{yygotominor.yy392 = OE_Replace;} - break; - case 107: /* cmd ::= DROP TABLE ifexists fullname */ -{ - sqlite3DropTable(pParse, yymsp[0].minor.yy347, 0, yymsp[-1].minor.yy392); -} - break; - case 110: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm AS select */ -{ - sqlite3CreateView(pParse, &yymsp[-7].minor.yy0, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, yymsp[0].minor.yy159, yymsp[-6].minor.yy392, yymsp[-4].minor.yy392); -} - break; - case 111: /* cmd ::= DROP VIEW ifexists fullname */ -{ - sqlite3DropTable(pParse, yymsp[0].minor.yy347, 1, yymsp[-1].minor.yy392); -} - break; - case 112: /* cmd ::= select */ -{ - SelectDest dest = {SRT_Output, 0, 0, 0, 0}; - sqlite3Select(pParse, yymsp[0].minor.yy159, &dest); - sqlite3ExplainBegin(pParse->pVdbe); - sqlite3ExplainSelect(pParse->pVdbe, yymsp[0].minor.yy159); - sqlite3ExplainFinish(pParse->pVdbe); - sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy159); -} - break; - case 113: /* select ::= oneselect */ -{yygotominor.yy159 = yymsp[0].minor.yy159;} - break; - case 114: /* select ::= select multiselect_op oneselect */ -{ - if( yymsp[0].minor.yy159 ){ - yymsp[0].minor.yy159->op = (u8)yymsp[-1].minor.yy392; - yymsp[0].minor.yy159->pPrior = yymsp[-2].minor.yy159; - if( yymsp[-1].minor.yy392!=TK_ALL ) pParse->hasCompound = 1; - }else{ - sqlite3SelectDelete(pParse->db, yymsp[-2].minor.yy159); - } - yygotominor.yy159 = yymsp[0].minor.yy159; + case 93: /* tcons ::= PRIMARY KEY LP idxlist autoinc RP onconf */ +{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy14,yymsp[0].minor.yy328,yymsp[-2].minor.yy328,0);} + break; + case 94: /* tcons ::= UNIQUE LP idxlist RP onconf */ +{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy14,yymsp[0].minor.yy328,0,0,0,0);} + break; + case 95: /* tcons ::= CHECK LP expr RP onconf */ +{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy346.pExpr);} + break; + case 96: /* tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt */ +{ + sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy14, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy14, yymsp[-1].minor.yy328); + sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy328); +} + break; + case 99: /* onconf ::= */ +{yygotominor.yy328 = OE_Default;} + break; + case 101: /* orconf ::= */ +{yygotominor.yy186 = OE_Default;} + break; + case 102: /* orconf ::= OR resolvetype */ +{yygotominor.yy186 = (u8)yymsp[0].minor.yy328;} + break; + case 104: /* resolvetype ::= IGNORE */ +{yygotominor.yy328 = OE_Ignore;} + break; + case 105: /* resolvetype ::= REPLACE */ +{yygotominor.yy328 = OE_Replace;} + break; + case 106: /* cmd ::= DROP TABLE ifexists fullname */ +{ + sqlite3DropTable(pParse, yymsp[0].minor.yy65, 0, yymsp[-1].minor.yy328); +} + break; + case 109: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm AS select */ +{ + sqlite3CreateView(pParse, &yymsp[-7].minor.yy0, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, yymsp[0].minor.yy3, yymsp[-6].minor.yy328, yymsp[-4].minor.yy328); +} + break; + case 110: /* cmd ::= DROP VIEW ifexists fullname */ +{ + sqlite3DropTable(pParse, yymsp[0].minor.yy65, 1, yymsp[-1].minor.yy328); +} + break; + case 111: /* cmd ::= select */ +{ + SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0}; + sqlite3Select(pParse, yymsp[0].minor.yy3, &dest); + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy3); +} + break; + case 112: /* select ::= with selectnowith */ +{ + Select *p = yymsp[0].minor.yy3, *pNext, *pLoop; + if( p ){ + int cnt = 0, mxSelect; + p->pWith = yymsp[-1].minor.yy59; + if( p->pPrior ){ + u16 allValues = SF_Values; + pNext = 0; + for(pLoop=p; pLoop; pNext=pLoop, pLoop=pLoop->pPrior, cnt++){ + pLoop->pNext = pNext; + pLoop->selFlags |= SF_Compound; + allValues &= pLoop->selFlags; + } + if( allValues ){ + p->selFlags |= SF_AllValues; + }else if( + (mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT])>0 + && cnt>mxSelect + ){ + sqlite3ErrorMsg(pParse, "too many terms in compound SELECT"); + } + } + }else{ + sqlite3WithDelete(pParse->db, yymsp[-1].minor.yy59); + } + yygotominor.yy3 = p; +} + break; + case 113: /* selectnowith ::= oneselect */ + case 119: /* oneselect ::= values */ yytestcase(yyruleno==119); +{yygotominor.yy3 = yymsp[0].minor.yy3;} + break; + case 114: /* selectnowith ::= selectnowith multiselect_op oneselect */ +{ + Select *pRhs = yymsp[0].minor.yy3; + if( pRhs && pRhs->pPrior ){ + SrcList *pFrom; + Token x; + x.n = 0; + pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0,0); + pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0,0); + } + if( pRhs ){ + pRhs->op = (u8)yymsp[-1].minor.yy328; + pRhs->pPrior = yymsp[-2].minor.yy3; + if( yymsp[-1].minor.yy328!=TK_ALL ) pParse->hasCompound = 1; + }else{ + sqlite3SelectDelete(pParse->db, yymsp[-2].minor.yy3); + } + yygotominor.yy3 = pRhs; } break; case 116: /* multiselect_op ::= UNION ALL */ -{yygotominor.yy392 = TK_ALL;} +{yygotominor.yy328 = TK_ALL;} break; case 118: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ { - yygotominor.yy159 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy442,yymsp[-5].minor.yy347,yymsp[-4].minor.yy122,yymsp[-3].minor.yy442,yymsp[-2].minor.yy122,yymsp[-1].minor.yy442,yymsp[-7].minor.yy305,yymsp[0].minor.yy64.pLimit,yymsp[0].minor.yy64.pOffset); -} - break; - case 119: /* distinct ::= DISTINCT */ -{yygotominor.yy305 = SF_Distinct;} - break; - case 120: /* distinct ::= ALL */ - case 121: /* distinct ::= */ yytestcase(yyruleno==121); -{yygotominor.yy305 = 0;} - break; - case 122: /* sclp ::= selcollist COMMA */ - case 246: /* idxlist_opt ::= LP idxlist RP */ yytestcase(yyruleno==246); -{yygotominor.yy442 = yymsp[-1].minor.yy442;} - break; - case 123: /* sclp ::= */ - case 151: /* orderby_opt ::= */ yytestcase(yyruleno==151); - case 158: /* groupby_opt ::= */ yytestcase(yyruleno==158); - case 239: /* exprlist ::= */ yytestcase(yyruleno==239); - case 245: /* idxlist_opt ::= */ yytestcase(yyruleno==245); -{yygotominor.yy442 = 0;} - break; - case 124: /* selcollist ::= sclp expr as */ -{ - yygotominor.yy442 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy442, yymsp[-1].minor.yy342.pExpr); - if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yygotominor.yy442, &yymsp[0].minor.yy0, 1); - sqlite3ExprListSetSpan(pParse,yygotominor.yy442,&yymsp[-1].minor.yy342); -} - break; - case 125: /* selcollist ::= sclp STAR */ + yygotominor.yy3 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy14,yymsp[-5].minor.yy65,yymsp[-4].minor.yy132,yymsp[-3].minor.yy14,yymsp[-2].minor.yy132,yymsp[-1].minor.yy14,yymsp[-7].minor.yy381,yymsp[0].minor.yy476.pLimit,yymsp[0].minor.yy476.pOffset); +#if SELECTTRACE_ENABLED + /* Populate the Select.zSelName[] string that is used to help with + ** query planner debugging, to differentiate between multiple Select + ** objects in a complex query. + ** + ** If the SELECT keyword is immediately followed by a C-style comment + ** then extract the first few alphanumeric characters from within that + ** comment to be the zSelName value. Otherwise, the label is #N where + ** is an integer that is incremented with each SELECT statement seen. + */ + if( yygotominor.yy3!=0 ){ + const char *z = yymsp[-8].minor.yy0.z+6; + int i; + sqlite3_snprintf(sizeof(yygotominor.yy3->zSelName), yygotominor.yy3->zSelName, "#%d", + ++pParse->nSelect); + while( z[0]==' ' ) z++; + if( z[0]=='/' && z[1]=='*' ){ + z += 2; + while( z[0]==' ' ) z++; + for(i=0; sqlite3Isalnum(z[i]); i++){} + sqlite3_snprintf(sizeof(yygotominor.yy3->zSelName), yygotominor.yy3->zSelName, "%.*s", i, z); + } + } +#endif /* SELECTRACE_ENABLED */ +} + break; + case 120: /* values ::= VALUES LP nexprlist RP */ +{ + yygotominor.yy3 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy14,0,0,0,0,0,SF_Values,0,0); +} + break; + case 121: /* values ::= values COMMA LP exprlist RP */ +{ + Select *pRight = sqlite3SelectNew(pParse,yymsp[-1].minor.yy14,0,0,0,0,0,SF_Values,0,0); + if( pRight ){ + pRight->op = TK_ALL; + pRight->pPrior = yymsp[-4].minor.yy3; + yygotominor.yy3 = pRight; + }else{ + yygotominor.yy3 = yymsp[-4].minor.yy3; + } +} + break; + case 122: /* distinct ::= DISTINCT */ +{yygotominor.yy381 = SF_Distinct;} + break; + case 123: /* distinct ::= ALL */ + case 124: /* distinct ::= */ yytestcase(yyruleno==124); +{yygotominor.yy381 = 0;} + break; + case 125: /* sclp ::= selcollist COMMA */ + case 243: /* idxlist_opt ::= LP idxlist RP */ yytestcase(yyruleno==243); +{yygotominor.yy14 = yymsp[-1].minor.yy14;} + break; + case 126: /* sclp ::= */ + case 154: /* orderby_opt ::= */ yytestcase(yyruleno==154); + case 161: /* groupby_opt ::= */ yytestcase(yyruleno==161); + case 236: /* exprlist ::= */ yytestcase(yyruleno==236); + case 242: /* idxlist_opt ::= */ yytestcase(yyruleno==242); +{yygotominor.yy14 = 0;} + break; + case 127: /* selcollist ::= sclp expr as */ +{ + yygotominor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy14, yymsp[-1].minor.yy346.pExpr); + if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yygotominor.yy14, &yymsp[0].minor.yy0, 1); + sqlite3ExprListSetSpan(pParse,yygotominor.yy14,&yymsp[-1].minor.yy346); +} + break; + case 128: /* selcollist ::= sclp STAR */ { Expr *p = sqlite3Expr(pParse->db, TK_ALL, 0); - yygotominor.yy442 = sqlite3ExprListAppend(pParse, yymsp[-1].minor.yy442, p); + yygotominor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-1].minor.yy14, p); } break; - case 126: /* selcollist ::= sclp nm DOT STAR */ + case 129: /* selcollist ::= sclp nm DOT STAR */ { Expr *pRight = sqlite3PExpr(pParse, TK_ALL, 0, 0, &yymsp[0].minor.yy0); Expr *pLeft = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy0); Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight, 0); - yygotominor.yy442 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy442, pDot); + yygotominor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy14, pDot); } break; - case 129: /* as ::= */ + case 132: /* as ::= */ {yygotominor.yy0.n = 0;} break; - case 130: /* from ::= */ -{yygotominor.yy347 = sqlite3DbMallocZero(pParse->db, sizeof(*yygotominor.yy347));} - break; - case 131: /* from ::= FROM seltablist */ -{ - yygotominor.yy347 = yymsp[0].minor.yy347; - sqlite3SrcListShiftJoinType(yygotominor.yy347); -} - break; - case 132: /* stl_prefix ::= seltablist joinop */ -{ - yygotominor.yy347 = yymsp[-1].minor.yy347; - if( ALWAYS(yygotominor.yy347 && yygotominor.yy347->nSrc>0) ) yygotominor.yy347->a[yygotominor.yy347->nSrc-1].jointype = (u8)yymsp[0].minor.yy392; -} - break; - case 133: /* stl_prefix ::= */ -{yygotominor.yy347 = 0;} - break; - case 134: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ -{ - yygotominor.yy347 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy347,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy122,yymsp[0].minor.yy180); - sqlite3SrcListIndexedBy(pParse, yygotominor.yy347, &yymsp[-2].minor.yy0); -} - break; - case 135: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */ -{ - yygotominor.yy347 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy347,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy159,yymsp[-1].minor.yy122,yymsp[0].minor.yy180); - } - break; - case 136: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ -{ - if( yymsp[-6].minor.yy347==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy122==0 && yymsp[0].minor.yy180==0 ){ - yygotominor.yy347 = yymsp[-4].minor.yy347; - }else if( yymsp[-4].minor.yy347->nSrc==1 ){ - yygotominor.yy347 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy347,0,0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy122,yymsp[0].minor.yy180); - if( yygotominor.yy347 ){ - struct SrcList_item *pNew = &yygotominor.yy347->a[yygotominor.yy347->nSrc-1]; - struct SrcList_item *pOld = yymsp[-4].minor.yy347->a; + case 133: /* from ::= */ +{yygotominor.yy65 = sqlite3DbMallocZero(pParse->db, sizeof(*yygotominor.yy65));} + break; + case 134: /* from ::= FROM seltablist */ +{ + yygotominor.yy65 = yymsp[0].minor.yy65; + sqlite3SrcListShiftJoinType(yygotominor.yy65); +} + break; + case 135: /* stl_prefix ::= seltablist joinop */ +{ + yygotominor.yy65 = yymsp[-1].minor.yy65; + if( ALWAYS(yygotominor.yy65 && yygotominor.yy65->nSrc>0) ) yygotominor.yy65->a[yygotominor.yy65->nSrc-1].jointype = (u8)yymsp[0].minor.yy328; +} + break; + case 136: /* stl_prefix ::= */ +{yygotominor.yy65 = 0;} + break; + case 137: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ +{ + yygotominor.yy65 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy65,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy132,yymsp[0].minor.yy408); + sqlite3SrcListIndexedBy(pParse, yygotominor.yy65, &yymsp[-2].minor.yy0); +} + break; + case 138: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */ +{ + yygotominor.yy65 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy65,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy3,yymsp[-1].minor.yy132,yymsp[0].minor.yy408); + } + break; + case 139: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ +{ + if( yymsp[-6].minor.yy65==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy132==0 && yymsp[0].minor.yy408==0 ){ + yygotominor.yy65 = yymsp[-4].minor.yy65; + }else if( yymsp[-4].minor.yy65->nSrc==1 ){ + yygotominor.yy65 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy65,0,0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy132,yymsp[0].minor.yy408); + if( yygotominor.yy65 ){ + struct SrcList_item *pNew = &yygotominor.yy65->a[yygotominor.yy65->nSrc-1]; + struct SrcList_item *pOld = yymsp[-4].minor.yy65->a; pNew->zName = pOld->zName; pNew->zDatabase = pOld->zDatabase; pNew->pSelect = pOld->pSelect; pOld->zName = pOld->zDatabase = 0; pOld->pSelect = 0; } - sqlite3SrcListDelete(pParse->db, yymsp[-4].minor.yy347); + sqlite3SrcListDelete(pParse->db, yymsp[-4].minor.yy65); }else{ Select *pSubquery; - sqlite3SrcListShiftJoinType(yymsp[-4].minor.yy347); - pSubquery = sqlite3SelectNew(pParse,0,yymsp[-4].minor.yy347,0,0,0,0,SF_NestedFrom,0,0); - yygotominor.yy347 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy347,0,0,&yymsp[-2].minor.yy0,pSubquery,yymsp[-1].minor.yy122,yymsp[0].minor.yy180); + sqlite3SrcListShiftJoinType(yymsp[-4].minor.yy65); + pSubquery = sqlite3SelectNew(pParse,0,yymsp[-4].minor.yy65,0,0,0,0,SF_NestedFrom,0,0); + yygotominor.yy65 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy65,0,0,&yymsp[-2].minor.yy0,pSubquery,yymsp[-1].minor.yy132,yymsp[0].minor.yy408); } } break; - case 137: /* dbnm ::= */ - case 146: /* indexed_opt ::= */ yytestcase(yyruleno==146); + case 140: /* dbnm ::= */ + case 149: /* indexed_opt ::= */ yytestcase(yyruleno==149); {yygotominor.yy0.z=0; yygotominor.yy0.n=0;} break; - case 139: /* fullname ::= nm dbnm */ -{yygotominor.yy347 = sqlite3SrcListAppend(pParse->db,0,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);} - break; - case 140: /* joinop ::= COMMA|JOIN */ -{ yygotominor.yy392 = JT_INNER; } - break; - case 141: /* joinop ::= JOIN_KW JOIN */ -{ yygotominor.yy392 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); } - break; - case 142: /* joinop ::= JOIN_KW nm JOIN */ -{ yygotominor.yy392 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); } - break; - case 143: /* joinop ::= JOIN_KW nm nm JOIN */ -{ yygotominor.yy392 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0); } - break; - case 144: /* on_opt ::= ON expr */ - case 161: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==161); - case 168: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==168); - case 234: /* case_else ::= ELSE expr */ yytestcase(yyruleno==234); - case 236: /* case_operand ::= expr */ yytestcase(yyruleno==236); -{yygotominor.yy122 = yymsp[0].minor.yy342.pExpr;} - break; - case 145: /* on_opt ::= */ - case 160: /* having_opt ::= */ yytestcase(yyruleno==160); - case 167: /* where_opt ::= */ yytestcase(yyruleno==167); - case 235: /* case_else ::= */ yytestcase(yyruleno==235); - case 237: /* case_operand ::= */ yytestcase(yyruleno==237); -{yygotominor.yy122 = 0;} - break; - case 148: /* indexed_opt ::= NOT INDEXED */ + case 142: /* fullname ::= nm dbnm */ +{yygotominor.yy65 = sqlite3SrcListAppend(pParse->db,0,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);} + break; + case 143: /* joinop ::= COMMA|JOIN */ +{ yygotominor.yy328 = JT_INNER; } + break; + case 144: /* joinop ::= JOIN_KW JOIN */ +{ yygotominor.yy328 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); } + break; + case 145: /* joinop ::= JOIN_KW nm JOIN */ +{ yygotominor.yy328 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); } + break; + case 146: /* joinop ::= JOIN_KW nm nm JOIN */ +{ yygotominor.yy328 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0); } + break; + case 147: /* on_opt ::= ON expr */ + case 164: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==164); + case 171: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==171); + case 231: /* case_else ::= ELSE expr */ yytestcase(yyruleno==231); + case 233: /* case_operand ::= expr */ yytestcase(yyruleno==233); +{yygotominor.yy132 = yymsp[0].minor.yy346.pExpr;} + break; + case 148: /* on_opt ::= */ + case 163: /* having_opt ::= */ yytestcase(yyruleno==163); + case 170: /* where_opt ::= */ yytestcase(yyruleno==170); + case 232: /* case_else ::= */ yytestcase(yyruleno==232); + case 234: /* case_operand ::= */ yytestcase(yyruleno==234); +{yygotominor.yy132 = 0;} + break; + case 151: /* indexed_opt ::= NOT INDEXED */ {yygotominor.yy0.z=0; yygotominor.yy0.n=1;} break; - case 149: /* using_opt ::= USING LP inscollist RP */ - case 180: /* inscollist_opt ::= LP inscollist RP */ yytestcase(yyruleno==180); -{yygotominor.yy180 = yymsp[-1].minor.yy180;} + case 152: /* using_opt ::= USING LP idlist RP */ + case 180: /* inscollist_opt ::= LP idlist RP */ yytestcase(yyruleno==180); +{yygotominor.yy408 = yymsp[-1].minor.yy408;} break; - case 150: /* using_opt ::= */ + case 153: /* using_opt ::= */ case 179: /* inscollist_opt ::= */ yytestcase(yyruleno==179); -{yygotominor.yy180 = 0;} - break; - case 152: /* orderby_opt ::= ORDER BY sortlist */ - case 159: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==159); - case 238: /* exprlist ::= nexprlist */ yytestcase(yyruleno==238); -{yygotominor.yy442 = yymsp[0].minor.yy442;} - break; - case 153: /* sortlist ::= sortlist COMMA expr sortorder */ -{ - yygotominor.yy442 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy442,yymsp[-1].minor.yy342.pExpr); - if( yygotominor.yy442 ) yygotominor.yy442->a[yygotominor.yy442->nExpr-1].sortOrder = (u8)yymsp[0].minor.yy392; -} - break; - case 154: /* sortlist ::= expr sortorder */ -{ - yygotominor.yy442 = sqlite3ExprListAppend(pParse,0,yymsp[-1].minor.yy342.pExpr); - if( yygotominor.yy442 && ALWAYS(yygotominor.yy442->a) ) yygotominor.yy442->a[0].sortOrder = (u8)yymsp[0].minor.yy392; -} - break; - case 155: /* sortorder ::= ASC */ - case 157: /* sortorder ::= */ yytestcase(yyruleno==157); -{yygotominor.yy392 = SQLITE_SO_ASC;} - break; - case 156: /* sortorder ::= DESC */ -{yygotominor.yy392 = SQLITE_SO_DESC;} - break; - case 162: /* limit_opt ::= */ -{yygotominor.yy64.pLimit = 0; yygotominor.yy64.pOffset = 0;} - break; - case 163: /* limit_opt ::= LIMIT expr */ -{yygotominor.yy64.pLimit = yymsp[0].minor.yy342.pExpr; yygotominor.yy64.pOffset = 0;} - break; - case 164: /* limit_opt ::= LIMIT expr OFFSET expr */ -{yygotominor.yy64.pLimit = yymsp[-2].minor.yy342.pExpr; yygotominor.yy64.pOffset = yymsp[0].minor.yy342.pExpr;} - break; - case 165: /* limit_opt ::= LIMIT expr COMMA expr */ -{yygotominor.yy64.pOffset = yymsp[-2].minor.yy342.pExpr; yygotominor.yy64.pLimit = yymsp[0].minor.yy342.pExpr;} - break; - case 166: /* cmd ::= DELETE FROM fullname indexed_opt where_opt */ -{ - sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy347, &yymsp[-1].minor.yy0); - sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy347,yymsp[0].minor.yy122); -} - break; - case 169: /* cmd ::= UPDATE orconf fullname indexed_opt SET setlist where_opt */ -{ - sqlite3SrcListIndexedBy(pParse, yymsp[-4].minor.yy347, &yymsp[-3].minor.yy0); - sqlite3ExprListCheckLength(pParse,yymsp[-1].minor.yy442,"set list"); - sqlite3Update(pParse,yymsp[-4].minor.yy347,yymsp[-1].minor.yy442,yymsp[0].minor.yy122,yymsp[-5].minor.yy258); -} - break; - case 170: /* setlist ::= setlist COMMA nm EQ expr */ -{ - yygotominor.yy442 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy442, yymsp[0].minor.yy342.pExpr); - sqlite3ExprListSetName(pParse, yygotominor.yy442, &yymsp[-2].minor.yy0, 1); -} - break; - case 171: /* setlist ::= nm EQ expr */ -{ - yygotominor.yy442 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy342.pExpr); - sqlite3ExprListSetName(pParse, yygotominor.yy442, &yymsp[-2].minor.yy0, 1); -} - break; - case 172: /* cmd ::= insert_cmd INTO fullname inscollist_opt valuelist */ -{sqlite3Insert(pParse, yymsp[-2].minor.yy347, yymsp[0].minor.yy487.pList, yymsp[0].minor.yy487.pSelect, yymsp[-1].minor.yy180, yymsp[-4].minor.yy258);} - break; - case 173: /* cmd ::= insert_cmd INTO fullname inscollist_opt select */ -{sqlite3Insert(pParse, yymsp[-2].minor.yy347, 0, yymsp[0].minor.yy159, yymsp[-1].minor.yy180, yymsp[-4].minor.yy258);} - break; - case 174: /* cmd ::= insert_cmd INTO fullname inscollist_opt DEFAULT VALUES */ -{sqlite3Insert(pParse, yymsp[-3].minor.yy347, 0, 0, yymsp[-2].minor.yy180, yymsp[-5].minor.yy258);} - break; - case 175: /* insert_cmd ::= INSERT orconf */ -{yygotominor.yy258 = yymsp[0].minor.yy258;} - break; - case 176: /* insert_cmd ::= REPLACE */ -{yygotominor.yy258 = OE_Replace;} - break; - case 177: /* valuelist ::= VALUES LP nexprlist RP */ -{ - yygotominor.yy487.pList = yymsp[-1].minor.yy442; - yygotominor.yy487.pSelect = 0; -} - break; - case 178: /* valuelist ::= valuelist COMMA LP exprlist RP */ -{ - Select *pRight = sqlite3SelectNew(pParse, yymsp[-1].minor.yy442, 0, 0, 0, 0, 0, 0, 0, 0); - if( yymsp[-4].minor.yy487.pList ){ - yymsp[-4].minor.yy487.pSelect = sqlite3SelectNew(pParse, yymsp[-4].minor.yy487.pList, 0, 0, 0, 0, 0, 0, 0, 0); - yymsp[-4].minor.yy487.pList = 0; - } - yygotominor.yy487.pList = 0; - if( yymsp[-4].minor.yy487.pSelect==0 || pRight==0 ){ - sqlite3SelectDelete(pParse->db, pRight); - sqlite3SelectDelete(pParse->db, yymsp[-4].minor.yy487.pSelect); - yygotominor.yy487.pSelect = 0; - }else{ - pRight->op = TK_ALL; - pRight->pPrior = yymsp[-4].minor.yy487.pSelect; - pRight->selFlags |= SF_Values; - pRight->pPrior->selFlags |= SF_Values; - yygotominor.yy487.pSelect = pRight; - } -} - break; - case 181: /* inscollist ::= inscollist COMMA nm */ -{yygotominor.yy180 = sqlite3IdListAppend(pParse->db,yymsp[-2].minor.yy180,&yymsp[0].minor.yy0);} - break; - case 182: /* inscollist ::= nm */ -{yygotominor.yy180 = sqlite3IdListAppend(pParse->db,0,&yymsp[0].minor.yy0);} +{yygotominor.yy408 = 0;} + break; + case 155: /* orderby_opt ::= ORDER BY sortlist */ + case 162: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==162); + case 235: /* exprlist ::= nexprlist */ yytestcase(yyruleno==235); +{yygotominor.yy14 = yymsp[0].minor.yy14;} + break; + case 156: /* sortlist ::= sortlist COMMA expr sortorder */ +{ + yygotominor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy14,yymsp[-1].minor.yy346.pExpr); + if( yygotominor.yy14 ) yygotominor.yy14->a[yygotominor.yy14->nExpr-1].sortOrder = (u8)yymsp[0].minor.yy328; +} + break; + case 157: /* sortlist ::= expr sortorder */ +{ + yygotominor.yy14 = sqlite3ExprListAppend(pParse,0,yymsp[-1].minor.yy346.pExpr); + if( yygotominor.yy14 && ALWAYS(yygotominor.yy14->a) ) yygotominor.yy14->a[0].sortOrder = (u8)yymsp[0].minor.yy328; +} + break; + case 158: /* sortorder ::= ASC */ + case 160: /* sortorder ::= */ yytestcase(yyruleno==160); +{yygotominor.yy328 = SQLITE_SO_ASC;} + break; + case 159: /* sortorder ::= DESC */ +{yygotominor.yy328 = SQLITE_SO_DESC;} + break; + case 165: /* limit_opt ::= */ +{yygotominor.yy476.pLimit = 0; yygotominor.yy476.pOffset = 0;} + break; + case 166: /* limit_opt ::= LIMIT expr */ +{yygotominor.yy476.pLimit = yymsp[0].minor.yy346.pExpr; yygotominor.yy476.pOffset = 0;} + break; + case 167: /* limit_opt ::= LIMIT expr OFFSET expr */ +{yygotominor.yy476.pLimit = yymsp[-2].minor.yy346.pExpr; yygotominor.yy476.pOffset = yymsp[0].minor.yy346.pExpr;} + break; + case 168: /* limit_opt ::= LIMIT expr COMMA expr */ +{yygotominor.yy476.pOffset = yymsp[-2].minor.yy346.pExpr; yygotominor.yy476.pLimit = yymsp[0].minor.yy346.pExpr;} + break; + case 169: /* cmd ::= with DELETE FROM fullname indexed_opt where_opt */ +{ + sqlite3WithPush(pParse, yymsp[-5].minor.yy59, 1); + sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy65, &yymsp[-1].minor.yy0); + sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy65,yymsp[0].minor.yy132); +} + break; + case 172: /* cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt */ +{ + sqlite3WithPush(pParse, yymsp[-7].minor.yy59, 1); + sqlite3SrcListIndexedBy(pParse, yymsp[-4].minor.yy65, &yymsp[-3].minor.yy0); + sqlite3ExprListCheckLength(pParse,yymsp[-1].minor.yy14,"set list"); + sqlite3Update(pParse,yymsp[-4].minor.yy65,yymsp[-1].minor.yy14,yymsp[0].minor.yy132,yymsp[-5].minor.yy186); +} + break; + case 173: /* setlist ::= setlist COMMA nm EQ expr */ +{ + yygotominor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy14, yymsp[0].minor.yy346.pExpr); + sqlite3ExprListSetName(pParse, yygotominor.yy14, &yymsp[-2].minor.yy0, 1); +} + break; + case 174: /* setlist ::= nm EQ expr */ +{ + yygotominor.yy14 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy346.pExpr); + sqlite3ExprListSetName(pParse, yygotominor.yy14, &yymsp[-2].minor.yy0, 1); +} + break; + case 175: /* cmd ::= with insert_cmd INTO fullname inscollist_opt select */ +{ + sqlite3WithPush(pParse, yymsp[-5].minor.yy59, 1); + sqlite3Insert(pParse, yymsp[-2].minor.yy65, yymsp[0].minor.yy3, yymsp[-1].minor.yy408, yymsp[-4].minor.yy186); +} + break; + case 176: /* cmd ::= with insert_cmd INTO fullname inscollist_opt DEFAULT VALUES */ +{ + sqlite3WithPush(pParse, yymsp[-6].minor.yy59, 1); + sqlite3Insert(pParse, yymsp[-3].minor.yy65, 0, yymsp[-2].minor.yy408, yymsp[-5].minor.yy186); +} + break; + case 177: /* insert_cmd ::= INSERT orconf */ +{yygotominor.yy186 = yymsp[0].minor.yy186;} + break; + case 178: /* insert_cmd ::= REPLACE */ +{yygotominor.yy186 = OE_Replace;} + break; + case 181: /* idlist ::= idlist COMMA nm */ +{yygotominor.yy408 = sqlite3IdListAppend(pParse->db,yymsp[-2].minor.yy408,&yymsp[0].minor.yy0);} + break; + case 182: /* idlist ::= nm */ +{yygotominor.yy408 = sqlite3IdListAppend(pParse->db,0,&yymsp[0].minor.yy0);} break; case 183: /* expr ::= term */ -{yygotominor.yy342 = yymsp[0].minor.yy342;} +{yygotominor.yy346 = yymsp[0].minor.yy346;} break; case 184: /* expr ::= LP expr RP */ -{yygotominor.yy342.pExpr = yymsp[-1].minor.yy342.pExpr; spanSet(&yygotominor.yy342,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0);} +{yygotominor.yy346.pExpr = yymsp[-1].minor.yy346.pExpr; spanSet(&yygotominor.yy346,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0);} break; case 185: /* term ::= NULL */ case 190: /* term ::= INTEGER|FLOAT|BLOB */ yytestcase(yyruleno==190); case 191: /* term ::= STRING */ yytestcase(yyruleno==191); -{spanExpr(&yygotominor.yy342, pParse, yymsp[0].major, &yymsp[0].minor.yy0);} +{spanExpr(&yygotominor.yy346, pParse, yymsp[0].major, &yymsp[0].minor.yy0);} break; - case 186: /* expr ::= id */ + case 186: /* expr ::= ID|INDEXED */ case 187: /* expr ::= JOIN_KW */ yytestcase(yyruleno==187); -{spanExpr(&yygotominor.yy342, pParse, TK_ID, &yymsp[0].minor.yy0);} +{spanExpr(&yygotominor.yy346, pParse, TK_ID, &yymsp[0].minor.yy0);} break; case 188: /* expr ::= nm DOT nm */ { Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy0); Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[0].minor.yy0); - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0); - spanSet(&yygotominor.yy342,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0); + spanSet(&yygotominor.yy346,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); } break; case 189: /* expr ::= nm DOT nm DOT nm */ { Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-4].minor.yy0); Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy0); Expr *temp3 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[0].minor.yy0); Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3, 0); - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp4, 0); - spanSet(&yygotominor.yy342,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); -} - break; - case 192: /* expr ::= REGISTER */ -{ - /* When doing a nested parse, one can include terms in an expression - ** that look like this: #1 #2 ... These terms refer to registers - ** in the virtual machine. #N is the N-th register. */ - if( pParse->nested==0 ){ - sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &yymsp[0].minor.yy0); - yygotominor.yy342.pExpr = 0; - }else{ - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_REGISTER, 0, 0, &yymsp[0].minor.yy0); - if( yygotominor.yy342.pExpr ) sqlite3GetInt32(&yymsp[0].minor.yy0.z[1], &yygotominor.yy342.pExpr->iTable); - } - spanSet(&yygotominor.yy342, &yymsp[0].minor.yy0, &yymsp[0].minor.yy0); -} - break; - case 193: /* expr ::= VARIABLE */ -{ - spanExpr(&yygotominor.yy342, pParse, TK_VARIABLE, &yymsp[0].minor.yy0); - sqlite3ExprAssignVarNumber(pParse, yygotominor.yy342.pExpr); - spanSet(&yygotominor.yy342, &yymsp[0].minor.yy0, &yymsp[0].minor.yy0); -} - break; - case 194: /* expr ::= expr COLLATE ids */ -{ - yygotominor.yy342.pExpr = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy342.pExpr, &yymsp[0].minor.yy0); - yygotominor.yy342.zStart = yymsp[-2].minor.yy342.zStart; - yygotominor.yy342.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; -} - break; - case 195: /* expr ::= CAST LP expr AS typetoken RP */ -{ - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_CAST, yymsp[-3].minor.yy342.pExpr, 0, &yymsp[-1].minor.yy0); - spanSet(&yygotominor.yy342,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0); -} - break; - case 196: /* expr ::= ID LP distinct exprlist RP */ -{ - if( yymsp[-1].minor.yy442 && yymsp[-1].minor.yy442->nExpr>pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){ - sqlite3ErrorMsg(pParse, "too many arguments on function %T", &yymsp[-4].minor.yy0); - } - yygotominor.yy342.pExpr = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy442, &yymsp[-4].minor.yy0); - spanSet(&yygotominor.yy342,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); - if( yymsp[-2].minor.yy305 && yygotominor.yy342.pExpr ){ - yygotominor.yy342.pExpr->flags |= EP_Distinct; - } -} - break; - case 197: /* expr ::= ID LP STAR RP */ -{ - yygotominor.yy342.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0); - spanSet(&yygotominor.yy342,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); -} - break; - case 198: /* term ::= CTIME_KW */ -{ - /* The CURRENT_TIME, CURRENT_DATE, and CURRENT_TIMESTAMP values are - ** treated as functions that return constants */ - yygotominor.yy342.pExpr = sqlite3ExprFunction(pParse, 0,&yymsp[0].minor.yy0); - if( yygotominor.yy342.pExpr ){ - yygotominor.yy342.pExpr->op = TK_CONST_FUNC; - } - spanSet(&yygotominor.yy342, &yymsp[0].minor.yy0, &yymsp[0].minor.yy0); -} - break; - case 199: /* expr ::= expr AND expr */ - case 200: /* expr ::= expr OR expr */ yytestcase(yyruleno==200); - case 201: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==201); - case 202: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==202); - case 203: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==203); - case 204: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==204); - case 205: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==205); - case 206: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==206); -{spanBinaryExpr(&yygotominor.yy342,pParse,yymsp[-1].major,&yymsp[-2].minor.yy342,&yymsp[0].minor.yy342);} - break; - case 207: /* likeop ::= LIKE_KW */ - case 209: /* likeop ::= MATCH */ yytestcase(yyruleno==209); -{yygotominor.yy318.eOperator = yymsp[0].minor.yy0; yygotominor.yy318.bNot = 0;} - break; - case 208: /* likeop ::= NOT LIKE_KW */ - case 210: /* likeop ::= NOT MATCH */ yytestcase(yyruleno==210); -{yygotominor.yy318.eOperator = yymsp[0].minor.yy0; yygotominor.yy318.bNot = 1;} - break; - case 211: /* expr ::= expr likeop expr */ -{ - ExprList *pList; - pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy342.pExpr); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy342.pExpr); - yygotominor.yy342.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy318.eOperator); - if( yymsp[-1].minor.yy318.bNot ) yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy342.pExpr, 0, 0); - yygotominor.yy342.zStart = yymsp[-2].minor.yy342.zStart; - yygotominor.yy342.zEnd = yymsp[0].minor.yy342.zEnd; - if( yygotominor.yy342.pExpr ) yygotominor.yy342.pExpr->flags |= EP_InfixFunc; -} - break; - case 212: /* expr ::= expr likeop expr ESCAPE expr */ -{ - ExprList *pList; - pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy342.pExpr); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy342.pExpr); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy342.pExpr); - yygotominor.yy342.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy318.eOperator); - if( yymsp[-3].minor.yy318.bNot ) yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy342.pExpr, 0, 0); - yygotominor.yy342.zStart = yymsp[-4].minor.yy342.zStart; - yygotominor.yy342.zEnd = yymsp[0].minor.yy342.zEnd; - if( yygotominor.yy342.pExpr ) yygotominor.yy342.pExpr->flags |= EP_InfixFunc; -} - break; - case 213: /* expr ::= expr ISNULL|NOTNULL */ -{spanUnaryPostfix(&yygotominor.yy342,pParse,yymsp[0].major,&yymsp[-1].minor.yy342,&yymsp[0].minor.yy0);} - break; - case 214: /* expr ::= expr NOT NULL */ -{spanUnaryPostfix(&yygotominor.yy342,pParse,TK_NOTNULL,&yymsp[-2].minor.yy342,&yymsp[0].minor.yy0);} - break; - case 215: /* expr ::= expr IS expr */ -{ - spanBinaryExpr(&yygotominor.yy342,pParse,TK_IS,&yymsp[-2].minor.yy342,&yymsp[0].minor.yy342); - binaryToUnaryIfNull(pParse, yymsp[0].minor.yy342.pExpr, yygotominor.yy342.pExpr, TK_ISNULL); -} - break; - case 216: /* expr ::= expr IS NOT expr */ -{ - spanBinaryExpr(&yygotominor.yy342,pParse,TK_ISNOT,&yymsp[-3].minor.yy342,&yymsp[0].minor.yy342); - binaryToUnaryIfNull(pParse, yymsp[0].minor.yy342.pExpr, yygotominor.yy342.pExpr, TK_NOTNULL); -} - break; - case 217: /* expr ::= NOT expr */ - case 218: /* expr ::= BITNOT expr */ yytestcase(yyruleno==218); -{spanUnaryPrefix(&yygotominor.yy342,pParse,yymsp[-1].major,&yymsp[0].minor.yy342,&yymsp[-1].minor.yy0);} - break; - case 219: /* expr ::= MINUS expr */ -{spanUnaryPrefix(&yygotominor.yy342,pParse,TK_UMINUS,&yymsp[0].minor.yy342,&yymsp[-1].minor.yy0);} - break; - case 220: /* expr ::= PLUS expr */ -{spanUnaryPrefix(&yygotominor.yy342,pParse,TK_UPLUS,&yymsp[0].minor.yy342,&yymsp[-1].minor.yy0);} - break; - case 223: /* expr ::= expr between_op expr AND expr */ -{ - ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy342.pExpr); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy342.pExpr); - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy342.pExpr, 0, 0); - if( yygotominor.yy342.pExpr ){ - yygotominor.yy342.pExpr->x.pList = pList; + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp4, 0); + spanSet(&yygotominor.yy346,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); +} + break; + case 192: /* expr ::= VARIABLE */ +{ + if( yymsp[0].minor.yy0.n>=2 && yymsp[0].minor.yy0.z[0]=='#' && sqlite3Isdigit(yymsp[0].minor.yy0.z[1]) ){ + /* When doing a nested parse, one can include terms in an expression + ** that look like this: #1 #2 ... These terms refer to registers + ** in the virtual machine. #N is the N-th register. */ + if( pParse->nested==0 ){ + sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &yymsp[0].minor.yy0); + yygotominor.yy346.pExpr = 0; + }else{ + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_REGISTER, 0, 0, &yymsp[0].minor.yy0); + if( yygotominor.yy346.pExpr ) sqlite3GetInt32(&yymsp[0].minor.yy0.z[1], &yygotominor.yy346.pExpr->iTable); + } + }else{ + spanExpr(&yygotominor.yy346, pParse, TK_VARIABLE, &yymsp[0].minor.yy0); + sqlite3ExprAssignVarNumber(pParse, yygotominor.yy346.pExpr); + } + spanSet(&yygotominor.yy346, &yymsp[0].minor.yy0, &yymsp[0].minor.yy0); +} + break; + case 193: /* expr ::= expr COLLATE ID|STRING */ +{ + yygotominor.yy346.pExpr = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy346.pExpr, &yymsp[0].minor.yy0, 1); + yygotominor.yy346.zStart = yymsp[-2].minor.yy346.zStart; + yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; +} + break; + case 194: /* expr ::= CAST LP expr AS typetoken RP */ +{ + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_CAST, yymsp[-3].minor.yy346.pExpr, 0, &yymsp[-1].minor.yy0); + spanSet(&yygotominor.yy346,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0); +} + break; + case 195: /* expr ::= ID|INDEXED LP distinct exprlist RP */ +{ + if( yymsp[-1].minor.yy14 && yymsp[-1].minor.yy14->nExpr>pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){ + sqlite3ErrorMsg(pParse, "too many arguments on function %T", &yymsp[-4].minor.yy0); + } + yygotominor.yy346.pExpr = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy14, &yymsp[-4].minor.yy0); + spanSet(&yygotominor.yy346,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); + if( yymsp[-2].minor.yy381 && yygotominor.yy346.pExpr ){ + yygotominor.yy346.pExpr->flags |= EP_Distinct; + } +} + break; + case 196: /* expr ::= ID|INDEXED LP STAR RP */ +{ + yygotominor.yy346.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0); + spanSet(&yygotominor.yy346,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); +} + break; + case 197: /* term ::= CTIME_KW */ +{ + yygotominor.yy346.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0); + spanSet(&yygotominor.yy346, &yymsp[0].minor.yy0, &yymsp[0].minor.yy0); +} + break; + case 198: /* expr ::= expr AND expr */ + case 199: /* expr ::= expr OR expr */ yytestcase(yyruleno==199); + case 200: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==200); + case 201: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==201); + case 202: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==202); + case 203: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==203); + case 204: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==204); + case 205: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==205); +{spanBinaryExpr(&yygotominor.yy346,pParse,yymsp[-1].major,&yymsp[-2].minor.yy346,&yymsp[0].minor.yy346);} + break; + case 206: /* likeop ::= LIKE_KW|MATCH */ +{yygotominor.yy96.eOperator = yymsp[0].minor.yy0; yygotominor.yy96.bNot = 0;} + break; + case 207: /* likeop ::= NOT LIKE_KW|MATCH */ +{yygotominor.yy96.eOperator = yymsp[0].minor.yy0; yygotominor.yy96.bNot = 1;} + break; + case 208: /* expr ::= expr likeop expr */ +{ + ExprList *pList; + pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy346.pExpr); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy346.pExpr); + yygotominor.yy346.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy96.eOperator); + if( yymsp[-1].minor.yy96.bNot ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); + yygotominor.yy346.zStart = yymsp[-2].minor.yy346.zStart; + yygotominor.yy346.zEnd = yymsp[0].minor.yy346.zEnd; + if( yygotominor.yy346.pExpr ) yygotominor.yy346.pExpr->flags |= EP_InfixFunc; +} + break; + case 209: /* expr ::= expr likeop expr ESCAPE expr */ +{ + ExprList *pList; + pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy346.pExpr); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy346.pExpr); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy346.pExpr); + yygotominor.yy346.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy96.eOperator); + if( yymsp[-3].minor.yy96.bNot ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); + yygotominor.yy346.zStart = yymsp[-4].minor.yy346.zStart; + yygotominor.yy346.zEnd = yymsp[0].minor.yy346.zEnd; + if( yygotominor.yy346.pExpr ) yygotominor.yy346.pExpr->flags |= EP_InfixFunc; +} + break; + case 210: /* expr ::= expr ISNULL|NOTNULL */ +{spanUnaryPostfix(&yygotominor.yy346,pParse,yymsp[0].major,&yymsp[-1].minor.yy346,&yymsp[0].minor.yy0);} + break; + case 211: /* expr ::= expr NOT NULL */ +{spanUnaryPostfix(&yygotominor.yy346,pParse,TK_NOTNULL,&yymsp[-2].minor.yy346,&yymsp[0].minor.yy0);} + break; + case 212: /* expr ::= expr IS expr */ +{ + spanBinaryExpr(&yygotominor.yy346,pParse,TK_IS,&yymsp[-2].minor.yy346,&yymsp[0].minor.yy346); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy346.pExpr, yygotominor.yy346.pExpr, TK_ISNULL); +} + break; + case 213: /* expr ::= expr IS NOT expr */ +{ + spanBinaryExpr(&yygotominor.yy346,pParse,TK_ISNOT,&yymsp[-3].minor.yy346,&yymsp[0].minor.yy346); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy346.pExpr, yygotominor.yy346.pExpr, TK_NOTNULL); +} + break; + case 214: /* expr ::= NOT expr */ + case 215: /* expr ::= BITNOT expr */ yytestcase(yyruleno==215); +{spanUnaryPrefix(&yygotominor.yy346,pParse,yymsp[-1].major,&yymsp[0].minor.yy346,&yymsp[-1].minor.yy0);} + break; + case 216: /* expr ::= MINUS expr */ +{spanUnaryPrefix(&yygotominor.yy346,pParse,TK_UMINUS,&yymsp[0].minor.yy346,&yymsp[-1].minor.yy0);} + break; + case 217: /* expr ::= PLUS expr */ +{spanUnaryPrefix(&yygotominor.yy346,pParse,TK_UPLUS,&yymsp[0].minor.yy346,&yymsp[-1].minor.yy0);} + break; + case 220: /* expr ::= expr between_op expr AND expr */ +{ + ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy346.pExpr); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy346.pExpr); + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy346.pExpr, 0, 0); + if( yygotominor.yy346.pExpr ){ + yygotominor.yy346.pExpr->x.pList = pList; }else{ sqlite3ExprListDelete(pParse->db, pList); } - if( yymsp[-3].minor.yy392 ) yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy342.pExpr, 0, 0); - yygotominor.yy342.zStart = yymsp[-4].minor.yy342.zStart; - yygotominor.yy342.zEnd = yymsp[0].minor.yy342.zEnd; -} - break; - case 226: /* expr ::= expr in_op LP exprlist RP */ -{ - if( yymsp[-1].minor.yy442==0 ){ + if( yymsp[-3].minor.yy328 ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); + yygotominor.yy346.zStart = yymsp[-4].minor.yy346.zStart; + yygotominor.yy346.zEnd = yymsp[0].minor.yy346.zEnd; +} + break; + case 223: /* expr ::= expr in_op LP exprlist RP */ +{ + if( yymsp[-1].minor.yy14==0 ){ /* Expressions of the form ** ** expr1 IN () ** expr1 NOT IN () ** ** simplify to constants 0 (false) and 1 (true), respectively, ** regardless of the value of expr1. */ - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, &sqlite3IntTokens[yymsp[-3].minor.yy392]); - sqlite3ExprDelete(pParse->db, yymsp[-4].minor.yy342.pExpr); - }else{ - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy342.pExpr, 0, 0); - if( yygotominor.yy342.pExpr ){ - yygotominor.yy342.pExpr->x.pList = yymsp[-1].minor.yy442; - sqlite3ExprSetHeight(pParse, yygotominor.yy342.pExpr); - }else{ - sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy442); - } - if( yymsp[-3].minor.yy392 ) yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy342.pExpr, 0, 0); - } - yygotominor.yy342.zStart = yymsp[-4].minor.yy342.zStart; - yygotominor.yy342.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; - } - break; - case 227: /* expr ::= LP select RP */ -{ - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_SELECT, 0, 0, 0); - if( yygotominor.yy342.pExpr ){ - yygotominor.yy342.pExpr->x.pSelect = yymsp[-1].minor.yy159; - ExprSetProperty(yygotominor.yy342.pExpr, EP_xIsSelect); - sqlite3ExprSetHeight(pParse, yygotominor.yy342.pExpr); - }else{ - sqlite3SelectDelete(pParse->db, yymsp[-1].minor.yy159); - } - yygotominor.yy342.zStart = yymsp[-2].minor.yy0.z; - yygotominor.yy342.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; - } - break; - case 228: /* expr ::= expr in_op LP select RP */ -{ - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy342.pExpr, 0, 0); - if( yygotominor.yy342.pExpr ){ - yygotominor.yy342.pExpr->x.pSelect = yymsp[-1].minor.yy159; - ExprSetProperty(yygotominor.yy342.pExpr, EP_xIsSelect); - sqlite3ExprSetHeight(pParse, yygotominor.yy342.pExpr); - }else{ - sqlite3SelectDelete(pParse->db, yymsp[-1].minor.yy159); - } - if( yymsp[-3].minor.yy392 ) yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy342.pExpr, 0, 0); - yygotominor.yy342.zStart = yymsp[-4].minor.yy342.zStart; - yygotominor.yy342.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; - } - break; - case 229: /* expr ::= expr in_op nm dbnm */ + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, &sqlite3IntTokens[yymsp[-3].minor.yy328]); + sqlite3ExprDelete(pParse->db, yymsp[-4].minor.yy346.pExpr); + }else if( yymsp[-1].minor.yy14->nExpr==1 ){ + /* Expressions of the form: + ** + ** expr1 IN (?1) + ** expr1 NOT IN (?2) + ** + ** with exactly one value on the RHS can be simplified to something + ** like this: + ** + ** expr1 == ?1 + ** expr1 <> ?2 + ** + ** But, the RHS of the == or <> is marked with the EP_Generic flag + ** so that it may not contribute to the computation of comparison + ** affinity or the collating sequence to use for comparison. Otherwise, + ** the semantics would be subtly different from IN or NOT IN. + */ + Expr *pRHS = yymsp[-1].minor.yy14->a[0].pExpr; + yymsp[-1].minor.yy14->a[0].pExpr = 0; + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy14); + /* pRHS cannot be NULL because a malloc error would have been detected + ** before now and control would have never reached this point */ + if( ALWAYS(pRHS) ){ + pRHS->flags &= ~EP_Collate; + pRHS->flags |= EP_Generic; + } + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, yymsp[-3].minor.yy328 ? TK_NE : TK_EQ, yymsp[-4].minor.yy346.pExpr, pRHS, 0); + }else{ + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy346.pExpr, 0, 0); + if( yygotominor.yy346.pExpr ){ + yygotominor.yy346.pExpr->x.pList = yymsp[-1].minor.yy14; + sqlite3ExprSetHeightAndFlags(pParse, yygotominor.yy346.pExpr); + }else{ + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy14); + } + if( yymsp[-3].minor.yy328 ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); + } + yygotominor.yy346.zStart = yymsp[-4].minor.yy346.zStart; + yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; + } + break; + case 224: /* expr ::= LP select RP */ +{ + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_SELECT, 0, 0, 0); + if( yygotominor.yy346.pExpr ){ + yygotominor.yy346.pExpr->x.pSelect = yymsp[-1].minor.yy3; + ExprSetProperty(yygotominor.yy346.pExpr, EP_xIsSelect|EP_Subquery); + sqlite3ExprSetHeightAndFlags(pParse, yygotominor.yy346.pExpr); + }else{ + sqlite3SelectDelete(pParse->db, yymsp[-1].minor.yy3); + } + yygotominor.yy346.zStart = yymsp[-2].minor.yy0.z; + yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; + } + break; + case 225: /* expr ::= expr in_op LP select RP */ +{ + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy346.pExpr, 0, 0); + if( yygotominor.yy346.pExpr ){ + yygotominor.yy346.pExpr->x.pSelect = yymsp[-1].minor.yy3; + ExprSetProperty(yygotominor.yy346.pExpr, EP_xIsSelect|EP_Subquery); + sqlite3ExprSetHeightAndFlags(pParse, yygotominor.yy346.pExpr); + }else{ + sqlite3SelectDelete(pParse->db, yymsp[-1].minor.yy3); + } + if( yymsp[-3].minor.yy328 ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); + yygotominor.yy346.zStart = yymsp[-4].minor.yy346.zStart; + yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; + } + break; + case 226: /* expr ::= expr in_op nm dbnm */ { SrcList *pSrc = sqlite3SrcListAppend(pParse->db, 0,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0); - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-3].minor.yy342.pExpr, 0, 0); - if( yygotominor.yy342.pExpr ){ - yygotominor.yy342.pExpr->x.pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0,0); - ExprSetProperty(yygotominor.yy342.pExpr, EP_xIsSelect); - sqlite3ExprSetHeight(pParse, yygotominor.yy342.pExpr); + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-3].minor.yy346.pExpr, 0, 0); + if( yygotominor.yy346.pExpr ){ + yygotominor.yy346.pExpr->x.pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0,0); + ExprSetProperty(yygotominor.yy346.pExpr, EP_xIsSelect|EP_Subquery); + sqlite3ExprSetHeightAndFlags(pParse, yygotominor.yy346.pExpr); }else{ sqlite3SrcListDelete(pParse->db, pSrc); } - if( yymsp[-2].minor.yy392 ) yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy342.pExpr, 0, 0); - yygotominor.yy342.zStart = yymsp[-3].minor.yy342.zStart; - yygotominor.yy342.zEnd = yymsp[0].minor.yy0.z ? &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] : &yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]; - } - break; - case 230: /* expr ::= EXISTS LP select RP */ -{ - Expr *p = yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_EXISTS, 0, 0, 0); + if( yymsp[-2].minor.yy328 ) yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_NOT, yygotominor.yy346.pExpr, 0, 0); + yygotominor.yy346.zStart = yymsp[-3].minor.yy346.zStart; + yygotominor.yy346.zEnd = yymsp[0].minor.yy0.z ? &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] : &yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]; + } + break; + case 227: /* expr ::= EXISTS LP select RP */ +{ + Expr *p = yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_EXISTS, 0, 0, 0); if( p ){ - p->x.pSelect = yymsp[-1].minor.yy159; - ExprSetProperty(p, EP_xIsSelect); - sqlite3ExprSetHeight(pParse, p); - }else{ - sqlite3SelectDelete(pParse->db, yymsp[-1].minor.yy159); - } - yygotominor.yy342.zStart = yymsp[-3].minor.yy0.z; - yygotominor.yy342.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; - } - break; - case 231: /* expr ::= CASE case_operand case_exprlist case_else END */ -{ - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy122, 0, 0); - if( yygotominor.yy342.pExpr ){ - yygotominor.yy342.pExpr->x.pList = yymsp[-1].minor.yy122 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy442,yymsp[-1].minor.yy122) : yymsp[-2].minor.yy442; - sqlite3ExprSetHeight(pParse, yygotominor.yy342.pExpr); - }else{ - sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy442); - sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy122); - } - yygotominor.yy342.zStart = yymsp[-4].minor.yy0.z; - yygotominor.yy342.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; -} - break; - case 232: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ -{ - yygotominor.yy442 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy442, yymsp[-2].minor.yy342.pExpr); - yygotominor.yy442 = sqlite3ExprListAppend(pParse,yygotominor.yy442, yymsp[0].minor.yy342.pExpr); -} - break; - case 233: /* case_exprlist ::= WHEN expr THEN expr */ -{ - yygotominor.yy442 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy342.pExpr); - yygotominor.yy442 = sqlite3ExprListAppend(pParse,yygotominor.yy442, yymsp[0].minor.yy342.pExpr); -} - break; - case 240: /* nexprlist ::= nexprlist COMMA expr */ -{yygotominor.yy442 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy442,yymsp[0].minor.yy342.pExpr);} - break; - case 241: /* nexprlist ::= expr */ -{yygotominor.yy442 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy342.pExpr);} - break; - case 242: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP idxlist RP where_opt */ + p->x.pSelect = yymsp[-1].minor.yy3; + ExprSetProperty(p, EP_xIsSelect|EP_Subquery); + sqlite3ExprSetHeightAndFlags(pParse, p); + }else{ + sqlite3SelectDelete(pParse->db, yymsp[-1].minor.yy3); + } + yygotominor.yy346.zStart = yymsp[-3].minor.yy0.z; + yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; + } + break; + case 228: /* expr ::= CASE case_operand case_exprlist case_else END */ +{ + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy132, 0, 0); + if( yygotominor.yy346.pExpr ){ + yygotominor.yy346.pExpr->x.pList = yymsp[-1].minor.yy132 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy14,yymsp[-1].minor.yy132) : yymsp[-2].minor.yy14; + sqlite3ExprSetHeightAndFlags(pParse, yygotominor.yy346.pExpr); + }else{ + sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy14); + sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy132); + } + yygotominor.yy346.zStart = yymsp[-4].minor.yy0.z; + yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; +} + break; + case 229: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ +{ + yygotominor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy14, yymsp[-2].minor.yy346.pExpr); + yygotominor.yy14 = sqlite3ExprListAppend(pParse,yygotominor.yy14, yymsp[0].minor.yy346.pExpr); +} + break; + case 230: /* case_exprlist ::= WHEN expr THEN expr */ +{ + yygotominor.yy14 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy346.pExpr); + yygotominor.yy14 = sqlite3ExprListAppend(pParse,yygotominor.yy14, yymsp[0].minor.yy346.pExpr); +} + break; + case 237: /* nexprlist ::= nexprlist COMMA expr */ +{yygotominor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy14,yymsp[0].minor.yy346.pExpr);} + break; + case 238: /* nexprlist ::= expr */ +{yygotominor.yy14 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy346.pExpr);} + break; + case 239: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP idxlist RP where_opt */ { sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, - sqlite3SrcListAppend(pParse->db,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy442, yymsp[-10].minor.yy392, - &yymsp[-11].minor.yy0, yymsp[0].minor.yy122, SQLITE_SO_ASC, yymsp[-8].minor.yy392); -} - break; - case 243: /* uniqueflag ::= UNIQUE */ - case 296: /* raisetype ::= ABORT */ yytestcase(yyruleno==296); -{yygotominor.yy392 = OE_Abort;} - break; - case 244: /* uniqueflag ::= */ -{yygotominor.yy392 = OE_None;} - break; - case 247: /* idxlist ::= idxlist COMMA nm collate sortorder */ -{ - Expr *p = sqlite3ExprAddCollateToken(pParse, 0, &yymsp[-1].minor.yy0); - yygotominor.yy442 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy442, p); - sqlite3ExprListSetName(pParse,yygotominor.yy442,&yymsp[-2].minor.yy0,1); - sqlite3ExprListCheckLength(pParse, yygotominor.yy442, "index"); - if( yygotominor.yy442 ) yygotominor.yy442->a[yygotominor.yy442->nExpr-1].sortOrder = (u8)yymsp[0].minor.yy392; -} - break; - case 248: /* idxlist ::= nm collate sortorder */ -{ - Expr *p = sqlite3ExprAddCollateToken(pParse, 0, &yymsp[-1].minor.yy0); - yygotominor.yy442 = sqlite3ExprListAppend(pParse,0, p); - sqlite3ExprListSetName(pParse, yygotominor.yy442, &yymsp[-2].minor.yy0, 1); - sqlite3ExprListCheckLength(pParse, yygotominor.yy442, "index"); - if( yygotominor.yy442 ) yygotominor.yy442->a[yygotominor.yy442->nExpr-1].sortOrder = (u8)yymsp[0].minor.yy392; -} - break; - case 249: /* collate ::= */ + sqlite3SrcListAppend(pParse->db,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy14, yymsp[-10].minor.yy328, + &yymsp[-11].minor.yy0, yymsp[0].minor.yy132, SQLITE_SO_ASC, yymsp[-8].minor.yy328); +} + break; + case 240: /* uniqueflag ::= UNIQUE */ + case 291: /* raisetype ::= ABORT */ yytestcase(yyruleno==291); +{yygotominor.yy328 = OE_Abort;} + break; + case 241: /* uniqueflag ::= */ +{yygotominor.yy328 = OE_None;} + break; + case 244: /* idxlist ::= idxlist COMMA nm collate sortorder */ +{ + Expr *p = sqlite3ExprAddCollateToken(pParse, 0, &yymsp[-1].minor.yy0, 1); + yygotominor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy14, p); + sqlite3ExprListSetName(pParse,yygotominor.yy14,&yymsp[-2].minor.yy0,1); + sqlite3ExprListCheckLength(pParse, yygotominor.yy14, "index"); + if( yygotominor.yy14 ) yygotominor.yy14->a[yygotominor.yy14->nExpr-1].sortOrder = (u8)yymsp[0].minor.yy328; +} + break; + case 245: /* idxlist ::= nm collate sortorder */ +{ + Expr *p = sqlite3ExprAddCollateToken(pParse, 0, &yymsp[-1].minor.yy0, 1); + yygotominor.yy14 = sqlite3ExprListAppend(pParse,0, p); + sqlite3ExprListSetName(pParse, yygotominor.yy14, &yymsp[-2].minor.yy0, 1); + sqlite3ExprListCheckLength(pParse, yygotominor.yy14, "index"); + if( yygotominor.yy14 ) yygotominor.yy14->a[yygotominor.yy14->nExpr-1].sortOrder = (u8)yymsp[0].minor.yy328; +} + break; + case 246: /* collate ::= */ {yygotominor.yy0.z = 0; yygotominor.yy0.n = 0;} break; - case 251: /* cmd ::= DROP INDEX ifexists fullname */ -{sqlite3DropIndex(pParse, yymsp[0].minor.yy347, yymsp[-1].minor.yy392);} + case 248: /* cmd ::= DROP INDEX ifexists fullname */ +{sqlite3DropIndex(pParse, yymsp[0].minor.yy65, yymsp[-1].minor.yy328);} break; - case 252: /* cmd ::= VACUUM */ - case 253: /* cmd ::= VACUUM nm */ yytestcase(yyruleno==253); + case 249: /* cmd ::= VACUUM */ + case 250: /* cmd ::= VACUUM nm */ yytestcase(yyruleno==250); {sqlite3Vacuum(pParse);} break; - case 254: /* cmd ::= PRAGMA nm dbnm */ + case 251: /* cmd ::= PRAGMA nm dbnm */ {sqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);} break; - case 255: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ + case 252: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,0);} break; - case 256: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ + case 253: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,0);} break; - case 257: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ + case 254: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,1);} break; - case 258: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ + case 255: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,1);} break; - case 268: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + case 264: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ { Token all; all.z = yymsp[-3].minor.yy0.z; all.n = (int)(yymsp[0].minor.yy0.z - yymsp[-3].minor.yy0.z) + yymsp[0].minor.yy0.n; - sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy327, &all); + sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy473, &all); } break; - case 269: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + case 265: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ { - sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy392, yymsp[-4].minor.yy410.a, yymsp[-4].minor.yy410.b, yymsp[-2].minor.yy347, yymsp[0].minor.yy122, yymsp[-10].minor.yy392, yymsp[-8].minor.yy392); + sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy328, yymsp[-4].minor.yy378.a, yymsp[-4].minor.yy378.b, yymsp[-2].minor.yy65, yymsp[0].minor.yy132, yymsp[-10].minor.yy328, yymsp[-8].minor.yy328); yygotominor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); } break; - case 270: /* trigger_time ::= BEFORE */ - case 273: /* trigger_time ::= */ yytestcase(yyruleno==273); -{ yygotominor.yy392 = TK_BEFORE; } - break; - case 271: /* trigger_time ::= AFTER */ -{ yygotominor.yy392 = TK_AFTER; } - break; - case 272: /* trigger_time ::= INSTEAD OF */ -{ yygotominor.yy392 = TK_INSTEAD;} - break; - case 274: /* trigger_event ::= DELETE|INSERT */ - case 275: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==275); -{yygotominor.yy410.a = yymsp[0].major; yygotominor.yy410.b = 0;} - break; - case 276: /* trigger_event ::= UPDATE OF inscollist */ -{yygotominor.yy410.a = TK_UPDATE; yygotominor.yy410.b = yymsp[0].minor.yy180;} - break; - case 279: /* when_clause ::= */ - case 301: /* key_opt ::= */ yytestcase(yyruleno==301); -{ yygotominor.yy122 = 0; } - break; - case 280: /* when_clause ::= WHEN expr */ - case 302: /* key_opt ::= KEY expr */ yytestcase(yyruleno==302); -{ yygotominor.yy122 = yymsp[0].minor.yy342.pExpr; } - break; - case 281: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ -{ - assert( yymsp[-2].minor.yy327!=0 ); - yymsp[-2].minor.yy327->pLast->pNext = yymsp[-1].minor.yy327; - yymsp[-2].minor.yy327->pLast = yymsp[-1].minor.yy327; - yygotominor.yy327 = yymsp[-2].minor.yy327; -} - break; - case 282: /* trigger_cmd_list ::= trigger_cmd SEMI */ -{ - assert( yymsp[-1].minor.yy327!=0 ); - yymsp[-1].minor.yy327->pLast = yymsp[-1].minor.yy327; - yygotominor.yy327 = yymsp[-1].minor.yy327; -} - break; - case 284: /* trnm ::= nm DOT nm */ + case 266: /* trigger_time ::= BEFORE */ + case 269: /* trigger_time ::= */ yytestcase(yyruleno==269); +{ yygotominor.yy328 = TK_BEFORE; } + break; + case 267: /* trigger_time ::= AFTER */ +{ yygotominor.yy328 = TK_AFTER; } + break; + case 268: /* trigger_time ::= INSTEAD OF */ +{ yygotominor.yy328 = TK_INSTEAD;} + break; + case 270: /* trigger_event ::= DELETE|INSERT */ + case 271: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==271); +{yygotominor.yy378.a = yymsp[0].major; yygotominor.yy378.b = 0;} + break; + case 272: /* trigger_event ::= UPDATE OF idlist */ +{yygotominor.yy378.a = TK_UPDATE; yygotominor.yy378.b = yymsp[0].minor.yy408;} + break; + case 275: /* when_clause ::= */ + case 296: /* key_opt ::= */ yytestcase(yyruleno==296); +{ yygotominor.yy132 = 0; } + break; + case 276: /* when_clause ::= WHEN expr */ + case 297: /* key_opt ::= KEY expr */ yytestcase(yyruleno==297); +{ yygotominor.yy132 = yymsp[0].minor.yy346.pExpr; } + break; + case 277: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ +{ + assert( yymsp[-2].minor.yy473!=0 ); + yymsp[-2].minor.yy473->pLast->pNext = yymsp[-1].minor.yy473; + yymsp[-2].minor.yy473->pLast = yymsp[-1].minor.yy473; + yygotominor.yy473 = yymsp[-2].minor.yy473; +} + break; + case 278: /* trigger_cmd_list ::= trigger_cmd SEMI */ +{ + assert( yymsp[-1].minor.yy473!=0 ); + yymsp[-1].minor.yy473->pLast = yymsp[-1].minor.yy473; + yygotominor.yy473 = yymsp[-1].minor.yy473; +} + break; + case 280: /* trnm ::= nm DOT nm */ { yygotominor.yy0 = yymsp[0].minor.yy0; sqlite3ErrorMsg(pParse, "qualified table names are not allowed on INSERT, UPDATE, and DELETE " "statements within triggers"); } break; - case 286: /* tridxby ::= INDEXED BY nm */ + case 282: /* tridxby ::= INDEXED BY nm */ { sqlite3ErrorMsg(pParse, "the INDEXED BY clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 287: /* tridxby ::= NOT INDEXED */ + case 283: /* tridxby ::= NOT INDEXED */ { sqlite3ErrorMsg(pParse, "the NOT INDEXED clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 288: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt */ -{ yygotominor.yy327 = sqlite3TriggerUpdateStep(pParse->db, &yymsp[-4].minor.yy0, yymsp[-1].minor.yy442, yymsp[0].minor.yy122, yymsp[-5].minor.yy258); } - break; - case 289: /* trigger_cmd ::= insert_cmd INTO trnm inscollist_opt valuelist */ -{yygotominor.yy327 = sqlite3TriggerInsertStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy180, yymsp[0].minor.yy487.pList, yymsp[0].minor.yy487.pSelect, yymsp[-4].minor.yy258);} - break; - case 290: /* trigger_cmd ::= insert_cmd INTO trnm inscollist_opt select */ -{yygotominor.yy327 = sqlite3TriggerInsertStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy180, 0, yymsp[0].minor.yy159, yymsp[-4].minor.yy258);} - break; - case 291: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt */ -{yygotominor.yy327 = sqlite3TriggerDeleteStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[0].minor.yy122);} - break; - case 292: /* trigger_cmd ::= select */ -{yygotominor.yy327 = sqlite3TriggerSelectStep(pParse->db, yymsp[0].minor.yy159); } - break; - case 293: /* expr ::= RAISE LP IGNORE RP */ -{ - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, 0); - if( yygotominor.yy342.pExpr ){ - yygotominor.yy342.pExpr->affinity = OE_Ignore; - } - yygotominor.yy342.zStart = yymsp[-3].minor.yy0.z; - yygotominor.yy342.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; -} - break; - case 294: /* expr ::= RAISE LP raisetype COMMA nm RP */ -{ - yygotominor.yy342.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, &yymsp[-1].minor.yy0); - if( yygotominor.yy342.pExpr ) { - yygotominor.yy342.pExpr->affinity = (char)yymsp[-3].minor.yy392; - } - yygotominor.yy342.zStart = yymsp[-5].minor.yy0.z; - yygotominor.yy342.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; -} - break; - case 295: /* raisetype ::= ROLLBACK */ -{yygotominor.yy392 = OE_Rollback;} - break; - case 297: /* raisetype ::= FAIL */ -{yygotominor.yy392 = OE_Fail;} - break; - case 298: /* cmd ::= DROP TRIGGER ifexists fullname */ -{ - sqlite3DropTrigger(pParse,yymsp[0].minor.yy347,yymsp[-1].minor.yy392); -} - break; - case 299: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ -{ - sqlite3Attach(pParse, yymsp[-3].minor.yy342.pExpr, yymsp[-1].minor.yy342.pExpr, yymsp[0].minor.yy122); -} - break; - case 300: /* cmd ::= DETACH database_kw_opt expr */ -{ - sqlite3Detach(pParse, yymsp[0].minor.yy342.pExpr); -} - break; - case 305: /* cmd ::= REINDEX */ + case 284: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt */ +{ yygotominor.yy473 = sqlite3TriggerUpdateStep(pParse->db, &yymsp[-4].minor.yy0, yymsp[-1].minor.yy14, yymsp[0].minor.yy132, yymsp[-5].minor.yy186); } + break; + case 285: /* trigger_cmd ::= insert_cmd INTO trnm inscollist_opt select */ +{yygotominor.yy473 = sqlite3TriggerInsertStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy408, yymsp[0].minor.yy3, yymsp[-4].minor.yy186);} + break; + case 286: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt */ +{yygotominor.yy473 = sqlite3TriggerDeleteStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[0].minor.yy132);} + break; + case 287: /* trigger_cmd ::= select */ +{yygotominor.yy473 = sqlite3TriggerSelectStep(pParse->db, yymsp[0].minor.yy3); } + break; + case 288: /* expr ::= RAISE LP IGNORE RP */ +{ + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, 0); + if( yygotominor.yy346.pExpr ){ + yygotominor.yy346.pExpr->affinity = OE_Ignore; + } + yygotominor.yy346.zStart = yymsp[-3].minor.yy0.z; + yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; +} + break; + case 289: /* expr ::= RAISE LP raisetype COMMA nm RP */ +{ + yygotominor.yy346.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, &yymsp[-1].minor.yy0); + if( yygotominor.yy346.pExpr ) { + yygotominor.yy346.pExpr->affinity = (char)yymsp[-3].minor.yy328; + } + yygotominor.yy346.zStart = yymsp[-5].minor.yy0.z; + yygotominor.yy346.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; +} + break; + case 290: /* raisetype ::= ROLLBACK */ +{yygotominor.yy328 = OE_Rollback;} + break; + case 292: /* raisetype ::= FAIL */ +{yygotominor.yy328 = OE_Fail;} + break; + case 293: /* cmd ::= DROP TRIGGER ifexists fullname */ +{ + sqlite3DropTrigger(pParse,yymsp[0].minor.yy65,yymsp[-1].minor.yy328); +} + break; + case 294: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ +{ + sqlite3Attach(pParse, yymsp[-3].minor.yy346.pExpr, yymsp[-1].minor.yy346.pExpr, yymsp[0].minor.yy132); +} + break; + case 295: /* cmd ::= DETACH database_kw_opt expr */ +{ + sqlite3Detach(pParse, yymsp[0].minor.yy346.pExpr); +} + break; + case 300: /* cmd ::= REINDEX */ {sqlite3Reindex(pParse, 0, 0);} break; - case 306: /* cmd ::= REINDEX nm dbnm */ + case 301: /* cmd ::= REINDEX nm dbnm */ {sqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 307: /* cmd ::= ANALYZE */ + case 302: /* cmd ::= ANALYZE */ {sqlite3Analyze(pParse, 0, 0);} break; - case 308: /* cmd ::= ANALYZE nm dbnm */ + case 303: /* cmd ::= ANALYZE nm dbnm */ {sqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 309: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ + case 304: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ { - sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy347,&yymsp[0].minor.yy0); + sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy65,&yymsp[0].minor.yy0); } break; - case 310: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt column */ + case 305: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt column */ { sqlite3AlterFinishAddColumn(pParse, &yymsp[0].minor.yy0); } break; - case 311: /* add_column_fullname ::= fullname */ + case 306: /* add_column_fullname ::= fullname */ { pParse->db->lookaside.bEnabled = 0; - sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy347); + sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy65); } break; - case 314: /* cmd ::= create_vtab */ + case 309: /* cmd ::= create_vtab */ {sqlite3VtabFinishParse(pParse,0);} break; - case 315: /* cmd ::= create_vtab LP vtabarglist RP */ + case 310: /* cmd ::= create_vtab LP vtabarglist RP */ {sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);} break; - case 316: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + case 311: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ { - sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy392); + sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy328); } break; - case 319: /* vtabarg ::= */ + case 314: /* vtabarg ::= */ {sqlite3VtabArgInit(pParse);} break; - case 321: /* vtabargtoken ::= ANY */ - case 322: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==322); - case 323: /* lp ::= LP */ yytestcase(yyruleno==323); + case 316: /* vtabargtoken ::= ANY */ + case 317: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==317); + case 318: /* lp ::= LP */ yytestcase(yyruleno==318); {sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);} + break; + case 322: /* with ::= */ +{yygotominor.yy59 = 0;} + break; + case 323: /* with ::= WITH wqlist */ + case 324: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==324); +{ yygotominor.yy59 = yymsp[0].minor.yy59; } + break; + case 325: /* wqlist ::= nm idxlist_opt AS LP select RP */ +{ + yygotominor.yy59 = sqlite3WithAdd(pParse, 0, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy14, yymsp[-1].minor.yy3); +} + break; + case 326: /* wqlist ::= wqlist COMMA nm idxlist_opt AS LP select RP */ +{ + yygotominor.yy59 = sqlite3WithAdd(pParse, yymsp[-7].minor.yy59, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy14, yymsp[-1].minor.yy3); +} break; default: /* (0) input ::= cmdlist */ yytestcase(yyruleno==0); /* (1) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==1); /* (2) cmdlist ::= ecmd */ yytestcase(yyruleno==2); @@ -115603,34 +125994,34 @@ /* (11) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==11); /* (12) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==12); /* (20) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==20); /* (21) savepoint_opt ::= */ yytestcase(yyruleno==21); /* (25) cmd ::= create_table create_table_args */ yytestcase(yyruleno==25); - /* (34) columnlist ::= columnlist COMMA column */ yytestcase(yyruleno==34); - /* (35) columnlist ::= column */ yytestcase(yyruleno==35); - /* (44) type ::= */ yytestcase(yyruleno==44); - /* (51) signed ::= plus_num */ yytestcase(yyruleno==51); - /* (52) signed ::= minus_num */ yytestcase(yyruleno==52); - /* (53) carglist ::= carglist ccons */ yytestcase(yyruleno==53); - /* (54) carglist ::= */ yytestcase(yyruleno==54); - /* (61) ccons ::= NULL onconf */ yytestcase(yyruleno==61); - /* (89) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==89); - /* (90) conslist ::= tcons */ yytestcase(yyruleno==90); - /* (92) tconscomma ::= */ yytestcase(yyruleno==92); - /* (277) foreach_clause ::= */ yytestcase(yyruleno==277); - /* (278) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==278); - /* (285) tridxby ::= */ yytestcase(yyruleno==285); - /* (303) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==303); - /* (304) database_kw_opt ::= */ yytestcase(yyruleno==304); - /* (312) kwcolumn_opt ::= */ yytestcase(yyruleno==312); - /* (313) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==313); - /* (317) vtabarglist ::= vtabarg */ yytestcase(yyruleno==317); - /* (318) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==318); - /* (320) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==320); - /* (324) anylist ::= */ yytestcase(yyruleno==324); - /* (325) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==325); - /* (326) anylist ::= anylist ANY */ yytestcase(yyruleno==326); + /* (36) columnlist ::= columnlist COMMA column */ yytestcase(yyruleno==36); + /* (37) columnlist ::= column */ yytestcase(yyruleno==37); + /* (43) type ::= */ yytestcase(yyruleno==43); + /* (50) signed ::= plus_num */ yytestcase(yyruleno==50); + /* (51) signed ::= minus_num */ yytestcase(yyruleno==51); + /* (52) carglist ::= carglist ccons */ yytestcase(yyruleno==52); + /* (53) carglist ::= */ yytestcase(yyruleno==53); + /* (60) ccons ::= NULL onconf */ yytestcase(yyruleno==60); + /* (88) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==88); + /* (89) conslist ::= tcons */ yytestcase(yyruleno==89); + /* (91) tconscomma ::= */ yytestcase(yyruleno==91); + /* (273) foreach_clause ::= */ yytestcase(yyruleno==273); + /* (274) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==274); + /* (281) tridxby ::= */ yytestcase(yyruleno==281); + /* (298) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==298); + /* (299) database_kw_opt ::= */ yytestcase(yyruleno==299); + /* (307) kwcolumn_opt ::= */ yytestcase(yyruleno==307); + /* (308) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==308); + /* (312) vtabarglist ::= vtabarg */ yytestcase(yyruleno==312); + /* (313) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==313); + /* (315) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==315); + /* (319) anylist ::= */ yytestcase(yyruleno==319); + /* (320) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==320); + /* (321) anylist ::= anylist ANY */ yytestcase(yyruleno==321); break; }; assert( yyruleno>=0 && yyruleno<sizeof(yyRuleInfo)/sizeof(yyRuleInfo[0]) ); yygoto = yyRuleInfo[yyruleno].lhs; yysize = yyRuleInfo[yyruleno].nrhs; @@ -115965,24 +126356,24 @@ ** might be implemented more directly using a hand-written hash table. ** But by using this automatically generated code, the size of the code ** is substantially reduced. This is important for embedded applications ** on platforms with limited memory. */ -/* Hash score: 175 */ +/* Hash score: 182 */ static int keywordCode(const char *z, int n){ - /* zText[] encodes 811 bytes of keywords in 541 bytes */ + /* zText[] encodes 834 bytes of keywords in 554 bytes */ /* REINDEXEDESCAPEACHECKEYBEFOREIGNOREGEXPLAINSTEADDATABASELECT */ /* ABLEFTHENDEFERRABLELSEXCEPTRANSACTIONATURALTERAISEXCLUSIVE */ /* XISTSAVEPOINTERSECTRIGGEREFERENCESCONSTRAINTOFFSETEMPORARY */ - /* UNIQUERYATTACHAVINGROUPDATEBEGINNERELEASEBETWEENOTNULLIKE */ - /* CASCADELETECASECOLLATECREATECURRENT_DATEDETACHIMMEDIATEJOIN */ - /* SERTMATCHPLANALYZEPRAGMABORTVALUESVIRTUALIMITWHENWHERENAME */ - /* AFTEREPLACEANDEFAULTAUTOINCREMENTCASTCOLUMNCOMMITCONFLICTCROSS */ - /* CURRENT_TIMESTAMPRIMARYDEFERREDISTINCTDROPFAILFROMFULLGLOBYIF */ - /* ISNULLORDERESTRICTOUTERIGHTROLLBACKROWUNIONUSINGVACUUMVIEW */ - /* INITIALLY */ - static const char zText[540] = { + /* UNIQUERYWITHOUTERELEASEATTACHAVINGROUPDATEBEGINNERECURSIVE */ + /* BETWEENOTNULLIKECASCADELETECASECOLLATECREATECURRENT_DATEDETACH */ + /* IMMEDIATEJOINSERTMATCHPLANALYZEPRAGMABORTVALUESVIRTUALIMITWHEN */ + /* WHERENAMEAFTEREPLACEANDEFAULTAUTOINCREMENTCASTCOLUMNCOMMIT */ + /* CONFLICTCROSSCURRENT_TIMESTAMPRIMARYDEFERREDISTINCTDROPFAIL */ + /* FROMFULLGLOBYIFISNULLORDERESTRICTRIGHTROLLBACKROWUNIONUSING */ + /* VACUUMVIEWINITIALLY */ + static const char zText[553] = { 'R','E','I','N','D','E','X','E','D','E','S','C','A','P','E','A','C','H', 'E','C','K','E','Y','B','E','F','O','R','E','I','G','N','O','R','E','G', 'E','X','P','L','A','I','N','S','T','E','A','D','D','A','T','A','B','A', 'S','E','L','E','C','T','A','B','L','E','F','T','H','E','N','D','E','F', 'E','R','R','A','B','L','E','L','S','E','X','C','E','P','T','R','A','N', @@ -115989,105 +126380,106 @@ 'S','A','C','T','I','O','N','A','T','U','R','A','L','T','E','R','A','I', 'S','E','X','C','L','U','S','I','V','E','X','I','S','T','S','A','V','E', 'P','O','I','N','T','E','R','S','E','C','T','R','I','G','G','E','R','E', 'F','E','R','E','N','C','E','S','C','O','N','S','T','R','A','I','N','T', 'O','F','F','S','E','T','E','M','P','O','R','A','R','Y','U','N','I','Q', - 'U','E','R','Y','A','T','T','A','C','H','A','V','I','N','G','R','O','U', - 'P','D','A','T','E','B','E','G','I','N','N','E','R','E','L','E','A','S', - 'E','B','E','T','W','E','E','N','O','T','N','U','L','L','I','K','E','C', - 'A','S','C','A','D','E','L','E','T','E','C','A','S','E','C','O','L','L', - 'A','T','E','C','R','E','A','T','E','C','U','R','R','E','N','T','_','D', - 'A','T','E','D','E','T','A','C','H','I','M','M','E','D','I','A','T','E', - 'J','O','I','N','S','E','R','T','M','A','T','C','H','P','L','A','N','A', - 'L','Y','Z','E','P','R','A','G','M','A','B','O','R','T','V','A','L','U', - 'E','S','V','I','R','T','U','A','L','I','M','I','T','W','H','E','N','W', - 'H','E','R','E','N','A','M','E','A','F','T','E','R','E','P','L','A','C', - 'E','A','N','D','E','F','A','U','L','T','A','U','T','O','I','N','C','R', - 'E','M','E','N','T','C','A','S','T','C','O','L','U','M','N','C','O','M', - 'M','I','T','C','O','N','F','L','I','C','T','C','R','O','S','S','C','U', - 'R','R','E','N','T','_','T','I','M','E','S','T','A','M','P','R','I','M', - 'A','R','Y','D','E','F','E','R','R','E','D','I','S','T','I','N','C','T', - 'D','R','O','P','F','A','I','L','F','R','O','M','F','U','L','L','G','L', - 'O','B','Y','I','F','I','S','N','U','L','L','O','R','D','E','R','E','S', - 'T','R','I','C','T','O','U','T','E','R','I','G','H','T','R','O','L','L', - 'B','A','C','K','R','O','W','U','N','I','O','N','U','S','I','N','G','V', - 'A','C','U','U','M','V','I','E','W','I','N','I','T','I','A','L','L','Y', + 'U','E','R','Y','W','I','T','H','O','U','T','E','R','E','L','E','A','S', + 'E','A','T','T','A','C','H','A','V','I','N','G','R','O','U','P','D','A', + 'T','E','B','E','G','I','N','N','E','R','E','C','U','R','S','I','V','E', + 'B','E','T','W','E','E','N','O','T','N','U','L','L','I','K','E','C','A', + 'S','C','A','D','E','L','E','T','E','C','A','S','E','C','O','L','L','A', + 'T','E','C','R','E','A','T','E','C','U','R','R','E','N','T','_','D','A', + 'T','E','D','E','T','A','C','H','I','M','M','E','D','I','A','T','E','J', + 'O','I','N','S','E','R','T','M','A','T','C','H','P','L','A','N','A','L', + 'Y','Z','E','P','R','A','G','M','A','B','O','R','T','V','A','L','U','E', + 'S','V','I','R','T','U','A','L','I','M','I','T','W','H','E','N','W','H', + 'E','R','E','N','A','M','E','A','F','T','E','R','E','P','L','A','C','E', + 'A','N','D','E','F','A','U','L','T','A','U','T','O','I','N','C','R','E', + 'M','E','N','T','C','A','S','T','C','O','L','U','M','N','C','O','M','M', + 'I','T','C','O','N','F','L','I','C','T','C','R','O','S','S','C','U','R', + 'R','E','N','T','_','T','I','M','E','S','T','A','M','P','R','I','M','A', + 'R','Y','D','E','F','E','R','R','E','D','I','S','T','I','N','C','T','D', + 'R','O','P','F','A','I','L','F','R','O','M','F','U','L','L','G','L','O', + 'B','Y','I','F','I','S','N','U','L','L','O','R','D','E','R','E','S','T', + 'R','I','C','T','R','I','G','H','T','R','O','L','L','B','A','C','K','R', + 'O','W','U','N','I','O','N','U','S','I','N','G','V','A','C','U','U','M', + 'V','I','E','W','I','N','I','T','I','A','L','L','Y', }; static const unsigned char aHash[127] = { - 72, 101, 114, 70, 0, 45, 0, 0, 78, 0, 73, 0, 0, - 42, 12, 74, 15, 0, 113, 81, 50, 108, 0, 19, 0, 0, - 118, 0, 116, 111, 0, 22, 89, 0, 9, 0, 0, 66, 67, - 0, 65, 6, 0, 48, 86, 98, 0, 115, 97, 0, 0, 44, - 0, 99, 24, 0, 17, 0, 119, 49, 23, 0, 5, 106, 25, - 92, 0, 0, 121, 102, 56, 120, 53, 28, 51, 0, 87, 0, - 96, 26, 0, 95, 0, 0, 0, 91, 88, 93, 84, 105, 14, - 39, 104, 0, 77, 0, 18, 85, 107, 32, 0, 117, 76, 109, - 58, 46, 80, 0, 0, 90, 40, 0, 112, 0, 36, 0, 0, - 29, 0, 82, 59, 60, 0, 20, 57, 0, 52, + 76, 105, 117, 74, 0, 45, 0, 0, 82, 0, 77, 0, 0, + 42, 12, 78, 15, 0, 116, 85, 54, 112, 0, 19, 0, 0, + 121, 0, 119, 115, 0, 22, 93, 0, 9, 0, 0, 70, 71, + 0, 69, 6, 0, 48, 90, 102, 0, 118, 101, 0, 0, 44, + 0, 103, 24, 0, 17, 0, 122, 53, 23, 0, 5, 110, 25, + 96, 0, 0, 124, 106, 60, 123, 57, 28, 55, 0, 91, 0, + 100, 26, 0, 99, 0, 0, 0, 95, 92, 97, 88, 109, 14, + 39, 108, 0, 81, 0, 18, 89, 111, 32, 0, 120, 80, 113, + 62, 46, 84, 0, 0, 94, 40, 59, 114, 0, 36, 0, 0, + 29, 0, 86, 63, 64, 0, 20, 61, 0, 56, }; - static const unsigned char aNext[121] = { + static const unsigned char aNext[124] = { 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 33, 0, 21, 0, 0, 0, 43, 3, 47, - 0, 0, 0, 0, 30, 0, 54, 0, 38, 0, 0, 0, 1, - 62, 0, 0, 63, 0, 41, 0, 0, 0, 0, 0, 0, 0, - 61, 0, 0, 0, 0, 31, 55, 16, 34, 10, 0, 0, 0, - 0, 0, 0, 0, 11, 68, 75, 0, 8, 0, 100, 94, 0, - 103, 0, 83, 0, 71, 0, 0, 110, 27, 37, 69, 79, 0, - 35, 64, 0, 0, + 0, 0, 0, 0, 33, 0, 21, 0, 0, 0, 0, 0, 50, + 0, 43, 3, 47, 0, 0, 0, 0, 30, 0, 58, 0, 38, + 0, 0, 0, 1, 66, 0, 0, 67, 0, 41, 0, 0, 0, + 0, 0, 0, 49, 65, 0, 0, 0, 0, 31, 52, 16, 34, + 10, 0, 0, 0, 0, 0, 0, 0, 11, 72, 79, 0, 8, + 0, 104, 98, 0, 107, 0, 87, 0, 75, 51, 0, 27, 37, + 73, 83, 0, 35, 68, 0, 0, }; - static const unsigned char aLen[121] = { + static const unsigned char aLen[124] = { 7, 7, 5, 4, 6, 4, 5, 3, 6, 7, 3, 6, 6, 7, 7, 3, 8, 2, 6, 5, 4, 4, 3, 10, 4, 6, 11, 6, 2, 7, 5, 5, 9, 6, 9, 9, 7, 10, 10, - 4, 6, 2, 3, 9, 4, 2, 6, 5, 6, 6, 5, 6, - 5, 5, 7, 7, 7, 3, 2, 4, 4, 7, 3, 6, 4, - 7, 6, 12, 6, 9, 4, 6, 5, 4, 7, 6, 5, 6, - 7, 5, 4, 5, 6, 5, 7, 3, 7, 13, 2, 2, 4, - 6, 6, 8, 5, 17, 12, 7, 8, 8, 2, 4, 4, 4, - 4, 4, 2, 2, 6, 5, 8, 5, 5, 8, 3, 5, 5, - 6, 4, 9, 3, + 4, 6, 2, 3, 9, 4, 2, 6, 5, 7, 4, 5, 7, + 6, 6, 5, 6, 5, 5, 9, 7, 7, 3, 2, 4, 4, + 7, 3, 6, 4, 7, 6, 12, 6, 9, 4, 6, 5, 4, + 7, 6, 5, 6, 7, 5, 4, 5, 6, 5, 7, 3, 7, + 13, 2, 2, 4, 6, 6, 8, 5, 17, 12, 7, 8, 8, + 2, 4, 4, 4, 4, 4, 2, 2, 6, 5, 8, 5, 8, + 3, 5, 5, 6, 4, 9, 3, }; - static const unsigned short int aOffset[121] = { + static const unsigned short int aOffset[124] = { 0, 2, 2, 8, 9, 14, 16, 20, 23, 25, 25, 29, 33, 36, 41, 46, 48, 53, 54, 59, 62, 65, 67, 69, 78, 81, 86, 91, 95, 96, 101, 105, 109, 117, 122, 128, 136, 142, 152, - 159, 162, 162, 165, 167, 167, 171, 176, 179, 184, 189, 194, 197, - 203, 206, 210, 217, 223, 223, 223, 226, 229, 233, 234, 238, 244, - 248, 255, 261, 273, 279, 288, 290, 296, 301, 303, 310, 315, 320, - 326, 332, 337, 341, 344, 350, 354, 361, 363, 370, 372, 374, 383, - 387, 393, 399, 407, 412, 412, 428, 435, 442, 443, 450, 454, 458, - 462, 466, 469, 471, 473, 479, 483, 491, 495, 500, 508, 511, 516, - 521, 527, 531, 536, + 159, 162, 162, 165, 167, 167, 171, 176, 179, 184, 184, 188, 192, + 199, 204, 209, 212, 218, 221, 225, 234, 240, 240, 240, 243, 246, + 250, 251, 255, 261, 265, 272, 278, 290, 296, 305, 307, 313, 318, + 320, 327, 332, 337, 343, 349, 354, 358, 361, 367, 371, 378, 380, + 387, 389, 391, 400, 404, 410, 416, 424, 429, 429, 445, 452, 459, + 460, 467, 471, 475, 479, 483, 486, 488, 490, 496, 500, 508, 513, + 521, 524, 529, 534, 540, 544, 549, }; - static const unsigned char aCode[121] = { + static const unsigned char aCode[124] = { TK_REINDEX, TK_INDEXED, TK_INDEX, TK_DESC, TK_ESCAPE, TK_EACH, TK_CHECK, TK_KEY, TK_BEFORE, TK_FOREIGN, TK_FOR, TK_IGNORE, TK_LIKE_KW, TK_EXPLAIN, TK_INSTEAD, TK_ADD, TK_DATABASE, TK_AS, TK_SELECT, TK_TABLE, TK_JOIN_KW, TK_THEN, TK_END, TK_DEFERRABLE, TK_ELSE, TK_EXCEPT, TK_TRANSACTION,TK_ACTION, TK_ON, TK_JOIN_KW, TK_ALTER, TK_RAISE, TK_EXCLUSIVE, TK_EXISTS, TK_SAVEPOINT, TK_INTERSECT, TK_TRIGGER, TK_REFERENCES, TK_CONSTRAINT, TK_INTO, TK_OFFSET, TK_OF, TK_SET, TK_TEMP, TK_TEMP, - TK_OR, TK_UNIQUE, TK_QUERY, TK_ATTACH, TK_HAVING, - TK_GROUP, TK_UPDATE, TK_BEGIN, TK_JOIN_KW, TK_RELEASE, - TK_BETWEEN, TK_NOTNULL, TK_NOT, TK_NO, TK_NULL, - TK_LIKE_KW, TK_CASCADE, TK_ASC, TK_DELETE, TK_CASE, - TK_COLLATE, TK_CREATE, TK_CTIME_KW, TK_DETACH, TK_IMMEDIATE, - TK_JOIN, TK_INSERT, TK_MATCH, TK_PLAN, TK_ANALYZE, - TK_PRAGMA, TK_ABORT, TK_VALUES, TK_VIRTUAL, TK_LIMIT, - TK_WHEN, TK_WHERE, TK_RENAME, TK_AFTER, TK_REPLACE, - TK_AND, TK_DEFAULT, TK_AUTOINCR, TK_TO, TK_IN, - TK_CAST, TK_COLUMNKW, TK_COMMIT, TK_CONFLICT, TK_JOIN_KW, - TK_CTIME_KW, TK_CTIME_KW, TK_PRIMARY, TK_DEFERRED, TK_DISTINCT, - TK_IS, TK_DROP, TK_FAIL, TK_FROM, TK_JOIN_KW, - TK_LIKE_KW, TK_BY, TK_IF, TK_ISNULL, TK_ORDER, - TK_RESTRICT, TK_JOIN_KW, TK_JOIN_KW, TK_ROLLBACK, TK_ROW, - TK_UNION, TK_USING, TK_VACUUM, TK_VIEW, TK_INITIALLY, - TK_ALL, + TK_OR, TK_UNIQUE, TK_QUERY, TK_WITHOUT, TK_WITH, + TK_JOIN_KW, TK_RELEASE, TK_ATTACH, TK_HAVING, TK_GROUP, + TK_UPDATE, TK_BEGIN, TK_JOIN_KW, TK_RECURSIVE, TK_BETWEEN, + TK_NOTNULL, TK_NOT, TK_NO, TK_NULL, TK_LIKE_KW, + TK_CASCADE, TK_ASC, TK_DELETE, TK_CASE, TK_COLLATE, + TK_CREATE, TK_CTIME_KW, TK_DETACH, TK_IMMEDIATE, TK_JOIN, + TK_INSERT, TK_MATCH, TK_PLAN, TK_ANALYZE, TK_PRAGMA, + TK_ABORT, TK_VALUES, TK_VIRTUAL, TK_LIMIT, TK_WHEN, + TK_WHERE, TK_RENAME, TK_AFTER, TK_REPLACE, TK_AND, + TK_DEFAULT, TK_AUTOINCR, TK_TO, TK_IN, TK_CAST, + TK_COLUMNKW, TK_COMMIT, TK_CONFLICT, TK_JOIN_KW, TK_CTIME_KW, + TK_CTIME_KW, TK_PRIMARY, TK_DEFERRED, TK_DISTINCT, TK_IS, + TK_DROP, TK_FAIL, TK_FROM, TK_JOIN_KW, TK_LIKE_KW, + TK_BY, TK_IF, TK_ISNULL, TK_ORDER, TK_RESTRICT, + TK_JOIN_KW, TK_ROLLBACK, TK_ROW, TK_UNION, TK_USING, + TK_VACUUM, TK_VIEW, TK_INITIALLY, TK_ALL, }; int h, i; if( n<2 ) return TK_ID; h = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ @@ -116140,92 +126532,95 @@ testcase( i==43 ); /* TEMPORARY */ testcase( i==44 ); /* TEMP */ testcase( i==45 ); /* OR */ testcase( i==46 ); /* UNIQUE */ testcase( i==47 ); /* QUERY */ - testcase( i==48 ); /* ATTACH */ - testcase( i==49 ); /* HAVING */ - testcase( i==50 ); /* GROUP */ - testcase( i==51 ); /* UPDATE */ - testcase( i==52 ); /* BEGIN */ - testcase( i==53 ); /* INNER */ - testcase( i==54 ); /* RELEASE */ - testcase( i==55 ); /* BETWEEN */ - testcase( i==56 ); /* NOTNULL */ - testcase( i==57 ); /* NOT */ - testcase( i==58 ); /* NO */ - testcase( i==59 ); /* NULL */ - testcase( i==60 ); /* LIKE */ - testcase( i==61 ); /* CASCADE */ - testcase( i==62 ); /* ASC */ - testcase( i==63 ); /* DELETE */ - testcase( i==64 ); /* CASE */ - testcase( i==65 ); /* COLLATE */ - testcase( i==66 ); /* CREATE */ - testcase( i==67 ); /* CURRENT_DATE */ - testcase( i==68 ); /* DETACH */ - testcase( i==69 ); /* IMMEDIATE */ - testcase( i==70 ); /* JOIN */ - testcase( i==71 ); /* INSERT */ - testcase( i==72 ); /* MATCH */ - testcase( i==73 ); /* PLAN */ - testcase( i==74 ); /* ANALYZE */ - testcase( i==75 ); /* PRAGMA */ - testcase( i==76 ); /* ABORT */ - testcase( i==77 ); /* VALUES */ - testcase( i==78 ); /* VIRTUAL */ - testcase( i==79 ); /* LIMIT */ - testcase( i==80 ); /* WHEN */ - testcase( i==81 ); /* WHERE */ - testcase( i==82 ); /* RENAME */ - testcase( i==83 ); /* AFTER */ - testcase( i==84 ); /* REPLACE */ - testcase( i==85 ); /* AND */ - testcase( i==86 ); /* DEFAULT */ - testcase( i==87 ); /* AUTOINCREMENT */ - testcase( i==88 ); /* TO */ - testcase( i==89 ); /* IN */ - testcase( i==90 ); /* CAST */ - testcase( i==91 ); /* COLUMN */ - testcase( i==92 ); /* COMMIT */ - testcase( i==93 ); /* CONFLICT */ - testcase( i==94 ); /* CROSS */ - testcase( i==95 ); /* CURRENT_TIMESTAMP */ - testcase( i==96 ); /* CURRENT_TIME */ - testcase( i==97 ); /* PRIMARY */ - testcase( i==98 ); /* DEFERRED */ - testcase( i==99 ); /* DISTINCT */ - testcase( i==100 ); /* IS */ - testcase( i==101 ); /* DROP */ - testcase( i==102 ); /* FAIL */ - testcase( i==103 ); /* FROM */ - testcase( i==104 ); /* FULL */ - testcase( i==105 ); /* GLOB */ - testcase( i==106 ); /* BY */ - testcase( i==107 ); /* IF */ - testcase( i==108 ); /* ISNULL */ - testcase( i==109 ); /* ORDER */ - testcase( i==110 ); /* RESTRICT */ - testcase( i==111 ); /* OUTER */ - testcase( i==112 ); /* RIGHT */ - testcase( i==113 ); /* ROLLBACK */ - testcase( i==114 ); /* ROW */ - testcase( i==115 ); /* UNION */ - testcase( i==116 ); /* USING */ - testcase( i==117 ); /* VACUUM */ - testcase( i==118 ); /* VIEW */ - testcase( i==119 ); /* INITIALLY */ - testcase( i==120 ); /* ALL */ + testcase( i==48 ); /* WITHOUT */ + testcase( i==49 ); /* WITH */ + testcase( i==50 ); /* OUTER */ + testcase( i==51 ); /* RELEASE */ + testcase( i==52 ); /* ATTACH */ + testcase( i==53 ); /* HAVING */ + testcase( i==54 ); /* GROUP */ + testcase( i==55 ); /* UPDATE */ + testcase( i==56 ); /* BEGIN */ + testcase( i==57 ); /* INNER */ + testcase( i==58 ); /* RECURSIVE */ + testcase( i==59 ); /* BETWEEN */ + testcase( i==60 ); /* NOTNULL */ + testcase( i==61 ); /* NOT */ + testcase( i==62 ); /* NO */ + testcase( i==63 ); /* NULL */ + testcase( i==64 ); /* LIKE */ + testcase( i==65 ); /* CASCADE */ + testcase( i==66 ); /* ASC */ + testcase( i==67 ); /* DELETE */ + testcase( i==68 ); /* CASE */ + testcase( i==69 ); /* COLLATE */ + testcase( i==70 ); /* CREATE */ + testcase( i==71 ); /* CURRENT_DATE */ + testcase( i==72 ); /* DETACH */ + testcase( i==73 ); /* IMMEDIATE */ + testcase( i==74 ); /* JOIN */ + testcase( i==75 ); /* INSERT */ + testcase( i==76 ); /* MATCH */ + testcase( i==77 ); /* PLAN */ + testcase( i==78 ); /* ANALYZE */ + testcase( i==79 ); /* PRAGMA */ + testcase( i==80 ); /* ABORT */ + testcase( i==81 ); /* VALUES */ + testcase( i==82 ); /* VIRTUAL */ + testcase( i==83 ); /* LIMIT */ + testcase( i==84 ); /* WHEN */ + testcase( i==85 ); /* WHERE */ + testcase( i==86 ); /* RENAME */ + testcase( i==87 ); /* AFTER */ + testcase( i==88 ); /* REPLACE */ + testcase( i==89 ); /* AND */ + testcase( i==90 ); /* DEFAULT */ + testcase( i==91 ); /* AUTOINCREMENT */ + testcase( i==92 ); /* TO */ + testcase( i==93 ); /* IN */ + testcase( i==94 ); /* CAST */ + testcase( i==95 ); /* COLUMN */ + testcase( i==96 ); /* COMMIT */ + testcase( i==97 ); /* CONFLICT */ + testcase( i==98 ); /* CROSS */ + testcase( i==99 ); /* CURRENT_TIMESTAMP */ + testcase( i==100 ); /* CURRENT_TIME */ + testcase( i==101 ); /* PRIMARY */ + testcase( i==102 ); /* DEFERRED */ + testcase( i==103 ); /* DISTINCT */ + testcase( i==104 ); /* IS */ + testcase( i==105 ); /* DROP */ + testcase( i==106 ); /* FAIL */ + testcase( i==107 ); /* FROM */ + testcase( i==108 ); /* FULL */ + testcase( i==109 ); /* GLOB */ + testcase( i==110 ); /* BY */ + testcase( i==111 ); /* IF */ + testcase( i==112 ); /* ISNULL */ + testcase( i==113 ); /* ORDER */ + testcase( i==114 ); /* RESTRICT */ + testcase( i==115 ); /* RIGHT */ + testcase( i==116 ); /* ROLLBACK */ + testcase( i==117 ); /* ROW */ + testcase( i==118 ); /* UNION */ + testcase( i==119 ); /* USING */ + testcase( i==120 ); /* VACUUM */ + testcase( i==121 ); /* VIEW */ + testcase( i==122 ); /* INITIALLY */ + testcase( i==123 ); /* ALL */ return aCode[i]; } } return TK_ID; } SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char *z, int n){ return keywordCode((char*)z, n); } -#define SQLITE_N_KEYWORD 121 +#define SQLITE_N_KEYWORD 124 /************** End of keywordhash.h *****************************************/ /************** Continuing where we left off in tokenize.c *******************/ @@ -116239,11 +126634,11 @@ ** ** For EBCDIC, the rules are more complex but have the same ** end result. ** ** Ticket #1066. the SQL standard does not allow '$' in the -** middle of identfiers. But many SQL implementations do. +** middle of identifiers. But many SQL implementations do. ** SQLite will allow '$' in identifiers for compatibility. ** But the feature is undocumented. */ #ifdef SQLITE_ASCII #define IdChar(C) ((sqlite3CtypeMap[(unsigned char)C]&0x46)!=0) @@ -116264,10 +126659,11 @@ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, /* Ex */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, /* Fx */ }; #define IdChar(C) (((c=C)>=0x42 && sqlite3IsEbcdicIdChar[c-0x40])) #endif +SQLITE_PRIVATE int sqlite3IsIdChar(u8 c){ return IdChar(c); } /* ** Return the length of the token that begins at z[0]. ** Store the token type in *tokenType before returning. @@ -116432,10 +126828,16 @@ testcase( z[0]=='0' ); testcase( z[0]=='1' ); testcase( z[0]=='2' ); testcase( z[0]=='3' ); testcase( z[0]=='4' ); testcase( z[0]=='5' ); testcase( z[0]=='6' ); testcase( z[0]=='7' ); testcase( z[0]=='8' ); testcase( z[0]=='9' ); *tokenType = TK_INTEGER; +#ifndef SQLITE_OMIT_HEX_INTEGER + if( z[0]=='0' && (z[1]=='x' || z[1]=='X') && sqlite3Isxdigit(z[2]) ){ + for(i=3; sqlite3Isxdigit(z[i]); i++){} + return i; + } +#endif for(i=0; sqlite3Isdigit(z[i]); i++){} #ifndef SQLITE_OMIT_FLOATING_POINT if( z[i]=='.' ){ i++; while( sqlite3Isdigit(z[i]) ){ i++; } @@ -116465,28 +126867,19 @@ case '?': { *tokenType = TK_VARIABLE; for(i=1; sqlite3Isdigit(z[i]); i++){} return i; } - case '#': { - for(i=1; sqlite3Isdigit(z[i]); i++){} - if( i>1 ){ - /* Parameters of the form #NNN (where NNN is a number) are used - ** internally by sqlite3NestedParse. */ - *tokenType = TK_REGISTER; - return i; - } - /* Fall through into the next case if the '#' is not followed by - ** a digit. Try to match #AAAA where AAAA is a parameter name. */ - } #ifndef SQLITE_OMIT_TCL_VARIABLE case '$': #endif case '@': /* For compatibility with MS SQL Server */ + case '#': case ':': { int n = 0; - testcase( z[0]=='$' ); testcase( z[0]=='@' ); testcase( z[0]==':' ); + testcase( z[0]=='$' ); testcase( z[0]=='@' ); + testcase( z[0]==':' ); testcase( z[0]=='#' ); *tokenType = TK_VARIABLE; for(i=1; (c=z[i])!=0; i++){ if( IdChar(c) ){ n++; #ifndef SQLITE_OMIT_TCL_VARIABLE @@ -116554,20 +126947,20 @@ int lastTokenParsed = -1; /* type of the previous token */ u8 enableLookaside; /* Saved value of db->lookaside.bEnabled */ sqlite3 *db = pParse->db; /* The database connection */ int mxSqlLen; /* Max length of an SQL string */ - + assert( zSql!=0 ); mxSqlLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; if( db->nVdbeActive==0 ){ db->u1.isInterrupted = 0; } pParse->rc = SQLITE_OK; pParse->zTail = zSql; i = 0; assert( pzErrMsg!=0 ); - pEngine = sqlite3ParserAlloc((void*(*)(size_t))sqlite3Malloc); + pEngine = sqlite3ParserAlloc(sqlite3Malloc); if( pEngine==0 ){ db->mallocFailed = 1; return SQLITE_NOMEM; } assert( pParse->pNewTable==0 ); @@ -116623,13 +127016,15 @@ pParse->zTail = &zSql[i]; } sqlite3Parser(pEngine, 0, pParse->sLastToken, pParse); } #ifdef YYTRACKMAXSTACKDEPTH + sqlite3_mutex_enter(sqlite3MallocMutex()); sqlite3StatusSet(SQLITE_STATUS_PARSER_STACK, sqlite3ParserStackPeak(pEngine) ); + sqlite3_mutex_leave(sqlite3MallocMutex()); #endif /* YYDEBUG */ sqlite3ParserFree(pEngine, sqlite3_free); db->lookaside.bEnabled = enableLookaside; if( db->mallocFailed ){ pParse->rc = SQLITE_NOMEM; @@ -116665,14 +127060,14 @@ ** will take responsibility for freeing the Table structure. */ sqlite3DeleteTable(db, pParse->pNewTable); } + if( pParse->bFreeWith ) sqlite3WithDelete(db, pParse->pWith); sqlite3DeleteTrigger(db, pParse->pNewTrigger); for(i=pParse->nzVar-1; i>=0; i--) sqlite3DbFree(db, pParse->azVar[i]); sqlite3DbFree(db, pParse->azVar); - sqlite3DbFree(db, pParse->aAlias); while( pParse->pAinc ){ AutoincInfo *p = pParse->pAinc; pParse->pAinc = p->pNext; sqlite3DbFree(db, p); } @@ -116758,21 +127153,21 @@ ** ** (3) EXPLAIN The keyword EXPLAIN has been seen at the beginning of ** a statement. ** ** (4) CREATE The keyword CREATE has been seen at the beginning of a -** statement, possibly preceeded by EXPLAIN and/or followed by +** statement, possibly preceded by EXPLAIN and/or followed by ** TEMP or TEMPORARY ** ** (5) TRIGGER We are in the middle of a trigger definition that must be ** ended by a semicolon, the keyword END, and another semicolon. ** ** (6) SEMI We've seen the first semicolon in the ";END;" that occurs at ** the end of a trigger definition. ** ** (7) END We've seen the ";END" of the ";END;" that occurs at the end -** of a trigger difinition. +** of a trigger definition. ** ** Transitions between states above are determined by tokens extracted ** from the input. The following tokens are significant: ** ** (0) tkSEMI A semicolon. @@ -116789,11 +127184,11 @@ ** ** If we compile with SQLITE_OMIT_TRIGGER, all of the computation needed ** to recognize the end of a trigger can be omitted. All we have to do ** is look for a semicolon that is not part of an string or comment. */ -SQLITE_API int sqlite3_complete(const char *zSql){ +SQLITE_API int SQLITE_STDCALL sqlite3_complete(const char *zSql){ u8 state = 0; /* Current state, using numbers defined in header comment */ u8 token; /* Value of the next token */ #ifndef SQLITE_OMIT_TRIGGER /* A complex statement machine used to detect the end of a CREATE TRIGGER @@ -116811,20 +127206,27 @@ /* 6 SEMI: */ { 6, 6, 5, 5, 5, 5, 5, 7, }, /* 7 END: */ { 1, 7, 5, 5, 5, 5, 5, 5, }, }; #else /* If triggers are not supported by this compile then the statement machine - ** used to detect the end of a statement is much simplier + ** used to detect the end of a statement is much simpler */ static const u8 trans[3][3] = { /* Token: */ /* State: ** SEMI WS OTHER */ /* 0 INVALID: */ { 1, 0, 2, }, /* 1 START: */ { 1, 1, 2, }, /* 2 NORMAL: */ { 1, 2, 2, }, }; #endif /* SQLITE_OMIT_TRIGGER */ + +#ifdef SQLITE_ENABLE_API_ARMOR + if( zSql==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif while( *zSql ){ switch( *zSql ){ case ';': { /* A semicolon */ token = tkSEMI; @@ -116947,11 +127349,11 @@ /* ** This routine is the same as the sqlite3_complete() routine described ** above, except that the parameter is required to be UTF-16 encoded, not ** UTF-8. */ -SQLITE_API int sqlite3_complete16(const void *zSql){ +SQLITE_API int SQLITE_STDCALL sqlite3_complete16(const void *zSql){ sqlite3_value *pVal; char const *zSql8; int rc = SQLITE_NOMEM; #ifndef SQLITE_OMIT_AUTOINIT @@ -117097,37 +127499,37 @@ #endif /* IMPLEMENTATION-OF: R-53536-42575 The sqlite3_libversion() function returns ** a pointer to the to the sqlite3_version[] string constant. */ -SQLITE_API const char *sqlite3_libversion(void){ return sqlite3_version; } +SQLITE_API const char *SQLITE_STDCALL sqlite3_libversion(void){ return sqlite3_version; } /* IMPLEMENTATION-OF: R-63124-39300 The sqlite3_sourceid() function returns a ** pointer to a string constant whose value is the same as the ** SQLITE_SOURCE_ID C preprocessor macro. */ -SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } +SQLITE_API const char *SQLITE_STDCALL sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } /* IMPLEMENTATION-OF: R-35210-63508 The sqlite3_libversion_number() function ** returns an integer equal to SQLITE_VERSION_NUMBER. */ -SQLITE_API int sqlite3_libversion_number(void){ return SQLITE_VERSION_NUMBER; } +SQLITE_API int SQLITE_STDCALL sqlite3_libversion_number(void){ return SQLITE_VERSION_NUMBER; } /* IMPLEMENTATION-OF: R-20790-14025 The sqlite3_threadsafe() function returns ** zero if and only if SQLite was compiled with mutexing code omitted due to ** the SQLITE_THREADSAFE compile-time option being set to 0. */ -SQLITE_API int sqlite3_threadsafe(void){ return SQLITE_THREADSAFE; } +SQLITE_API int SQLITE_STDCALL sqlite3_threadsafe(void){ return SQLITE_THREADSAFE; } #if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE) /* ** If the following function pointer is not NULL and if ** SQLITE_ENABLE_IOTRACE is enabled, then messages describing ** I/O active are written using this function. These messages ** are intended for debugging activity only. */ -SQLITE_PRIVATE void (*sqlite3IoTrace)(const char*, ...) = 0; +SQLITE_API void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...) = 0; #endif /* ** If the following global variable points to a string which is the ** name of a directory, then that directory will be used to store @@ -117175,11 +127577,11 @@ ** call by X completes. ** ** * Recursive calls to this routine from thread X return immediately ** without blocking. */ -SQLITE_API int sqlite3_initialize(void){ +SQLITE_API int SQLITE_STDCALL sqlite3_initialize(void){ MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */ int rc; /* Result code */ #ifdef SQLITE_EXTRA_INIT int bRunExtraInit = 0; /* Extra initialization needed */ #endif @@ -117188,25 +127590,23 @@ rc = sqlite3_wsd_init(4096, 24); if( rc!=SQLITE_OK ){ return rc; } #endif + + /* If the following assert() fails on some obscure processor/compiler + ** combination, the work-around is to set the correct pointer + ** size at compile-time using -DSQLITE_PTRSIZE=n compile-time option */ + assert( SQLITE_PTRSIZE==sizeof(char*) ); /* If SQLite is already completely initialized, then this call ** to sqlite3_initialize() should be a no-op. But the initialization ** must be complete. So isInit must not be set until the very end ** of this routine. */ if( sqlite3GlobalConfig.isInit ) return SQLITE_OK; -#ifdef SQLITE_ENABLE_SQLLOG - { - extern void sqlite3_init_sqllog(void); - sqlite3_init_sqllog(); - } -#endif - /* Make sure the mutex subsystem is initialized. If unable to ** initialize the mutex subsystem, return early with the error. ** If the system is so sick that we are unable to allocate a mutex, ** there is not much SQLite is going to be able to do. ** @@ -117338,11 +127738,18 @@ ** while any part of SQLite is otherwise in use in any thread. This ** routine is not threadsafe. But it is safe to invoke this routine ** on when SQLite is already shut down. If SQLite is already shut down ** when this routine is invoked, then this routine is a harmless no-op. */ -SQLITE_API int sqlite3_shutdown(void){ +SQLITE_API int SQLITE_STDCALL sqlite3_shutdown(void){ +#ifdef SQLITE_OMIT_WSD + int rc = sqlite3_wsd_init(4096, 24); + if( rc!=SQLITE_OK ){ + return rc; + } +#endif + if( sqlite3GlobalConfig.isInit ){ #ifdef SQLITE_EXTRA_SHUTDOWN void SQLITE_EXTRA_SHUTDOWN(void); SQLITE_EXTRA_SHUTDOWN(); #endif @@ -117385,11 +127792,11 @@ ** This routine should only be called when there are no outstanding ** database connections or memory allocations. This routine is not ** threadsafe. Failure to heed these warnings can lead to unpredictable ** behavior. */ -SQLITE_API int sqlite3_config(int op, ...){ +SQLITE_API int SQLITE_CDECL sqlite3_config(int op, ...){ va_list ap; int rc = SQLITE_OK; /* sqlite3_config() shall return SQLITE_MISUSE if it is invoked while ** the SQLite library is in use. */ @@ -117397,74 +127804,108 @@ va_start(ap, op); switch( op ){ /* Mutex configuration options are only available in a threadsafe - ** compile. + ** compile. */ -#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-54466-46756 */ case SQLITE_CONFIG_SINGLETHREAD: { - /* Disable all mutexing */ - sqlite3GlobalConfig.bCoreMutex = 0; - sqlite3GlobalConfig.bFullMutex = 0; + /* EVIDENCE-OF: R-02748-19096 This option sets the threading mode to + ** Single-thread. */ + sqlite3GlobalConfig.bCoreMutex = 0; /* Disable mutex on core */ + sqlite3GlobalConfig.bFullMutex = 0; /* Disable mutex on connections */ break; } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-20520-54086 */ case SQLITE_CONFIG_MULTITHREAD: { - /* Disable mutexing of database connections */ - /* Enable mutexing of core data structures */ - sqlite3GlobalConfig.bCoreMutex = 1; - sqlite3GlobalConfig.bFullMutex = 0; + /* EVIDENCE-OF: R-14374-42468 This option sets the threading mode to + ** Multi-thread. */ + sqlite3GlobalConfig.bCoreMutex = 1; /* Enable mutex on core */ + sqlite3GlobalConfig.bFullMutex = 0; /* Disable mutex on connections */ break; } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-59593-21810 */ case SQLITE_CONFIG_SERIALIZED: { - /* Enable all mutexing */ - sqlite3GlobalConfig.bCoreMutex = 1; - sqlite3GlobalConfig.bFullMutex = 1; + /* EVIDENCE-OF: R-41220-51800 This option sets the threading mode to + ** Serialized. */ + sqlite3GlobalConfig.bCoreMutex = 1; /* Enable mutex on core */ + sqlite3GlobalConfig.bFullMutex = 1; /* Enable mutex on connections */ break; } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-63666-48755 */ case SQLITE_CONFIG_MUTEX: { /* Specify an alternative mutex implementation */ sqlite3GlobalConfig.mutex = *va_arg(ap, sqlite3_mutex_methods*); break; } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-14450-37597 */ case SQLITE_CONFIG_GETMUTEX: { /* Retrieve the current mutex implementation */ *va_arg(ap, sqlite3_mutex_methods*) = sqlite3GlobalConfig.mutex; break; } #endif - case SQLITE_CONFIG_MALLOC: { - /* Specify an alternative malloc implementation */ + /* EVIDENCE-OF: R-55594-21030 The SQLITE_CONFIG_MALLOC option takes a + ** single argument which is a pointer to an instance of the + ** sqlite3_mem_methods structure. The argument specifies alternative + ** low-level memory allocation routines to be used in place of the memory + ** allocation routines built into SQLite. */ sqlite3GlobalConfig.m = *va_arg(ap, sqlite3_mem_methods*); break; } case SQLITE_CONFIG_GETMALLOC: { - /* Retrieve the current malloc() implementation */ + /* EVIDENCE-OF: R-51213-46414 The SQLITE_CONFIG_GETMALLOC option takes a + ** single argument which is a pointer to an instance of the + ** sqlite3_mem_methods structure. The sqlite3_mem_methods structure is + ** filled with the currently defined memory allocation routines. */ if( sqlite3GlobalConfig.m.xMalloc==0 ) sqlite3MemSetDefault(); *va_arg(ap, sqlite3_mem_methods*) = sqlite3GlobalConfig.m; break; } case SQLITE_CONFIG_MEMSTATUS: { - /* Enable or disable the malloc status collection */ + /* EVIDENCE-OF: R-61275-35157 The SQLITE_CONFIG_MEMSTATUS option takes + ** single argument of type int, interpreted as a boolean, which enables + ** or disables the collection of memory allocation statistics. */ sqlite3GlobalConfig.bMemstat = va_arg(ap, int); break; } case SQLITE_CONFIG_SCRATCH: { - /* Designate a buffer for scratch memory space */ + /* EVIDENCE-OF: R-08404-60887 There are three arguments to + ** SQLITE_CONFIG_SCRATCH: A pointer an 8-byte aligned memory buffer from + ** which the scratch allocations will be drawn, the size of each scratch + ** allocation (sz), and the maximum number of scratch allocations (N). */ sqlite3GlobalConfig.pScratch = va_arg(ap, void*); sqlite3GlobalConfig.szScratch = va_arg(ap, int); sqlite3GlobalConfig.nScratch = va_arg(ap, int); break; } case SQLITE_CONFIG_PAGECACHE: { - /* Designate a buffer for page cache memory space */ + /* EVIDENCE-OF: R-31408-40510 There are three arguments to + ** SQLITE_CONFIG_PAGECACHE: A pointer to 8-byte aligned memory, the size + ** of each page buffer (sz), and the number of pages (N). */ sqlite3GlobalConfig.pPage = va_arg(ap, void*); sqlite3GlobalConfig.szPage = va_arg(ap, int); sqlite3GlobalConfig.nPage = va_arg(ap, int); break; + } + case SQLITE_CONFIG_PCACHE_HDRSZ: { + /* EVIDENCE-OF: R-39100-27317 The SQLITE_CONFIG_PCACHE_HDRSZ option takes + ** a single parameter which is a pointer to an integer and writes into + ** that integer the number of extra bytes per page required for each page + ** in SQLITE_CONFIG_PAGECACHE. */ + *va_arg(ap, int*) = + sqlite3HeaderSizeBtree() + + sqlite3HeaderSizePcache() + + sqlite3HeaderSizePcache1(); + break; } case SQLITE_CONFIG_PCACHE: { /* no-op */ break; @@ -117474,25 +127915,38 @@ rc = SQLITE_ERROR; break; } case SQLITE_CONFIG_PCACHE2: { - /* Specify an alternative page cache implementation */ + /* EVIDENCE-OF: R-63325-48378 The SQLITE_CONFIG_PCACHE2 option takes a + ** single argument which is a pointer to an sqlite3_pcache_methods2 + ** object. This object specifies the interface to a custom page cache + ** implementation. */ sqlite3GlobalConfig.pcache2 = *va_arg(ap, sqlite3_pcache_methods2*); break; } case SQLITE_CONFIG_GETPCACHE2: { + /* EVIDENCE-OF: R-22035-46182 The SQLITE_CONFIG_GETPCACHE2 option takes a + ** single argument which is a pointer to an sqlite3_pcache_methods2 + ** object. SQLite copies of the current page cache implementation into + ** that object. */ if( sqlite3GlobalConfig.pcache2.xInit==0 ){ sqlite3PCacheSetDefault(); } *va_arg(ap, sqlite3_pcache_methods2*) = sqlite3GlobalConfig.pcache2; break; } +/* EVIDENCE-OF: R-06626-12911 The SQLITE_CONFIG_HEAP option is only +** available if SQLite is compiled with either SQLITE_ENABLE_MEMSYS3 or +** SQLITE_ENABLE_MEMSYS5 and returns SQLITE_ERROR if invoked otherwise. */ #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) case SQLITE_CONFIG_HEAP: { - /* Designate a buffer for heap memory space */ + /* EVIDENCE-OF: R-19854-42126 There are three arguments to + ** SQLITE_CONFIG_HEAP: An 8-byte aligned pointer to the memory, the + ** number of bytes in the memory buffer, and the minimum allocation size. + */ sqlite3GlobalConfig.pHeap = va_arg(ap, void*); sqlite3GlobalConfig.nHeap = va_arg(ap, int); sqlite3GlobalConfig.mnReq = va_arg(ap, int); if( sqlite3GlobalConfig.mnReq<1 ){ @@ -117501,21 +127955,23 @@ /* cap min request size at 2^12 */ sqlite3GlobalConfig.mnReq = (1<<12); } if( sqlite3GlobalConfig.pHeap==0 ){ - /* If the heap pointer is NULL, then restore the malloc implementation - ** back to NULL pointers too. This will cause the malloc to go - ** back to its default implementation when sqlite3_initialize() is - ** run. + /* EVIDENCE-OF: R-49920-60189 If the first pointer (the memory pointer) + ** is NULL, then SQLite reverts to using its default memory allocator + ** (the system malloc() implementation), undoing any prior invocation of + ** SQLITE_CONFIG_MALLOC. + ** + ** Setting sqlite3GlobalConfig.m to all zeros will cause malloc to + ** revert to its default implementation when sqlite3_initialize() is run */ memset(&sqlite3GlobalConfig.m, 0, sizeof(sqlite3GlobalConfig.m)); }else{ - /* The heap pointer is not NULL, then install one of the - ** mem5.c/mem3.c methods. The enclosing #if guarantees at - ** least one of these methods is currently enabled. - */ + /* EVIDENCE-OF: R-61006-08918 If the memory pointer is not NULL then the + ** alternative memory allocator is engaged to handle all of SQLites + ** memory allocation needs. */ #ifdef SQLITE_ENABLE_MEMSYS3 sqlite3GlobalConfig.m = *sqlite3MemGetMemsys3(); #endif #ifdef SQLITE_ENABLE_MEMSYS5 sqlite3GlobalConfig.m = *sqlite3MemGetMemsys5(); @@ -117544,16 +128000,29 @@ sqlite3GlobalConfig.xLog = va_arg(ap, LOGFUNC_t); sqlite3GlobalConfig.pLogArg = va_arg(ap, void*); break; } + /* EVIDENCE-OF: R-55548-33817 The compile-time setting for URI filenames + ** can be changed at start-time using the + ** sqlite3_config(SQLITE_CONFIG_URI,1) or + ** sqlite3_config(SQLITE_CONFIG_URI,0) configuration calls. + */ case SQLITE_CONFIG_URI: { + /* EVIDENCE-OF: R-25451-61125 The SQLITE_CONFIG_URI option takes a single + ** argument of type int. If non-zero, then URI handling is globally + ** enabled. If the parameter is zero, then URI handling is globally + ** disabled. */ sqlite3GlobalConfig.bOpenUri = va_arg(ap, int); break; } case SQLITE_CONFIG_COVERING_INDEX_SCAN: { + /* EVIDENCE-OF: R-36592-02772 The SQLITE_CONFIG_COVERING_INDEX_SCAN + ** option takes a single integer argument which is interpreted as a + ** boolean in order to enable or disable the use of covering indices for + ** full table scans in the query optimizer. */ sqlite3GlobalConfig.bUseCis = va_arg(ap, int); break; } #ifdef SQLITE_ENABLE_SQLLOG @@ -117564,21 +128033,48 @@ break; } #endif case SQLITE_CONFIG_MMAP_SIZE: { + /* EVIDENCE-OF: R-58063-38258 SQLITE_CONFIG_MMAP_SIZE takes two 64-bit + ** integer (sqlite3_int64) values that are the default mmap size limit + ** (the default setting for PRAGMA mmap_size) and the maximum allowed + ** mmap size limit. */ sqlite3_int64 szMmap = va_arg(ap, sqlite3_int64); sqlite3_int64 mxMmap = va_arg(ap, sqlite3_int64); + /* EVIDENCE-OF: R-53367-43190 If either argument to this option is + ** negative, then that argument is changed to its compile-time default. + ** + ** EVIDENCE-OF: R-34993-45031 The maximum allowed mmap size will be + ** silently truncated if necessary so that it does not exceed the + ** compile-time maximum mmap size set by the SQLITE_MAX_MMAP_SIZE + ** compile-time option. + */ if( mxMmap<0 || mxMmap>SQLITE_MAX_MMAP_SIZE ){ mxMmap = SQLITE_MAX_MMAP_SIZE; } - sqlite3GlobalConfig.mxMmap = mxMmap; if( szMmap<0 ) szMmap = SQLITE_DEFAULT_MMAP_SIZE; if( szMmap>mxMmap) szMmap = mxMmap; + sqlite3GlobalConfig.mxMmap = mxMmap; sqlite3GlobalConfig.szMmap = szMmap; break; } + +#if SQLITE_OS_WIN && defined(SQLITE_WIN32_MALLOC) /* IMP: R-04780-55815 */ + case SQLITE_CONFIG_WIN32_HEAPSIZE: { + /* EVIDENCE-OF: R-34926-03360 SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit + ** unsigned integer value that specifies the maximum size of the created + ** heap. */ + sqlite3GlobalConfig.nHeap = va_arg(ap, int); + break; + } +#endif + + case SQLITE_CONFIG_PMASZ: { + sqlite3GlobalConfig.szPma = va_arg(ap, unsigned int); + break; + } default: { rc = SQLITE_ERROR; break; } @@ -117642,30 +128138,41 @@ } db->lookaside.pEnd = p; db->lookaside.bEnabled = 1; db->lookaside.bMalloced = pBuf==0 ?1:0; }else{ - db->lookaside.pEnd = 0; + db->lookaside.pStart = db; + db->lookaside.pEnd = db; db->lookaside.bEnabled = 0; db->lookaside.bMalloced = 0; } return SQLITE_OK; } /* ** Return the mutex associated with a database connection. */ -SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3 *db){ +SQLITE_API sqlite3_mutex *SQLITE_STDCALL sqlite3_db_mutex(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return db->mutex; } /* ** Free up as much memory as we can from the given database ** connection. */ -SQLITE_API int sqlite3_db_release_memory(sqlite3 *db){ +SQLITE_API int SQLITE_STDCALL sqlite3_db_release_memory(sqlite3 *db){ int i; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); sqlite3BtreeEnterAll(db); for(i=0; i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ @@ -117679,11 +128186,11 @@ } /* ** Configuration settings for an individual database connection */ -SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ +SQLITE_API int SQLITE_CDECL sqlite3_db_config(sqlite3 *db, int op, ...){ va_list ap; int rc; va_start(ap, op); switch( op ){ case SQLITE_DBCONFIG_LOOKASIDE: { @@ -117751,17 +128258,24 @@ int nKey1, const void *pKey1, int nKey2, const void *pKey2 ){ int rc, n; n = nKey1<nKey2 ? nKey1 : nKey2; + /* EVIDENCE-OF: R-65033-28449 The built-in BINARY collation compares + ** strings byte by byte using the memcmp() function from the standard C + ** library. */ rc = memcmp(pKey1, pKey2, n); if( rc==0 ){ if( padFlag && allSpaces(((char*)pKey1)+n, nKey1-n) && allSpaces(((char*)pKey2)+n, nKey2-n) ){ - /* Leave rc unchanged at 0 */ + /* EVIDENCE-OF: R-31624-24737 RTRIM is like BINARY except that extra + ** spaces at the end of either string do not change the result. In other + ** words, strings will compare equal to one another as long as they + ** differ only in the number of spaces at the end. + */ }else{ rc = nKey1 - nKey2; } } return rc; @@ -117791,25 +128305,43 @@ } /* ** Return the ROWID of the most recent insert */ -SQLITE_API sqlite_int64 sqlite3_last_insert_rowid(sqlite3 *db){ +SQLITE_API sqlite_int64 SQLITE_STDCALL sqlite3_last_insert_rowid(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return db->lastRowid; } /* ** Return the number of changes in the most recent call to sqlite3_exec(). */ -SQLITE_API int sqlite3_changes(sqlite3 *db){ +SQLITE_API int SQLITE_STDCALL sqlite3_changes(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return db->nChange; } /* ** Return the number of changes since the database handle was opened. */ -SQLITE_API int sqlite3_total_changes(sqlite3 *db){ +SQLITE_API int SQLITE_STDCALL sqlite3_total_changes(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return db->nTotalChange; } /* ** Close all open savepoints. This function only manipulates fields of the @@ -117860,10 +128392,11 @@ Table *pTab = (Table *)sqliteHashData(p); if( IsVirtual(pTab) ) sqlite3VtabDisconnect(db, pTab); } } } + sqlite3VtabUnlockList(db); sqlite3BtreeLeaveAll(db); #else UNUSED_PARAMETER(db); #endif } @@ -117886,10 +128419,12 @@ /* ** Close an existing SQLite database */ static int sqlite3Close(sqlite3 *db, int forceZombie){ if( !db ){ + /* EVIDENCE-OF: R-63257-11740 Calling sqlite3_close() or + ** sqlite3_close_v2() with a NULL pointer argument is a harmless no-op. */ return SQLITE_OK; } if( !sqlite3SafetyCheckSickOrOk(db) ){ return SQLITE_MISUSE_BKPT; } @@ -117909,11 +128444,11 @@ /* Legacy behavior (sqlite3_close() behavior) is to return ** SQLITE_BUSY if the connection can not be closed immediately. */ if( !forceZombie && connectionIsBusy(db) ){ - sqlite3Error(db, SQLITE_BUSY, "unable to close due to unfinalized " + sqlite3ErrorWithMsg(db, SQLITE_BUSY, "unable to close due to unfinalized " "statements or unfinished backups"); sqlite3_mutex_leave(db->mutex); return SQLITE_BUSY; } @@ -117938,12 +128473,12 @@ ** statements or unfinished sqlite3_backups. The sqlite3_close_v2() ** version forces the connection to become a zombie if there are ** unclosed resources, and arranges for deallocation when the last ** prepare statement or sqlite3_backup closes. */ -SQLITE_API int sqlite3_close(sqlite3 *db){ return sqlite3Close(db,0); } -SQLITE_API int sqlite3_close_v2(sqlite3 *db){ return sqlite3Close(db,1); } +SQLITE_API int SQLITE_STDCALL sqlite3_close(sqlite3 *db){ return sqlite3Close(db,0); } +SQLITE_API int SQLITE_STDCALL sqlite3_close_v2(sqlite3 *db){ return sqlite3Close(db,1); } /* ** Close the mutex on database connection db. ** @@ -118039,15 +128574,17 @@ sqlite3DbFree(db, pMod); } sqlite3HashClear(&db->aModule); #endif - sqlite3Error(db, SQLITE_OK, 0); /* Deallocates any cached error strings. */ - if( db->pErr ){ - sqlite3ValueFree(db->pErr); - } + sqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */ + sqlite3ValueFree(db->pErr); sqlite3CloseExtensions(db); +#if SQLITE_USER_AUTHENTICATION + sqlite3_free(db->auth.zAuthUser); + sqlite3_free(db->auth.zAuthPW); +#endif db->magic = SQLITE_MAGIC_ERROR; /* The temp-database schema is allocated differently from the other schema ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()). @@ -118066,17 +128603,19 @@ sqlite3_free(db); } /* ** Rollback all database files. If tripCode is not SQLITE_OK, then -** any open cursors are invalidated ("tripped" - as in "tripping a circuit +** any write cursors are invalidated ("tripped" - as in "tripping a circuit ** breaker") and made to return tripCode if there are any further -** attempts to use that cursor. +** attempts to use that cursor. Read cursors remain open and valid +** but are "saved" in case the table pages are moved around. */ SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3 *db, int tripCode){ int i; int inTrans = 0; + int schemaChange; assert( sqlite3_mutex_held(db->mutex) ); sqlite3BeginBenignMalloc(); /* Obtain all b-tree mutexes before making any calls to BtreeRollback(). ** This is important in case the transaction being rolled back has @@ -118083,18 +128622,19 @@ ** modified the database schema. If the b-tree mutexes are not taken ** here, then another shared-cache connection might sneak in between ** the database rollback and schema reset, which can cause false ** corruption reports in some cases. */ sqlite3BtreeEnterAll(db); + schemaChange = (db->flags & SQLITE_InternChanges)!=0 && db->init.busy==0; for(i=0; i<db->nDb; i++){ Btree *p = db->aDb[i].pBt; if( p ){ if( sqlite3BtreeIsInTrans(p) ){ inTrans = 1; } - sqlite3BtreeRollback(p, tripCode); + sqlite3BtreeRollback(p, tripCode, !schemaChange); } } sqlite3VtabRollback(db); sqlite3EndBenignMalloc(); @@ -118117,12 +128657,11 @@ /* ** Return a static string containing the name corresponding to the error code ** specified in the argument. */ -#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) || \ - defined(SQLITE_DEBUG_OS_TRACE) +#if (defined(SQLITE_DEBUG) && SQLITE_OS_WIN) || defined(SQLITE_TEST) SQLITE_PRIVATE const char *sqlite3ErrName(int rc){ const char *zName = 0; int i, origRc = rc; for(i=0; i<2 && zName==0; i++, rc &= 0xff){ switch( rc ){ @@ -118140,10 +128679,11 @@ case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; case SQLITE_READONLY_RECOVERY: zName = "SQLITE_READONLY_RECOVERY"; break; case SQLITE_READONLY_CANTLOCK: zName = "SQLITE_READONLY_CANTLOCK"; break; case SQLITE_READONLY_ROLLBACK: zName = "SQLITE_READONLY_ROLLBACK"; break; + case SQLITE_READONLY_DBMOVED: zName = "SQLITE_READONLY_DBMOVED"; break; case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break; case SQLITE_IOERR: zName = "SQLITE_IOERR"; break; case SQLITE_IOERR_READ: zName = "SQLITE_IOERR_READ"; break; case SQLITE_IOERR_SHORT_READ: zName = "SQLITE_IOERR_SHORT_READ"; break; case SQLITE_IOERR_WRITE: zName = "SQLITE_IOERR_WRITE"; break; @@ -118152,11 +128692,10 @@ case SQLITE_IOERR_TRUNCATE: zName = "SQLITE_IOERR_TRUNCATE"; break; case SQLITE_IOERR_FSTAT: zName = "SQLITE_IOERR_FSTAT"; break; case SQLITE_IOERR_UNLOCK: zName = "SQLITE_IOERR_UNLOCK"; break; case SQLITE_IOERR_RDLOCK: zName = "SQLITE_IOERR_RDLOCK"; break; case SQLITE_IOERR_DELETE: zName = "SQLITE_IOERR_DELETE"; break; - case SQLITE_IOERR_BLOCKED: zName = "SQLITE_IOERR_BLOCKED"; break; case SQLITE_IOERR_NOMEM: zName = "SQLITE_IOERR_NOMEM"; break; case SQLITE_IOERR_ACCESS: zName = "SQLITE_IOERR_ACCESS"; break; case SQLITE_IOERR_CHECKRESERVEDLOCK: zName = "SQLITE_IOERR_CHECKRESERVEDLOCK"; break; case SQLITE_IOERR_LOCK: zName = "SQLITE_IOERR_LOCK"; break; @@ -118196,10 +128735,11 @@ case SQLITE_CONSTRAINT_COMMITHOOK: zName = "SQLITE_CONSTRAINT_COMMITHOOK"; break; case SQLITE_CONSTRAINT_VTAB: zName = "SQLITE_CONSTRAINT_VTAB"; break; case SQLITE_CONSTRAINT_FUNCTION: zName = "SQLITE_CONSTRAINT_FUNCTION"; break; + case SQLITE_CONSTRAINT_ROWID: zName = "SQLITE_CONSTRAINT_ROWID"; break; case SQLITE_MISMATCH: zName = "SQLITE_MISMATCH"; break; case SQLITE_MISUSE: zName = "SQLITE_MISUSE"; break; case SQLITE_NOLFS: zName = "SQLITE_NOLFS"; break; case SQLITE_AUTH: zName = "SQLITE_AUTH"; break; case SQLITE_FORMAT: zName = "SQLITE_FORMAT"; break; @@ -118283,11 +128823,11 @@ */ static int sqliteDefaultBusyCallback( void *ptr, /* Database connection */ int count /* Number of times table has been busy */ ){ -#if SQLITE_OS_WIN || (defined(HAVE_USLEEP) && HAVE_USLEEP) +#if SQLITE_OS_WIN || HAVE_USLEEP static const u8 delays[] = { 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 }; static const u8 totals[] = { 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228 }; # define NDELAY ArraySize(delays) @@ -118341,15 +128881,18 @@ /* ** This routine sets the busy callback for an Sqlite database to the ** given callback function with the given argument. */ -SQLITE_API int sqlite3_busy_handler( +SQLITE_API int SQLITE_STDCALL sqlite3_busy_handler( sqlite3 *db, int (*xBusy)(void*,int), void *pArg ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); db->busyHandler.xFunc = xBusy; db->busyHandler.pArg = pArg; db->busyHandler.nBusy = 0; db->busyTimeout = 0; @@ -118361,16 +128904,22 @@ /* ** This routine sets the progress callback for an Sqlite database to the ** given callback function with the given argument. The progress callback will ** be invoked every nOps opcodes. */ -SQLITE_API void sqlite3_progress_handler( +SQLITE_API void SQLITE_STDCALL sqlite3_progress_handler( sqlite3 *db, int nOps, int (*xProgress)(void*), void *pArg ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return; + } +#endif sqlite3_mutex_enter(db->mutex); if( nOps>0 ){ db->xProgress = xProgress; db->nProgressOps = (unsigned)nOps; db->pProgressArg = pArg; @@ -118386,11 +128935,14 @@ /* ** This routine installs a default busy handler that waits for the ** specified number of milliseconds before returning 0. */ -SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ +SQLITE_API int SQLITE_STDCALL sqlite3_busy_timeout(sqlite3 *db, int ms){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif if( ms>0 ){ sqlite3_busy_handler(db, sqliteDefaultBusyCallback, (void*)db); db->busyTimeout = ms; }else{ sqlite3_busy_handler(db, 0, 0); @@ -118399,11 +128951,17 @@ } /* ** Cause any pending operation to stop at its earliest opportunity. */ -SQLITE_API void sqlite3_interrupt(sqlite3 *db){ +SQLITE_API void SQLITE_STDCALL sqlite3_interrupt(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return; + } +#endif db->u1.isInterrupted = 1; } /* @@ -118423,10 +128981,11 @@ void (*xFinal)(sqlite3_context*), FuncDestructor *pDestructor ){ FuncDef *p; int nName; + int extraFlags; assert( sqlite3_mutex_held(db->mutex) ); if( zFunctionName==0 || (xFunc && (xFinal || xStep)) || (!xFunc && (xFinal && !xStep)) || @@ -118433,10 +128992,14 @@ (!xFunc && (!xFinal && xStep)) || (nArg<-1 || nArg>SQLITE_MAX_FUNCTION_ARG) || (255<(nName = sqlite3Strlen30( zFunctionName))) ){ return SQLITE_MISUSE_BKPT; } + + assert( SQLITE_FUNC_CONSTANT==SQLITE_DETERMINISTIC ); + extraFlags = enc & SQLITE_DETERMINISTIC; + enc &= (SQLITE_FUNC_ENCMASK|SQLITE_ANY); #ifndef SQLITE_OMIT_UTF16 /* If SQLITE_UTF16 is specified as the encoding type, transform this ** to one of SQLITE_UTF16LE or SQLITE_UTF16BE using the ** SQLITE_UTF16NATIVE macro. SQLITE_UTF16 is not used internally. @@ -118446,14 +129009,14 @@ */ if( enc==SQLITE_UTF16 ){ enc = SQLITE_UTF16NATIVE; }else if( enc==SQLITE_ANY ){ int rc; - rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF8, + rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF8|extraFlags, pUserData, xFunc, xStep, xFinal, pDestructor); if( rc==SQLITE_OK ){ - rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF16LE, + rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF16LE|extraFlags, pUserData, xFunc, xStep, xFinal, pDestructor); } if( rc!=SQLITE_OK ){ return rc; } @@ -118469,11 +129032,11 @@ ** operation to continue but invalidate all precompiled statements. */ p = sqlite3FindFunction(db, zFunctionName, nName, nArg, (u8)enc, 0); if( p && (p->funcFlags & SQLITE_FUNC_ENCMASK)==enc && p->nArg==nArg ){ if( db->nVdbeActive ){ - sqlite3Error(db, SQLITE_BUSY, + sqlite3ErrorWithMsg(db, SQLITE_BUSY, "unable to delete/modify user-function due to active statements"); assert( !db->mallocFailed ); return SQLITE_BUSY; }else{ sqlite3ExpirePreparedStatements(db); @@ -118492,11 +129055,12 @@ if( pDestructor ){ pDestructor->nRef++; } p->pDestructor = pDestructor; - p->funcFlags &= SQLITE_FUNC_ENCMASK; + p->funcFlags = (p->funcFlags & SQLITE_FUNC_ENCMASK) | extraFlags; + testcase( p->funcFlags & SQLITE_DETERMINISTIC ); p->xFunc = xFunc; p->xStep = xStep; p->xFinalize = xFinal; p->pUserData = pUserData; p->nArg = (u16)nArg; @@ -118504,11 +129068,11 @@ } /* ** Create new user functions. */ -SQLITE_API int sqlite3_create_function( +SQLITE_API int SQLITE_STDCALL sqlite3_create_function( sqlite3 *db, const char *zFunc, int nArg, int enc, void *p, @@ -118518,11 +129082,11 @@ ){ return sqlite3_create_function_v2(db, zFunc, nArg, enc, p, xFunc, xStep, xFinal, 0); } -SQLITE_API int sqlite3_create_function_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_create_function_v2( sqlite3 *db, const char *zFunc, int nArg, int enc, void *p, @@ -118531,10 +129095,16 @@ void (*xFinal)(sqlite3_context*), void (*xDestroy)(void *) ){ int rc = SQLITE_ERROR; FuncDestructor *pArg = 0; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif sqlite3_mutex_enter(db->mutex); if( xDestroy ){ pArg = (FuncDestructor *)sqlite3DbMallocZero(db, sizeof(FuncDestructor)); if( !pArg ){ xDestroy(p); @@ -118555,11 +129125,11 @@ sqlite3_mutex_leave(db->mutex); return rc; } #ifndef SQLITE_OMIT_UTF16 -SQLITE_API int sqlite3_create_function16( +SQLITE_API int SQLITE_STDCALL sqlite3_create_function16( sqlite3 *db, const void *zFunctionName, int nArg, int eTextRep, void *p, @@ -118567,10 +129137,14 @@ void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ){ int rc; char *zFunc8; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zFunctionName==0 ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); assert( !db->mallocFailed ); zFunc8 = sqlite3Utf16to8(db, zFunctionName, -1, SQLITE_UTF16NATIVE); rc = sqlite3CreateFunc(db, zFunc8, nArg, eTextRep, p, xFunc, xStep, xFinal,0); sqlite3DbFree(db, zFunc8); @@ -118591,17 +129165,23 @@ ** When virtual tables intend to provide an overloaded function, they ** should call this routine to make sure the global function exists. ** A global function must exist in order for name resolution to work ** properly. */ -SQLITE_API int sqlite3_overload_function( +SQLITE_API int SQLITE_STDCALL sqlite3_overload_function( sqlite3 *db, const char *zName, int nArg ){ int nName = sqlite3Strlen30(zName); int rc = SQLITE_OK; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 || nArg<-2 ){ + return SQLITE_MISUSE_BKPT; + } +#endif sqlite3_mutex_enter(db->mutex); if( sqlite3FindFunction(db, zName, nName, nArg, SQLITE_UTF8, 0)==0 ){ rc = sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8, 0, sqlite3InvalidFunction, 0, 0, 0); } @@ -118617,12 +129197,19 @@ ** ** A NULL trace function means that no tracing is executes. A non-NULL ** trace is a pointer to a function that is invoked at the start of each ** SQL statement. */ -SQLITE_API void *sqlite3_trace(sqlite3 *db, void (*xTrace)(void*,const char*), void *pArg){ +SQLITE_API void *SQLITE_STDCALL sqlite3_trace(sqlite3 *db, void (*xTrace)(void*,const char*), void *pArg){ void *pOld; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pOld = db->pTraceArg; db->xTrace = xTrace; db->pTraceArg = pArg; sqlite3_mutex_leave(db->mutex); @@ -118634,16 +129221,23 @@ ** ** A NULL profile function means that no profiling is executes. A non-NULL ** profile is a pointer to a function that is invoked at the conclusion of ** each SQL statement that is run. */ -SQLITE_API void *sqlite3_profile( +SQLITE_API void *SQLITE_STDCALL sqlite3_profile( sqlite3 *db, void (*xProfile)(void*,const char*,sqlite_uint64), void *pArg ){ void *pOld; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pOld = db->pProfileArg; db->xProfile = xProfile; db->pProfileArg = pArg; sqlite3_mutex_leave(db->mutex); @@ -118654,16 +129248,23 @@ /* ** Register a function to be invoked when a transaction commits. ** If the invoked function returns non-zero, then the commit becomes a ** rollback. */ -SQLITE_API void *sqlite3_commit_hook( +SQLITE_API void *SQLITE_STDCALL sqlite3_commit_hook( sqlite3 *db, /* Attach the hook to this database */ int (*xCallback)(void*), /* Function to invoke on each commit */ void *pArg /* Argument to the function */ ){ void *pOld; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pOld = db->pCommitArg; db->xCommitCallback = xCallback; db->pCommitArg = pArg; sqlite3_mutex_leave(db->mutex); @@ -118672,16 +129273,23 @@ /* ** Register a callback to be invoked each time a row is updated, ** inserted or deleted using this database connection. */ -SQLITE_API void *sqlite3_update_hook( +SQLITE_API void *SQLITE_STDCALL sqlite3_update_hook( sqlite3 *db, /* Attach the hook to this database */ void (*xCallback)(void*,int,char const *,char const *,sqlite_int64), void *pArg /* Argument to the function */ ){ void *pRet; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pRet = db->pUpdateArg; db->xUpdateCallback = xCallback; db->pUpdateArg = pArg; sqlite3_mutex_leave(db->mutex); @@ -118690,16 +129298,23 @@ /* ** Register a callback to be invoked each time a transaction is rolled ** back by this database connection. */ -SQLITE_API void *sqlite3_rollback_hook( +SQLITE_API void *SQLITE_STDCALL sqlite3_rollback_hook( sqlite3 *db, /* Attach the hook to this database */ void (*xCallback)(void*), /* Callback function */ void *pArg /* Argument to the function */ ){ void *pRet; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pRet = db->pRollbackArg; db->xRollbackCallback = xCallback; db->pRollbackArg = pArg; sqlite3_mutex_leave(db->mutex); @@ -118737,15 +129352,18 @@ ** The callback registered by this function replaces any existing callback ** registered using sqlite3_wal_hook(). Likewise, registering a callback ** using sqlite3_wal_hook() disables the automatic checkpoint mechanism ** configured by this function. */ -SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int nFrame){ +SQLITE_API int SQLITE_STDCALL sqlite3_wal_autocheckpoint(sqlite3 *db, int nFrame){ #ifdef SQLITE_OMIT_WAL UNUSED_PARAMETER(db); UNUSED_PARAMETER(nFrame); #else +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif if( nFrame>0 ){ sqlite3_wal_hook(db, sqlite3WalDefaultHook, SQLITE_INT_TO_PTR(nFrame)); }else{ sqlite3_wal_hook(db, 0, 0); } @@ -118755,17 +129373,23 @@ /* ** Register a callback to be invoked each time a transaction is written ** into the write-ahead-log by this database connection. */ -SQLITE_API void *sqlite3_wal_hook( +SQLITE_API void *SQLITE_STDCALL sqlite3_wal_hook( sqlite3 *db, /* Attach the hook to this db handle */ int(*xCallback)(void *, sqlite3*, const char*, int), void *pArg /* First argument passed to xCallback() */ ){ #ifndef SQLITE_OMIT_WAL void *pRet; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pRet = db->pWalArg; db->xWalCallback = xCallback; db->pWalArg = pArg; sqlite3_mutex_leave(db->mutex); @@ -118776,11 +129400,11 @@ } /* ** Checkpoint database zDb. */ -SQLITE_API int sqlite3_wal_checkpoint_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_wal_checkpoint_v2( sqlite3 *db, /* Database handle */ const char *zDb, /* Name of attached database (or NULL) */ int eMode, /* SQLITE_CHECKPOINT_* value */ int *pnLog, /* OUT: Size of WAL log in frames */ int *pnCkpt /* OUT: Total number of frames checkpointed */ @@ -118788,32 +129412,40 @@ #ifdef SQLITE_OMIT_WAL return SQLITE_OK; #else int rc; /* Return code */ int iDb = SQLITE_MAX_ATTACHED; /* sqlite3.aDb[] index of db to checkpoint */ + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif /* Initialize the output variables to -1 in case an error occurs. */ if( pnLog ) *pnLog = -1; if( pnCkpt ) *pnCkpt = -1; - assert( SQLITE_CHECKPOINT_FULL>SQLITE_CHECKPOINT_PASSIVE ); - assert( SQLITE_CHECKPOINT_FULL<SQLITE_CHECKPOINT_RESTART ); - assert( SQLITE_CHECKPOINT_PASSIVE+2==SQLITE_CHECKPOINT_RESTART ); - if( eMode<SQLITE_CHECKPOINT_PASSIVE || eMode>SQLITE_CHECKPOINT_RESTART ){ + assert( SQLITE_CHECKPOINT_PASSIVE==0 ); + assert( SQLITE_CHECKPOINT_FULL==1 ); + assert( SQLITE_CHECKPOINT_RESTART==2 ); + assert( SQLITE_CHECKPOINT_TRUNCATE==3 ); + if( eMode<SQLITE_CHECKPOINT_PASSIVE || eMode>SQLITE_CHECKPOINT_TRUNCATE ){ + /* EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint + ** mode: */ return SQLITE_MISUSE; } sqlite3_mutex_enter(db->mutex); if( zDb && zDb[0] ){ iDb = sqlite3FindDbName(db, zDb); } if( iDb<0 ){ rc = SQLITE_ERROR; - sqlite3Error(db, SQLITE_ERROR, "unknown database: %s", zDb); + sqlite3ErrorWithMsg(db, SQLITE_ERROR, "unknown database: %s", zDb); }else{ + db->busyHandler.nBusy = 0; rc = sqlite3Checkpoint(db, iDb, eMode, pnLog, pnCkpt); - sqlite3Error(db, rc, 0); + sqlite3Error(db, rc); } rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; #endif @@ -118823,12 +129455,14 @@ /* ** Checkpoint database zDb. If zDb is NULL, or if the buffer zDb points ** to contains a zero-length string, all attached databases are ** checkpointed. */ -SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){ - return sqlite3_wal_checkpoint_v2(db, zDb, SQLITE_CHECKPOINT_PASSIVE, 0, 0); +SQLITE_API int SQLITE_STDCALL sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){ + /* EVIDENCE-OF: R-41613-20553 The sqlite3_wal_checkpoint(D,X) is equivalent to + ** sqlite3_wal_checkpoint_v2(D,X,SQLITE_CHECKPOINT_PASSIVE,0,0). */ + return sqlite3_wal_checkpoint_v2(db,zDb,SQLITE_CHECKPOINT_PASSIVE,0,0); } #ifndef SQLITE_OMIT_WAL /* ** Run a checkpoint on database iDb. This is a no-op if database iDb is @@ -118910,11 +129544,11 @@ /* ** Return UTF-8 encoded English language explanation of the most recent ** error. */ -SQLITE_API const char *sqlite3_errmsg(sqlite3 *db){ +SQLITE_API const char *SQLITE_STDCALL sqlite3_errmsg(sqlite3 *db){ const char *z; if( !db ){ return sqlite3ErrStr(SQLITE_NOMEM); } if( !sqlite3SafetyCheckSickOrOk(db) ){ @@ -118922,10 +129556,11 @@ } sqlite3_mutex_enter(db->mutex); if( db->mallocFailed ){ z = sqlite3ErrStr(SQLITE_NOMEM); }else{ + testcase( db->pErr==0 ); z = (char*)sqlite3_value_text(db->pErr); assert( !db->mallocFailed ); if( z==0 ){ z = sqlite3ErrStr(db->errCode); } @@ -118937,11 +129572,11 @@ #ifndef SQLITE_OMIT_UTF16 /* ** Return UTF-16 encoded English language explanation of the most recent ** error. */ -SQLITE_API const void *sqlite3_errmsg16(sqlite3 *db){ +SQLITE_API const void *SQLITE_STDCALL sqlite3_errmsg16(sqlite3 *db){ static const u16 outOfMem[] = { 'o', 'u', 't', ' ', 'o', 'f', ' ', 'm', 'e', 'm', 'o', 'r', 'y', 0 }; static const u16 misuse[] = { 'l', 'i', 'b', 'r', 'a', 'r', 'y', ' ', @@ -118963,12 +129598,11 @@ if( db->mallocFailed ){ z = (void *)outOfMem; }else{ z = sqlite3_value_text16(db->pErr); if( z==0 ){ - sqlite3ValueSetStr(db->pErr, -1, sqlite3ErrStr(db->errCode), - SQLITE_UTF8, SQLITE_STATIC); + sqlite3ErrorWithMsg(db, db->errCode, sqlite3ErrStr(db->errCode)); z = sqlite3_value_text16(db->pErr); } /* A malloc() may have failed within the call to sqlite3_value_text16() ** above. If this is the case, then the db->mallocFailed flag needs to ** be cleared before returning. Do this directly, instead of via @@ -118983,20 +129617,20 @@ /* ** Return the most recent error code generated by an SQLite routine. If NULL is ** passed to this function, we assume a malloc() failed during sqlite3_open(). */ -SQLITE_API int sqlite3_errcode(sqlite3 *db){ +SQLITE_API int SQLITE_STDCALL sqlite3_errcode(sqlite3 *db){ if( db && !sqlite3SafetyCheckSickOrOk(db) ){ return SQLITE_MISUSE_BKPT; } if( !db || db->mallocFailed ){ return SQLITE_NOMEM; } return db->errCode & db->errMask; } -SQLITE_API int sqlite3_extended_errcode(sqlite3 *db){ +SQLITE_API int SQLITE_STDCALL sqlite3_extended_errcode(sqlite3 *db){ if( db && !sqlite3SafetyCheckSickOrOk(db) ){ return SQLITE_MISUSE_BKPT; } if( !db || db->mallocFailed ){ return SQLITE_NOMEM; @@ -119007,11 +129641,11 @@ /* ** Return a string that describes the kind of error specified in the ** argument. For now, this simply calls the internal sqlite3ErrStr() ** function. */ -SQLITE_API const char *sqlite3_errstr(int rc){ +SQLITE_API const char *SQLITE_STDCALL sqlite3_errstr(int rc){ return sqlite3ErrStr(rc); } /* ** Create a new collating function for database "db". The name is zName @@ -119025,11 +129659,10 @@ int(*xCompare)(void*,int,const void*,int,const void*), void(*xDel)(void*) ){ CollSeq *pColl; int enc2; - int nName = sqlite3Strlen30(zName); assert( sqlite3_mutex_held(db->mutex) ); /* If SQLITE_UTF16 is specified as the encoding type, transform this ** to one of SQLITE_UTF16LE or SQLITE_UTF16BE using the @@ -119050,11 +129683,11 @@ ** are no active VMs, invalidate any pre-compiled statements. */ pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 0); if( pColl && pColl->xCmp ){ if( db->nVdbeActive ){ - sqlite3Error(db, SQLITE_BUSY, + sqlite3ErrorWithMsg(db, SQLITE_BUSY, "unable to delete/modify collation sequence due to active statements"); return SQLITE_BUSY; } sqlite3ExpirePreparedStatements(db); @@ -119063,11 +129696,11 @@ ** then any copies made by synthCollSeq() need to be invalidated. ** Also, collation destructor - CollSeq.xDel() - function may need ** to be called. */ if( (pColl->enc & ~SQLITE_UTF16_ALIGNED)==enc2 ){ - CollSeq *aColl = sqlite3HashFind(&db->aCollSeq, zName, nName); + CollSeq *aColl = sqlite3HashFind(&db->aCollSeq, zName); int j; for(j=0; j<3; j++){ CollSeq *p = &aColl[j]; if( p->enc==pColl->enc ){ if( p->xDel ){ @@ -119083,11 +129716,11 @@ if( pColl==0 ) return SQLITE_NOMEM; pColl->xCmp = xCompare; pColl->pUser = pCtx; pColl->xDel = xDel; pColl->enc = (u8)(enc2 | (enc & SQLITE_UTF16_ALIGNED)); - sqlite3Error(db, SQLITE_OK, 0); + sqlite3Error(db, SQLITE_OK); return SQLITE_OK; } /* @@ -119103,12 +129736,13 @@ SQLITE_MAX_COMPOUND_SELECT, SQLITE_MAX_VDBE_OP, SQLITE_MAX_FUNCTION_ARG, SQLITE_MAX_ATTACHED, SQLITE_MAX_LIKE_PATTERN_LENGTH, - SQLITE_MAX_VARIABLE_NUMBER, + SQLITE_MAX_VARIABLE_NUMBER, /* IMP: R-38091-32352 */ SQLITE_MAX_TRIGGER_DEPTH, + SQLITE_MAX_WORKER_THREADS, }; /* ** Make sure the hard limits are set to reasonable values */ @@ -119128,21 +129762,24 @@ # error SQLITE_MAX_VDBE_OP must be at least 40 #endif #if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>1000 # error SQLITE_MAX_FUNCTION_ARG must be between 0 and 1000 #endif -#if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>62 -# error SQLITE_MAX_ATTACHED must be between 0 and 62 +#if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125 +# error SQLITE_MAX_ATTACHED must be between 0 and 125 #endif #if SQLITE_MAX_LIKE_PATTERN_LENGTH<1 # error SQLITE_MAX_LIKE_PATTERN_LENGTH must be at least 1 #endif #if SQLITE_MAX_COLUMN>32767 # error SQLITE_MAX_COLUMN must not exceed 32767 #endif #if SQLITE_MAX_TRIGGER_DEPTH<1 # error SQLITE_MAX_TRIGGER_DEPTH must be at least 1 +#endif +#if SQLITE_MAX_WORKER_THREADS<0 || SQLITE_MAX_WORKER_THREADS>50 +# error SQLITE_MAX_WORKER_THREADS must be between 0 and 50 #endif /* ** Change the value of a limit. Report the old value. @@ -119152,13 +129789,19 @@ ** ** A new lower limit does not shrink existing constructs. ** It merely prevents new constructs that exceed the limit ** from forming. */ -SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ +SQLITE_API int SQLITE_STDCALL sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ int oldLimit; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return -1; + } +#endif /* EVIDENCE-OF: R-30189-54097 For each limit category SQLITE_LIMIT_NAME ** there is a hard upper bound set at compile-time by a C preprocessor ** macro called SQLITE_MAX_NAME. (The "_LIMIT_" in the name is changed to ** "_MAX_".) @@ -119173,11 +129816,12 @@ assert( aHardLimit[SQLITE_LIMIT_ATTACHED]==SQLITE_MAX_ATTACHED ); assert( aHardLimit[SQLITE_LIMIT_LIKE_PATTERN_LENGTH]== SQLITE_MAX_LIKE_PATTERN_LENGTH ); assert( aHardLimit[SQLITE_LIMIT_VARIABLE_NUMBER]==SQLITE_MAX_VARIABLE_NUMBER); assert( aHardLimit[SQLITE_LIMIT_TRIGGER_DEPTH]==SQLITE_MAX_TRIGGER_DEPTH ); - assert( SQLITE_LIMIT_TRIGGER_DEPTH==(SQLITE_N_LIMIT-1) ); + assert( aHardLimit[SQLITE_LIMIT_WORKER_THREADS]==SQLITE_MAX_WORKER_THREADS ); + assert( SQLITE_LIMIT_WORKER_THREADS==(SQLITE_N_LIMIT-1) ); if( limitId<0 || limitId>=SQLITE_N_LIMIT ){ return -1; } @@ -119230,12 +129874,13 @@ char c; int nUri = sqlite3Strlen30(zUri); assert( *pzErrMsg==0 ); - if( ((flags & SQLITE_OPEN_URI) || sqlite3GlobalConfig.bOpenUri) - && nUri>=5 && memcmp(zUri, "file:", 5)==0 + if( ((flags & SQLITE_OPEN_URI) /* IMP: R-48725-32206 */ + || sqlite3GlobalConfig.bOpenUri) /* IMP: R-51689-46548 */ + && nUri>=5 && memcmp(zUri, "file:", 5)==0 /* IMP: R-57884-37496 */ ){ char *zOpt; int eState; /* Parser state when parsing URI */ int iIn; /* Input character index */ int iOut = 0; /* Output character index */ @@ -119248,11 +129893,23 @@ for(iIn=0; iIn<nUri; iIn++) nByte += (zUri[iIn]=='&'); zFile = sqlite3_malloc(nByte); if( !zFile ) return SQLITE_NOMEM; iIn = 5; -#ifndef SQLITE_ALLOW_URI_AUTHORITY +#ifdef SQLITE_ALLOW_URI_AUTHORITY + if( strncmp(zUri+5, "///", 3)==0 ){ + iIn = 7; + /* The following condition causes URIs with five leading / characters + ** like file://///host/path to be converted into UNCs like //host/path. + ** The correct URI for that UNC has only two or four leading / characters + ** file://host/path or file:////host/path. But 5 leading slashes is a + ** common error, we are told, so we handle it as a special case. */ + if( strncmp(zUri+7, "///", 3)==0 ){ iIn++; } + }else if( strncmp(zUri+5, "//localhost/", 12)==0 ){ + iIn = 16; + } +#else /* Discard the scheme and authority segments of the URI. */ if( zUri[5]=='/' && zUri[6]=='/' ){ iIn = 7; while( zUri[iIn] && zUri[iIn]!='/' ) iIn++; if( iIn!=7 && (iIn!=16 || memcmp("localhost", &zUri[7], 9)) ){ @@ -119439,10 +130096,13 @@ int rc; /* Return code */ int isThreadsafe; /* True for threadsafe connections */ char *zOpen = 0; /* Filename argument to pass to BtreeOpen() */ char *zErrMsg = 0; /* Error message from sqlite3ParseUri() */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppDb==0 ) return SQLITE_MISUSE_BKPT; +#endif *ppDb = 0; #ifndef SQLITE_OMIT_AUTOINIT rc = sqlite3_initialize(); if( rc ) return rc; #endif @@ -119461,11 +130121,13 @@ assert( SQLITE_OPEN_READWRITE == 0x02 ); assert( SQLITE_OPEN_CREATE == 0x04 ); testcase( (1<<(flags&7))==0x02 ); /* READONLY */ testcase( (1<<(flags&7))==0x04 ); /* READWRITE */ testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */ - if( ((1<<(flags&7)) & 0x46)==0 ) return SQLITE_MISUSE_BKPT; + if( ((1<<(flags&7)) & 0x46)==0 ){ + return SQLITE_MISUSE_BKPT; /* IMP: R-65497-44594 */ + } if( sqlite3GlobalConfig.bCoreMutex==0 ){ isThreadsafe = 0; }else if( flags & SQLITE_OPEN_NOMUTEX ){ isThreadsafe = 0; @@ -119520,18 +130182,23 @@ db->magic = SQLITE_MAGIC_BUSY; db->aDb = db->aDbStatic; assert( sizeof(db->aLimit)==sizeof(aHardLimit) ); memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit)); + db->aLimit[SQLITE_LIMIT_WORKER_THREADS] = SQLITE_DEFAULT_WORKER_THREADS; db->autoCommit = 1; db->nextAutovac = -1; db->szMmap = sqlite3GlobalConfig.szMmap; db->nextPagesize = 0; + db->nMaxSorterMmap = 0x7FFFFFFF; db->flags |= SQLITE_ShortColNames | SQLITE_EnableTrigger | SQLITE_CacheSpill #if !defined(SQLITE_DEFAULT_AUTOMATIC_INDEX) || SQLITE_DEFAULT_AUTOMATIC_INDEX | SQLITE_AutoIndex #endif +#if SQLITE_DEFAULT_CKPTFULLFSYNC + | SQLITE_CkptFullFSync +#endif #if SQLITE_DEFAULT_FILE_FORMAT<4 | SQLITE_LegacyFileFmt #endif #ifdef SQLITE_ENABLE_LOAD_EXTENSION | SQLITE_LoadExtension @@ -119540,39 +130207,46 @@ | SQLITE_RecTriggers #endif #if defined(SQLITE_DEFAULT_FOREIGN_KEYS) && SQLITE_DEFAULT_FOREIGN_KEYS | SQLITE_ForeignKeys #endif +#if defined(SQLITE_REVERSE_UNORDERED_SELECTS) + | SQLITE_ReverseOrder +#endif ; sqlite3HashInit(&db->aCollSeq); #ifndef SQLITE_OMIT_VIRTUALTABLE sqlite3HashInit(&db->aModule); #endif /* Add the default collation sequence BINARY. BINARY works for both UTF-8 ** and UTF-16, so add a version for each to avoid any unnecessary ** conversions. The only error that can occur here is a malloc() failure. + ** + ** EVIDENCE-OF: R-52786-44878 SQLite defines three built-in collating + ** functions: */ createCollation(db, "BINARY", SQLITE_UTF8, 0, binCollFunc, 0); createCollation(db, "BINARY", SQLITE_UTF16BE, 0, binCollFunc, 0); createCollation(db, "BINARY", SQLITE_UTF16LE, 0, binCollFunc, 0); + createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0); createCollation(db, "RTRIM", SQLITE_UTF8, (void*)1, binCollFunc, 0); if( db->mallocFailed ){ goto opendb_out; } + /* EVIDENCE-OF: R-08308-17224 The default collating function for all + ** strings is BINARY. + */ db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "BINARY", 0); assert( db->pDfltColl!=0 ); - /* Also add a UTF-8 case-insensitive collation sequence. */ - createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0); - /* Parse the filename/URI argument. */ db->openFlags = flags; rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg); if( rc!=SQLITE_OK ){ if( rc==SQLITE_NOMEM ) db->mallocFailed = 1; - sqlite3Error(db, rc, zErrMsg ? "%s" : 0, zErrMsg); + sqlite3ErrorWithMsg(db, rc, zErrMsg ? "%s" : 0, zErrMsg); sqlite3_free(zErrMsg); goto opendb_out; } /* Open the backend database driver */ @@ -119580,16 +130254,18 @@ flags | SQLITE_OPEN_MAIN_DB); if( rc!=SQLITE_OK ){ if( rc==SQLITE_IOERR_NOMEM ){ rc = SQLITE_NOMEM; } - sqlite3Error(db, rc, 0); + sqlite3Error(db, rc); goto opendb_out; } + sqlite3BtreeEnter(db->aDb[0].pBt); db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt); + if( !db->mallocFailed ) ENC(db) = SCHEMA_ENC(db); + sqlite3BtreeLeave(db->aDb[0].pBt); db->aDb[1].pSchema = sqlite3SchemaGet(db, 0); - /* The default safety_level for the main database is 'full'; for the temp ** database it is 'NONE'. This matches the pager layer defaults. */ db->aDb[0].zName = "main"; @@ -119604,11 +130280,11 @@ /* Register all built-in functions, but do not attempt to read the ** database schema yet. This is delayed until the first time the database ** is accessed. */ - sqlite3Error(db, SQLITE_OK, 0); + sqlite3Error(db, SQLITE_OK); sqlite3RegisterBuiltinFunctions(db); /* Load automatic extensions - extensions that have been registered ** using the sqlite3_automatic_extension() API. */ @@ -119651,21 +130327,21 @@ if( !db->mallocFailed && rc==SQLITE_OK){ rc = sqlite3RtreeInit(db); } #endif - sqlite3Error(db, rc, 0); - /* -DSQLITE_DEFAULT_LOCKING_MODE=1 makes EXCLUSIVE the default locking ** mode. -DSQLITE_DEFAULT_LOCKING_MODE=0 make NORMAL the default locking ** mode. Doing nothing at all also makes NORMAL the default. */ #ifdef SQLITE_DEFAULT_LOCKING_MODE db->dfltLockMode = SQLITE_DEFAULT_LOCKING_MODE; sqlite3PagerLockingMode(sqlite3BtreePager(db->aDb[0].pBt), SQLITE_DEFAULT_LOCKING_MODE); #endif + + if( rc ) sqlite3Error(db, rc); /* Enable the lookaside-malloc subsystem */ setupLookaside(db, 0, sqlite3GlobalConfig.szLookaside, sqlite3GlobalConfig.nLookaside); @@ -119672,11 +130348,12 @@ sqlite3_wal_autocheckpoint(db, SQLITE_DEFAULT_WAL_AUTOCHECKPOINT); opendb_out: sqlite3_free(zOpen); if( db ){ - assert( db->mutex!=0 || isThreadsafe==0 || sqlite3GlobalConfig.bFullMutex==0 ); + assert( db->mutex!=0 || isThreadsafe==0 + || sqlite3GlobalConfig.bFullMutex==0 ); sqlite3_mutex_leave(db->mutex); } rc = sqlite3_errcode(db); assert( db!=0 || rc==SQLITE_NOMEM ); if( rc==SQLITE_NOMEM ){ @@ -119697,18 +130374,18 @@ } /* ** Open a new database handle. */ -SQLITE_API int sqlite3_open( +SQLITE_API int SQLITE_STDCALL sqlite3_open( const char *zFilename, sqlite3 **ppDb ){ return openDatabase(zFilename, ppDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); } -SQLITE_API int sqlite3_open_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_open_v2( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb, /* OUT: SQLite db handle */ int flags, /* Flags */ const char *zVfs /* Name of VFS module to use */ ){ @@ -119717,34 +130394,36 @@ #ifndef SQLITE_OMIT_UTF16 /* ** Open a new database handle. */ -SQLITE_API int sqlite3_open16( +SQLITE_API int SQLITE_STDCALL sqlite3_open16( const void *zFilename, sqlite3 **ppDb ){ char const *zFilename8; /* zFilename encoded in UTF-8 instead of UTF-16 */ sqlite3_value *pVal; int rc; - assert( zFilename ); - assert( ppDb ); +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppDb==0 ) return SQLITE_MISUSE_BKPT; +#endif *ppDb = 0; #ifndef SQLITE_OMIT_AUTOINIT rc = sqlite3_initialize(); if( rc ) return rc; #endif + if( zFilename==0 ) zFilename = "\000\000"; pVal = sqlite3ValueNew(0); sqlite3ValueSetStr(pVal, -1, zFilename, SQLITE_UTF16NATIVE, SQLITE_STATIC); zFilename8 = sqlite3ValueText(pVal, SQLITE_UTF8); if( zFilename8 ){ rc = openDatabase(zFilename8, ppDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); assert( *ppDb || rc==SQLITE_NOMEM ); if( rc==SQLITE_OK && !DbHasProperty(*ppDb, 0, DB_SchemaLoaded) ){ - ENC(*ppDb) = SQLITE_UTF16NATIVE; + SCHEMA_ENC(*ppDb) = ENC(*ppDb) = SQLITE_UTF16NATIVE; } }else{ rc = SQLITE_NOMEM; } sqlite3ValueFree(pVal); @@ -119754,38 +130433,36 @@ #endif /* SQLITE_OMIT_UTF16 */ /* ** Register a new collation sequence with the database handle db. */ -SQLITE_API int sqlite3_create_collation( +SQLITE_API int SQLITE_STDCALL sqlite3_create_collation( sqlite3* db, const char *zName, int enc, void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*) ){ - int rc; - sqlite3_mutex_enter(db->mutex); - assert( !db->mallocFailed ); - rc = createCollation(db, zName, (u8)enc, pCtx, xCompare, 0); - rc = sqlite3ApiExit(db, rc); - sqlite3_mutex_leave(db->mutex); - return rc; + return sqlite3_create_collation_v2(db, zName, enc, pCtx, xCompare, 0); } /* ** Register a new collation sequence with the database handle db. */ -SQLITE_API int sqlite3_create_collation_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_create_collation_v2( sqlite3* db, const char *zName, int enc, void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*), void(*xDel)(void*) ){ int rc; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); assert( !db->mallocFailed ); rc = createCollation(db, zName, (u8)enc, pCtx, xCompare, xDel); rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); @@ -119794,19 +130471,23 @@ #ifndef SQLITE_OMIT_UTF16 /* ** Register a new collation sequence with the database handle db. */ -SQLITE_API int sqlite3_create_collation16( +SQLITE_API int SQLITE_STDCALL sqlite3_create_collation16( sqlite3* db, const void *zName, int enc, void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*) ){ int rc = SQLITE_OK; char *zName8; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); assert( !db->mallocFailed ); zName8 = sqlite3Utf16to8(db, zName, -1, SQLITE_UTF16NATIVE); if( zName8 ){ rc = createCollation(db, zName8, (u8)enc, pCtx, xCompare, 0); @@ -119820,15 +130501,18 @@ /* ** Register a collation sequence factory callback with the database handle ** db. Replace any previously installed collation sequence factory. */ -SQLITE_API int sqlite3_collation_needed( +SQLITE_API int SQLITE_STDCALL sqlite3_collation_needed( sqlite3 *db, void *pCollNeededArg, void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*) ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); db->xCollNeeded = xCollNeeded; db->xCollNeeded16 = 0; db->pCollNeededArg = pCollNeededArg; sqlite3_mutex_leave(db->mutex); @@ -119838,15 +130522,18 @@ #ifndef SQLITE_OMIT_UTF16 /* ** Register a collation sequence factory callback with the database handle ** db. Replace any previously installed collation sequence factory. */ -SQLITE_API int sqlite3_collation_needed16( +SQLITE_API int SQLITE_STDCALL sqlite3_collation_needed16( sqlite3 *db, void *pCollNeededArg, void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*) ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); db->xCollNeeded = 0; db->xCollNeeded16 = xCollNeeded16; db->pCollNeededArg = pCollNeededArg; sqlite3_mutex_leave(db->mutex); @@ -119857,11 +130544,11 @@ #ifndef SQLITE_OMIT_DEPRECATED /* ** This function is now an anachronism. It used to be used to recover from a ** malloc() failure, but SQLite now does this automatically. */ -SQLITE_API int sqlite3_global_recover(void){ +SQLITE_API int SQLITE_STDCALL sqlite3_global_recover(void){ return SQLITE_OK; } #endif /* @@ -119868,18 +130555,24 @@ ** Test to see whether or not the database connection is in autocommit ** mode. Return TRUE if it is and FALSE if not. Autocommit mode is on ** by default. Autocommit is disabled by a BEGIN statement and reenabled ** by the next COMMIT or ROLLBACK. */ -SQLITE_API int sqlite3_get_autocommit(sqlite3 *db){ +SQLITE_API int SQLITE_STDCALL sqlite3_get_autocommit(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return db->autoCommit; } /* -** The following routines are subtitutes for constants SQLITE_CORRUPT, +** The following routines are substitutes for constants SQLITE_CORRUPT, ** SQLITE_MISUSE, SQLITE_CANTOPEN, SQLITE_IOERR and possibly other error -** constants. They server two purposes: +** constants. They serve two purposes: ** ** 1. Serve as a convenient place to set a breakpoint in a debugger ** to detect when version error conditions occurs. ** ** 2. Invoke sqlite3_log() to provide the source code location where @@ -119914,20 +130607,19 @@ ** data for this thread has been deallocated. ** ** SQLite no longer uses thread-specific data so this routine is now a ** no-op. It is retained for historical compatibility. */ -SQLITE_API void sqlite3_thread_cleanup(void){ +SQLITE_API void SQLITE_STDCALL sqlite3_thread_cleanup(void){ } #endif /* ** Return meta information about a specific column of a database table. ** See comment in sqlite3.h (sqlite.h.in) for details. */ -#ifdef SQLITE_ENABLE_COLUMN_METADATA -SQLITE_API int sqlite3_table_column_metadata( +SQLITE_API int SQLITE_STDCALL sqlite3_table_column_metadata( sqlite3 *db, /* Connection handle */ const char *zDbName, /* Database name or NULL */ const char *zTableName, /* Table name */ const char *zColumnName, /* Column name */ char const **pzDataType, /* OUTPUT: Declared data type */ @@ -119938,18 +130630,24 @@ ){ int rc; char *zErrMsg = 0; Table *pTab = 0; Column *pCol = 0; - int iCol; - + int iCol = 0; char const *zDataType = 0; char const *zCollSeq = 0; int notnull = 0; int primarykey = 0; int autoinc = 0; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zTableName==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + /* Ensure the database schema has been loaded */ sqlite3_mutex_enter(db->mutex); sqlite3BtreeEnterAll(db); rc = sqlite3Init(db, &zErrMsg); if( SQLITE_OK!=rc ){ @@ -119962,25 +130660,27 @@ pTab = 0; goto error_out; } /* Find the column for which info is requested */ - if( sqlite3IsRowid(zColumnName) ){ - iCol = pTab->iPKey; - if( iCol>=0 ){ - pCol = &pTab->aCol[iCol]; - } + if( zColumnName==0 ){ + /* Query for existance of table only */ }else{ for(iCol=0; iCol<pTab->nCol; iCol++){ pCol = &pTab->aCol[iCol]; if( 0==sqlite3StrICmp(pCol->zName, zColumnName) ){ break; } } if( iCol==pTab->nCol ){ - pTab = 0; - goto error_out; + if( HasRowid(pTab) && sqlite3IsRowid(zColumnName) ){ + iCol = pTab->iPKey; + pCol = iCol>=0 ? &pTab->aCol[iCol] : 0; + }else{ + pTab = 0; + goto error_out; + } } } /* The following block stores the meta information that will be returned ** to the caller in local variables zDataType, zCollSeq, notnull, primarykey @@ -120023,22 +130723,21 @@ sqlite3DbFree(db, zErrMsg); zErrMsg = sqlite3MPrintf(db, "no such table column: %s.%s", zTableName, zColumnName); rc = SQLITE_ERROR; } - sqlite3Error(db, rc, (zErrMsg?"%s":0), zErrMsg); + sqlite3ErrorWithMsg(db, rc, (zErrMsg?"%s":0), zErrMsg); sqlite3DbFree(db, zErrMsg); rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; } -#endif /* ** Sleep for a little while. Return the amount of time slept. */ -SQLITE_API int sqlite3_sleep(int ms){ +SQLITE_API int SQLITE_STDCALL sqlite3_sleep(int ms){ sqlite3_vfs *pVfs; int rc; pVfs = sqlite3_vfs_find(0); if( pVfs==0 ) return 0; @@ -120050,24 +130749,30 @@ } /* ** Enable or disable the extended result codes. */ -SQLITE_API int sqlite3_extended_result_codes(sqlite3 *db, int onoff){ +SQLITE_API int SQLITE_STDCALL sqlite3_extended_result_codes(sqlite3 *db, int onoff){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); db->errMask = onoff ? 0xffffffff : 0xff; sqlite3_mutex_leave(db->mutex); return SQLITE_OK; } /* ** Invoke the xFileControl method on a particular database. */ -SQLITE_API int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){ +SQLITE_API int SQLITE_STDCALL sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){ int rc = SQLITE_ERROR; Btree *pBtree; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); pBtree = sqlite3DbNameToBtree(db, zDbName); if( pBtree ){ Pager *pPager; sqlite3_file *fd; @@ -120085,17 +130790,17 @@ rc = SQLITE_NOTFOUND; } sqlite3BtreeLeave(pBtree); } sqlite3_mutex_leave(db->mutex); - return rc; + return rc; } /* ** Interface to the testing logic. */ -SQLITE_API int sqlite3_test_control(int op, ...){ +SQLITE_API int SQLITE_CDECL sqlite3_test_control(int op, ...){ int rc = 0; #ifndef SQLITE_OMIT_BUILTIN_TEST va_list ap; va_start(ap, op); switch( op ){ @@ -120122,11 +130827,11 @@ ** Reset the PRNG back to its uninitialized state. The next call ** to sqlite3_randomness() will reseed the PRNG using a single call ** to the xRandomness method of the default VFS. */ case SQLITE_TESTCTRL_PRNG_RESET: { - sqlite3PrngResetState(); + sqlite3_randomness(0,0); break; } /* ** sqlite3_test_control(BITVEC_TEST, size, program) @@ -120140,10 +130845,32 @@ int sz = va_arg(ap, int); int *aProg = va_arg(ap, int*); rc = sqlite3BitvecBuiltinTest(sz, aProg); break; } + + /* + ** sqlite3_test_control(FAULT_INSTALL, xCallback) + ** + ** Arrange to invoke xCallback() whenever sqlite3FaultSim() is called, + ** if xCallback is not NULL. + ** + ** As a test of the fault simulator mechanism itself, sqlite3FaultSim(0) + ** is called immediately after installing the new callback and the return + ** value from sqlite3FaultSim(0) becomes the return from + ** sqlite3_test_control(). + */ + case SQLITE_TESTCTRL_FAULT_INSTALL: { + /* MSVC is picky about pulling func ptrs from va lists. + ** http://support.microsoft.com/kb/47961 + ** sqlite3GlobalConfig.xTestCallback = va_arg(ap, int(*)(int)); + */ + typedef int(*TESTCALLBACKFUNC_t)(int); + sqlite3GlobalConfig.xTestCallback = va_arg(ap, TESTCALLBACKFUNC_t); + rc = sqlite3FaultSim(0); + break; + } /* ** sqlite3_test_control(BENIGN_MALLOC_HOOKS, xBegin, xEnd) ** ** Register hooks to call to indicate which malloc() failures @@ -120167,11 +130894,11 @@ ** as it existing before this routine was called. ** ** IMPORTANT: Changing the PENDING byte from 0x40000000 results in ** an incompatible database file format. Changing the PENDING byte ** while any database connection is open results in undefined and - ** dileterious behavior. + ** deleterious behavior. */ case SQLITE_TESTCTRL_PENDING_BYTE: { rc = PENDING_BYTE; #ifndef SQLITE_OMIT_WSD { @@ -120231,10 +130958,26 @@ case SQLITE_TESTCTRL_ALWAYS: { int x = va_arg(ap,int); rc = ALWAYS(x); break; } + + /* + ** sqlite3_test_control(SQLITE_TESTCTRL_BYTEORDER); + ** + ** The integer returned reveals the byte-order of the computer on which + ** SQLite is running: + ** + ** 1 big-endian, determined at run-time + ** 10 little-endian, determined at run-time + ** 432101 big-endian, determined at compile-time + ** 123410 little-endian, determined at compile-time + */ + case SQLITE_TESTCTRL_BYTEORDER: { + rc = SQLITE_BYTEORDER*100 + SQLITE_LITTLEENDIAN*10 + SQLITE_BIGENDIAN; + break; + } /* sqlite3_test_control(SQLITE_TESTCTRL_RESERVE, sqlite3 *db, int N) ** ** Set the nReserve size to N for the main database on the database ** connection db. @@ -120306,26 +131049,83 @@ case SQLITE_TESTCTRL_LOCALTIME_FAULT: { sqlite3GlobalConfig.bLocaltimeFault = va_arg(ap, int); break; } -#if defined(SQLITE_ENABLE_TREE_EXPLAIN) - /* sqlite3_test_control(SQLITE_TESTCTRL_EXPLAIN_STMT, - ** sqlite3_stmt*,const char**); + /* sqlite3_test_control(SQLITE_TESTCTRL_NEVER_CORRUPT, int); + ** + ** Set or clear a flag that indicates that the database file is always well- + ** formed and never corrupt. This flag is clear by default, indicating that + ** database files might have arbitrary corruption. Setting the flag during + ** testing causes certain assert() statements in the code to be activated + ** that demonstrat invariants on well-formed database files. + */ + case SQLITE_TESTCTRL_NEVER_CORRUPT: { + sqlite3GlobalConfig.neverCorrupt = va_arg(ap, int); + break; + } + + + /* sqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE, xCallback, ptr); + ** + ** Set the VDBE coverage callback function to xCallback with context + ** pointer ptr. + */ + case SQLITE_TESTCTRL_VDBE_COVERAGE: { +#ifdef SQLITE_VDBE_COVERAGE + typedef void (*branch_callback)(void*,int,u8,u8); + sqlite3GlobalConfig.xVdbeBranch = va_arg(ap,branch_callback); + sqlite3GlobalConfig.pVdbeBranchArg = va_arg(ap,void*); +#endif + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_SORTER_MMAP, db, nMax); */ + case SQLITE_TESTCTRL_SORTER_MMAP: { + sqlite3 *db = va_arg(ap, sqlite3*); + db->nMaxSorterMmap = va_arg(ap, int); + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_ISINIT); + ** + ** Return SQLITE_OK if SQLite has been initialized and SQLITE_ERROR if + ** not. + */ + case SQLITE_TESTCTRL_ISINIT: { + if( sqlite3GlobalConfig.isInit==0 ) rc = SQLITE_ERROR; + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, onOff, tnum); + ** + ** This test control is used to create imposter tables. "db" is a pointer + ** to the database connection. dbName is the database name (ex: "main" or + ** "temp") which will receive the imposter. "onOff" turns imposter mode on + ** or off. "tnum" is the root page of the b-tree to which the imposter + ** table should connect. + ** + ** Enable imposter mode only when the schema has already been parsed. Then + ** run a single CREATE TABLE statement to construct the imposter table in + ** the parsed schema. Then turn imposter mode back off again. ** - ** If compiled with SQLITE_ENABLE_TREE_EXPLAIN, each sqlite3_stmt holds - ** a string that describes the optimized parse tree. This test-control - ** returns a pointer to that string. + ** If onOff==0 and tnum>0 then reset the schema for all databases, causing + ** the schema to be reparsed the next time it is needed. This has the + ** effect of erasing all imposter tables. */ - case SQLITE_TESTCTRL_EXPLAIN_STMT: { - sqlite3_stmt *pStmt = va_arg(ap, sqlite3_stmt*); - const char **pzRet = va_arg(ap, const char**); - *pzRet = sqlite3VdbeExplanation((Vdbe*)pStmt); + case SQLITE_TESTCTRL_IMPOSTER: { + sqlite3 *db = va_arg(ap, sqlite3*); + sqlite3_mutex_enter(db->mutex); + db->init.iDb = sqlite3FindDbName(db, va_arg(ap,const char*)); + db->init.busy = db->init.imposterTable = va_arg(ap,int); + db->init.newTnum = va_arg(ap,int); + if( db->init.busy==0 && db->init.newTnum>0 ){ + sqlite3ResetAllSchemasOfConnection(db); + } + sqlite3_mutex_leave(db->mutex); break; } -#endif - } va_end(ap); #endif /* SQLITE_OMIT_BUILTIN_TEST */ return rc; } @@ -120339,12 +131139,12 @@ ** method of a VFS implementation. The zParam argument is the name of the ** query parameter we seek. This routine returns the value of the zParam ** parameter if it exists. If the parameter does not exist, this routine ** returns a NULL pointer. */ -SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam){ - if( zFilename==0 ) return 0; +SQLITE_API const char *SQLITE_STDCALL sqlite3_uri_parameter(const char *zFilename, const char *zParam){ + if( zFilename==0 || zParam==0 ) return 0; zFilename += sqlite3Strlen30(zFilename) + 1; while( zFilename[0] ){ int x = strcmp(zFilename, zParam); zFilename += sqlite3Strlen30(zFilename) + 1; if( x==0 ) return zFilename; @@ -120354,27 +131154,27 @@ } /* ** Return a boolean value for a query parameter. */ -SQLITE_API int sqlite3_uri_boolean(const char *zFilename, const char *zParam, int bDflt){ +SQLITE_API int SQLITE_STDCALL sqlite3_uri_boolean(const char *zFilename, const char *zParam, int bDflt){ const char *z = sqlite3_uri_parameter(zFilename, zParam); bDflt = bDflt!=0; return z ? sqlite3GetBoolean(z, bDflt) : bDflt; } /* ** Return a 64-bit integer value for a query parameter. */ -SQLITE_API sqlite3_int64 sqlite3_uri_int64( +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_uri_int64( const char *zFilename, /* Filename as passed to xOpen */ const char *zParam, /* URI parameter sought */ sqlite3_int64 bDflt /* return if parameter is missing */ ){ const char *z = sqlite3_uri_parameter(zFilename, zParam); sqlite3_int64 v; - if( z && sqlite3Atoi64(z, &v, sqlite3Strlen30(z), SQLITE_UTF8)==SQLITE_OK ){ + if( z && sqlite3DecOrHexToI64(z, &v)==SQLITE_OK ){ bDflt = v; } return bDflt; } @@ -120395,22 +131195,36 @@ /* ** Return the filename of the database associated with a database ** connection. */ -SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName){ - Btree *pBt = sqlite3DbNameToBtree(db, zDbName); +SQLITE_API const char *SQLITE_STDCALL sqlite3_db_filename(sqlite3 *db, const char *zDbName){ + Btree *pBt; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + pBt = sqlite3DbNameToBtree(db, zDbName); return pBt ? sqlite3BtreeGetFilename(pBt) : 0; } /* ** Return 1 if database is read-only or 0 if read/write. Return -1 if ** no such database exists. */ -SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName){ - Btree *pBt = sqlite3DbNameToBtree(db, zDbName); - return pBt ? sqlite3PagerIsreadonly(sqlite3BtreePager(pBt)) : -1; +SQLITE_API int SQLITE_STDCALL sqlite3_db_readonly(sqlite3 *db, const char *zDbName){ + Btree *pBt; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return -1; + } +#endif + pBt = sqlite3DbNameToBtree(db, zDbName); + return pBt ? sqlite3BtreeIsReadonly(pBt) : -1; } /************** End of main.c ************************************************/ /************** Begin file notify.c ******************************************/ /* @@ -120556,11 +131370,11 @@ ** ** Each call to this routine overrides any prior callbacks registered ** on the same "db". If xNotify==0 then any prior callbacks are immediately ** cancelled. */ -SQLITE_API int sqlite3_unlock_notify( +SQLITE_API int SQLITE_STDCALL sqlite3_unlock_notify( sqlite3 *db, void (*xNotify)(void **, int), void *pArg ){ int rc = SQLITE_OK; @@ -120595,11 +131409,11 @@ } } leaveMutex(); assert( !db->mallocFailed ); - sqlite3Error(db, rc, (rc?"database is deadlocked":0)); + sqlite3ErrorWithMsg(db, rc, (rc?"database is deadlocked":0)); sqlite3_mutex_leave(db->mutex); return rc; } /* @@ -121526,24 +132340,24 @@ char **azColumn; /* column names. malloced */ u8 *abNotindexed; /* True for 'notindexed' columns */ sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */ char *zContentTbl; /* content=xxx option, or NULL */ char *zLanguageid; /* languageid=xxx option, or NULL */ - u8 bAutoincrmerge; /* True if automerge=1 */ + int nAutoincrmerge; /* Value configured by 'automerge' */ u32 nLeafAdd; /* Number of leaf blocks added this trans */ /* Precompiled statements used by the implementation. Each of these ** statements is run and reset within a single virtual table API call. */ - sqlite3_stmt *aStmt[37]; + sqlite3_stmt *aStmt[40]; char *zReadExprlist; char *zWriteExprlist; int nNodeSize; /* Soft limit for node size */ u8 bFts4; /* True for FTS4, false for FTS3 */ - u8 bHasStat; /* True if %_stat table exists */ + u8 bHasStat; /* True if %_stat table exists (2==unknown) */ u8 bHasDocsize; /* True if %_docsize table exists */ u8 bDescIdx; /* True if doclists are in reverse order */ u8 bIgnoreSavepoint; /* True to ignore xSavepoint invocations */ int nPgsz; /* Page size for host database */ char *zSegmentsTbl; /* Name of %_segments table */ @@ -121583,10 +132397,16 @@ ** verifying the operation of the SQLite core. */ int inTransaction; /* True after xBegin but before xCommit/xRollback */ int mxSavepoint; /* Largest valid xSavepoint integer */ #endif + +#ifdef SQLITE_TEST + /* True to disable the incremental doclist optimization. This is controled + ** by special insert command 'test-no-incr-doclist'. */ + int bNoIncrDoclist; +#endif }; /* ** When the core wants to read from the virtual table, it creates a ** virtual table cursor (an instance of the following structure) using @@ -121608,11 +132428,12 @@ int nDoclist; /* Size of buffer at aDoclist */ u8 bDesc; /* True to sort in descending order */ int eEvalmode; /* An FTS3_EVAL_XX constant */ int nRowAvg; /* Average size of database rows, in pages */ sqlite3_int64 nDoc; /* Documents in table */ - + i64 iMinDocid; /* Minimum docid to return */ + i64 iMaxDocid; /* Maximum docid to return */ int isMatchinfoNeeded; /* True when aMatchinfo[] needs filling in */ u32 *aMatchinfo; /* Information about most recent match */ int nMatchinfo; /* Number of elements in aMatchinfo[] */ char *zMatchinfo; /* Matchinfo specification */ }; @@ -121638,10 +132459,19 @@ */ #define FTS3_FULLSCAN_SEARCH 0 /* Linear scan of %_content table */ #define FTS3_DOCID_SEARCH 1 /* Lookup by rowid on %_content table */ #define FTS3_FULLTEXT_SEARCH 2 /* Full-text index search */ +/* +** The lower 16-bits of the sqlite3_index_info.idxNum value set by +** the xBestIndex() method contains the Fts3Cursor.eSearch value described +** above. The upper 16-bits contain a combination of the following +** bits, used to describe extra constraints on full-text searches. +*/ +#define FTS3_HAVE_LANGID 0x00010000 /* languageid=? */ +#define FTS3_HAVE_DOCID_GE 0x00020000 /* docid>=? */ +#define FTS3_HAVE_DOCID_LE 0x00040000 /* docid<=? */ struct Fts3Doclist { char *aAll; /* Array containing doclist (or NULL) */ int nAll; /* Size of a[] in bytes */ char *pNextDocid; /* Pointer to next docid */ @@ -121674,10 +132504,15 @@ struct Fts3Phrase { /* Cache of doclist for this phrase. */ Fts3Doclist doclist; int bIncr; /* True if doclist is loaded incrementally */ int iDoclistToken; + + /* Used by sqlite3Fts3EvalPhrasePoslist() if this is a descendent of an + ** OR condition. */ + char *pOrPoslist; + i64 iOrDocid; /* Variables below this point are populated by fts3_expr.c when parsing ** a MATCH expression. Everything above is part of the evaluation phase. */ int nToken; /* Number of tokens in the phrase */ @@ -121823,10 +132658,14 @@ char *aDoclist; /* Pointer to doclist buffer */ int nDoclist; /* Size of aDoclist[] in bytes */ }; SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table*,int,int); + +#define fts3GetVarint32(p, piVal) ( \ + (*(u8*)(p)&0x80) ? sqlite3Fts3GetVarint32(p, piVal) : (*piVal=*(u8*)(p), 1) \ +) /* fts3.c */ SQLITE_PRIVATE int sqlite3Fts3PutVarint(char *, sqlite3_int64); SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *, sqlite_int64 *); SQLITE_PRIVATE int sqlite3Fts3GetVarint32(const char *, int *); @@ -121881,11 +132720,11 @@ /* fts3_tokenize_vtab.c */ SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3*, Fts3Hash *); /* fts3_unicode2.c (functions generated by parsing unicode text files) */ -#ifdef SQLITE_ENABLE_FTS4_UNICODE61 +#ifndef SQLITE_DISABLE_FTS3_UNICODE SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int, int); SQLITE_PRIVATE int sqlite3FtsUnicodeIsalnum(int); SQLITE_PRIVATE int sqlite3FtsUnicodeIsdiacritic(int); #endif @@ -121931,36 +132770,63 @@ q[-1] &= 0x7f; /* turn off high bit in final byte */ assert( q - (unsigned char *)p <= FTS3_VARINT_MAX ); return (int) (q - (unsigned char *)p); } +#define GETVARINT_STEP(v, ptr, shift, mask1, mask2, var, ret) \ + v = (v & mask1) | ( (*ptr++) << shift ); \ + if( (v & mask2)==0 ){ var = v; return ret; } +#define GETVARINT_INIT(v, ptr, shift, mask1, mask2, var, ret) \ + v = (*ptr++); \ + if( (v & mask2)==0 ){ var = v; return ret; } + /* ** Read a 64-bit variable-length integer from memory starting at p[0]. ** Return the number of bytes read, or 0 on error. ** The value is stored in *v. */ SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *p, sqlite_int64 *v){ - const unsigned char *q = (const unsigned char *) p; - sqlite_uint64 x = 0, y = 1; - while( (*q&0x80)==0x80 && q-(unsigned char *)p<FTS3_VARINT_MAX ){ - x += y * (*q++ & 0x7f); - y <<= 7; - } - x += y * (*q++); - *v = (sqlite_int64) x; - return (int) (q - (unsigned char *)p); + const char *pStart = p; + u32 a; + u64 b; + int shift; + + GETVARINT_INIT(a, p, 0, 0x00, 0x80, *v, 1); + GETVARINT_STEP(a, p, 7, 0x7F, 0x4000, *v, 2); + GETVARINT_STEP(a, p, 14, 0x3FFF, 0x200000, *v, 3); + GETVARINT_STEP(a, p, 21, 0x1FFFFF, 0x10000000, *v, 4); + b = (a & 0x0FFFFFFF ); + + for(shift=28; shift<=63; shift+=7){ + u64 c = *p++; + b += (c&0x7F) << shift; + if( (c & 0x80)==0 ) break; + } + *v = b; + return (int)(p - pStart); } /* ** Similar to sqlite3Fts3GetVarint(), except that the output is truncated to a ** 32-bit integer before it is returned. */ SQLITE_PRIVATE int sqlite3Fts3GetVarint32(const char *p, int *pi){ - sqlite_int64 i; - int ret = sqlite3Fts3GetVarint(p, &i); - *pi = (int) i; - return ret; + u32 a; + +#ifndef fts3GetVarint32 + GETVARINT_INIT(a, p, 0, 0x00, 0x80, *pi, 1); +#else + a = (*p++); + assert( a & 0x80 ); +#endif + + GETVARINT_STEP(a, p, 7, 0x7F, 0x4000, *pi, 2); + GETVARINT_STEP(a, p, 14, 0x3FFF, 0x200000, *pi, 3); + GETVARINT_STEP(a, p, 21, 0x1FFFFF, 0x10000000, *pi, 4); + a = (a & 0x0FFFFFFF ); + *pi = (int)(a | ((u32)(*p & 0x0F) << 28)); + return 5; } /* ** Return the number of bytes required to encode v as a varint */ @@ -122484,15 +133350,20 @@ ** the output value undefined. Otherwise SQLITE_OK is returned. ** ** This function is used when parsing the "prefix=" FTS4 parameter. */ static int fts3GobbleInt(const char **pp, int *pnOut){ + const int MAX_NPREFIX = 10000000; const char *p; /* Iterator pointer */ int nInt = 0; /* Output value */ for(p=*pp; p[0]>='0' && p[0]<='9'; p++){ nInt = nInt * 10 + (p[0] - '0'); + if( nInt>MAX_NPREFIX ){ + nInt = 0; + break; + } } if( p==*pp ) return SQLITE_ERROR; *pnOut = nInt; *pp = p; return SQLITE_OK; @@ -122531,27 +133402,33 @@ } } aIndex = sqlite3_malloc(sizeof(struct Fts3Index) * nIndex); *apIndex = aIndex; - *pnIndex = nIndex; if( !aIndex ){ return SQLITE_NOMEM; } memset(aIndex, 0, sizeof(struct Fts3Index) * nIndex); if( zParam ){ const char *p = zParam; int i; for(i=1; i<nIndex; i++){ - int nPrefix; + int nPrefix = 0; if( fts3GobbleInt(&p, &nPrefix) ) return SQLITE_ERROR; - aIndex[i].nPrefix = nPrefix; + assert( nPrefix>=0 ); + if( nPrefix==0 ){ + nIndex--; + i--; + }else{ + aIndex[i].nPrefix = nPrefix; + } p++; } } + *pnIndex = nIndex; return SQLITE_OK; } /* ** This function is called when initializing an FTS4 table that uses the @@ -122671,11 +133548,11 @@ int nName; /* Bytes required to hold table name */ int isFts4 = (argv[0][3]=='4'); /* True for FTS4, false for FTS3 */ const char **aCol; /* Array of column names */ sqlite3_tokenizer *pTokenizer = 0; /* Tokenizer for this table */ - int nIndex; /* Size of aIndex[] array */ + int nIndex = 0; /* Size of aIndex[] array */ struct Fts3Index *aIndex = 0; /* Array of indexes for this table */ /* The results of parsing supported FTS4 key=value options: */ int bNoDocsize = 0; /* True to omit %_docsize table */ int bDescIdx = 0; /* True to store descending indexes */ @@ -122907,11 +133784,11 @@ p->nMaxPendingData = FTS3_MAX_PENDING_DATA; p->bHasDocsize = (isFts4 && bNoDocsize==0); p->bHasStat = isFts4; p->bFts4 = isFts4; p->bDescIdx = bDescIdx; - p->bAutoincrmerge = 0xff; /* 0xff means setting unknown */ + p->nAutoincrmerge = 0xff; /* 0xff means setting unknown */ p->zContentTbl = zContent; p->zLanguageid = zLanguageid; zContent = 0; zLanguageid = 0; TESTONLY( p->inTransaction = -1 ); @@ -122950,11 +133827,13 @@ /* Fill in the abNotindexed array */ for(iCol=0; iCol<nCol; iCol++){ int n = (int)strlen(p->azColumn[iCol]); for(i=0; i<nNotindexed; i++){ char *zNot = azNotindexed[i]; - if( zNot && 0==sqlite3_strnicmp(p->azColumn[iCol], zNot, n) ){ + if( zNot && n==(int)strlen(zNot) + && 0==sqlite3_strnicmp(p->azColumn[iCol], zNot, n) + ){ p->abNotindexed[iCol] = 1; sqlite3_free(zNot); azNotindexed[i] = 0; } } @@ -122984,14 +133863,11 @@ /* Check to see if a legacy fts3 table has been "upgraded" by the ** addition of a %_stat table so that it can use incremental merge. */ if( !isFts4 && !isCreate ){ - int rc2 = SQLITE_OK; - fts3DbExec(&rc2, db, "SELECT 1 FROM %Q.'%q_stat' WHERE id=2", - p->zDb, p->zName); - if( rc2==SQLITE_OK ) p->bHasStat = 1; + p->bHasStat = 2; } /* Figure out the page-size for the database. This is required in order to ** estimate the cost of loading large doclists from the database. */ fts3DatabasePageSize(&rc, p); @@ -123045,10 +133921,23 @@ sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ char **pzErr /* OUT: sqlite3_malloc'd error message */ ){ return fts3InitVtab(1, db, pAux, argc, argv, ppVtab, pzErr); } + +/* +** Set the pIdxInfo->estimatedRows variable to nRow. Unless this +** extension is currently being used by a version of SQLite too old to +** support estimatedRows. In that case this function is a no-op. +*/ +static void fts3SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){ +#if SQLITE_VERSION_NUMBER>=3008002 + if( sqlite3_libversion_number()>=3008002 ){ + pIdxInfo->estimatedRows = nRow; + } +#endif +} /* ** Implementation of the xBestIndex method for FTS3 tables. There ** are three possible strategies, in order of preference: ** @@ -123058,27 +133947,44 @@ */ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ Fts3Table *p = (Fts3Table *)pVTab; int i; /* Iterator variable */ int iCons = -1; /* Index of constraint to use */ + int iLangidCons = -1; /* Index of langid=x constraint, if present */ + int iDocidGe = -1; /* Index of docid>=x constraint, if present */ + int iDocidLe = -1; /* Index of docid<=x constraint, if present */ + int iIdx; /* By default use a full table scan. This is an expensive option, ** so search through the constraints to see if a more efficient ** strategy is possible. */ pInfo->idxNum = FTS3_FULLSCAN_SEARCH; pInfo->estimatedCost = 5000000; for(i=0; i<pInfo->nConstraint; i++){ + int bDocid; /* True if this constraint is on docid */ struct sqlite3_index_constraint *pCons = &pInfo->aConstraint[i]; - if( pCons->usable==0 ) continue; + if( pCons->usable==0 ){ + if( pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH ){ + /* There exists an unusable MATCH constraint. This means that if + ** the planner does elect to use the results of this call as part + ** of the overall query plan the user will see an "unable to use + ** function MATCH in the requested context" error. To discourage + ** this, return a very high cost here. */ + pInfo->idxNum = FTS3_FULLSCAN_SEARCH; + pInfo->estimatedCost = 1e50; + fts3SetEstimatedRows(pInfo, ((sqlite3_int64)1) << 50); + return SQLITE_OK; + } + continue; + } + + bDocid = (pCons->iColumn<0 || pCons->iColumn==p->nColumn+1); /* A direct lookup on the rowid or docid column. Assign a cost of 1.0. */ - if( iCons<0 - && pCons->op==SQLITE_INDEX_CONSTRAINT_EQ - && (pCons->iColumn<0 || pCons->iColumn==p->nColumn+1 ) - ){ + if( iCons<0 && pCons->op==SQLITE_INDEX_CONSTRAINT_EQ && bDocid ){ pInfo->idxNum = FTS3_DOCID_SEARCH; pInfo->estimatedCost = 1.0; iCons = i; } @@ -123103,18 +134009,42 @@ if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ && pCons->iColumn==p->nColumn + 2 ){ iLangidCons = i; } + + if( bDocid ){ + switch( pCons->op ){ + case SQLITE_INDEX_CONSTRAINT_GE: + case SQLITE_INDEX_CONSTRAINT_GT: + iDocidGe = i; + break; + + case SQLITE_INDEX_CONSTRAINT_LE: + case SQLITE_INDEX_CONSTRAINT_LT: + iDocidLe = i; + break; + } + } } + iIdx = 1; if( iCons>=0 ){ - pInfo->aConstraintUsage[iCons].argvIndex = 1; + pInfo->aConstraintUsage[iCons].argvIndex = iIdx++; pInfo->aConstraintUsage[iCons].omit = 1; } if( iLangidCons>=0 ){ - pInfo->aConstraintUsage[iLangidCons].argvIndex = 2; + pInfo->idxNum |= FTS3_HAVE_LANGID; + pInfo->aConstraintUsage[iLangidCons].argvIndex = iIdx++; + } + if( iDocidGe>=0 ){ + pInfo->idxNum |= FTS3_HAVE_DOCID_GE; + pInfo->aConstraintUsage[iDocidGe].argvIndex = iIdx++; + } + if( iDocidLe>=0 ){ + pInfo->idxNum |= FTS3_HAVE_DOCID_LE; + pInfo->aConstraintUsage[iDocidLe].argvIndex = iIdx++; } /* Regardless of the strategy selected, FTS can deliver rows in rowid (or ** docid) order. Both ascending and descending are possible. */ @@ -123288,14 +134218,14 @@ int nBuffer; /* Total term size */ /* Load the next term on the node into zBuffer. Use realloc() to expand ** the size of zBuffer if required. */ if( !isFirstTerm ){ - zCsr += sqlite3Fts3GetVarint32(zCsr, &nPrefix); + zCsr += fts3GetVarint32(zCsr, &nPrefix); } isFirstTerm = 0; - zCsr += sqlite3Fts3GetVarint32(zCsr, &nSuffix); + zCsr += fts3GetVarint32(zCsr, &nSuffix); if( nPrefix<0 || nSuffix<0 || &zCsr[nSuffix]>zEnd ){ rc = FTS_CORRUPT_VTAB; goto finish_scan; } @@ -123374,22 +134304,22 @@ const char *zNode, /* Buffer containing segment interior node */ int nNode, /* Size of buffer at zNode */ sqlite3_int64 *piLeaf, /* Selected leaf node */ sqlite3_int64 *piLeaf2 /* Selected leaf node */ ){ - int rc; /* Return code */ + int rc = SQLITE_OK; /* Return code */ int iHeight; /* Height of this node in tree */ assert( piLeaf || piLeaf2 ); - sqlite3Fts3GetVarint32(zNode, &iHeight); + fts3GetVarint32(zNode, &iHeight); rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2); assert( !piLeaf2 || !piLeaf || rc!=SQLITE_OK || (*piLeaf<=*piLeaf2) ); if( rc==SQLITE_OK && iHeight>1 ){ char *zBlob = 0; /* Blob read from %_segments table */ - int nBlob; /* Size of zBlob in bytes */ + int nBlob = 0; /* Size of zBlob in bytes */ if( piLeaf && piLeaf2 && (*piLeaf!=*piLeaf2) ){ rc = sqlite3Fts3ReadBlock(p, *piLeaf, &zBlob, &nBlob, 0); if( rc==SQLITE_OK ){ rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, 0); @@ -123581,15 +134511,15 @@ while( *p1 || *p2 ){ int iCol1; /* The current column index in pp1 */ int iCol2; /* The current column index in pp2 */ - if( *p1==POS_COLUMN ) sqlite3Fts3GetVarint32(&p1[1], &iCol1); + if( *p1==POS_COLUMN ) fts3GetVarint32(&p1[1], &iCol1); else if( *p1==POS_END ) iCol1 = POSITION_LIST_END; else iCol1 = 0; - if( *p2==POS_COLUMN ) sqlite3Fts3GetVarint32(&p2[1], &iCol2); + if( *p2==POS_COLUMN ) fts3GetVarint32(&p2[1], &iCol2); else if( *p2==POS_END ) iCol2 = POSITION_LIST_END; else iCol2 = 0; if( iCol1==iCol2 ){ sqlite3_int64 i1 = 0; /* Last position from pp1 */ @@ -123678,15 +134608,15 @@ assert( isSaveLeft==0 || isExact==0 ); assert( p!=0 && *p1!=0 && *p2!=0 ); if( *p1==POS_COLUMN ){ p1++; - p1 += sqlite3Fts3GetVarint32(p1, &iCol1); + p1 += fts3GetVarint32(p1, &iCol1); } if( *p2==POS_COLUMN ){ p2++; - p2 += sqlite3Fts3GetVarint32(p2, &iCol2); + p2 += fts3GetVarint32(p2, &iCol2); } while( 1 ){ if( iCol1==iCol2 ){ char *pSave = p; @@ -123732,13 +134662,13 @@ fts3ColumnlistCopy(0, &p2); assert( (*p1&0xFE)==0 && (*p2&0xFE)==0 ); if( 0==*p1 || 0==*p2 ) break; p1++; - p1 += sqlite3Fts3GetVarint32(p1, &iCol1); + p1 += fts3GetVarint32(p1, &iCol1); p2++; - p2 += sqlite3Fts3GetVarint32(p2, &iCol2); + p2 += fts3GetVarint32(p2, &iCol2); } /* Advance pointer p1 or p2 (whichever corresponds to the smaller of ** iCol1 and iCol2) so that it points to either the 0x00 that marks the ** end of the position list, or the 0x01 that precedes the next @@ -123746,16 +134676,16 @@ */ else if( iCol1<iCol2 ){ fts3ColumnlistCopy(0, &p1); if( 0==*p1 ) break; p1++; - p1 += sqlite3Fts3GetVarint32(p1, &iCol1); + p1 += fts3GetVarint32(p1, &iCol1); }else{ fts3ColumnlistCopy(0, &p2); if( 0==*p2 ) break; p2++; - p2 += sqlite3Fts3GetVarint32(p2, &iCol2); + p2 += fts3GetVarint32(p2, &iCol2); } } fts3PoslistCopy(0, &p2); fts3PoslistCopy(0, &p1); @@ -124556,10 +135486,37 @@ rc = fts3EvalNext((Fts3Cursor *)pCursor); } assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); return rc; } + +/* +** The following are copied from sqliteInt.h. +** +** Constants for the largest and smallest possible 64-bit signed integers. +** These macros are designed to work correctly on both 32-bit and 64-bit +** compilers. +*/ +#ifndef SQLITE_AMALGAMATION +# define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32)) +# define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) +#endif + +/* +** If the numeric type of argument pVal is "integer", then return it +** converted to a 64-bit signed integer. Otherwise, return a copy of +** the second parameter, iDefault. +*/ +static sqlite3_int64 fts3DocidRange(sqlite3_value *pVal, i64 iDefault){ + if( pVal ){ + int eType = sqlite3_value_numeric_type(pVal); + if( eType==SQLITE_INTEGER ){ + return sqlite3_value_int64(pVal); + } + } + return iDefault; +} /* ** This is the xFilter interface for the virtual table. See ** the virtual table xFilter method documentation for additional ** information. @@ -124580,46 +135537,65 @@ int idxNum, /* Strategy index */ const char *idxStr, /* Unused */ int nVal, /* Number of elements in apVal */ sqlite3_value **apVal /* Arguments for the indexing scheme */ ){ - int rc; + int rc = SQLITE_OK; char *zSql; /* SQL statement used to access %_content */ + int eSearch; Fts3Table *p = (Fts3Table *)pCursor->pVtab; Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; + + sqlite3_value *pCons = 0; /* The MATCH or rowid constraint, if any */ + sqlite3_value *pLangid = 0; /* The "langid = ?" constraint, if any */ + sqlite3_value *pDocidGe = 0; /* The "docid >= ?" constraint, if any */ + sqlite3_value *pDocidLe = 0; /* The "docid <= ?" constraint, if any */ + int iIdx; UNUSED_PARAMETER(idxStr); UNUSED_PARAMETER(nVal); - assert( idxNum>=0 && idxNum<=(FTS3_FULLTEXT_SEARCH+p->nColumn) ); - assert( nVal==0 || nVal==1 || nVal==2 ); - assert( (nVal==0)==(idxNum==FTS3_FULLSCAN_SEARCH) ); + eSearch = (idxNum & 0x0000FFFF); + assert( eSearch>=0 && eSearch<=(FTS3_FULLTEXT_SEARCH+p->nColumn) ); assert( p->pSegments==0 ); + + /* Collect arguments into local variables */ + iIdx = 0; + if( eSearch!=FTS3_FULLSCAN_SEARCH ) pCons = apVal[iIdx++]; + if( idxNum & FTS3_HAVE_LANGID ) pLangid = apVal[iIdx++]; + if( idxNum & FTS3_HAVE_DOCID_GE ) pDocidGe = apVal[iIdx++]; + if( idxNum & FTS3_HAVE_DOCID_LE ) pDocidLe = apVal[iIdx++]; + assert( iIdx==nVal ); /* In case the cursor has been used before, clear it now. */ sqlite3_finalize(pCsr->pStmt); sqlite3_free(pCsr->aDoclist); + sqlite3_free(pCsr->aMatchinfo); sqlite3Fts3ExprFree(pCsr->pExpr); memset(&pCursor[1], 0, sizeof(Fts3Cursor)-sizeof(sqlite3_vtab_cursor)); + /* Set the lower and upper bounds on docids to return */ + pCsr->iMinDocid = fts3DocidRange(pDocidGe, SMALLEST_INT64); + pCsr->iMaxDocid = fts3DocidRange(pDocidLe, LARGEST_INT64); + if( idxStr ){ pCsr->bDesc = (idxStr[0]=='D'); }else{ pCsr->bDesc = p->bDescIdx; } - pCsr->eSearch = (i16)idxNum; + pCsr->eSearch = (i16)eSearch; - if( idxNum!=FTS3_DOCID_SEARCH && idxNum!=FTS3_FULLSCAN_SEARCH ){ - int iCol = idxNum-FTS3_FULLTEXT_SEARCH; - const char *zQuery = (const char *)sqlite3_value_text(apVal[0]); + if( eSearch!=FTS3_DOCID_SEARCH && eSearch!=FTS3_FULLSCAN_SEARCH ){ + int iCol = eSearch-FTS3_FULLTEXT_SEARCH; + const char *zQuery = (const char *)sqlite3_value_text(pCons); - if( zQuery==0 && sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ + if( zQuery==0 && sqlite3_value_type(pCons)!=SQLITE_NULL ){ return SQLITE_NOMEM; } pCsr->iLangid = 0; - if( nVal==2 ) pCsr->iLangid = sqlite3_value_int(apVal[1]); + if( pLangid ) pCsr->iLangid = sqlite3_value_int(pLangid); assert( p->base.zErrMsg==0 ); rc = sqlite3Fts3ExprParse(p->pTokenizer, pCsr->iLangid, p->azColumn, p->bFts4, p->nColumn, iCol, zQuery, -1, &pCsr->pExpr, &p->base.zErrMsg @@ -124638,25 +135614,32 @@ /* Compile a SELECT statement for this cursor. For a full-table-scan, the ** statement loops through all rows of the %_content table. For a ** full-text query or docid lookup, the statement retrieves a single ** row by docid. */ - if( idxNum==FTS3_FULLSCAN_SEARCH ){ - zSql = sqlite3_mprintf( - "SELECT %s ORDER BY rowid %s", - p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC") - ); + if( eSearch==FTS3_FULLSCAN_SEARCH ){ + if( pDocidGe || pDocidLe ){ + zSql = sqlite3_mprintf( + "SELECT %s WHERE rowid BETWEEN %lld AND %lld ORDER BY rowid %s", + p->zReadExprlist, pCsr->iMinDocid, pCsr->iMaxDocid, + (pCsr->bDesc ? "DESC" : "ASC") + ); + }else{ + zSql = sqlite3_mprintf("SELECT %s ORDER BY rowid %s", + p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC") + ); + } if( zSql ){ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); sqlite3_free(zSql); }else{ rc = SQLITE_NOMEM; } - }else if( idxNum==FTS3_DOCID_SEARCH ){ + }else if( eSearch==FTS3_DOCID_SEARCH ){ rc = fts3CursorSeekStmt(pCsr, &pCsr->pStmt); if( rc==SQLITE_OK ){ - rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]); + rc = sqlite3_bind_value(pCsr->pStmt, 1, pCons); } } if( rc!=SQLITE_OK ) return rc; return fts3NextMethod(pCursor); @@ -124780,26 +135763,56 @@ const u32 nMinMerge = 64; /* Minimum amount of incr-merge work to do */ Fts3Table *p = (Fts3Table*)pVtab; int rc = sqlite3Fts3PendingTermsFlush(p); - if( rc==SQLITE_OK && p->bAutoincrmerge==1 && p->nLeafAdd>(nMinMerge/16) ){ + if( rc==SQLITE_OK + && p->nLeafAdd>(nMinMerge/16) + && p->nAutoincrmerge && p->nAutoincrmerge!=0xff + ){ int mxLevel = 0; /* Maximum relative level value in db */ int A; /* Incr-merge parameter A */ rc = sqlite3Fts3MaxLevel(p, &mxLevel); assert( rc==SQLITE_OK || mxLevel==0 ); A = p->nLeafAdd * mxLevel; A += (A/2); - if( A>(int)nMinMerge ) rc = sqlite3Fts3Incrmerge(p, A, 8); + if( A>(int)nMinMerge ) rc = sqlite3Fts3Incrmerge(p, A, p->nAutoincrmerge); } sqlite3Fts3SegmentsClose(p); return rc; } /* -** Implementation of xBegin() method. This is a no-op. +** If it is currently unknown whether or not the FTS table has an %_stat +** table (if p->bHasStat==2), attempt to determine this (set p->bHasStat +** to 0 or 1). Return SQLITE_OK if successful, or an SQLite error code +** if an error occurs. +*/ +static int fts3SetHasStat(Fts3Table *p){ + int rc = SQLITE_OK; + if( p->bHasStat==2 ){ + const char *zFmt ="SELECT 1 FROM %Q.sqlite_master WHERE tbl_name='%q_stat'"; + char *zSql = sqlite3_mprintf(zFmt, p->zDb, p->zName); + if( zSql ){ + sqlite3_stmt *pStmt = 0; + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + int bHasStat = (sqlite3_step(pStmt)==SQLITE_ROW); + rc = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ) p->bHasStat = bHasStat; + } + sqlite3_free(zSql); + }else{ + rc = SQLITE_NOMEM; + } + } + return rc; +} + +/* +** Implementation of xBegin() method. */ static int fts3BeginMethod(sqlite3_vtab *pVtab){ Fts3Table *p = (Fts3Table*)pVtab; UNUSED_PARAMETER(pVtab); assert( p->pSegments==0 ); @@ -124806,11 +135819,11 @@ assert( p->nPendingData==0 ); assert( p->inTransaction!=1 ); TESTONLY( p->inTransaction = 1 ); TESTONLY( p->mxSavepoint = -1; ); p->nLeafAdd = 0; - return SQLITE_OK; + return fts3SetHasStat(p); } /* ** Implementation of xCommit() method. This is a no-op. The contents of ** the pending-terms hash-table have already been flushed into the database @@ -125055,18 +136068,24 @@ ){ Fts3Table *p = (Fts3Table *)pVtab; sqlite3 *db = p->db; /* Database connection */ int rc; /* Return Code */ + /* At this point it must be known if the %_stat table exists or not. + ** So bHasStat may not be 2. */ + rc = fts3SetHasStat(p); + /* As it happens, the pending terms table is always empty here. This is ** because an "ALTER TABLE RENAME TABLE" statement inside a transaction ** always opens a savepoint transaction. And the xSavepoint() method ** flushes the pending terms table. But leave the (no-op) call to ** PendingTermsFlush() in in case that changes. */ assert( p->nPendingData==0 ); - rc = sqlite3Fts3PendingTermsFlush(p); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3PendingTermsFlush(p); + } if( p->zContentTbl==0 ){ fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';", p->zDb, p->zName, zName @@ -125190,11 +136209,11 @@ ** to by the argument to point to the "simple" tokenizer implementation. ** And so on. */ SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule); SQLITE_PRIVATE void sqlite3Fts3PorterTokenizerModule(sqlite3_tokenizer_module const**ppModule); -#ifdef SQLITE_ENABLE_FTS4_UNICODE61 +#ifndef SQLITE_DISABLE_FTS3_UNICODE SQLITE_PRIVATE void sqlite3Fts3UnicodeTokenizer(sqlite3_tokenizer_module const**ppModule); #endif #ifdef SQLITE_ENABLE_ICU SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(sqlite3_tokenizer_module const**ppModule); #endif @@ -125208,20 +136227,20 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ int rc = SQLITE_OK; Fts3Hash *pHash = 0; const sqlite3_tokenizer_module *pSimple = 0; const sqlite3_tokenizer_module *pPorter = 0; -#ifdef SQLITE_ENABLE_FTS4_UNICODE61 +#ifndef SQLITE_DISABLE_FTS3_UNICODE const sqlite3_tokenizer_module *pUnicode = 0; #endif #ifdef SQLITE_ENABLE_ICU const sqlite3_tokenizer_module *pIcu = 0; sqlite3Fts3IcuTokenizerModule(&pIcu); #endif -#ifdef SQLITE_ENABLE_FTS4_UNICODE61 +#ifndef SQLITE_DISABLE_FTS3_UNICODE sqlite3Fts3UnicodeTokenizer(&pUnicode); #endif #ifdef SQLITE_TEST rc = sqlite3Fts3InitTerm(db); @@ -125245,11 +136264,11 @@ /* Load the built-in tokenizers into the hash table */ if( rc==SQLITE_OK ){ if( sqlite3Fts3HashInsert(pHash, "simple", 7, (void *)pSimple) || sqlite3Fts3HashInsert(pHash, "porter", 7, (void *)pPorter) -#ifdef SQLITE_ENABLE_FTS4_UNICODE61 +#ifndef SQLITE_DISABLE_FTS3_UNICODE || sqlite3Fts3HashInsert(pHash, "unicode61", 10, (void *)pUnicode) #endif #ifdef SQLITE_ENABLE_ICU || (pIcu && sqlite3Fts3HashInsert(pHash, "icu", 4, (void *)pIcu)) #endif @@ -125543,10 +136562,16 @@ } return SQLITE_OK; } +/* +** Maximum number of tokens a phrase may have to be considered for the +** incremental doclists strategy. +*/ +#define MAX_INCR_PHRASE_TOKENS 4 + /* ** This function is called for each Fts3Phrase in a full-text query ** expression to initialize the mechanism for returning rows. Once this ** function has been called successfully on an Fts3Phrase, it may be ** used with fts3EvalPhraseNext() to iterate through the matching docids. @@ -125556,27 +136581,47 @@ ** memory within this call. ** ** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code. */ static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){ - int rc; /* Error code */ - Fts3PhraseToken *pFirst = &p->aToken[0]; Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; - - if( pCsr->bDesc==pTab->bDescIdx - && bOptOk==1 - && p->nToken==1 - && pFirst->pSegcsr - && pFirst->pSegcsr->bLookup - && pFirst->bFirst==0 - ){ + int rc = SQLITE_OK; /* Error code */ + int i; + + /* Determine if doclists may be loaded from disk incrementally. This is + ** possible if the bOptOk argument is true, the FTS doclists will be + ** scanned in forward order, and the phrase consists of + ** MAX_INCR_PHRASE_TOKENS or fewer tokens, none of which are are "^first" + ** tokens or prefix tokens that cannot use a prefix-index. */ + int bHaveIncr = 0; + int bIncrOk = (bOptOk + && pCsr->bDesc==pTab->bDescIdx + && p->nToken<=MAX_INCR_PHRASE_TOKENS && p->nToken>0 + && p->nToken<=MAX_INCR_PHRASE_TOKENS && p->nToken>0 +#ifdef SQLITE_TEST + && pTab->bNoIncrDoclist==0 +#endif + ); + for(i=0; bIncrOk==1 && i<p->nToken; i++){ + Fts3PhraseToken *pToken = &p->aToken[i]; + if( pToken->bFirst || (pToken->pSegcsr!=0 && !pToken->pSegcsr->bLookup) ){ + bIncrOk = 0; + } + if( pToken->pSegcsr ) bHaveIncr = 1; + } + + if( bIncrOk && bHaveIncr ){ /* Use the incremental approach. */ int iCol = (p->iColumn >= pTab->nColumn ? -1 : p->iColumn); - rc = sqlite3Fts3MsrIncrStart( - pTab, pFirst->pSegcsr, iCol, pFirst->z, pFirst->n); + for(i=0; rc==SQLITE_OK && i<p->nToken; i++){ + Fts3PhraseToken *pToken = &p->aToken[i]; + Fts3MultiSegReader *pSegcsr = pToken->pSegcsr; + if( pSegcsr ){ + rc = sqlite3Fts3MsrIncrStart(pTab, pSegcsr, iCol, pToken->z, pToken->n); + } + } p->bIncr = 1; - }else{ /* Load the full doclist for the phrase into memory. */ rc = fts3EvalPhraseLoad(pCsr, p); p->bIncr = 0; } @@ -125680,10 +136725,220 @@ } } *ppIter = p; } + +/* +** Advance the iterator pDL to the next entry in pDL->aAll/nAll. Set *pbEof +** to true if EOF is reached. +*/ +static void fts3EvalDlPhraseNext( + Fts3Table *pTab, + Fts3Doclist *pDL, + u8 *pbEof +){ + char *pIter; /* Used to iterate through aAll */ + char *pEnd = &pDL->aAll[pDL->nAll]; /* 1 byte past end of aAll */ + + if( pDL->pNextDocid ){ + pIter = pDL->pNextDocid; + }else{ + pIter = pDL->aAll; + } + + if( pIter>=pEnd ){ + /* We have already reached the end of this doclist. EOF. */ + *pbEof = 1; + }else{ + sqlite3_int64 iDelta; + pIter += sqlite3Fts3GetVarint(pIter, &iDelta); + if( pTab->bDescIdx==0 || pDL->pNextDocid==0 ){ + pDL->iDocid += iDelta; + }else{ + pDL->iDocid -= iDelta; + } + pDL->pList = pIter; + fts3PoslistCopy(0, &pIter); + pDL->nList = (int)(pIter - pDL->pList); + + /* pIter now points just past the 0x00 that terminates the position- + ** list for document pDL->iDocid. However, if this position-list was + ** edited in place by fts3EvalNearTrim(), then pIter may not actually + ** point to the start of the next docid value. The following line deals + ** with this case by advancing pIter past the zero-padding added by + ** fts3EvalNearTrim(). */ + while( pIter<pEnd && *pIter==0 ) pIter++; + + pDL->pNextDocid = pIter; + assert( pIter>=&pDL->aAll[pDL->nAll] || *pIter ); + *pbEof = 0; + } +} + +/* +** Helper type used by fts3EvalIncrPhraseNext() and incrPhraseTokenNext(). +*/ +typedef struct TokenDoclist TokenDoclist; +struct TokenDoclist { + int bIgnore; + sqlite3_int64 iDocid; + char *pList; + int nList; +}; + +/* +** Token pToken is an incrementally loaded token that is part of a +** multi-token phrase. Advance it to the next matching document in the +** database and populate output variable *p with the details of the new +** entry. Or, if the iterator has reached EOF, set *pbEof to true. +** +** If an error occurs, return an SQLite error code. Otherwise, return +** SQLITE_OK. +*/ +static int incrPhraseTokenNext( + Fts3Table *pTab, /* Virtual table handle */ + Fts3Phrase *pPhrase, /* Phrase to advance token of */ + int iToken, /* Specific token to advance */ + TokenDoclist *p, /* OUT: Docid and doclist for new entry */ + u8 *pbEof /* OUT: True if iterator is at EOF */ +){ + int rc = SQLITE_OK; + + if( pPhrase->iDoclistToken==iToken ){ + assert( p->bIgnore==0 ); + assert( pPhrase->aToken[iToken].pSegcsr==0 ); + fts3EvalDlPhraseNext(pTab, &pPhrase->doclist, pbEof); + p->pList = pPhrase->doclist.pList; + p->nList = pPhrase->doclist.nList; + p->iDocid = pPhrase->doclist.iDocid; + }else{ + Fts3PhraseToken *pToken = &pPhrase->aToken[iToken]; + assert( pToken->pDeferred==0 ); + assert( pToken->pSegcsr || pPhrase->iDoclistToken>=0 ); + if( pToken->pSegcsr ){ + assert( p->bIgnore==0 ); + rc = sqlite3Fts3MsrIncrNext( + pTab, pToken->pSegcsr, &p->iDocid, &p->pList, &p->nList + ); + if( p->pList==0 ) *pbEof = 1; + }else{ + p->bIgnore = 1; + } + } + + return rc; +} + + +/* +** The phrase iterator passed as the second argument: +** +** * features at least one token that uses an incremental doclist, and +** +** * does not contain any deferred tokens. +** +** Advance it to the next matching documnent in the database and populate +** the Fts3Doclist.pList and nList fields. +** +** If there is no "next" entry and no error occurs, then *pbEof is set to +** 1 before returning. Otherwise, if no error occurs and the iterator is +** successfully advanced, *pbEof is set to 0. +** +** If an error occurs, return an SQLite error code. Otherwise, return +** SQLITE_OK. +*/ +static int fts3EvalIncrPhraseNext( + Fts3Cursor *pCsr, /* FTS Cursor handle */ + Fts3Phrase *p, /* Phrase object to advance to next docid */ + u8 *pbEof /* OUT: Set to 1 if EOF */ +){ + int rc = SQLITE_OK; + Fts3Doclist *pDL = &p->doclist; + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + u8 bEof = 0; + + /* This is only called if it is guaranteed that the phrase has at least + ** one incremental token. In which case the bIncr flag is set. */ + assert( p->bIncr==1 ); + + if( p->nToken==1 && p->bIncr ){ + rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr, + &pDL->iDocid, &pDL->pList, &pDL->nList + ); + if( pDL->pList==0 ) bEof = 1; + }else{ + int bDescDoclist = pCsr->bDesc; + struct TokenDoclist a[MAX_INCR_PHRASE_TOKENS]; + + memset(a, 0, sizeof(a)); + assert( p->nToken<=MAX_INCR_PHRASE_TOKENS ); + assert( p->iDoclistToken<MAX_INCR_PHRASE_TOKENS ); + + while( bEof==0 ){ + int bMaxSet = 0; + sqlite3_int64 iMax = 0; /* Largest docid for all iterators */ + int i; /* Used to iterate through tokens */ + + /* Advance the iterator for each token in the phrase once. */ + for(i=0; rc==SQLITE_OK && i<p->nToken && bEof==0; i++){ + rc = incrPhraseTokenNext(pTab, p, i, &a[i], &bEof); + if( a[i].bIgnore==0 && (bMaxSet==0 || DOCID_CMP(iMax, a[i].iDocid)<0) ){ + iMax = a[i].iDocid; + bMaxSet = 1; + } + } + assert( rc!=SQLITE_OK || (p->nToken>=1 && a[p->nToken-1].bIgnore==0) ); + assert( rc!=SQLITE_OK || bMaxSet ); + + /* Keep advancing iterators until they all point to the same document */ + for(i=0; i<p->nToken; i++){ + while( rc==SQLITE_OK && bEof==0 + && a[i].bIgnore==0 && DOCID_CMP(a[i].iDocid, iMax)<0 + ){ + rc = incrPhraseTokenNext(pTab, p, i, &a[i], &bEof); + if( DOCID_CMP(a[i].iDocid, iMax)>0 ){ + iMax = a[i].iDocid; + i = 0; + } + } + } + + /* Check if the current entries really are a phrase match */ + if( bEof==0 ){ + int nList = 0; + int nByte = a[p->nToken-1].nList; + char *aDoclist = sqlite3_malloc(nByte+1); + if( !aDoclist ) return SQLITE_NOMEM; + memcpy(aDoclist, a[p->nToken-1].pList, nByte+1); + + for(i=0; i<(p->nToken-1); i++){ + if( a[i].bIgnore==0 ){ + char *pL = a[i].pList; + char *pR = aDoclist; + char *pOut = aDoclist; + int nDist = p->nToken-1-i; + int res = fts3PoslistPhraseMerge(&pOut, nDist, 0, 1, &pL, &pR); + if( res==0 ) break; + nList = (int)(pOut - aDoclist); + } + } + if( i==(p->nToken-1) ){ + pDL->iDocid = iMax; + pDL->pList = aDoclist; + pDL->nList = nList; + pDL->bFreeList = 1; + break; + } + sqlite3_free(aDoclist); + } + } + } + + *pbEof = bEof; + return rc; +} /* ** Attempt to move the phrase iterator to point to the next matching docid. ** If an error occurs, return an SQLite error code. Otherwise, return ** SQLITE_OK. @@ -125700,59 +136955,18 @@ int rc = SQLITE_OK; Fts3Doclist *pDL = &p->doclist; Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; if( p->bIncr ){ - assert( p->nToken==1 ); - assert( pDL->pNextDocid==0 ); - rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr, - &pDL->iDocid, &pDL->pList, &pDL->nList - ); - if( rc==SQLITE_OK && !pDL->pList ){ - *pbEof = 1; - } + rc = fts3EvalIncrPhraseNext(pCsr, p, pbEof); }else if( pCsr->bDesc!=pTab->bDescIdx && pDL->nAll ){ sqlite3Fts3DoclistPrev(pTab->bDescIdx, pDL->aAll, pDL->nAll, &pDL->pNextDocid, &pDL->iDocid, &pDL->nList, pbEof ); pDL->pList = pDL->pNextDocid; }else{ - char *pIter; /* Used to iterate through aAll */ - char *pEnd = &pDL->aAll[pDL->nAll]; /* 1 byte past end of aAll */ - if( pDL->pNextDocid ){ - pIter = pDL->pNextDocid; - }else{ - pIter = pDL->aAll; - } - - if( pIter>=pEnd ){ - /* We have already reached the end of this doclist. EOF. */ - *pbEof = 1; - }else{ - sqlite3_int64 iDelta; - pIter += sqlite3Fts3GetVarint(pIter, &iDelta); - if( pTab->bDescIdx==0 || pDL->pNextDocid==0 ){ - pDL->iDocid += iDelta; - }else{ - pDL->iDocid -= iDelta; - } - pDL->pList = pIter; - fts3PoslistCopy(0, &pIter); - pDL->nList = (int)(pIter - pDL->pList); - - /* pIter now points just past the 0x00 that terminates the position- - ** list for document pDL->iDocid. However, if this position-list was - ** edited in place by fts3EvalNearTrim(), then pIter may not actually - ** point to the start of the next docid value. The following line deals - ** with this case by advancing pIter past the zero-padding added by - ** fts3EvalNearTrim(). */ - while( pIter<pEnd && *pIter==0 ) pIter++; - - pDL->pNextDocid = pIter; - assert( pIter>=&pDL->aAll[pDL->nAll] || *pIter ); - *pbEof = 0; - } + fts3EvalDlPhraseNext(pTab, pDL, pbEof); } return rc; } @@ -125773,11 +136987,10 @@ ** code before returning. */ static void fts3EvalStartReaders( Fts3Cursor *pCsr, /* FTS Cursor handle */ Fts3Expr *pExpr, /* Expression to initialize phrases in */ - int bOptOk, /* True to enable incremental loading */ int *pRc /* IN/OUT: Error code */ ){ if( pExpr && SQLITE_OK==*pRc ){ if( pExpr->eType==FTSQUERY_PHRASE ){ int i; @@ -125784,14 +136997,14 @@ int nToken = pExpr->pPhrase->nToken; for(i=0; i<nToken; i++){ if( pExpr->pPhrase->aToken[i].pDeferred==0 ) break; } pExpr->bDeferred = (i==nToken); - *pRc = fts3EvalPhraseStart(pCsr, bOptOk, pExpr->pPhrase); + *pRc = fts3EvalPhraseStart(pCsr, 1, pExpr->pPhrase); }else{ - fts3EvalStartReaders(pCsr, pExpr->pLeft, bOptOk, pRc); - fts3EvalStartReaders(pCsr, pExpr->pRight, bOptOk, pRc); + fts3EvalStartReaders(pCsr, pExpr->pLeft, pRc); + fts3EvalStartReaders(pCsr, pExpr->pRight, pRc); pExpr->bDeferred = (pExpr->pLeft->bDeferred && pExpr->pRight->bDeferred); } } } @@ -126029,11 +137242,11 @@ /* Set nLoad4 to the value of (4^nOther) for the next iteration of the ** for-loop. Except, limit the value to 2^24 to prevent it from ** overflowing the 32-bit integer it is stored in. */ if( ii<12 ) nLoad4 = nLoad4*4; - if( ii==0 || pTC->pPhrase->nToken>1 ){ + if( ii==0 || (pTC->pPhrase->nToken>1 && ii!=nToken-1) ){ /* Either this is the cheapest token in the entire query, or it is ** part of a multi-token phrase. Either way, the entire doclist will ** (eventually) be loaded into memory. It may as well be now. */ Fts3PhraseToken *pToken = pTC->pToken; int nList = 0; @@ -126109,11 +137322,11 @@ sqlite3_free(aTC); } } #endif - fts3EvalStartReaders(pCsr, pCsr->pExpr, 1, &rc); + fts3EvalStartReaders(pCsr, pCsr->pExpr, &rc); return rc; } /* ** Invalidate the current position list for phrase pPhrase. @@ -126265,10 +137478,26 @@ fts3EvalNextRow(pCsr, pRight, pRc); } } pExpr->iDocid = pLeft->iDocid; pExpr->bEof = (pLeft->bEof || pRight->bEof); + if( pExpr->eType==FTSQUERY_NEAR && pExpr->bEof ){ + if( pRight->pPhrase && pRight->pPhrase->doclist.aAll ){ + Fts3Doclist *pDl = &pRight->pPhrase->doclist; + while( *pRc==SQLITE_OK && pRight->bEof==0 ){ + memset(pDl->pList, 0, pDl->nList); + fts3EvalNextRow(pCsr, pRight, pRc); + } + } + if( pLeft->pPhrase && pLeft->pPhrase->doclist.aAll ){ + Fts3Doclist *pDl = &pLeft->pPhrase->doclist; + while( *pRc==SQLITE_OK && pLeft->bEof==0 ){ + memset(pDl->pList, 0, pDl->nList); + fts3EvalNextRow(pCsr, pLeft, pRc); + } + } + } } break; } case FTSQUERY_OR: { @@ -126592,10 +137821,20 @@ pCsr->isRequireSeek = 1; pCsr->isMatchinfoNeeded = 1; pCsr->iPrevId = pExpr->iDocid; }while( pCsr->isEof==0 && fts3EvalTestDeferredAndNear(pCsr, &rc) ); } + + /* Check if the cursor is past the end of the docid range specified + ** by Fts3Cursor.iMinDocid/iMaxDocid. If so, set the EOF flag. */ + if( rc==SQLITE_OK && ( + (pCsr->bDesc==0 && pCsr->iPrevId>pCsr->iMaxDocid) + || (pCsr->bDesc!=0 && pCsr->iPrevId<pCsr->iMinDocid) + )){ + pCsr->isEof = 1; + } + return rc; } /* ** Restart interation for expression pExpr so that the next call to @@ -126615,18 +137854,23 @@ Fts3Phrase *pPhrase = pExpr->pPhrase; if( pPhrase ){ fts3EvalInvalidatePoslist(pPhrase); if( pPhrase->bIncr ){ - assert( pPhrase->nToken==1 ); - assert( pPhrase->aToken[0].pSegcsr ); - sqlite3Fts3MsrIncrRestart(pPhrase->aToken[0].pSegcsr); + int i; + for(i=0; i<pPhrase->nToken; i++){ + Fts3PhraseToken *pToken = &pPhrase->aToken[i]; + assert( pToken->pDeferred==0 ); + if( pToken->pSegcsr ){ + sqlite3Fts3MsrIncrRestart(pToken->pSegcsr); + } + } *pRc = fts3EvalPhraseStart(pCsr, 0, pPhrase); } - pPhrase->doclist.pNextDocid = 0; pPhrase->doclist.iDocid = 0; + pPhrase->pOrPoslist = 0; } pExpr->iDocid = 0; pExpr->bEof = 0; pExpr->bStart = 0; @@ -126665,11 +137909,11 @@ */ pExpr->aMI[iCol*3 + 1] += iCnt; pExpr->aMI[iCol*3 + 2] += (iCnt>0); if( *p==0x00 ) break; p++; - p += sqlite3Fts3GetVarint32(p, &iCol); + p += fts3GetVarint32(p, &iCol); } } fts3EvalUpdateCounts(pExpr->pLeft); fts3EvalUpdateCounts(pExpr->pRight); @@ -126868,79 +138112,91 @@ } iDocid = pExpr->iDocid; pIter = pPhrase->doclist.pList; if( iDocid!=pCsr->iPrevId || pExpr->bEof ){ + int rc = SQLITE_OK; int bDescDoclist = pTab->bDescIdx; /* For DOCID_CMP macro */ int bOr = 0; u8 bEof = 0; - Fts3Expr *p; + u8 bTreeEof = 0; + Fts3Expr *p; /* Used to iterate from pExpr to root */ + Fts3Expr *pNear; /* Most senior NEAR ancestor (or pExpr) */ /* Check if this phrase descends from an OR expression node. If not, ** return NULL. Otherwise, the entry that corresponds to docid - ** pCsr->iPrevId may lie earlier in the doclist buffer. */ + ** pCsr->iPrevId may lie earlier in the doclist buffer. Or, if the + ** tree that the node is part of has been marked as EOF, but the node + ** itself is not EOF, then it may point to an earlier entry. */ + pNear = pExpr; for(p=pExpr->pParent; p; p=p->pParent){ if( p->eType==FTSQUERY_OR ) bOr = 1; + if( p->eType==FTSQUERY_NEAR ) pNear = p; + if( p->bEof ) bTreeEof = 1; } if( bOr==0 ) return SQLITE_OK; /* This is the descendent of an OR node. In this case we cannot use ** an incremental phrase. Load the entire doclist for the phrase ** into memory in this case. */ if( pPhrase->bIncr ){ - int rc = SQLITE_OK; - int bEofSave = pExpr->bEof; - fts3EvalRestart(pCsr, pExpr, &rc); - while( rc==SQLITE_OK && !pExpr->bEof ){ - fts3EvalNextRow(pCsr, pExpr, &rc); - if( bEofSave==0 && pExpr->iDocid==iDocid ) break; - } - pIter = pPhrase->doclist.pList; + int bEofSave = pNear->bEof; + fts3EvalRestart(pCsr, pNear, &rc); + while( rc==SQLITE_OK && !pNear->bEof ){ + fts3EvalNextRow(pCsr, pNear, &rc); + if( bEofSave==0 && pNear->iDocid==iDocid ) break; + } assert( rc!=SQLITE_OK || pPhrase->bIncr==0 ); - if( rc!=SQLITE_OK ) return rc; - } - - if( pExpr->bEof ){ - pIter = 0; - iDocid = 0; - } - bEof = (pPhrase->doclist.nAll==0); - assert( bDescDoclist==0 || bDescDoclist==1 ); - assert( pCsr->bDesc==0 || pCsr->bDesc==1 ); - + } + if( bTreeEof ){ + while( rc==SQLITE_OK && !pNear->bEof ){ + fts3EvalNextRow(pCsr, pNear, &rc); + } + } + if( rc!=SQLITE_OK ) return rc; + + pIter = pPhrase->pOrPoslist; + iDocid = pPhrase->iOrDocid; if( pCsr->bDesc==bDescDoclist ){ - int dummy; - while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)>0 ) && bEof==0 ){ - sqlite3Fts3DoclistPrev( - bDescDoclist, pPhrase->doclist.aAll, pPhrase->doclist.nAll, - &pIter, &iDocid, &dummy, &bEof - ); - } - }else{ + bEof = (pIter >= (pPhrase->doclist.aAll + pPhrase->doclist.nAll)); while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)<0 ) && bEof==0 ){ sqlite3Fts3DoclistNext( bDescDoclist, pPhrase->doclist.aAll, pPhrase->doclist.nAll, &pIter, &iDocid, &bEof ); } + }else{ + bEof = !pPhrase->doclist.nAll || (pIter && pIter<=pPhrase->doclist.aAll); + while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)>0 ) && bEof==0 ){ + int dummy; + sqlite3Fts3DoclistPrev( + bDescDoclist, pPhrase->doclist.aAll, pPhrase->doclist.nAll, + &pIter, &iDocid, &dummy, &bEof + ); + } } + pPhrase->pOrPoslist = pIter; + pPhrase->iOrDocid = iDocid; if( bEof || iDocid!=pCsr->iPrevId ) pIter = 0; } if( pIter==0 ) return SQLITE_OK; if( *pIter==0x01 ){ pIter++; - pIter += sqlite3Fts3GetVarint32(pIter, &iThis); + pIter += fts3GetVarint32(pIter, &iThis); }else{ iThis = 0; } while( iThis<iCol ){ fts3ColumnlistCopy(0, &pIter); - if( *pIter==0x00 ) return 0; + if( *pIter==0x00 ) return SQLITE_OK; pIter++; - pIter += sqlite3Fts3GetVarint32(pIter, &iThis); + pIter += fts3GetVarint32(pIter, &iThis); + } + if( *pIter==0x00 ){ + pIter = 0; } *ppOut = ((iCol==iThis)?pIter:0); return SQLITE_OK; } @@ -126980,11 +138236,11 @@ ** Initialize API pointer table, if required. */ #ifdef _WIN32 __declspec(dllexport) #endif -SQLITE_API int sqlite3_fts3_init( +SQLITE_API int SQLITE_STDCALL sqlite3_fts3_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ SQLITE_EXTENSION_INIT2(pApi) @@ -127026,10 +138282,11 @@ sqlite3_vtab_cursor base; /* Base class used by SQLite core */ Fts3MultiSegReader csr; /* Must be right after "base" */ Fts3SegFilter filter; char *zStop; int nStop; /* Byte-length of string zStop */ + int iLangid; /* Language id to query */ int isEof; /* True if cursor is at EOF */ sqlite3_int64 iRowid; /* Current rowid */ int iCol; /* Current value of 'col' column */ int nStat; /* Size of aStat[] array */ @@ -127040,11 +138297,12 @@ }; /* ** Schema of the terms table. */ -#define FTS3_TERMS_SCHEMA "CREATE TABLE x(term, col, documents, occurrences)" +#define FTS3_AUX_SCHEMA \ + "CREATE TABLE x(term, col, documents, occurrences, languageid HIDDEN)" /* ** This function does all the work for both the xConnect and xCreate methods. ** These tables have no persistent representation of their own, so xConnect ** and xCreate are identical operations. @@ -127087,11 +138345,11 @@ }else{ zFts3 = argv[3]; } nFts3 = (int)strlen(zFts3); - rc = sqlite3_declare_vtab(db, FTS3_TERMS_SCHEMA); + rc = sqlite3_declare_vtab(db, FTS3_AUX_SCHEMA); if( rc!=SQLITE_OK ) return rc; nByte = sizeof(Fts3auxTable) + sizeof(Fts3Table) + nDb + nFts3 + 2; p = (Fts3auxTable *)sqlite3_malloc(nByte); if( !p ) return SQLITE_NOMEM; @@ -127147,10 +138405,12 @@ ){ int i; int iEq = -1; int iGe = -1; int iLe = -1; + int iLangid = -1; + int iNext = 1; /* Next free argvIndex value */ UNUSED_PARAMETER(pVTab); /* This vtab delivers always results in "ORDER BY term ASC" order. */ if( pInfo->nOrderBy==1 @@ -127158,40 +138418,52 @@ && pInfo->aOrderBy[0].desc==0 ){ pInfo->orderByConsumed = 1; } - /* Search for equality and range constraints on the "term" column. */ + /* Search for equality and range constraints on the "term" column. + ** And equality constraints on the hidden "languageid" column. */ for(i=0; i<pInfo->nConstraint; i++){ - if( pInfo->aConstraint[i].usable && pInfo->aConstraint[i].iColumn==0 ){ + if( pInfo->aConstraint[i].usable ){ int op = pInfo->aConstraint[i].op; - if( op==SQLITE_INDEX_CONSTRAINT_EQ ) iEq = i; - if( op==SQLITE_INDEX_CONSTRAINT_LT ) iLe = i; - if( op==SQLITE_INDEX_CONSTRAINT_LE ) iLe = i; - if( op==SQLITE_INDEX_CONSTRAINT_GT ) iGe = i; - if( op==SQLITE_INDEX_CONSTRAINT_GE ) iGe = i; + int iCol = pInfo->aConstraint[i].iColumn; + + if( iCol==0 ){ + if( op==SQLITE_INDEX_CONSTRAINT_EQ ) iEq = i; + if( op==SQLITE_INDEX_CONSTRAINT_LT ) iLe = i; + if( op==SQLITE_INDEX_CONSTRAINT_LE ) iLe = i; + if( op==SQLITE_INDEX_CONSTRAINT_GT ) iGe = i; + if( op==SQLITE_INDEX_CONSTRAINT_GE ) iGe = i; + } + if( iCol==4 ){ + if( op==SQLITE_INDEX_CONSTRAINT_EQ ) iLangid = i; + } } } if( iEq>=0 ){ pInfo->idxNum = FTS4AUX_EQ_CONSTRAINT; - pInfo->aConstraintUsage[iEq].argvIndex = 1; + pInfo->aConstraintUsage[iEq].argvIndex = iNext++; pInfo->estimatedCost = 5; }else{ pInfo->idxNum = 0; pInfo->estimatedCost = 20000; if( iGe>=0 ){ pInfo->idxNum += FTS4AUX_GE_CONSTRAINT; - pInfo->aConstraintUsage[iGe].argvIndex = 1; + pInfo->aConstraintUsage[iGe].argvIndex = iNext++; pInfo->estimatedCost /= 2; } if( iLe>=0 ){ pInfo->idxNum += FTS4AUX_LE_CONSTRAINT; - pInfo->aConstraintUsage[iLe].argvIndex = 1 + (iGe>=0); + pInfo->aConstraintUsage[iLe].argvIndex = iNext++; pInfo->estimatedCost /= 2; } } + if( iLangid>=0 ){ + pInfo->aConstraintUsage[iLangid].argvIndex = iNext++; + pInfo->estimatedCost--; + } return SQLITE_OK; } /* @@ -127347,21 +138619,42 @@ sqlite3_value **apVal /* Arguments for the indexing scheme */ ){ Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab; int rc; - int isScan; + int isScan = 0; + int iLangVal = 0; /* Language id to query */ + + int iEq = -1; /* Index of term=? value in apVal */ + int iGe = -1; /* Index of term>=? value in apVal */ + int iLe = -1; /* Index of term<=? value in apVal */ + int iLangid = -1; /* Index of languageid=? value in apVal */ + int iNext = 0; UNUSED_PARAMETER(nVal); UNUSED_PARAMETER(idxStr); assert( idxStr==0 ); assert( idxNum==FTS4AUX_EQ_CONSTRAINT || idxNum==0 || idxNum==FTS4AUX_LE_CONSTRAINT || idxNum==FTS4AUX_GE_CONSTRAINT || idxNum==(FTS4AUX_LE_CONSTRAINT|FTS4AUX_GE_CONSTRAINT) ); - isScan = (idxNum!=FTS4AUX_EQ_CONSTRAINT); + + if( idxNum==FTS4AUX_EQ_CONSTRAINT ){ + iEq = iNext++; + }else{ + isScan = 1; + if( idxNum & FTS4AUX_GE_CONSTRAINT ){ + iGe = iNext++; + } + if( idxNum & FTS4AUX_LE_CONSTRAINT ){ + iLe = iNext++; + } + } + if( iNext<nVal ){ + iLangid = iNext++; + } /* In case this cursor is being reused, close and zero it. */ testcase(pCsr->filter.zTerm); sqlite3Fts3SegReaderFinish(&pCsr->csr); sqlite3_free((void *)pCsr->filter.zTerm); @@ -127369,26 +138662,39 @@ memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr); pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY; if( isScan ) pCsr->filter.flags |= FTS3_SEGMENT_SCAN; - if( idxNum&(FTS4AUX_EQ_CONSTRAINT|FTS4AUX_GE_CONSTRAINT) ){ + if( iEq>=0 || iGe>=0 ){ const unsigned char *zStr = sqlite3_value_text(apVal[0]); + assert( (iEq==0 && iGe==-1) || (iEq==-1 && iGe==0) ); if( zStr ){ pCsr->filter.zTerm = sqlite3_mprintf("%s", zStr); pCsr->filter.nTerm = sqlite3_value_bytes(apVal[0]); if( pCsr->filter.zTerm==0 ) return SQLITE_NOMEM; } } - if( idxNum&FTS4AUX_LE_CONSTRAINT ){ - int iIdx = (idxNum&FTS4AUX_GE_CONSTRAINT) ? 1 : 0; - pCsr->zStop = sqlite3_mprintf("%s", sqlite3_value_text(apVal[iIdx])); - pCsr->nStop = sqlite3_value_bytes(apVal[iIdx]); + + if( iLe>=0 ){ + pCsr->zStop = sqlite3_mprintf("%s", sqlite3_value_text(apVal[iLe])); + pCsr->nStop = sqlite3_value_bytes(apVal[iLe]); if( pCsr->zStop==0 ) return SQLITE_NOMEM; } + + if( iLangid>=0 ){ + iLangVal = sqlite3_value_int(apVal[iLangid]); - rc = sqlite3Fts3SegReaderCursor(pFts3, 0, 0, FTS3_SEGCURSOR_ALL, + /* If the user specified a negative value for the languageid, use zero + ** instead. This works, as the "languageid=?" constraint will also + ** be tested by the VDBE layer. The test will always be false (since + ** this module will not return a row with a negative languageid), and + ** so the overall query will return zero rows. */ + if( iLangVal<0 ) iLangVal = 0; + } + pCsr->iLangid = iLangVal; + + rc = sqlite3Fts3SegReaderCursor(pFts3, iLangVal, 0, FTS3_SEGCURSOR_ALL, pCsr->filter.zTerm, pCsr->filter.nTerm, 0, isScan, &pCsr->csr ); if( rc==SQLITE_OK ){ rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter); } @@ -127408,28 +138714,41 @@ /* ** xColumn - Return a column value. */ static int fts3auxColumnMethod( sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ - sqlite3_context *pContext, /* Context for sqlite3_result_xxx() calls */ + sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ int iCol /* Index of column to read value from */ ){ Fts3auxCursor *p = (Fts3auxCursor *)pCursor; assert( p->isEof==0 ); - if( iCol==0 ){ /* Column "term" */ - sqlite3_result_text(pContext, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT); - }else if( iCol==1 ){ /* Column "col" */ - if( p->iCol ){ - sqlite3_result_int(pContext, p->iCol-1); - }else{ - sqlite3_result_text(pContext, "*", -1, SQLITE_STATIC); - } - }else if( iCol==2 ){ /* Column "documents" */ - sqlite3_result_int64(pContext, p->aStat[p->iCol].nDoc); - }else{ /* Column "occurrences" */ - sqlite3_result_int64(pContext, p->aStat[p->iCol].nOcc); + switch( iCol ){ + case 0: /* term */ + sqlite3_result_text(pCtx, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT); + break; + + case 1: /* col */ + if( p->iCol ){ + sqlite3_result_int(pCtx, p->iCol-1); + }else{ + sqlite3_result_text(pCtx, "*", -1, SQLITE_STATIC); + } + break; + + case 2: /* documents */ + sqlite3_result_int64(pCtx, p->aStat[p->iCol].nDoc); + break; + + case 3: /* occurrences */ + sqlite3_result_int64(pCtx, p->aStat[p->iCol].nOcc); + break; + + default: /* languageid */ + assert( iCol==4 ); + sqlite3_result_int(pCtx, p->iLangid); + break; } return SQLITE_OK; } @@ -127639,10 +138958,15 @@ } *ppCsr = pCsr; return rc; } +/* +** Function getNextNode(), which is called by fts3ExprParse(), may itself +** call fts3ExprParse(). So this forward declaration is required. +*/ +static int fts3ExprParse(ParseContext *, const char *, int, Fts3Expr **, int *); /* ** Extract the next token from buffer z (length n) using the tokenizer ** and other information (column names etc.) in pParse. Create an Fts3Expr ** structure of type FTSQUERY_PHRASE containing a phrase consisting of this @@ -127664,13 +138988,20 @@ sqlite3_tokenizer *pTokenizer = pParse->pTokenizer; sqlite3_tokenizer_module const *pModule = pTokenizer->pModule; int rc; sqlite3_tokenizer_cursor *pCursor; Fts3Expr *pRet = 0; - int nConsumed = 0; + int i = 0; - rc = sqlite3Fts3OpenTokenizer(pTokenizer, pParse->iLangid, z, n, &pCursor); + /* Set variable i to the maximum number of bytes of input to tokenize. */ + for(i=0; i<n; i++){ + if( sqlite3_fts3_enable_parentheses && (z[i]=='(' || z[i]==')') ) break; + if( z[i]=='"' ) break; + } + + *pnConsumed = i; + rc = sqlite3Fts3OpenTokenizer(pTokenizer, pParse->iLangid, z, i, &pCursor); if( rc==SQLITE_OK ){ const char *zToken; int nToken = 0, iStart = 0, iEnd = 0, iPosition = 0; int nByte; /* total space to allocate */ @@ -127707,17 +139038,18 @@ break; } } } - nConsumed = iEnd; + *pnConsumed = iEnd; + }else if( i && rc==SQLITE_DONE ){ + rc = SQLITE_OK; } pModule->xClose(pCursor); } - *pnConsumed = nConsumed; *ppExpr = pRet; return rc; } @@ -127853,16 +139185,10 @@ sqlite3_free(p); *ppExpr = 0; return SQLITE_NOMEM; } -/* -** Function getNextNode(), which is called by fts3ExprParse(), may itself -** call fts3ExprParse(). So this forward declaration is required. -*/ -static int fts3ExprParse(ParseContext *, const char *, int, Fts3Expr **, int *); - /* ** The output variable *ppExpr is populated with an allocated Fts3Expr ** structure, or set to 0 if the end of the input buffer is reached. ** ** Returns an SQLite error code. SQLITE_OK if everything works, SQLITE_NOMEM @@ -127955,31 +139281,10 @@ ** user has supplied a token such as "ORacle". Continue. */ } } - /* Check for an open bracket. */ - if( sqlite3_fts3_enable_parentheses ){ - if( *zInput=='(' ){ - int nConsumed; - pParse->nNest++; - rc = fts3ExprParse(pParse, &zInput[1], nInput-1, ppExpr, &nConsumed); - if( rc==SQLITE_OK && !*ppExpr ){ - rc = SQLITE_DONE; - } - *pnConsumed = (int)((zInput - z) + 1 + nConsumed); - return rc; - } - - /* Check for a close bracket. */ - if( *zInput==')' ){ - pParse->nNest--; - *pnConsumed = (int)((zInput - z) + 1); - return SQLITE_DONE; - } - } - /* See if we are dealing with a quoted phrase. If this is the case, then ** search for the closing quote and pass the whole string to getNextString() ** for processing. This is easy to do, as fts3 has no syntax for escaping ** a quote character embedded in a string. */ @@ -127990,10 +139295,25 @@ return SQLITE_ERROR; } return getNextString(pParse, &zInput[1], ii-1, ppExpr); } + if( sqlite3_fts3_enable_parentheses ){ + if( *zInput=='(' ){ + int nConsumed = 0; + pParse->nNest++; + rc = fts3ExprParse(pParse, zInput+1, nInput-1, ppExpr, &nConsumed); + if( rc==SQLITE_OK && !*ppExpr ){ rc = SQLITE_DONE; } + *pnConsumed = (int)(zInput - z) + 1 + nConsumed; + return rc; + }else if( *zInput==')' ){ + pParse->nNest--; + *pnConsumed = (int)((zInput - z) + 1); + *ppExpr = 0; + return SQLITE_DONE; + } + } /* If control flows to this point, this must be a regular token, or ** the end of the input. Read a regular token using the sqlite3_tokenizer ** interface. Before doing so, figure out if there is an explicit ** column specifier for the token. @@ -128108,100 +139428,104 @@ int isRequirePhrase = 1; while( rc==SQLITE_OK ){ Fts3Expr *p = 0; int nByte = 0; - rc = getNextNode(pParse, zIn, nIn, &p, &nByte); - if( rc==SQLITE_OK ){ - int isPhrase; - - if( !sqlite3_fts3_enable_parentheses - && p->eType==FTSQUERY_PHRASE && pParse->isNot - ){ - /* Create an implicit NOT operator. */ - Fts3Expr *pNot = fts3MallocZero(sizeof(Fts3Expr)); - if( !pNot ){ - sqlite3Fts3ExprFree(p); - rc = SQLITE_NOMEM; - goto exprparse_out; - } - pNot->eType = FTSQUERY_NOT; - pNot->pRight = p; - p->pParent = pNot; - if( pNotBranch ){ - pNot->pLeft = pNotBranch; - pNotBranch->pParent = pNot; - } - pNotBranch = pNot; - p = pPrev; - }else{ - int eType = p->eType; - isPhrase = (eType==FTSQUERY_PHRASE || p->pLeft); - - /* The isRequirePhrase variable is set to true if a phrase or - ** an expression contained in parenthesis is required. If a - ** binary operator (AND, OR, NOT or NEAR) is encounted when - ** isRequirePhrase is set, this is a syntax error. - */ - if( !isPhrase && isRequirePhrase ){ - sqlite3Fts3ExprFree(p); - rc = SQLITE_ERROR; - goto exprparse_out; - } - - if( isPhrase && !isRequirePhrase ){ - /* Insert an implicit AND operator. */ - Fts3Expr *pAnd; - assert( pRet && pPrev ); - pAnd = fts3MallocZero(sizeof(Fts3Expr)); - if( !pAnd ){ - sqlite3Fts3ExprFree(p); - rc = SQLITE_NOMEM; - goto exprparse_out; - } - pAnd->eType = FTSQUERY_AND; - insertBinaryOperator(&pRet, pPrev, pAnd); - pPrev = pAnd; - } - - /* This test catches attempts to make either operand of a NEAR - ** operator something other than a phrase. For example, either of - ** the following: - ** - ** (bracketed expression) NEAR phrase - ** phrase NEAR (bracketed expression) - ** - ** Return an error in either case. - */ - if( pPrev && ( - (eType==FTSQUERY_NEAR && !isPhrase && pPrev->eType!=FTSQUERY_PHRASE) - || (eType!=FTSQUERY_PHRASE && isPhrase && pPrev->eType==FTSQUERY_NEAR) - )){ - sqlite3Fts3ExprFree(p); - rc = SQLITE_ERROR; - goto exprparse_out; - } - - if( isPhrase ){ - if( pRet ){ - assert( pPrev && pPrev->pLeft && pPrev->pRight==0 ); - pPrev->pRight = p; - p->pParent = pPrev; - }else{ - pRet = p; - } - }else{ - insertBinaryOperator(&pRet, pPrev, p); - } - isRequirePhrase = !isPhrase; + + rc = getNextNode(pParse, zIn, nIn, &p, &nByte); + assert( nByte>0 || (rc!=SQLITE_OK && p==0) ); + if( rc==SQLITE_OK ){ + if( p ){ + int isPhrase; + + if( !sqlite3_fts3_enable_parentheses + && p->eType==FTSQUERY_PHRASE && pParse->isNot + ){ + /* Create an implicit NOT operator. */ + Fts3Expr *pNot = fts3MallocZero(sizeof(Fts3Expr)); + if( !pNot ){ + sqlite3Fts3ExprFree(p); + rc = SQLITE_NOMEM; + goto exprparse_out; + } + pNot->eType = FTSQUERY_NOT; + pNot->pRight = p; + p->pParent = pNot; + if( pNotBranch ){ + pNot->pLeft = pNotBranch; + pNotBranch->pParent = pNot; + } + pNotBranch = pNot; + p = pPrev; + }else{ + int eType = p->eType; + isPhrase = (eType==FTSQUERY_PHRASE || p->pLeft); + + /* The isRequirePhrase variable is set to true if a phrase or + ** an expression contained in parenthesis is required. If a + ** binary operator (AND, OR, NOT or NEAR) is encounted when + ** isRequirePhrase is set, this is a syntax error. + */ + if( !isPhrase && isRequirePhrase ){ + sqlite3Fts3ExprFree(p); + rc = SQLITE_ERROR; + goto exprparse_out; + } + + if( isPhrase && !isRequirePhrase ){ + /* Insert an implicit AND operator. */ + Fts3Expr *pAnd; + assert( pRet && pPrev ); + pAnd = fts3MallocZero(sizeof(Fts3Expr)); + if( !pAnd ){ + sqlite3Fts3ExprFree(p); + rc = SQLITE_NOMEM; + goto exprparse_out; + } + pAnd->eType = FTSQUERY_AND; + insertBinaryOperator(&pRet, pPrev, pAnd); + pPrev = pAnd; + } + + /* This test catches attempts to make either operand of a NEAR + ** operator something other than a phrase. For example, either of + ** the following: + ** + ** (bracketed expression) NEAR phrase + ** phrase NEAR (bracketed expression) + ** + ** Return an error in either case. + */ + if( pPrev && ( + (eType==FTSQUERY_NEAR && !isPhrase && pPrev->eType!=FTSQUERY_PHRASE) + || (eType!=FTSQUERY_PHRASE && isPhrase && pPrev->eType==FTSQUERY_NEAR) + )){ + sqlite3Fts3ExprFree(p); + rc = SQLITE_ERROR; + goto exprparse_out; + } + + if( isPhrase ){ + if( pRet ){ + assert( pPrev && pPrev->pLeft && pPrev->pRight==0 ); + pPrev->pRight = p; + p->pParent = pPrev; + }else{ + pRet = p; + } + }else{ + insertBinaryOperator(&pRet, pPrev, p); + } + isRequirePhrase = !isPhrase; + } + pPrev = p; } assert( nByte>0 ); } assert( rc!=SQLITE_OK || (nByte>0 && nByte<=nIn) ); nIn -= nByte; zIn += nByte; - pPrev = p; } if( rc==SQLITE_DONE && pRet && isRequirePhrase ){ rc = SQLITE_ERROR; } @@ -128859,17 +140183,17 @@ /* ** Hash and comparison functions when the mode is FTS3_HASH_STRING */ static int fts3StrHash(const void *pKey, int nKey){ const char *z = (const char *)pKey; - int h = 0; + unsigned h = 0; if( nKey<=0 ) nKey = (int) strlen(z); while( nKey > 0 ){ h = (h<<3) ^ h ^ *z++; nKey--; } - return h & 0x7fffffff; + return (int)(h & 0x7fffffff); } static int fts3StrCompare(const void *pKey1, int n1, const void *pKey2, int n2){ if( n1!=n2 ) return 1; return strncmp((const char*)pKey1,(const char*)pKey2,n1); } @@ -129330,11 +140654,11 @@ ** Return true if the m-value for z is 1 or more. In other words, ** return true if z contains at least one vowel that is followed ** by a consonant. ** ** In this routine z[] is in reverse order. So we are really looking -** for an instance of of a consonant followed by a vowel. +** for an instance of a consonant followed by a vowel. */ static int m_gt_0(const char *z){ while( isVowel(z) ){ z++; } if( *z==0 ) return 0; while( isConsonant(z) ){ z++; } @@ -129550,61 +140874,74 @@ } /* Step 2 */ switch( z[1] ){ case 'a': - stem(&z, "lanoita", "ate", m_gt_0) || - stem(&z, "lanoit", "tion", m_gt_0); + if( !stem(&z, "lanoita", "ate", m_gt_0) ){ + stem(&z, "lanoit", "tion", m_gt_0); + } break; case 'c': - stem(&z, "icne", "ence", m_gt_0) || - stem(&z, "icna", "ance", m_gt_0); + if( !stem(&z, "icne", "ence", m_gt_0) ){ + stem(&z, "icna", "ance", m_gt_0); + } break; case 'e': stem(&z, "rezi", "ize", m_gt_0); break; case 'g': stem(&z, "igol", "log", m_gt_0); break; case 'l': - stem(&z, "ilb", "ble", m_gt_0) || - stem(&z, "illa", "al", m_gt_0) || - stem(&z, "iltne", "ent", m_gt_0) || - stem(&z, "ile", "e", m_gt_0) || - stem(&z, "ilsuo", "ous", m_gt_0); + if( !stem(&z, "ilb", "ble", m_gt_0) + && !stem(&z, "illa", "al", m_gt_0) + && !stem(&z, "iltne", "ent", m_gt_0) + && !stem(&z, "ile", "e", m_gt_0) + ){ + stem(&z, "ilsuo", "ous", m_gt_0); + } break; case 'o': - stem(&z, "noitazi", "ize", m_gt_0) || - stem(&z, "noita", "ate", m_gt_0) || - stem(&z, "rota", "ate", m_gt_0); + if( !stem(&z, "noitazi", "ize", m_gt_0) + && !stem(&z, "noita", "ate", m_gt_0) + ){ + stem(&z, "rota", "ate", m_gt_0); + } break; case 's': - stem(&z, "msila", "al", m_gt_0) || - stem(&z, "ssenevi", "ive", m_gt_0) || - stem(&z, "ssenluf", "ful", m_gt_0) || - stem(&z, "ssensuo", "ous", m_gt_0); + if( !stem(&z, "msila", "al", m_gt_0) + && !stem(&z, "ssenevi", "ive", m_gt_0) + && !stem(&z, "ssenluf", "ful", m_gt_0) + ){ + stem(&z, "ssensuo", "ous", m_gt_0); + } break; case 't': - stem(&z, "itila", "al", m_gt_0) || - stem(&z, "itivi", "ive", m_gt_0) || - stem(&z, "itilib", "ble", m_gt_0); + if( !stem(&z, "itila", "al", m_gt_0) + && !stem(&z, "itivi", "ive", m_gt_0) + ){ + stem(&z, "itilib", "ble", m_gt_0); + } break; } /* Step 3 */ switch( z[0] ){ case 'e': - stem(&z, "etaci", "ic", m_gt_0) || - stem(&z, "evita", "", m_gt_0) || - stem(&z, "ezila", "al", m_gt_0); + if( !stem(&z, "etaci", "ic", m_gt_0) + && !stem(&z, "evita", "", m_gt_0) + ){ + stem(&z, "ezila", "al", m_gt_0); + } break; case 'i': stem(&z, "itici", "ic", m_gt_0); break; case 'l': - stem(&z, "laci", "ic", m_gt_0) || - stem(&z, "luf", "", m_gt_0); + if( !stem(&z, "laci", "ic", m_gt_0) ){ + stem(&z, "luf", "", m_gt_0); + } break; case 's': stem(&z, "ssen", "", m_gt_0); break; } @@ -129641,13 +140978,15 @@ if( z[2]=='a' ){ if( m_gt_1(z+3) ){ z += 3; } }else if( z[2]=='e' ){ - stem(&z, "tneme", "", m_gt_1) || - stem(&z, "tnem", "", m_gt_1) || - stem(&z, "tne", "", m_gt_1); + if( !stem(&z, "tneme", "", m_gt_1) + && !stem(&z, "tnem", "", m_gt_1) + ){ + stem(&z, "tne", "", m_gt_1); + } } } break; case 'o': if( z[0]=='u' ){ @@ -129662,12 +141001,13 @@ if( z[0]=='m' && z[2]=='i' && m_gt_1(z+3) ){ z += 3; } break; case 't': - stem(&z, "eta", "", m_gt_1) || - stem(&z, "iti", "", m_gt_1); + if( !stem(&z, "eta", "", m_gt_1) ){ + stem(&z, "iti", "", m_gt_1); + } break; case 'u': if( z[0]=='s' && z[2]=='o' && m_gt_1(z+3) ){ z += 3; } @@ -129864,11 +141204,11 @@ nName = sqlite3_value_bytes(argv[0])+1; if( argc==2 ){ void *pOld; int n = sqlite3_value_bytes(argv[1]); - if( n!=sizeof(pPtr) ){ + if( zName==0 || n!=sizeof(pPtr) ){ sqlite3_result_error(context, "argument type mismatch", -1); return; } pPtr = *(void **)sqlite3_value_blob(argv[1]); pOld = sqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr); @@ -129875,11 +141215,13 @@ if( pOld==pPtr ){ sqlite3_result_error(context, "out of memory", -1); return; } }else{ - pPtr = sqlite3Fts3HashFind(pHash, zName, nName); + if( zName ){ + pPtr = sqlite3Fts3HashFind(pHash, zName, nName); + } if( !pPtr ){ char *zErr = sqlite3_mprintf("unknown tokenizer: %s", zName); sqlite3_result_error(context, zErr, -1); sqlite3_free(zErr); return; @@ -129956,10 +141298,14 @@ zCopy = sqlite3_mprintf("%s", zArg); if( !zCopy ) return SQLITE_NOMEM; zEnd = &zCopy[strlen(zCopy)]; z = (char *)sqlite3Fts3NextToken(zCopy, &n); + if( z==0 ){ + assert( n==0 ); + z = zCopy; + } z[n] = '\0'; sqlite3Fts3Dequote(z); m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash,z,(int)strlen(z)+1); if( !m ){ @@ -130683,11 +142029,11 @@ int argc, /* Number of elements in argv array */ const char * const *argv, /* xCreate/xConnect argument array */ sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ char **pzErr /* OUT: sqlite3_malloc'd error message */ ){ - Fts3tokTable *pTab; + Fts3tokTable *pTab = 0; const sqlite3_tokenizer_module *pMod = 0; sqlite3_tokenizer *pTok = 0; int rc; char **azDequote = 0; int nDequote; @@ -131169,10 +142515,11 @@ int nMalloc; /* Size of malloc'd buffer at zMalloc */ char *zMalloc; /* Malloc'd space (possibly) used for zTerm */ int nSize; /* Size of allocation at aData */ int nData; /* Bytes of data in aData */ char *aData; /* Pointer to block from malloc() */ + i64 nLeafData; /* Number of bytes of leaf data written */ }; /* ** Type SegmentNode is used by the following three functions to create ** the interior part of the segment b+-tree structures (everything except @@ -131243,10 +142590,14 @@ #define SQL_SELECT_SEGDIR 32 #define SQL_CHOMP_SEGDIR 33 #define SQL_SEGMENT_IS_APPENDABLE 34 #define SQL_SELECT_INDEXES 35 #define SQL_SELECT_MXLEVEL 36 + +#define SQL_SELECT_LEVEL_RANGE2 37 +#define SQL_UPDATE_LEVEL_IDX 38 +#define SQL_UPDATE_LEVEL 39 /* ** This function is used to obtain an SQLite prepared statement handle ** for the statement identified by the second argument. If successful, ** *pp is set to the requested statement handle and SQLITE_OK returned. @@ -131345,11 +142696,22 @@ ** Return the list of valid segment indexes for absolute level ? */ /* 35 */ "SELECT idx FROM %Q.'%q_segdir' WHERE level=? ORDER BY 1 ASC", /* SQL_SELECT_MXLEVEL ** Return the largest relative level in the FTS index or indexes. */ -/* 36 */ "SELECT max( level %% 1024 ) FROM %Q.'%q_segdir'" +/* 36 */ "SELECT max( level %% 1024 ) FROM %Q.'%q_segdir'", + + /* Return segments in order from oldest to newest.*/ +/* 37 */ "SELECT level, idx, end_block " + "FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ? " + "ORDER BY level DESC, idx ASC", + + /* Update statements used while promoting segments */ +/* 38 */ "UPDATE OR FAIL %Q.'%q_segdir' SET level=-1,idx=? " + "WHERE level=? AND idx=?", +/* 39 */ "UPDATE OR FAIL %Q.'%q_segdir' SET level=? WHERE level=-1" + }; int rc = SQLITE_OK; sqlite3_stmt *pStmt; assert( SizeofArray(azSql)==SizeofArray(p->aStmt) ); @@ -132320,12 +143682,12 @@ rc = fts3SegReaderRequire(pReader, pNext, FTS3_VARINT_MAX*2); if( rc!=SQLITE_OK ) return rc; /* Because of the FTS3_NODE_PADDING bytes of padding, the following is ** safe (no risk of overread) even if the node data is corrupted. */ - pNext += sqlite3Fts3GetVarint32(pNext, &nPrefix); - pNext += sqlite3Fts3GetVarint32(pNext, &nSuffix); + pNext += fts3GetVarint32(pNext, &nPrefix); + pNext += fts3GetVarint32(pNext, &nSuffix); if( nPrefix<0 || nSuffix<=0 || &pNext[nSuffix]>&pReader->aNode[pReader->nNode] ){ return FTS_CORRUPT_VTAB; } @@ -132344,11 +143706,11 @@ if( rc!=SQLITE_OK ) return rc; memcpy(&pReader->zTerm[nPrefix], pNext, nSuffix); pReader->nTerm = nPrefix+nSuffix; pNext += nSuffix; - pNext += sqlite3Fts3GetVarint32(pNext, &pReader->nDoclist); + pNext += fts3GetVarint32(pNext, &pReader->nDoclist); pReader->aDoclist = pNext; pReader->pOffsetList = 0; /* Check that the doclist does not appear to extend past the end of the ** b-tree node. And that the final byte of the doclist is 0x00. If either @@ -132585,11 +143947,14 @@ /* ** This is a comparison function used as a qsort() callback when sorting ** an array of pending terms by term. This occurs as part of flushing ** the contents of the pending-terms hash table to the database. */ -static int fts3CompareElemByTerm(const void *lhs, const void *rhs){ +static int SQLITE_CDECL fts3CompareElemByTerm( + const void *lhs, + const void *rhs +){ char *z1 = fts3HashKey(*(Fts3HashElem **)lhs); char *z2 = fts3HashKey(*(Fts3HashElem **)rhs); int n1 = fts3HashKeysize(*(Fts3HashElem **)lhs); int n2 = fts3HashKeysize(*(Fts3HashElem **)rhs); @@ -132886,10 +144251,11 @@ sqlite3_int64 iLevel, /* Value for "level" field (absolute level) */ int iIdx, /* Value for "idx" field */ sqlite3_int64 iStartBlock, /* Value for "start_block" field */ sqlite3_int64 iLeafEndBlock, /* Value for "leaves_end_block" field */ sqlite3_int64 iEndBlock, /* Value for "end_block" field */ + sqlite3_int64 nLeafData, /* Bytes of leaf data in segment */ char *zRoot, /* Blob value for "root" field */ int nRoot /* Number of bytes in buffer zRoot */ ){ sqlite3_stmt *pStmt; int rc = fts3SqlStmt(p, SQL_INSERT_SEGDIR, &pStmt, 0); @@ -132896,11 +144262,17 @@ if( rc==SQLITE_OK ){ sqlite3_bind_int64(pStmt, 1, iLevel); sqlite3_bind_int(pStmt, 2, iIdx); sqlite3_bind_int64(pStmt, 3, iStartBlock); sqlite3_bind_int64(pStmt, 4, iLeafEndBlock); - sqlite3_bind_int64(pStmt, 5, iEndBlock); + if( nLeafData==0 ){ + sqlite3_bind_int64(pStmt, 5, iEndBlock); + }else{ + char *zEnd = sqlite3_mprintf("%lld %lld", iEndBlock, nLeafData); + if( !zEnd ) return SQLITE_NOMEM; + sqlite3_bind_text(pStmt, 5, zEnd, -1, sqlite3_free); + } sqlite3_bind_blob(pStmt, 6, zRoot, nRoot, SQLITE_STATIC); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); } return rc; @@ -133221,10 +144593,13 @@ sqlite3Fts3VarintLen(nTerm) + /* varint containing suffix size */ nTerm + /* Term suffix */ sqlite3Fts3VarintLen(nDoclist) + /* Size of doclist */ nDoclist; /* Doclist data */ } + + /* Increase the total number of bytes written to account for the new entry. */ + pWriter->nLeafData += nReq; /* If the buffer currently allocated is too small for this entry, realloc ** the buffer to make it large enough. */ if( nReq>pWriter->nSize ){ @@ -133293,17 +144668,17 @@ if( rc==SQLITE_OK ){ rc = fts3NodeWrite(p, pWriter->pTree, 1, pWriter->iFirst, pWriter->iFree, &iLast, &zRoot, &nRoot); } if( rc==SQLITE_OK ){ - rc = fts3WriteSegdir( - p, iLevel, iIdx, pWriter->iFirst, iLastLeaf, iLast, zRoot, nRoot); + rc = fts3WriteSegdir(p, iLevel, iIdx, + pWriter->iFirst, iLastLeaf, iLast, pWriter->nLeafData, zRoot, nRoot); } }else{ /* The entire tree fits on the root node. Write it to the segdir table. */ - rc = fts3WriteSegdir( - p, iLevel, iIdx, 0, 0, 0, pWriter->aData, pWriter->nData); + rc = fts3WriteSegdir(p, iLevel, iIdx, + 0, 0, 0, pWriter->nLeafData, pWriter->aData, pWriter->nData); } p->nLeafAdd++; return rc; } @@ -133382,10 +144757,41 @@ if( SQLITE_ROW==sqlite3_step(pStmt) ){ *pnMax = sqlite3_column_int64(pStmt, 0); } return sqlite3_reset(pStmt); } + +/* +** iAbsLevel is an absolute level that may be assumed to exist within +** the database. This function checks if it is the largest level number +** within its index. Assuming no error occurs, *pbMax is set to 1 if +** iAbsLevel is indeed the largest level, or 0 otherwise, and SQLITE_OK +** is returned. If an error occurs, an error code is returned and the +** final value of *pbMax is undefined. +*/ +static int fts3SegmentIsMaxLevel(Fts3Table *p, i64 iAbsLevel, int *pbMax){ + + /* Set pStmt to the compiled version of: + ** + ** SELECT max(level) FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ? + ** + ** (1024 is actually the value of macro FTS3_SEGDIR_PREFIXLEVEL_STR). + */ + sqlite3_stmt *pStmt; + int rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR_MAX_LEVEL, &pStmt, 0); + if( rc!=SQLITE_OK ) return rc; + sqlite3_bind_int64(pStmt, 1, iAbsLevel+1); + sqlite3_bind_int64(pStmt, 2, + ((iAbsLevel/FTS3_SEGDIR_MAXLEVEL)+1) * FTS3_SEGDIR_MAXLEVEL + ); + + *pbMax = 0; + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + *pbMax = sqlite3_column_type(pStmt, 0)==SQLITE_NULL; + } + return sqlite3_reset(pStmt); +} /* ** Delete all entries in the %_segments table associated with the segment ** opened with seg-reader pSeg. This function does not affect the contents ** of the %_segdir table. @@ -133505,11 +144911,11 @@ pList = p; if( nList==0 ){ break; } p = &pList[1]; - p += sqlite3Fts3GetVarint32(p, &iCurrent); + p += fts3GetVarint32(p, &iCurrent); } if( bZero && &pList[nList]!=pEnd ){ memset(&pList[nList], 0, pEnd - &pList[nList]); } @@ -133917,10 +145323,144 @@ pCsr->nSegment = 0; pCsr->apSegment = 0; pCsr->aBuffer = 0; } } + +/* +** Decode the "end_block" field, selected by column iCol of the SELECT +** statement passed as the first argument. +** +** The "end_block" field may contain either an integer, or a text field +** containing the text representation of two non-negative integers separated +** by one or more space (0x20) characters. In the first case, set *piEndBlock +** to the integer value and *pnByte to zero before returning. In the second, +** set *piEndBlock to the first value and *pnByte to the second. +*/ +static void fts3ReadEndBlockField( + sqlite3_stmt *pStmt, + int iCol, + i64 *piEndBlock, + i64 *pnByte +){ + const unsigned char *zText = sqlite3_column_text(pStmt, iCol); + if( zText ){ + int i; + int iMul = 1; + i64 iVal = 0; + for(i=0; zText[i]>='0' && zText[i]<='9'; i++){ + iVal = iVal*10 + (zText[i] - '0'); + } + *piEndBlock = iVal; + while( zText[i]==' ' ) i++; + iVal = 0; + if( zText[i]=='-' ){ + i++; + iMul = -1; + } + for(/* no-op */; zText[i]>='0' && zText[i]<='9'; i++){ + iVal = iVal*10 + (zText[i] - '0'); + } + *pnByte = (iVal * (i64)iMul); + } +} + + +/* +** A segment of size nByte bytes has just been written to absolute level +** iAbsLevel. Promote any segments that should be promoted as a result. +*/ +static int fts3PromoteSegments( + Fts3Table *p, /* FTS table handle */ + sqlite3_int64 iAbsLevel, /* Absolute level just updated */ + sqlite3_int64 nByte /* Size of new segment at iAbsLevel */ +){ + int rc = SQLITE_OK; + sqlite3_stmt *pRange; + + rc = fts3SqlStmt(p, SQL_SELECT_LEVEL_RANGE2, &pRange, 0); + + if( rc==SQLITE_OK ){ + int bOk = 0; + i64 iLast = (iAbsLevel/FTS3_SEGDIR_MAXLEVEL + 1) * FTS3_SEGDIR_MAXLEVEL - 1; + i64 nLimit = (nByte*3)/2; + + /* Loop through all entries in the %_segdir table corresponding to + ** segments in this index on levels greater than iAbsLevel. If there is + ** at least one such segment, and it is possible to determine that all + ** such segments are smaller than nLimit bytes in size, they will be + ** promoted to level iAbsLevel. */ + sqlite3_bind_int64(pRange, 1, iAbsLevel+1); + sqlite3_bind_int64(pRange, 2, iLast); + while( SQLITE_ROW==sqlite3_step(pRange) ){ + i64 nSize = 0, dummy; + fts3ReadEndBlockField(pRange, 2, &dummy, &nSize); + if( nSize<=0 || nSize>nLimit ){ + /* If nSize==0, then the %_segdir.end_block field does not not + ** contain a size value. This happens if it was written by an + ** old version of FTS. In this case it is not possible to determine + ** the size of the segment, and so segment promotion does not + ** take place. */ + bOk = 0; + break; + } + bOk = 1; + } + rc = sqlite3_reset(pRange); + + if( bOk ){ + int iIdx = 0; + sqlite3_stmt *pUpdate1 = 0; + sqlite3_stmt *pUpdate2 = 0; + + if( rc==SQLITE_OK ){ + rc = fts3SqlStmt(p, SQL_UPDATE_LEVEL_IDX, &pUpdate1, 0); + } + if( rc==SQLITE_OK ){ + rc = fts3SqlStmt(p, SQL_UPDATE_LEVEL, &pUpdate2, 0); + } + + if( rc==SQLITE_OK ){ + + /* Loop through all %_segdir entries for segments in this index with + ** levels equal to or greater than iAbsLevel. As each entry is visited, + ** updated it to set (level = -1) and (idx = N), where N is 0 for the + ** oldest segment in the range, 1 for the next oldest, and so on. + ** + ** In other words, move all segments being promoted to level -1, + ** setting the "idx" fields as appropriate to keep them in the same + ** order. The contents of level -1 (which is never used, except + ** transiently here), will be moved back to level iAbsLevel below. */ + sqlite3_bind_int64(pRange, 1, iAbsLevel); + while( SQLITE_ROW==sqlite3_step(pRange) ){ + sqlite3_bind_int(pUpdate1, 1, iIdx++); + sqlite3_bind_int(pUpdate1, 2, sqlite3_column_int(pRange, 0)); + sqlite3_bind_int(pUpdate1, 3, sqlite3_column_int(pRange, 1)); + sqlite3_step(pUpdate1); + rc = sqlite3_reset(pUpdate1); + if( rc!=SQLITE_OK ){ + sqlite3_reset(pRange); + break; + } + } + } + if( rc==SQLITE_OK ){ + rc = sqlite3_reset(pRange); + } + + /* Move level -1 to level iAbsLevel */ + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pUpdate2, 1, iAbsLevel); + sqlite3_step(pUpdate2); + rc = sqlite3_reset(pUpdate2); + } + } + } + + + return rc; +} /* ** Merge all level iLevel segments in the database into a single ** iLevel+1 segment. Or, if iLevel<0, merge all segments into a ** single segment with a level equal to the numerically largest level @@ -133942,10 +145482,11 @@ sqlite3_int64 iNewLevel = 0; /* Level/index to create new segment at */ SegmentWriter *pWriter = 0; /* Used to write the new, merged, segment */ Fts3SegFilter filter; /* Segment term filter condition */ Fts3MultiSegReader csr; /* Cursor to iterate through level(s) */ int bIgnoreEmpty = 0; /* True to ignore empty segments */ + i64 iMaxLevel = 0; /* Max level number for this index/langid */ assert( iLevel==FTS3_SEGCURSOR_ALL || iLevel==FTS3_SEGCURSOR_PENDING || iLevel>=0 ); @@ -133952,10 +145493,15 @@ assert( iLevel<FTS3_SEGDIR_MAXLEVEL ); assert( iIndex>=0 && iIndex<p->nIndex ); rc = sqlite3Fts3SegReaderCursor(p, iLangid, iIndex, iLevel, 0, 0, 1, 0, &csr); if( rc!=SQLITE_OK || csr.nSegment==0 ) goto finished; + + if( iLevel!=FTS3_SEGCURSOR_PENDING ){ + rc = fts3SegmentMaxLevel(p, iLangid, iIndex, &iMaxLevel); + if( rc!=SQLITE_OK ) goto finished; + } if( iLevel==FTS3_SEGCURSOR_ALL ){ /* This call is to merge all segments in the database to a single ** segment. The level of the new segment is equal to the numerically ** greatest segment level currently present in the database for this @@ -133962,25 +145508,25 @@ ** index. The idx of the new segment is always 0. */ if( csr.nSegment==1 ){ rc = SQLITE_DONE; goto finished; } - rc = fts3SegmentMaxLevel(p, iLangid, iIndex, &iNewLevel); + iNewLevel = iMaxLevel; bIgnoreEmpty = 1; - }else if( iLevel==FTS3_SEGCURSOR_PENDING ){ - iNewLevel = getAbsoluteLevel(p, iLangid, iIndex, 0); - rc = fts3AllocateSegdirIdx(p, iLangid, iIndex, 0, &iIdx); }else{ /* This call is to merge all segments at level iLevel. find the next ** available segment index at level iLevel+1. The call to ** fts3AllocateSegdirIdx() will merge the segments at level iLevel+1 to ** a single iLevel+2 segment if necessary. */ - rc = fts3AllocateSegdirIdx(p, iLangid, iIndex, iLevel+1, &iIdx); + assert( FTS3_SEGCURSOR_PENDING==-1 ); iNewLevel = getAbsoluteLevel(p, iLangid, iIndex, iLevel+1); + rc = fts3AllocateSegdirIdx(p, iLangid, iIndex, iLevel+1, &iIdx); + bIgnoreEmpty = (iLevel!=FTS3_SEGCURSOR_PENDING) && (iNewLevel>iMaxLevel); } if( rc!=SQLITE_OK ) goto finished; + assert( csr.nSegment>0 ); assert( iNewLevel>=getAbsoluteLevel(p, iLangid, iIndex, 0) ); assert( iNewLevel<getAbsoluteLevel(p, iLangid, iIndex,FTS3_SEGDIR_MAXLEVEL) ); memset(&filter, 0, sizeof(Fts3SegFilter)); @@ -133993,29 +145539,36 @@ if( rc!=SQLITE_ROW ) break; rc = fts3SegWriterAdd(p, &pWriter, 1, csr.zTerm, csr.nTerm, csr.aDoclist, csr.nDoclist); } if( rc!=SQLITE_OK ) goto finished; - assert( pWriter ); + assert( pWriter || bIgnoreEmpty ); if( iLevel!=FTS3_SEGCURSOR_PENDING ){ rc = fts3DeleteSegdir( p, iLangid, iIndex, iLevel, csr.apSegment, csr.nSegment ); if( rc!=SQLITE_OK ) goto finished; } - rc = fts3SegWriterFlush(p, pWriter, iNewLevel, iIdx); + if( pWriter ){ + rc = fts3SegWriterFlush(p, pWriter, iNewLevel, iIdx); + if( rc==SQLITE_OK ){ + if( iLevel==FTS3_SEGCURSOR_PENDING || iNewLevel<iMaxLevel ){ + rc = fts3PromoteSegments(p, iNewLevel, pWriter->nLeafData); + } + } + } finished: fts3SegWriterFree(pWriter); sqlite3Fts3SegReaderFinish(&csr); return rc; } /* -** Flush the contents of pendingTerms to level 0 segments. +** Flush the contents of pendingTerms to level 0 segments. */ SQLITE_PRIVATE int sqlite3Fts3PendingTermsFlush(Fts3Table *p){ int rc = SQLITE_OK; int i; @@ -134027,18 +145580,23 @@ /* Determine the auto-incr-merge setting if unknown. If enabled, ** estimate the number of leaf blocks of content to be written */ if( rc==SQLITE_OK && p->bHasStat - && p->bAutoincrmerge==0xff && p->nLeafAdd>0 + && p->nAutoincrmerge==0xff && p->nLeafAdd>0 ){ sqlite3_stmt *pStmt = 0; rc = fts3SqlStmt(p, SQL_SELECT_STAT, &pStmt, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int(pStmt, 1, FTS_STAT_AUTOINCRMERGE); rc = sqlite3_step(pStmt); - p->bAutoincrmerge = (rc==SQLITE_ROW && sqlite3_column_int(pStmt, 0)); + if( rc==SQLITE_ROW ){ + p->nAutoincrmerge = sqlite3_column_int(pStmt, 0); + if( p->nAutoincrmerge==1 ) p->nAutoincrmerge = 8; + }else if( rc==SQLITE_DONE ){ + p->nAutoincrmerge = 0; + } rc = sqlite3_reset(pStmt); } } return rc; } @@ -134402,10 +145960,12 @@ int nWork; /* Number of leaf pages flushed */ sqlite3_int64 iAbsLevel; /* Absolute level of input segments */ int iIdx; /* Index of *output* segment in iAbsLevel+1 */ sqlite3_int64 iStart; /* Block number of first allocated block */ sqlite3_int64 iEnd; /* Block number of last allocated block */ + sqlite3_int64 nLeafData; /* Bytes of leaf page data so far */ + u8 bNoLeafData; /* If true, store 0 for segment size */ NodeWriter aNodeWriter[FTS_MAX_APPENDABLE_HEIGHT]; }; /* ** An object of the following type is used to read data from a single @@ -134470,21 +146030,21 @@ if( p->iOff>=p->nNode ){ /* EOF */ p->aNode = 0; }else{ if( bFirst==0 ){ - p->iOff += sqlite3Fts3GetVarint32(&p->aNode[p->iOff], &nPrefix); + p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &nPrefix); } - p->iOff += sqlite3Fts3GetVarint32(&p->aNode[p->iOff], &nSuffix); + p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &nSuffix); blobGrowBuffer(&p->term, nPrefix+nSuffix, &rc); if( rc==SQLITE_OK ){ memcpy(&p->term.a[nPrefix], &p->aNode[p->iOff], nSuffix); p->term.n = nPrefix+nSuffix; p->iOff += nSuffix; if( p->iChild==0 ){ - p->iOff += sqlite3Fts3GetVarint32(&p->aNode[p->iOff], &p->nDoclist); + p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &p->nDoclist); p->aDoclist = &p->aNode[p->iOff]; p->iOff += p->nDoclist; } } } @@ -134740,12 +146300,12 @@ nSpace = 1; nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist; } + pWriter->nLeafData += nSpace; blobGrowBuffer(&pLeaf->block, pLeaf->block.n + nSpace, &rc); - if( rc==SQLITE_OK ){ if( pLeaf->block.n==0 ){ pLeaf->block.n = 1; pLeaf->block.a[0] = '\0'; } @@ -134840,10 +146400,11 @@ pWriter->iAbsLevel+1, /* level */ pWriter->iIdx, /* idx */ pWriter->iStart, /* start_block */ pWriter->aNodeWriter[0].iBlock, /* leaves_end_block */ pWriter->iEnd, /* end_block */ + (pWriter->bNoLeafData==0 ? pWriter->nLeafData : 0), /* end_block */ pRoot->block.a, pRoot->block.n /* root */ ); } sqlite3_free(pRoot->block.a); sqlite3_free(pRoot->key.a); @@ -134941,11 +146502,15 @@ sqlite3_bind_int64(pSelect, 1, iAbsLevel+1); sqlite3_bind_int(pSelect, 2, iIdx); if( sqlite3_step(pSelect)==SQLITE_ROW ){ iStart = sqlite3_column_int64(pSelect, 1); iLeafEnd = sqlite3_column_int64(pSelect, 2); - iEnd = sqlite3_column_int64(pSelect, 3); + fts3ReadEndBlockField(pSelect, 3, &iEnd, &pWriter->nLeafData); + if( pWriter->nLeafData<0 ){ + pWriter->nLeafData = pWriter->nLeafData * -1; + } + pWriter->bNoLeafData = (pWriter->nLeafData==0); nRoot = sqlite3_column_bytes(pSelect, 4); aRoot = sqlite3_column_blob(pSelect, 4); }else{ return sqlite3_reset(pSelect); } @@ -135532,25 +147097,25 @@ while( i>0 && (pHint->a[i-1] & 0x80) ) i--; while( i>0 && (pHint->a[i-1] & 0x80) ) i--; pHint->n = i; i += sqlite3Fts3GetVarint(&pHint->a[i], piAbsLevel); - i += sqlite3Fts3GetVarint32(&pHint->a[i], pnInput); + i += fts3GetVarint32(&pHint->a[i], pnInput); if( i!=nHint ) return SQLITE_CORRUPT_VTAB; return SQLITE_OK; } /* ** Attempt an incremental merge that writes nMerge leaf blocks. ** -** Incremental merges happen nMin segments at a time. The two -** segments to be merged are the nMin oldest segments (the ones with -** the smallest indexes) in the highest level that contains at least -** nMin segments. Multiple merges might occur in an attempt to write the -** quota of nMerge leaf blocks. +** Incremental merges happen nMin segments at a time. The segments +** to be merged are the nMin oldest segments (the ones with the smallest +** values for the _segdir.idx field) in the highest level that contains +** at least nMin segments. Multiple merges might occur in an attempt to +** write the quota of nMerge leaf blocks. */ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ int rc; /* Return code */ int nRem = nMerge; /* Number of leaf pages yet to be written */ Fts3MultiSegReader *pCsr; /* Cursor used to read input data */ @@ -135571,10 +147136,11 @@ rc = fts3IncrmergeHintLoad(p, &hint); while( rc==SQLITE_OK && nRem>0 ){ const i64 nMod = FTS3_SEGDIR_MAXLEVEL * p->nIndex; sqlite3_stmt *pFindLevel = 0; /* SQL used to determine iAbsLevel */ int bUseHint = 0; /* True if attempting to append */ + int iIdx = 0; /* Largest idx in level (iAbsLevel+1) */ /* Search the %_segdir table for the absolute level with the smallest ** relative level number that contains at least nMin segments, if any. ** If one is found, set iAbsLevel to the absolute level number and ** nSeg to nMin. If no level with at least nMin segments can be found, @@ -135624,27 +147190,36 @@ ** segments available in level iAbsLevel. In this case, no work is ** done on iAbsLevel - fall through to the next iteration of the loop ** to start work on some other level. */ memset(pWriter, 0, nAlloc); pFilter->flags = FTS3_SEGMENT_REQUIRE_POS; + + if( rc==SQLITE_OK ){ + rc = fts3IncrmergeOutputIdx(p, iAbsLevel, &iIdx); + assert( bUseHint==1 || bUseHint==0 ); + if( iIdx==0 || (bUseHint && iIdx==1) ){ + int bIgnore = 0; + rc = fts3SegmentIsMaxLevel(p, iAbsLevel+1, &bIgnore); + if( bIgnore ){ + pFilter->flags |= FTS3_SEGMENT_IGNORE_EMPTY; + } + } + } + if( rc==SQLITE_OK ){ rc = fts3IncrmergeCsr(p, iAbsLevel, nSeg, pCsr); } if( SQLITE_OK==rc && pCsr->nSegment==nSeg && SQLITE_OK==(rc = sqlite3Fts3SegReaderStart(p, pCsr, pFilter)) && SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, pCsr)) ){ - int iIdx = 0; /* Largest idx in level (iAbsLevel+1) */ - rc = fts3IncrmergeOutputIdx(p, iAbsLevel, &iIdx); - if( rc==SQLITE_OK ){ - if( bUseHint && iIdx>0 ){ - const char *zKey = pCsr->zTerm; - int nKey = pCsr->nTerm; - rc = fts3IncrmergeLoad(p, iAbsLevel, iIdx-1, zKey, nKey, pWriter); - }else{ - rc = fts3IncrmergeWriter(p, iAbsLevel, iIdx, pCsr, pWriter); - } + if( bUseHint && iIdx>0 ){ + const char *zKey = pCsr->zTerm; + int nKey = pCsr->nTerm; + rc = fts3IncrmergeLoad(p, iAbsLevel, iIdx-1, zKey, nKey, pWriter); + }else{ + rc = fts3IncrmergeWriter(p, iAbsLevel, iIdx, pCsr, pWriter); } if( rc==SQLITE_OK && pWriter->nLeafEst ){ fts3LogMerge(nSeg, iAbsLevel); do { @@ -135662,11 +147237,17 @@ fts3IncrmergeHintPush(&hint, iAbsLevel, nSeg, &rc); } } } + if( nSeg!=0 ){ + pWriter->nLeafData = pWriter->nLeafData * -1; + } fts3IncrmergeRelease(p, pWriter, &rc); + if( nSeg==0 && pWriter->bNoLeafData==0 ){ + fts3PromoteSegments(p, iAbsLevel+1, pWriter->nLeafData); + } } sqlite3Fts3SegReaderFinish(pCsr); } @@ -135749,20 +147330,23 @@ Fts3Table *p, /* FTS3 table handle */ const char *zParam /* Nul-terminated string containing boolean */ ){ int rc = SQLITE_OK; sqlite3_stmt *pStmt = 0; - p->bAutoincrmerge = fts3Getint(&zParam)!=0; + p->nAutoincrmerge = fts3Getint(&zParam); + if( p->nAutoincrmerge==1 || p->nAutoincrmerge>FTS3_MERGE_COUNT ){ + p->nAutoincrmerge = 8; + } if( !p->bHasStat ){ assert( p->bFts4==0 ); sqlite3Fts3CreateStatTable(&rc, p); if( rc ) return rc; } rc = fts3SqlStmt(p, SQL_REPLACE_STAT, &pStmt, 0); - if( rc ) return rc;; + if( rc ) return rc; sqlite3_bind_int(pStmt, 1, FTS_STAT_AUTOINCRMERGE); - sqlite3_bind_int(pStmt, 2, p->bAutoincrmerge); + sqlite3_bind_int(pStmt, 2, p->nAutoincrmerge); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); return rc; } @@ -135915,38 +147499,40 @@ i64 iDocid = sqlite3_column_int64(pStmt, 0); int iLang = langidFromSelect(p, pStmt); int iCol; for(iCol=0; rc==SQLITE_OK && iCol<p->nColumn; iCol++){ - const char *zText = (const char *)sqlite3_column_text(pStmt, iCol+1); - int nText = sqlite3_column_bytes(pStmt, iCol+1); - sqlite3_tokenizer_cursor *pT = 0; - - rc = sqlite3Fts3OpenTokenizer(p->pTokenizer, iLang, zText, nText, &pT); - while( rc==SQLITE_OK ){ - char const *zToken; /* Buffer containing token */ - int nToken = 0; /* Number of bytes in token */ - int iDum1 = 0, iDum2 = 0; /* Dummy variables */ - int iPos = 0; /* Position of token in zText */ - - rc = pModule->xNext(pT, &zToken, &nToken, &iDum1, &iDum2, &iPos); - if( rc==SQLITE_OK ){ - int i; - cksum2 = cksum2 ^ fts3ChecksumEntry( - zToken, nToken, iLang, 0, iDocid, iCol, iPos - ); - for(i=1; i<p->nIndex; i++){ - if( p->aIndex[i].nPrefix<=nToken ){ - cksum2 = cksum2 ^ fts3ChecksumEntry( - zToken, p->aIndex[i].nPrefix, iLang, i, iDocid, iCol, iPos - ); - } - } - } - } - if( pT ) pModule->xClose(pT); - if( rc==SQLITE_DONE ) rc = SQLITE_OK; + if( p->abNotindexed[iCol]==0 ){ + const char *zText = (const char *)sqlite3_column_text(pStmt, iCol+1); + int nText = sqlite3_column_bytes(pStmt, iCol+1); + sqlite3_tokenizer_cursor *pT = 0; + + rc = sqlite3Fts3OpenTokenizer(p->pTokenizer, iLang, zText, nText,&pT); + while( rc==SQLITE_OK ){ + char const *zToken; /* Buffer containing token */ + int nToken = 0; /* Number of bytes in token */ + int iDum1 = 0, iDum2 = 0; /* Dummy variables */ + int iPos = 0; /* Position of token in zText */ + + rc = pModule->xNext(pT, &zToken, &nToken, &iDum1, &iDum2, &iPos); + if( rc==SQLITE_OK ){ + int i; + cksum2 = cksum2 ^ fts3ChecksumEntry( + zToken, nToken, iLang, 0, iDocid, iCol, iPos + ); + for(i=1; i<p->nIndex; i++){ + if( p->aIndex[i].nPrefix<=nToken ){ + cksum2 = cksum2 ^ fts3ChecksumEntry( + zToken, p->aIndex[i].nPrefix, iLang, i, iDocid, iCol, iPos + ); + } + } + } + } + if( pT ) pModule->xClose(pT); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + } } } sqlite3_finalize(pStmt); } @@ -136025,10 +147611,13 @@ }else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){ p->nNodeSize = atoi(&zVal[9]); rc = SQLITE_OK; }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){ p->nMaxPendingData = atoi(&zVal[11]); + rc = SQLITE_OK; + }else if( nVal>21 && 0==sqlite3_strnicmp(zVal, "test-no-incr-doclist=", 21) ){ + p->bNoIncrDoclist = atoi(&zVal[21]); rc = SQLITE_OK; #endif }else{ rc = SQLITE_ERROR; } @@ -136243,10 +147832,14 @@ int isRemove = 0; /* True for an UPDATE or DELETE */ u32 *aSzIns = 0; /* Sizes of inserted documents */ u32 *aSzDel = 0; /* Sizes of deleted documents */ int nChng = 0; /* Net change in number of documents */ int bInsertDone = 0; + + /* At this point it must be known if the %_stat table exists or not. + ** So bHasStat may not be 2. */ + assert( p->bHasStat==0 || p->bHasStat==1 ); assert( p->pSegments==0 ); assert( nArg==1 /* DELETE operations */ || nArg==(2 + p->nColumn + 3) /* INSERT or UPDATE operations */ @@ -136522,11 +148115,11 @@ ** After it returns, *piPos contains the value of the next element of the ** list and *pp is advanced to the following varint. */ static void fts3GetDeltaPosition(char **pp, int *piPos){ int iVal; - *pp += sqlite3Fts3GetVarint32(*pp, &iVal); + *pp += fts3GetVarint32(*pp, &iVal); *piPos += (iVal-2); } /* ** Helper function for fts3ExprIterate() (see below). @@ -136836,41 +148429,43 @@ sIter.pCsr = pCsr; sIter.iCol = iCol; sIter.nSnippet = nSnippet; sIter.nPhrase = nList; sIter.iCurrent = -1; - (void)fts3ExprIterate(pCsr->pExpr, fts3SnippetFindPositions, (void *)&sIter); - - /* Set the *pmSeen output variable. */ - for(i=0; i<nList; i++){ - if( sIter.aPhrase[i].pHead ){ - *pmSeen |= (u64)1 << i; - } - } - - /* Loop through all candidate snippets. Store the best snippet in - ** *pFragment. Store its associated 'score' in iBestScore. - */ - pFragment->iCol = iCol; - while( !fts3SnippetNextCandidate(&sIter) ){ - int iPos; - int iScore; - u64 mCover; - u64 mHighlight; - fts3SnippetDetails(&sIter, mCovered, &iPos, &iScore, &mCover, &mHighlight); - assert( iScore>=0 ); - if( iScore>iBestScore ){ - pFragment->iPos = iPos; - pFragment->hlmask = mHighlight; - pFragment->covered = mCover; - iBestScore = iScore; - } - } - + rc = fts3ExprIterate(pCsr->pExpr, fts3SnippetFindPositions, (void *)&sIter); + if( rc==SQLITE_OK ){ + + /* Set the *pmSeen output variable. */ + for(i=0; i<nList; i++){ + if( sIter.aPhrase[i].pHead ){ + *pmSeen |= (u64)1 << i; + } + } + + /* Loop through all candidate snippets. Store the best snippet in + ** *pFragment. Store its associated 'score' in iBestScore. + */ + pFragment->iCol = iCol; + while( !fts3SnippetNextCandidate(&sIter) ){ + int iPos; + int iScore; + u64 mCover; + u64 mHighlite; + fts3SnippetDetails(&sIter, mCovered, &iPos, &iScore, &mCover,&mHighlite); + assert( iScore>=0 ); + if( iScore>iBestScore ){ + pFragment->iPos = iPos; + pFragment->hlmask = mHighlite; + pFragment->covered = mCover; + iBestScore = iScore; + } + } + + *piScore = iBestScore; + } sqlite3_free(sIter.aPhrase); - *piScore = iBestScore; - return SQLITE_OK; + return rc; } /* ** Append a string to the string-buffer passed as the first argument. @@ -137074,12 +148669,16 @@ /* Now that the shift has been done, check if the initial "..." are ** required. They are required if (a) this is not the first fragment, ** or (b) this fragment does not begin at position 0 of its column. */ - if( rc==SQLITE_OK && (iPos>0 || iFragment>0) ){ - rc = fts3StringAppend(pOut, zEllipsis, -1); + if( rc==SQLITE_OK ){ + if( iPos>0 || iFragment>0 ){ + rc = fts3StringAppend(pOut, zEllipsis, -1); + }else if( iBegin ){ + rc = fts3StringAppend(pOut, zDoc, iBegin); + } } if( rc!=SQLITE_OK || iCurrent<iPos ) continue; } if( iCurrent>=(iPos+nSnippet) ){ @@ -137641,11 +149240,11 @@ ** If the iCol argument to this function was negative, this means all ** columns of the FTS3 table. Otherwise, only column iCol is considered. */ for(iRead=0; iRead<pTab->nColumn; iRead++){ SnippetFragment sF = {0, 0, 0, 0}; - int iS; + int iS = 0; if( iCol>=0 && iRead!=iCol ) continue; /* Find the best snippet of nFToken tokens in column iRead. */ rc = fts3BestSnippet(nFToken, pCsr, iRead, mCovered, &mSeen, &sF, &iS); if( rc!=SQLITE_OK ){ @@ -137931,11 +149530,11 @@ ****************************************************************************** ** ** Implementation of the "unicode" full-text-search tokenizer. */ -#ifdef SQLITE_ENABLE_FTS4_UNICODE61 +#ifndef SQLITE_DISABLE_FTS3_UNICODE #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) /* #include <assert.h> */ /* #include <stdlib.h> */ @@ -138147,11 +149746,11 @@ memset(pNew, 0, sizeof(unicode_tokenizer)); pNew->bRemoveDiacritic = 1; for(i=0; rc==SQLITE_OK && i<nArg; i++){ const char *z = azArg[i]; - int n = strlen(z); + int n = (int)strlen(z); if( n==19 && memcmp("remove_diacritics=1", z, 19)==0 ){ pNew->bRemoveDiacritic = 1; } else if( n==19 && memcmp("remove_diacritics=0", z, 19)==0 ){ @@ -138234,11 +149833,11 @@ int *piEnd, /* OUT: Ending offset of token */ int *piPos /* OUT: Position integer of token */ ){ unicode_cursor *pCsr = (unicode_cursor *)pC; unicode_tokenizer *p = ((unicode_tokenizer *)pCsr->base.pTokenizer); - int iCode; + int iCode = 0; char *zOut; const unsigned char *z = &pCsr->aInput[pCsr->iOff]; const unsigned char *zStart = z; const unsigned char *zEnd; const unsigned char *zTerm = &pCsr->aInput[pCsr->nInput]; @@ -138279,15 +149878,15 @@ }while( unicodeIsAlnum(p, iCode) || sqlite3FtsUnicodeIsdiacritic(iCode) ); /* Set the output variables and return. */ - pCsr->iOff = (z - pCsr->aInput); + pCsr->iOff = (int)(z - pCsr->aInput); *paToken = pCsr->zToken; - *pnToken = zOut - pCsr->zToken; - *piStart = (zStart - pCsr->aInput); - *piEnd = (zEnd - pCsr->aInput); + *pnToken = (int)(zOut - pCsr->zToken); + *piStart = (int)(zStart - pCsr->aInput); + *piEnd = (int)(zEnd - pCsr->aInput); *piPos = pCsr->iToken++; return SQLITE_OK; } /* @@ -138306,11 +149905,11 @@ }; *ppModule = &module; } #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ -#endif /* ifndef SQLITE_ENABLE_FTS4_UNICODE61 */ +#endif /* ifndef SQLITE_DISABLE_FTS3_UNICODE */ /************** End of fts3_unicode.c ****************************************/ /************** Begin file fts3_unicode2.c ***********************************/ /* ** 2012 May 25 @@ -138327,11 +149926,11 @@ /* ** DO NOT EDIT THIS MACHINE GENERATED FILE. */ -#if defined(SQLITE_ENABLE_FTS4_UNICODE61) +#ifndef SQLITE_DISABLE_FTS3_UNICODE #if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) /* #include <assert.h> */ /* @@ -138351,11 +149950,11 @@ ** the size of the range (always at least 1). In other words, the value ** ((C<<22) + N) represents a range of N codepoints starting with codepoint ** C. It is not possible to represent a range larger than 1023 codepoints ** using this format. */ - const static unsigned int aEntry[] = { + static const unsigned int aEntry[] = { 0x00000030, 0x0000E807, 0x00016C06, 0x0001EC2F, 0x0002AC07, 0x0002D001, 0x0002D803, 0x0002EC01, 0x0002FC01, 0x00035C01, 0x0003DC01, 0x000B0804, 0x000B480E, 0x000B9407, 0x000BB401, 0x000BBC81, 0x000DD401, 0x000DF801, 0x000E1002, 0x000E1C01, 0x000FD801, 0x00120808, 0x00156806, 0x00162402, 0x00163C01, @@ -138443,11 +150042,11 @@ if( c<128 ){ return ( (aAscii[c >> 5] & (1 << (c & 0x001F)))==0 ); }else if( c<(1<<22) ){ unsigned int key = (((unsigned int)c)<<10) | 0x000003FF; - int iRes; + int iRes = 0; int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1; int iLo = 0; while( iHi>=iLo ){ int iTest = (iHi + iLo) / 2; if( key >= aEntry[iTest] ){ @@ -138514,11 +150113,11 @@ iHi = iTest-1; } } assert( key>=aDia[iRes] ); return ((c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : (int)aChar[iRes]); -}; +} /* ** Return true if the argument interpreted as a unicode codepoint ** is a diacritical modifier character. @@ -138674,11 +150273,11 @@ } return ret; } #endif /* defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) */ -#endif /* !defined(SQLITE_ENABLE_FTS4_UNICODE61) */ +#endif /* !defined(SQLITE_DISABLE_FTS3_UNICODE) */ /************** End of fts3_unicode2.c ***************************************/ /************** Begin file rtree.c *******************************************/ /* ** 2001 September 15 @@ -138734,64 +150333,24 @@ ** child page. */ #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_RTREE) -/* -** This file contains an implementation of a couple of different variants -** of the r-tree algorithm. See the README file for further details. The -** same data-structure is used for all, but the algorithms for insert and -** delete operations vary. The variants used are selected at compile time -** by defining the following symbols: -*/ - -/* Either, both or none of the following may be set to activate -** r*tree variant algorithms. -*/ -#define VARIANT_RSTARTREE_CHOOSESUBTREE 0 -#define VARIANT_RSTARTREE_REINSERT 1 - -/* -** Exactly one of the following must be set to 1. -*/ -#define VARIANT_GUTTMAN_QUADRATIC_SPLIT 0 -#define VARIANT_GUTTMAN_LINEAR_SPLIT 0 -#define VARIANT_RSTARTREE_SPLIT 1 - -#define VARIANT_GUTTMAN_SPLIT \ - (VARIANT_GUTTMAN_LINEAR_SPLIT||VARIANT_GUTTMAN_QUADRATIC_SPLIT) - -#if VARIANT_GUTTMAN_QUADRATIC_SPLIT - #define PickNext QuadraticPickNext - #define PickSeeds QuadraticPickSeeds - #define AssignCells splitNodeGuttman -#endif -#if VARIANT_GUTTMAN_LINEAR_SPLIT - #define PickNext LinearPickNext - #define PickSeeds LinearPickSeeds - #define AssignCells splitNodeGuttman -#endif -#if VARIANT_RSTARTREE_SPLIT - #define AssignCells splitNodeStartree -#endif - -#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) -# define NDEBUG 1 -#endif - #ifndef SQLITE_CORE SQLITE_EXTENSION_INIT1 #else #endif /* #include <string.h> */ /* #include <assert.h> */ +/* #include <stdio.h> */ #ifndef SQLITE_AMALGAMATION #include "sqlite3rtree.h" typedef sqlite3_int64 i64; typedef unsigned char u8; +typedef unsigned short u16; typedef unsigned int u32; #endif /* The following macro is used to suppress compiler warnings. */ @@ -138805,34 +150364,46 @@ typedef struct RtreeCell RtreeCell; typedef struct RtreeConstraint RtreeConstraint; typedef struct RtreeMatchArg RtreeMatchArg; typedef struct RtreeGeomCallback RtreeGeomCallback; typedef union RtreeCoord RtreeCoord; +typedef struct RtreeSearchPoint RtreeSearchPoint; /* The rtree may have between 1 and RTREE_MAX_DIMENSIONS dimensions. */ #define RTREE_MAX_DIMENSIONS 5 /* Size of hash table Rtree.aHash. This hash table is not expected to ** ever contain very many entries, so a fixed number of buckets is ** used. */ -#define HASHSIZE 128 +#define HASHSIZE 97 + +/* The xBestIndex method of this virtual table requires an estimate of +** the number of rows in the virtual table to calculate the costs of +** various strategies. If possible, this estimate is loaded from the +** sqlite_stat1 table (with RTREE_MIN_ROWEST as a hard-coded minimum). +** Otherwise, if no sqlite_stat1 entry is available, use +** RTREE_DEFAULT_ROWEST. +*/ +#define RTREE_DEFAULT_ROWEST 1048576 +#define RTREE_MIN_ROWEST 100 /* ** An rtree virtual-table object. */ struct Rtree { - sqlite3_vtab base; + sqlite3_vtab base; /* Base class. Must be first */ sqlite3 *db; /* Host database connection */ int iNodeSize; /* Size in bytes of each node in the node table */ - int nDim; /* Number of dimensions */ - int nBytesPerCell; /* Bytes consumed per cell */ + u8 nDim; /* Number of dimensions */ + u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */ + u8 nBytesPerCell; /* Bytes consumed per cell */ int iDepth; /* Current depth of the r-tree structure */ char *zDb; /* Name of database containing r-tree table */ char *zName; /* Name of r-tree table */ - RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */ int nBusy; /* Current number of users of this structure */ + i64 nRowEst; /* Estimated number of rows in this table */ /* List of nodes removed during a CondenseTree operation. List is ** linked together via the pointer normally used for hash chains - ** RtreeNode.pNext. RtreeNode.iNode stores the depth of the sub-tree ** headed by the node (leaf nodes have RtreeNode.iNode==0). @@ -138853,14 +150424,14 @@ /* Statements to read/write/delete a record from xxx_parent */ sqlite3_stmt *pReadParent; sqlite3_stmt *pWriteParent; sqlite3_stmt *pDeleteParent; - int eCoordType; + RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */ }; -/* Possible values for eCoordType: */ +/* Possible values for Rtree.eCoordType: */ #define RTREE_COORD_REAL32 0 #define RTREE_COORD_INT32 1 /* ** If SQLITE_RTREE_INT_ONLY is defined, then this virtual table will @@ -138868,14 +150439,33 @@ ** will be done. */ #ifdef SQLITE_RTREE_INT_ONLY typedef sqlite3_int64 RtreeDValue; /* High accuracy coordinate */ typedef int RtreeValue; /* Low accuracy coordinate */ +# define RTREE_ZERO 0 #else typedef double RtreeDValue; /* High accuracy coordinate */ typedef float RtreeValue; /* Low accuracy coordinate */ +# define RTREE_ZERO 0.0 #endif + +/* +** When doing a search of an r-tree, instances of the following structure +** record intermediate results from the tree walk. +** +** The id is always a node-id. For iLevel>=1 the id is the node-id of +** the node that the RtreeSearchPoint represents. When iLevel==0, however, +** the id is of the parent node and the cell that RtreeSearchPoint +** represents is the iCell-th entry in the parent node. +*/ +struct RtreeSearchPoint { + RtreeDValue rScore; /* The score for this node. Smallest goes first. */ + sqlite3_int64 id; /* Node ID */ + u8 iLevel; /* 0=entries. 1=leaf node. 2+ for higher */ + u8 eWithin; /* PARTLY_WITHIN or FULLY_WITHIN */ + u8 iCell; /* Cell index within the node */ +}; /* ** The minimum number of cells allowed for a node is a third of the ** maximum. In Gutman's notation: ** @@ -138895,25 +150485,48 @@ ** 2^40 is greater than 2^64, an r-tree structure always has a depth of ** 40 or less. */ #define RTREE_MAX_DEPTH 40 + +/* +** Number of entries in the cursor RtreeNode cache. The first entry is +** used to cache the RtreeNode for RtreeCursor.sPoint. The remaining +** entries cache the RtreeNode for the first elements of the priority queue. +*/ +#define RTREE_CACHE_SZ 5 + /* ** An rtree cursor object. */ struct RtreeCursor { - sqlite3_vtab_cursor base; - RtreeNode *pNode; /* Node cursor is currently pointing at */ - int iCell; /* Index of current cell in pNode */ + sqlite3_vtab_cursor base; /* Base class. Must be first */ + u8 atEOF; /* True if at end of search */ + u8 bPoint; /* True if sPoint is valid */ int iStrategy; /* Copy of idxNum search parameter */ int nConstraint; /* Number of entries in aConstraint */ RtreeConstraint *aConstraint; /* Search constraints. */ + int nPointAlloc; /* Number of slots allocated for aPoint[] */ + int nPoint; /* Number of slots used in aPoint[] */ + int mxLevel; /* iLevel value for root of the tree */ + RtreeSearchPoint *aPoint; /* Priority queue for search points */ + RtreeSearchPoint sPoint; /* Cached next search point */ + RtreeNode *aNode[RTREE_CACHE_SZ]; /* Rtree node cache */ + u32 anQueue[RTREE_MAX_DEPTH+1]; /* Number of queued entries by iLevel */ }; +/* Return the Rtree of a RtreeCursor */ +#define RTREE_OF_CURSOR(X) ((Rtree*)((X)->base.pVtab)) + +/* +** A coordinate can be either a floating point number or a integer. All +** coordinates within a single R-Tree are always of the same time. +*/ union RtreeCoord { - RtreeValue f; - int i; + RtreeValue f; /* Floating point value */ + int i; /* Integer value */ + u32 u; /* Unsigned for byte-order conversions */ }; /* ** The argument is an RtreeCoord. Return the value stored within the RtreeCoord ** formatted as a RtreeDValue (double or int64). This macro assumes that local @@ -138934,42 +150547,71 @@ ** A search constraint. */ struct RtreeConstraint { int iCoord; /* Index of constrained coordinate */ int op; /* Constraining operation */ - RtreeDValue rValue; /* Constraint value. */ - int (*xGeom)(sqlite3_rtree_geometry*, int, RtreeDValue*, int*); - sqlite3_rtree_geometry *pGeom; /* Constraint callback argument for a MATCH */ + union { + RtreeDValue rValue; /* Constraint value. */ + int (*xGeom)(sqlite3_rtree_geometry*,int,RtreeDValue*,int*); + int (*xQueryFunc)(sqlite3_rtree_query_info*); + } u; + sqlite3_rtree_query_info *pInfo; /* xGeom and xQueryFunc argument */ }; /* Possible values for RtreeConstraint.op */ -#define RTREE_EQ 0x41 -#define RTREE_LE 0x42 -#define RTREE_LT 0x43 -#define RTREE_GE 0x44 -#define RTREE_GT 0x45 -#define RTREE_MATCH 0x46 +#define RTREE_EQ 0x41 /* A */ +#define RTREE_LE 0x42 /* B */ +#define RTREE_LT 0x43 /* C */ +#define RTREE_GE 0x44 /* D */ +#define RTREE_GT 0x45 /* E */ +#define RTREE_MATCH 0x46 /* F: Old-style sqlite3_rtree_geometry_callback() */ +#define RTREE_QUERY 0x47 /* G: New-style sqlite3_rtree_query_callback() */ + /* ** An rtree structure node. */ struct RtreeNode { - RtreeNode *pParent; /* Parent node */ - i64 iNode; - int nRef; - int isDirty; - u8 *zData; - RtreeNode *pNext; /* Next node in this hash chain */ + RtreeNode *pParent; /* Parent node */ + i64 iNode; /* The node number */ + int nRef; /* Number of references to this node */ + int isDirty; /* True if the node needs to be written to disk */ + u8 *zData; /* Content of the node, as should be on disk */ + RtreeNode *pNext; /* Next node in this hash collision chain */ }; + +/* Return the number of cells in a node */ #define NCELL(pNode) readInt16(&(pNode)->zData[2]) /* -** Structure to store a deserialized rtree record. +** A single cell from a node, deserialized */ struct RtreeCell { - i64 iRowid; - RtreeCoord aCoord[RTREE_MAX_DIMENSIONS*2]; + i64 iRowid; /* Node or entry ID */ + RtreeCoord aCoord[RTREE_MAX_DIMENSIONS*2]; /* Bounding box coordinates */ +}; + + +/* +** This object becomes the sqlite3_user_data() for the SQL functions +** that are created by sqlite3_rtree_geometry_callback() and +** sqlite3_rtree_query_callback() and which appear on the right of MATCH +** operators in order to constrain a search. +** +** xGeom and xQueryFunc are the callback functions. Exactly one of +** xGeom and xQueryFunc fields is non-NULL, depending on whether the +** SQL function was created using sqlite3_rtree_geometry_callback() or +** sqlite3_rtree_query_callback(). +** +** This object is deleted automatically by the destructor mechanism in +** sqlite3_create_function_v2(). +*/ +struct RtreeGeomCallback { + int (*xGeom)(sqlite3_rtree_geometry*, int, RtreeDValue*, int*); + int (*xQueryFunc)(sqlite3_rtree_query_info*); + void (*xDestructor)(void*); + void *pContext; }; /* ** Value for the first field of every RtreeMatchArg object. The MATCH @@ -138977,33 +150619,20 @@ ** value to avoid operating on invalid blobs (which could cause a segfault). */ #define RTREE_GEOMETRY_MAGIC 0x891245AB /* -** An instance of this structure must be supplied as a blob argument to -** the right-hand-side of an SQL MATCH operator used to constrain an -** r-tree query. +** An instance of this structure (in the form of a BLOB) is returned by +** the SQL functions that sqlite3_rtree_geometry_callback() and +** sqlite3_rtree_query_callback() create, and is read as the right-hand +** operand to the MATCH operator of an R-Tree. */ struct RtreeMatchArg { - u32 magic; /* Always RTREE_GEOMETRY_MAGIC */ - int (*xGeom)(sqlite3_rtree_geometry *, int, RtreeDValue*, int *); - void *pContext; - int nParam; - RtreeDValue aParam[1]; -}; - -/* -** When a geometry callback is created (see sqlite3_rtree_geometry_callback), -** a single instance of the following structure is allocated. It is used -** as the context for the user-function created by by s_r_g_c(). The object -** is eventually deleted by the destructor mechanism provided by -** sqlite3_create_function_v2() (which is called by s_r_g_c() to create -** the geometry callback function). -*/ -struct RtreeGeomCallback { - int (*xGeom)(sqlite3_rtree_geometry*, int, RtreeDValue*, int*); - void *pContext; + u32 magic; /* Always RTREE_GEOMETRY_MAGIC */ + RtreeGeomCallback cb; /* Info about the callback functions */ + int nParam; /* Number of parameters to the SQL function */ + RtreeDValue aParam[1]; /* Values for parameters to the SQL function */ }; #ifndef MAX # define MAX(x,y) ((x) < (y) ? (y) : (x)) #endif @@ -139017,17 +150646,16 @@ */ static int readInt16(u8 *p){ return (p[0]<<8) + p[1]; } static void readCoord(u8 *p, RtreeCoord *pCoord){ - u32 i = ( + pCoord->u = ( (((u32)p[0]) << 24) + (((u32)p[1]) << 16) + (((u32)p[2]) << 8) + (((u32)p[3]) << 0) ); - *(u32 *)pCoord = i; } static i64 readInt64(u8 *p){ return ( (((i64)p[0]) << 56) + (((i64)p[1]) << 48) + @@ -139052,11 +150680,11 @@ } static int writeCoord(u8 *p, RtreeCoord *pCoord){ u32 i; assert( sizeof(RtreeCoord)==4 ); assert( sizeof(u32)==4 ); - i = *(u32 *)pCoord; + i = pCoord->u; p[0] = (i>>24)&0xFF; p[1] = (i>>16)&0xFF; p[2] = (i>> 8)&0xFF; p[3] = (i>> 0)&0xFF; return 4; @@ -139093,14 +150721,11 @@ /* ** Given a node number iNode, return the corresponding key to use ** in the Rtree.aHash table. */ static int nodeHash(i64 iNode){ - return ( - (iNode>>56) ^ (iNode>>48) ^ (iNode>>40) ^ (iNode>>32) ^ - (iNode>>24) ^ (iNode>>16) ^ (iNode>> 8) ^ (iNode>> 0) - ) % HASHSIZE; + return iNode % HASHSIZE; } /* ** Search the node hash table for node iNode. If found, return a pointer ** to it. Otherwise, return 0. @@ -139156,12 +150781,11 @@ } /* ** Obtain a reference to an r-tree node. */ -static int -nodeAcquire( +static int nodeAcquire( Rtree *pRtree, /* R-tree structure */ i64 iNode, /* Node number to load */ RtreeNode *pParent, /* Either the parent node or NULL */ RtreeNode **ppNode /* OUT: Acquired node */ ){ @@ -139246,14 +150870,14 @@ /* ** Overwrite cell iCell of node pNode with the contents of pCell. */ static void nodeOverwriteCell( - Rtree *pRtree, - RtreeNode *pNode, - RtreeCell *pCell, - int iCell + Rtree *pRtree, /* The overall R-Tree */ + RtreeNode *pNode, /* The node into which the cell is to be written */ + RtreeCell *pCell, /* The cell to write */ + int iCell /* Index into pNode into which pCell is written */ ){ int ii; u8 *p = &pNode->zData[4 + pRtree->nBytesPerCell*iCell]; p += writeInt64(p, pCell->iRowid); for(ii=0; ii<(pRtree->nDim*2); ii++){ @@ -139261,11 +150885,11 @@ } pNode->isDirty = 1; } /* -** Remove cell the cell with index iCell from node pNode. +** Remove the cell with index iCell from node pNode. */ static void nodeDeleteCell(Rtree *pRtree, RtreeNode *pNode, int iCell){ u8 *pDst = &pNode->zData[4 + pRtree->nBytesPerCell*iCell]; u8 *pSrc = &pDst[pRtree->nBytesPerCell]; int nByte = (NCELL(pNode) - iCell - 1) * pRtree->nBytesPerCell; @@ -139278,15 +150902,14 @@ ** Insert the contents of cell pCell into node pNode. If the insert ** is successful, return SQLITE_OK. ** ** If there is not enough free space in pNode, return SQLITE_FULL. */ -static int -nodeInsertCell( - Rtree *pRtree, - RtreeNode *pNode, - RtreeCell *pCell +static int nodeInsertCell( + Rtree *pRtree, /* The overall R-Tree */ + RtreeNode *pNode, /* Write new cell into this node */ + RtreeCell *pCell /* The cell to be inserted */ ){ int nCell; /* Current number of cells in pNode */ int nMaxCell; /* Maximum number of cells for pNode */ nMaxCell = (pRtree->iNodeSize-4)/pRtree->nBytesPerCell; @@ -139303,12 +150926,11 @@ } /* ** If the node is dirty, write it out to the database. */ -static int -nodeWrite(Rtree *pRtree, RtreeNode *pNode){ +static int nodeWrite(Rtree *pRtree, RtreeNode *pNode){ int rc = SQLITE_OK; if( pNode->isDirty ){ sqlite3_stmt *p = pRtree->pWriteNode; if( pNode->iNode ){ sqlite3_bind_int64(p, 1, pNode->iNode); @@ -139329,12 +150951,11 @@ /* ** Release a reference to a node. If the node is dirty and the reference ** count drops to zero, the node data is written to the database. */ -static int -nodeRelease(Rtree *pRtree, RtreeNode *pNode){ +static int nodeRelease(Rtree *pRtree, RtreeNode *pNode){ int rc = SQLITE_OK; if( pNode ){ assert( pNode->nRef>0 ); pNode->nRef--; if( pNode->nRef==0 ){ @@ -139358,45 +150979,49 @@ ** Return the 64-bit integer value associated with cell iCell of ** node pNode. If pNode is a leaf node, this is a rowid. If it is ** an internal node, then the 64-bit integer is a child page number. */ static i64 nodeGetRowid( - Rtree *pRtree, - RtreeNode *pNode, - int iCell + Rtree *pRtree, /* The overall R-Tree */ + RtreeNode *pNode, /* The node from which to extract the ID */ + int iCell /* The cell index from which to extract the ID */ ){ assert( iCell<NCELL(pNode) ); return readInt64(&pNode->zData[4 + pRtree->nBytesPerCell*iCell]); } /* ** Return coordinate iCoord from cell iCell in node pNode. */ static void nodeGetCoord( - Rtree *pRtree, - RtreeNode *pNode, - int iCell, - int iCoord, - RtreeCoord *pCoord /* Space to write result to */ + Rtree *pRtree, /* The overall R-Tree */ + RtreeNode *pNode, /* The node from which to extract a coordinate */ + int iCell, /* The index of the cell within the node */ + int iCoord, /* Which coordinate to extract */ + RtreeCoord *pCoord /* OUT: Space to write result to */ ){ readCoord(&pNode->zData[12 + pRtree->nBytesPerCell*iCell + 4*iCoord], pCoord); } /* ** Deserialize cell iCell of node pNode. Populate the structure pointed ** to by pCell with the results. */ static void nodeGetCell( - Rtree *pRtree, - RtreeNode *pNode, - int iCell, - RtreeCell *pCell + Rtree *pRtree, /* The overall R-Tree */ + RtreeNode *pNode, /* The node containing the cell to be read */ + int iCell, /* Index of the cell within the node */ + RtreeCell *pCell /* OUT: Write the cell contents here */ ){ + u8 *pData; + RtreeCoord *pCoord; int ii; pCell->iRowid = nodeGetRowid(pRtree, pNode, iCell); + pData = pNode->zData + (12 + pRtree->nBytesPerCell*iCell); + pCoord = pCell->aCoord; for(ii=0; ii<pRtree->nDim*2; ii++){ - nodeGetCoord(pRtree, pNode, iCell, ii, &pCell->aCoord[ii]); + readCoord(&pData[ii*4], &pCoord[ii]); } } /* Forward declaration for the function that does the work of @@ -139518,14 +151143,14 @@ */ static void freeCursorConstraints(RtreeCursor *pCsr){ if( pCsr->aConstraint ){ int i; /* Used to iterate through constraint array */ for(i=0; i<pCsr->nConstraint; i++){ - sqlite3_rtree_geometry *pGeom = pCsr->aConstraint[i].pGeom; - if( pGeom ){ - if( pGeom->xDelUser ) pGeom->xDelUser(pGeom->pUser); - sqlite3_free(pGeom); + sqlite3_rtree_query_info *pInfo = pCsr->aConstraint[i].pInfo; + if( pInfo ){ + if( pInfo->xDelUser ) pInfo->xDelUser(pInfo->pUser); + sqlite3_free(pInfo); } } sqlite3_free(pCsr->aConstraint); pCsr->aConstraint = 0; } @@ -139534,16 +151159,17 @@ /* ** Rtree virtual table module xClose method. */ static int rtreeClose(sqlite3_vtab_cursor *cur){ Rtree *pRtree = (Rtree *)(cur->pVtab); - int rc; + int ii; RtreeCursor *pCsr = (RtreeCursor *)cur; freeCursorConstraints(pCsr); - rc = nodeRelease(pRtree, pCsr->pNode); + sqlite3_free(pCsr->aPoint); + for(ii=0; ii<RTREE_CACHE_SZ; ii++) nodeRelease(pRtree, pCsr->aNode[ii]); sqlite3_free(pCsr); - return rc; + return SQLITE_OK; } /* ** Rtree virtual table module xEof method. ** @@ -139550,198 +151176,168 @@ ** Return non-zero if the cursor does not currently point to a valid ** record (i.e if the scan has finished), or zero otherwise. */ static int rtreeEof(sqlite3_vtab_cursor *cur){ RtreeCursor *pCsr = (RtreeCursor *)cur; - return (pCsr->pNode==0); -} - -/* -** The r-tree constraint passed as the second argument to this function is -** guaranteed to be a MATCH constraint. -*/ -static int testRtreeGeom( - Rtree *pRtree, /* R-Tree object */ - RtreeConstraint *pConstraint, /* MATCH constraint to test */ - RtreeCell *pCell, /* Cell to test */ - int *pbRes /* OUT: Test result */ -){ - int i; - RtreeDValue aCoord[RTREE_MAX_DIMENSIONS*2]; - int nCoord = pRtree->nDim*2; - - assert( pConstraint->op==RTREE_MATCH ); - assert( pConstraint->pGeom ); - - for(i=0; i<nCoord; i++){ - aCoord[i] = DCOORD(pCell->aCoord[i]); - } - return pConstraint->xGeom(pConstraint->pGeom, nCoord, aCoord, pbRes); -} - -/* -** Cursor pCursor currently points to a cell in a non-leaf page. -** Set *pbEof to true if the sub-tree headed by the cell is filtered -** (excluded) by the constraints in the pCursor->aConstraint[] -** array, or false otherwise. -** -** Return SQLITE_OK if successful or an SQLite error code if an error -** occurs within a geometry callback. -*/ -static int testRtreeCell(Rtree *pRtree, RtreeCursor *pCursor, int *pbEof){ - RtreeCell cell; - int ii; - int bRes = 0; - int rc = SQLITE_OK; - - nodeGetCell(pRtree, pCursor->pNode, pCursor->iCell, &cell); - for(ii=0; bRes==0 && ii<pCursor->nConstraint; ii++){ - RtreeConstraint *p = &pCursor->aConstraint[ii]; - RtreeDValue cell_min = DCOORD(cell.aCoord[(p->iCoord>>1)*2]); - RtreeDValue cell_max = DCOORD(cell.aCoord[(p->iCoord>>1)*2+1]); - - assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE - || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_MATCH - ); - - switch( p->op ){ - case RTREE_LE: case RTREE_LT: - bRes = p->rValue<cell_min; - break; - - case RTREE_GE: case RTREE_GT: - bRes = p->rValue>cell_max; - break; - - case RTREE_EQ: - bRes = (p->rValue>cell_max || p->rValue<cell_min); - break; - - default: { - assert( p->op==RTREE_MATCH ); - rc = testRtreeGeom(pRtree, p, &cell, &bRes); - bRes = !bRes; - break; - } - } - } - - *pbEof = bRes; - return rc; -} - -/* -** Test if the cell that cursor pCursor currently points to -** would be filtered (excluded) by the constraints in the -** pCursor->aConstraint[] array. If so, set *pbEof to true before -** returning. If the cell is not filtered (excluded) by the constraints, -** set pbEof to zero. -** -** Return SQLITE_OK if successful or an SQLite error code if an error -** occurs within a geometry callback. -** -** This function assumes that the cell is part of a leaf node. -*/ -static int testRtreeEntry(Rtree *pRtree, RtreeCursor *pCursor, int *pbEof){ - RtreeCell cell; - int ii; - *pbEof = 0; - - nodeGetCell(pRtree, pCursor->pNode, pCursor->iCell, &cell); - for(ii=0; ii<pCursor->nConstraint; ii++){ - RtreeConstraint *p = &pCursor->aConstraint[ii]; - RtreeDValue coord = DCOORD(cell.aCoord[p->iCoord]); - int res; - assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE - || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_MATCH - ); - switch( p->op ){ - case RTREE_LE: res = (coord<=p->rValue); break; - case RTREE_LT: res = (coord<p->rValue); break; - case RTREE_GE: res = (coord>=p->rValue); break; - case RTREE_GT: res = (coord>p->rValue); break; - case RTREE_EQ: res = (coord==p->rValue); break; - default: { - int rc; - assert( p->op==RTREE_MATCH ); - rc = testRtreeGeom(pRtree, p, &cell, &res); - if( rc!=SQLITE_OK ){ - return rc; - } - break; - } - } - - if( !res ){ - *pbEof = 1; - return SQLITE_OK; - } - } - - return SQLITE_OK; -} - -/* -** Cursor pCursor currently points at a node that heads a sub-tree of -** height iHeight (if iHeight==0, then the node is a leaf). Descend -** to point to the left-most cell of the sub-tree that matches the -** configured constraints. -*/ -static int descendToCell( - Rtree *pRtree, - RtreeCursor *pCursor, - int iHeight, - int *pEof /* OUT: Set to true if cannot descend */ -){ - int isEof; - int rc; - int ii; - RtreeNode *pChild; - sqlite3_int64 iRowid; - - RtreeNode *pSavedNode = pCursor->pNode; - int iSavedCell = pCursor->iCell; - - assert( iHeight>=0 ); - - if( iHeight==0 ){ - rc = testRtreeEntry(pRtree, pCursor, &isEof); - }else{ - rc = testRtreeCell(pRtree, pCursor, &isEof); - } - if( rc!=SQLITE_OK || isEof || iHeight==0 ){ - goto descend_to_cell_out; - } - - iRowid = nodeGetRowid(pRtree, pCursor->pNode, pCursor->iCell); - rc = nodeAcquire(pRtree, iRowid, pCursor->pNode, &pChild); - if( rc!=SQLITE_OK ){ - goto descend_to_cell_out; - } - - nodeRelease(pRtree, pCursor->pNode); - pCursor->pNode = pChild; - isEof = 1; - for(ii=0; isEof && ii<NCELL(pChild); ii++){ - pCursor->iCell = ii; - rc = descendToCell(pRtree, pCursor, iHeight-1, &isEof); - if( rc!=SQLITE_OK ){ - goto descend_to_cell_out; - } - } - - if( isEof ){ - assert( pCursor->pNode==pChild ); - nodeReference(pSavedNode); - nodeRelease(pRtree, pChild); - pCursor->pNode = pSavedNode; - pCursor->iCell = iSavedCell; - } - -descend_to_cell_out: - *pEof = isEof; - return rc; + return pCsr->atEOF; +} + +/* +** Convert raw bits from the on-disk RTree record into a coordinate value. +** The on-disk format is big-endian and needs to be converted for little- +** endian platforms. The on-disk record stores integer coordinates if +** eInt is true and it stores 32-bit floating point records if eInt is +** false. a[] is the four bytes of the on-disk record to be decoded. +** Store the results in "r". +** +** There are three versions of this macro, one each for little-endian and +** big-endian processors and a third generic implementation. The endian- +** specific implementations are much faster and are preferred if the +** processor endianness is known at compile-time. The SQLITE_BYTEORDER +** macro is part of sqliteInt.h and hence the endian-specific +** implementation will only be used if this module is compiled as part +** of the amalgamation. +*/ +#if defined(SQLITE_BYTEORDER) && SQLITE_BYTEORDER==1234 +#define RTREE_DECODE_COORD(eInt, a, r) { \ + RtreeCoord c; /* Coordinate decoded */ \ + memcpy(&c.u,a,4); \ + c.u = ((c.u>>24)&0xff)|((c.u>>8)&0xff00)| \ + ((c.u&0xff)<<24)|((c.u&0xff00)<<8); \ + r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \ +} +#elif defined(SQLITE_BYTEORDER) && SQLITE_BYTEORDER==4321 +#define RTREE_DECODE_COORD(eInt, a, r) { \ + RtreeCoord c; /* Coordinate decoded */ \ + memcpy(&c.u,a,4); \ + r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \ +} +#else +#define RTREE_DECODE_COORD(eInt, a, r) { \ + RtreeCoord c; /* Coordinate decoded */ \ + c.u = ((u32)a[0]<<24) + ((u32)a[1]<<16) \ + +((u32)a[2]<<8) + a[3]; \ + r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \ +} +#endif + +/* +** Check the RTree node or entry given by pCellData and p against the MATCH +** constraint pConstraint. +*/ +static int rtreeCallbackConstraint( + RtreeConstraint *pConstraint, /* The constraint to test */ + int eInt, /* True if RTree holding integer coordinates */ + u8 *pCellData, /* Raw cell content */ + RtreeSearchPoint *pSearch, /* Container of this cell */ + sqlite3_rtree_dbl *prScore, /* OUT: score for the cell */ + int *peWithin /* OUT: visibility of the cell */ +){ + int i; /* Loop counter */ + sqlite3_rtree_query_info *pInfo = pConstraint->pInfo; /* Callback info */ + int nCoord = pInfo->nCoord; /* No. of coordinates */ + int rc; /* Callback return code */ + sqlite3_rtree_dbl aCoord[RTREE_MAX_DIMENSIONS*2]; /* Decoded coordinates */ + + assert( pConstraint->op==RTREE_MATCH || pConstraint->op==RTREE_QUERY ); + assert( nCoord==2 || nCoord==4 || nCoord==6 || nCoord==8 || nCoord==10 ); + + if( pConstraint->op==RTREE_QUERY && pSearch->iLevel==1 ){ + pInfo->iRowid = readInt64(pCellData); + } + pCellData += 8; + for(i=0; i<nCoord; i++, pCellData += 4){ + RTREE_DECODE_COORD(eInt, pCellData, aCoord[i]); + } + if( pConstraint->op==RTREE_MATCH ){ + rc = pConstraint->u.xGeom((sqlite3_rtree_geometry*)pInfo, + nCoord, aCoord, &i); + if( i==0 ) *peWithin = NOT_WITHIN; + *prScore = RTREE_ZERO; + }else{ + pInfo->aCoord = aCoord; + pInfo->iLevel = pSearch->iLevel - 1; + pInfo->rScore = pInfo->rParentScore = pSearch->rScore; + pInfo->eWithin = pInfo->eParentWithin = pSearch->eWithin; + rc = pConstraint->u.xQueryFunc(pInfo); + if( pInfo->eWithin<*peWithin ) *peWithin = pInfo->eWithin; + if( pInfo->rScore<*prScore || *prScore<RTREE_ZERO ){ + *prScore = pInfo->rScore; + } + } + return rc; +} + +/* +** Check the internal RTree node given by pCellData against constraint p. +** If this constraint cannot be satisfied by any child within the node, +** set *peWithin to NOT_WITHIN. +*/ +static void rtreeNonleafConstraint( + RtreeConstraint *p, /* The constraint to test */ + int eInt, /* True if RTree holds integer coordinates */ + u8 *pCellData, /* Raw cell content as appears on disk */ + int *peWithin /* Adjust downward, as appropriate */ +){ + sqlite3_rtree_dbl val; /* Coordinate value convert to a double */ + + /* p->iCoord might point to either a lower or upper bound coordinate + ** in a coordinate pair. But make pCellData point to the lower bound. + */ + pCellData += 8 + 4*(p->iCoord&0xfe); + + assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE + || p->op==RTREE_GT || p->op==RTREE_EQ ); + switch( p->op ){ + case RTREE_LE: + case RTREE_LT: + case RTREE_EQ: + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the lower bound of the coordinate pair */ + if( p->u.rValue>=val ) return; + if( p->op!=RTREE_EQ ) break; /* RTREE_LE and RTREE_LT end here */ + /* Fall through for the RTREE_EQ case */ + + default: /* RTREE_GT or RTREE_GE, or fallthrough of RTREE_EQ */ + pCellData += 4; + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the upper bound of the coordinate pair */ + if( p->u.rValue<=val ) return; + } + *peWithin = NOT_WITHIN; +} + +/* +** Check the leaf RTree cell given by pCellData against constraint p. +** If this constraint is not satisfied, set *peWithin to NOT_WITHIN. +** If the constraint is satisfied, leave *peWithin unchanged. +** +** The constraint is of the form: xN op $val +** +** The op is given by p->op. The xN is p->iCoord-th coordinate in +** pCellData. $val is given by p->u.rValue. +*/ +static void rtreeLeafConstraint( + RtreeConstraint *p, /* The constraint to test */ + int eInt, /* True if RTree holds integer coordinates */ + u8 *pCellData, /* Raw cell content as appears on disk */ + int *peWithin /* Adjust downward, as appropriate */ +){ + RtreeDValue xN; /* Coordinate value converted to a double */ + + assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE + || p->op==RTREE_GT || p->op==RTREE_EQ ); + pCellData += 8 + p->iCoord*4; + RTREE_DECODE_COORD(eInt, pCellData, xN); + switch( p->op ){ + case RTREE_LE: if( xN <= p->u.rValue ) return; break; + case RTREE_LT: if( xN < p->u.rValue ) return; break; + case RTREE_GE: if( xN >= p->u.rValue ) return; break; + case RTREE_GT: if( xN > p->u.rValue ) return; break; + default: if( xN == p->u.rValue ) return; break; + } + *peWithin = NOT_WITHIN; } /* ** One of the cells in node pNode is guaranteed to have a 64-bit ** integer value equal to iRowid. Return the index of this cell. @@ -139752,10 +151348,11 @@ i64 iRowid, int *piIndex ){ int ii; int nCell = NCELL(pNode); + assert( nCell<200 ); for(ii=0; ii<nCell; ii++){ if( nodeGetRowid(pRtree, pNode, ii)==iRowid ){ *piIndex = ii; return SQLITE_OK; } @@ -139773,82 +151370,342 @@ return nodeRowidIndex(pRtree, pParent, pNode->iNode, piIndex); } *piIndex = -1; return SQLITE_OK; } + +/* +** Compare two search points. Return negative, zero, or positive if the first +** is less than, equal to, or greater than the second. +** +** The rScore is the primary key. Smaller rScore values come first. +** If the rScore is a tie, then use iLevel as the tie breaker with smaller +** iLevel values coming first. In this way, if rScore is the same for all +** SearchPoints, then iLevel becomes the deciding factor and the result +** is a depth-first search, which is the desired default behavior. +*/ +static int rtreeSearchPointCompare( + const RtreeSearchPoint *pA, + const RtreeSearchPoint *pB +){ + if( pA->rScore<pB->rScore ) return -1; + if( pA->rScore>pB->rScore ) return +1; + if( pA->iLevel<pB->iLevel ) return -1; + if( pA->iLevel>pB->iLevel ) return +1; + return 0; +} + +/* +** Interchange to search points in a cursor. +*/ +static void rtreeSearchPointSwap(RtreeCursor *p, int i, int j){ + RtreeSearchPoint t = p->aPoint[i]; + assert( i<j ); + p->aPoint[i] = p->aPoint[j]; + p->aPoint[j] = t; + i++; j++; + if( i<RTREE_CACHE_SZ ){ + if( j>=RTREE_CACHE_SZ ){ + nodeRelease(RTREE_OF_CURSOR(p), p->aNode[i]); + p->aNode[i] = 0; + }else{ + RtreeNode *pTemp = p->aNode[i]; + p->aNode[i] = p->aNode[j]; + p->aNode[j] = pTemp; + } + } +} + +/* +** Return the search point with the lowest current score. +*/ +static RtreeSearchPoint *rtreeSearchPointFirst(RtreeCursor *pCur){ + return pCur->bPoint ? &pCur->sPoint : pCur->nPoint ? pCur->aPoint : 0; +} + +/* +** Get the RtreeNode for the search point with the lowest score. +*/ +static RtreeNode *rtreeNodeOfFirstSearchPoint(RtreeCursor *pCur, int *pRC){ + sqlite3_int64 id; + int ii = 1 - pCur->bPoint; + assert( ii==0 || ii==1 ); + assert( pCur->bPoint || pCur->nPoint ); + if( pCur->aNode[ii]==0 ){ + assert( pRC!=0 ); + id = ii ? pCur->aPoint[0].id : pCur->sPoint.id; + *pRC = nodeAcquire(RTREE_OF_CURSOR(pCur), id, 0, &pCur->aNode[ii]); + } + return pCur->aNode[ii]; +} + +/* +** Push a new element onto the priority queue +*/ +static RtreeSearchPoint *rtreeEnqueue( + RtreeCursor *pCur, /* The cursor */ + RtreeDValue rScore, /* Score for the new search point */ + u8 iLevel /* Level for the new search point */ +){ + int i, j; + RtreeSearchPoint *pNew; + if( pCur->nPoint>=pCur->nPointAlloc ){ + int nNew = pCur->nPointAlloc*2 + 8; + pNew = sqlite3_realloc(pCur->aPoint, nNew*sizeof(pCur->aPoint[0])); + if( pNew==0 ) return 0; + pCur->aPoint = pNew; + pCur->nPointAlloc = nNew; + } + i = pCur->nPoint++; + pNew = pCur->aPoint + i; + pNew->rScore = rScore; + pNew->iLevel = iLevel; + assert( iLevel<=RTREE_MAX_DEPTH ); + while( i>0 ){ + RtreeSearchPoint *pParent; + j = (i-1)/2; + pParent = pCur->aPoint + j; + if( rtreeSearchPointCompare(pNew, pParent)>=0 ) break; + rtreeSearchPointSwap(pCur, j, i); + i = j; + pNew = pParent; + } + return pNew; +} + +/* +** Allocate a new RtreeSearchPoint and return a pointer to it. Return +** NULL if malloc fails. +*/ +static RtreeSearchPoint *rtreeSearchPointNew( + RtreeCursor *pCur, /* The cursor */ + RtreeDValue rScore, /* Score for the new search point */ + u8 iLevel /* Level for the new search point */ +){ + RtreeSearchPoint *pNew, *pFirst; + pFirst = rtreeSearchPointFirst(pCur); + pCur->anQueue[iLevel]++; + if( pFirst==0 + || pFirst->rScore>rScore + || (pFirst->rScore==rScore && pFirst->iLevel>iLevel) + ){ + if( pCur->bPoint ){ + int ii; + pNew = rtreeEnqueue(pCur, rScore, iLevel); + if( pNew==0 ) return 0; + ii = (int)(pNew - pCur->aPoint) + 1; + if( ii<RTREE_CACHE_SZ ){ + assert( pCur->aNode[ii]==0 ); + pCur->aNode[ii] = pCur->aNode[0]; + }else{ + nodeRelease(RTREE_OF_CURSOR(pCur), pCur->aNode[0]); + } + pCur->aNode[0] = 0; + *pNew = pCur->sPoint; + } + pCur->sPoint.rScore = rScore; + pCur->sPoint.iLevel = iLevel; + pCur->bPoint = 1; + return &pCur->sPoint; + }else{ + return rtreeEnqueue(pCur, rScore, iLevel); + } +} + +#if 0 +/* Tracing routines for the RtreeSearchPoint queue */ +static void tracePoint(RtreeSearchPoint *p, int idx, RtreeCursor *pCur){ + if( idx<0 ){ printf(" s"); }else{ printf("%2d", idx); } + printf(" %d.%05lld.%02d %g %d", + p->iLevel, p->id, p->iCell, p->rScore, p->eWithin + ); + idx++; + if( idx<RTREE_CACHE_SZ ){ + printf(" %p\n", pCur->aNode[idx]); + }else{ + printf("\n"); + } +} +static void traceQueue(RtreeCursor *pCur, const char *zPrefix){ + int ii; + printf("=== %9s ", zPrefix); + if( pCur->bPoint ){ + tracePoint(&pCur->sPoint, -1, pCur); + } + for(ii=0; ii<pCur->nPoint; ii++){ + if( ii>0 || pCur->bPoint ) printf(" "); + tracePoint(&pCur->aPoint[ii], ii, pCur); + } +} +# define RTREE_QUEUE_TRACE(A,B) traceQueue(A,B) +#else +# define RTREE_QUEUE_TRACE(A,B) /* no-op */ +#endif + +/* Remove the search point with the lowest current score. +*/ +static void rtreeSearchPointPop(RtreeCursor *p){ + int i, j, k, n; + i = 1 - p->bPoint; + assert( i==0 || i==1 ); + if( p->aNode[i] ){ + nodeRelease(RTREE_OF_CURSOR(p), p->aNode[i]); + p->aNode[i] = 0; + } + if( p->bPoint ){ + p->anQueue[p->sPoint.iLevel]--; + p->bPoint = 0; + }else if( p->nPoint ){ + p->anQueue[p->aPoint[0].iLevel]--; + n = --p->nPoint; + p->aPoint[0] = p->aPoint[n]; + if( n<RTREE_CACHE_SZ-1 ){ + p->aNode[1] = p->aNode[n+1]; + p->aNode[n+1] = 0; + } + i = 0; + while( (j = i*2+1)<n ){ + k = j+1; + if( k<n && rtreeSearchPointCompare(&p->aPoint[k], &p->aPoint[j])<0 ){ + if( rtreeSearchPointCompare(&p->aPoint[k], &p->aPoint[i])<0 ){ + rtreeSearchPointSwap(p, i, k); + i = k; + }else{ + break; + } + }else{ + if( rtreeSearchPointCompare(&p->aPoint[j], &p->aPoint[i])<0 ){ + rtreeSearchPointSwap(p, i, j); + i = j; + }else{ + break; + } + } + } + } +} + + +/* +** Continue the search on cursor pCur until the front of the queue +** contains an entry suitable for returning as a result-set row, +** or until the RtreeSearchPoint queue is empty, indicating that the +** query has completed. +*/ +static int rtreeStepToLeaf(RtreeCursor *pCur){ + RtreeSearchPoint *p; + Rtree *pRtree = RTREE_OF_CURSOR(pCur); + RtreeNode *pNode; + int eWithin; + int rc = SQLITE_OK; + int nCell; + int nConstraint = pCur->nConstraint; + int ii; + int eInt; + RtreeSearchPoint x; + + eInt = pRtree->eCoordType==RTREE_COORD_INT32; + while( (p = rtreeSearchPointFirst(pCur))!=0 && p->iLevel>0 ){ + pNode = rtreeNodeOfFirstSearchPoint(pCur, &rc); + if( rc ) return rc; + nCell = NCELL(pNode); + assert( nCell<200 ); + while( p->iCell<nCell ){ + sqlite3_rtree_dbl rScore = (sqlite3_rtree_dbl)-1; + u8 *pCellData = pNode->zData + (4+pRtree->nBytesPerCell*p->iCell); + eWithin = FULLY_WITHIN; + for(ii=0; ii<nConstraint; ii++){ + RtreeConstraint *pConstraint = pCur->aConstraint + ii; + if( pConstraint->op>=RTREE_MATCH ){ + rc = rtreeCallbackConstraint(pConstraint, eInt, pCellData, p, + &rScore, &eWithin); + if( rc ) return rc; + }else if( p->iLevel==1 ){ + rtreeLeafConstraint(pConstraint, eInt, pCellData, &eWithin); + }else{ + rtreeNonleafConstraint(pConstraint, eInt, pCellData, &eWithin); + } + if( eWithin==NOT_WITHIN ) break; + } + p->iCell++; + if( eWithin==NOT_WITHIN ) continue; + x.iLevel = p->iLevel - 1; + if( x.iLevel ){ + x.id = readInt64(pCellData); + x.iCell = 0; + }else{ + x.id = p->id; + x.iCell = p->iCell - 1; + } + if( p->iCell>=nCell ){ + RTREE_QUEUE_TRACE(pCur, "POP-S:"); + rtreeSearchPointPop(pCur); + } + if( rScore<RTREE_ZERO ) rScore = RTREE_ZERO; + p = rtreeSearchPointNew(pCur, rScore, x.iLevel); + if( p==0 ) return SQLITE_NOMEM; + p->eWithin = eWithin; + p->id = x.id; + p->iCell = x.iCell; + RTREE_QUEUE_TRACE(pCur, "PUSH-S:"); + break; + } + if( p->iCell>=nCell ){ + RTREE_QUEUE_TRACE(pCur, "POP-Se:"); + rtreeSearchPointPop(pCur); + } + } + pCur->atEOF = p==0; + return SQLITE_OK; +} /* ** Rtree virtual table module xNext method. */ static int rtreeNext(sqlite3_vtab_cursor *pVtabCursor){ - Rtree *pRtree = (Rtree *)(pVtabCursor->pVtab); RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor; int rc = SQLITE_OK; - /* RtreeCursor.pNode must not be NULL. If is is NULL, then this cursor is - ** already at EOF. It is against the rules to call the xNext() method of - ** a cursor that has already reached EOF. - */ - assert( pCsr->pNode ); - - if( pCsr->iStrategy==1 ){ - /* This "scan" is a direct lookup by rowid. There is no next entry. */ - nodeRelease(pRtree, pCsr->pNode); - pCsr->pNode = 0; - }else{ - /* Move to the next entry that matches the configured constraints. */ - int iHeight = 0; - while( pCsr->pNode ){ - RtreeNode *pNode = pCsr->pNode; - int nCell = NCELL(pNode); - for(pCsr->iCell++; pCsr->iCell<nCell; pCsr->iCell++){ - int isEof; - rc = descendToCell(pRtree, pCsr, iHeight, &isEof); - if( rc!=SQLITE_OK || !isEof ){ - return rc; - } - } - pCsr->pNode = pNode->pParent; - rc = nodeParentIndex(pRtree, pNode, &pCsr->iCell); - if( rc!=SQLITE_OK ){ - return rc; - } - nodeReference(pCsr->pNode); - nodeRelease(pRtree, pNode); - iHeight++; - } - } - + /* Move to the next entry that matches the configured constraints. */ + RTREE_QUEUE_TRACE(pCsr, "POP-Nx:"); + rtreeSearchPointPop(pCsr); + rc = rtreeStepToLeaf(pCsr); return rc; } /* ** Rtree virtual table module xRowid method. */ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){ - Rtree *pRtree = (Rtree *)pVtabCursor->pVtab; RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor; - - assert(pCsr->pNode); - *pRowid = nodeGetRowid(pRtree, pCsr->pNode, pCsr->iCell); - - return SQLITE_OK; + RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr); + int rc = SQLITE_OK; + RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); + if( rc==SQLITE_OK && p ){ + *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); + } + return rc; } /* ** Rtree virtual table module xColumn method. */ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ Rtree *pRtree = (Rtree *)cur->pVtab; RtreeCursor *pCsr = (RtreeCursor *)cur; + RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr); + RtreeCoord c; + int rc = SQLITE_OK; + RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); + if( rc ) return rc; + if( p==0 ) return SQLITE_OK; if( i==0 ){ - i64 iRowid = nodeGetRowid(pRtree, pCsr->pNode, pCsr->iCell); - sqlite3_result_int64(ctx, iRowid); + sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell)); }else{ - RtreeCoord c; - nodeGetCoord(pRtree, pCsr->pNode, pCsr->iCell, i-1, &c); + if( rc ) return rc; + nodeGetCoord(pRtree, pNode, p->iCell, i-1, &c); #ifndef SQLITE_RTREE_INT_ONLY if( pRtree->eCoordType==RTREE_COORD_REAL32 ){ sqlite3_result_double(ctx, c.f); }else #endif @@ -139855,11 +151712,10 @@ { assert( pRtree->eCoordType==RTREE_COORD_INT32 ); sqlite3_result_int(ctx, c.i); } } - return SQLITE_OK; } /* ** Use nodeAcquire() to obtain the leaf node containing the record with @@ -139866,16 +151722,22 @@ ** rowid iRowid. If successful, set *ppLeaf to point to the node and ** return SQLITE_OK. If there is no such record in the table, set ** *ppLeaf to 0 and return SQLITE_OK. If an error occurs, set *ppLeaf ** to zero and return an SQLite error code. */ -static int findLeafNode(Rtree *pRtree, i64 iRowid, RtreeNode **ppLeaf){ +static int findLeafNode( + Rtree *pRtree, /* RTree to search */ + i64 iRowid, /* The rowid searching for */ + RtreeNode **ppLeaf, /* Write the node here */ + sqlite3_int64 *piNode /* Write the node-id here */ +){ int rc; *ppLeaf = 0; sqlite3_bind_int64(pRtree->pReadRowid, 1, iRowid); if( sqlite3_step(pRtree->pReadRowid)==SQLITE_ROW ){ i64 iNode = sqlite3_column_int64(pRtree->pReadRowid, 0); + if( piNode ) *piNode = iNode; rc = nodeAcquire(pRtree, iNode, 0, ppLeaf); sqlite3_reset(pRtree->pReadRowid); }else{ rc = sqlite3_reset(pRtree->pReadRowid); } @@ -139887,13 +151749,14 @@ ** as the second argument for a MATCH constraint. The value passed as the ** first argument to this function is the right-hand operand to the MATCH ** operator. */ static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){ - RtreeMatchArg *p; - sqlite3_rtree_geometry *pGeom; - int nBlob; + RtreeMatchArg *pBlob; /* BLOB returned by geometry function */ + sqlite3_rtree_query_info *pInfo; /* Callback information */ + int nBlob; /* Size of the geometry function blob */ + int nExpected; /* Expected size of the BLOB */ /* Check that value is actually a blob. */ if( sqlite3_value_type(pValue)!=SQLITE_BLOB ) return SQLITE_ERROR; /* Check that the blob is roughly the right size. */ @@ -139902,31 +151765,33 @@ || ((nBlob-sizeof(RtreeMatchArg))%sizeof(RtreeDValue))!=0 ){ return SQLITE_ERROR; } - pGeom = (sqlite3_rtree_geometry *)sqlite3_malloc( - sizeof(sqlite3_rtree_geometry) + nBlob - ); - if( !pGeom ) return SQLITE_NOMEM; - memset(pGeom, 0, sizeof(sqlite3_rtree_geometry)); - p = (RtreeMatchArg *)&pGeom[1]; - - memcpy(p, sqlite3_value_blob(pValue), nBlob); - if( p->magic!=RTREE_GEOMETRY_MAGIC - || nBlob!=(int)(sizeof(RtreeMatchArg) + (p->nParam-1)*sizeof(RtreeDValue)) - ){ - sqlite3_free(pGeom); + pInfo = (sqlite3_rtree_query_info*)sqlite3_malloc( sizeof(*pInfo)+nBlob ); + if( !pInfo ) return SQLITE_NOMEM; + memset(pInfo, 0, sizeof(*pInfo)); + pBlob = (RtreeMatchArg*)&pInfo[1]; + + memcpy(pBlob, sqlite3_value_blob(pValue), nBlob); + nExpected = (int)(sizeof(RtreeMatchArg) + + (pBlob->nParam-1)*sizeof(RtreeDValue)); + if( pBlob->magic!=RTREE_GEOMETRY_MAGIC || nBlob!=nExpected ){ + sqlite3_free(pInfo); return SQLITE_ERROR; } + pInfo->pContext = pBlob->cb.pContext; + pInfo->nParam = pBlob->nParam; + pInfo->aParam = pBlob->aParam; - pGeom->pContext = p->pContext; - pGeom->nParam = p->nParam; - pGeom->aParam = p->aParam; - - pCons->xGeom = p->xGeom; - pCons->pGeom = pGeom; + if( pBlob->cb.xGeom ){ + pCons->u.xGeom = pBlob->cb.xGeom; + }else{ + pCons->op = RTREE_QUERY; + pCons->u.xQueryFunc = pBlob->cb.xQueryFunc; + } + pCons->pInfo = pInfo; return SQLITE_OK; } /* ** Rtree virtual table module xFilter method. @@ -139936,94 +151801,116 @@ int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ Rtree *pRtree = (Rtree *)pVtabCursor->pVtab; RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor; - RtreeNode *pRoot = 0; int ii; int rc = SQLITE_OK; + int iCell = 0; rtreeReference(pRtree); + /* Reset the cursor to the same state as rtreeOpen() leaves it in. */ freeCursorConstraints(pCsr); + sqlite3_free(pCsr->aPoint); + memset(pCsr, 0, sizeof(RtreeCursor)); + pCsr->base.pVtab = (sqlite3_vtab*)pRtree; + pCsr->iStrategy = idxNum; - if( idxNum==1 ){ /* Special case - lookup by rowid. */ RtreeNode *pLeaf; /* Leaf on which the required cell resides */ + RtreeSearchPoint *p; /* Search point for the the leaf */ i64 iRowid = sqlite3_value_int64(argv[0]); - rc = findLeafNode(pRtree, iRowid, &pLeaf); - pCsr->pNode = pLeaf; - if( pLeaf ){ - assert( rc==SQLITE_OK ); - rc = nodeRowidIndex(pRtree, pLeaf, iRowid, &pCsr->iCell); + i64 iNode = 0; + rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); + if( rc==SQLITE_OK && pLeaf!=0 ){ + p = rtreeSearchPointNew(pCsr, RTREE_ZERO, 0); + assert( p!=0 ); /* Always returns pCsr->sPoint */ + pCsr->aNode[0] = pLeaf; + p->id = iNode; + p->eWithin = PARTLY_WITHIN; + rc = nodeRowidIndex(pRtree, pLeaf, iRowid, &iCell); + p->iCell = iCell; + RTREE_QUEUE_TRACE(pCsr, "PUSH-F1:"); + }else{ + pCsr->atEOF = 1; } }else{ /* Normal case - r-tree scan. Set up the RtreeCursor.aConstraint array ** with the configured constraints. */ - if( argc>0 ){ + rc = nodeAcquire(pRtree, 1, 0, &pRoot); + if( rc==SQLITE_OK && argc>0 ){ pCsr->aConstraint = sqlite3_malloc(sizeof(RtreeConstraint)*argc); pCsr->nConstraint = argc; if( !pCsr->aConstraint ){ rc = SQLITE_NOMEM; }else{ memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*argc); + memset(pCsr->anQueue, 0, sizeof(u32)*(pRtree->iDepth + 1)); assert( (idxStr==0 && argc==0) || (idxStr && (int)strlen(idxStr)==argc*2) ); for(ii=0; ii<argc; ii++){ RtreeConstraint *p = &pCsr->aConstraint[ii]; p->op = idxStr[ii*2]; - p->iCoord = idxStr[ii*2+1]-'a'; - if( p->op==RTREE_MATCH ){ + p->iCoord = idxStr[ii*2+1]-'0'; + if( p->op>=RTREE_MATCH ){ /* A MATCH operator. The right-hand-side must be a blob that ** can be cast into an RtreeMatchArg object. One created using ** an sqlite3_rtree_geometry_callback() SQL user function. */ rc = deserializeGeometry(argv[ii], p); if( rc!=SQLITE_OK ){ break; } + p->pInfo->nCoord = pRtree->nDim*2; + p->pInfo->anQueue = pCsr->anQueue; + p->pInfo->mxLevel = pRtree->iDepth + 1; }else{ #ifdef SQLITE_RTREE_INT_ONLY - p->rValue = sqlite3_value_int64(argv[ii]); + p->u.rValue = sqlite3_value_int64(argv[ii]); #else - p->rValue = sqlite3_value_double(argv[ii]); + p->u.rValue = sqlite3_value_double(argv[ii]); #endif } } } } - - if( rc==SQLITE_OK ){ - pCsr->pNode = 0; - rc = nodeAcquire(pRtree, 1, 0, &pRoot); - } - if( rc==SQLITE_OK ){ - int isEof = 1; - int nCell = NCELL(pRoot); - pCsr->pNode = pRoot; - for(pCsr->iCell=0; rc==SQLITE_OK && pCsr->iCell<nCell; pCsr->iCell++){ - assert( pCsr->pNode==pRoot ); - rc = descendToCell(pRtree, pCsr, pRtree->iDepth, &isEof); - if( !isEof ){ - break; - } - } - if( rc==SQLITE_OK && isEof ){ - assert( pCsr->pNode==pRoot ); - nodeRelease(pRtree, pRoot); - pCsr->pNode = 0; - } - assert( rc!=SQLITE_OK || !pCsr->pNode || pCsr->iCell<NCELL(pCsr->pNode) ); - } - } - + if( rc==SQLITE_OK ){ + RtreeSearchPoint *pNew; + pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, pRtree->iDepth+1); + if( pNew==0 ) return SQLITE_NOMEM; + pNew->id = 1; + pNew->iCell = 0; + pNew->eWithin = PARTLY_WITHIN; + assert( pCsr->bPoint==1 ); + pCsr->aNode[0] = pRoot; + pRoot = 0; + RTREE_QUEUE_TRACE(pCsr, "PUSH-Fm:"); + rc = rtreeStepToLeaf(pCsr); + } + } + + nodeRelease(pRtree, pRoot); rtreeRelease(pRtree); return rc; } + +/* +** Set the pIdxInfo->estimatedRows variable to nRow. Unless this +** extension is currently being used by a version of SQLite too old to +** support estimatedRows. In that case this function is a no-op. +*/ +static void setEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){ +#if SQLITE_VERSION_NUMBER>=3008002 + if( sqlite3_libversion_number()>=3008002 ){ + pIdxInfo->estimatedRows = nRow; + } +#endif +} /* ** Rtree virtual table module xBestIndex method. There are three ** table scan strategies to choose from (in order from most to ** least desirable): @@ -140056,17 +151943,18 @@ ** The second of each pair of bytes identifies the coordinate column ** to which the constraint applies. The leftmost coordinate column ** is 'a', the second from the left 'b' etc. */ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + Rtree *pRtree = (Rtree*)tab; int rc = SQLITE_OK; int ii; + i64 nRow; /* Estimated rows returned by this scan */ int iIdx = 0; char zIdxStr[RTREE_MAX_DIMENSIONS*8+1]; memset(zIdxStr, 0, sizeof(zIdxStr)); - UNUSED_PARAMETER(tab); assert( pIdxInfo->idxStr==0 ); for(ii=0; ii<pIdxInfo->nConstraint && iIdx<(int)(sizeof(zIdxStr)-1); ii++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; @@ -140082,13 +151970,15 @@ pIdxInfo->aConstraintUsage[jj].omit = 1; /* This strategy involves a two rowid lookups on an B-Tree structures ** and then a linear search of an R-Tree node. This should be ** considered almost as quick as a direct rowid lookup (for which - ** sqlite uses an internal cost of 0.0). + ** sqlite uses an internal cost of 0.0). It is expected to return + ** a single row. */ - pIdxInfo->estimatedCost = 10.0; + pIdxInfo->estimatedCost = 30.0; + setEstimatedRows(pIdxInfo, 1); return SQLITE_OK; } if( p->usable && (p->iColumn>0 || p->op==SQLITE_INDEX_CONSTRAINT_MATCH) ){ u8 op; @@ -140102,11 +151992,11 @@ assert( p->op==SQLITE_INDEX_CONSTRAINT_MATCH ); op = RTREE_MATCH; break; } zIdxStr[iIdx++] = op; - zIdxStr[iIdx++] = p->iColumn - 1 + 'a'; + zIdxStr[iIdx++] = p->iColumn - 1 + '0'; pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2); pIdxInfo->aConstraintUsage[ii].omit = 1; } } @@ -140113,12 +152003,15 @@ pIdxInfo->idxNum = 2; pIdxInfo->needToFreeIdxStr = 1; if( iIdx>0 && 0==(pIdxInfo->idxStr = sqlite3_mprintf("%s", zIdxStr)) ){ return SQLITE_NOMEM; } - assert( iIdx>=0 ); - pIdxInfo->estimatedCost = (2000000.0 / (double)(iIdx + 1)); + + nRow = pRtree->nRowEst / (iIdx + 1); + pIdxInfo->estimatedCost = (double)6.0 * (double)nRow; + setEstimatedRows(pIdxInfo, nRow); + return rc; } /* ** Return the N-dimensional volumn of the cell stored in *p. @@ -140192,66 +152085,36 @@ area = cellArea(pRtree, &cell); cellUnion(pRtree, &cell, pCell); return (cellArea(pRtree, &cell)-area); } -#if VARIANT_RSTARTREE_CHOOSESUBTREE || VARIANT_RSTARTREE_SPLIT static RtreeDValue cellOverlap( Rtree *pRtree, RtreeCell *p, RtreeCell *aCell, - int nCell, - int iExclude + int nCell ){ int ii; - RtreeDValue overlap = 0.0; + RtreeDValue overlap = RTREE_ZERO; for(ii=0; ii<nCell; ii++){ -#if VARIANT_RSTARTREE_CHOOSESUBTREE - if( ii!=iExclude ) -#else - assert( iExclude==-1 ); - UNUSED_PARAMETER(iExclude); -#endif - { - int jj; - RtreeDValue o = (RtreeDValue)1; - for(jj=0; jj<(pRtree->nDim*2); jj+=2){ - RtreeDValue x1, x2; - - x1 = MAX(DCOORD(p->aCoord[jj]), DCOORD(aCell[ii].aCoord[jj])); - x2 = MIN(DCOORD(p->aCoord[jj+1]), DCOORD(aCell[ii].aCoord[jj+1])); - - if( x2<x1 ){ - o = 0.0; - break; - }else{ - o = o * (x2-x1); - } - } - overlap += o; - } + int jj; + RtreeDValue o = (RtreeDValue)1; + for(jj=0; jj<(pRtree->nDim*2); jj+=2){ + RtreeDValue x1, x2; + x1 = MAX(DCOORD(p->aCoord[jj]), DCOORD(aCell[ii].aCoord[jj])); + x2 = MIN(DCOORD(p->aCoord[jj+1]), DCOORD(aCell[ii].aCoord[jj+1])); + if( x2<x1 ){ + o = (RtreeDValue)0; + break; + }else{ + o = o * (x2-x1); + } + } + overlap += o; } return overlap; } -#endif - -#if VARIANT_RSTARTREE_CHOOSESUBTREE -static RtreeDValue cellOverlapEnlargement( - Rtree *pRtree, - RtreeCell *p, - RtreeCell *pInsert, - RtreeCell *aCell, - int nCell, - int iExclude -){ - RtreeDValue before, after; - before = cellOverlap(pRtree, p, aCell, nCell, iExclude); - cellUnion(pRtree, p, pInsert); - after = cellOverlap(pRtree, p, aCell, nCell, iExclude); - return (after-before); -} -#endif /* ** This function implements the ChooseLeaf algorithm from Gutman[84]. ** ChooseSubTree in r*tree terminology. @@ -140269,39 +152132,19 @@ for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){ int iCell; sqlite3_int64 iBest = 0; - RtreeDValue fMinGrowth = 0.0; - RtreeDValue fMinArea = 0.0; -#if VARIANT_RSTARTREE_CHOOSESUBTREE - RtreeDValue fMinOverlap = 0.0; - RtreeDValue overlap; -#endif + RtreeDValue fMinGrowth = RTREE_ZERO; + RtreeDValue fMinArea = RTREE_ZERO; int nCell = NCELL(pNode); RtreeCell cell; RtreeNode *pChild; RtreeCell *aCell = 0; -#if VARIANT_RSTARTREE_CHOOSESUBTREE - if( ii==(pRtree->iDepth-1) ){ - int jj; - aCell = sqlite3_malloc(sizeof(RtreeCell)*nCell); - if( !aCell ){ - rc = SQLITE_NOMEM; - nodeRelease(pRtree, pNode); - pNode = 0; - continue; - } - for(jj=0; jj<nCell; jj++){ - nodeGetCell(pRtree, pNode, jj, &aCell[jj]); - } - } -#endif - /* Select the child node which will be enlarged the least if pCell ** is inserted into it. Resolve ties by choosing the entry with ** the smallest area. */ for(iCell=0; iCell<nCell; iCell++){ @@ -140309,30 +152152,13 @@ RtreeDValue growth; RtreeDValue area; nodeGetCell(pRtree, pNode, iCell, &cell); growth = cellGrowth(pRtree, &cell, pCell); area = cellArea(pRtree, &cell); - -#if VARIANT_RSTARTREE_CHOOSESUBTREE - if( ii==(pRtree->iDepth-1) ){ - overlap = cellOverlapEnlargement(pRtree,&cell,pCell,aCell,nCell,iCell); - }else{ - overlap = 0.0; - } - if( (iCell==0) - || (overlap<fMinOverlap) - || (overlap==fMinOverlap && growth<fMinGrowth) - || (overlap==fMinOverlap && growth==fMinGrowth && area<fMinArea) - ){ - bBest = 1; - fMinOverlap = overlap; - } -#else if( iCell==0||growth<fMinGrowth||(growth==fMinGrowth && area<fMinArea) ){ bBest = 1; } -#endif if( bBest ){ fMinGrowth = growth; fMinArea = area; iBest = cell.iRowid; } @@ -140399,159 +152225,10 @@ return sqlite3_reset(pRtree->pWriteParent); } static int rtreeInsertCell(Rtree *, RtreeNode *, RtreeCell *, int); -#if VARIANT_GUTTMAN_LINEAR_SPLIT -/* -** Implementation of the linear variant of the PickNext() function from -** Guttman[84]. -*/ -static RtreeCell *LinearPickNext( - Rtree *pRtree, - RtreeCell *aCell, - int nCell, - RtreeCell *pLeftBox, - RtreeCell *pRightBox, - int *aiUsed -){ - int ii; - for(ii=0; aiUsed[ii]; ii++); - aiUsed[ii] = 1; - return &aCell[ii]; -} - -/* -** Implementation of the linear variant of the PickSeeds() function from -** Guttman[84]. -*/ -static void LinearPickSeeds( - Rtree *pRtree, - RtreeCell *aCell, - int nCell, - int *piLeftSeed, - int *piRightSeed -){ - int i; - int iLeftSeed = 0; - int iRightSeed = 1; - RtreeDValue maxNormalInnerWidth = (RtreeDValue)0; - - /* Pick two "seed" cells from the array of cells. The algorithm used - ** here is the LinearPickSeeds algorithm from Gutman[1984]. The - ** indices of the two seed cells in the array are stored in local - ** variables iLeftSeek and iRightSeed. - */ - for(i=0; i<pRtree->nDim; i++){ - RtreeDValue x1 = DCOORD(aCell[0].aCoord[i*2]); - RtreeDValue x2 = DCOORD(aCell[0].aCoord[i*2+1]); - RtreeDValue x3 = x1; - RtreeDValue x4 = x2; - int jj; - - int iCellLeft = 0; - int iCellRight = 0; - - for(jj=1; jj<nCell; jj++){ - RtreeDValue left = DCOORD(aCell[jj].aCoord[i*2]); - RtreeDValue right = DCOORD(aCell[jj].aCoord[i*2+1]); - - if( left<x1 ) x1 = left; - if( right>x4 ) x4 = right; - if( left>x3 ){ - x3 = left; - iCellRight = jj; - } - if( right<x2 ){ - x2 = right; - iCellLeft = jj; - } - } - - if( x4!=x1 ){ - RtreeDValue normalwidth = (x3 - x2) / (x4 - x1); - if( normalwidth>maxNormalInnerWidth ){ - iLeftSeed = iCellLeft; - iRightSeed = iCellRight; - } - } - } - - *piLeftSeed = iLeftSeed; - *piRightSeed = iRightSeed; -} -#endif /* VARIANT_GUTTMAN_LINEAR_SPLIT */ - -#if VARIANT_GUTTMAN_QUADRATIC_SPLIT -/* -** Implementation of the quadratic variant of the PickNext() function from -** Guttman[84]. -*/ -static RtreeCell *QuadraticPickNext( - Rtree *pRtree, - RtreeCell *aCell, - int nCell, - RtreeCell *pLeftBox, - RtreeCell *pRightBox, - int *aiUsed -){ - #define FABS(a) ((a)<0.0?-1.0*(a):(a)) - - int iSelect = -1; - RtreeDValue fDiff; - int ii; - for(ii=0; ii<nCell; ii++){ - if( aiUsed[ii]==0 ){ - RtreeDValue left = cellGrowth(pRtree, pLeftBox, &aCell[ii]); - RtreeDValue right = cellGrowth(pRtree, pLeftBox, &aCell[ii]); - RtreeDValue diff = FABS(right-left); - if( iSelect<0 || diff>fDiff ){ - fDiff = diff; - iSelect = ii; - } - } - } - aiUsed[iSelect] = 1; - return &aCell[iSelect]; -} - -/* -** Implementation of the quadratic variant of the PickSeeds() function from -** Guttman[84]. -*/ -static void QuadraticPickSeeds( - Rtree *pRtree, - RtreeCell *aCell, - int nCell, - int *piLeftSeed, - int *piRightSeed -){ - int ii; - int jj; - - int iLeftSeed = 0; - int iRightSeed = 1; - RtreeDValue fWaste = 0.0; - - for(ii=0; ii<nCell; ii++){ - for(jj=ii+1; jj<nCell; jj++){ - RtreeDValue right = cellArea(pRtree, &aCell[jj]); - RtreeDValue growth = cellGrowth(pRtree, &aCell[ii], &aCell[jj]); - RtreeDValue waste = growth - right; - - if( waste>fWaste ){ - iLeftSeed = ii; - iRightSeed = jj; - fWaste = waste; - } - } - } - - *piLeftSeed = iLeftSeed; - *piRightSeed = iRightSeed; -} -#endif /* VARIANT_GUTTMAN_QUADRATIC_SPLIT */ /* ** Arguments aIdx, aDistance and aSpare all point to arrays of size ** nIdx. The aIdx array contains the set of integers from 0 to ** (nIdx-1) in no particular order. This function sorts the values @@ -140688,11 +152365,10 @@ } #endif } } -#if VARIANT_RSTARTREE_SPLIT /* ** Implementation of the R*-tree variant of SplitNode from Beckman[1990]. */ static int splitNodeStartree( Rtree *pRtree, @@ -140707,11 +152383,11 @@ int *aSpare; int ii; int iBestDim = 0; int iBestSplit = 0; - RtreeDValue fBestMargin = 0.0; + RtreeDValue fBestMargin = RTREE_ZERO; int nByte = (pRtree->nDim+1)*(sizeof(int*)+nCell*sizeof(int)); aaSorted = (int **)sqlite3_malloc(nByte); if( !aaSorted ){ @@ -140728,13 +152404,13 @@ } SortByDimension(pRtree, aaSorted[ii], nCell, ii, aCell, aSpare); } for(ii=0; ii<pRtree->nDim; ii++){ - RtreeDValue margin = 0.0; - RtreeDValue fBestOverlap = 0.0; - RtreeDValue fBestArea = 0.0; + RtreeDValue margin = RTREE_ZERO; + RtreeDValue fBestOverlap = RTREE_ZERO; + RtreeDValue fBestArea = RTREE_ZERO; int iBestLeft = 0; int nLeft; for( nLeft=RTREE_MINCELLS(pRtree); @@ -140756,11 +152432,11 @@ cellUnion(pRtree, &right, &aCell[aaSorted[ii][kk]]); } } margin += cellMargin(pRtree, &left); margin += cellMargin(pRtree, &right); - overlap = cellOverlap(pRtree, &left, &right, 1, -1); + overlap = cellOverlap(pRtree, &left, &right, 1); area = cellArea(pRtree, &left) + cellArea(pRtree, &right); if( (nLeft==RTREE_MINCELLS(pRtree)) || (overlap<fBestOverlap) || (overlap==fBestOverlap && area<fBestArea) ){ @@ -140788,67 +152464,11 @@ } sqlite3_free(aaSorted); return SQLITE_OK; } -#endif - -#if VARIANT_GUTTMAN_SPLIT -/* -** Implementation of the regular R-tree SplitNode from Guttman[1984]. -*/ -static int splitNodeGuttman( - Rtree *pRtree, - RtreeCell *aCell, - int nCell, - RtreeNode *pLeft, - RtreeNode *pRight, - RtreeCell *pBboxLeft, - RtreeCell *pBboxRight -){ - int iLeftSeed = 0; - int iRightSeed = 1; - int *aiUsed; - int i; - - aiUsed = sqlite3_malloc(sizeof(int)*nCell); - if( !aiUsed ){ - return SQLITE_NOMEM; - } - memset(aiUsed, 0, sizeof(int)*nCell); - - PickSeeds(pRtree, aCell, nCell, &iLeftSeed, &iRightSeed); - - memcpy(pBboxLeft, &aCell[iLeftSeed], sizeof(RtreeCell)); - memcpy(pBboxRight, &aCell[iRightSeed], sizeof(RtreeCell)); - nodeInsertCell(pRtree, pLeft, &aCell[iLeftSeed]); - nodeInsertCell(pRtree, pRight, &aCell[iRightSeed]); - aiUsed[iLeftSeed] = 1; - aiUsed[iRightSeed] = 1; - - for(i=nCell-2; i>0; i--){ - RtreeCell *pNext; - pNext = PickNext(pRtree, aCell, nCell, pBboxLeft, pBboxRight, aiUsed); - RtreeDValue diff = - cellGrowth(pRtree, pBboxLeft, pNext) - - cellGrowth(pRtree, pBboxRight, pNext) - ; - if( (RTREE_MINCELLS(pRtree)-NCELL(pRight)==i) - || (diff>0.0 && (RTREE_MINCELLS(pRtree)-NCELL(pLeft)!=i)) - ){ - nodeInsertCell(pRtree, pRight, pNext); - cellUnion(pRtree, pBboxRight, pNext); - }else{ - nodeInsertCell(pRtree, pLeft, pNext); - cellUnion(pRtree, pBboxLeft, pNext); - } - } - - sqlite3_free(aiUsed); - return SQLITE_OK; -} -#endif + static int updateMapping( Rtree *pRtree, i64 iRowid, RtreeNode *pNode, @@ -140922,11 +152542,12 @@ } memset(pLeft->zData, 0, pRtree->iNodeSize); memset(pRight->zData, 0, pRtree->iNodeSize); - rc = AssignCells(pRtree, aCell, nCell, pLeft, pRight, &leftbbox, &rightbbox); + rc = splitNodeStartree(pRtree, aCell, nCell, pLeft, pRight, + &leftbbox, &rightbbox); if( rc!=SQLITE_OK ){ goto splitnode_out; } /* Ensure both child nodes have node numbers assigned to them by calling @@ -141205,11 +152826,11 @@ for(iDim=0; iDim<pRtree->nDim; iDim++){ aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2)); } for(ii=0; ii<nCell; ii++){ - aDistance[ii] = 0.0; + aDistance[ii] = RTREE_ZERO; for(iDim=0; iDim<pRtree->nDim; iDim++){ RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) - DCOORD(aCell[ii].aCoord[iDim*2])); aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]); } @@ -141271,20 +152892,16 @@ nodeReference(pNode); pChild->pParent = pNode; } } if( nodeInsertCell(pRtree, pNode, pCell) ){ -#if VARIANT_RSTARTREE_REINSERT if( iHeight<=pRtree->iReinsertHeight || pNode->iNode==1){ rc = SplitNode(pRtree, pNode, pCell, iHeight); }else{ pRtree->iReinsertHeight = iHeight; rc = Reinsert(pRtree, pNode, pCell, iHeight); } -#else - rc = SplitNode(pRtree, pNode, pCell, iHeight); -#endif }else{ rc = AdjustTree(pRtree, pNode, pCell); if( rc==SQLITE_OK ){ if( iHeight==0 ){ rc = rowidWrite(pRtree, pCell->iRowid, pNode->iNode); @@ -141350,11 +152967,11 @@ /* Obtain a reference to the leaf node that contains the entry ** about to be deleted. */ if( rc==SQLITE_OK ){ - rc = findLeafNode(pRtree, iDelete, &pLeaf); + rc = findLeafNode(pRtree, iDelete, &pLeaf, 0); } /* Delete the cell in question from the leaf node. */ if( rc==SQLITE_OK ){ int rc2; @@ -141463,10 +153080,12 @@ RtreeCell cell; /* New cell to insert if nData>1 */ int bHaveRowid = 0; /* Set to 1 after new rowid is determined */ rtreeReference(pRtree); assert(nData>=1); + + cell.iRowid = 0; /* Used only to suppress a compiler warning */ /* Constraint handling. A write operation on an r-tree table may return ** SQLITE_CONSTRAINT for two reasons: ** ** 1. A duplicate rowid value, or @@ -141588,10 +153207,47 @@ rc = sqlite3_exec(pRtree->db, zSql, 0, 0, 0); sqlite3_free(zSql); } return rc; } + +/* +** This function populates the pRtree->nRowEst variable with an estimate +** of the number of rows in the virtual table. If possible, this is based +** on sqlite_stat1 data. Otherwise, use RTREE_DEFAULT_ROWEST. +*/ +static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){ + const char *zFmt = "SELECT stat FROM %Q.sqlite_stat1 WHERE tbl = '%q_rowid'"; + char *zSql; + sqlite3_stmt *p; + int rc; + i64 nRow = 0; + + zSql = sqlite3_mprintf(zFmt, pRtree->zDb, pRtree->zName); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(db, zSql, -1, &p, 0); + if( rc==SQLITE_OK ){ + if( sqlite3_step(p)==SQLITE_ROW ) nRow = sqlite3_column_int64(p, 0); + rc = sqlite3_finalize(p); + }else if( rc!=SQLITE_NOMEM ){ + rc = SQLITE_OK; + } + + if( rc==SQLITE_OK ){ + if( nRow==0 ){ + pRtree->nRowEst = RTREE_DEFAULT_ROWEST; + }else{ + pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST); + } + } + sqlite3_free(zSql); + } + + return rc; +} static sqlite3_module rtreeModule = { 0, /* iVersion */ rtreeCreate, /* xCreate - create a table */ rtreeConnect, /* xConnect - connect to an existing table */ @@ -141650,11 +153306,12 @@ if( isCreate ){ char *zCreate = sqlite3_mprintf( "CREATE TABLE \"%w\".\"%w_node\"(nodeno INTEGER PRIMARY KEY, data BLOB);" "CREATE TABLE \"%w\".\"%w_rowid\"(rowid INTEGER PRIMARY KEY, nodeno INTEGER);" -"CREATE TABLE \"%w\".\"%w_parent\"(nodeno INTEGER PRIMARY KEY, parentnode INTEGER);" +"CREATE TABLE \"%w\".\"%w_parent\"(nodeno INTEGER PRIMARY KEY," + " parentnode INTEGER);" "INSERT INTO '%q'.'%q_node' VALUES(1, zeroblob(%d))", zDb, zPrefix, zDb, zPrefix, zDb, zPrefix, zDb, zPrefix, pRtree->iNodeSize ); if( !zCreate ){ return SQLITE_NOMEM; @@ -141674,10 +153331,11 @@ appStmt[5] = &pRtree->pDeleteRowid; appStmt[6] = &pRtree->pReadParent; appStmt[7] = &pRtree->pWriteParent; appStmt[8] = &pRtree->pDeleteParent; + rc = rtreeQueryStat1(db, pRtree); for(i=0; i<N_STATEMENT && rc==SQLITE_OK; i++){ char *zSql = sqlite3_mprintf(azSql[i], zDb, zPrefix); if( zSql ){ rc = sqlite3_prepare_v2(db, zSql, -1, appStmt[i], 0); }else{ @@ -141851,10 +153509,12 @@ } if( rc==SQLITE_OK ){ *ppVtab = (sqlite3_vtab *)pRtree; }else{ + assert( *ppVtab==0 ); + assert( pRtree->nBusy==1 ); rtreeRelease(pRtree); } return rc; } @@ -141861,14 +153521,14 @@ /* ** Implementation of a scalar function that decodes r-tree nodes to ** human readable strings. This can be used for debugging and analysis. ** -** The scalar function takes two arguments, a blob of data containing -** an r-tree node, and the number of dimensions the r-tree indexes. -** For a two-dimensional r-tree structure called "rt", to deserialize -** all nodes, a statement like: +** The scalar function takes two arguments: (1) the number of dimensions +** to the rtree (between 1 and 5, inclusive) and (2) a blob of data containing +** an r-tree node. For a two-dimensional r-tree structure called "rt", to +** deserialize all nodes, a statement like: ** ** SELECT rtreenode(2, data) FROM rt_node; ** ** The human readable string takes the form of a Tcl list with one ** entry for each cell in the r-tree node. Each entry is itself a @@ -141897,11 +153557,11 @@ nodeGetCell(&tree, &node, ii, &cell); sqlite3_snprintf(512-nCell,&zCell[nCell],"%lld", cell.iRowid); nCell = (int)strlen(zCell); for(jj=0; jj<tree.nDim*2; jj++){ #ifndef SQLITE_RTREE_INT_ONLY - sqlite3_snprintf(512-nCell,&zCell[nCell], " %f", + sqlite3_snprintf(512-nCell,&zCell[nCell], " %g", (double)cell.aCoord[jj].f); #else sqlite3_snprintf(512-nCell,&zCell[nCell], " %d", cell.aCoord[jj].i); #endif @@ -141918,10 +153578,19 @@ } sqlite3_result_text(ctx, zText, -1, sqlite3_free); } +/* This routine implements an SQL function that returns the "depth" parameter +** from the front of a blob that is an r-tree node. For example: +** +** SELECT rtreedepth(data) FROM rt_node WHERE nodeno=1; +** +** The depth value is 0 for all nodes other than the root node, and the root +** node always has nodeno=1, so the example above is the primary use for this +** routine. This routine is intended for testing and analysis only. +*/ static void rtreedepth(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ UNUSED_PARAMETER(nArg); if( sqlite3_value_type(apArg[0])!=SQLITE_BLOB || sqlite3_value_bytes(apArg[0])<2 ){ @@ -141960,26 +153629,35 @@ return rc; } /* -** A version of sqlite3_free() that can be used as a callback. This is used -** in two places - as the destructor for the blob value returned by the -** invocation of a geometry function, and as the destructor for the geometry -** functions themselves. +** This routine deletes the RtreeGeomCallback object that was attached +** one of the SQL functions create by sqlite3_rtree_geometry_callback() +** or sqlite3_rtree_query_callback(). In other words, this routine is the +** destructor for an RtreeGeomCallback objecct. This routine is called when +** the corresponding SQL function is deleted. */ -static void doSqlite3Free(void *p){ +static void rtreeFreeCallback(void *p){ + RtreeGeomCallback *pInfo = (RtreeGeomCallback*)p; + if( pInfo->xDestructor ) pInfo->xDestructor(pInfo->pContext); sqlite3_free(p); } /* -** Each call to sqlite3_rtree_geometry_callback() creates an ordinary SQLite -** scalar user function. This C function is the callback used for all such -** registered SQL functions. +** Each call to sqlite3_rtree_geometry_callback() or +** sqlite3_rtree_query_callback() creates an ordinary SQLite +** scalar function that is implemented by this routine. +** +** All this function does is construct an RtreeMatchArg object that +** contains the geometry-checking callback routines and a list of +** parameters to this function, then return that RtreeMatchArg object +** as a BLOB. ** -** The scalar user functions return a blob that is interpreted by r-tree -** table MATCH operators. +** The R-Tree MATCH operator will read the returned BLOB, deserialize +** the RtreeMatchArg object, and use the RtreeMatchArg object to figure +** out which elements of the R-Tree should be returned by the query. */ static void geomCallback(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){ RtreeGeomCallback *pGeomCtx = (RtreeGeomCallback *)sqlite3_user_data(ctx); RtreeMatchArg *pBlob; int nBlob; @@ -141989,53 +153667,76 @@ if( !pBlob ){ sqlite3_result_error_nomem(ctx); }else{ int i; pBlob->magic = RTREE_GEOMETRY_MAGIC; - pBlob->xGeom = pGeomCtx->xGeom; - pBlob->pContext = pGeomCtx->pContext; + pBlob->cb = pGeomCtx[0]; pBlob->nParam = nArg; for(i=0; i<nArg; i++){ #ifdef SQLITE_RTREE_INT_ONLY pBlob->aParam[i] = sqlite3_value_int64(aArg[i]); #else pBlob->aParam[i] = sqlite3_value_double(aArg[i]); #endif } - sqlite3_result_blob(ctx, pBlob, nBlob, doSqlite3Free); + sqlite3_result_blob(ctx, pBlob, nBlob, sqlite3_free); } } /* ** Register a new geometry function for use with the r-tree MATCH operator. */ -SQLITE_API int sqlite3_rtree_geometry_callback( - sqlite3 *db, - const char *zGeom, - int (*xGeom)(sqlite3_rtree_geometry *, int, RtreeDValue *, int *), - void *pContext +SQLITE_API int SQLITE_STDCALL sqlite3_rtree_geometry_callback( + sqlite3 *db, /* Register SQL function on this connection */ + const char *zGeom, /* Name of the new SQL function */ + int (*xGeom)(sqlite3_rtree_geometry*,int,RtreeDValue*,int*), /* Callback */ + void *pContext /* Extra data associated with the callback */ ){ RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */ /* Allocate and populate the context object. */ pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback)); if( !pGeomCtx ) return SQLITE_NOMEM; pGeomCtx->xGeom = xGeom; + pGeomCtx->xQueryFunc = 0; + pGeomCtx->xDestructor = 0; pGeomCtx->pContext = pContext; - - /* Create the new user-function. Register a destructor function to delete - ** the context object when it is no longer required. */ return sqlite3_create_function_v2(db, zGeom, -1, SQLITE_ANY, - (void *)pGeomCtx, geomCallback, 0, 0, doSqlite3Free + (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback + ); +} + +/* +** Register a new 2nd-generation geometry function for use with the +** r-tree MATCH operator. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_rtree_query_callback( + sqlite3 *db, /* Register SQL function on this connection */ + const char *zQueryFunc, /* Name of new SQL function */ + int (*xQueryFunc)(sqlite3_rtree_query_info*), /* Callback */ + void *pContext, /* Extra data passed into the callback */ + void (*xDestructor)(void*) /* Destructor for the extra data */ +){ + RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */ + + /* Allocate and populate the context object. */ + pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback)); + if( !pGeomCtx ) return SQLITE_NOMEM; + pGeomCtx->xGeom = 0; + pGeomCtx->xQueryFunc = xQueryFunc; + pGeomCtx->xDestructor = xDestructor; + pGeomCtx->pContext = pContext; + return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY, + (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback ); } #if !SQLITE_CORE #ifdef _WIN32 __declspec(dllexport) #endif -SQLITE_API int sqlite3_rtree_init( +SQLITE_API int SQLITE_STDCALL sqlite3_rtree_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ SQLITE_EXTENSION_INIT2(pApi) @@ -142536,11 +154237,11 @@ #if !SQLITE_CORE #ifdef _WIN32 __declspec(dllexport) #endif -SQLITE_API int sqlite3_icu_init( +SQLITE_API int SQLITE_STDCALL sqlite3_icu_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ SQLITE_EXTENSION_INIT2(pApi) Index: src/sqlite3.h ================================================================== --- src/sqlite3.h +++ src/sqlite3.h @@ -41,25 +41,29 @@ extern "C" { #endif /* -** Add the ability to override 'extern' +** Provide the ability to override linkage features of the interface. */ #ifndef SQLITE_EXTERN # define SQLITE_EXTERN extern #endif - #ifndef SQLITE_API # define SQLITE_API #endif - +#ifndef SQLITE_CDECL +# define SQLITE_CDECL +#endif +#ifndef SQLITE_STDCALL +# define SQLITE_STDCALL +#endif /* ** These no-op macros are used in front of interfaces to mark those ** interfaces as either deprecated or experimental. New applications -** should not use deprecated interfaces - they are support for backwards +** should not use deprecated interfaces - they are supported for backwards ** compatibility only. Application writers should be aware that ** experimental interfaces are subject to change in point releases. ** ** These macros used to resolve to various kinds of compiler magic that ** would generate warning messages when they were used. But that @@ -105,13 +109,13 @@ ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.8.1" -#define SQLITE_VERSION_NUMBER 3008001 -#define SQLITE_SOURCE_ID "2013-09-16 12:57:19 daf6ba413cb3cb6065774ba07495eab4a28b49b0" +#define SQLITE_VERSION "3.8.9" +#define SQLITE_VERSION_NUMBER 3008009 +#define SQLITE_SOURCE_ID "2015-03-30 23:43:56 395bb3e677a6551b06ba96fc58c393132b93d1e8" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version, sqlite3_sourceid ** @@ -140,13 +144,13 @@ ** [SQLITE_SOURCE_ID] C preprocessor macro. ** ** See also: [sqlite_version()] and [sqlite_source_id()]. */ SQLITE_API SQLITE_EXTERN const char sqlite3_version[]; -SQLITE_API const char *sqlite3_libversion(void); -SQLITE_API const char *sqlite3_sourceid(void); -SQLITE_API int sqlite3_libversion_number(void); +SQLITE_API const char *SQLITE_STDCALL sqlite3_libversion(void); +SQLITE_API const char *SQLITE_STDCALL sqlite3_sourceid(void); +SQLITE_API int SQLITE_STDCALL sqlite3_libversion_number(void); /* ** CAPI3REF: Run-Time Library Compilation Options Diagnostics ** ** ^The sqlite3_compileoption_used() function returns 0 or 1 @@ -167,12 +171,12 @@ ** ** See also: SQL functions [sqlite_compileoption_used()] and ** [sqlite_compileoption_get()] and the [compile_options pragma]. */ #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS -SQLITE_API int sqlite3_compileoption_used(const char *zOptName); -SQLITE_API const char *sqlite3_compileoption_get(int N); +SQLITE_API int SQLITE_STDCALL sqlite3_compileoption_used(const char *zOptName); +SQLITE_API const char *SQLITE_STDCALL sqlite3_compileoption_get(int N); #endif /* ** CAPI3REF: Test To See If The Library Is Threadsafe ** @@ -199,19 +203,19 @@ ** This interface only reports on the compile-time mutex setting ** of the [SQLITE_THREADSAFE] flag. If SQLite is compiled with ** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but ** can be fully or partially disabled using a call to [sqlite3_config()] ** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD], -** or [SQLITE_CONFIG_MUTEX]. ^(The return value of the +** or [SQLITE_CONFIG_SERIALIZED]. ^(The return value of the ** sqlite3_threadsafe() function shows only the compile-time setting of ** thread safety, not any run-time changes to that setting made by ** sqlite3_config(). In other words, the return value from sqlite3_threadsafe() ** is unchanged by calls to sqlite3_config().)^ ** ** See the [threading mode] documentation for additional information. */ -SQLITE_API int sqlite3_threadsafe(void); +SQLITE_API int SQLITE_STDCALL sqlite3_threadsafe(void); /* ** CAPI3REF: Database Connection Handle ** KEYWORDS: {database connection} {database connections} ** @@ -267,19 +271,19 @@ /* ** CAPI3REF: Closing A Database Connection ** ** ^The sqlite3_close() and sqlite3_close_v2() routines are destructors ** for the [sqlite3] object. -** ^Calls to sqlite3_close() and sqlite3_close_v2() return SQLITE_OK if +** ^Calls to sqlite3_close() and sqlite3_close_v2() return [SQLITE_OK] if ** the [sqlite3] object is successfully destroyed and all associated ** resources are deallocated. ** ** ^If the database connection is associated with unfinalized prepared ** statements or unfinished sqlite3_backup objects then sqlite3_close() ** will leave the database connection open and return [SQLITE_BUSY]. ** ^If sqlite3_close_v2() is called with unfinalized prepared statements -** and unfinished sqlite3_backups, then the database connection becomes +** and/or unfinished sqlite3_backups, then the database connection becomes ** an unusable "zombie" which will automatically be deallocated when the ** last prepared statement is finalized or the last sqlite3_backup is ** finished. The sqlite3_close_v2() interface is intended for use with ** host languages that are garbage collected, and where the order in which ** destructors are called is arbitrary. @@ -288,11 +292,11 @@ ** [sqlite3_blob_close | close] all [BLOB handles], and ** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated ** with the [sqlite3] object prior to attempting to close the object. ^If ** sqlite3_close_v2() is called on a [database connection] that still has ** outstanding [prepared statements], [BLOB handles], and/or -** [sqlite3_backup] objects then it returns SQLITE_OK but the deallocation +** [sqlite3_backup] objects then it returns [SQLITE_OK] and the deallocation ** of resources is deferred until all [prepared statements], [BLOB handles], ** and [sqlite3_backup] objects are also destroyed. ** ** ^If an [sqlite3] object is destroyed while a transaction is open, ** the transaction is automatically rolled back. @@ -303,12 +307,12 @@ ** from [sqlite3_open()], [sqlite3_open16()], or ** [sqlite3_open_v2()], and not previously closed. ** ^Calling sqlite3_close() or sqlite3_close_v2() with a NULL pointer ** argument is a harmless no-op. */ -SQLITE_API int sqlite3_close(sqlite3*); -SQLITE_API int sqlite3_close_v2(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_close(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_close_v2(sqlite3*); /* ** The type for a callback function. ** This is legacy and deprecated. It is included for historical ** compatibility and is not documented. @@ -368,36 +372,34 @@ ** Restrictions: ** ** <ul> ** <li> The application must insure that the 1st parameter to sqlite3_exec() ** is a valid and open [database connection]. -** <li> The application must not close [database connection] specified by +** <li> The application must not close the [database connection] specified by ** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running. ** <li> The application must not modify the SQL statement text passed into ** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running. ** </ul> */ -SQLITE_API int sqlite3_exec( +SQLITE_API int SQLITE_STDCALL sqlite3_exec( sqlite3*, /* An open database */ const char *sql, /* SQL to be evaluated */ int (*callback)(void*,int,char**,char**), /* Callback function */ void *, /* 1st argument to callback */ char **errmsg /* Error msg written here */ ); /* ** CAPI3REF: Result Codes -** KEYWORDS: SQLITE_OK {error code} {error codes} -** KEYWORDS: {result code} {result codes} +** KEYWORDS: {result code definitions} ** ** Many SQLite functions return an integer result code from the set shown ** here in order to indicate success or failure. ** ** New error codes may be added in future versions of SQLite. ** -** See also: [SQLITE_IOERR_READ | extended result codes], -** [sqlite3_vtab_on_conflict()] [SQLITE_ROLLBACK | result codes]. +** See also: [extended result code definitions] */ #define SQLITE_OK 0 /* Successful result */ /* beginning-of-error-codes */ #define SQLITE_ERROR 1 /* SQL error or missing database */ #define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ @@ -431,30 +433,23 @@ #define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ /* end-of-error-codes */ /* ** CAPI3REF: Extended Result Codes -** KEYWORDS: {extended error code} {extended error codes} -** KEYWORDS: {extended result code} {extended result codes} +** KEYWORDS: {extended result code definitions} ** -** In its default configuration, SQLite API routines return one of 26 integer -** [SQLITE_OK | result codes]. However, experience has shown that many of +** In its default configuration, SQLite API routines return one of 30 integer +** [result codes]. However, experience has shown that many of ** these result codes are too coarse-grained. They do not provide as ** much information about problems as programmers might like. In an effort to ** address this, newer versions of SQLite (version 3.3.8 and later) include ** support for additional result codes that provide more detailed information -** about errors. The extended result codes are enabled or disabled +** about errors. These [extended result codes] are enabled or disabled ** on a per database connection basis using the -** [sqlite3_extended_result_codes()] API. -** -** Some of the available extended result codes are listed here. -** One may expect the number of extended result codes will be expand -** over time. Software that uses extended result codes should expect -** to see new result codes in future releases of SQLite. -** -** The SQLITE_OK result code will never be extended. It will always -** be exactly zero. +** [sqlite3_extended_result_codes()] API. Or, the extended code for +** the most recent error can be obtained using +** [sqlite3_extended_errcode()]. */ #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) #define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) @@ -489,10 +484,11 @@ #define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8)) #define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8)) #define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) #define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8)) #define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3<<8)) +#define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4<<8)) #define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2<<8)) #define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1<<8)) #define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2<<8)) #define SQLITE_CONSTRAINT_FOREIGNKEY (SQLITE_CONSTRAINT | (3<<8)) #define SQLITE_CONSTRAINT_FUNCTION (SQLITE_CONSTRAINT | (4<<8)) @@ -499,13 +495,15 @@ #define SQLITE_CONSTRAINT_NOTNULL (SQLITE_CONSTRAINT | (5<<8)) #define SQLITE_CONSTRAINT_PRIMARYKEY (SQLITE_CONSTRAINT | (6<<8)) #define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7<<8)) #define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) #define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) +#define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) #define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) #define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) +#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) /* ** CAPI3REF: Flags For File Open Operations ** ** These bit values are intended for use in the @@ -555,11 +553,15 @@ ** information is written to disk in the same order as calls ** to xWrite(). The SQLITE_IOCAP_POWERSAFE_OVERWRITE property means that ** after reboot following a crash or power loss, the only bytes in a ** file that were written at the application level might have changed ** and that adjacent bytes, even bytes within the same sector are -** guaranteed to be unchanged. +** guaranteed to be unchanged. The SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN +** flag indicate that a file cannot be deleted when open. The +** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on +** read-only media and cannot be changed even by processes with +** elevated privileges. */ #define SQLITE_IOCAP_ATOMIC 0x00000001 #define SQLITE_IOCAP_ATOMIC512 0x00000002 #define SQLITE_IOCAP_ATOMIC1K 0x00000004 #define SQLITE_IOCAP_ATOMIC2K 0x00000008 @@ -570,10 +572,11 @@ #define SQLITE_IOCAP_ATOMIC64K 0x00000100 #define SQLITE_IOCAP_SAFE_APPEND 0x00000200 #define SQLITE_IOCAP_SEQUENTIAL 0x00000400 #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 +#define SQLITE_IOCAP_IMMUTABLE 0x00002000 /* ** CAPI3REF: File Locking Levels ** ** SQLite uses one of these integer values as the second @@ -676,11 +679,11 @@ ** write return values. Potential uses for xFileControl() might be ** functions to enable blocking locks with timeouts, to change the ** locking strategy (for example to use dot-file locks), to inquire ** about the status of a lock, or to break stale locks. The SQLite ** core reserves all opcodes less than 100 for its own use. -** A [SQLITE_FCNTL_LOCKSTATE | list of opcodes] less than 100 is available. +** A [file control opcodes | list of opcodes] less than 100 is available. ** Applications that define a custom xFileControl method should use opcodes ** greater than 100 to avoid conflicts. VFS implementations should ** return [SQLITE_NOTFOUND] for file control opcodes that they do not ** recognize. ** @@ -749,23 +752,26 @@ /* Additional methods may be added in future releases */ }; /* ** CAPI3REF: Standard File Control Opcodes +** KEYWORDS: {file control opcodes} {file control opcode} ** ** These integer constants are opcodes for the xFileControl method ** of the [sqlite3_io_methods] object and for the [sqlite3_file_control()] ** interface. ** +** <ul> +** <li>[[SQLITE_FCNTL_LOCKSTATE]] ** The [SQLITE_FCNTL_LOCKSTATE] opcode is used for debugging. This ** opcode causes the xFileControl method to write the current state of ** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED], ** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE]) ** into an integer that the pArg argument points to. This capability -** is used during testing and only needs to be supported when SQLITE_TEST -** is defined. -** <ul> +** is used during testing and is only available when the SQLITE_TEST +** compile-time option is used. +** ** <li>[[SQLITE_FCNTL_SIZE_HINT]] ** The [SQLITE_FCNTL_SIZE_HINT] opcode is used by SQLite to give the VFS ** layer a hint of how large the database file will grow to be during the ** current transaction. This hint is not guaranteed to be accurate but it ** is often close. The underlying VFS might choose to preallocate database @@ -786,19 +792,33 @@ ** to the [sqlite3_file] object associated with a particular database ** connection. See the [sqlite3_file_control()] documentation for ** additional information. ** ** <li>[[SQLITE_FCNTL_SYNC_OMITTED]] -** ^(The [SQLITE_FCNTL_SYNC_OMITTED] opcode is generated internally by -** SQLite and sent to all VFSes in place of a call to the xSync method -** when the database connection has [PRAGMA synchronous] set to OFF.)^ -** Some specialized VFSes need this signal in order to operate correctly -** when [PRAGMA synchronous | PRAGMA synchronous=OFF] is set, but most -** VFSes do not need this signal and should silently ignore this opcode. -** Applications should not call [sqlite3_file_control()] with this -** opcode as doing so may disrupt the operation of the specialized VFSes -** that do require it. +** No longer in use. +** +** <li>[[SQLITE_FCNTL_SYNC]] +** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and +** sent to the VFS immediately before the xSync method is invoked on a +** database file descriptor. Or, if the xSync method is not invoked +** because the user has configured SQLite with +** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place +** of the xSync method. In most cases, the pointer argument passed with +** this file-control is NULL. However, if the database file is being synced +** as part of a multi-database commit, the argument points to a nul-terminated +** string containing the transactions master-journal file name. VFSes that +** do not need this signal should silently ignore this opcode. Applications +** should not call [sqlite3_file_control()] with this opcode as doing so may +** disrupt the operation of the specialized VFSes that do require it. +** +** <li>[[SQLITE_FCNTL_COMMIT_PHASETWO]] +** The [SQLITE_FCNTL_COMMIT_PHASETWO] opcode is generated internally by SQLite +** and sent to the VFS after a transaction has been committed immediately +** but before the database is unlocked. VFSes that do not need this signal +** should silently ignore this opcode. Applications should not call +** [sqlite3_file_control()] with this opcode as doing so may disrupt the +** operation of the specialized VFSes that do require it. ** ** <li>[[SQLITE_FCNTL_WIN32_AV_RETRY]] ** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic ** retry counts and intervals for certain disk I/O operations for the ** windows [VFS] in order to provide robustness in the presence of @@ -872,11 +892,13 @@ ** the error message if the pragma fails. ^If the ** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal ** [PRAGMA] processing continues. ^If the [SQLITE_FCNTL_PRAGMA] ** file control returns [SQLITE_OK], then the parser assumes that the ** VFS has handled the PRAGMA itself and the parser generates a no-op -** prepared statement. ^If the [SQLITE_FCNTL_PRAGMA] file control returns +** prepared statement if result string is NULL, or that returns a copy +** of the result string if the string is non-NULL. +** ^If the [SQLITE_FCNTL_PRAGMA] file control returns ** any result code other than [SQLITE_OK] or [SQLITE_NOTFOUND], that means ** that the VFS encountered an error while handling the [PRAGMA] and the ** compilation of the PRAGMA fails with an error. ^The [SQLITE_FCNTL_PRAGMA] ** file control occurs at the beginning of pragma statement analysis and so ** it is able to override built-in [PRAGMA] statements. @@ -910,16 +932,43 @@ ** pointer is overwritten with the old value. The limit is not changed if ** the value originally pointed to is negative, and so the current limit ** can be queried by passing in a pointer to a negative number. This ** file-control is used internally to implement [PRAGMA mmap_size]. ** +** <li>[[SQLITE_FCNTL_TRACE]] +** The [SQLITE_FCNTL_TRACE] file control provides advisory information +** to the VFS about what the higher layers of the SQLite stack are doing. +** This file control is used by some VFS activity tracing [shims]. +** The argument is a zero-terminated string. Higher layers in the +** SQLite stack may generate instances of this file control if +** the [SQLITE_USE_FCNTL_TRACE] compile-time option is enabled. +** +** <li>[[SQLITE_FCNTL_HAS_MOVED]] +** The [SQLITE_FCNTL_HAS_MOVED] file control interprets its argument as a +** pointer to an integer and it writes a boolean into that integer depending +** on whether or not the file has been renamed, moved, or deleted since it +** was first opened. +** +** <li>[[SQLITE_FCNTL_WIN32_SET_HANDLE]] +** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This +** opcode causes the xFileControl method to swap the file handle with the one +** pointed to by the pArg argument. This capability is used during testing +** and only needs to be supported when SQLITE_TEST is defined. +** +** <li>[[SQLITE_FCNTL_WAL_BLOCK]] +** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might +** be advantageous to block on the next WAL lock if the lock is not immediately +** available. The WAL subsystem issues this signal during rare +** circumstances in order to fix a problem with priority inversion. +** Applications should <em>not</em> use this file-control. +** ** </ul> */ #define SQLITE_FCNTL_LOCKSTATE 1 -#define SQLITE_GET_LOCKPROXYFILE 2 -#define SQLITE_SET_LOCKPROXYFILE 3 -#define SQLITE_LAST_ERRNO 4 +#define SQLITE_FCNTL_GET_LOCKPROXYFILE 2 +#define SQLITE_FCNTL_SET_LOCKPROXYFILE 3 +#define SQLITE_FCNTL_LAST_ERRNO 4 #define SQLITE_FCNTL_SIZE_HINT 5 #define SQLITE_FCNTL_CHUNK_SIZE 6 #define SQLITE_FCNTL_FILE_POINTER 7 #define SQLITE_FCNTL_SYNC_OMITTED 8 #define SQLITE_FCNTL_WIN32_AV_RETRY 9 @@ -929,10 +978,22 @@ #define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 #define SQLITE_FCNTL_PRAGMA 14 #define SQLITE_FCNTL_BUSYHANDLER 15 #define SQLITE_FCNTL_TEMPFILENAME 16 #define SQLITE_FCNTL_MMAP_SIZE 18 +#define SQLITE_FCNTL_TRACE 19 +#define SQLITE_FCNTL_HAS_MOVED 20 +#define SQLITE_FCNTL_SYNC 21 +#define SQLITE_FCNTL_COMMIT_PHASETWO 22 +#define SQLITE_FCNTL_WIN32_SET_HANDLE 23 +#define SQLITE_FCNTL_WAL_BLOCK 24 + +/* deprecated names */ +#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE +#define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE +#define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO + /* ** CAPI3REF: Mutex Handle ** ** The mutex module within SQLite defines [sqlite3_mutex] to be an @@ -1180,11 +1241,11 @@ ** <li> SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED ** <li> SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE ** </ul> ** ** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as -** was given no the corresponding lock. +** was given on the corresponding lock. ** ** The xShmLock method can transition between unlocked and SHARED or ** between unlocked and EXCLUSIVE. It cannot transition between SHARED ** and EXCLUSIVE. */ @@ -1277,14 +1338,14 @@ ** sqlite3_os_init() and sqlite3_os_end(). An application-supplied ** implementation of sqlite3_os_init() or sqlite3_os_end() ** must return [SQLITE_OK] on success and some other [error code] upon ** failure. */ -SQLITE_API int sqlite3_initialize(void); -SQLITE_API int sqlite3_shutdown(void); -SQLITE_API int sqlite3_os_init(void); -SQLITE_API int sqlite3_os_end(void); +SQLITE_API int SQLITE_STDCALL sqlite3_initialize(void); +SQLITE_API int SQLITE_STDCALL sqlite3_shutdown(void); +SQLITE_API int SQLITE_STDCALL sqlite3_os_init(void); +SQLITE_API int SQLITE_STDCALL sqlite3_os_end(void); /* ** CAPI3REF: Configuring The SQLite Library ** ** The sqlite3_config() interface is used to make global configuration @@ -1311,11 +1372,11 @@ ** ** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK]. ** ^If the option is unknown or SQLite is unable to set the option ** then this routine returns a non-zero [error code]. */ -SQLITE_API int sqlite3_config(int, ...); +SQLITE_API int SQLITE_CDECL sqlite3_config(int, ...); /* ** CAPI3REF: Configure database connections ** ** The sqlite3_db_config() interface is used to make configuration @@ -1329,11 +1390,11 @@ ** Subsequent arguments vary depending on the configuration verb. ** ** ^Calls to sqlite3_db_config() return SQLITE_OK if and only if ** the call is considered successful. */ -SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...); +SQLITE_API int SQLITE_CDECL sqlite3_db_config(sqlite3*, int op, ...); /* ** CAPI3REF: Memory Allocation Routines ** ** An instance of this object defines the interface between SQLite @@ -1373,11 +1434,11 @@ ** of 8. Some allocators round up to a larger multiple or to a power of 2. ** Every memory allocation request coming in through [sqlite3_malloc()] ** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, ** that causes the corresponding memory allocation to fail. ** -** The xInit method initializes the memory allocator. (For example, +** The xInit method initializes the memory allocator. For example, ** it might allocate any require mutexes or initialize internal data ** structures. The xShutdown method is invoked (indirectly) by ** [sqlite3_shutdown()] and should deallocate any resources acquired ** by xInit. The pAppData pointer is used as the only parameter to ** xInit and xShutdown. @@ -1463,110 +1524,126 @@ ** it is not possible to set the Serialized [threading mode] and ** [sqlite3_config()] will return [SQLITE_ERROR] if called with the ** SQLITE_CONFIG_SERIALIZED configuration option.</dd> ** ** [[SQLITE_CONFIG_MALLOC]] <dt>SQLITE_CONFIG_MALLOC</dt> -** <dd> ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mem_methods] structure. The argument specifies +** <dd> ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is +** a pointer to an instance of the [sqlite3_mem_methods] structure. +** The argument specifies ** alternative low-level memory allocation routines to be used in place of ** the memory allocation routines built into SQLite.)^ ^SQLite makes ** its own private copy of the content of the [sqlite3_mem_methods] structure ** before the [sqlite3_config()] call returns.</dd> ** ** [[SQLITE_CONFIG_GETMALLOC]] <dt>SQLITE_CONFIG_GETMALLOC</dt> -** <dd> ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mem_methods] structure. The [sqlite3_mem_methods] +** <dd> ^(The SQLITE_CONFIG_GETMALLOC option takes a single argument which +** is a pointer to an instance of the [sqlite3_mem_methods] structure. +** The [sqlite3_mem_methods] ** structure is filled with the currently defined memory allocation routines.)^ ** This option can be used to overload the default memory allocation ** routines with a wrapper that simulations memory allocation failure or ** tracks memory usage, for example. </dd> ** ** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt> -** <dd> ^This option takes single argument of type int, interpreted as a -** boolean, which enables or disables the collection of memory allocation -** statistics. ^(When memory allocation statistics are disabled, the -** following SQLite interfaces become non-operational: +** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, +** interpreted as a boolean, which enables or disables the collection of +** memory allocation statistics. ^(When memory allocation statistics are +** disabled, the following SQLite interfaces become non-operational: ** <ul> ** <li> [sqlite3_memory_used()] ** <li> [sqlite3_memory_highwater()] ** <li> [sqlite3_soft_heap_limit64()] -** <li> [sqlite3_status()] +** <li> [sqlite3_status64()] ** </ul>)^ ** ^Memory allocation statistics are enabled by default unless SQLite is ** compiled with [SQLITE_DEFAULT_MEMSTATUS]=0 in which case memory ** allocation statistics are disabled by default. ** </dd> ** ** [[SQLITE_CONFIG_SCRATCH]] <dt>SQLITE_CONFIG_SCRATCH</dt> -** <dd> ^This option specifies a static memory buffer that SQLite can use for -** scratch memory. There are three arguments: A pointer an 8-byte +** <dd> ^The SQLITE_CONFIG_SCRATCH option specifies a static memory buffer +** that SQLite can use for scratch memory. ^(There are three arguments +** to SQLITE_CONFIG_SCRATCH: A pointer an 8-byte ** aligned memory buffer from which the scratch allocations will be ** drawn, the size of each scratch allocation (sz), -** and the maximum number of scratch allocations (N). The sz -** argument must be a multiple of 16. +** and the maximum number of scratch allocations (N).)^ ** The first argument must be a pointer to an 8-byte aligned buffer ** of at least sz*N bytes of memory. -** ^SQLite will use no more than two scratch buffers per thread. So -** N should be set to twice the expected maximum number of threads. -** ^SQLite will never require a scratch buffer that is more than 6 -** times the database page size. ^If SQLite needs needs additional +** ^SQLite will not use more than one scratch buffers per thread. +** ^SQLite will never request a scratch buffer that is more than 6 +** times the database page size. +** ^If SQLite needs needs additional ** scratch memory beyond what is provided by this configuration option, then -** [sqlite3_malloc()] will be used to obtain the memory needed.</dd> +** [sqlite3_malloc()] will be used to obtain the memory needed.<p> +** ^When the application provides any amount of scratch memory using +** SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary large +** [sqlite3_malloc|heap allocations]. +** This can help [Robson proof|prevent memory allocation failures] due to heap +** fragmentation in low-memory embedded systems. +** </dd> ** ** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt> -** <dd> ^This option specifies a static memory buffer that SQLite can use for -** the database page cache with the default page cache implementation. +** <dd> ^The SQLITE_CONFIG_PAGECACHE option specifies a static memory buffer +** that SQLite can use for the database page cache with the default page +** cache implementation. ** This configuration should not be used if an application-define page -** cache implementation is loaded using the SQLITE_CONFIG_PCACHE2 option. -** There are three arguments to this option: A pointer to 8-byte aligned +** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2] +** configuration option. +** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to +** 8-byte aligned ** memory, the size of each page buffer (sz), and the number of pages (N). ** The sz argument should be the size of the largest database page -** (a power of two between 512 and 32768) plus a little extra for each -** page header. ^The page header size is 20 to 40 bytes depending on -** the host architecture. ^It is harmless, apart from the wasted memory, -** to make sz a little too large. The first -** argument should point to an allocation of at least sz*N bytes of memory. +** (a power of two between 512 and 65536) plus some extra bytes for each +** page header. ^The number of extra bytes needed by the page header +** can be determined using the [SQLITE_CONFIG_PCACHE_HDRSZ] option +** to [sqlite3_config()]. +** ^It is harmless, apart from the wasted memory, +** for the sz parameter to be larger than necessary. The first +** argument should pointer to an 8-byte aligned block of memory that +** is at least sz*N bytes of memory, otherwise subsequent behavior is +** undefined. ** ^SQLite will use the memory provided by the first argument to satisfy its ** memory needs for the first N pages that it adds to cache. ^If additional ** page cache memory is needed beyond what is provided by this option, then -** SQLite goes to [sqlite3_malloc()] for the additional storage space. -** The pointer in the first argument must -** be aligned to an 8-byte boundary or subsequent behavior of SQLite -** will be undefined.</dd> +** SQLite goes to [sqlite3_malloc()] for the additional storage space.</dd> ** ** [[SQLITE_CONFIG_HEAP]] <dt>SQLITE_CONFIG_HEAP</dt> -** <dd> ^This option specifies a static memory buffer that SQLite will use -** for all of its dynamic memory allocation needs beyond those provided -** for by [SQLITE_CONFIG_SCRATCH] and [SQLITE_CONFIG_PAGECACHE]. -** There are three arguments: An 8-byte aligned pointer to the memory, +** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer +** that SQLite will use for all of its dynamic memory allocation needs +** beyond those provided for by [SQLITE_CONFIG_SCRATCH] and +** [SQLITE_CONFIG_PAGECACHE]. +** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled +** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns +** [SQLITE_ERROR] if invoked otherwise. +** ^There are three arguments to SQLITE_CONFIG_HEAP: +** An 8-byte aligned pointer to the memory, ** the number of bytes in the memory buffer, and the minimum allocation size. ** ^If the first pointer (the memory pointer) is NULL, then SQLite reverts ** to using its default memory allocator (the system malloc() implementation), ** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. ^If the -** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or -** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory +** memory pointer is not NULL then the alternative memory ** allocator is engaged to handle all of SQLites memory allocation needs. ** The first pointer (the memory pointer) must be aligned to an 8-byte ** boundary or subsequent behavior of SQLite will be undefined. ** The minimum allocation size is capped at 2**12. Reasonable values ** for the minimum allocation size are 2**5 through 2**8.</dd> ** ** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt> -** <dd> ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mutex_methods] structure. The argument specifies -** alternative low-level mutex routines to be used in place -** the mutex routines built into SQLite.)^ ^SQLite makes a copy of the -** content of the [sqlite3_mutex_methods] structure before the call to +** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a +** pointer to an instance of the [sqlite3_mutex_methods] structure. +** The argument specifies alternative low-level mutex routines to be used +** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of +** the content of the [sqlite3_mutex_methods] structure before the call to ** [sqlite3_config()] returns. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** the entire mutexing subsystem is omitted from the build and hence calls to ** [sqlite3_config()] with the SQLITE_CONFIG_MUTEX configuration option will ** return [SQLITE_ERROR].</dd> ** ** [[SQLITE_CONFIG_GETMUTEX]] <dt>SQLITE_CONFIG_GETMUTEX</dt> -** <dd> ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mutex_methods] structure. The +** <dd> ^(The SQLITE_CONFIG_GETMUTEX option takes a single argument which +** is a pointer to an instance of the [sqlite3_mutex_methods] structure. The ** [sqlite3_mutex_methods] ** structure is filled with the currently defined mutex routines.)^ ** This option can be used to overload the default mutex allocation ** routines with a wrapper used to track mutex usage for performance ** profiling or testing, for example. ^If SQLite is compiled with @@ -1574,29 +1651,29 @@ ** the entire mutexing subsystem is omitted from the build and hence calls to ** [sqlite3_config()] with the SQLITE_CONFIG_GETMUTEX configuration option will ** return [SQLITE_ERROR].</dd> ** ** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt> -** <dd> ^(This option takes two arguments that determine the default -** memory allocation for the lookaside memory allocator on each -** [database connection]. The first argument is the +** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine +** the default size of lookaside memory on each [database connection]. +** The first argument is the ** size of each lookaside buffer slot and the second is the number of -** slots allocated to each database connection.)^ ^(This option sets the -** <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] -** verb to [sqlite3_db_config()] can be used to change the lookaside +** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE +** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] +** option to [sqlite3_db_config()] can be used to change the lookaside ** configuration on individual connections.)^ </dd> ** ** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt> -** <dd> ^(This option takes a single argument which is a pointer to -** an [sqlite3_pcache_methods2] object. This object specifies the interface -** to a custom page cache implementation.)^ ^SQLite makes a copy of the -** object and uses it for page cache memory allocations.</dd> +** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is +** a pointer to an [sqlite3_pcache_methods2] object. This object specifies +** the interface to a custom page cache implementation.)^ +** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.</dd> ** ** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt> -** <dd> ^(This option takes a single argument which is a pointer to an -** [sqlite3_pcache_methods2] object. SQLite copies of the current -** page cache implementation into that object.)^ </dd> +** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which +** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of +** the current page cache implementation into that object.)^ </dd> ** ** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt> ** <dd> The SQLITE_CONFIG_LOG option is used to configure the SQLite ** global [error log]. ** (^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a @@ -1615,31 +1692,33 @@ ** supplied by the application must not invoke any SQLite interface. ** In a multi-threaded application, the application-defined logger ** function must be threadsafe. </dd> ** ** [[SQLITE_CONFIG_URI]] <dt>SQLITE_CONFIG_URI -** <dd> This option takes a single argument of type int. If non-zero, then -** URI handling is globally enabled. If the parameter is zero, then URI handling -** is globally disabled. If URI handling is globally enabled, all filenames -** passed to [sqlite3_open()], [sqlite3_open_v2()], [sqlite3_open16()] or +** <dd>^(The SQLITE_CONFIG_URI option takes a single argument of type int. +** If non-zero, then URI handling is globally enabled. If the parameter is zero, +** then URI handling is globally disabled.)^ ^If URI handling is globally +** enabled, all filenames passed to [sqlite3_open()], [sqlite3_open_v2()], +** [sqlite3_open16()] or ** specified as part of [ATTACH] commands are interpreted as URIs, regardless ** of whether or not the [SQLITE_OPEN_URI] flag is set when the database -** connection is opened. If it is globally disabled, filenames are +** connection is opened. ^If it is globally disabled, filenames are ** only interpreted as URIs if the SQLITE_OPEN_URI flag is set when the -** database connection is opened. By default, URI handling is globally +** database connection is opened. ^(By default, URI handling is globally ** disabled. The default value may be changed by compiling with the -** [SQLITE_USE_URI] symbol defined. +** [SQLITE_USE_URI] symbol defined.)^ ** ** [[SQLITE_CONFIG_COVERING_INDEX_SCAN]] <dt>SQLITE_CONFIG_COVERING_INDEX_SCAN -** <dd> This option takes a single integer argument which is interpreted as -** a boolean in order to enable or disable the use of covering indices for -** full table scans in the query optimizer. The default setting is determined +** <dd>^The SQLITE_CONFIG_COVERING_INDEX_SCAN option takes a single integer +** argument which is interpreted as a boolean in order to enable or disable +** the use of covering indices for full table scans in the query optimizer. +** ^The default setting is determined ** by the [SQLITE_ALLOW_COVERING_INDEX_SCAN] compile-time option, or is "on" ** if that compile-time option is omitted. ** The ability to disable the use of covering indices for full table scans ** is because some incorrectly coded legacy applications might malfunction -** malfunction when the optimization is enabled. Providing the ability to +** when the optimization is enabled. Providing the ability to ** disable the optimization allows the older, buggy application code to work ** without change even with newer versions of SQLite. ** ** [[SQLITE_CONFIG_PCACHE]] [[SQLITE_CONFIG_GETPCACHE]] ** <dt>SQLITE_CONFIG_PCACHE and SQLITE_CONFIG_GETPCACHE @@ -1664,21 +1743,47 @@ ** configuration option can be seen in the "test_sqllog.c" source file in ** the canonical SQLite source tree.</dd> ** ** [[SQLITE_CONFIG_MMAP_SIZE]] ** <dt>SQLITE_CONFIG_MMAP_SIZE -** <dd>SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (sqlite3_int64) values +** <dd>^SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (sqlite3_int64) values ** that are the default mmap size limit (the default setting for ** [PRAGMA mmap_size]) and the maximum allowed mmap size limit. -** The default setting can be overridden by each database connection using +** ^The default setting can be overridden by each database connection using ** either the [PRAGMA mmap_size] command, or by using the -** [SQLITE_FCNTL_MMAP_SIZE] file control. The maximum allowed mmap size -** cannot be changed at run-time. Nor may the maximum allowed mmap size -** exceed the compile-time maximum mmap size set by the -** [SQLITE_MAX_MMAP_SIZE] compile-time option. -** If either argument to this option is negative, then that argument is +** [SQLITE_FCNTL_MMAP_SIZE] file control. ^(The maximum allowed mmap size +** will be silently truncated if necessary so that it does not exceed the +** compile-time maximum mmap size set by the +** [SQLITE_MAX_MMAP_SIZE] compile-time option.)^ +** ^If either argument to this option is negative, then that argument is ** changed to its compile-time default. +** +** [[SQLITE_CONFIG_WIN32_HEAPSIZE]] +** <dt>SQLITE_CONFIG_WIN32_HEAPSIZE +** <dd>^The SQLITE_CONFIG_WIN32_HEAPSIZE option is only available if SQLite is +** compiled for Windows with the [SQLITE_WIN32_MALLOC] pre-processor macro +** defined. ^SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value +** that specifies the maximum size of the created heap. +** +** [[SQLITE_CONFIG_PCACHE_HDRSZ]] +** <dt>SQLITE_CONFIG_PCACHE_HDRSZ +** <dd>^The SQLITE_CONFIG_PCACHE_HDRSZ option takes a single parameter which +** is a pointer to an integer and writes into that integer the number of extra +** bytes per page required for each page in [SQLITE_CONFIG_PAGECACHE]. +** The amount of extra space required can change depending on the compiler, +** target platform, and SQLite version. +** +** [[SQLITE_CONFIG_PMASZ]] +** <dt>SQLITE_CONFIG_PMASZ +** <dd>^The SQLITE_CONFIG_PMASZ option takes a single parameter which +** is an unsigned integer and sets the "Minimum PMA Size" for the multithreaded +** sorter to that integer. The default minimum PMA Size is set by the +** [SQLITE_SORTER_PMASZ] compile-time option. New threads are launched +** to help with sort operations when multithreaded sorting +** is enabled (using the [PRAGMA threads] command) and the amount of content +** to be sorted exceeds the page size times the minimum of the +** [PRAGMA cache_size] setting and this value. ** </dl> */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ #define SQLITE_CONFIG_SERIALIZED 3 /* nil */ @@ -1699,10 +1804,13 @@ #define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ #define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ #define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */ #define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ #define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ +#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */ +#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */ +#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */ /* ** CAPI3REF: Database Connection Configuration Options ** ** These constants are the available integer configuration options that @@ -1770,28 +1878,30 @@ ** ** ^The sqlite3_extended_result_codes() routine enables or disables the ** [extended result codes] feature of SQLite. ^The extended result ** codes are disabled by default for historical compatibility. */ -SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); +SQLITE_API int SQLITE_STDCALL sqlite3_extended_result_codes(sqlite3*, int onoff); /* ** CAPI3REF: Last Insert Rowid ** -** ^Each entry in an SQLite table has a unique 64-bit signed +** ^Each entry in most SQLite tables (except for [WITHOUT ROWID] tables) +** has a unique 64-bit signed ** integer key called the [ROWID | "rowid"]. ^The rowid is always available ** as an undeclared column named ROWID, OID, or _ROWID_ as long as those ** names are not also used by explicitly declared columns. ^If ** the table has a column of type [INTEGER PRIMARY KEY] then that column ** is another alias for the rowid. ** -** ^This routine returns the [rowid] of the most recent -** successful [INSERT] into the database from the [database connection] -** in the first argument. ^As of SQLite version 3.7.7, this routines -** records the last insert rowid of both ordinary tables and [virtual tables]. -** ^If no successful [INSERT]s -** have ever occurred on that database connection, zero is returned. +** ^The sqlite3_last_insert_rowid(D) interface returns the [rowid] of the +** most recent successful [INSERT] into a rowid table or [virtual table] +** on database connection D. +** ^Inserts into [WITHOUT ROWID] tables are not recorded. +** ^If no successful [INSERT]s into rowid tables +** have ever occurred on the database connection D, +** then sqlite3_last_insert_rowid(D) returns zero. ** ** ^(If an [INSERT] occurs within a trigger or within a [virtual table] ** method, then this routine will return the [rowid] of the inserted ** row as long as the trigger or virtual table method is running. ** But once the trigger or virtual table method ends, the value returned @@ -1819,91 +1929,86 @@ ** function is running and thus changes the last insert [rowid], ** then the value returned by [sqlite3_last_insert_rowid()] is ** unpredictable and might not equal either the old or the new ** last insert [rowid]. */ -SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_last_insert_rowid(sqlite3*); /* ** CAPI3REF: Count The Number Of Rows Modified ** -** ^This function returns the number of database rows that were changed -** or inserted or deleted by the most recently completed SQL statement -** on the [database connection] specified by the first parameter. -** ^(Only changes that are directly specified by the [INSERT], [UPDATE], -** or [DELETE] statement are counted. Auxiliary changes caused by -** triggers or [foreign key actions] are not counted.)^ Use the -** [sqlite3_total_changes()] function to find the total number of changes -** including changes caused by triggers and foreign key actions. -** -** ^Changes to a view that are simulated by an [INSTEAD OF trigger] -** are not counted. Only real table changes are counted. -** -** ^(A "row change" is a change to a single row of a single table -** caused by an INSERT, DELETE, or UPDATE statement. Rows that -** are changed as side effects of [REPLACE] constraint resolution, -** rollback, ABORT processing, [DROP TABLE], or by any other -** mechanisms do not count as direct row changes.)^ -** -** A "trigger context" is a scope of execution that begins and -** ends with the script of a [CREATE TRIGGER | trigger]. -** Most SQL statements are -** evaluated outside of any trigger. This is the "top level" -** trigger context. If a trigger fires from the top level, a -** new trigger context is entered for the duration of that one -** trigger. Subtriggers create subcontexts for their duration. -** -** ^Calling [sqlite3_exec()] or [sqlite3_step()] recursively does -** not create a new trigger context. -** -** ^This function returns the number of direct row changes in the -** most recent INSERT, UPDATE, or DELETE statement within the same -** trigger context. -** -** ^Thus, when called from the top level, this function returns the -** number of changes in the most recent INSERT, UPDATE, or DELETE -** that also occurred at the top level. ^(Within the body of a trigger, -** the sqlite3_changes() interface can be called to find the number of -** changes in the most recently completed INSERT, UPDATE, or DELETE -** statement within the body of the same trigger. -** However, the number returned does not include changes -** caused by subtriggers since those have their own context.)^ +** ^This function returns the number of rows modified, inserted or +** deleted by the most recently completed INSERT, UPDATE or DELETE +** statement on the database connection specified by the only parameter. +** ^Executing any other type of SQL statement does not modify the value +** returned by this function. +** +** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are +** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], +** [foreign key actions] or [REPLACE] constraint resolution are not counted. +** +** Changes to a view that are intercepted by +** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value +** returned by sqlite3_changes() immediately after an INSERT, UPDATE or +** DELETE statement run on a view is always zero. Only changes made to real +** tables are counted. +** +** Things are more complicated if the sqlite3_changes() function is +** executed while a trigger program is running. This may happen if the +** program uses the [changes() SQL function], or if some other callback +** function invokes sqlite3_changes() directly. Essentially: +** +** <ul> +** <li> ^(Before entering a trigger program the value returned by +** sqlite3_changes() function is saved. After the trigger program +** has finished, the original value is restored.)^ +** +** <li> ^(Within a trigger program each INSERT, UPDATE and DELETE +** statement sets the value returned by sqlite3_changes() +** upon completion as normal. Of course, this value will not include +** any changes performed by sub-triggers, as the sqlite3_changes() +** value will be saved and restored after each sub-trigger has run.)^ +** </ul> +** +** ^This means that if the changes() SQL function (or similar) is used +** by the first INSERT, UPDATE or DELETE statement within a trigger, it +** returns the value as set when the calling statement began executing. +** ^If it is used by the second or subsequent such statement within a trigger +** program, the value returned reflects the number of rows modified by the +** previous INSERT, UPDATE or DELETE statement within the same trigger. ** ** See also the [sqlite3_total_changes()] interface, the ** [count_changes pragma], and the [changes() SQL function]. ** ** If a separate thread makes changes on the same database connection ** while [sqlite3_changes()] is running then the value returned ** is unpredictable and not meaningful. */ -SQLITE_API int sqlite3_changes(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_changes(sqlite3*); /* ** CAPI3REF: Total Number Of Rows Modified ** -** ^This function returns the number of row changes caused by [INSERT], -** [UPDATE] or [DELETE] statements since the [database connection] was opened. -** ^(The count returned by sqlite3_total_changes() includes all changes -** from all [CREATE TRIGGER | trigger] contexts and changes made by -** [foreign key actions]. However, -** the count does not include changes used to implement [REPLACE] constraints, -** do rollbacks or ABORT processing, or [DROP TABLE] processing. The -** count does not include rows of views that fire an [INSTEAD OF trigger], -** though if the INSTEAD OF trigger makes changes of its own, those changes -** are counted.)^ -** ^The sqlite3_total_changes() function counts the changes as soon as -** the statement that makes them is completed (when the statement handle -** is passed to [sqlite3_reset()] or [sqlite3_finalize()]). -** +** ^This function returns the total number of rows inserted, modified or +** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed +** since the database connection was opened, including those executed as +** part of trigger programs. ^Executing any other type of SQL statement +** does not affect the value returned by sqlite3_total_changes(). +** +** ^Changes made as part of [foreign key actions] are included in the +** count, but those made as part of REPLACE constraint resolution are +** not. ^Changes to a view that are intercepted by INSTEAD OF triggers +** are not counted. +** ** See also the [sqlite3_changes()] interface, the ** [count_changes pragma], and the [total_changes() SQL function]. ** ** If a separate thread makes changes on the same database connection ** while [sqlite3_total_changes()] is running then the value ** returned is unpredictable and not meaningful. */ -SQLITE_API int sqlite3_total_changes(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_total_changes(sqlite3*); /* ** CAPI3REF: Interrupt A Long-Running Query ** ** ^This function causes any pending database operation to abort and @@ -1938,11 +2043,11 @@ ** that are started after the sqlite3_interrupt() call returns. ** ** If the database connection closes while [sqlite3_interrupt()] ** is running then bad things will likely happen. */ -SQLITE_API void sqlite3_interrupt(sqlite3*); +SQLITE_API void SQLITE_STDCALL sqlite3_interrupt(sqlite3*); /* ** CAPI3REF: Determine If An SQL Statement Is Complete ** ** These routines are useful during command-line input to determine if the @@ -1973,37 +2078,44 @@ ** UTF-8 string. ** ** The input to [sqlite3_complete16()] must be a zero-terminated ** UTF-16 string in native byte order. */ -SQLITE_API int sqlite3_complete(const char *sql); -SQLITE_API int sqlite3_complete16(const void *sql); +SQLITE_API int SQLITE_STDCALL sqlite3_complete(const char *sql); +SQLITE_API int SQLITE_STDCALL sqlite3_complete16(const void *sql); /* ** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors +** KEYWORDS: {busy-handler callback} {busy handler} ** -** ^This routine sets a callback function that might be invoked whenever -** an attempt is made to open a database table that another thread -** or process has locked. +** ^The sqlite3_busy_handler(D,X,P) routine sets a callback function X +** that might be invoked with argument P whenever +** an attempt is made to access a database table associated with +** [database connection] D when another thread +** or process has the table locked. +** The sqlite3_busy_handler() interface is used to implement +** [sqlite3_busy_timeout()] and [PRAGMA busy_timeout]. ** -** ^If the busy callback is NULL, then [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] +** ^If the busy callback is NULL, then [SQLITE_BUSY] ** is returned immediately upon encountering the lock. ^If the busy callback ** is not NULL, then the callback might be invoked with two arguments. ** ** ^The first argument to the busy handler is a copy of the void* pointer which ** is the third argument to sqlite3_busy_handler(). ^The second argument to ** the busy handler callback is the number of times that the busy handler has -** been invoked for this locking event. ^If the +** been invoked previously for the same locking event. ^If the ** busy callback returns 0, then no additional attempts are made to -** access the database and [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] is returned. +** access the database and [SQLITE_BUSY] is returned +** to the application. ** ^If the callback returns non-zero, then another attempt -** is made to open the database for reading and the cycle repeats. +** is made to access the database and the cycle repeats. ** ** The presence of a busy handler does not guarantee that it will be invoked ** when there is lock contention. ^If SQLite determines that invoking the busy ** handler could result in a deadlock, it will go ahead and return [SQLITE_BUSY] -** or [SQLITE_IOERR_BLOCKED] instead of invoking the busy handler. +** to the application instead of invoking the +** busy handler. ** Consider a scenario where one process is holding a read lock that ** it is trying to promote to a reserved lock and ** a second process is holding a reserved lock that it is trying ** to promote to an exclusive lock. The first process cannot proceed ** because it is blocked by the second and the second process cannot @@ -2013,58 +2125,47 @@ ** will induce the first process to release its read lock and allow ** the second process to proceed. ** ** ^The default busy callback is NULL. ** -** ^The [SQLITE_BUSY] error is converted to [SQLITE_IOERR_BLOCKED] -** when SQLite is in the middle of a large transaction where all the -** changes will not fit into the in-memory cache. SQLite will -** already hold a RESERVED lock on the database file, but it needs -** to promote this lock to EXCLUSIVE so that it can spill cache -** pages into the database file without harm to concurrent -** readers. ^If it is unable to promote the lock, then the in-memory -** cache will be left in an inconsistent state and so the error -** code is promoted from the relatively benign [SQLITE_BUSY] to -** the more severe [SQLITE_IOERR_BLOCKED]. ^This error code promotion -** forces an automatic rollback of the changes. See the -** <a href="/cvstrac/wiki?p=CorruptionFollowingBusyError"> -** CorruptionFollowingBusyError</a> wiki page for a discussion of why -** this is important. -** ** ^(There can only be a single busy handler defined for each ** [database connection]. Setting a new busy handler clears any ** previously set handler.)^ ^Note that calling [sqlite3_busy_timeout()] -** will also set or clear the busy handler. +** or evaluating [PRAGMA busy_timeout=N] will change the +** busy handler and thus clear any previously set busy handler. ** ** The busy callback should not take any actions which modify the -** database connection that invoked the busy handler. Any such actions +** database connection that invoked the busy handler. In other words, +** the busy handler is not reentrant. Any such actions ** result in undefined behavior. ** ** A busy handler must not close the database connection ** or [prepared statement] that invoked the busy handler. */ -SQLITE_API int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*); +SQLITE_API int SQLITE_STDCALL sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*); /* ** CAPI3REF: Set A Busy Timeout ** ** ^This routine sets a [sqlite3_busy_handler | busy handler] that sleeps ** for a specified amount of time when a table is locked. ^The handler ** will sleep multiple times until at least "ms" milliseconds of sleeping ** have accumulated. ^After at least "ms" milliseconds of sleeping, ** the handler returns 0 which causes [sqlite3_step()] to return -** [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED]. +** [SQLITE_BUSY]. ** ** ^Calling this routine with an argument less than or equal to zero ** turns off all busy handlers. ** ** ^(There can only be a single busy handler for a particular -** [database connection] any any given moment. If another busy handler +** [database connection] at any given moment. If another busy handler ** was defined (using [sqlite3_busy_handler()]) prior to calling ** this routine, that other busy handler is cleared.)^ +** +** See also: [PRAGMA busy_timeout] */ -SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); +SQLITE_API int SQLITE_STDCALL sqlite3_busy_timeout(sqlite3*, int ms); /* ** CAPI3REF: Convenience Routines For Running Queries ** ** This is a legacy interface that is preserved for backwards compatibility. @@ -2134,25 +2235,29 @@ ** interface defined here. As a consequence, errors that occur in the ** wrapper layer outside of the internal [sqlite3_exec()] call are not ** reflected in subsequent calls to [sqlite3_errcode()] or ** [sqlite3_errmsg()]. */ -SQLITE_API int sqlite3_get_table( +SQLITE_API int SQLITE_STDCALL sqlite3_get_table( sqlite3 *db, /* An open database */ const char *zSql, /* SQL to be evaluated */ char ***pazResult, /* Results of the query */ int *pnRow, /* Number of result rows written here */ int *pnColumn, /* Number of result columns written here */ char **pzErrmsg /* Error msg written here */ ); -SQLITE_API void sqlite3_free_table(char **result); +SQLITE_API void SQLITE_STDCALL sqlite3_free_table(char **result); /* ** CAPI3REF: Formatted String Printing Functions ** ** These routines are work-alikes of the "printf()" family of functions ** from the standard C library. +** These routines understand most of the common K&R formatting options, +** plus some additional non-standard formats, detailed below. +** Note that some of the more obscure formatting options from recent +** C-library standards are omitted from this implementation. ** ** ^The sqlite3_mprintf() and sqlite3_vmprintf() routines write their ** results into memory obtained from [sqlite3_malloc()]. ** The strings returned by these two routines should be ** released by [sqlite3_free()]. ^Both routines return a @@ -2181,11 +2286,11 @@ ** ^The sqlite3_vsnprintf() routine is a varargs version of sqlite3_snprintf(). ** ** These routines all implement some additional formatting ** options that are useful for constructing SQL statements. ** All of the usual printf() formatting options apply. In addition, there -** is are "%q", "%Q", and "%z" options. +** is are "%q", "%Q", "%w" and "%z" options. ** ** ^(The %q option works like %s in that it substitutes a nul-terminated ** string from the argument list. But %q also doubles every '\'' character. ** %q is designed for use inside a string literal.)^ By doubling each '\'' ** character it escapes that character and allows it to be inserted into @@ -2233,19 +2338,25 @@ ** sqlite3_free(zSQL); ** </pre></blockquote> ** ** The code above will render a correct SQL statement in the zSQL ** variable even if the zText variable is a NULL pointer. +** +** ^(The "%w" formatting option is like "%q" except that it expects to +** be contained within double-quotes instead of single quotes, and it +** escapes the double-quote character instead of the single-quote +** character.)^ The "%w" formatting option is intended for safely inserting +** table and column names into a constructed SQL statement. ** ** ^(The "%z" formatting option works like "%s" but with the ** addition that after the string has been read and copied into ** the result, [sqlite3_free()] is called on the input string.)^ */ -SQLITE_API char *sqlite3_mprintf(const char*,...); -SQLITE_API char *sqlite3_vmprintf(const char*, va_list); -SQLITE_API char *sqlite3_snprintf(int,char*,const char*, ...); -SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list); +SQLITE_API char *SQLITE_CDECL sqlite3_mprintf(const char*,...); +SQLITE_API char *SQLITE_STDCALL sqlite3_vmprintf(const char*, va_list); +SQLITE_API char *SQLITE_CDECL sqlite3_snprintf(int,char*,const char*, ...); +SQLITE_API char *SQLITE_STDCALL sqlite3_vsnprintf(int,char*,const char*, va_list); /* ** CAPI3REF: Memory Allocation Subsystem ** ** The SQLite core uses these three routines for all of its own @@ -2257,10 +2368,14 @@ ** of memory at least N bytes in length, where N is the parameter. ** ^If sqlite3_malloc() is unable to obtain sufficient free ** memory, it returns a NULL pointer. ^If the parameter N to ** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns ** a NULL pointer. +** +** ^The sqlite3_malloc64(N) routine works just like +** sqlite3_malloc(N) except that N is an unsigned 64-bit integer instead +** of a signed 32-bit integer. ** ** ^Calling sqlite3_free() with a pointer previously returned ** by sqlite3_malloc() or sqlite3_realloc() releases that memory so ** that it might be reused. ^The sqlite3_free() routine is ** a no-op if is called with a NULL pointer. Passing a NULL pointer @@ -2269,28 +2384,42 @@ ** memory might result in a segmentation fault or other severe error. ** Memory corruption, a segmentation fault, or other severe error ** might result if sqlite3_free() is called with a non-NULL pointer that ** was not obtained from sqlite3_malloc() or sqlite3_realloc(). ** -** ^(The sqlite3_realloc() interface attempts to resize a -** prior memory allocation to be at least N bytes, where N is the -** second parameter. The memory allocation to be resized is the first -** parameter.)^ ^ If the first parameter to sqlite3_realloc() +** ^The sqlite3_realloc(X,N) interface attempts to resize a +** prior memory allocation X to be at least N bytes. +** ^If the X parameter to sqlite3_realloc(X,N) ** is a NULL pointer then its behavior is identical to calling -** sqlite3_malloc(N) where N is the second parameter to sqlite3_realloc(). -** ^If the second parameter to sqlite3_realloc() is zero or +** sqlite3_malloc(N). +** ^If the N parameter to sqlite3_realloc(X,N) is zero or ** negative then the behavior is exactly the same as calling -** sqlite3_free(P) where P is the first parameter to sqlite3_realloc(). -** ^sqlite3_realloc() returns a pointer to a memory allocation -** of at least N bytes in size or NULL if sufficient memory is unavailable. +** sqlite3_free(X). +** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation +** of at least N bytes in size or NULL if insufficient memory is available. ** ^If M is the size of the prior allocation, then min(N,M) bytes ** of the prior allocation are copied into the beginning of buffer returned -** by sqlite3_realloc() and the prior allocation is freed. -** ^If sqlite3_realloc() returns NULL, then the prior allocation -** is not freed. +** by sqlite3_realloc(X,N) and the prior allocation is freed. +** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the +** prior allocation is not freed. ** -** ^The memory returned by sqlite3_malloc() and sqlite3_realloc() +** ^The sqlite3_realloc64(X,N) interfaces works the same as +** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead +** of a 32-bit signed integer. +** +** ^If X is a memory allocation previously obtained from sqlite3_malloc(), +** sqlite3_malloc64(), sqlite3_realloc(), or sqlite3_realloc64(), then +** sqlite3_msize(X) returns the size of that memory allocation in bytes. +** ^The value returned by sqlite3_msize(X) might be larger than the number +** of bytes requested when X was allocated. ^If X is a NULL pointer then +** sqlite3_msize(X) returns zero. If X points to something that is not +** the beginning of memory allocation, or if it points to a formerly +** valid memory allocation that has now been freed, then the behavior +** of sqlite3_msize(X) is undefined and possibly harmful. +** +** ^The memory returned by sqlite3_malloc(), sqlite3_realloc(), +** sqlite3_malloc64(), and sqlite3_realloc64() ** is always aligned to at least an 8 byte boundary, or to a ** 4 byte boundary if the [SQLITE_4_BYTE_ALIGNED_MALLOC] compile-time ** option is used. ** ** In SQLite version 3.5.0 and 3.5.1, it was possible to define @@ -2313,13 +2442,16 @@ ** ** The application must not read or write any part of ** a block of memory after it has been released using ** [sqlite3_free()] or [sqlite3_realloc()]. */ -SQLITE_API void *sqlite3_malloc(int); -SQLITE_API void *sqlite3_realloc(void*, int); -SQLITE_API void sqlite3_free(void*); +SQLITE_API void *SQLITE_STDCALL sqlite3_malloc(int); +SQLITE_API void *SQLITE_STDCALL sqlite3_malloc64(sqlite3_uint64); +SQLITE_API void *SQLITE_STDCALL sqlite3_realloc(void*, int); +SQLITE_API void *SQLITE_STDCALL sqlite3_realloc64(void*, sqlite3_uint64); +SQLITE_API void SQLITE_STDCALL sqlite3_free(void*); +SQLITE_API sqlite3_uint64 SQLITE_STDCALL sqlite3_msize(void*); /* ** CAPI3REF: Memory Allocator Statistics ** ** SQLite provides these two interfaces for reporting on the status @@ -2340,12 +2472,12 @@ ** [sqlite3_memory_used()] if and only if the parameter to ** [sqlite3_memory_highwater()] is true. ^The value returned ** by [sqlite3_memory_highwater(1)] is the high-water mark ** prior to the reset. */ -SQLITE_API sqlite3_int64 sqlite3_memory_used(void); -SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_memory_used(void); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_memory_highwater(int resetFlag); /* ** CAPI3REF: Pseudo-Random Number Generator ** ** SQLite contains a high-quality pseudo-random number generator (PRNG) used to @@ -2353,19 +2485,22 @@ ** already uses the largest possible [ROWID]. The PRNG is also used for ** the build-in random() and randomblob() SQL functions. This interface allows ** applications to access the same PRNG for other purposes. ** ** ^A call to this routine stores N bytes of randomness into buffer P. +** ^The P parameter can be a NULL pointer. ** -** ^The first time this routine is invoked (either internally or by -** the application) the PRNG is seeded using randomness obtained -** from the xRandomness method of the default [sqlite3_vfs] object. -** ^On all subsequent invocations, the pseudo-randomness is generated +** ^If this routine has not been previously called or if the previous +** call had N less than one or a NULL pointer for P, then the PRNG is +** seeded using randomness obtained from the xRandomness method of +** the default [sqlite3_vfs] object. +** ^If the previous call to this routine had an N of 1 or more and a +** non-NULL P then the pseudo-randomness is generated ** internally and without recourse to the [sqlite3_vfs] xRandomness ** method. */ -SQLITE_API void sqlite3_randomness(int N, void *P); +SQLITE_API void SQLITE_STDCALL sqlite3_randomness(int N, void *P); /* ** CAPI3REF: Compile-Time Authorization Callbacks ** ** ^This routine registers an authorizer callback with a particular @@ -2443,11 +2578,11 @@ ** [sqlite3_prepare()] or its variants. Authorization is not ** performed during statement evaluation in [sqlite3_step()], unless ** as stated in the previous paragraph, sqlite3_step() invokes ** sqlite3_prepare_v2() to reprepare a statement after a schema change. */ -SQLITE_API int sqlite3_set_authorizer( +SQLITE_API int SQLITE_STDCALL sqlite3_set_authorizer( sqlite3*, int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), void *pUserData ); @@ -2458,12 +2593,12 @@ ** return either [SQLITE_OK] or one of these two constants in order ** to signal SQLite whether or not the action is permitted. See the ** [sqlite3_set_authorizer | authorizer documentation] for additional ** information. ** -** Note that SQLITE_IGNORE is also used as a [SQLITE_ROLLBACK | return code] -** from the [sqlite3_vtab_on_conflict()] interface. +** Note that SQLITE_IGNORE is also used as a [conflict resolution mode] +** returned from the [sqlite3_vtab_on_conflict()] interface. */ #define SQLITE_DENY 1 /* Abort the SQL statement with an error */ #define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */ /* @@ -2517,10 +2652,11 @@ #define SQLITE_CREATE_VTABLE 29 /* Table Name Module Name */ #define SQLITE_DROP_VTABLE 30 /* Table Name Module Name */ #define SQLITE_FUNCTION 31 /* NULL Function Name */ #define SQLITE_SAVEPOINT 32 /* Operation Savepoint Name */ #define SQLITE_COPY 0 /* No longer used */ +#define SQLITE_RECURSIVE 33 /* NULL NULL */ /* ** CAPI3REF: Tracing And Profiling Functions ** ** These routines register callback functions that can be used for @@ -2546,12 +2682,12 @@ ** digits in the time are meaningless. Future versions of SQLite ** might provide greater resolution on the profiler callback. The ** sqlite3_profile() function is considered experimental and is ** subject to change in future versions of SQLite. */ -SQLITE_API void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*); -SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_profile(sqlite3*, +SQLITE_API void *SQLITE_STDCALL sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*); +SQLITE_API SQLITE_EXPERIMENTAL void *SQLITE_STDCALL sqlite3_profile(sqlite3*, void(*xProfile)(void*,const char*,sqlite3_uint64), void*); /* ** CAPI3REF: Query Progress Callbacks ** @@ -2581,11 +2717,11 @@ ** the database connection that invoked the progress handler. ** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their ** database connections for the meaning of "modify" in this paragraph. ** */ -SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); +SQLITE_API void SQLITE_STDCALL sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); /* ** CAPI3REF: Opening A New Database Connection ** ** ^These routines open an SQLite database file as specified by the @@ -2599,13 +2735,13 @@ ** [SQLITE_OK] is returned. Otherwise an [error code] is returned.)^ ^The ** [sqlite3_errmsg()] or [sqlite3_errmsg16()] routines can be used to obtain ** an English language description of the error following a failure of any ** of the sqlite3_open() routines. ** -** ^The default encoding for the database will be UTF-8 if -** sqlite3_open() or sqlite3_open_v2() is called and -** UTF-16 in the native byte order if sqlite3_open16() is used. +** ^The default encoding will be UTF-8 for databases created using +** sqlite3_open() or sqlite3_open_v2(). ^The default encoding for databases +** created using sqlite3_open16() will be UTF-16 in the native byte order. ** ** Whether or not an error occurs when it is opened, resources ** associated with the [database connection] handle should be released by ** passing it to [sqlite3_close()] when it is no longer required. ** @@ -2689,17 +2825,18 @@ ** ^SQLite uses the path component of the URI as the name of the disk file ** which contains the database. ^If the path begins with a '/' character, ** then it is interpreted as an absolute path. ^If the path does not begin ** with a '/' (meaning that the authority section is omitted from the URI) ** then the path is interpreted as a relative path. -** ^On windows, the first component of an absolute path -** is a drive specification (e.g. "C:"). +** ^(On windows, the first component of an absolute path +** is a drive specification (e.g. "C:").)^ ** ** [[core URI query parameters]] ** The query component of a URI may contain parameters that are interpreted ** either by SQLite itself, or by a [VFS | custom VFS implementation]. -** SQLite interprets the following three query parameters: +** SQLite and its built-in [VFSes] interpret the +** following query parameters: ** ** <ul> ** <li> <b>vfs</b>: ^The "vfs" parameter may be used to specify the name of ** a VFS object that provides the operating system interface that should ** be used to access the database file on disk. ^If this option is set to @@ -2729,10 +2866,32 @@ ** sqlite3_open_v2(). ^Setting the cache parameter to "private" is ** equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit. ** ^If sqlite3_open_v2() is used and the "cache" parameter is present in ** a URI filename, its value overrides any behavior requested by setting ** SQLITE_OPEN_PRIVATECACHE or SQLITE_OPEN_SHAREDCACHE flag. +** +** <li> <b>psow</b>: ^The psow parameter indicates whether or not the +** [powersafe overwrite] property does or does not apply to the +** storage media on which the database file resides. +** +** <li> <b>nolock</b>: ^The nolock parameter is a boolean query parameter +** which if set disables file locking in rollback journal modes. This +** is useful for accessing a database on a filesystem that does not +** support locking. Caution: Database corruption might result if two +** or more processes write to the same database and any one of those +** processes uses nolock=1. +** +** <li> <b>immutable</b>: ^The immutable parameter is a boolean query +** parameter that indicates that the database file is stored on +** read-only media. ^When immutable is set, SQLite assumes that the +** database file cannot be changed, even by a process with higher +** privilege, and so the database is opened read-only and all locking +** and change detection is disabled. Caution: Setting the immutable +** property on a database file that does in fact change can result +** in incorrect query results and/or [SQLITE_CORRUPT] errors. +** See also: [SQLITE_IOCAP_IMMUTABLE]. +** ** </ul> ** ** ^Specifying an unknown parameter in the query component of a URI is not an ** error. Future versions of SQLite might understand additional query ** parameters. See "[query parameters with special meaning to SQLite]" for @@ -2758,12 +2917,13 @@ ** in URI filenames. ** <tr><td> file:data.db?mode=ro&cache=private <td> ** Open file "data.db" in the current directory for read-only access. ** Regardless of whether or not shared-cache mode is enabled by ** default, use a private cache. -** <tr><td> file:/home/fred/data.db?vfs=unix-nolock <td> -** Open file "/home/fred/data.db". Use the special VFS "unix-nolock". +** <tr><td> file:/home/fred/data.db?vfs=unix-dotfile <td> +** Open file "/home/fred/data.db". Use the special VFS "unix-dotfile" +** that uses dot-files in place of posix advisory locking. ** <tr><td> file:data.db?mode=readonly <td> ** An error. "readonly" is not a valid option for the "mode" parameter. ** </table> ** ** ^URI hexadecimal escape sequences (%HH) are supported within the path and @@ -2785,19 +2945,19 @@ ** prior to calling sqlite3_open() or sqlite3_open_v2(). Otherwise, various ** features that require the use of temporary files may fail. ** ** See also: [sqlite3_temp_directory] */ -SQLITE_API int sqlite3_open( +SQLITE_API int SQLITE_STDCALL sqlite3_open( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb /* OUT: SQLite db handle */ ); -SQLITE_API int sqlite3_open16( +SQLITE_API int SQLITE_STDCALL sqlite3_open16( const void *filename, /* Database filename (UTF-16) */ sqlite3 **ppDb /* OUT: SQLite db handle */ ); -SQLITE_API int sqlite3_open_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_open_v2( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb, /* OUT: SQLite db handle */ int flags, /* Flags */ const char *zVfs /* Name of VFS module to use */ ); @@ -2839,23 +2999,25 @@ ** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and ** is not a database file pathname pointer that SQLite passed into the xOpen ** VFS method, then the behavior of this routine is undefined and probably ** undesirable. */ -SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam); -SQLITE_API int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault); -SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64); +SQLITE_API const char *SQLITE_STDCALL sqlite3_uri_parameter(const char *zFilename, const char *zParam); +SQLITE_API int SQLITE_STDCALL sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_uri_int64(const char*, const char*, sqlite3_int64); /* ** CAPI3REF: Error Codes And Messages ** -** ^The sqlite3_errcode() interface returns the numeric [result code] or -** [extended result code] for the most recent failed sqlite3_* API call -** associated with a [database connection]. If a prior API call failed -** but the most recent API call succeeded, the return value from -** sqlite3_errcode() is undefined. ^The sqlite3_extended_errcode() +** ^If the most recent sqlite3_* API call associated with +** [database connection] D failed, then the sqlite3_errcode(D) interface +** returns the numeric [result code] or [extended result code] for that +** API call. +** If the most recent API call was successful, +** then the return value from sqlite3_errcode() is undefined. +** ^The sqlite3_extended_errcode() ** interface is the same except that it always returns the ** [extended result code] even when extended result codes are ** disabled. ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language @@ -2882,15 +3044,15 @@ ** ** If an interface fails with SQLITE_MISUSE, that means the interface ** was invoked incorrectly by the application. In that case, the ** error code and message may or may not be set. */ -SQLITE_API int sqlite3_errcode(sqlite3 *db); -SQLITE_API int sqlite3_extended_errcode(sqlite3 *db); -SQLITE_API const char *sqlite3_errmsg(sqlite3*); -SQLITE_API const void *sqlite3_errmsg16(sqlite3*); -SQLITE_API const char *sqlite3_errstr(int); +SQLITE_API int SQLITE_STDCALL sqlite3_errcode(sqlite3 *db); +SQLITE_API int SQLITE_STDCALL sqlite3_extended_errcode(sqlite3 *db); +SQLITE_API const char *SQLITE_STDCALL sqlite3_errmsg(sqlite3*); +SQLITE_API const void *SQLITE_STDCALL sqlite3_errmsg16(sqlite3*); +SQLITE_API const char *SQLITE_STDCALL sqlite3_errstr(int); /* ** CAPI3REF: SQL Statement Object ** KEYWORDS: {prepared statement} {prepared statements} ** @@ -2953,11 +3115,11 @@ ** created by an untrusted script can be contained using the ** [max_page_count] [PRAGMA]. ** ** New run-time limit categories may be added in future releases. */ -SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); +SQLITE_API int SQLITE_STDCALL sqlite3_limit(sqlite3*, int id, int newVal); /* ** CAPI3REF: Run-Time Limit Categories ** KEYWORDS: {limit category} {*limit categories} ** @@ -3005,10 +3167,14 @@ ** ^(<dt>SQLITE_LIMIT_VARIABLE_NUMBER</dt> ** <dd>The maximum index number of any [parameter] in an SQL statement.)^ ** ** [[SQLITE_LIMIT_TRIGGER_DEPTH]] ^(<dt>SQLITE_LIMIT_TRIGGER_DEPTH</dt> ** <dd>The maximum depth of recursion for triggers.</dd>)^ +** +** [[SQLITE_LIMIT_WORKER_THREADS]] ^(<dt>SQLITE_LIMIT_WORKER_THREADS</dt> +** <dd>The maximum number of auxiliary worker threads that a single +** [prepared statement] may start.</dd>)^ ** </dl> */ #define SQLITE_LIMIT_LENGTH 0 #define SQLITE_LIMIT_SQL_LENGTH 1 #define SQLITE_LIMIT_COLUMN 2 @@ -3018,10 +3184,11 @@ #define SQLITE_LIMIT_FUNCTION_ARG 6 #define SQLITE_LIMIT_ATTACHED 7 #define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 #define SQLITE_LIMIT_VARIABLE_NUMBER 9 #define SQLITE_LIMIT_TRIGGER_DEPTH 10 +#define SQLITE_LIMIT_WORKER_THREADS 11 /* ** CAPI3REF: Compiling An SQL Statement ** KEYWORDS: {SQL statement compiler} ** @@ -3035,20 +3202,18 @@ ** The second argument, "zSql", is the statement to be compiled, encoded ** as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2() ** interfaces use UTF-8, and sqlite3_prepare16() and sqlite3_prepare16_v2() ** use UTF-16. ** -** ^If the nByte argument is less than zero, then zSql is read up to the -** first zero terminator. ^If nByte is non-negative, then it is the maximum -** number of bytes read from zSql. ^When nByte is non-negative, the -** zSql string ends at either the first '\000' or '\u0000' character or -** the nByte-th byte, whichever comes first. If the caller knows -** that the supplied string is nul-terminated, then there is a small -** performance advantage to be gained by passing an nByte parameter that -** is equal to the number of bytes in the input string <i>including</i> -** the nul-terminator bytes as this saves SQLite from having to -** make a copy of the input string. +** ^If the nByte argument is negative, then zSql is read up to the +** first zero terminator. ^If nByte is positive, then it is the +** number of bytes read from zSql. ^If nByte is zero, then no prepared +** statement is generated. +** If the caller knows that the supplied string is nul-terminated, then +** there is a small performance advantage to passing an nByte parameter that +** is the number of bytes in the input string <i>including</i> +** the nul-terminator. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte ** past the end of the first SQL statement in zSql. These routines only ** compile the first statement in zSql, so *pzTail is left pointing to ** what remains uncompiled. @@ -3097,36 +3262,35 @@ ** to the [sqlite3_bind_text | bindings] of that [parameter]. ** ^The specific value of WHERE-clause [parameter] might influence the ** choice of query plan if the parameter is the left-hand side of a [LIKE] ** or [GLOB] operator or if the parameter is compared to an indexed column ** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. -** the ** </li> ** </ol> */ -SQLITE_API int sqlite3_prepare( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ ); -SQLITE_API int sqlite3_prepare_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare_v2( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ ); -SQLITE_API int sqlite3_prepare16( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare16( sqlite3 *db, /* Database handle */ const void *zSql, /* SQL statement, UTF-16 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const void **pzTail /* OUT: Pointer to unused portion of zSql */ ); -SQLITE_API int sqlite3_prepare16_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_prepare16_v2( sqlite3 *db, /* Database handle */ const void *zSql, /* SQL statement, UTF-16 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const void **pzTail /* OUT: Pointer to unused portion of zSql */ @@ -3137,11 +3301,11 @@ ** ** ^This interface can be used to retrieve a saved copy of the original ** SQL text used to create a [prepared statement] if that statement was ** compiled using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()]. */ -SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); +SQLITE_API const char *SQLITE_STDCALL sqlite3_sql(sqlite3_stmt *pStmt); /* ** CAPI3REF: Determine If An SQL Statement Writes The Database ** ** ^The sqlite3_stmt_readonly(X) interface returns true (non-zero) if @@ -3168,11 +3332,11 @@ ** database. ^The [ATTACH] and [DETACH] statements also cause ** sqlite3_stmt_readonly() to return true since, while those statements ** change the configuration of a database connection, they do not make ** changes to the content of the database files on disk. */ -SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); +SQLITE_API int SQLITE_STDCALL sqlite3_stmt_readonly(sqlite3_stmt *pStmt); /* ** CAPI3REF: Determine If A Prepared Statement Has Been Reset ** ** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the @@ -3187,11 +3351,11 @@ ** to locate all prepared statements associated with a database ** connection that are in need of being reset. This can be used, ** for example, in diagnostic routines to search for prepared ** statements that are holding a transaction open. */ -SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); +SQLITE_API int SQLITE_STDCALL sqlite3_stmt_busy(sqlite3_stmt*); /* ** CAPI3REF: Dynamically Typed Value Object ** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value} ** @@ -3292,28 +3456,36 @@ ** is negative, then the length of the string is ** the number of bytes up to the first zero terminator. ** If the fourth parameter to sqlite3_bind_blob() is negative, then ** the behavior is undefined. ** If a non-negative fourth parameter is provided to sqlite3_bind_text() -** or sqlite3_bind_text16() then that parameter must be the byte offset +** or sqlite3_bind_text16() or sqlite3_bind_text64() then +** that parameter must be the byte offset ** where the NUL terminator would occur assuming the string were NUL ** terminated. If any NUL characters occur at byte offsets less than ** the value of the fourth parameter then the resulting string value will ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. ** -** ^The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and -** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or +** ^The fifth argument to the BLOB and string binding interfaces +** is a destructor used to dispose of the BLOB or ** string after SQLite has finished with it. ^The destructor is called -** to dispose of the BLOB or string even if the call to sqlite3_bind_blob(), -** sqlite3_bind_text(), or sqlite3_bind_text16() fails. +** to dispose of the BLOB or string even if the call to bind API fails. ** ^If the fifth argument is ** the special value [SQLITE_STATIC], then SQLite assumes that the ** information is in static, unmanaged space and does not need to be freed. ** ^If the fifth argument has the value [SQLITE_TRANSIENT], then ** SQLite makes its own private copy of the data immediately, before ** the sqlite3_bind_*() routine returns. +** +** ^The sixth argument to sqlite3_bind_text64() must be one of +** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] +** to specify the encoding of the text in the third parameter. If +** the sixth argument to sqlite3_bind_text64() is not one of the +** allowed values shown above, or if the text encoding is different +** from the encoding specified by the sixth parameter, then the behavior +** is undefined. ** ** ^The sqlite3_bind_zeroblob() routine binds a BLOB of length N that ** is filled with zeroes. ^A zeroblob uses a fixed amount of memory ** (just an integer to hold its size) while it is being processed. ** Zeroblobs are intended to serve as placeholders for BLOBs whose @@ -3331,25 +3503,32 @@ ** ^Bindings are not cleared by the [sqlite3_reset()] routine. ** ^Unbound parameters are interpreted as NULL. ** ** ^The sqlite3_bind_* routines return [SQLITE_OK] on success or an ** [error code] if anything goes wrong. +** ^[SQLITE_TOOBIG] might be returned if the size of a string or BLOB +** exceeds limits imposed by [sqlite3_limit]([SQLITE_LIMIT_LENGTH]) or +** [SQLITE_MAX_LENGTH]. ** ^[SQLITE_RANGE] is returned if the parameter ** index is out of range. ^[SQLITE_NOMEM] is returned if malloc() fails. ** ** See also: [sqlite3_bind_parameter_count()], ** [sqlite3_bind_parameter_name()], and [sqlite3_bind_parameter_index()]. */ -SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); -SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double); -SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int); -SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); -SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int); -SQLITE_API int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); -SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); -SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); -SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64, + void(*)(void*)); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_double(sqlite3_stmt*, int, double); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_int(sqlite3_stmt*, int, int); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_null(sqlite3_stmt*, int); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*)); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64, + void(*)(void*), unsigned char encoding); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); /* ** CAPI3REF: Number Of SQL Parameters ** ** ^This routine can be used to find the number of [SQL parameters] @@ -3365,11 +3544,11 @@ ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_name()], and ** [sqlite3_bind_parameter_index()]. */ -SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_parameter_count(sqlite3_stmt*); /* ** CAPI3REF: Name Of A Host Parameter ** ** ^The sqlite3_bind_parameter_name(P,N) interface returns @@ -3392,11 +3571,11 @@ ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_count()], and ** [sqlite3_bind_parameter_index()]. */ -SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int); +SQLITE_API const char *SQLITE_STDCALL sqlite3_bind_parameter_name(sqlite3_stmt*, int); /* ** CAPI3REF: Index Of A Parameter With A Given Name ** ** ^Return the index of an SQL parameter given its name. ^The @@ -3408,20 +3587,20 @@ ** ** See also: [sqlite3_bind_blob|sqlite3_bind()], ** [sqlite3_bind_parameter_count()], and ** [sqlite3_bind_parameter_index()]. */ -SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); +SQLITE_API int SQLITE_STDCALL sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); /* ** CAPI3REF: Reset All Bindings On A Prepared Statement ** ** ^Contrary to the intuition of many, [sqlite3_reset()] does not reset ** the [sqlite3_bind_blob | bindings] on a [prepared statement]. ** ^Use this routine to reset all host parameters to NULL. */ -SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); +SQLITE_API int SQLITE_STDCALL sqlite3_clear_bindings(sqlite3_stmt*); /* ** CAPI3REF: Number Of Columns In A Result Set ** ** ^Return the number of columns in the result set returned by the @@ -3428,11 +3607,11 @@ ** [prepared statement]. ^This routine returns 0 if pStmt is an SQL ** statement that does not return data (for example an [UPDATE]). ** ** See also: [sqlite3_data_count()] */ -SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt); +SQLITE_API int SQLITE_STDCALL sqlite3_column_count(sqlite3_stmt *pStmt); /* ** CAPI3REF: Column Names In A Result Set ** ** ^These routines return the name assigned to a particular column @@ -3456,12 +3635,12 @@ ** ^The name of a result column is the value of the "AS" clause for ** that column, if there is an AS clause. If there is no AS clause ** then the name of the column is unspecified and may change from ** one release of SQLite to the next. */ -SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N); -SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_name(sqlite3_stmt*, int N); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_name16(sqlite3_stmt*, int N); /* ** CAPI3REF: Source Of Data In A Query Result ** ** ^These routines provide a means to determine the database, table, and @@ -3504,16 +3683,16 @@ ** If two or more threads call one or more ** [sqlite3_column_database_name | column metadata interfaces] ** for the same [prepared statement] and result column ** at the same time then the results are undefined. */ -SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int); -SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int); -SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt*,int); -SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt*,int); -SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt*,int); -SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int); +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_database_name(sqlite3_stmt*,int); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_database_name16(sqlite3_stmt*,int); +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_table_name(sqlite3_stmt*,int); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_table_name16(sqlite3_stmt*,int); +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_origin_name(sqlite3_stmt*,int); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_origin_name16(sqlite3_stmt*,int); /* ** CAPI3REF: Declared Datatype Of A Query Result ** ** ^(The first parameter is a [prepared statement]. @@ -3540,12 +3719,12 @@ ** data stored in that column is of the declared type. SQLite is ** strongly typed, but the typing is dynamic not static. ^Type ** is associated with individual values, not with the containers ** used to hold those values. */ -SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt*,int); -SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); +SQLITE_API const char *SQLITE_STDCALL sqlite3_column_decltype(sqlite3_stmt*,int); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_decltype16(sqlite3_stmt*,int); /* ** CAPI3REF: Evaluate An SQL Statement ** ** After a [prepared statement] has been prepared using either @@ -3620,11 +3799,11 @@ ** using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead ** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces, ** then the more specific [error codes] are returned directly ** by sqlite3_step(). The use of the "v2" interface is recommended. */ -SQLITE_API int sqlite3_step(sqlite3_stmt*); +SQLITE_API int SQLITE_STDCALL sqlite3_step(sqlite3_stmt*); /* ** CAPI3REF: Number of columns in a result set ** ** ^The sqlite3_data_count(P) interface returns the number of columns in the @@ -3640,11 +3819,11 @@ ** where it always returns zero since each step of that multi-step ** pragma returns 0 columns of data. ** ** See also: [sqlite3_column_count()] */ -SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); +SQLITE_API int SQLITE_STDCALL sqlite3_data_count(sqlite3_stmt *pStmt); /* ** CAPI3REF: Fundamental Datatypes ** KEYWORDS: SQLITE_TEXT ** @@ -3759,23 +3938,23 @@ ** <table border="1"> ** <tr><th> Internal<br>Type <th> Requested<br>Type <th> Conversion ** ** <tr><td> NULL <td> INTEGER <td> Result is 0 ** <tr><td> NULL <td> FLOAT <td> Result is 0.0 -** <tr><td> NULL <td> TEXT <td> Result is NULL pointer -** <tr><td> NULL <td> BLOB <td> Result is NULL pointer +** <tr><td> NULL <td> TEXT <td> Result is a NULL pointer +** <tr><td> NULL <td> BLOB <td> Result is a NULL pointer ** <tr><td> INTEGER <td> FLOAT <td> Convert from integer to float ** <tr><td> INTEGER <td> TEXT <td> ASCII rendering of the integer ** <tr><td> INTEGER <td> BLOB <td> Same as INTEGER->TEXT -** <tr><td> FLOAT <td> INTEGER <td> Convert from float to integer +** <tr><td> FLOAT <td> INTEGER <td> [CAST] to INTEGER ** <tr><td> FLOAT <td> TEXT <td> ASCII rendering of the float -** <tr><td> FLOAT <td> BLOB <td> Same as FLOAT->TEXT -** <tr><td> TEXT <td> INTEGER <td> Use atoi() -** <tr><td> TEXT <td> FLOAT <td> Use atof() +** <tr><td> FLOAT <td> BLOB <td> [CAST] to BLOB +** <tr><td> TEXT <td> INTEGER <td> [CAST] to INTEGER +** <tr><td> TEXT <td> FLOAT <td> [CAST] to REAL ** <tr><td> TEXT <td> BLOB <td> No change -** <tr><td> BLOB <td> INTEGER <td> Convert to TEXT then use atoi() -** <tr><td> BLOB <td> FLOAT <td> Convert to TEXT then use atof() +** <tr><td> BLOB <td> INTEGER <td> [CAST] to INTEGER +** <tr><td> BLOB <td> FLOAT <td> [CAST] to REAL ** <tr><td> BLOB <td> TEXT <td> Add a zero terminator if needed ** </table> ** </blockquote>)^ ** ** The table above makes reference to standard C library functions atoi() @@ -3827,29 +4006,29 @@ ** ** ^The pointers returned are valid until a type conversion occurs as ** described above, or until [sqlite3_step()] or [sqlite3_reset()] or ** [sqlite3_finalize()] is called. ^The memory space used to hold strings ** and BLOBs is freed automatically. Do <b>not</b> pass the pointers returned -** [sqlite3_column_blob()], [sqlite3_column_text()], etc. into +** from [sqlite3_column_blob()], [sqlite3_column_text()], etc. into ** [sqlite3_free()]. ** ** ^(If a memory allocation error occurs during the evaluation of any ** of these routines, a default value is returned. The default value ** is either the integer 0, the floating point number 0.0, or a NULL ** pointer. Subsequent calls to [sqlite3_errcode()] will return ** [SQLITE_NOMEM].)^ */ -SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); -SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); -SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); -SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); -SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); -SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); -SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_blob(sqlite3_stmt*, int iCol); +SQLITE_API int SQLITE_STDCALL sqlite3_column_bytes(sqlite3_stmt*, int iCol); +SQLITE_API int SQLITE_STDCALL sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +SQLITE_API double SQLITE_STDCALL sqlite3_column_double(sqlite3_stmt*, int iCol); +SQLITE_API int SQLITE_STDCALL sqlite3_column_int(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_column_int64(sqlite3_stmt*, int iCol); +SQLITE_API const unsigned char *SQLITE_STDCALL sqlite3_column_text(sqlite3_stmt*, int iCol); +SQLITE_API const void *SQLITE_STDCALL sqlite3_column_text16(sqlite3_stmt*, int iCol); +SQLITE_API int SQLITE_STDCALL sqlite3_column_type(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_value *SQLITE_STDCALL sqlite3_column_value(sqlite3_stmt*, int iCol); /* ** CAPI3REF: Destroy A Prepared Statement Object ** ** ^The sqlite3_finalize() function is called to delete a [prepared statement]. @@ -3872,11 +4051,11 @@ ** resource leaks. It is a grievous error for the application to try to use ** a prepared statement after it has been finalized. Any use of a prepared ** statement after it has been finalized can result in undefined and ** undesirable behavior such as segfaults and heap corruption. */ -SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); +SQLITE_API int SQLITE_STDCALL sqlite3_finalize(sqlite3_stmt *pStmt); /* ** CAPI3REF: Reset A Prepared Statement Object ** ** The sqlite3_reset() function is called to reset a [prepared statement] @@ -3898,11 +4077,11 @@ ** [sqlite3_reset(S)] returns an appropriate [error code]. ** ** ^The [sqlite3_reset(S)] interface does not change the values ** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. */ -SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); +SQLITE_API int SQLITE_STDCALL sqlite3_reset(sqlite3_stmt *pStmt); /* ** CAPI3REF: Create Or Redefine SQL Functions ** KEYWORDS: {function creation routines} ** KEYWORDS: {application-defined SQL function} @@ -3936,19 +4115,28 @@ ** parameter is less than -1 or greater than 127 then the behavior is ** undefined. ** ** ^The fourth parameter, eTextRep, specifies what ** [SQLITE_UTF8 | text encoding] this SQL function prefers for -** its parameters. Every SQL function implementation must be able to work -** with UTF-8, UTF-16le, or UTF-16be. But some implementations may be -** more efficient with one encoding than another. ^An application may -** invoke sqlite3_create_function() or sqlite3_create_function16() multiple -** times with the same function but with different values of eTextRep. +** its parameters. The application should set this parameter to +** [SQLITE_UTF16LE] if the function implementation invokes +** [sqlite3_value_text16le()] on an input, or [SQLITE_UTF16BE] if the +** implementation invokes [sqlite3_value_text16be()] on an input, or +** [SQLITE_UTF16] if [sqlite3_value_text16()] is used, or [SQLITE_UTF8] +** otherwise. ^The same SQL function may be registered multiple times using +** different preferred text encodings, with different implementations for +** each encoding. ** ^When multiple implementations of the same function are available, SQLite ** will pick the one that involves the least amount of data conversion. -** If there is only a single implementation which does not care what text -** encoding is used, then the fourth argument should be [SQLITE_ANY]. +** +** ^The fourth parameter may optionally be ORed with [SQLITE_DETERMINISTIC] +** to signal that the function will always return the same result given +** the same inputs within a single SQL statement. Most SQL functions are +** deterministic. The built-in [random()] SQL function is an example of a +** function that is not deterministic. The SQLite query planner is able to +** perform additional optimizations on deterministic functions, so use +** of the [SQLITE_DETERMINISTIC] flag is recommended where possible. ** ** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** function can gain access to this pointer using [sqlite3_user_data()].)^ ** ** ^The sixth, seventh and eighth parameters, xFunc, xStep and xFinal, are @@ -3988,31 +4176,31 @@ ** ^An application-defined function is permitted to call other ** SQLite interfaces. However, such calls must not ** close the database connection nor finalize or reset the prepared ** statement in which the function is running. */ -SQLITE_API int sqlite3_create_function( +SQLITE_API int SQLITE_STDCALL sqlite3_create_function( sqlite3 *db, const char *zFunctionName, int nArg, int eTextRep, void *pApp, void (*xFunc)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ); -SQLITE_API int sqlite3_create_function16( +SQLITE_API int SQLITE_STDCALL sqlite3_create_function16( sqlite3 *db, const void *zFunctionName, int nArg, int eTextRep, void *pApp, void (*xFunc)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ); -SQLITE_API int sqlite3_create_function_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_create_function_v2( sqlite3 *db, const char *zFunctionName, int nArg, int eTextRep, void *pApp, @@ -4026,34 +4214,44 @@ ** CAPI3REF: Text Encodings ** ** These constant define integer codes that represent the various ** text encodings supported by SQLite. */ -#define SQLITE_UTF8 1 -#define SQLITE_UTF16LE 2 -#define SQLITE_UTF16BE 3 +#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ +#define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ +#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */ #define SQLITE_UTF16 4 /* Use native byte order */ -#define SQLITE_ANY 5 /* sqlite3_create_function only */ +#define SQLITE_ANY 5 /* Deprecated */ #define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ +/* +** CAPI3REF: Function Flags +** +** These constants may be ORed together with the +** [SQLITE_UTF8 | preferred text encoding] as the fourth argument +** to [sqlite3_create_function()], [sqlite3_create_function16()], or +** [sqlite3_create_function_v2()]. +*/ +#define SQLITE_DETERMINISTIC 0x800 + /* ** CAPI3REF: Deprecated Functions ** DEPRECATED ** ** These functions are [deprecated]. In order to maintain ** backwards compatibility with older code, these functions continue ** to be supported. However, new applications should avoid -** the use of these functions. To help encourage people to avoid -** using these functions, we are not going to tell you what they do. +** the use of these functions. To encourage programmers to avoid +** these functions, we will not explain what they do. */ #ifndef SQLITE_OMIT_DEPRECATED -SQLITE_API SQLITE_DEPRECATED int sqlite3_aggregate_count(sqlite3_context*); -SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*); -SQLITE_API SQLITE_DEPRECATED int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); -SQLITE_API SQLITE_DEPRECATED int sqlite3_global_recover(void); -SQLITE_API SQLITE_DEPRECATED void sqlite3_thread_cleanup(void); -SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), +SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_aggregate_count(sqlite3_context*); +SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_expired(sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_global_recover(void); +SQLITE_API SQLITE_DEPRECATED void SQLITE_STDCALL sqlite3_thread_cleanup(void); +SQLITE_API SQLITE_DEPRECATED int SQLITE_STDCALL sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), void*,sqlite3_int64); #endif /* ** CAPI3REF: Obtaining SQL Function Parameter Values @@ -4073,11 +4271,11 @@ ** These routines work only with [protected sqlite3_value] objects. ** Any attempt to use these routines on an [unprotected sqlite3_value] ** object results in undefined behavior. ** ** ^These routines work just like the corresponding [column access functions] -** except that these routines take a single [protected sqlite3_value] object +** except that these routines take a single [protected sqlite3_value] object ** pointer instead of a [sqlite3_stmt*] pointer and an integer column number. ** ** ^The sqlite3_value_text16() interface extracts a UTF-16 string ** in the native byte-order of the host machine. ^The ** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces @@ -4098,22 +4296,22 @@ ** or [sqlite3_value_text16()]. ** ** These routines must be called from the same thread as ** the SQL function that supplied the [sqlite3_value*] parameters. */ -SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); -SQLITE_API int sqlite3_value_bytes(sqlite3_value*); -SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); -SQLITE_API double sqlite3_value_double(sqlite3_value*); -SQLITE_API int sqlite3_value_int(sqlite3_value*); -SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); -SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*); -SQLITE_API const void *sqlite3_value_text16(sqlite3_value*); -SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*); -SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*); -SQLITE_API int sqlite3_value_type(sqlite3_value*); -SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_blob(sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_value_bytes(sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_value_bytes16(sqlite3_value*); +SQLITE_API double SQLITE_STDCALL sqlite3_value_double(sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_value_int(sqlite3_value*); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_value_int64(sqlite3_value*); +SQLITE_API const unsigned char *SQLITE_STDCALL sqlite3_value_text(sqlite3_value*); +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16(sqlite3_value*); +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16le(sqlite3_value*); +SQLITE_API const void *SQLITE_STDCALL sqlite3_value_text16be(sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_value_type(sqlite3_value*); +SQLITE_API int SQLITE_STDCALL sqlite3_value_numeric_type(sqlite3_value*); /* ** CAPI3REF: Obtain Aggregate Function Context ** ** Implementations of aggregate SQL functions use this @@ -4153,11 +4351,11 @@ ** function. ** ** This routine must be called from the same thread in which ** the aggregate SQL function is running. */ -SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes); +SQLITE_API void *SQLITE_STDCALL sqlite3_aggregate_context(sqlite3_context*, int nBytes); /* ** CAPI3REF: User Data For Functions ** ** ^The sqlite3_user_data() interface returns a copy of @@ -4167,11 +4365,11 @@ ** registered the application defined function. ** ** This routine must be called from the same thread in which ** the application-defined function is running. */ -SQLITE_API void *sqlite3_user_data(sqlite3_context*); +SQLITE_API void *SQLITE_STDCALL sqlite3_user_data(sqlite3_context*); /* ** CAPI3REF: Database Connection For Functions ** ** ^The sqlite3_context_db_handle() interface returns a copy of @@ -4178,11 +4376,11 @@ ** the pointer to the [database connection] (the 1st parameter) ** of the [sqlite3_create_function()] ** and [sqlite3_create_function16()] routines that originally ** registered the application defined function. */ -SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); +SQLITE_API sqlite3 *SQLITE_STDCALL sqlite3_context_db_handle(sqlite3_context*); /* ** CAPI3REF: Function Auxiliary Data ** ** These functions may be used by (non-aggregate) SQL functions to @@ -4230,12 +4428,12 @@ ** values and [parameters] and expressions composed from the same.)^ ** ** These routines must be called from the same thread in which ** the SQL function is running. */ -SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N); -SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); +SQLITE_API void *SQLITE_STDCALL sqlite3_get_auxdata(sqlite3_context*, int N); +SQLITE_API void SQLITE_STDCALL sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); /* ** CAPI3REF: Constants Defining Special Destructor Behavior ** @@ -4320,10 +4518,14 @@ ** ^The sqlite3_result_text(), sqlite3_result_text16(), ** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces ** set the return value of the application-defined function to be ** a text string which is represented as UTF-8, UTF-16 native byte order, ** UTF-16 little endian, or UTF-16 big endian, respectively. +** ^The sqlite3_result_text64() interface sets the return value of an +** application-defined function to be a text string in an encoding +** specified by the fifth (and last) parameter, which must be one +** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. ** ^SQLite takes the text result from the application from ** the 2nd parameter of the sqlite3_result_text* interfaces. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is negative, then SQLite takes result text from the 2nd parameter ** through the first zero character. @@ -4362,26 +4564,30 @@ ** ** If these routines are called from within the different thread ** than the one containing the application-defined function that received ** the [sqlite3_context] pointer, the results are undefined. */ -SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); -SQLITE_API void sqlite3_result_double(sqlite3_context*, double); -SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); -SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); -SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*); -SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*); -SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int); -SQLITE_API void sqlite3_result_int(sqlite3_context*, int); -SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); -SQLITE_API void sqlite3_result_null(sqlite3_context*); -SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); -SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); -SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); -SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); -SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); -SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); +SQLITE_API void SQLITE_STDCALL sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_blob64(sqlite3_context*,const void*, + sqlite3_uint64,void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_double(sqlite3_context*, double); +SQLITE_API void SQLITE_STDCALL sqlite3_result_error(sqlite3_context*, const char*, int); +SQLITE_API void SQLITE_STDCALL sqlite3_result_error16(sqlite3_context*, const void*, int); +SQLITE_API void SQLITE_STDCALL sqlite3_result_error_toobig(sqlite3_context*); +SQLITE_API void SQLITE_STDCALL sqlite3_result_error_nomem(sqlite3_context*); +SQLITE_API void SQLITE_STDCALL sqlite3_result_error_code(sqlite3_context*, int); +SQLITE_API void SQLITE_STDCALL sqlite3_result_int(sqlite3_context*, int); +SQLITE_API void SQLITE_STDCALL sqlite3_result_int64(sqlite3_context*, sqlite3_int64); +SQLITE_API void SQLITE_STDCALL sqlite3_result_null(sqlite3_context*); +SQLITE_API void SQLITE_STDCALL sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64, + void(*)(void*), unsigned char encoding); +SQLITE_API void SQLITE_STDCALL sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void SQLITE_STDCALL sqlite3_result_value(sqlite3_context*, sqlite3_value*); +SQLITE_API void SQLITE_STDCALL sqlite3_result_zeroblob(sqlite3_context*, int n); /* ** CAPI3REF: Define New Collating Sequences ** ** ^These functions add, remove, or modify a [collation] associated @@ -4458,26 +4664,26 @@ ** is unfortunate but cannot be changed without breaking backwards ** compatibility. ** ** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()]. */ -SQLITE_API int sqlite3_create_collation( +SQLITE_API int SQLITE_STDCALL sqlite3_create_collation( sqlite3*, const char *zName, int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); -SQLITE_API int sqlite3_create_collation_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_create_collation_v2( sqlite3*, const char *zName, int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*), void(*xDestroy)(void*) ); -SQLITE_API int sqlite3_create_collation16( +SQLITE_API int SQLITE_STDCALL sqlite3_create_collation16( sqlite3*, const void *zName, int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) @@ -4507,16 +4713,16 @@ ** ** The callback function should register the desired collation using ** [sqlite3_create_collation()], [sqlite3_create_collation16()], or ** [sqlite3_create_collation_v2()]. */ -SQLITE_API int sqlite3_collation_needed( +SQLITE_API int SQLITE_STDCALL sqlite3_collation_needed( sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const char*) ); -SQLITE_API int sqlite3_collation_needed16( +SQLITE_API int SQLITE_STDCALL sqlite3_collation_needed16( sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const void*) ); @@ -4526,15 +4732,15 @@ ** called right after sqlite3_open(). ** ** The code to implement this API is not available in the public release ** of SQLite. */ -SQLITE_API int sqlite3_key( +SQLITE_API int SQLITE_STDCALL sqlite3_key( sqlite3 *db, /* Database to be rekeyed */ const void *pKey, int nKey /* The key */ ); -SQLITE_API int sqlite3_key_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_key_v2( sqlite3 *db, /* Database to be rekeyed */ const char *zDbName, /* Name of the database */ const void *pKey, int nKey /* The key */ ); @@ -4544,35 +4750,35 @@ ** database is decrypted. ** ** The code to implement this API is not available in the public release ** of SQLite. */ -SQLITE_API int sqlite3_rekey( +SQLITE_API int SQLITE_STDCALL sqlite3_rekey( sqlite3 *db, /* Database to be rekeyed */ const void *pKey, int nKey /* The new key */ ); -SQLITE_API int sqlite3_rekey_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_rekey_v2( sqlite3 *db, /* Database to be rekeyed */ const char *zDbName, /* Name of the database */ const void *pKey, int nKey /* The new key */ ); /* ** Specify the activation key for a SEE database. Unless ** activated, none of the SEE routines will work. */ -SQLITE_API void sqlite3_activate_see( +SQLITE_API void SQLITE_STDCALL sqlite3_activate_see( const char *zPassPhrase /* Activation phrase */ ); #endif #ifdef SQLITE_ENABLE_CEROD /* ** Specify the activation key for a CEROD database. Unless ** activated, none of the CEROD routines will work. */ -SQLITE_API void sqlite3_activate_cerod( +SQLITE_API void SQLITE_STDCALL sqlite3_activate_cerod( const char *zPassPhrase /* Activation phrase */ ); #endif /* @@ -4590,11 +4796,11 @@ ** method of the default [sqlite3_vfs] object. If the xSleep() method ** of the default VFS is not implemented correctly, or not implemented at ** all, then the behavior of sqlite3_sleep() may deviate from the description ** in the previous paragraphs. */ -SQLITE_API int sqlite3_sleep(int); +SQLITE_API int SQLITE_STDCALL sqlite3_sleep(int); /* ** CAPI3REF: Name Of The Folder Holding Temporary Files ** ** ^(If this global variable is made to point to a string which is @@ -4601,10 +4807,17 @@ ** the name of a folder (a.k.a. directory), then all temporary files ** created by SQLite when using a built-in [sqlite3_vfs | VFS] ** will be placed in that directory.)^ ^If this variable ** is a NULL pointer, then SQLite performs a search for an appropriate ** temporary file directory. +** +** Applications are strongly discouraged from using this global variable. +** It is required to set a temporary folder on Windows Runtime (WinRT). +** But for all other platforms, it is highly recommended that applications +** neither read nor write this variable. This global variable is a relic +** that exists for backwards compatibility of legacy applications and should +** be avoided in new projects. ** ** It is not safe to read or modify this variable in more than one ** thread at a time. It is not safe to read or modify this variable ** if a [database connection] is being used at the same time in a separate ** thread. @@ -4620,10 +4833,15 @@ ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be ** made NULL or made to point to memory obtained from [sqlite3_malloc] ** or else the use of the [temp_store_directory pragma] should be avoided. +** Except when requested by the [temp_store_directory pragma], SQLite +** does not free the memory that sqlite3_temp_directory points to. If +** the application wants that memory to be freed, it must do +** so itself, taking care to only do so after all [database connection] +** objects have been destroyed. ** ** <b>Note to Windows Runtime users:</b> The temporary directory must be set ** prior to calling [sqlite3_open] or [sqlite3_open_v2]. Otherwise, various ** features that require the use of temporary files may fail. Here is an ** example of how to do this using C++ with the Windows Runtime: @@ -4696,11 +4914,11 @@ ** ** If another thread changes the autocommit status of the database ** connection while this routine is running, then the return value ** is undefined. */ -SQLITE_API int sqlite3_get_autocommit(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_get_autocommit(sqlite3*); /* ** CAPI3REF: Find The Database Handle Of A Prepared Statement ** ** ^The sqlite3_db_handle interface returns the [database connection] handle @@ -4708,11 +4926,11 @@ ** returned by sqlite3_db_handle is the same [database connection] ** that was the first argument ** to the [sqlite3_prepare_v2()] call (or its variants) that was used to ** create the statement in the first place. */ -SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); +SQLITE_API sqlite3 *SQLITE_STDCALL sqlite3_db_handle(sqlite3_stmt*); /* ** CAPI3REF: Return The Filename For A Database Connection ** ** ^The sqlite3_db_filename(D,N) interface returns a pointer to a filename @@ -4724,20 +4942,20 @@ ** ^The filename returned by this function is the output of the ** xFullPathname method of the [VFS]. ^In other words, the filename ** will be an absolute pathname, even if the filename used ** to open the database originally was a URI or relative pathname. */ -SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName); +SQLITE_API const char *SQLITE_STDCALL sqlite3_db_filename(sqlite3 *db, const char *zDbName); /* ** CAPI3REF: Determine if a database is read-only ** ** ^The sqlite3_db_readonly(D,N) interface returns 1 if the database N ** of connection D is read-only, 0 if it is read/write, or -1 if N is not ** the name of a database on connection D. */ -SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName); +SQLITE_API int SQLITE_STDCALL sqlite3_db_readonly(sqlite3 *db, const char *zDbName); /* ** CAPI3REF: Find the next prepared statement ** ** ^This interface returns a pointer to the next [prepared statement] after @@ -4748,11 +4966,11 @@ ** ** The [database connection] pointer D in a call to ** [sqlite3_next_stmt(D,S)] must refer to an open database ** connection and in particular must not be a NULL pointer. */ -SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); +SQLITE_API sqlite3_stmt *SQLITE_STDCALL sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); /* ** CAPI3REF: Commit And Rollback Notification Callbacks ** ** ^The sqlite3_commit_hook() interface registers a callback @@ -4796,24 +5014,25 @@ ** ^The rollback callback is not invoked if a transaction is ** automatically rolled back because the database connection is closed. ** ** See also the [sqlite3_update_hook()] interface. */ -SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); -SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); +SQLITE_API void *SQLITE_STDCALL sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); +SQLITE_API void *SQLITE_STDCALL sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); /* ** CAPI3REF: Data Change Notification Callbacks ** ** ^The sqlite3_update_hook() interface registers a callback function ** with the [database connection] identified by the first argument -** to be invoked whenever a row is updated, inserted or deleted. +** to be invoked whenever a row is updated, inserted or deleted in +** a rowid table. ** ^Any callback set by a previous call to this function ** for the same database connection is overridden. ** ** ^The second argument is a pointer to the function to invoke when a -** row is updated, inserted or deleted. +** row is updated, inserted or deleted in a rowid table. ** ^The first argument to the callback is a copy of the third argument ** to sqlite3_update_hook(). ** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], ** or [SQLITE_UPDATE], depending on the operation that caused the callback ** to be invoked. @@ -4822,10 +5041,11 @@ ** ^The final callback parameter is the [rowid] of the row. ** ^In the case of an update, this is the [rowid] after the update takes place. ** ** ^(The update hook is not invoked when internal system tables are ** modified (i.e. sqlite_master and sqlite_sequence).)^ +** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified. ** ** ^In the current implementation, the update hook ** is not invoked when duplication rows are deleted because of an ** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook ** invoked when rows are deleted using the [truncate optimization]. @@ -4845,11 +5065,11 @@ ** the first call on D. ** ** See also the [sqlite3_commit_hook()] and [sqlite3_rollback_hook()] ** interfaces. */ -SQLITE_API void *sqlite3_update_hook( +SQLITE_API void *SQLITE_STDCALL sqlite3_update_hook( sqlite3*, void(*)(void *,int ,char const *,char const *,sqlite3_int64), void* ); @@ -4874,17 +5094,22 @@ ** successfully. An [error code] is returned otherwise.)^ ** ** ^Shared cache is disabled by default. But this might change in ** future releases of SQLite. Applications that care about shared ** cache setting should set it explicitly. +** +** Note: This method is disabled on MacOS X 10.7 and iOS version 5.0 +** and will always return SQLITE_MISUSE. On those systems, +** shared cache mode should be enabled per-database connection via +** [sqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE]. ** ** This interface is threadsafe on processors where writing a ** 32-bit integer is atomic. ** ** See Also: [SQLite Shared-Cache Mode] */ -SQLITE_API int sqlite3_enable_shared_cache(int); +SQLITE_API int SQLITE_STDCALL sqlite3_enable_shared_cache(int); /* ** CAPI3REF: Attempt To Free Heap Memory ** ** ^The sqlite3_release_memory() interface attempts to free N bytes @@ -4896,24 +5121,24 @@ ** ^The sqlite3_release_memory() routine is a no-op returning zero ** if SQLite is not compiled with [SQLITE_ENABLE_MEMORY_MANAGEMENT]. ** ** See also: [sqlite3_db_release_memory()] */ -SQLITE_API int sqlite3_release_memory(int); +SQLITE_API int SQLITE_STDCALL sqlite3_release_memory(int); /* ** CAPI3REF: Free Memory Used By A Database Connection ** ** ^The sqlite3_db_release_memory(D) interface attempts to free as much heap ** memory as possible from database connection D. Unlike the -** [sqlite3_release_memory()] interface, this interface is effect even -** when then [SQLITE_ENABLE_MEMORY_MANAGEMENT] compile-time option is +** [sqlite3_release_memory()] interface, this interface is in effect even +** when the [SQLITE_ENABLE_MEMORY_MANAGEMENT] compile-time option is ** omitted. ** ** See also: [sqlite3_release_memory()] */ -SQLITE_API int sqlite3_db_release_memory(sqlite3*); +SQLITE_API int SQLITE_STDCALL sqlite3_db_release_memory(sqlite3*); /* ** CAPI3REF: Impose A Limit On Heap Size ** ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the @@ -4961,11 +5186,11 @@ ** the use of [SQLITE_ENABLE_MEMORY_MANAGEMENT]. ** ** The circumstances under which SQLite will enforce the soft heap limit may ** changes in future releases of SQLite. */ -SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); +SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_soft_heap_limit64(sqlite3_int64 N); /* ** CAPI3REF: Deprecated Soft Heap Limit Interface ** DEPRECATED ** @@ -4972,30 +5197,37 @@ ** This is a deprecated version of the [sqlite3_soft_heap_limit64()] ** interface. This routine is provided for historical compatibility ** only. All new applications should use the ** [sqlite3_soft_heap_limit64()] interface rather than this one. */ -SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); +SQLITE_API SQLITE_DEPRECATED void SQLITE_STDCALL sqlite3_soft_heap_limit(int N); /* ** CAPI3REF: Extract Metadata About A Column Of A Table ** -** ^This routine returns metadata about a specific column of a specific -** database table accessible using the [database connection] handle -** passed as the first function argument. +** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns +** information about column C of table T in database D +** on [database connection] X.)^ ^The sqlite3_table_column_metadata() +** interface returns SQLITE_OK and fills in the non-NULL pointers in +** the final five arguments with appropriate values if the specified +** column exists. ^The sqlite3_table_column_metadata() interface returns +** SQLITE_ERROR and if the specified column does not exist. +** ^If the column-name parameter to sqlite3_table_column_metadata() is a +** NULL pointer, then this routine simply checks for the existance of the +** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it +** does not. ** ** ^The column is identified by the second, third and fourth parameters to -** this function. ^The second parameter is either the name of the database +** this function. ^(The second parameter is either the name of the database ** (i.e. "main", "temp", or an attached database) containing the specified -** table or NULL. ^If it is NULL, then all attached databases are searched +** table or NULL.)^ ^If it is NULL, then all attached databases are searched ** for the table using the same algorithm used by the database engine to ** resolve unqualified table references. ** ** ^The third and fourth parameters to this function are the table and column -** name of the desired column, respectively. Neither of these parameters -** may be NULL. +** name of the desired column, respectively. ** ** ^Metadata is returned by writing to the memory locations passed as the 5th ** and subsequent parameters to this function. ^Any of these arguments may be ** NULL, in which case the corresponding element of metadata is omitted. ** @@ -5010,38 +5242,35 @@ ** <tr><td> 9th <td> int <td> True if column is [AUTOINCREMENT] ** </table> ** </blockquote>)^ ** ** ^The memory pointed to by the character pointers returned for the -** declaration type and collation sequence is valid only until the next +** declaration type and collation sequence is valid until the next ** call to any SQLite API function. ** ** ^If the specified table is actually a view, an [error code] is returned. ** -** ^If the specified column is "rowid", "oid" or "_rowid_" and an +** ^If the specified column is "rowid", "oid" or "_rowid_" and the table +** is not a [WITHOUT ROWID] table and an ** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output ** parameters are set for the explicitly declared column. ^(If there is no -** explicitly declared [INTEGER PRIMARY KEY] column, then the output -** parameters are set as follows: +** [INTEGER PRIMARY KEY] column, then the outputs +** for the [rowid] are set as follows: ** ** <pre> ** data type: "INTEGER" ** collation sequence: "BINARY" ** not null: 0 ** primary key: 1 ** auto increment: 0 ** </pre>)^ ** -** ^(This function may load one or more schemas from database files. If an -** error occurs during this process, or if the requested table or column -** cannot be found, an [error code] is returned and an error message left -** in the [database connection] (to be retrieved using sqlite3_errmsg()).)^ -** -** ^This API is only available if the library was compiled with the -** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined. +** ^This function causes all database schemas to be read from disk and +** parsed, if that has not already been done, and returns an error if +** any errors are encountered while loading the schema. */ -SQLITE_API int sqlite3_table_column_metadata( +SQLITE_API int SQLITE_STDCALL sqlite3_table_column_metadata( sqlite3 *db, /* Connection handle */ const char *zDbName, /* Database name or NULL */ const char *zTableName, /* Table name */ const char *zColumnName, /* Column name */ char const **pzDataType, /* OUTPUT: Declared data type */ @@ -5083,11 +5312,11 @@ ** [sqlite3_enable_load_extension()] prior to calling this API, ** otherwise an error will be returned. ** ** See also the [load_extension() SQL function]. */ -SQLITE_API int sqlite3_load_extension( +SQLITE_API int SQLITE_STDCALL sqlite3_load_extension( sqlite3 *db, /* Load the extension into this database connection */ const char *zFile, /* Name of the shared library containing extension */ const char *zProc, /* Entry point. Derived from zFile if 0 */ char **pzErrMsg /* Put error message here if not 0 */ ); @@ -5103,11 +5332,11 @@ ** ^Extension loading is off by default. ** ^Call the sqlite3_enable_load_extension() routine with onoff==1 ** to turn extension loading on and call it with onoff==0 to turn ** it back off again. */ -SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); +SQLITE_API int SQLITE_STDCALL sqlite3_enable_load_extension(sqlite3 *db, int onoff); /* ** CAPI3REF: Automatically Load Statically Linked Extensions ** ** ^This interface causes the xEntryPoint() function to be invoked for @@ -5141,11 +5370,11 @@ ** will be called more than once for each database connection that is opened. ** ** See also: [sqlite3_reset_auto_extension()] ** and [sqlite3_cancel_auto_extension()] */ -SQLITE_API int sqlite3_auto_extension(void (*xEntryPoint)(void)); +SQLITE_API int SQLITE_STDCALL sqlite3_auto_extension(void (*xEntryPoint)(void)); /* ** CAPI3REF: Cancel Automatic Extension Loading ** ** ^The [sqlite3_cancel_auto_extension(X)] interface unregisters the @@ -5153,19 +5382,19 @@ ** [sqlite3_auto_extension(X)]. ^The [sqlite3_cancel_auto_extension(X)] ** routine returns 1 if initialization routine X was successfully ** unregistered and it returns 0 if X was not on the list of initialization ** routines. */ -SQLITE_API int sqlite3_cancel_auto_extension(void (*xEntryPoint)(void)); +SQLITE_API int SQLITE_STDCALL sqlite3_cancel_auto_extension(void (*xEntryPoint)(void)); /* ** CAPI3REF: Reset Automatic Extension Loading ** ** ^This interface disables all automatic extensions previously ** registered using [sqlite3_auto_extension()]. */ -SQLITE_API void sqlite3_reset_auto_extension(void); +SQLITE_API void SQLITE_STDCALL sqlite3_reset_auto_extension(void); /* ** The interface to the virtual-table mechanism is currently considered ** to be experimental. The interface might change in incompatible ways. ** If this is a problem for you, do not use the interface at this time. @@ -5279,14 +5508,26 @@ ** ** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in ** the correct order to satisfy the ORDER BY clause so that no separate ** sorting step is required. ** -** ^The estimatedCost value is an estimate of the cost of doing the -** particular lookup. A full scan of a table with N entries should have -** a cost of N. A binary search of a table of N entries should have a -** cost of approximately log(N). +** ^The estimatedCost value is an estimate of the cost of a particular +** strategy. A cost of N indicates that the cost of the strategy is similar +** to a linear scan of an SQLite table with N rows. A cost of log(N) +** indicates that the expense of the operation is similar to that of a +** binary search on a unique indexed field of an SQLite table with N rows. +** +** ^The estimatedRows value is an estimate of the number of rows that +** will be returned by the strategy. +** +** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info +** structure for SQLite version 3.8.2. If a virtual table extension is +** used with an SQLite version earlier than 3.8.2, the results of attempting +** to read or write the estimatedRows field are undefined (but are likely +** to included crashing the application). The estimatedRows field should +** therefore only be used if [sqlite3_libversion_number()] returns a +** value greater than or equal to 3008002. */ struct sqlite3_index_info { /* Inputs */ int nConstraint; /* Number of entries in aConstraint */ struct sqlite3_index_constraint { @@ -5307,11 +5548,13 @@ } *aConstraintUsage; int idxNum; /* Number used to identify the index */ char *idxStr; /* String, possibly obtained from sqlite3_malloc */ int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ int orderByConsumed; /* True if output is already ordered */ - double estimatedCost; /* Estimated cost of using this index */ + double estimatedCost; /* Estimated cost of using this index */ + /* Fields below are only available in SQLite 3.8.2 and later */ + sqlite3_int64 estimatedRows; /* Estimated number of rows returned */ }; /* ** CAPI3REF: Virtual Table Constraint Operator Codes ** @@ -5350,17 +5593,17 @@ ** be invoked if the call to sqlite3_create_module_v2() fails. ** ^The sqlite3_create_module() ** interface is equivalent to sqlite3_create_module_v2() with a NULL ** destructor. */ -SQLITE_API int sqlite3_create_module( +SQLITE_API int SQLITE_STDCALL sqlite3_create_module( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *p, /* Methods for the module */ void *pClientData /* Client data for xCreate/xConnect */ ); -SQLITE_API int sqlite3_create_module_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_create_module_v2( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *p, /* Methods for the module */ void *pClientData, /* Client data for xCreate/xConnect */ void(*xDestroy)(void*) /* Module destructor function */ @@ -5384,11 +5627,11 @@ ** is delivered up to the client application, the string will be automatically ** freed by sqlite3_free() and the zErrMsg field will be zeroed. */ struct sqlite3_vtab { const sqlite3_module *pModule; /* The module for this virtual table */ - int nRef; /* NO LONGER USED */ + int nRef; /* Number of open cursors */ char *zErrMsg; /* Error message from sqlite3_mprintf() */ /* Virtual table implementations will typically add additional fields */ }; /* @@ -5419,11 +5662,11 @@ ** ^The [xCreate] and [xConnect] methods of a ** [virtual table module] call this interface ** to declare the format (the names and datatypes of the columns) of ** the virtual tables they implement. */ -SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL); +SQLITE_API int SQLITE_STDCALL sqlite3_declare_vtab(sqlite3*, const char *zSQL); /* ** CAPI3REF: Overload A Function For A Virtual Table ** ** ^(Virtual tables can provide alternative implementations of functions @@ -5437,11 +5680,11 @@ ** of the new function always causes an exception to be thrown. So ** the new function is not good for anything by itself. Its only ** purpose is to be a placeholder function that can be overloaded ** by a [virtual table]. */ -SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); +SQLITE_API int SQLITE_STDCALL sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); /* ** The interface to the virtual-table mechanism defined above (back up ** to a comment remarkably similar to this one) is currently considered ** to be experimental. The interface might change in incompatible ways. @@ -5473,31 +5716,47 @@ ** in other words, the same BLOB that would be selected by: ** ** <pre> ** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow; ** </pre>)^ +** +** ^(Parameter zDb is not the filename that contains the database, but +** rather the symbolic name of the database. For attached databases, this is +** the name that appears after the AS keyword in the [ATTACH] statement. +** For the main database file, the database name is "main". For TEMP +** tables, the database name is "temp".)^ ** ** ^If the flags parameter is non-zero, then the BLOB is opened for read -** and write access. ^If it is zero, the BLOB is opened for read access. -** ^It is not possible to open a column that is part of an index or primary -** key for writing. ^If [foreign key constraints] are enabled, it is -** not possible to open a column that is part of a [child key] for writing. -** -** ^Note that the database name is not the filename that contains -** the database but rather the symbolic name of the database that -** appears after the AS keyword when the database is connected using [ATTACH]. -** ^For the main database file, the database name is "main". -** ^For TEMP tables, the database name is "temp". -** -** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is written -** to *ppBlob. Otherwise an [error code] is returned and *ppBlob is set -** to be a null pointer.)^ -** ^This function sets the [database connection] error code and message -** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()] and related -** functions. ^Note that the *ppBlob variable is always initialized in a -** way that makes it safe to invoke [sqlite3_blob_close()] on *ppBlob -** regardless of the success or failure of this routine. +** and write access. ^If the flags parameter is zero, the BLOB is opened for +** read-only access. +** +** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored +** in *ppBlob. Otherwise an [error code] is returned and, unless the error +** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided +** the API is not misused, it is always safe to call [sqlite3_blob_close()] +** on *ppBlob after this function it returns. +** +** This function fails with SQLITE_ERROR if any of the following are true: +** <ul> +** <li> ^(Database zDb does not exist)^, +** <li> ^(Table zTable does not exist within database zDb)^, +** <li> ^(Table zTable is a WITHOUT ROWID table)^, +** <li> ^(Column zColumn does not exist)^, +** <li> ^(Row iRow is not present in the table)^, +** <li> ^(The specified column of row iRow contains a value that is not +** a TEXT or BLOB value)^, +** <li> ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE +** constraint and the blob is being opened for read/write access)^, +** <li> ^([foreign key constraints | Foreign key constraints] are enabled, +** column zColumn is part of a [child key] definition and the blob is +** being opened for read/write access)^. +** </ul> +** +** ^Unless it returns SQLITE_MISUSE, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** ** ** ^(If the row that a BLOB handle points to is modified by an ** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects ** then the BLOB handle is marked as "expired". ** This is true if any column of the row is changed, even a column @@ -5512,18 +5771,17 @@ ** the opened blob. ^The size of a blob may not be changed by this ** interface. Use the [UPDATE] SQL command to change the size of a ** blob. ** ** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces -** and the built-in [zeroblob] SQL function can be used, if desired, -** to create an empty, zero-filled blob in which to read or write using -** this interface. +** and the built-in [zeroblob] SQL function may be used to create a +** zero-filled blob to read or write using the incremental-blob interface. ** ** To avoid a resource leak, every open [BLOB handle] should eventually ** be released by a call to [sqlite3_blob_close()]. */ -SQLITE_API int sqlite3_blob_open( +SQLITE_API int SQLITE_STDCALL sqlite3_blob_open( sqlite3*, const char *zDb, const char *zTable, const char *zColumn, sqlite3_int64 iRow, @@ -5551,35 +5809,33 @@ ** SQLITE_ABORT. ^Calling [sqlite3_blob_bytes()] on an aborted blob handle ** always returns zero. ** ** ^This function sets the database handle error code and message. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); /* ** CAPI3REF: Close A BLOB Handle ** -** ^Closes an open [BLOB handle]. -** -** ^Closing a BLOB shall cause the current transaction to commit -** if there are no other BLOBs, no pending prepared statements, and the -** database connection is in [autocommit mode]. -** ^If any writes were made to the BLOB, they might be held in cache -** until the close operation if they will fit. -** -** ^(Closing the BLOB often forces the changes -** out to disk and so if any I/O errors occur, they will likely occur -** at the time when the BLOB is closed. Any errors that occur during -** closing are reported as a non-zero return value.)^ -** -** ^(The BLOB is closed unconditionally. Even if this routine returns -** an error code, the BLOB is still closed.)^ -** -** ^Calling this routine with a null pointer (such as would be returned -** by a failed call to [sqlite3_blob_open()]) is a harmless no-op. +** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed +** unconditionally. Even if this routine returns an error code, the +** handle is still closed.)^ +** +** ^If the blob handle being closed was opened for read-write access, and if +** the database is in auto-commit mode and there are no other open read-write +** blob handles or active write statements, the current transaction is +** committed. ^If an error occurs while committing the transaction, an error +** code is returned and the transaction rolled back. +** +** Calling this function with an argument that is not a NULL pointer or an +** open blob handle results in undefined behaviour. ^Calling this routine +** with a null pointer (such as would be returned by a failed call to +** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function +** is passed a valid open blob handle, the values returned by the +** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning. */ -SQLITE_API int sqlite3_blob_close(sqlite3_blob *); +SQLITE_API int SQLITE_STDCALL sqlite3_blob_close(sqlite3_blob *); /* ** CAPI3REF: Return The Size Of An Open BLOB ** ** ^Returns the size in bytes of the BLOB accessible via the @@ -5590,11 +5846,11 @@ ** This routine only works on a [BLOB handle] which has been created ** by a prior successful call to [sqlite3_blob_open()] and which has not ** been closed by [sqlite3_blob_close()]. Passing any other pointer in ** to this routine results in undefined and probably undesirable behavior. */ -SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *); +SQLITE_API int SQLITE_STDCALL sqlite3_blob_bytes(sqlite3_blob *); /* ** CAPI3REF: Read Data From A BLOB Incrementally ** ** ^(This function is used to read data from an open [BLOB handle] into a @@ -5618,49 +5874,52 @@ ** been closed by [sqlite3_blob_close()]. Passing any other pointer in ** to this routine results in undefined and probably undesirable behavior. ** ** See also: [sqlite3_blob_write()]. */ -SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); +SQLITE_API int SQLITE_STDCALL sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); /* ** CAPI3REF: Write Data Into A BLOB Incrementally ** -** ^This function is used to write data into an open [BLOB handle] from a -** caller-supplied buffer. ^N bytes of data are copied from the buffer Z -** into the open BLOB, starting at offset iOffset. +** ^(This function is used to write data into an open [BLOB handle] from a +** caller-supplied buffer. N bytes of data are copied from the buffer Z +** into the open BLOB, starting at offset iOffset.)^ +** +** ^(On success, sqlite3_blob_write() returns SQLITE_OK. +** Otherwise, an [error code] or an [extended error code] is returned.)^ +** ^Unless SQLITE_MISUSE is returned, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** ^If the [BLOB handle] passed as the first argument was not opened for ** writing (the flags parameter to [sqlite3_blob_open()] was zero), ** this function returns [SQLITE_READONLY]. ** -** ^This function may only modify the contents of the BLOB; it is +** This function may only modify the contents of the BLOB; it is ** not possible to increase the size of a BLOB using this API. ** ^If offset iOffset is less than N bytes from the end of the BLOB, -** [SQLITE_ERROR] is returned and no data is written. ^If N is -** less than zero [SQLITE_ERROR] is returned and no data is written. -** The size of the BLOB (and hence the maximum value of N+iOffset) -** can be determined using the [sqlite3_blob_bytes()] interface. +** [SQLITE_ERROR] is returned and no data is written. The size of the +** BLOB (and hence the maximum value of N+iOffset) can be determined +** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less +** than zero [SQLITE_ERROR] is returned and no data is written. ** ** ^An attempt to write to an expired [BLOB handle] fails with an ** error code of [SQLITE_ABORT]. ^Writes to the BLOB that occurred ** before the [BLOB handle] expired are not rolled back by the ** expiration of the handle, though of course those changes might ** have been overwritten by the statement that expired the BLOB handle ** or by other independent statements. ** -** ^(On success, sqlite3_blob_write() returns SQLITE_OK. -** Otherwise, an [error code] or an [extended error code] is returned.)^ -** ** This routine only works on a [BLOB handle] which has been created ** by a prior successful call to [sqlite3_blob_open()] and which has not ** been closed by [sqlite3_blob_close()]. Passing any other pointer in ** to this routine results in undefined and probably undesirable behavior. ** ** See also: [sqlite3_blob_read()]. */ -SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); +SQLITE_API int SQLITE_STDCALL sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); /* ** CAPI3REF: Virtual File System Objects ** ** A virtual filesystem (VFS) is an [sqlite3_vfs] object @@ -5687,13 +5946,13 @@ ** ** ^Unregister a VFS with the sqlite3_vfs_unregister() interface. ** ^(If the default VFS is unregistered, another VFS is chosen as ** the default. The choice for the new VFS is arbitrary.)^ */ -SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName); -SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); -SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); +SQLITE_API sqlite3_vfs *SQLITE_STDCALL sqlite3_vfs_find(const char *zVfsName); +SQLITE_API int SQLITE_STDCALL sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); +SQLITE_API int SQLITE_STDCALL sqlite3_vfs_unregister(sqlite3_vfs*); /* ** CAPI3REF: Mutexes ** ** The SQLite core uses these routines for thread @@ -5701,129 +5960,127 @@ ** use by SQLite, code that links against SQLite is ** permitted to use any of these routines. ** ** The SQLite source code contains multiple implementations ** of these mutex routines. An appropriate implementation -** is selected automatically at compile-time. ^(The following +** is selected automatically at compile-time. The following ** implementations are available in the SQLite core: ** ** <ul> ** <li> SQLITE_MUTEX_PTHREADS ** <li> SQLITE_MUTEX_W32 ** <li> SQLITE_MUTEX_NOOP -** </ul>)^ +** </ul> ** -** ^The SQLITE_MUTEX_NOOP implementation is a set of routines +** The SQLITE_MUTEX_NOOP implementation is a set of routines ** that does no real locking and is appropriate for use in -** a single-threaded application. ^The SQLITE_MUTEX_PTHREADS and +** a single-threaded application. The SQLITE_MUTEX_PTHREADS and ** SQLITE_MUTEX_W32 implementations are appropriate for use on Unix ** and Windows. ** -** ^(If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor +** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor ** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex ** implementation is included with the library. In this case the ** application must supply a custom mutex implementation using the ** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function ** before calling sqlite3_initialize() or any other public sqlite3_ -** function that calls sqlite3_initialize().)^ +** function that calls sqlite3_initialize(). ** ** ^The sqlite3_mutex_alloc() routine allocates a new -** mutex and returns a pointer to it. ^If it returns NULL -** that means that a mutex could not be allocated. ^SQLite -** will unwind its stack and return an error. ^(The argument -** to sqlite3_mutex_alloc() is one of these integer constants: +** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc() +** routine returns NULL if it is unable to allocate the requested +** mutex. The argument to sqlite3_mutex_alloc() must one of these +** integer constants: ** ** <ul> ** <li> SQLITE_MUTEX_FAST ** <li> SQLITE_MUTEX_RECURSIVE ** <li> SQLITE_MUTEX_STATIC_MASTER ** <li> SQLITE_MUTEX_STATIC_MEM -** <li> SQLITE_MUTEX_STATIC_MEM2 +** <li> SQLITE_MUTEX_STATIC_OPEN ** <li> SQLITE_MUTEX_STATIC_PRNG ** <li> SQLITE_MUTEX_STATIC_LRU -** <li> SQLITE_MUTEX_STATIC_LRU2 -** </ul>)^ +** <li> SQLITE_MUTEX_STATIC_PMEM +** <li> SQLITE_MUTEX_STATIC_APP1 +** <li> SQLITE_MUTEX_STATIC_APP2 +** <li> SQLITE_MUTEX_STATIC_APP3 +** </ul> ** ** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) ** cause sqlite3_mutex_alloc() to create ** a new mutex. ^The new mutex is recursive when SQLITE_MUTEX_RECURSIVE ** is used but not necessarily so when SQLITE_MUTEX_FAST is used. ** The mutex implementation does not need to make a distinction ** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does -** not want to. ^SQLite will only request a recursive mutex in -** cases where it really needs one. ^If a faster non-recursive mutex +** not want to. SQLite will only request a recursive mutex in +** cases where it really needs one. If a faster non-recursive mutex ** implementation is available on the host platform, the mutex subsystem ** might return such a mutex in response to SQLITE_MUTEX_FAST. ** ** ^The other allowed parameters to sqlite3_mutex_alloc() (anything other ** than SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return -** a pointer to a static preexisting mutex. ^Six static mutexes are +** a pointer to a static preexisting mutex. ^Nine static mutexes are ** used by the current version of SQLite. Future versions of SQLite ** may add additional static mutexes. Static mutexes are for internal ** use by SQLite only. Applications that use SQLite mutexes should ** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or ** SQLITE_MUTEX_RECURSIVE. ** ** ^Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST ** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() -** returns a different mutex on every call. ^But for the static +** returns a different mutex on every call. ^For the static ** mutex types, the same mutex is returned on every call that has ** the same type number. ** ** ^The sqlite3_mutex_free() routine deallocates a previously -** allocated dynamic mutex. ^SQLite is careful to deallocate every -** dynamic mutex that it allocates. The dynamic mutexes must not be in -** use when they are deallocated. Attempting to deallocate a static -** mutex results in undefined behavior. ^SQLite never deallocates -** a static mutex. +** allocated dynamic mutex. Attempting to deallocate a static +** mutex results in undefined behavior. ** ** ^The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt ** to enter a mutex. ^If another thread is already within the mutex, ** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return ** SQLITE_BUSY. ^The sqlite3_mutex_try() interface returns [SQLITE_OK] ** upon successful entry. ^(Mutexes created using ** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread. -** In such cases the, +** In such cases, the ** mutex must be exited an equal number of times before another thread -** can enter.)^ ^(If the same thread tries to enter any other -** kind of mutex more than once, the behavior is undefined. -** SQLite will never exhibit -** such behavior in its own use of mutexes.)^ +** can enter.)^ If the same thread tries to enter any mutex other +** than an SQLITE_MUTEX_RECURSIVE more than once, the behavior is undefined. ** ** ^(Some systems (for example, Windows 95) do not support the operation ** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() -** will always return SQLITE_BUSY. The SQLite core only ever uses -** sqlite3_mutex_try() as an optimization so this is acceptable behavior.)^ +** will always return SQLITE_BUSY. The SQLite core only ever uses +** sqlite3_mutex_try() as an optimization so this is acceptable +** behavior.)^ ** ** ^The sqlite3_mutex_leave() routine exits a mutex that was -** previously entered by the same thread. ^(The behavior +** previously entered by the same thread. The behavior ** is undefined if the mutex is not currently entered by the -** calling thread or is not currently allocated. SQLite will -** never do either.)^ +** calling thread or is not currently allocated. ** ** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or ** sqlite3_mutex_leave() is a NULL pointer, then all three routines ** behave as no-ops. ** ** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. */ -SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int); -SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*); -SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*); -SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*); -SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); +SQLITE_API sqlite3_mutex *SQLITE_STDCALL sqlite3_mutex_alloc(int); +SQLITE_API void SQLITE_STDCALL sqlite3_mutex_free(sqlite3_mutex*); +SQLITE_API void SQLITE_STDCALL sqlite3_mutex_enter(sqlite3_mutex*); +SQLITE_API int SQLITE_STDCALL sqlite3_mutex_try(sqlite3_mutex*); +SQLITE_API void SQLITE_STDCALL sqlite3_mutex_leave(sqlite3_mutex*); /* ** CAPI3REF: Mutex Methods Object ** ** An instance of this structure defines the low-level routines ** used to allocate and use mutexes. ** ** Usually, the default mutex implementations provided by SQLite are -** sufficient, however the user has the option of substituting a custom +** sufficient, however the application has the option of substituting a custom ** implementation for specialized deployments or systems for which SQLite -** does not provide a suitable implementation. In this case, the user +** does not provide a suitable implementation. In this case, the application ** creates and populates an instance of this structure to pass ** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option. ** Additionally, an instance of this structure can be used as an ** output variable when querying the system for the current mutex ** implementation, using the [SQLITE_CONFIG_GETMUTEX] option. @@ -5860,17 +6117,17 @@ ** by this structure are not required to handle this case, the results ** of passing a NULL pointer instead of a valid mutex handle are undefined ** (i.e. it is acceptable to provide an implementation that segfaults if ** it is passed a NULL pointer). ** -** The xMutexInit() method must be threadsafe. ^It must be harmless to +** The xMutexInit() method must be threadsafe. It must be harmless to ** invoke xMutexInit() multiple times within the same process and without ** intervening calls to xMutexEnd(). Second and subsequent calls to ** xMutexInit() must be no-ops. ** -** ^xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] -** and its associates). ^Similarly, xMutexAlloc() must not use SQLite memory +** xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] +** and its associates). Similarly, xMutexAlloc() must not use SQLite memory ** allocation for a static mutex. ^However xMutexAlloc() may use SQLite ** memory allocation for a fast or recursive mutex. ** ** ^SQLite will invoke the xMutexEnd() method when [sqlite3_shutdown()] is ** called, but only if the prior call to xMutexInit returned SQLITE_OK. @@ -5892,38 +6149,38 @@ /* ** CAPI3REF: Mutex Verification Routines ** ** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines -** are intended for use inside assert() statements. ^The SQLite core +** are intended for use inside assert() statements. The SQLite core ** never uses these routines except inside an assert() and applications -** are advised to follow the lead of the core. ^The SQLite core only +** are advised to follow the lead of the core. The SQLite core only ** provides implementations for these routines when it is compiled -** with the SQLITE_DEBUG flag. ^External mutex implementations +** with the SQLITE_DEBUG flag. External mutex implementations ** are only required to provide these routines if SQLITE_DEBUG is ** defined and if NDEBUG is not defined. ** -** ^These routines should return true if the mutex in their argument +** These routines should return true if the mutex in their argument ** is held or not held, respectively, by the calling thread. ** -** ^The implementation is not required to provide versions of these +** The implementation is not required to provide versions of these ** routines that actually work. If the implementation does not provide working ** versions of these routines, it should at least provide stubs that always ** return true so that one does not get spurious assertion failures. ** -** ^If the argument to sqlite3_mutex_held() is a NULL pointer then +** If the argument to sqlite3_mutex_held() is a NULL pointer then ** the routine should return 1. This seems counter-intuitive since ** clearly the mutex cannot be held if it does not exist. But ** the reason the mutex does not exist is because the build is not ** using mutexes. And we do not want the assert() containing the ** call to sqlite3_mutex_held() to fail, so a non-zero return is -** the appropriate thing to do. ^The sqlite3_mutex_notheld() +** the appropriate thing to do. The sqlite3_mutex_notheld() ** interface should also return 1 when given a NULL pointer. */ #ifndef NDEBUG -SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); -SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); +SQLITE_API int SQLITE_STDCALL sqlite3_mutex_held(sqlite3_mutex*); +SQLITE_API int SQLITE_STDCALL sqlite3_mutex_notheld(sqlite3_mutex*); #endif /* ** CAPI3REF: Mutex Types ** @@ -5942,10 +6199,13 @@ #define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ #define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_random() */ #define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ #define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ #define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ +#define SQLITE_MUTEX_STATIC_APP1 8 /* For use by application */ +#define SQLITE_MUTEX_STATIC_APP2 9 /* For use by application */ +#define SQLITE_MUTEX_STATIC_APP3 10 /* For use by application */ /* ** CAPI3REF: Retrieve the mutex for a database connection ** ** ^This interface returns a pointer the [sqlite3_mutex] object that @@ -5952,11 +6212,11 @@ ** serializes access to the [database connection] given in the argument ** when the [threading mode] is Serialized. ** ^If the [threading mode] is Single-thread or Multi-thread then this ** routine returns a NULL pointer. */ -SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*); +SQLITE_API sqlite3_mutex *SQLITE_STDCALL sqlite3_db_mutex(sqlite3*); /* ** CAPI3REF: Low-Level Control Of Database Files ** ** ^The [sqlite3_file_control()] interface makes a direct call to the @@ -5986,11 +6246,11 @@ ** an incorrect zDbName and an SQLITE_ERROR return from the underlying ** xFileControl method. ** ** See also: [SQLITE_FCNTL_LOCKSTATE] */ -SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); +SQLITE_API int SQLITE_STDCALL sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); /* ** CAPI3REF: Testing Interface ** ** ^The sqlite3_test_control() interface is used to read out internal @@ -6005,11 +6265,11 @@ ** The details of the operation codes, their meanings, the parameters ** they take, and what they do are all subject to change without notice. ** Unlike most of the SQLite API, this function is not guaranteed to ** operate consistently from one release to the next. */ -SQLITE_API int sqlite3_test_control(int op, ...); +SQLITE_API int SQLITE_CDECL sqlite3_test_control(int op, ...); /* ** CAPI3REF: Testing Interface Operation Codes ** ** These constants are the valid operation code parameters used @@ -6033,17 +6293,23 @@ #define SQLITE_TESTCTRL_RESERVE 14 #define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_ISKEYWORD 16 #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 #define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 -#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 -#define SQLITE_TESTCTRL_LAST 19 +#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */ +#define SQLITE_TESTCTRL_NEVER_CORRUPT 20 +#define SQLITE_TESTCTRL_VDBE_COVERAGE 21 +#define SQLITE_TESTCTRL_BYTEORDER 22 +#define SQLITE_TESTCTRL_ISINIT 23 +#define SQLITE_TESTCTRL_SORTER_MMAP 24 +#define SQLITE_TESTCTRL_IMPOSTER 25 +#define SQLITE_TESTCTRL_LAST 25 /* ** CAPI3REF: SQLite Runtime Status ** -** ^This interface is used to retrieve runtime status information +** ^These interfaces are used to retrieve runtime status information ** about the performance of SQLite, and optionally to reset various ** highwater marks. ^The first argument is an integer code for ** the specific parameter to measure. ^(Recognized integer codes ** are of the form [status parameters | SQLITE_STATUS_...].)^ ** ^The current value of the parameter is returned into *pCurrent. @@ -6053,23 +6319,26 @@ ** value. For those parameters ** nothing is written into *pHighwater and the resetFlag is ignored.)^ ** ^(Other parameters record only the highwater mark and not the current ** value. For these latter parameters nothing is written into *pCurrent.)^ ** -** ^The sqlite3_status() routine returns SQLITE_OK on success and a -** non-zero [error code] on failure. +** ^The sqlite3_status() and sqlite3_status64() routines return +** SQLITE_OK on success and a non-zero [error code] on failure. ** -** This routine is threadsafe but is not atomic. This routine can be -** called while other threads are running the same or different SQLite -** interfaces. However the values returned in *pCurrent and -** *pHighwater reflect the status of SQLite at different points in time -** and it is possible that another thread might change the parameter -** in between the times when *pCurrent and *pHighwater are written. +** If either the current value or the highwater mark is too large to +** be represented by a 32-bit integer, then the values returned by +** sqlite3_status() are undefined. ** ** See also: [sqlite3_db_status()] */ -SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); +SQLITE_API int SQLITE_STDCALL sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); +SQLITE_API int SQLITE_STDCALL sqlite3_status64( + int op, + sqlite3_int64 *pCurrent, + sqlite3_int64 *pHighwater, + int resetFlag +); /* ** CAPI3REF: Status Parameters ** KEYWORDS: {status parameters} @@ -6183,11 +6452,11 @@ ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a ** non-zero [error code] on failure. ** ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. */ -SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); +SQLITE_API int SQLITE_STDCALL sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); /* ** CAPI3REF: Status Parameters for database connections ** KEYWORDS: {SQLITE_DBSTATUS options} ** @@ -6225,25 +6494,25 @@ ** memory already being in use. ** Only the high-water value is meaningful; ** the current value is always zero.)^ ** ** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt> -** <dd>This parameter returns the approximate number of of bytes of heap +** <dd>This parameter returns the approximate number of bytes of heap ** memory used by all pager caches associated with the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. ** ** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt> -** <dd>This parameter returns the approximate number of of bytes of heap +** <dd>This parameter returns the approximate number of bytes of heap ** memory used to store the schema for all databases associated ** with the connection - main, temp, and any [ATTACH]-ed databases.)^ ** ^The full amount of memory used by the schemas is reported, even if the ** schema memory is shared with other database connections due to ** [shared cache mode] being enabled. ** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0. ** ** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt> -** <dd>This parameter returns the approximate number of of bytes of heap +** <dd>This parameter returns the approximate number of bytes of heap ** and lookaside memory used by all prepared statements associated with ** the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_STMT_USED is always 0. ** </dd> ** @@ -6312,11 +6581,11 @@ ** ^If the resetFlg is true, then the counter is reset to zero after this ** interface call returns. ** ** See also: [sqlite3_status()] and [sqlite3_db_status()]. */ -SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); +SQLITE_API int SQLITE_STDCALL sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); /* ** CAPI3REF: Status Parameters for prepared statements ** KEYWORDS: {SQLITE_STMTSTATUS counter} {SQLITE_STMTSTATUS counters} ** @@ -6638,10 +6907,14 @@ ** sqlite3_backup_init(D,N,S,M) identify the [database connection] ** and database name of the source database, respectively. ** ^The source and destination [database connections] (parameters S and D) ** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. +** +** ^A call to sqlite3_backup_init() will fail, returning SQLITE_ERROR, if +** there is already a read or read-write transaction open on the +** destination database. ** ** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is ** returned and an error code and error message are stored in the ** destination [database connection] D. ** ^The error code and message for the failed call to sqlite3_backup_init() @@ -6731,24 +7004,24 @@ ** ** ^A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from sqlite3_backup_step() ** is not a permanent error and does not affect the return value of ** sqlite3_backup_finish(). ** -** [[sqlite3_backup__remaining()]] [[sqlite3_backup_pagecount()]] +** [[sqlite3_backup_remaining()]] [[sqlite3_backup_pagecount()]] ** <b>sqlite3_backup_remaining() and sqlite3_backup_pagecount()</b> ** -** ^Each call to sqlite3_backup_step() sets two values inside -** the [sqlite3_backup] object: the number of pages still to be backed -** up and the total number of pages in the source database file. -** The sqlite3_backup_remaining() and sqlite3_backup_pagecount() interfaces -** retrieve these two values, respectively. -** -** ^The values returned by these functions are only updated by -** sqlite3_backup_step(). ^If the source database is modified during a backup -** operation, then the values are not updated to account for any extra -** pages that need to be updated or the size of the source database file -** changing. +** ^The sqlite3_backup_remaining() routine returns the number of pages still +** to be backed up at the conclusion of the most recent sqlite3_backup_step(). +** ^The sqlite3_backup_pagecount() routine returns the total number of pages +** in the source database at the conclusion of the most recent +** sqlite3_backup_step(). +** ^(The values returned by these functions are only updated by +** sqlite3_backup_step(). If the source database is modified in a way that +** changes the size of the source database or the number of pages remaining, +** those changes are not reflected in the output of sqlite3_backup_pagecount() +** and sqlite3_backup_remaining() until after the next +** sqlite3_backup_step().)^ ** ** <b>Concurrent Usage of Database Handles</b> ** ** ^The source [database connection] may be used by the application for other ** purposes while a backup operation is underway or being initialized. @@ -6777,20 +7050,20 @@ ** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount() ** APIs are not strictly speaking threadsafe. If they are invoked at the ** same time as another thread is invoking sqlite3_backup_step() it is ** possible that they return invalid values. */ -SQLITE_API sqlite3_backup *sqlite3_backup_init( +SQLITE_API sqlite3_backup *SQLITE_STDCALL sqlite3_backup_init( sqlite3 *pDest, /* Destination database handle */ const char *zDestName, /* Destination database name */ sqlite3 *pSource, /* Source database handle */ const char *zSourceName /* Source database name */ ); -SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage); -SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p); -SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p); -SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); +SQLITE_API int SQLITE_STDCALL sqlite3_backup_step(sqlite3_backup *p, int nPage); +SQLITE_API int SQLITE_STDCALL sqlite3_backup_finish(sqlite3_backup *p); +SQLITE_API int SQLITE_STDCALL sqlite3_backup_remaining(sqlite3_backup *p); +SQLITE_API int SQLITE_STDCALL sqlite3_backup_pagecount(sqlite3_backup *p); /* ** CAPI3REF: Unlock Notification ** ** ^When running in shared-cache mode, a database operation may fail with @@ -6902,11 +7175,11 @@ ** by an sqlite3_step() call. ^(If there is a blocking connection, then the ** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in ** the special "DROP TABLE/INDEX" case, the extended error code is just ** SQLITE_LOCKED.)^ */ -SQLITE_API int sqlite3_unlock_notify( +SQLITE_API int SQLITE_STDCALL sqlite3_unlock_notify( sqlite3 *pBlocked, /* Waiting connection */ void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */ void *pNotifyArg /* Argument to pass to xNotify */ ); @@ -6917,12 +7190,12 @@ ** ^The [sqlite3_stricmp()] and [sqlite3_strnicmp()] APIs allow applications ** and extensions to compare the contents of two buffers containing UTF-8 ** strings in a case-independent fashion, using the same definition of "case ** independence" that SQLite uses internally when comparing identifiers. */ -SQLITE_API int sqlite3_stricmp(const char *, const char *); -SQLITE_API int sqlite3_strnicmp(const char *, const char *, int); +SQLITE_API int SQLITE_STDCALL sqlite3_stricmp(const char *, const char *); +SQLITE_API int SQLITE_STDCALL sqlite3_strnicmp(const char *, const char *, int); /* ** CAPI3REF: String Globbing * ** ^The [sqlite3_strglob(P,X)] interface returns zero if string X matches @@ -6933,11 +7206,11 @@ ** sensitive. ** ** Note that this routine returns zero on a match and non-zero if the strings ** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()]. */ -SQLITE_API int sqlite3_strglob(const char *zGlob, const char *zStr); +SQLITE_API int SQLITE_STDCALL sqlite3_strglob(const char *zGlob, const char *zStr); /* ** CAPI3REF: Error Logging Interface ** ** ^The [sqlite3_log()] interface writes a message into the [error log] @@ -6956,22 +7229,20 @@ ** will not use dynamically allocated memory. The log message is stored in ** a fixed-length buffer on the stack. If the log message is longer than ** a few hundred characters, it will be truncated to the length of the ** buffer. */ -SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); +SQLITE_API void SQLITE_CDECL sqlite3_log(int iErrCode, const char *zFormat, ...); /* ** CAPI3REF: Write-Ahead Log Commit Hook ** ** ^The [sqlite3_wal_hook()] function is used to register a callback that -** will be invoked each time a database connection commits data to a -** [write-ahead log] (i.e. whenever a transaction is committed in -** [journal_mode | journal_mode=WAL mode]). +** is invoked each time data is committed to a database in wal mode. ** -** ^The callback is invoked by SQLite after the commit has taken place and -** the associated write-lock on the database released, so the implementation +** ^(The callback is invoked by SQLite after the commit has taken place and +** the associated write-lock on the database released)^, so the implementation ** may read, write or [checkpoint] the database as required. ** ** ^The first parameter passed to the callback function when it is invoked ** is a copy of the third parameter passed to sqlite3_wal_hook() when ** registering the callback. ^The second is a copy of the database handle. @@ -6993,11 +7264,11 @@ ** previously registered write-ahead log callback. ^Note that the ** [sqlite3_wal_autocheckpoint()] interface and the ** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will ** those overwrite any prior [sqlite3_wal_hook()] settings. */ -SQLITE_API void *sqlite3_wal_hook( +SQLITE_API void *SQLITE_STDCALL sqlite3_wal_hook( sqlite3*, int(*)(void *,sqlite3*,const char*,int), void* ); @@ -7017,126 +7288,155 @@ ** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism ** configured by this function. ** ** ^The [wal_autocheckpoint pragma] can be used to invoke this interface ** from SQL. +** +** ^Checkpoints initiated by this mechanism are +** [sqlite3_wal_checkpoint_v2|PASSIVE]. ** ** ^Every new [database connection] defaults to having the auto-checkpoint ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] ** pages. The use of this interface ** is only necessary if the default setting is found to be suboptimal ** for a particular application. */ -SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); - -/* -** CAPI3REF: Checkpoint a database -** -** ^The [sqlite3_wal_checkpoint(D,X)] interface causes database named X -** on [database connection] D to be [checkpointed]. ^If X is NULL or an -** empty string, then a checkpoint is run on all databases of -** connection D. ^If the database connection D is not in -** [WAL | write-ahead log mode] then this interface is a harmless no-op. -** -** ^The [wal_checkpoint pragma] can be used to invoke this interface -** from SQL. ^The [sqlite3_wal_autocheckpoint()] interface and the -** [wal_autocheckpoint pragma] can be used to cause this interface to be -** run whenever the WAL reaches a certain size threshold. -** -** See also: [sqlite3_wal_checkpoint_v2()] -*/ -SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); - -/* -** CAPI3REF: Checkpoint a database -** -** Run a checkpoint operation on WAL database zDb attached to database -** handle db. The specific operation is determined by the value of the -** eMode parameter: -** -** <dl> -** <dt>SQLITE_CHECKPOINT_PASSIVE<dd> -** Checkpoint as many frames as possible without waiting for any database -** readers or writers to finish. Sync the db file if all frames in the log -** are checkpointed. This mode is the same as calling -** sqlite3_wal_checkpoint(). The busy-handler callback is never invoked. -** -** <dt>SQLITE_CHECKPOINT_FULL<dd> -** This mode blocks (calls the busy-handler callback) until there is no -** database writer and all readers are reading from the most recent database -** snapshot. It then checkpoints all frames in the log file and syncs the -** database file. This call blocks database writers while it is running, -** but not database readers. -** -** <dt>SQLITE_CHECKPOINT_RESTART<dd> -** This mode works the same way as SQLITE_CHECKPOINT_FULL, except after -** checkpointing the log file it blocks (calls the busy-handler callback) -** until all readers are reading from the database file only. This ensures -** that the next client to write to the database file restarts the log file -** from the beginning. This call blocks database writers while it is running, -** but not database readers. -** </dl> -** -** If pnLog is not NULL, then *pnLog is set to the total number of frames in -** the log file before returning. If pnCkpt is not NULL, then *pnCkpt is set to -** the total number of checkpointed frames (including any that were already -** checkpointed when this function is called). *pnLog and *pnCkpt may be -** populated even if sqlite3_wal_checkpoint_v2() returns other than SQLITE_OK. -** If no values are available because of an error, they are both set to -1 -** before returning to communicate this to the caller. -** -** All calls obtain an exclusive "checkpoint" lock on the database file. If -** any other process is running a checkpoint operation at the same time, the -** lock cannot be obtained and SQLITE_BUSY is returned. Even if there is a -** busy-handler configured, it will not be invoked in this case. -** -** The SQLITE_CHECKPOINT_FULL and RESTART modes also obtain the exclusive -** "writer" lock on the database file. If the writer lock cannot be obtained -** immediately, and a busy-handler is configured, it is invoked and the writer -** lock retried until either the busy-handler returns 0 or the lock is -** successfully obtained. The busy-handler is also invoked while waiting for -** database readers as described above. If the busy-handler returns 0 before -** the writer lock is obtained or while waiting for database readers, the -** checkpoint operation proceeds from that point in the same way as -** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible -** without blocking any further. SQLITE_BUSY is returned in this case. -** -** If parameter zDb is NULL or points to a zero length string, then the -** specified operation is attempted on all WAL databases. In this case the -** values written to output parameters *pnLog and *pnCkpt are undefined. If -** an SQLITE_BUSY error is encountered when processing one or more of the -** attached WAL databases, the operation is still attempted on any remaining -** attached databases and SQLITE_BUSY is returned to the caller. If any other -** error occurs while processing an attached database, processing is abandoned -** and the error code returned to the caller immediately. If no error -** (SQLITE_BUSY or otherwise) is encountered while processing the attached -** databases, SQLITE_OK is returned. -** -** If database zDb is the name of an attached database that is not in WAL -** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. If -** zDb is not NULL (or a zero length string) and is not the name of any -** attached database, SQLITE_ERROR is returned to the caller. -*/ -SQLITE_API int sqlite3_wal_checkpoint_v2( +SQLITE_API int SQLITE_STDCALL sqlite3_wal_autocheckpoint(sqlite3 *db, int N); + +/* +** CAPI3REF: Checkpoint a database +** +** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to +** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^ +** +** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the +** [write-ahead log] for database X on [database connection] D to be +** transferred into the database file and for the write-ahead log to +** be reset. See the [checkpointing] documentation for addition +** information. +** +** This interface used to be the only way to cause a checkpoint to +** occur. But then the newer and more powerful [sqlite3_wal_checkpoint_v2()] +** interface was added. This interface is retained for backwards +** compatibility and as a convenience for applications that need to manually +** start a callback but which do not need the full power (and corresponding +** complication) of [sqlite3_wal_checkpoint_v2()]. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); + +/* +** CAPI3REF: Checkpoint a database +** +** ^(The sqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint +** operation on database X of [database connection] D in mode M. Status +** information is written back into integers pointed to by L and C.)^ +** ^(The M parameter must be a valid [checkpoint mode]:)^ +** +** <dl> +** <dt>SQLITE_CHECKPOINT_PASSIVE<dd> +** ^Checkpoint as many frames as possible without waiting for any database +** readers or writers to finish, then sync the database file if all frames +** in the log were checkpointed. ^The [busy-handler callback] +** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. +** ^On the other hand, passive mode might leave the checkpoint unfinished +** if there are concurrent readers or writers. +** +** <dt>SQLITE_CHECKPOINT_FULL<dd> +** ^This mode blocks (it invokes the +** [sqlite3_busy_handler|busy-handler callback]) until there is no +** database writer and all readers are reading from the most recent database +** snapshot. ^It then checkpoints all frames in the log file and syncs the +** database file. ^This mode blocks new database writers while it is pending, +** but new database readers are allowed to continue unimpeded. +** +** <dt>SQLITE_CHECKPOINT_RESTART<dd> +** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition +** that after checkpointing the log file it blocks (calls the +** [busy-handler callback]) +** until all readers are reading from the database file only. ^This ensures +** that the next writer will restart the log file from the beginning. +** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new +** database writer attempts while it is pending, but does not impede readers. +** +** <dt>SQLITE_CHECKPOINT_TRUNCATE<dd> +** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the +** addition that it also truncates the log file to zero bytes just prior +** to a successful return. +** </dl> +** +** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in +** the log file or to -1 if the checkpoint could not run because +** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not +** NULL,then *pnCkpt is set to the total number of checkpointed frames in the +** log file (including any that were already checkpointed before the function +** was called) or to -1 if the checkpoint could not run due to an error or +** because the database is not in WAL mode. ^Note that upon successful +** completion of an SQLITE_CHECKPOINT_TRUNCATE, the log file will have been +** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero. +** +** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If +** any other process is running a checkpoint operation at the same time, the +** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a +** busy-handler configured, it will not be invoked in this case. +** +** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the +** exclusive "writer" lock on the database file. ^If the writer lock cannot be +** obtained immediately, and a busy-handler is configured, it is invoked and +** the writer lock retried until either the busy-handler returns 0 or the lock +** is successfully obtained. ^The busy-handler is also invoked while waiting for +** database readers as described above. ^If the busy-handler returns 0 before +** the writer lock is obtained or while waiting for database readers, the +** checkpoint operation proceeds from that point in the same way as +** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible +** without blocking any further. ^SQLITE_BUSY is returned in this case. +** +** ^If parameter zDb is NULL or points to a zero length string, then the +** specified operation is attempted on all WAL databases [attached] to +** [database connection] db. In this case the +** values written to output parameters *pnLog and *pnCkpt are undefined. ^If +** an SQLITE_BUSY error is encountered when processing one or more of the +** attached WAL databases, the operation is still attempted on any remaining +** attached databases and SQLITE_BUSY is returned at the end. ^If any other +** error occurs while processing an attached database, processing is abandoned +** and the error code is returned to the caller immediately. ^If no error +** (SQLITE_BUSY or otherwise) is encountered while processing the attached +** databases, SQLITE_OK is returned. +** +** ^If database zDb is the name of an attached database that is not in WAL +** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. ^If +** zDb is not NULL (or a zero length string) and is not the name of any +** attached database, SQLITE_ERROR is returned to the caller. +** +** ^Unless it returns SQLITE_MISUSE, +** the sqlite3_wal_checkpoint_v2() interface +** sets the error information that is queried by +** [sqlite3_errcode()] and [sqlite3_errmsg()]. +** +** ^The [PRAGMA wal_checkpoint] command can be used to invoke this interface +** from SQL. +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_wal_checkpoint_v2( sqlite3 *db, /* Database handle */ const char *zDb, /* Name of attached database (or NULL) */ int eMode, /* SQLITE_CHECKPOINT_* value */ int *pnLog, /* OUT: Size of WAL log in frames */ int *pnCkpt /* OUT: Total number of frames checkpointed */ ); /* -** CAPI3REF: Checkpoint operation parameters +** CAPI3REF: Checkpoint Mode Values +** KEYWORDS: {checkpoint mode} ** -** These constants can be used as the 3rd parameter to -** [sqlite3_wal_checkpoint_v2()]. See the [sqlite3_wal_checkpoint_v2()] -** documentation for additional information about the meaning and use of -** each of these values. +** These constants define all valid values for the "checkpoint mode" passed +** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. +** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the +** meaning of each of these checkpoint modes. */ -#define SQLITE_CHECKPOINT_PASSIVE 0 -#define SQLITE_CHECKPOINT_FULL 1 -#define SQLITE_CHECKPOINT_RESTART 2 +#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ +#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ +#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */ +#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ /* ** CAPI3REF: Virtual Table Interface Configuration ** ** This function may be called by either the [xConnect] or [xCreate] method @@ -7148,11 +7448,11 @@ ** ** At present, there is only one option that may be configured using ** this function. (See [SQLITE_VTAB_CONSTRAINT_SUPPORT].) Further options ** may be added in the future. */ -SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); +SQLITE_API int SQLITE_CDECL sqlite3_vtab_config(sqlite3*, int op, ...); /* ** CAPI3REF: Virtual Table Configuration Options ** ** These macros define the various options to the @@ -7201,14 +7501,15 @@ ** value returned is one of [SQLITE_ROLLBACK], [SQLITE_IGNORE], [SQLITE_FAIL], ** [SQLITE_ABORT], or [SQLITE_REPLACE], according to the [ON CONFLICT] mode ** of the SQL statement that triggered the call to the [xUpdate] method of the ** [virtual table]. */ -SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); +SQLITE_API int SQLITE_STDCALL sqlite3_vtab_on_conflict(sqlite3 *); /* ** CAPI3REF: Conflict resolution modes +** KEYWORDS: {conflict resolution mode} ** ** These constants are returned by [sqlite3_vtab_on_conflict()] to ** inform a [virtual table] implementation what the [ON CONFLICT] mode ** is for the SQL statement being evaluated. ** @@ -7220,10 +7521,110 @@ /* #define SQLITE_IGNORE 2 // Also used by sqlite3_authorizer() callback */ #define SQLITE_FAIL 3 /* #define SQLITE_ABORT 4 // Also an error code */ #define SQLITE_REPLACE 5 +/* +** CAPI3REF: Prepared Statement Scan Status Opcodes +** KEYWORDS: {scanstatus options} +** +** The following constants can be used for the T parameter to the +** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a +** different metric for sqlite3_stmt_scanstatus() to return. +** +** When the value returned to V is a string, space to hold that string is +** managed by the prepared statement S and will be automatically freed when +** S is finalized. +** +** <dl> +** [[SQLITE_SCANSTAT_NLOOP]] <dt>SQLITE_SCANSTAT_NLOOP</dt> +** <dd>^The [sqlite3_int64] variable pointed to by the T parameter will be +** set to the total number of times that the X-th loop has run.</dd> +** +** [[SQLITE_SCANSTAT_NVISIT]] <dt>SQLITE_SCANSTAT_NVISIT</dt> +** <dd>^The [sqlite3_int64] variable pointed to by the T parameter will be set +** to the total number of rows examined by all iterations of the X-th loop.</dd> +** +** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt> +** <dd>^The "double" variable pointed to by the T parameter will be set to the +** query planner's estimate for the average number of rows output from each +** iteration of the X-th loop. If the query planner's estimates was accurate, +** then this value will approximate the quotient NVISIT/NLOOP and the +** product of this value for all prior loops with the same SELECTID will +** be the NLOOP value for the current loop. +** +** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt> +** <dd>^The "const char *" variable pointed to by the T parameter will be set +** to a zero-terminated UTF-8 string containing the name of the index or table +** used for the X-th loop. +** +** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt> +** <dd>^The "const char *" variable pointed to by the T parameter will be set +** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] +** description for the X-th loop. +** +** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECT</dt> +** <dd>^The "int" variable pointed to by the T parameter will be set to the +** "select-id" for the X-th loop. The select-id identifies which query or +** subquery the loop is part of. The main query has a select-id of zero. +** The select-id is the same value as is output in the first column +** of an [EXPLAIN QUERY PLAN] query. +** </dl> +*/ +#define SQLITE_SCANSTAT_NLOOP 0 +#define SQLITE_SCANSTAT_NVISIT 1 +#define SQLITE_SCANSTAT_EST 2 +#define SQLITE_SCANSTAT_NAME 3 +#define SQLITE_SCANSTAT_EXPLAIN 4 +#define SQLITE_SCANSTAT_SELECTID 5 + +/* +** CAPI3REF: Prepared Statement Scan Status +** +** This interface returns information about the predicted and measured +** performance for pStmt. Advanced applications can use this +** interface to compare the predicted and the measured performance and +** issue warnings and/or rerun [ANALYZE] if discrepancies are found. +** +** Since this interface is expected to be rarely used, it is only +** available if SQLite is compiled using the [SQLITE_ENABLE_STMT_SCANSTATUS] +** compile-time option. +** +** The "iScanStatusOp" parameter determines which status information to return. +** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior +** of this interface is undefined. +** ^The requested measurement is written into a variable pointed to by +** the "pOut" parameter. +** Parameter "idx" identifies the specific loop to retrieve statistics for. +** Loops are numbered starting from zero. ^If idx is out of range - less than +** zero or greater than or equal to the total number of loops used to implement +** the statement - a non-zero value is returned and the variable that pOut +** points to is unchanged. +** +** ^Statistics might not be available for all loops in all statements. ^In cases +** where there exist loops with no available statistics, this function behaves +** as if the loop did not exist - it returns non-zero and leave the variable +** that pOut points to unchanged. +** +** See also: [sqlite3_stmt_scanstatus_reset()] +*/ +SQLITE_API SQLITE_EXPERIMENTAL int SQLITE_STDCALL sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + void *pOut /* Result written here */ +); + +/* +** CAPI3REF: Zero Scan-Status Counters +** +** ^Zero all [sqlite3_stmt_scanstatus()] related event counters. +** +** This API is only available if the library is built with pre-processor +** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined. +*/ +SQLITE_API SQLITE_EXPERIMENTAL void SQLITE_STDCALL sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. @@ -7257,25 +7658,31 @@ #ifdef __cplusplus extern "C" { #endif typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry; +typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info; + +/* The double-precision datatype used by RTree depends on the +** SQLITE_RTREE_INT_ONLY compile-time option. +*/ +#ifdef SQLITE_RTREE_INT_ONLY + typedef sqlite3_int64 sqlite3_rtree_dbl; +#else + typedef double sqlite3_rtree_dbl; +#endif /* ** Register a geometry callback named zGeom that can be used as part of an ** R-Tree geometry query as follows: ** ** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zGeom(... params ...) */ -SQLITE_API int sqlite3_rtree_geometry_callback( +SQLITE_API int SQLITE_STDCALL sqlite3_rtree_geometry_callback( sqlite3 *db, const char *zGeom, -#ifdef SQLITE_RTREE_INT_ONLY - int (*xGeom)(sqlite3_rtree_geometry*, int n, sqlite3_int64 *a, int *pRes), -#else - int (*xGeom)(sqlite3_rtree_geometry*, int n, double *a, int *pRes), -#endif + int (*xGeom)(sqlite3_rtree_geometry*, int, sqlite3_rtree_dbl*,int*), void *pContext ); /* @@ -7283,17 +7690,66 @@ ** argument to callbacks registered using rtree_geometry_callback(). */ struct sqlite3_rtree_geometry { void *pContext; /* Copy of pContext passed to s_r_g_c() */ int nParam; /* Size of array aParam[] */ - double *aParam; /* Parameters passed to SQL geom function */ + sqlite3_rtree_dbl *aParam; /* Parameters passed to SQL geom function */ void *pUser; /* Callback implementation user data */ void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */ }; +/* +** Register a 2nd-generation geometry callback named zScore that can be +** used as part of an R-Tree geometry query as follows: +** +** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zQueryFunc(... params ...) +*/ +SQLITE_API int SQLITE_STDCALL sqlite3_rtree_query_callback( + sqlite3 *db, + const char *zQueryFunc, + int (*xQueryFunc)(sqlite3_rtree_query_info*), + void *pContext, + void (*xDestructor)(void*) +); + + +/* +** A pointer to a structure of the following type is passed as the +** argument to scored geometry callback registered using +** sqlite3_rtree_query_callback(). +** +** Note that the first 5 fields of this structure are identical to +** sqlite3_rtree_geometry. This structure is a subclass of +** sqlite3_rtree_geometry. +*/ +struct sqlite3_rtree_query_info { + void *pContext; /* pContext from when function registered */ + int nParam; /* Number of function parameters */ + sqlite3_rtree_dbl *aParam; /* value of function parameters */ + void *pUser; /* callback can use this, if desired */ + void (*xDelUser)(void*); /* function to free pUser */ + sqlite3_rtree_dbl *aCoord; /* Coordinates of node or entry to check */ + unsigned int *anQueue; /* Number of pending entries in the queue */ + int nCoord; /* Number of coordinates */ + int iLevel; /* Level of current node or entry */ + int mxLevel; /* The largest iLevel value in the tree */ + sqlite3_int64 iRowid; /* Rowid for current entry */ + sqlite3_rtree_dbl rParentScore; /* Score of parent node */ + int eParentWithin; /* Visibility of parent node */ + int eWithin; /* OUT: Visiblity */ + sqlite3_rtree_dbl rScore; /* OUT: Write the score here */ +}; + +/* +** Allowed values for sqlite3_rtree_query.eWithin and .eParentWithin. +*/ +#define NOT_WITHIN 0 /* Object completely outside of query region */ +#define PARTLY_WITHIN 1 /* Object partially overlaps query region */ +#define FULLY_WITHIN 2 /* Object fully contained within query region */ + #ifdef __cplusplus } /* end of the 'extern "C"' block */ #endif #endif /* ifndef _SQLITE3RTREE_H_ */ Index: src/stash.c ================================================================== --- src/stash.c +++ src/stash.c @@ -23,17 +23,17 @@ /* ** SQL code to implement the tables needed by the stash. */ static const char zStashInit[] = -@ CREATE TABLE IF NOT EXISTS %s.stash( +@ CREATE TABLE IF NOT EXISTS "%w".stash( @ stashid INTEGER PRIMARY KEY, -- Unique stash identifier @ vid INTEGER, -- The baseline check-out for this stash @ comment TEXT, -- Comment for this stash. Or NULL @ ctime TIMESTAMP -- When the stash was created @ ); -@ CREATE TABLE IF NOT EXISTS %s.stashfile( +@ CREATE TABLE IF NOT EXISTS "%w".stashfile( @ stashid INTEGER REFERENCES stash, -- Stash that contains this file @ rid INTEGER, -- Baseline content in BLOB table or 0. @ isAdded BOOLEAN, -- True if this is an added file @ isRemoved BOOLEAN, -- True if this file is deleted @ isExec BOOLEAN, -- True if file is executable @@ -61,24 +61,24 @@ zFile = mprintf("%/", zFName); file_tree_name(zFile, &fname, 1); zTreename = blob_str(&fname); blob_zero(&sql); - blob_appendf(&sql, + blob_append_sql(&sql, "SELECT deleted, isexe, islink, mrid, pathname, coalesce(origname,pathname)" " FROM vfile" " WHERE vid=%d AND (chnged OR deleted OR origname NOT NULL OR mrid==0)", vid ); if( fossil_strcmp(zTreename,".")!=0 ){ - blob_appendf(&sql, + blob_append_sql(&sql, " AND (pathname GLOB '%q/*' OR origname GLOB '%q/*'" " OR pathname=%Q OR origname=%Q)", zTreename, zTreename, zTreename, zTreename ); } - db_prepare(&q, blob_str(&sql)); + db_prepare(&q, "%s", blob_sql_text(&sql)); blob_reset(&sql); db_prepare(&ins, "INSERT INTO stashfile(stashid, rid, isAdded, isRemoved, isExec, isLink," "origname, newname, delta)" "VALUES(%d,:rid,:isadd,:isrm,:isexe,:islink,:orig,:new,:content)", @@ -168,11 +168,11 @@ blob_zero(&prompt); #endif blob_append(&prompt, "\n" "# Enter a description of what is being stashed. Lines beginning\n" - "# with \"#\" are ignored. Stash comments are plain text except.\n" + "# with \"#\" are ignored. Stash comments are plain text except\n" "# newlines are not preserved.\n", -1); prompt_for_user_comment(&comment, &prompt); blob_reset(&prompt); zComment = blob_str(&comment); @@ -199,16 +199,20 @@ /* ** Apply a stash to the current check-out. */ static void stash_apply(int stashid, int nConflict){ + int vid; Stmt q; db_prepare(&q, "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta" " FROM stashfile WHERE stashid=%d", stashid ); + vid = db_lget_int("checkout",0); + db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)", + filename_collation()); while( db_step(&q)==SQLITE_ROW ){ int rid = db_column_int(&q, 0); int isRemoved = db_column_int(&q, 1); int isExec = db_column_int(&q, 2); int isLink = db_column_int(&q, 3); @@ -218,14 +222,14 @@ char *zNPath = mprintf("%s%s", g.zLocalRoot, zNew); Blob delta; undo_save(zNew); blob_zero(&delta); if( rid==0 ){ + db_multi_exec("INSERT OR IGNORE INTO sfile(x) VALUES(%Q)", zNew); db_ephemeral_blob(&q, 6, &delta); blob_write_to_file(&delta, zNPath); file_wd_setexe(zNPath, isExec); - fossil_print("ADD %s\n", zNew); }else if( isRemoved ){ fossil_print("DELETE %s\n", zOrig); file_delete(zOPath); }else{ Blob a, b, out, disk; @@ -276,10 +280,11 @@ if( fossil_strcmp(zOrig,zNew)!=0 ){ undo_save(zOrig); file_delete(zOPath); } } + stash_add_files_in_sfile(vid); db_finalize(&q); if( nConflict ){ fossil_print( "WARNING: %d merge conflicts - see messages above for details.\n", nConflict); @@ -311,11 +316,11 @@ int isLink = db_column_int(&q, 3); int isBin1, isBin2; const char *zOrig = db_column_text(&q, 4); const char *zNew = db_column_text(&q, 5); char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig); - Blob delta, a, b, disk; + Blob a, b; if( rid==0 ){ db_ephemeral_blob(&q, 6, &a); fossil_print("ADDED %s\n", zNew); diff_print_index(zNew, diffFlags); isBin1 = 0; @@ -337,10 +342,11 @@ isBin1 = fIncludeBinary ? 0 : looks_like_binary(&a); isBin2 = 0; diff_file_mem(&a, &empty, isBin1, isBin2, zOrig, zDiffCmd, zBinGlob, fIncludeBinary, diffFlags); }else{ + Blob delta, disk; int isOrigLink = file_wd_islink(zOPath); db_ephemeral_blob(&q, 6, &delta); if( fBaseline==0 ){ if( isOrigLink ){ blob_read_link(&disk, zOPath); @@ -363,12 +369,12 @@ zDiffCmd, zBinGlob, fIncludeBinary, diffFlags); blob_reset(&a); blob_reset(&b); } if( !fBaseline ) blob_reset(&disk); + blob_reset(&delta); } - blob_reset(&delta); } db_finalize(&q); } /* @@ -457,11 +463,11 @@ ** ** SUMMARY: ** fossil stash ** fossil stash save ?-m|--comment COMMENT? ?FILES...? ** fossil stash snapshot ?-m|--comment COMMENT? ?FILES...? -** fossil stash list|ls ?-v|--verbose? +** fossil stash list|ls ?-v|--verbose? ?-W|--width <num>? ** fossil stash show ?STASHID? ?DIFF-OPTIONS? ** fossil stash pop ** fossil stash apply ?STASHID? ** fossil stash goto ?STASHID? ** fossil stash rm|drop ?STASHID? ?-a|--all? @@ -470,17 +476,16 @@ void stash_cmd(void){ const char *zDb; const char *zCmd; int nCmd; int stashid = 0; - undo_capture_command_line(); db_must_be_within_tree(); db_open_config(0); db_begin_transaction(); zDb = db_name("localdb"); - db_multi_exec(zStashInit, zDb, zDb); + db_multi_exec(zStashInit /*works-like:"%w,%w"*/, zDb, zDb); if( g.argc<=2 ){ zCmd = "save"; }else{ zCmd = g.argv[2]; } @@ -510,20 +515,30 @@ if( memcmp(zCmd, "snapshot", nCmd)==0 ){ stash_create(); }else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){ Stmt q, q2; - int n = 0; + int n = 0, width; int verboseFlag = find_option("verbose","v",0)!=0; + const char *zWidth = find_option("width","W",1); + + if( zWidth ){ + width = atoi(zWidth); + if( (width!=0) && (width<=46) ){ + fossil_fatal("-W|--width value must be >46 or 0"); + } + }else{ + width = -1; + } if( !verboseFlag ){ verboseFlag = find_option("detail","l",0)!=0; /* deprecated */ } verify_all_options(); db_prepare(&q, "SELECT stashid, (SELECT uuid FROM blob WHERE rid=vid)," " comment, datetime(ctime) FROM stash" - " ORDER BY ctime DESC" + " ORDER BY ctime" ); if( verboseFlag ){ db_prepare(&q2, "SELECT isAdded, isRemoved, origname, newname" " FROM stashfile WHERE stashid=$id"); } @@ -537,11 +552,11 @@ db_column_text(&q, 3) ); zCom = db_column_text(&q, 2); if( zCom && zCom[0] ){ fossil_print(" "); - comment_print(zCom, 7, 79); + comment_print(zCom, 0, 7, width, g.comFmtFlags); } if( verboseFlag ){ db_bind_int(&q2, "$id", stashid); while( db_step(&q2)==SQLITE_ROW ){ int isAdded = db_column_int(&q2, 0); @@ -568,11 +583,10 @@ if( memcmp(zCmd, "drop", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0 ){ int allFlag = find_option("all", "a", 0)!=0; if( allFlag ){ Blob ans; char cReply; - blob_zero(&ans); prompt_user("This action is not undoable. Continue (y/N)? ", &ans); cReply = blob_str(&ans)[0]; if( cReply=='y' || cReply=='Y' ){ db_multi_exec("DELETE FROM stash; DELETE FROM stashfile;"); } @@ -609,11 +623,10 @@ undo_finish(); }else if( memcmp(zCmd, "goto", nCmd)==0 ){ int nConflict; int vid; - if( g.argc>4 ) usage("apply STASHID"); stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); undo_begin(); vid = db_int(0, "SELECT vid FROM stash WHERE stashid=%d", stashid); nConflict = update_to(vid); @@ -636,11 +649,11 @@ db_close(0); diff_tk((zCmd[0]=='s' ? "stash show" : "stash diff"), 3); return; } if( find_option("internal","i",0)==0 ){ - zDiffCmd = diff_command_external(0); + zDiffCmd = diff_command_external(memcmp(zCmd, "gdiff", nCmd)==0); } diffFlags = diff_options(); if( find_option("verbose","v",0)!=0 ) diffFlags |= DIFF_VERBOSE; if( g.argc>4 ) usage(mprintf("%s STASHID", zCmd)); if( zDiffCmd ){ Index: src/stat.c ================================================================== --- src/stat.c +++ src/stat.c @@ -16,19 +16,20 @@ ******************************************************************************* ** ** This file contains code to implement the stat web page ** */ -#include <string.h> +#include "VERSION.h" #include "config.h" +#include <string.h> #include "stat.h" /* ** For a sufficiently large integer, provide an alternative ** representation as MB or GB or TB. */ -static void bigSizeName(int nOut, char *zOut, sqlite3_int64 v){ +void bigSizeName(int nOut, char *zOut, sqlite3_int64 v){ if( v<100000 ){ sqlite3_snprintf(nOut, zOut, "%lld bytes", v); }else if( v<1000000000 ){ sqlite3_snprintf(nOut, zOut, "%lld bytes (%.1fMB)", v, (double)v/1000000.0); @@ -48,18 +49,24 @@ int n, m; int szMax, szAvg; const char *zDb; int brief; char zBuf[100]; + const char *p; login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } brief = P("brief")!=0; style_header("Repository Statistics"); + style_adunit_config(ADUNIT_RIGHT_OK); if( g.perm.Admin ){ style_submenu_element("URLs", "URLs and Checkouts", "urllist"); + style_submenu_element("Schema", "Repository Schema", "repo_schema"); + style_submenu_element("Web-Cache", "Web-Cache Stats", "cachestat"); } + style_submenu_element("Activity Reports", 0, "reports"); + style_submenu_element("SHA1 Collisions", 0, "hash-collisions"); @ <table class="label-value"> @ <tr><th>Repository Size:</th><td> fsize = file_size(g.zRepositoryName); bigSizeName(sizeof(zBuf), zBuf, fsize); @ %s(zBuf) @@ -73,11 +80,11 @@ if( n>0 ){ int a, b; Stmt q; @ <tr><th>Uncompressed Artifact Size:</th><td> db_prepare(&q, "SELECT total(size), avg(size), max(size)" - " FROM blob WHERE size>0"); + " FROM blob WHERE size>0 /*scan*/"); db_step(&q); t = db_column_int64(&q, 0); szAvg = db_column_int(&q, 1); szMax = db_column_int(&q, 2); db_finalize(&q); @@ -117,52 +124,76 @@ @ <tr><th>Duration Of Project:</th><td> n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)" " + 0.99"); @ %d(n) days or approximately %.2f(n/365.2425) years. @ </td></tr> - @ <tr><th>Project ID:</th><td>%h(db_get("project-code",""))</td></tr> + p = db_get("project-code", 0); + if( p ){ + @ <tr><th>Project ID:</th><td>%h(p)</td></tr> + } + @ <tr><th>Server ID:</th><td>%h(db_get("server-code",""))</td></tr> @ <tr><th>Fossil Version:</th><td> @ %h(MANIFEST_DATE) %h(MANIFEST_VERSION) @ (%h(RELEASE_VERSION)) [compiled using %h(COMPILER_NAME)] @ </td></tr> - @ <tr><th>SQLite Version:</th><td>%.19s(SQLITE_SOURCE_ID) - @ [%.10s(&SQLITE_SOURCE_ID[20])] (%s(SQLITE_VERSION))</td></tr> + @ <tr><th>SQLite Version:</th><td>%.19s(sqlite3_sourceid()) + @ [%.10s(&sqlite3_sourceid()[20])] (%s(sqlite3_libversion()))</td></tr> + @ <tr><th>Schema Version:</th><td>%h(g.zAuxSchema)</td></tr> + @ <tr><th>Repository Rebuilt:</th><td> + @ %h(db_get_mtime("rebuilt","%Y-%m-%d %H:%M:%S","Never")) + @ By Fossil %h(db_get("rebuilt","Unknown"))</td></tr> @ <tr><th>Database Stats:</th><td> zDb = db_name("repository"); - @ %d(db_int(0, "PRAGMA %s.page_count", zDb)) pages, - @ %d(db_int(0, "PRAGMA %s.page_size", zDb)) bytes/page, - @ %d(db_int(0, "PRAGMA %s.freelist_count", zDb)) free pages, - @ %s(db_text(0, "PRAGMA %s.encoding", zDb)), - @ %s(db_text(0, "PRAGMA %s.journal_mode", zDb)) mode + @ %d(db_int(0, "PRAGMA \"%w\".page_count", zDb)) pages, + @ %d(db_int(0, "PRAGMA \"%w\".page_size", zDb)) bytes/page, + @ %d(db_int(0, "PRAGMA \"%w\".freelist_count", zDb)) free pages, + @ %s(db_text(0, "PRAGMA \"%w\".encoding", zDb)), + @ %s(db_text(0, "PRAGMA \"%w\".journal_mode", zDb)) mode @ </td></tr> @ </table> style_footer(); } /* ** COMMAND: dbstat* ** -** Usage: %fossil dbstat ?-brief | -b? +** Usage: %fossil dbstat OPTIONS ** ** Shows statistics and global information about the repository. ** -** The (-brief|-b) option removes any "long-running" statistics, namely -** those whose calculations are known to slow down as the repository -** grows. +** Options: ** +** --brief|-b Only show essential elements +** --db-check Run a PRAGMA quick_check on the repository database +** --omit-version-info Omit the SQLite and Fossil version information */ void dbstat_cmd(void){ i64 t, fsize; int n, m; int szMax, szAvg; const char *zDb; int brief; + int omitVers; /* Omit Fossil and SQLite version information */ + int dbCheck; /* True for the --db-check option */ char zBuf[100]; const int colWidth = -19 /* printf alignment/width for left column */; + const char *p, *z; + brief = find_option("brief", "b",0)!=0; + omitVers = find_option("omit-version-info", 0, 0)!=0; + dbCheck = find_option("db-check",0,0)!=0; db_find_and_open_repository(0,0); + + /* We should be done with options.. */ + verify_all_options(); + + if( (z = db_get("project-name",0))!=0 + || (z = db_get("short-project-name",0))!=0 + ){ + fossil_print("%*s%s\n", colWidth, "project-name:", z); + } fsize = file_size(g.zRepositoryName); bigSizeName(sizeof(zBuf), zBuf, fsize); fossil_print( "%*s%s\n", colWidth, "repository-size:", zBuf ); if( !brief ){ n = db_int(0, "SELECT count(*) FROM blob"); @@ -193,52 +224,68 @@ } a = t/fsize; fossil_print("%*s%d:%d\n", colWidth, "compression-ratio:", a, b); } n = db_int(0, "SELECT COUNT(*) FROM event e WHERE e.type='ci'"); - fossil_print("%*s%d\n", colWidth, "checkins:", n); + fossil_print("%*s%d\n", colWidth, "check-ins:", n); n = db_int(0, "SELECT count(*) FROM filename /*scan*/"); fossil_print("%*s%d across all branches\n", colWidth, "files:", n); n = db_int(0, "SELECT count(*) FROM tag /*scan*/" " WHERE tagname GLOB 'wiki-*'"); m = db_int(0, "SELECT COUNT(*) FROM event WHERE type='w'"); - fossil_print("%*s%d (%d changes)\n", colWidth, "wikipages:", n, m); + fossil_print("%*s%d (%d changes)\n", colWidth, "wiki-pages:", n, m); n = db_int(0, "SELECT count(*) FROM tag /*scan*/" " WHERE tagname GLOB 'tkt-*'"); m = db_int(0, "SELECT COUNT(*) FROM event WHERE type='t'"); fossil_print("%*s%d (%d changes)\n", colWidth, "tickets:", n, m); n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='e'"); fossil_print("%*s%d\n", colWidth, "events:", n); n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='g'"); - fossil_print("%*s%d\n", colWidth, "tagchanges:", n); + fossil_print("%*s%d\n", colWidth, "tag-changes:", n); + z = db_text(0, "SELECT datetime(mtime) || ' - about ' ||" + " CAST(julianday('now') - mtime AS INTEGER)" + " || ' days ago' FROM event " + " ORDER BY mtime DESC LIMIT 1"); + fossil_print("%*s%s\n", colWidth, "latest-change:", z); } n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)" " + 0.99"); fossil_print("%*s%d days or approximately %.2f years.\n", colWidth, "project-age:", n, n/365.2425); - fossil_print("%*s%s\n", colWidth, "project-id:", db_get("project-code","")); - fossil_print("%*s%s %s [%s] (%s)\n", - colWidth, "fossil-version:", - MANIFEST_DATE, MANIFEST_VERSION, RELEASE_VERSION, - COMPILER_NAME); - fossil_print("%*s%.19s [%.10s] (%s)\n", - colWidth, "sqlite-version:", - SQLITE_SOURCE_ID, &SQLITE_SOURCE_ID[20], - SQLITE_VERSION); + p = db_get("project-code", 0); + if( p ){ + fossil_print("%*s%s\n", colWidth, "project-id:", p); + } +#if 0 + /* Server-id is not useful information any more */ + fossil_print("%*s%s\n", colWidth, "server-id:", db_get("server-code", 0)); +#endif + fossil_print("%*s%s\n", colWidth, "schema-version:", g.zAuxSchema); + if( !omitVers ){ + fossil_print("%*s%s %s [%s] (%s)\n", + colWidth, "fossil-version:", + MANIFEST_DATE, MANIFEST_VERSION, RELEASE_VERSION, + COMPILER_NAME); + fossil_print("%*s%.19s [%.10s] (%s)\n", + colWidth, "sqlite-version:", + sqlite3_sourceid(), &sqlite3_sourceid()[20], + sqlite3_libversion()); + } zDb = db_name("repository"); fossil_print("%*s%d pages, %d bytes/pg, %d free pages, " "%s, %s mode\n", colWidth, "database-stats:", - db_int(0, "PRAGMA %s.page_count", zDb), - db_int(0, "PRAGMA %s.page_size", zDb), - db_int(0, "PRAGMA %s.freelist_count", zDb), - db_text(0, "PRAGMA %s.encoding", zDb), - db_text(0, "PRAGMA %s.journal_mode", zDb)); - -} - - + db_int(0, "PRAGMA \"%w\".page_count", zDb), + db_int(0, "PRAGMA \"%w\".page_size", zDb), + db_int(0, "PRAGMA \"%w\".freelist_count", zDb), + db_text(0, "PRAGMA \"%w\".encoding", zDb), + db_text(0, "PRAGMA \"%w\".journal_mode", zDb)); + if( dbCheck ){ + fossil_print("%*s%s\n", colWidth, "database-check:", + db_text(0, "PRAGMA quick_check(1)")); + } +} /* ** WEBPAGE: urllist ** ** Show ways in which this repository has been accessed @@ -245,14 +292,16 @@ */ void urllist_page(void){ Stmt q; int cnt; login_check_credentials(); - if( !g.perm.Admin ){ login_needed(); return; } + if( !g.perm.Admin ){ login_needed(0); return; } style_header("URLs and Checkouts"); + style_adunit_config(ADUNIT_RIGHT_OK); style_submenu_element("Stat", "Repository Stats", "stat"); + style_submenu_element("Schema", "Repository Schema", "repo_schema"); @ <div class="section">URLs</div> @ <table border="0" width='100%%'> db_prepare(&q, "SELECT substr(name,9), datetime(mtime,'unixepoch')" " FROM config WHERE name GLOB 'baseurl:*' ORDER BY 2 DESC"); cnt = 0; @@ -281,5 +330,30 @@ @ <tr><td>(none)</td> } @ </table> style_footer(); } + +/* +** WEBPAGE: repo_schema +** +** Show the repository schema +*/ +void repo_schema_page(void){ + Stmt q; + login_check_credentials(); + if( !g.perm.Admin ){ login_needed(0); return; } + + style_header("Repository Schema"); + style_adunit_config(ADUNIT_RIGHT_OK); + style_submenu_element("Stat", "Repository Stats", "stat"); + style_submenu_element("URLs", "URLs and Checkouts", "urllist"); + db_prepare(&q, "SELECT sql FROM %s.sqlite_master WHERE sql IS NOT NULL", + db_name("repository")); + @ <pre> + while( db_step(&q)==SQLITE_ROW ){ + @ %h(db_column_text(&q, 0)); + } + @ </pre> + db_finalize(&q); + style_footer(); +} ADDED src/statrep.c Index: src/statrep.c ================================================================== --- /dev/null +++ src/statrep.c @@ -0,0 +1,774 @@ +/* +** Copyright (c) 2013 Stephen Beal +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code to implement the /reports web page. +** +*/ +#include "config.h" +#include <string.h> +#include <time.h> +#include "statrep.h" + + +/* +** Used by stats_report_xxxxx() to remember which type of events +** to show. Populated by stats_report_init_view() and holds the +** return value of that function. +*/ +static int statsReportType = 0; + +/* +** Set by stats_report_init_view() to one of the y=XXXX values +** accepted by /timeline?y=XXXX. +*/ +static const char *statsReportTimelineYFlag = NULL; + + +/* +** Generate a submenu element with a single parameter change. +*/ +static void statrep_submenu( + HQuery *pUrl, /* Base URL */ + const char *zMenuName, /* Submenu name */ + const char *zParam, /* Parameter value to add or change */ + const char *zValue, /* Value of the new parameter */ + const char *zRemove /* Parameter to omit */ +){ + style_submenu_element(zMenuName, zMenuName, "%s", + url_render(pUrl, zParam, zValue, zRemove, 0)); +} + +/* +** Creates a TEMP VIEW named v_reports which is a wrapper around the +** EVENT table filtered on event.type. It looks for the request +** parameter 'type' (reminder: we "should" use 'y' for consistency +** with /timeline, but /reports uses 'y' for the year) and expects it +** to contain one of the conventional values from event.type or the +** value "all", which is treated as equivalent to "*". By default (if +** no 'y' is specified), "*" is assumed (that is also the default for +** invalid/unknown filter values). That 'y' filter is the one used for +** the event list. Note that a filter of "*" or "all" is equivalent to +** querying against the full event table. The view, however, adds an +** abstraction level to simplify the implementation code for the +** various /reports pages. +** +** Returns one of: 'c', 'w', 'g', 't', 'e', representing the type of +** filter it applies, or '*' if no filter is applied (i.e. if "all" is +** used). +*/ +static int stats_report_init_view(){ + const char *zType = PD("type","*"); /* analog to /timeline?y=... */ + const char *zRealType = NULL; /* normalized form of zType */ + int rc = 0; /* result code */ + assert( !statsReportType && "Must not be called more than once." ); + switch( (zType && *zType) ? *zType : 0 ){ + case 'c': + case 'C': + zRealType = "ci"; + rc = *zRealType; + break; + case 'e': + case 'E': + zRealType = "e"; + rc = *zRealType; + break; + case 'g': + case 'G': + zRealType = "g"; + rc = *zRealType; + break; + case 't': + case 'T': + zRealType = "t"; + rc = *zRealType; + break; + case 'w': + case 'W': + zRealType = "w"; + rc = *zRealType; + break; + default: + rc = '*'; + break; + } + assert(0 != rc); + if(zRealType){ + statsReportTimelineYFlag = zRealType; + db_multi_exec("CREATE TEMP VIEW v_reports AS " + "SELECT * FROM event WHERE type GLOB %Q", + zRealType); + }else{ + statsReportTimelineYFlag = "a"; + db_multi_exec("CREATE TEMP VIEW v_reports AS " + "SELECT * FROM event"); + } + return statsReportType = rc; +} + +/* +** Returns a string suitable (for a given value of suitable) for +** use in a label with the header of the /reports pages, dependent +** on the 'type' flag. See stats_report_init_view(). +** The returned bytes are static. +*/ +static const char *stats_report_label_for_type(){ + assert( statsReportType && "Must call stats_report_init_view() first." ); + switch( statsReportType ){ + case 'c': + return "check-ins"; + case 'e': + return "technotes"; + case 'w': + return "wiki changes"; + case 't': + return "ticket changes"; + case 'g': + return "tag changes"; + default: + return "all types"; + } +} + +/* +** A helper for the /reports family of pages which prints out a menu +** of links for the various type=XXX flags. zCurrentViewName must be +** the name/value of the 'view' parameter which is in effect at the +** time this is called. e.g. if called from the 'byuser' view then +** zCurrentViewName must be "byuser". Any URL parameters which need to +** be added to the generated URLs should be passed in zParam. The +** caller is expected to have already encoded any zParam in the %T or +** %t encoding. */ +static void stats_report_event_types_menu(const char *zCurrentViewName, + const char *zParam){ + char *zTop; + if(zParam && !*zParam){ + zParam = NULL; + } + zTop = mprintf("%s/reports?view=%s%s%s", g.zTop, zCurrentViewName, + zParam ? "&" : "", zParam); + cgi_printf("<div>"); + cgi_printf("<span>Types:</span> "); + if('*' == statsReportType){ + cgi_printf(" <strong>all</strong>", zTop); + }else{ + cgi_printf(" <a href='%s'>all</a>", zTop); + } + if('c' == statsReportType){ + cgi_printf(" <strong>check-ins</strong>", zTop); + }else{ + cgi_printf(" <a href='%s&type=ci'>check-ins</a>", zTop); + } + if('e' == statsReportType){ + cgi_printf(" <strong>technotes</strong>", zTop); + }else{ + cgi_printf(" <a href='%s&type=e'>technotes</a>", zTop); + } + if( 't' == statsReportType ){ + cgi_printf(" <strong>tickets</strong>", zTop); + }else{ + cgi_printf(" <a href='%s&type=t'>tickets</a>", zTop); + } + if( 'g' == statsReportType ){ + cgi_printf(" <strong>tags</strong>", zTop); + }else{ + cgi_printf(" <a href='%s&type=g'>tags</a>", zTop); + } + if( 'w' == statsReportType ){ + cgi_printf(" <strong>wiki</strong>", zTop); + }else{ + cgi_printf(" <a href='%s&type=w'>wiki</a>", zTop); + } + fossil_free(zTop); + cgi_printf("</div>"); +} + + +/* +** Helper for stats_report_by_month_year(), which generates a list of +** week numbers. zTimeframe should be either a timeframe in the form YYYY +** or YYYY-MM. +*/ +static void stats_report_output_week_links(const char *zTimeframe){ + Stmt stWeek = empty_Stmt; + char yearPart[5] = {0,0,0,0,0}; + memcpy(yearPart, zTimeframe, 4); + db_prepare(&stWeek, + "SELECT DISTINCT strftime('%%W',mtime) AS wk, " + "count(*) AS n, " + "substr(date(mtime),1,%d) AS ym " + "FROM v_reports " + "WHERE ym=%Q AND mtime < current_timestamp " + "GROUP BY wk ORDER BY wk", + strlen(zTimeframe), + zTimeframe); + while( SQLITE_ROW == db_step(&stWeek) ){ + const char *zWeek = db_column_text(&stWeek,0); + const int nCount = db_column_int(&stWeek,1); + cgi_printf("<a href='%R/timeline?" + "yw=%t-%t&n=%d&y=%s'>%s</a>", + yearPart, zWeek, + nCount, statsReportTimelineYFlag, zWeek); + } + db_finalize(&stWeek); +} + +/* +** Implements the "byyear" and "bymonth" reports for /reports. +** If includeMonth is true then it generates the "bymonth" report, +** else the "byyear" report. If zUserName is not NULL and not empty +** then the report is restricted to events created by the named user +** account. +*/ +static void stats_report_by_month_year(char includeMonth, + char includeWeeks, + const char *zUserName){ + Stmt query = empty_Stmt; + int nRowNumber = 0; /* current TR number */ + int nEventTotal = 0; /* Total event count */ + int rowClass = 0; /* counter for alternating + row colors */ + Blob sql = empty_blob; /* SQL */ + const char *zTimeLabel = includeMonth ? "Year/Month" : "Year"; + char zPrevYear[5] = {0}; /* For keeping track of when + we change years while looping */ + int nEventsPerYear = 0; /* Total event count for the + current year */ + char showYearTotal = 0; /* Flag telling us when to show + the per-year event totals */ + Blob header = empty_blob; /* Page header text */ + int nMaxEvents = 1; /* for calculating length of graph + bars. */ + int iterations = 0; /* number of weeks/months we iterate + over */ + stats_report_init_view(); + stats_report_event_types_menu( includeMonth ? "bymonth" : "byyear", NULL ); + blob_appendf(&header, "Timeline Events (%s) by year%s", + stats_report_label_for_type(), + (includeMonth ? "/month" : "")); + blob_append_sql(&sql, + "SELECT substr(date(mtime),1,%d) AS timeframe, " + "count(*) AS eventCount " + "FROM v_reports ", + includeMonth ? 7 : 4); + if(zUserName&&*zUserName){ + blob_append_sql(&sql, " WHERE user=%Q ", zUserName); + blob_appendf(&header," for user %q", zUserName); + } + blob_append(&sql, + " GROUP BY timeframe" + " ORDER BY timeframe DESC", + -1); + db_prepare(&query, "%s", blob_sql_text(&sql)); + blob_reset(&sql); + @ <h1>%b(&header)</h1> + @ <table class='statistics-report-table-events' border='0' cellpadding='2' + @ cellspacing='0' id='statsTable'> + @ <thead> + @ <th>%s(zTimeLabel)</th> + @ <th>Events</th> + @ <th width='90%%'><!-- relative commits graph --></th> + @ </thead><tbody> + blob_reset(&header); + /* + Run the query twice. The first time we calculate the maximum + number of events for a given row. Maybe someone with better SQL + Fu can re-implement this with a single query. + */ + while( SQLITE_ROW == db_step(&query) ){ + const int nCount = db_column_int(&query, 1); + if(nCount>nMaxEvents){ + nMaxEvents = nCount; + } + ++iterations; + } + db_reset(&query); + while( SQLITE_ROW == db_step(&query) ){ + const char *zTimeframe = db_column_text(&query, 0); + const int nCount = db_column_int(&query, 1); + int nSize = nCount + ? (int)(100 * nCount / nMaxEvents) + : 1; + showYearTotal = 0; + if(!nSize) nSize = 1; + if(includeMonth){ + /* For Month/year view, add a separator for each distinct year. */ + if(!*zPrevYear || + (0!=fossil_strncmp(zPrevYear,zTimeframe,4))){ + showYearTotal = *zPrevYear; + if(showYearTotal){ + rowClass = ++nRowNumber % 2; + @ <tr class='row%d(rowClass)'> + @ <td></td> + @ <td colspan='2'>Yearly total: %d(nEventsPerYear)</td> + @</tr> + } + nEventsPerYear = 0; + memcpy(zPrevYear,zTimeframe,4); + rowClass = ++nRowNumber % 2; + @ <tr class='row%d(rowClass)'> + @ <th colspan='3' class='statistics-report-row-year'>%s(zPrevYear)</th> + @ </tr> + } + } + rowClass = ++nRowNumber % 2; + nEventTotal += nCount; + nEventsPerYear += nCount; + @<tr class='row%d(rowClass)'> + @ <td> + if(includeMonth){ + cgi_printf("<a href='%R/timeline?" + "ym=%t&n=%d&y=%s", + zTimeframe, nCount, + statsReportTimelineYFlag ); + /* Reminder: n=nCount is not actually correct for bymonth unless + that was the only user who caused events. + */ + if( zUserName && *zUserName ){ + cgi_printf("&u=%t", zUserName); + } + cgi_printf("' target='_new'>%s</a>",zTimeframe); + }else { + cgi_printf("<a href='?view=byweek&y=%s&type=%c", + zTimeframe, (char)statsReportType); + if(zUserName && *zUserName){ + cgi_printf("&u=%t", zUserName); + } + cgi_printf("'>%s</a>", zTimeframe); + } + @ </td><td>%d(nCount)</td> + @ <td> + @ <div class='statistics-report-graph-line' + @ style='width:%d(nSize)%%;'> </div> + @ </td> + @</tr> + if(includeWeeks){ + /* This part works fine for months but it terribly slow (4.5s on my PC), + so it's only shown for by-year for now. Suggestions/patches for + a better/faster layout are welcomed. */ + @ <tr class='row%d(rowClass)'> + @ <td colspan='2' class='statistics-report-week-number-label'>Week #:</td> + @ <td class='statistics-report-week-of-year-list'> + stats_report_output_week_links(zTimeframe); + @ </td></tr> + } + + /* + Potential improvement: calculate the min/max event counts and + use percent-based graph bars. + */ + } + db_finalize(&query); + if(includeMonth && !showYearTotal && *zPrevYear){ + /* Add final year total separator. */ + rowClass = ++nRowNumber % 2; + @ <tr class='row%d(rowClass)'> + @ <td></td> + @ <td colspan='2'>Yearly total: %d(nEventsPerYear)</td> + @</tr> + } + @ </tbody></table> + if(nEventTotal){ + const char *zAvgLabel = includeMonth ? "month" : "year"; + int nAvg = iterations ? (nEventTotal/iterations) : 0; + @ <br><div>Total events: %d(nEventTotal) + @ <br>Average per active %s(zAvgLabel): %d(nAvg) + @ </div> + } + if( !includeMonth ){ + output_table_sorting_javascript("statsTable","tnx",-1); + } +} + +/* +** Implements the "byuser" view for /reports. +*/ +static void stats_report_by_user(){ + Stmt query = empty_Stmt; + int nRowNumber = 0; /* current TR number */ + int nEventTotal = 0; /* Total event count */ + int rowClass = 0; /* counter for alternating + row colors */ + int nMaxEvents = 1; /* max number of events for + all rows. */ + stats_report_init_view(); + stats_report_event_types_menu("byuser", NULL); + db_prepare(&query, + "SELECT user, " + "COUNT(*) AS eventCount " + "FROM v_reports " + "GROUP BY user ORDER BY eventCount DESC"); + @ <h1>Timeline Events + @ (%s(stats_report_label_for_type())) by User</h1> + @ <table class='statistics-report-table-events' border='0' + @ cellpadding='2' cellspacing='0' id='statsTable'> + @ <thead><tr> + @ <th>User</th> + @ <th>Events</th> + @ <th width='90%%'><!-- relative commits graph --></th> + @ </tr></thead><tbody> + while( SQLITE_ROW == db_step(&query) ){ + const int nCount = db_column_int(&query, 1); + if(nCount>nMaxEvents){ + nMaxEvents = nCount; + } + } + db_reset(&query); + while( SQLITE_ROW == db_step(&query) ){ + const char *zUser = db_column_text(&query, 0); + const int nCount = db_column_int(&query, 1); + int nSize = nCount + ? (int)(100 * nCount / nMaxEvents) + : 0; + if(!nCount) continue /* arguable! Possible? */; + else if(!nSize) nSize = 1; + rowClass = ++nRowNumber % 2; + nEventTotal += nCount; + @<tr class='row%d(rowClass)'> + @ <td> + @ <a href="?view=bymonth&user=%h(zUser)&type=%c((char)statsReportType)">%h(zUser)</a> + @ </td><td data-sortkey='%08x(-nCount)'>%d(nCount)</td> + @ <td> + @ <div class='statistics-report-graph-line' + @ style='width:%d(nSize)%%;'> </div> + @ </td> + @</tr> + /* + Potential improvement: calculate the min/max event counts and + use percent-based graph bars. + */ + } + @ </tbody></table> + db_finalize(&query); + output_table_sorting_javascript("statsTable","tkx",2); +} + +/* +** Implements the "byfile" view for /reports. +*/ +static void stats_report_by_file(){ + Stmt query; + int mxEvent = 1; /* max number of events across all rows */ + int nRowNumber = 0; + + db_multi_exec( + "CREATE TEMP TABLE statrep(filename, cnt);" + "INSERT INTO statrep(filename, cnt)" + " SELECT filename.name, count(distinct mlink.mid)" + " FROM filename, mlink" + " WHERE filename.fnid=mlink.fnid" + " GROUP BY 1" + ); + db_prepare(&query, + "SELECT filename, cnt FROM statrep ORDER BY cnt DESC, filename /*sort*/" + ); + mxEvent = db_int(1, "SELECT max(cnt) FROM statrep"); + @ <h1>Check-ins Per File</h1> + @ <table class='statistics-report-table-events' border='0' + @ cellpadding='2' cellspacing='0' id='statsTable'> + @ <thead><tr> + @ <th>File</th> + @ <th>Check-ins</th> + @ <th width='90%%'><!-- relative commits graph --></th> + @ </tr></thead><tbody> + while( SQLITE_ROW == db_step(&query) ){ + const char *zFile = db_column_text(&query, 0); + const int n = db_column_int(&query, 1); + int sz; + if( n<=0 ) continue; + sz = (int)(100*n/mxEvent); + if( sz==0 ) sz = 1; + @<tr class='row%d(++nRowNumber%2)'> + @ <td>%z(href("%R/finfo?name=%T",zFile))%h(zFile)</a></td> + @ <td>%d(n)</td> + @ <td> + @ <div class='statistics-report-graph-line' + @ style='width:%d(sz)%%;'> </div> + @ </td> + @</tr> + } + @ </tbody></table> + db_finalize(&query); + output_table_sorting_javascript("statsTable","tNx",2); +} + +/* +** Implements the "byweekday" view for /reports. +*/ +static void stats_report_day_of_week(){ + Stmt query = empty_Stmt; + int nRowNumber = 0; /* current TR number */ + int nEventTotal = 0; /* Total event count */ + int rowClass = 0; /* counter for alternating + row colors */ + int nMaxEvents = 1; /* max number of events for + all rows. */ + static const char *const daysOfWeek[] = { + "Monday", "Tuesday", "Wednesday", "Thursday", + "Friday", "Saturday", "Sunday" + }; + + stats_report_init_view(); + stats_report_event_types_menu("byweekday", NULL); + db_prepare(&query, + "SELECT cast(mtime %% 7 AS INTEGER) dow, " + "COUNT(*) AS eventCount " + "FROM v_reports " + "GROUP BY dow ORDER BY dow"); + @ <h1>Timeline Events + @ (%s(stats_report_label_for_type())) by Day of the Week</h1> + @ <table class='statistics-report-table-events' border='0' + @ cellpadding='2' cellspacing='0' id='statsTable'> + @ <thead><tr> + @ <th>DoW</th> + @ <th>Day</th> + @ <th>Events</th> + @ <th width='90%%'><!-- relative commits graph --></th> + @ </tr></thead><tbody> + while( SQLITE_ROW == db_step(&query) ){ + const int nCount = db_column_int(&query, 1); + if(nCount>nMaxEvents){ + nMaxEvents = nCount; + } + } + db_reset(&query); + while( SQLITE_ROW == db_step(&query) ){ + const int dayNum =db_column_int(&query, 0); + const int nCount = db_column_int(&query, 1); + int nSize = nCount + ? (int)(100 * nCount / nMaxEvents) + : 0; + if(!nCount) continue /* arguable! Possible? */; + else if(!nSize) nSize = 1; + rowClass = ++nRowNumber % 2; + nEventTotal += nCount; + @<tr class='row%d(rowClass)'> + @ <td>%d(dayNum)</td> + @ <td>%s(daysOfWeek[dayNum])</td> + @ <td>%d(nCount)</td> + @ <td> + @ <div class='statistics-report-graph-line' + @ style='width:%d(nSize)%%;'> </div> + @ </td> + @</tr> + } + @ </tbody></table> + db_finalize(&query); + output_table_sorting_javascript("statsTable","ntnx",1); +} + + +/* +** Helper for stats_report_by_month_year(), which generates a list of +** week numbers. zTimeframe should be either a timeframe in the form YYYY +** or YYYY-MM. +*/ +static void stats_report_year_weeks(const char *zUserName){ + const char *zYear = P("y"); + int nYear = zYear ? strlen(zYear) : 0; + int i = 0; + Stmt qYears = empty_Stmt; + char *zDefaultYear = NULL; + Blob sql = empty_blob; + int nMaxEvents = 1; /* max number of events for + all rows. */ + int iterations = 0; /* # of active time periods. */ + stats_report_init_view(); + if(4==nYear){ + Blob urlParams = empty_blob; + blob_appendf(&urlParams, "y=%T", zYear); + stats_report_event_types_menu("byweek", blob_str(&urlParams)); + blob_reset(&urlParams); + }else{ + stats_report_event_types_menu("byweek", NULL); + } + blob_append(&sql, + "SELECT DISTINCT substr(date(mtime),1,4) AS y " + "FROM v_reports WHERE 1 ", -1); + if(zUserName&&*zUserName){ + blob_append_sql(&sql,"AND user=%Q ", zUserName); + } + blob_append(&sql,"GROUP BY y ORDER BY y", -1); + db_prepare(&qYears, "%s", blob_sql_text(&sql)); + blob_reset(&sql); + cgi_printf("Select year: "); + while( SQLITE_ROW == db_step(&qYears) ){ + const char *zT = db_column_text(&qYears, 0); + if( i++ ){ + cgi_printf(" "); + } + cgi_printf("<a href='?view=byweek&y=%s&type=%c", zT, + (char)statsReportType); + if(zUserName && *zUserName){ + cgi_printf("&user=%t",zUserName); + } + cgi_printf("'>%s</a>",zT); + } + db_finalize(&qYears); + cgi_printf("<br/>"); + if(!zYear || !*zYear){ + zDefaultYear = db_text("????", "SELECT strftime('%%Y')"); + zYear = zDefaultYear; + nYear = 4; + } + if(4 == nYear){ + Stmt stWeek = empty_Stmt; + int rowCount = 0; + int total = 0; + Blob header = empty_blob; + blob_appendf(&header, "Timeline events (%s) for the calendar weeks " + "of %h", stats_report_label_for_type(), + zYear); + blob_append_sql(&sql, + "SELECT DISTINCT strftime('%%W',mtime) AS wk, " + "count(*) AS n " + "FROM v_reports " + "WHERE %Q=substr(date(mtime),1,4) " + "AND mtime < current_timestamp ", + zYear); + if(zUserName&&*zUserName){ + blob_append_sql(&sql, " AND user=%Q ", zUserName); + blob_appendf(&header," for user %h", zUserName); + } + blob_append_sql(&sql, "GROUP BY wk ORDER BY wk DESC"); + cgi_printf("<h1>%h</h1>", blob_str(&header)); + blob_reset(&header); + cgi_printf("<table class='statistics-report-table-events' " + "border='0' cellpadding='2' width='100%%' " + "cellspacing='0' id='statsTable'>"); + cgi_printf("<thead><tr>" + "<th>Week</th>" + "<th>Events</th>" + "<th width='90%%'><!-- relative commits graph --></th>" + "</tr></thead>" + "<tbody>"); + db_prepare(&stWeek, "%s", blob_sql_text(&sql)); + blob_reset(&sql); + while( SQLITE_ROW == db_step(&stWeek) ){ + const int nCount = db_column_int(&stWeek, 1); + if(nCount>nMaxEvents){ + nMaxEvents = nCount; + } + ++iterations; + } + db_reset(&stWeek); + while( SQLITE_ROW == db_step(&stWeek) ){ + const char *zWeek = db_column_text(&stWeek,0); + const int nCount = db_column_int(&stWeek,1); + int nSize = nCount + ? (int)(100 * nCount / nMaxEvents) + : 0; + if(!nSize) nSize = 1; + total += nCount; + cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); + cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", + zYear, zWeek, nCount, + statsReportTimelineYFlag); + if(zUserName && *zUserName){ + cgi_printf("&u=%t",zUserName); + } + cgi_printf("'>%s</a></td>",zWeek); + + cgi_printf("<td>%d</td>",nCount); + cgi_printf("<td>"); + if(nCount){ + cgi_printf("<div class='statistics-report-graph-line'" + "style='width:%d%%;'> </div>", + nSize); + } + cgi_printf("</td></tr>"); + } + db_finalize(&stWeek); + free(zDefaultYear); + cgi_printf("</tbody></table>"); + if(total){ + int nAvg = iterations ? (total/iterations) : 0; + cgi_printf("<br><div>Total events: %d<br>" + "Average per active week: %d</div>", + total, nAvg); + } + output_table_sorting_javascript("statsTable","tnx",-1); + } +} + +/* +** WEBPAGE: reports +** +** Shows activity reports for the repository. +** +** Query Parameters: +** +** view=REPORT_NAME Valid values: bymonth, byyear, byuser +** user=NAME Restricts statistics to the given user +** type=TYPE Restricts the report to a specific event type: +** ci (check-in), w (wiki), t (ticket), g (tag) +** Defaulting to all event types. +** +** The view-specific query parameters include: +** +** view=byweek: +** +** y=YYYY The year to report (default is the server's +** current year). +*/ +void stats_report_page(){ + HQuery url; /* URL for various branch links */ + const char *zView = P("view"); /* Which view/report to show. */ + const char *zUserName = P("user"); + + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + if(!zUserName) zUserName = P("u"); + url_initialize(&url, "reports"); + if(zUserName && *zUserName){ + url_add_parameter(&url,"user", zUserName); + statrep_submenu(&url, "(Remove User Flag)", "view", zView, "user"); + } + statrep_submenu(&url, "By Year", "view", "byyear", 0); + statrep_submenu(&url, "By Month", "view", "bymonth", 0); + statrep_submenu(&url, "By Week", "view", "byweek", 0); + statrep_submenu(&url, "By Weekday", "view", "byweekday", 0); + statrep_submenu(&url, "By User", "view", "byuser", "user"); + statrep_submenu(&url, "By File", "view", "byfile", "file"); + style_submenu_element("Stats", "Stats", "%R/stat"); + url_reset(&url); + style_header("Activity Reports"); + if(0==fossil_strcmp(zView,"byyear")){ + stats_report_by_month_year(0, 0, zUserName); + }else if(0==fossil_strcmp(zView,"bymonth")){ + stats_report_by_month_year(1, 0, zUserName); + }else if(0==fossil_strcmp(zView,"byweek")){ + stats_report_year_weeks(zUserName); + }else if(0==fossil_strcmp(zView,"byuser")){ + stats_report_by_user(); + }else if(0==fossil_strcmp(zView,"byweekday")){ + stats_report_day_of_week(); + }else if(0==fossil_strcmp(zView,"byfile")){ + stats_report_by_file(); + }else{ + @ <h1>Activity Reports:</h1> + @ <ul> + @ <li>%z(href("?view=byyear"))Events by year</a></li> + @ <li>%z(href("?view=bymonth"))Events by month</a></li> + @ <li>%z(href("?view=byweek"))Events by calendar week</a></li> + @ <li>%z(href("?view=byweekday"))Events by day of the week</a></li> + @ <li>%z(href("?view=byuser"))Events by user</a></li> + @ <li>%z(href("?view=byfile"))Events by file</a></li> + @ </ul> + } + + style_footer(); +} Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -16,27 +16,49 @@ ******************************************************************************* ** ** This file contains code to implement the basic web page look and feel. ** */ +#include "VERSION.h" #include "config.h" #include "style.h" /* ** Elements of the submenu are collected into the following -** structure and displayed below the main menu by style_header(). +** structure and displayed below the main menu. +** +** Populate these structure with calls to +** +** style_submenu_element() +** style_submenu_entry() +** style_submenu_checkbox() +** style_submenu_multichoice() ** -** Populate this structure with calls to style_submenu_element() -** prior to calling style_header(). +** prior to calling style_footer(). The style_footer() routine +** will generate the appropriate HTML text just below the main +** menu. */ static struct Submenu { - const char *zLabel; + const char *zLabel; /* Button label */ const char *zTitle; - const char *zLink; + const char *zLink; /* Jump to this link when button is pressed */ } aSubmenu[30]; -static int nSubmenu = 0; +static int nSubmenu = 0; /* Number of buttons */ +static struct SubmenuCtrl { + const char *zName; /* Form query parameter */ + const char *zLabel; /* Label. Might be NULL for FF_MULTI */ + unsigned char eType; /* FF_ENTRY, FF_MULTI, FF_BINARY */ + unsigned char isDisabled; /* True if this control is grayed out */ + short int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */ + const char **azChoice; /* value/display pairs for FF_MULTI */ + const char *zFalse; /* FF_BINARY label when false */ +} aSubmenuCtrl[20]; +static int nSubmenuCtrl = 0; +#define FF_ENTRY 1 +#define FF_MULTI 2 +#define FF_BINARY 3 /* ** Remember that the header has been generated. The footer is omitted ** if an error occurs before the header. */ @@ -45,10 +67,15 @@ /* ** remember, if a sidebox was used */ static int sideboxUsed = 0; +/* +** Ad-unit styles. +*/ +static unsigned adUnitFlags = 0; + /* ** List of hyperlinks and forms that need to be resolved by javascript in ** the footer. */ @@ -163,12 +190,11 @@ void style_resolve_href(void){ int i; int nDelay = db_get_int("auto-hyperlink-delay",10); if( !g.perm.Hyperlink ) return; if( nHref==0 && nFormAction==0 ) return; - @ <script type="text/JavaScript"> - @ /* <![CDATA[ */ + @ <script> @ function setAllHrefs(){ if( g.javascriptHyperlink ){ for(i=0; i<nHref; i++){ @ gebi("a%d(i+1)").href="%s(aHref[i])"; } @@ -175,28 +201,31 @@ } for(i=0; i<nFormAction; i++){ @ gebi("form%d(i+1)").action="%s(aFormAction[i])"; } @ } - if( strglob("*Opera Mini/[1-9]*", P("HTTP_USER_AGENT")) ){ + if( sqlite3_strglob("*Opera Mini/[1-9]*", P("HTTP_USER_AGENT"))==0 ){ /* Special case for Opera Mini, which executes JS server-side */ @ var isOperaMini = Object.prototype.toString.call(window.operamini) @ === "[object OperaMini]"; @ if( isOperaMini ){ @ setTimeout("setAllHrefs();",%d(nDelay)); @ } + }else if( db_get_boolean("auto-hyperlink-ishuman",0) && g.isHuman ){ + /* Active hyperlinks after a delay */ + @ setTimeout("setAllHrefs();",%d(nDelay)); }else if( db_get_boolean("auto-hyperlink-mouseover",0) ){ - /* Require mouse movement prior to activating hyperlinks */ + /* Require mouse movement before starting the teim that will + ** activating hyperlinks */ @ document.getElementsByTagName("body")[0].onmousemove=function(){ @ setTimeout("setAllHrefs();",%d(nDelay)); @ this.onmousemove = null; @ } }else{ - /* Active hyperlinks right away */ + /* Active hyperlinks after a delay */ @ setTimeout("setAllHrefs();",%d(nDelay)); } - @ /* ]]> */ @ </script> } /* ** Add a new element to the submenu @@ -208,16 +237,59 @@ ... ){ va_list ap; assert( nSubmenu < sizeof(aSubmenu)/sizeof(aSubmenu[0]) ); aSubmenu[nSubmenu].zLabel = zLabel; - aSubmenu[nSubmenu].zTitle = zTitle; + aSubmenu[nSubmenu].zTitle = zTitle ? zTitle : zLabel; va_start(ap, zLink); aSubmenu[nSubmenu].zLink = vmprintf(zLink, ap); va_end(ap); nSubmenu++; } +void style_submenu_entry( + const char *zName, /* Query parameter name */ + const char *zLabel, /* Label before the entry box */ + int iSize, /* Size of the entry box */ + int isDisabled /* True if disabled */ +){ + assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); + aSubmenuCtrl[nSubmenuCtrl].zName = zName; + aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel; + aSubmenuCtrl[nSubmenuCtrl].iSize = iSize; + aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled; + aSubmenuCtrl[nSubmenuCtrl].eType = FF_ENTRY; + nSubmenuCtrl++; +} +void style_submenu_binary( + const char *zName, /* Query parameter name */ + const char *zTrue, /* Label to show when parameter is true */ + const char *zFalse, /* Label to show when the parameter is false */ + int isDisabled /* True if this control is disabled */ +){ + assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); + aSubmenuCtrl[nSubmenuCtrl].zName = zName; + aSubmenuCtrl[nSubmenuCtrl].zLabel = zTrue; + aSubmenuCtrl[nSubmenuCtrl].zFalse = zFalse; + aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled; + aSubmenuCtrl[nSubmenuCtrl].eType = FF_BINARY; + nSubmenuCtrl++; +} +void style_submenu_multichoice( + const char *zName, /* Query parameter name */ + int nChoice, /* Number of options */ + const char **azChoice, /* value/display pairs. 2*nChoice entries */ + int isDisabled /* True if this control is disabled */ +){ + assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); + aSubmenuCtrl[nSubmenuCtrl].zName = zName; + aSubmenuCtrl[nSubmenuCtrl].iSize = nChoice; + aSubmenuCtrl[nSubmenuCtrl].azChoice = azChoice; + aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled; + aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI; + nSubmenuCtrl++; +} + /* ** Compare two submenu items for sorting purposes */ static int submenuCompare(const void *a, const void *b){ @@ -243,18 +315,47 @@ va_start(ap, zFormat); local_zCurrentPage = vmprintf(zFormat, ap); va_end(ap); } } + +/* +** Create a TH1 variable containing the URL for the specified config resource. +** The resulting variable name will be of the form $[zVarPrefix]_url. +*/ +static void url_var( + const char *zVarPrefix, + const char *zConfigName, + const char *zPageName +){ + char *zVarName = mprintf("%s_url", zVarPrefix); + char *zUrl = mprintf("%s/%s?id=%x", g.zTop, zPageName, + skin_id(zConfigName)); + Th_Store(zVarName, zUrl); + free(zUrl); + free(zVarName); +} + +/* +** Create a TH1 variable containing the URL for the specified config image. +** The resulting variable name will be of the form $[zImageName]_image_url. +*/ +static void image_url_var(const char *zImageName){ + char *zVarPrefix = mprintf("%s_image", zImageName); + char *zConfigName = mprintf("%s-image", zImageName); + url_var(zVarPrefix, zConfigName, zImageName); + free(zVarPrefix); + free(zConfigName); +} /* ** Draw the header. */ void style_header(const char *zTitleFormat, ...){ va_list ap; char *zTitle; - const char *zHeader = db_get("header", (char*)zDefaultHeader); + const char *zHeader = skin_get("header"); login_check_credentials(); va_start(ap, zTitleFormat); zTitle = vmprintf(zTitleFormat, ap); va_end(ap); @@ -267,19 +368,24 @@ /* Generate the header up through the main menu */ Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); Th_Store("title", zTitle); Th_Store("baseurl", g.zBaseURL); + Th_Store("secureurl", login_wants_https_redirect()? g.zHttpsURL: g.zBaseURL); Th_Store("home", g.zTop); Th_Store("index_page", db_get("index-page","/home")); - Th_Store("current_page", local_zCurrentPage ? local_zCurrentPage : g.zPath); + if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath); + Th_Store("current_page", local_zCurrentPage); Th_Store("csrf_token", g.zCsrfToken); Th_Store("release_version", RELEASE_VERSION); Th_Store("manifest_version", MANIFEST_VERSION); Th_Store("manifest_date", MANIFEST_DATE); Th_Store("compiler_name", COMPILER_NAME); - if( g.zLogin ){ + url_var("stylesheet", "css", "style.css"); + image_url_var("logo"); + image_url_var("background"); + if( !login_is_nobody() ){ Th_Store("login", g.zLogin); } if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br />\n", -1); Th_Render(zHeader); if( g.thTrace ) Th_Trace("END_HEADER<br />\n", -1); @@ -298,65 +404,183 @@ ** HEAD code can make use of it, but because the client can replace ** the HEAD, and some fossil pages rely on gebi(), we put it here. */ @ <script> @ function gebi(x){ - @ if(/^#/.test(x)) x = x.substr(1); + @ if(x.substr(0,1)=='#') x = x.substr(1); @ var e = document.getElementById(x); - @ if(!e) throw new Error("Expecting element with ID "+x); + @ if(!e) throw new Error('Expecting element with ID '+x); @ else return e;} @ </script> } + +#if INTERFACE +/* Allowed parameters for style_adunit() */ +#define ADUNIT_OFF 0x0001 /* Do not allow ads on this page */ +#define ADUNIT_RIGHT_OK 0x0002 /* Right-side vertical ads ok here */ +#endif + +/* +** Various page implementations can invoke this interface to let the +** style manager know what kinds of ads are appropriate for this page. +*/ +void style_adunit_config(unsigned int mFlags){ + adUnitFlags = mFlags; +} /* -** Append ad unit text if appropriate. +** Return the text of an ad-unit, if one should be rendered. Return +** NULL if no ad-unit is desired. +** +** The *pAdFlag value might be set to ADUNIT_RIGHT_OK if this is +** a right-hand vertical ad. */ -static void style_ad_unit(void){ - const char *zAd; +static const char *style_adunit_text(unsigned int *pAdFlag){ + const char *zAd = 0; + *pAdFlag = 0; + if( adUnitFlags & ADUNIT_OFF ) return 0; /* Disallow ads on this page */ if( g.perm.Admin && db_get_boolean("adunit-omit-if-admin",0) ){ - return; - } - if( g.zLogin && strcmp(g.zLogin,"anonymous")!=0 - && db_get_boolean("adunit-omit-if-user",0) ){ - return; - } - zAd = db_get("adunit", 0); - if( zAd ) cgi_append_content(zAd, -1); + return 0; + } + if( !login_is_nobody() + && fossil_strcmp(g.zLogin,"anonymous")!=0 + && db_get_boolean("adunit-omit-if-user",0) + ){ + return 0; + } + if( (adUnitFlags & ADUNIT_RIGHT_OK)!=0 + && !fossil_all_whitespace(zAd = db_get("adunit-right", 0)) + && !cgi_body_contains("<table") + ){ + *pAdFlag = ADUNIT_RIGHT_OK; + return zAd; + }else if( !fossil_all_whitespace(zAd = db_get("adunit",0)) ){ + return zAd; + } + return 0; } /* ** Draw the footer at the bottom of the page. */ void style_footer(void){ const char *zFooter; + const char *zAd = 0; + unsigned int mAdFlags = 0; if( !headerHasBeenGenerated ) return; /* Go back and put the submenu at the top of the page. We delay the ** creation of the submenu until the end so that we can add elements ** to the submenu while generating page text. */ cgi_destination(CGI_HEADER); - if( nSubmenu>0 ){ + if( nSubmenu+nSubmenuCtrl>0 ){ int i; + if( nSubmenuCtrl ){ + cgi_printf("<form id='f01' method='GET' action='%R/%s'>", g.zPath); + } @ <div class="submenu"> - qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare); - for(i=0; i<nSubmenu; i++){ - struct Submenu *p = &aSubmenu[i]; - if( p->zLink==0 ){ - @ <span class="label">%h(p->zLabel)</span> - }else{ - @ <a class="label" href="%h(p->zLink)">%h(p->zLabel)</a> + if( nSubmenu>0 ){ + qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare); + for(i=0; i<nSubmenu; i++){ + struct Submenu *p = &aSubmenu[i]; + if( p->zLink==0 ){ + @ <span class="label">%h(p->zLabel)</span> + }else{ + @ <a class="label" href="%h(p->zLink)">%h(p->zLabel)</a> + } + } + } + if( nSubmenuCtrl>0 ){ + for(i=0; i<nSubmenuCtrl; i++){ + const char *zQPN = aSubmenuCtrl[i].zName; + const char *zDisabled = " disabled"; + if( !aSubmenuCtrl[i].isDisabled ){ + zDisabled = ""; + cgi_tag_query_parameter(zQPN); + } + switch( aSubmenuCtrl[i].eType ){ + case FF_ENTRY: { + cgi_printf( + "<span class='submenuctrl'>" + " %h<input type='text' name='%s' size='%d' maxlength='%d'" + " value='%h'%s></span>\n", + aSubmenuCtrl[i].zLabel, + zQPN, + aSubmenuCtrl[i].iSize, aSubmenuCtrl[i].iSize, + PD(zQPN,""), + zDisabled + ); + break; + } + case FF_MULTI: { + int j; + const char *zVal = P(zQPN); + cgi_printf( + "<select class='submenuctrl' size='1' name='%s'%s " + "onchange='gebi(\"f01\").submit();'>\n", + zQPN, zDisabled + ); + for(j=0; j<aSubmenuCtrl[i].iSize*2; j+=2){ + const char *zQPV = aSubmenuCtrl[i].azChoice[j]; + cgi_printf( + "<option value='%h'%s>%h</option>\n", + zQPV, + fossil_strcmp(zVal,zQPV)==0 ? " selected" : "", + aSubmenuCtrl[i].azChoice[j+1] + ); + } + @ </select> + break; + } + case FF_BINARY: { + int isTrue = PB(zQPN); + cgi_printf( + "<select class='submenuctrl' size='1' name='%s'%s " + "onchange='gebi(\"f01\").submit();'>\n", + zQPN, zDisabled + ); + cgi_printf( + "<option value='1'%s>%h</option>\n", + isTrue ? " selected":"", aSubmenuCtrl[i].zLabel + ); + cgi_printf( + "<option value='0'%s>%h</option>\n", + (!isTrue) ? " selected":"", aSubmenuCtrl[i].zFalse + ); + @ </select> + break; + } + } } } @ </div> + if( nSubmenuCtrl ){ + cgi_query_parameters_to_hidden(); + cgi_tag_query_parameter(0); + @ </form> + } } - style_ad_unit(); - @ <div class="content"> + + zAd = style_adunit_text(&mAdFlags); + if( (mAdFlags & ADUNIT_RIGHT_OK)!=0 ){ + @ <div class="content adunit_right_container"> + @ <div class="adunit_right"> + cgi_append_content(zAd, -1); + @ </div> + }else{ + if( zAd ){ + @ <div class="adunit_banner"> + cgi_append_content(zAd, -1); + @ </div> + } + @ <div class="content"> + } cgi_destination(CGI_BODY); - if (sideboxUsed) { + if( sideboxUsed ){ /* Put the footer at the bottom of the page. ** the additional clear/both is needed to extend the content ** part to the end of an optional sidebox. */ @ <div class="endContent"></div> @@ -365,11 +589,11 @@ /* Set the href= field on hyperlinks. Do this before the footer since ** the footer will be generating </html> */ style_resolve_href(); - zFooter = db_get("footer", (char*)zDefaultFooter); + zFooter = skin_get("footer"); if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1); Th_Render(zFooter); if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1); /* Render trace log if TH1 tracing is enabled. */ @@ -395,244 +619,20 @@ */ void style_sidebox_end(void){ @ </div> } -/* @-comment: // */ -/* -** The default page header. -*/ -const char zDefaultHeader[] = -@ <html> -@ <head> -@ <base href="$baseurl/$current_page" /> -@ <title>$<project_name>: $<title> -@ -@ -@ -@ -@
            -@ -@
            $
            $</div> -@ <div class="status"><th1> -@ if {[info exists login]} { -@ puts "Logged in as $login" -@ } else { -@ puts "Not logged in" -@ } -@ </th1></div> -@ </div> -@ <div class="mainmenu"> -@ <th1> -@ html "<a href='$home$index_page'>Home</a>\n" -@ if {[anycap jor]} { -@ html "<a href='$home/timeline'>Timeline</a>\n" -@ } -@ if {[hascap oh]} { -@ html "<a href='$home/dir?ci=tip'>Files</a>\n" -@ } -@ if {[hascap o]} { -@ html "<a href='$home/brlist'>Branches</a>\n" -@ html "<a href='$home/taglist'>Tags</a>\n" -@ } -@ if {[hascap r]} { -@ html "<a href='$home/reportlist'>Tickets</a>\n" -@ } -@ if {[hascap j]} { -@ html "<a href='$home/wiki'>Wiki</a>\n" -@ } -@ if {[hascap s]} { -@ html "<a href='$home/setup'>Admin</a>\n" -@ } elseif {[hascap a]} { -@ html "<a href='$home/setup_ulist'>Users</a>\n" -@ } -@ if {[info exists login]} { -@ html "<a href='$home/login'>Logout</a>\n" -@ } else { -@ html "<a href='$home/login'>Login</a>\n" -@ } -@ </th1></div> -; - -/* -** The default page footer -*/ -const char zDefaultFooter[] = -@ <div class="footer"> -@ This page was generated in about -@ <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by -@ Fossil version $manifest_version $manifest_date -@ </div> -@ </body></html> -; - -/* -** The default Cascading Style Sheet. -** It's assembled by different strings for each class. -** The default css contains all definitions. -** The style sheet, send to the client only contains the ones, -** not defined in the user defined css. -*/ -const char zDefaultCSS[] = -@ /* General settings for the entire page */ -@ body { -@ margin: 0ex 1ex; -@ padding: 0px; -@ background-color: white; -@ font-family: sans-serif; -@ } -@ -@ /* The project logo in the upper left-hand corner of each page */ -@ div.logo { -@ display: table-cell; -@ text-align: center; -@ vertical-align: bottom; -@ font-weight: bold; -@ color: #558195; -@ min-width: 200px; -@ white-space: nowrap; -@ } -@ -@ /* The page title centered at the top of each page */ -@ div.title { -@ display: table-cell; -@ font-size: 2em; -@ font-weight: bold; -@ text-align: center; -@ padding: 0 0 0 1em; -@ color: #558195; -@ vertical-align: bottom; -@ width: 100%; -@ } -@ -@ /* The login status message in the top right-hand corner */ -@ div.status { -@ display: table-cell; -@ text-align: right; -@ vertical-align: bottom; -@ color: #558195; -@ font-size: 0.8em; -@ font-weight: bold; -@ min-width: 200px; -@ white-space: nowrap; -@ } -@ -@ /* The header across the top of the page */ -@ div.header { -@ display: table; -@ width: 100%; -@ } -@ -@ /* The main menu bar that appears at the top of the page beneath -@ ** the header */ -@ div.mainmenu { -@ padding: 5px 10px 5px 10px; -@ font-size: 0.9em; -@ font-weight: bold; -@ text-align: center; -@ letter-spacing: 1px; -@ background-color: #558195; -@ border-top-left-radius: 8px; -@ border-top-right-radius: 8px; -@ color: white; -@ } -@ -@ /* The submenu bar that *sometimes* appears below the main menu */ -@ div.submenu, div.sectionmenu { -@ padding: 3px 10px 3px 0px; -@ font-size: 0.9em; -@ text-align: center; -@ background-color: #456878; -@ color: white; -@ } -@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, -@ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited { -@ padding: 3px 10px 3px 10px; -@ color: white; -@ text-decoration: none; -@ } -@ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover { -@ color: #558195; -@ background-color: white; -@ } -@ -@ /* All page content from the bottom of the menu or submenu down to -@ ** the footer */ -@ div.content { -@ padding: 0ex 1ex 1ex 1ex; -@ border: solid #aaa; -@ border-width: 1px; -@ } -@ -@ /* Some pages have section dividers */ -@ div.section { -@ margin-bottom: 0px; -@ margin-top: 1em; -@ padding: 1px 1px 1px 1px; -@ font-size: 1.2em; -@ font-weight: bold; -@ background-color: #558195; -@ color: white; -@ white-space: nowrap; -@ } -@ -@ /* The "Date" that occurs on the left hand side of timelines */ -@ div.divider { -@ background: #a1c4d4; -@ border: 2px #558195 solid; -@ font-size: 1em; font-weight: normal; -@ padding: .25em; -@ margin: .2em 0 .2em 0; -@ float: left; -@ clear: left; -@ white-space: nowrap; -@ } -@ -@ /* The footer at the very bottom of the page */ -@ div.footer { -@ clear: both; -@ font-size: 0.8em; -@ padding: 5px 10px 5px 10px; -@ text-align: right; -@ background-color: #558195; -@ border-bottom-left-radius: 8px; -@ border-bottom-right-radius: 8px; -@ color: white; -@ } -@ -@ /* Hyperlink colors in the footer */ -@ div.footer a { color: white; } -@ div.footer a:link { color: white; } -@ div.footer a:visited { color: white; } -@ div.footer a:hover { background-color: white; color: #558195; } -@ -@ /* verbatim blocks */ -@ pre.verbatim { -@ background-color: #f5f5f5; -@ padding: 0.5em; -@ white-space: pre-wrap; -@} -; - /* The following table contains bits of default CSS that must ** be included if they are not found in the application-defined ** CSS. */ const struct strctCssDefaults { - char const * const elementClass; /* Name of element needed */ - char const * const comment; /* Comment text */ - char const * const value; /* CSS text */ + const char *elementClass; /* Name of element needed */ + const char *comment; /* Comment text */ + const char *value; /* CSS text */ } cssDefaultList[] = { - { "", - "", - zDefaultCSS - }, { "div.sidebox", "The nomenclature sidebox for branches,..", @ float: right; @ background-color: white; @ border-width: medium; @@ -659,10 +659,11 @@ @ font-size: small; }, { "table.timelineTable", "the format for the timeline data table", @ border: 0; + @ border-collapse: collapse; }, { "td.timelineTableCell", "the format for the timeline data cells", @ vertical-align: top; @ text-align: left; @@ -669,10 +670,21 @@ }, { "tr.timelineCurrent td.timelineTableCell", "the format for the timeline data cell of the current checkout", @ padding: .1em .2em; @ border: 1px dashed #446979; + }, + { "tr.timelineSelected", + "The row in the timeline table that contains the entry of interest", + @ padding: .1em .2em; + @ border: 2px solid lightgray; + @ background-color: #ffc; + @ box-shadow: 4px 4px 2px #888; + }, + { "tr.timelineSpacer", + "An extra row inserted to give vertical space between two rows", + @ height: 1ex; }, { "span.timelineLeaf", "the format for the timeline leaf marks", @ font-weight: bold; }, @@ -686,10 +698,11 @@ }, { "td.timelineTime", "the format for the timeline time display", @ vertical-align: top; @ text-align: right; + @ white-space: nowrap; }, { "td.timelineGraph", "the format for the grap placeholder cells in timelines", @ width: 20px; @ text-align: left; @@ -730,15 +743,126 @@ { "td.browser", "format for cells in the file browser", @ width: 24%; @ vertical-align: top; }, + { ".filetree", + "tree-view file browser", + @ margin: 1em 0; + @ line-height: 1.5; + }, + { + ".filetree > ul", + "tree-view top-level list", + @ display: inline-block; + }, + { ".filetree ul", + "tree-view lists", + @ margin: 0; + @ padding: 0; + @ list-style: none; + }, + { ".filetree ul.collapsed", + "tree-view collapsed list", + @ display: none; + }, + { ".filetree ul ul", + "tree-view lists below the root", + @ position: relative; + @ margin: 0 0 0 21px; + }, + { ".filetree li", + "tree-view lists items", + @ position: relative; + @ margin: 0; + @ padding: 0; + }, + { ".filetree li li:before", + "tree-view node lines", + @ content: ''; + @ position: absolute; + @ top: -.8em; + @ left: -14px; + @ width: 14px; + @ height: 1.5em; + @ border-left: 2px solid #aaa; + @ border-bottom: 2px solid #aaa; + }, + { ".filetree li > ul:before", + "tree-view directory lines", + @ content: ''; + @ position: absolute; + @ top: -1.5em; + @ bottom: 0; + @ left: -35px; + @ border-left: 2px solid #aaa; + }, + { ".filetree li.last > ul:before", + "hide lines for last-child directories", + @ display: none; + }, + { ".filetree a", + "tree-view links", + " position: relative;\n" + " z-index: 1;\n" + " display: table-cell;\n" + " min-height: 16px;\n" + " padding-left: 21px;\n" + " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP" + "\\/\\/\\/yEhIf\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmg" + "OUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);\n" + " background-position: center left;\n" + " background-repeat: no-repeat;\n" + }, { "ul.browser", - "format for the list in the file browser", - @ margin-left: 0.5em; - @ padding-left: 0.5em; + "list of files in the 'flat-view' file browser", + @ list-style-type: none; + @ padding: 10px; + @ margin: 0px; + @ white-space: nowrap; + }, + { "ul.browser li.file", + "List element in the 'flat-view' file browser for a file", + " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP" + "\\/\\/\\/yEhIf\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfm" + "gOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);\n" + " background-repeat: no-repeat;\n" + " background-position: 0px center;\n" + " padding-left: 20px;\n" + " padding-top: 2px;\n" + }, + { "ul.browser li.dir", + "List element in the 'flat-view file browser for a directory", + " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIi" + "Iv\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaX" + "po+jUs6b5Z/K4siDu5RPUFADs=);\n" + " background-repeat: no-repeat;\n" + " background-position: 0px center;\n" + " padding-left: 20px;\n" + " padding-top: 2px;\n" + }, + { "div.filetreeline", + "line of a file tree", + @ display: table; + @ width: 100%; @ white-space: nowrap; + }, + { ".filetree .dir > div.filetreeline > a", + "tree-view directory links", + " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIi" + "Iv\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXp" + "o+jUs6b5Z/K4siDu5RPUFADs=);\n" + }, + { "div.filetreeage", + "Last change floating display on the right", + @ display: table-cell; + @ padding-left: 3em; + @ text-align: right; + }, + { "div.filetreeline:hover", + "Highlight the line of a file tree", + @ background-color: #eee; }, { "table.login_out", "table format for login/out label/input table", @ text-align: left; @ margin-right: 10px; @@ -832,22 +956,26 @@ @ white-space: nowrap; }, { "span.ueditInheritNobody", "color for capabilities, inherited by nobody", @ color: green; + @ padding: .2em; }, { "span.ueditInheritDeveloper", "color for capabilities, inherited by developer", @ color: red; + @ padding: .2em; }, { "span.ueditInheritReader", "color for capabilities, inherited by reader", @ color: black; + @ padding: .2em; }, { "span.ueditInheritAnonymous", "color for capabilities, inherited by anonymous", @ color: blue; + @ padding: .2em; }, { "span.capability", "format for capabilities, mentioned on the user edit page", @ font-weight: bold; }, @@ -915,11 +1043,11 @@ @ border-width: thin; @ border-color: #000000; @ border-style: solid; }, { "input.checkinUserColor", - "format for user color input on checkin edit page", + "format for user color input on check-in edit page", @ /* no special definitions, class defined, to enable color pickers, f.e.: @ ** add the color picker found at http:jscolor.com as java script include @ ** to the header and configure the java script file with @ ** 1. use as bindClass :checkinUserColor @ ** 2. change the default hash adding behaviour to ON @@ -978,10 +1106,14 @@ { "ul.filelist", "List of files in a timeline", @ margin-top: 3px; @ line-height: 100%; }, + { "ul.filelist li", + "List of files in a timeline", + @ padding-top: 1px; + }, { "table.sbsdiffcols", "side-by-side diff display (column-based)", @ width: 90%; @ border-spacing: 0; @ font-size: xx-small; @@ -1051,18 +1183,18 @@ "format for th1 script errors", @ white-space: pre-wrap; @ word-wrap: break-word; @ color: red; }, - { "table.tale-value th", + { "table.label-value th", "The label/value pairs on (for example) the ci page", @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; }, { ".statistics-report-graph-line", - "for the /stats_report views", + "for the /reports views", @ background-color: #446979; }, { ".statistics-report-table-events th", "", @ padding: 0 1em 0 1em; @@ -1073,14 +1205,10 @@ }, { ".statistics-report-row-year", "", @ text-align: left; }, - { ".statistics-report-graph-line", - "for the /stats_report views", - @ background-color: #446979; - }, { ".statistics-report-week-number-label", "for the /stats_report views", @ text-align: right; @ font-size: 0.8em; }, @@ -1093,14 +1221,99 @@ @ /* use default */ }, { "tr.row1", "odd table row color", @ /* Use default */ + }, + { "#usetupEditCapability", + "format for capabilities string, mentioned on the user edit page", + @ font-weight: bold; }, { "#canvas", "timeline graph node colors", @ color: black; @ background-color: white; + }, + { "table.adminLogTable", + "Class for the /admin_log table", + @ text-align: left; + }, + { ".adminLogTable .adminTime", + "Class for the /admin_log table", + @ text-align: left; + @ vertical-align: top; + @ white-space: nowrap; + }, + { ".fileage table", + "The fileage table", + @ border-spacing: 0; + }, + { ".fileage tr:hover", + "Mouse-over effects for the file-age table", + @ background-color: #eee; + }, + { ".fileage td", + "fileage table cells", + @ vertical-align: top; + @ text-align: left; + @ border-top: 1px solid #ddd; + @ padding-top: 3px; + }, + { ".fileage td:first-child", + "fileage first column (the age)", + @ white-space: nowrap; + }, + { ".fileage td:nth-child(2)", + "fileage second column (the filename)", + @ padding-left: 1em; + @ padding-right: 1em; + }, + { ".fileage td:nth-child(3)", + "fileage third column (the check-in comment)", + @ word-wrap: break-word; + @ max-width: 50%; + }, + { ".brlist table", "The list of branches", + @ border-spacing: 0; + }, + { ".brlist table th", "Branch list table headers", + @ text-align: left; + @ padding: 0px 1em 0.5ex 0px; + }, + { ".brlist table td", "Branch list table headers", + @ padding: 0px 2em 0px 0px; + @ white-space: nowrap; + }, + { "th.sort:after", + "General styles for sortable column marker", + @ margin-left: .4em; + @ cursor: pointer; + @ text-shadow: 0 0 0 #000; /* Makes arrow darker */ + }, + { "th.sort.none:after", + "None sort column marker", + @ content: '\2666'; + }, + { "th.sort.asc:after", + "Ascending sort column marker", + @ content: '\2193'; + }, + { "th.sort.desc:after", + "Descending sort column marker", + @ content: '\2191'; + }, + { "span.snippet>mark", + "Search markup", + @ background-color: inherit; + @ font-weight: bold; + }, + { "div.searchForm", + "Container for the search terms entry box", + @ text-align: center; + }, + { "p.searchEmpty", + "Message explaining that there are no search results", + @ font-style: italic; }, { 0, 0, 0 } @@ -1110,38 +1323,59 @@ ** Append all of the default CSS to the CGI output. */ void cgi_append_default_css(void) { int i; - for (i=0;cssDefaultList[i].elementClass;i++){ - if (cssDefaultList[i].elementClass[0]){ + cgi_printf("%s", builtin_text("skins/default/css.txt")); + for( i=0; cssDefaultList[i].elementClass; i++ ){ + if( cssDefaultList[i].elementClass[0] ){ cgi_printf("/* %s */\n%s {\n%s\n}\n\n", - cssDefaultList[i].comment, - cssDefaultList[i].elementClass, - cssDefaultList[i].value - ); - }else{ - cgi_printf("%s", - cssDefaultList[i].value - ); - } - } -} + cssDefaultList[i].comment, + cssDefaultList[i].elementClass, + cssDefaultList[i].value + ); + } + } +} + +/* +** Search string zHaystack for zNeedle. zNeedle must be an isolated +** word with space or punctuation on either size. +** +** Return true if found. Return false if not found +*/ +static int containsString(const char *zHaystack, const char *zNeedle){ + char *z; + int n; + + while( zHaystack[0] ){ + z = strstr(zHaystack, zNeedle); + if( z==0 ) return 0; + n = (int)strlen(zNeedle); + if( (z==zHaystack || !fossil_isalnum(z[-1])) && !fossil_isalnum(z[n]) ){ + return 1; + } + zHaystack = z + n; + } + return 0; +} + /* ** WEBPAGE: style.css */ void page_style_css(void){ Blob css; int i; cgi_set_content_type("text/css"); - blob_init(&css, db_get("css",(char*)zDefaultCSS), -1); + blob_init(&css,skin_get("css"),-1); /* add special missing definitions */ for(i=1; cssDefaultList[i].elementClass; i++){ - if( strstr(blob_str(&css), cssDefaultList[i].elementClass)==0 ){ + char *z = blob_str(&css); + if( !containsString(z, cssDefaultList[i].elementClass) ){ blob_appendf(&css, "/* %s */\n%s {\n%s}\n", cssDefaultList[i].comment, cssDefaultList[i].elementClass, cssDefaultList[i].value); } @@ -1149,11 +1383,14 @@ /* Process through TH1 in order to give an opportunity to substitute ** variables such as $baseurl. */ Th_Store("baseurl", g.zBaseURL); + Th_Store("secureurl", login_wants_https_redirect()? g.zHttpsURL: g.zBaseURL); Th_Store("home", g.zTop); + image_url_var("logo"); + image_url_var("background"); Th_Render(blob_str(&css)); /* Tell CGI that the content returned by this page is considered cacheable */ g.isConst = 1; } @@ -1164,11 +1401,11 @@ void page_test_env(void){ char c; int i; int showAll; char zCap[30]; - static const char *azCgiVars[] = { + static const char *const azCgiVars[] = { "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE", "HTTP_CONNECTION", "HTTP_HOST", "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED", "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT", "REQUEST_METHOD", @@ -1175,36 +1412,46 @@ "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL", }; login_check_credentials(); if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){ - login_needed(); + login_needed(0); return; } for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]); style_header("Environment Test"); showAll = atoi(PD("showall","0")); if( !showAll ){ - style_submenu_element("Show Cookies", "Show Cookies", - "%s/test_env?showall=1", g.zTop); + style_submenu_element("Show Cookies", 0, "%R/test_env?showall=1"); }else{ - style_submenu_element("Hide Cookies", "Hide Cookies", - "%s/test_env", g.zTop); + style_submenu_element("Hide Cookies", 0, "%R/test_env"); } #if !defined(_WIN32) @ uid=%d(getuid()), gid=%d(getgid())<br /> #endif @ g.zBaseURL = %h(g.zBaseURL)<br /> + @ g.zHttpsURL = %h(g.zHttpsURL)<br /> @ g.zTop = %h(g.zTop)<br /> + @ g.zPath = %h(g.zPath)<br /> for(i=0, c='a'; c<='z'; c++){ - if( login_has_capability(&c, 1) ) zCap[i++] = c; + if( login_has_capability(&c, 1, 0) ) zCap[i++] = c; } zCap[i] = 0; @ g.userUid = %d(g.userUid)<br /> @ g.zLogin = %h(g.zLogin)<br /> @ g.isHuman = %d(g.isHuman)<br /> @ capabilities = %s(zCap)<br /> + for(i=0, c='a'; c<='z'; c++){ + if( login_has_capability(&c, 1, LOGIN_ANON) + && !login_has_capability(&c, 1, 0) ) zCap[i++] = c; + } + zCap[i] = 0; + if( i>0 ){ + @ anonymous-adds = %s(zCap)<br /> + } + @ g.zRepositoryName = %h(g.zRepositoryName)<br /> + @ load_average() = %f(load_average())<br /> @ <hr> P("HTTP_USER_AGENT"); cgi_print_all(showAll); if( showAll && blob_size(&g.httpHeader)>0 ){ @ <hr> @@ -1225,7 +1472,7 @@ ** ** WEBPAGE: honeypot */ void honeypot_page(void){ cgi_set_status(403, "Forbidden"); - @ <p>Access by spiders and robots is forbidden</p> + @ <p>Please enable javascript or log in to see this content</p> } Index: src/sync.c ================================================================== --- src/sync.c +++ src/sync.c @@ -48,31 +48,54 @@ } }else{ /* Autosync defaults on. To make it default off, "return" here. */ } url_parse(0, URL_REMEMBER); - if( g.urlProtocol==0 ) return 0; - if( g.urlUser!=0 && g.urlPasswd==0 ){ - g.urlPasswd = unobscure(db_get("last-sync-pw", 0)); + if( g.url.protocol==0 ) return 0; + if( g.url.user!=0 && g.url.passwd==0 ){ + g.url.passwd = unobscure(db_get("last-sync-pw", 0)); + g.url.flags |= URL_PROMPT_PW; + url_prompt_for_password(); } + g.zHttpAuth = get_httpauth(); + url_remember(); #if 0 /* Disabled for now */ if( (flags & AUTOSYNC_PULL)!=0 && db_get_boolean("auto-shun",1) ){ /* When doing an automatic pull, also automatically pull shuns from ** the server if pull_shuns is enabled. ** - ** TODO: What happens if the shun list gets really big? + ** TODO: What happens if the shun list gets really big? ** Maybe the shunning list should only be pulled on every 10th ** autosync, or something? */ configSync = CONFIGSET_SHUN; } #endif if( find_option("verbose","v",0)!=0 ) flags |= SYNC_VERBOSE; - fossil_print("Autosync: %s\n", g.urlCanonical); + fossil_print("Autosync: %s\n", g.url.canonical); url_enable_proxy("via proxy: "); rc = client_sync(flags, configSync, 0); - if( rc ) fossil_warning("Autosync failed"); + return rc; +} + +/* +** This routine will try a number of times to perform autosync with a +** 0.5 second sleep between attempts; returning the last autosync status. +*/ +int autosync_loop(int flags, int nTries){ + int n = 0; + int rc = 0; + while( (n==0 || n<nTries) && (rc=autosync(flags)) ){ + if( rc ){ + if( ++n<nTries ){ + fossil_warning("Autosync failed, making another attempt."); + sqlite3_sleep(500); + }else{ + fossil_warning("Autosync failed."); + } + } + } return rc; } /* ** This routine processes the command-line argument for push, pull, @@ -80,17 +103,19 @@ ** of a server to sync against. If no argument is given, use the ** most recently synced URL. Remember the current URL for next time. */ static void process_sync_args(unsigned *pConfigFlags, unsigned *pSyncFlags){ const char *zUrl = 0; + const char *zHttpAuth = 0; unsigned configSync = 0; unsigned urlFlags = URL_REMEMBER | URL_PROMPT_PW; int urlOptional = 0; if( find_option("autourl",0,0)!=0 ){ urlOptional = 1; urlFlags = 0; } + zHttpAuth = find_option("httpauth","B",1); if( find_option("once",0,0)!=0 ) urlFlags &= ~URL_REMEMBER; if( find_option("private",0,0)!=0 ){ *pSyncFlags |= SYNC_PRIVATE; } if( find_option("verbose","v",0)!=0 ){ @@ -101,30 +126,36 @@ */ if( find_option("verily",0,0)!=0 ){ *pSyncFlags |= SYNC_RESYNC; } url_proxy_options(); + clone_ssh_find_options(); db_find_and_open_repository(0, 0); db_open_config(0); if( g.argc==2 ){ if( db_get_boolean("auto-shun",1) ) configSync = CONFIGSET_SHUN; }else if( g.argc==3 ){ zUrl = g.argv[2]; } + if( urlFlags & URL_REMEMBER ){ + clone_ssh_db_set_options(); + } url_parse(zUrl, urlFlags); - if( g.urlProtocol==0 ){ + remember_or_get_http_auth(zHttpAuth, urlFlags & URL_REMEMBER, zUrl); + url_remember(); + if( g.url.protocol==0 ){ if( urlOptional ) fossil_exit(0); usage("URL"); } user_select(); if( g.argc==2 ){ if( ((*pSyncFlags) & (SYNC_PUSH|SYNC_PULL))==(SYNC_PUSH|SYNC_PULL) ){ - fossil_print("Sync with %s\n", g.urlCanonical); + fossil_print("Sync with %s\n", g.url.canonical); }else if( (*pSyncFlags) & SYNC_PUSH ){ - fossil_print("Push to %s\n", g.urlCanonical); + fossil_print("Push to %s\n", g.url.canonical); }else if( (*pSyncFlags) & SYNC_PULL ){ - fossil_print("Pull from %s\n", g.urlCanonical); + fossil_print("Pull from %s\n", g.url.canonical); } } url_enable_proxy("via proxy: "); *pConfigFlags |= configSync; } @@ -155,10 +186,14 @@ */ void pull_cmd(void){ unsigned configFlags = 0; unsigned syncFlags = SYNC_PULL; process_sync_args(&configFlags, &syncFlags); + + /* We should be done with options.. */ + verify_all_options(); + client_sync(syncFlags, configFlags, 0); } /* ** COMMAND: push @@ -186,10 +221,14 @@ */ void push_cmd(void){ unsigned configFlags = 0; unsigned syncFlags = SYNC_PUSH; process_sync_args(&configFlags, &syncFlags); + + /* We should be done with options.. */ + verify_all_options(); + if( db_get_boolean("dont-push",0) ){ fossil_fatal("pushing is prohibited: the 'dont-push' option is set"); } client_sync(syncFlags, 0, 0); } @@ -222,10 +261,14 @@ */ void sync_cmd(void){ unsigned configFlags = 0; unsigned syncFlags = SYNC_PUSH|SYNC_PULL; process_sync_args(&configFlags, &syncFlags); + + /* We should be done with options.. */ + verify_all_options(); + if( db_get_boolean("dont-push",0) ) syncFlags &= ~SYNC_PUSH; client_sync(syncFlags, configFlags, 0); if( (syncFlags & SYNC_PUSH)==0 ){ fossil_warning("pull only: the 'dont-push' option is set"); } @@ -249,23 +292,29 @@ ** See also: clone, push, pull, sync */ void remote_url_cmd(void){ char *zUrl; db_find_and_open_repository(0, 0); + + /* We should be done with options.. */ + verify_all_options(); + if( g.argc!=2 && g.argc!=3 ){ usage("remote-url ?URL|off?"); } if( g.argc==3 ){ db_unset("last-sync-url", 0); db_unset("last-sync-pw", 0); + db_unset("http-auth", 0); if( is_false(g.argv[2]) ) return; - url_parse(g.argv[2], URL_REMEMBER|URL_PROMPT_PW); + url_parse(g.argv[2], URL_REMEMBER|URL_PROMPT_PW|URL_ASK_REMEMBER_PW); } + url_remember(); zUrl = db_get("last-sync-url", 0); if( zUrl==0 ){ fossil_print("off\n"); return; }else{ url_parse(zUrl, 0); - fossil_print("%s\n", g.urlCanonical); + fossil_print("%s\n", g.url.canonical); } } Index: src/tag.c ================================================================== --- src/tag.c +++ src/tag.c @@ -50,29 +50,29 @@ /* Query for children of :pid to which to propagate the tag. ** Three returns: (1) rid of the child. (2) timestamp of child. ** (3) True to propagate or false to block. */ - db_prepare(&s, + db_prepare(&s, "SELECT cid, plink.mtime," " coalesce(srcid=0 AND tagxref.mtime<:mtime, %d) AS doit" " FROM plink LEFT JOIN tagxref ON cid=rid AND tagid=%d" " WHERE pid=:pid AND isprim", tagType==2, tagid ); db_bind_double(&s, ":mtime", mtime); if( tagType==2 ){ - /* Set the propagated tag marker on checkin :rid */ + /* Set the propagated tag marker on check-in :rid */ db_prepare(&ins, "REPLACE INTO tagxref(tagid, tagtype, srcid, origid, value, mtime, rid)" "VALUES(%d,2,0,%d,%Q,:mtime,:rid)", tagid, origId, zValue ); db_bind_double(&ins, ":mtime", mtime); }else{ - /* Remove all references to the tag from checkin :rid */ + /* Remove all references to the tag from check-in :rid */ zValue = 0; db_prepare(&ins, "DELETE FROM tagxref WHERE tagid=%d AND rid=:rid", tagid ); } @@ -179,11 +179,11 @@ db_finalize(&s); if( rc==SQLITE_ROW ){ /* Another entry that is more recent already exists. Do nothing */ return tagid; } - db_prepare(&s, + db_prepare(&s, "REPLACE INTO tagxref(tagid,tagtype,srcId,origid,value,mtime,rid)" " VALUES(%d,%d,%d,%d,%Q,:mtime,%d)", tagid, tagtype, srcId, rid, zValue, rid ); db_bind_double(&s, ":mtime", mtime); @@ -213,11 +213,12 @@ rid ); } } if( zCol ){ - db_multi_exec("UPDATE event SET %s=%Q WHERE objid=%d", zCol, zValue, rid); + db_multi_exec("UPDATE event SET \"%w\"=%Q WHERE objid=%d", + zCol, zValue, rid); if( tagid==TAG_COMMENT ){ char *zCopy = mprintf("%s", zValue); wiki_extract_links(zCopy, rid, 0, mtime, 1, WIKI_INLINE); free(zCopy); } @@ -256,11 +257,11 @@ zTag = g.argv[2]; switch( zTag[0] ){ case '+': tagtype = 1; break; case '*': tagtype = 2; break; case '-': tagtype = 0; break; - default: + default: fossil_fatal("tag should begin with '+', '*', or '-'"); return; } rid = name_to_rid(g.argv[3]); if( rid==0 ){ @@ -268,11 +269,11 @@ } g.markPrivate = content_is_private(rid); zValue = g.argc==5 ? g.argv[4] : 0; db_begin_transaction(); tag_insert(zTag, tagtype, zValue, -1, 0.0, rid); - db_end_transaction(0); + db_end_transaction(0); } /* ** Add a control record to the repository that either creates ** or cancels a tag. @@ -322,15 +323,15 @@ if( tagtype>0 && zValue && zValue[0] ){ blob_appendf(&ctrl, " %F\n", zValue); }else{ blob_appendf(&ctrl, "\n"); } - blob_appendf(&ctrl, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); + blob_appendf(&ctrl, "U %F\n", zUserOvrd ? zUserOvrd : login_name()); md5sum_blob(&ctrl, &cksum); blob_appendf(&ctrl, "Z %b\n", &cksum); nrid = content_put(&ctrl); - manifest_crosslink(nrid, &ctrl); + manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS); assert( blob_is_reset(&ctrl) ); } /* ** COMMAND: tag @@ -351,14 +352,14 @@ ** the propagation of the tag to any descendants. ** ** %fossil tag find ?--raw? ?-t|--type TYPE? ?-n|--limit #? TAGNAME ** ** List all objects that use TAGNAME. TYPE can be "ci" for -** checkins or "e" for events. The limit option limits the number +** check-ins or "e" for events. The limit option limits the number ** of results to the given value. ** -** %fossil tag list ?--raw? ?CHECK-IN? +** %fossil tag list|ls ?--raw? ?CHECK-IN? ** ** List all tags, or if CHECK-IN is supplied, list ** all tags and their values for CHECK-IN. ** ** The option --raw allows the manipulation of all types of tags @@ -378,22 +379,22 @@ ** ** fossil update tag:decaf ** ** will assume that "decaf" is a tag/branch name. ** -** only allow --date-override and --user-override in +** only allow --date-override and --user-override in ** %fossil tag add --date-override 'YYYY-MMM-DD HH:MM:SS' \\ -** --user-override user +** --user-override user ** in order to import history from other scm systems */ void tag_cmd(void){ int n; int fRaw = find_option("raw","",0)!=0; int fPropagate = find_option("propagate","",0)!=0; const char *zPrefix = fRaw ? "" : "sym-"; - char const * zFindLimit = find_option("limit","n",1); - int const nFindLimit = zFindLimit ? atoi(zFindLimit) : 0; + const char *zFindLimit = find_option("limit","n",1); + const int nFindLimit = zFindLimit ? atoi(zFindLimit) : -2000; db_find_and_open_repository(0, 0); if( g.argc<3 ){ goto tag_cmd_usage; } @@ -437,55 +438,52 @@ if( zType==0 || zType[0]==0 ) zType = "*"; if( g.argc!=4 ){ usage("find ?--raw? ?-t|--type TYPE? ?-n|--limit #? TAGNAME"); } if( fRaw ){ - blob_appendf(&sql, + blob_append_sql(&sql, "SELECT blob.uuid FROM tagxref, blob" " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" " AND tagxref.tagtype>0" " AND blob.rid=tagxref.rid", g.argv[3] ); - if(nFindLimit>0){ - blob_appendf(&sql, " LIMIT %d", nFindLimit); + if( nFindLimit>0 ){ + blob_append_sql(&sql, " LIMIT %d", nFindLimit); } - db_prepare(&q, "%s", blob_str(&sql)); + db_prepare(&q, "%s", blob_sql_text(&sql)); blob_reset(&sql); while( db_step(&q)==SQLITE_ROW ){ fossil_print("%s\n", db_column_text(&q, 0)); } db_finalize(&q); }else{ int tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", g.argv[3]); if( tagid>0 ){ - blob_appendf(&sql, + blob_append_sql(&sql, "%s" " AND event.type GLOB '%q'" " AND blob.rid IN (" " SELECT rid FROM tagxref" " WHERE tagtype>0 AND tagid=%d" ")" " ORDER BY event.mtime DESC", timeline_query_for_tty(), zType, tagid ); - if(nFindLimit>0){ - blob_appendf(&sql, " LIMIT %d", nFindLimit); - } - db_prepare(&q, "%s", blob_str(&sql)); + db_prepare(&q, "%s", blob_sql_text(&sql)); blob_reset(&sql); - print_timeline(&q, 2000, 0); + print_timeline(&q, nFindLimit, 79, 0); db_finalize(&q); } } }else - if( strncmp(g.argv[2],"list",n)==0 ){ + if(( strncmp(g.argv[2],"list",n)==0 )||( strncmp(g.argv[2],"ls",n)==0 )){ Stmt q; if( g.argc==3 ){ - db_prepare(&q, + db_prepare(&q, "SELECT tagname FROM tag" " WHERE EXISTS(SELECT 1 FROM tagxref" " WHERE tagid=tag.tagid" " AND tagtype>0)" " ORDER BY tagname" @@ -537,21 +535,22 @@ tag_cmd_usage: usage("add|cancel|find|list ..."); } /* -** WEBPAGE: /taglist +** WEBPAGE: taglist */ void taglist_page(void){ Stmt q; login_check_credentials(); if( !g.perm.Read ){ - login_needed(); + login_needed(g.anon.Read); } login_anonymous_available(); style_header("Tags"); + style_adunit_config(ADUNIT_RIGHT_OK); style_submenu_element("Timeline", "Timeline", "tagtimeline"); @ <h2>Non-propagating tags:</h2> db_prepare(&q, "SELECT substr(tagname,5)" " FROM tag" @@ -563,11 +562,11 @@ ); @ <ul> while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); if( g.perm.Hyperlink ){ - @ <li>%z(xhref("class='taglink'","%R/timeline?t=%T",zName)) + @ <li>%z(xhref("class='taglink'","%R/timeline?t=%T&n=200",zName)) @ %h(zName)</a></li> }else{ @ <li><span class="tagDsp">%h(zName)</span></li> } } @@ -581,11 +580,11 @@ */ void tagtimeline_page(void){ Stmt q; login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } style_header("Tagged Check-ins"); style_submenu_element("List", "List", "taglist"); login_anonymous_available(); @ <h2>Check-ins with non-propagating tags:</h2> @@ -595,16 +594,10 @@ " AND tagid IN (SELECT tagid FROM tag " " WHERE tagname GLOB 'sym-*'))" " ORDER BY event.mtime DESC", timeline_query_for_www() ); - www_print_timeline(&q, 0, 0, 0, 0); + www_print_timeline(&q, 0, 0, 0, 0, 0); db_finalize(&q); @ <br /> - @ <script type="text/JavaScript"> - @ function xin(id){ - @ } - @ function xout(id){ - @ } - @ </script> style_footer(); } Index: src/tar.c ================================================================== --- src/tar.c +++ src/tar.c @@ -15,13 +15,18 @@ ** ******************************************************************************* ** ** This file contains code used to generate tarballs. */ +#include "config.h" #include <assert.h> -#include <zlib.h> -#include "config.h" +#if defined(FOSSIL_ENABLE_MINIZ) +# define MINIZ_HEADER_FILE_ONLY +# include "miniz.c" +#else +# include <zlib.h> +#endif #include "tar.h" /* ** State information for the tarball builder. */ @@ -280,11 +285,11 @@ const char *zName, /* Name of the object */ int nName, /* Number of characters in zName */ int iMode, /* Mode. 0644 or 0755 */ unsigned int mTime, /* File modification time */ int iSize, /* Size of the object in bytes */ - char cType /* Type of object: + char cType /* Type of object: '0'==file. '2'==symlink. '5'==directory */ ){ /* set mode and modification time */ sqlite3_snprintf(8, (char*)&tball.aHdr[100], "%07o", iMode); sqlite3_snprintf(12, (char*)&tball.aHdr[136], "%011o", mTime); @@ -336,12 +341,13 @@ unsigned int mTime /* Modification time */ ){ int i; for(i=nName-1; i>0 && zName[i]!='/'; i--){} if( i<=0 ) return; - if( i < tball.nPrevDirAlloc && tball.zPrevDir[i]==0 && - memcmp(tball.zPrevDir, zName, i)==0 ) return; + if( i<tball.nPrevDirAlloc + && strncmp(tball.zPrevDir, zName, i)==0 + && tball.zPrevDir[i]==0 ) return; db_multi_exec("INSERT OR IGNORE INTO dir VALUES('%#q')", i, zName); if( sqlite3_changes(g.db)==0 ) return; tar_add_directory_of(zName, i-1, mTime); tar_add_header(zName, i, 0755, mTime, 0, '5'); if( i >= tball.nPrevDirAlloc ){ @@ -371,11 +377,11 @@ char cType = '0'; /* length check moved to tar_split_path */ tar_add_directory_of(zName, nName, mTime); - /* + /* * If we have a symlink, write its destination path (which is stored in * pContent) into header, and set content length to 0 to avoid storing path * as file content in the next step. Since 'linkname' header is limited to * 100 bytes (-1 byte for terminating zero), if path is greater than that, * store symlink as a plain-text file. (Not sure how TAR handles long links.) @@ -384,11 +390,11 @@ sqlite3_snprintf(100, (char*)&tball.aHdr[157], "%s", blob_str(pContent)); cType = '2'; n = 0; } - tar_add_header(zName, nName, ( mPerm==PERM_EXE ) ? 0755 : 0644, + tar_add_header(zName, nName, ( mPerm==PERM_EXE ) ? 0755 : 0644, mTime, n, cType); if( n ){ gzip_step(blob_buffer(pContent), n); lastPage = n % 512; if( lastPage!=0 ){ @@ -422,30 +428,29 @@ ** that contains files given in the second and subsequent arguments. */ void test_tarball_cmd(void){ int i; Blob zip; - Blob file; if( g.argc<3 ){ usage("ARCHIVE FILE...."); } sqlite3_open(":memory:", &g.db); - tar_begin(0); + tar_begin(-1); for(i=3; i<g.argc; i++){ + Blob file; blob_zero(&file); blob_read_from_file(&file, g.argv[i]); - tar_add_file(g.argv[i], &file, - file_wd_perm(g.argv[i]), file_wd_mtime(g.argv[i])); + tar_add_file(g.argv[i], &file, file_wd_perm(0), file_wd_mtime(0)); blob_reset(&file); } tar_finish(&zip); blob_write_to_file(&zip, g.argv[2]); } /* -** Given the RID for a checkin, construct a tarball containing -** all files in that checkin +** Given the RID for a check-in, construct a tarball containing +** all files in that check-in ** ** If RID is for an object that is not a real manifest, then the ** resulting tarball contains a single file which is the RID ** object. ** @@ -486,12 +491,13 @@ mTime = (pManifest->rDate - 2440587.5)*86400.0; tar_begin(mTime); if( db_get_boolean("manifest", 0) ){ blob_append(&filename, "manifest", -1); zName = blob_str(&filename); - tar_add_file(zName, &mfile, 0, mTime); sha1sum_blob(&mfile, &hash); + sterilize_manifest(&mfile); + tar_add_file(zName, &mfile, 0, mTime); blob_reset(&mfile); blob_append(&hash, "\n", 1); blob_resize(&filename, nPrefix); blob_append(&filename, "manifest.uuid", -1); zName = blob_str(&filename); @@ -525,30 +531,38 @@ } /* ** COMMAND: tarball* ** -** Usage: %fossil tarball VERSION OUTPUTFILE [--name DIRECTORYNAME] [-R|--repository REPO] +** Usage: %fossil tarball VERSION OUTPUTFILE ** ** Generate a compressed tarball for a specified version. If the --name ** option is used, its argument becomes the name of the top-level directory ** in the resulting tarball. If --name is omitted, the top-level directory ** named is derived from the project name, the check-in date and time, and ** the artifact ID of the check-in. +** +** Options: +** --name DIRECTORYNAME The name of the top-level directory in the archive +** -R REPOSITORY Specify a Fossil repository */ void tarball_cmd(void){ int rid; Blob tarball; const char *zName; zName = find_option("name", 0, 1); db_find_and_open_repository(0, 0); + + /* We should be done with options.. */ + verify_all_options(); + if( g.argc!=4 ){ usage("VERSION OUTPUTFILE"); } rid = name_to_typed_rid(g.argv[2], "ci"); if( rid==0 ){ - fossil_fatal("Checkin not found: %s", g.argv[2]); + fossil_fatal("Check-in not found: %s", g.argv[2]); return; } if( zName==0 ){ zName = db_text("default-name", @@ -568,21 +582,33 @@ /* ** WEBPAGE: tarball ** URL: /tarball/RID.tar.gz ** -** Generate a compressed tarball for a checkin. +** Generate a compressed tarball for a check-in. ** Return that tarball as the HTTP reply content. +** +** Optional URL Parameters: +** +** - name=NAME[.tar.gz] is base name of the output file. Defaults to +** something project/version-specific. The prefix of the name, up to +** the last '.', are used as the top-most directory name in the tar +** output. +** +** - uuid=the version to tar (may be a tag/branch name). +** Defaults to "trunk". +** */ void tarball_page(void){ int rid; - char *zName, *zRid; + char *zName, *zRid, *zKey; int nName, nRid; Blob tarball; login_check_credentials(); - if( !g.perm.Zip ){ login_needed(); return; } + if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } + load_control(); zName = mprintf("%s", PD("name","")); nName = strlen(zName); zRid = mprintf("%s", PD("uuid","trunk")); nRid = strlen(zRid); if( nName>7 && fossil_strcmp(&zName[nName-7], ".tar.gz")==0 ){ @@ -603,11 +629,37 @@ if( rid==0 ){ @ Not found return; } if( nRid==0 && nName>10 ) zName[10] = 0; - tarball_of_checkin(rid, &tarball, zName); + zKey = db_text(0, "SELECT '/tarball/'||uuid||'/%q'" + " FROM blob WHERE rid=%d",zName,rid); + if( P("debug")!=0 ){ + style_header("Tarball Generator Debug Screen"); + @ zName = "%h(zName)"<br> + @ rid = %d(rid)<br> + @ zKey = "%h(zKey)" + style_footer(); + return; + } + if( referred_from_login() ){ + style_header("Tarball Download"); + @ <form action='%R/tarball'> + cgi_query_parameters_to_hidden(); + @ <p>Tarball named <b>%h(zName).tar.gz</b> holding the content + @ of check-in <b>%h(zRid)</b>: + @ <input type="submit" value="Download" /> + @ </form> + style_footer(); + return; + } + blob_zero(&tarball); + if( cache_read(&tarball, zKey)==0 ){ + tarball_of_checkin(rid, &tarball, zName); + cache_write(&tarball, zKey); + } free( zName ); free( zRid ); + free( zKey ); cgi_set_content(&tarball); cgi_set_content_type("application/x-compressed"); } Index: src/th.c ================================================================== --- src/th.c +++ src/th.c @@ -1,11 +1,12 @@ /* -** The implementation of the TH core. This file contains the parser, and +** The implementation of the TH core. This file contains the parser, and ** the implementation of the interface in th.h. */ +#include "config.h" #include "th.h" #include <string.h> #include <assert.h> typedef struct Th_Command Th_Command; @@ -15,11 +16,11 @@ /* ** Interpreter structure. */ struct Th_Interp { Th_Vtab *pVtab; /* Copy of the argument passed to Th_CreateInterp() */ - char *zResult; /* Current interpreter result (Th_Malloc()ed) */ + char *zResult; /* Current interpreter result (Th_Malloc()ed) */ int nResult; /* number of bytes in zResult */ Th_Hash *paCmd; /* Table of registered commands */ Th_Frame *pFrame; /* Current execution frame */ int isListMode; /* True if thSplitList() should operate in "list" mode */ }; @@ -41,25 +42,25 @@ ** are stored in the Th_Frame.paVar hash table member of the associated ** stack frame object. ** ** When an interpreter is created, a single Th_Frame structure is also ** allocated - the global variable scope. Th_Interp.pFrame (the current -** interpreter frame) is initialised to point to this Th_Frame. It is -** not deleted for the lifetime of the interpreter (because the global +** interpreter frame) is initialised to point to this Th_Frame. It is +** not deleted for the lifetime of the interpreter (because the global ** frame never goes out of scope). ** ** New stack frames are created by the Th_InFrame() function. Before ** invoking its callback function, Th_InFrame() allocates a new Th_Frame ** structure with pCaller set to the current frame (Th_Interp.pFrame), ** and sets the current frame to the new frame object. After the callback ** has been invoked, the allocated Th_Frame is deleted and the value ** of the current frame pointer restored. -** -** By default, the Th_SetVar(), Th_UnsetVar() and Th_GetVar() functions -** access variable values in the current frame. If they need to access +** +** By default, the Th_SetVar(), Th_UnsetVar() and Th_GetVar() functions +** access variable values in the current frame. If they need to access ** the global frame, they do so by traversing the pCaller pointer list. -** Likewise, the Th_LinkVar() function uses the pCaller pointers to +** Likewise, the Th_LinkVar() function uses the pCaller pointers to ** link to variables located in the global or other stack frames. */ struct Th_Frame { Th_Hash *paVar; /* Variables defined in this scope */ Th_Frame *pCaller; /* Calling frame */ @@ -83,11 +84,11 @@ ** value. */ struct Th_Variable { int nRef; /* Number of references to this structure */ int nData; /* Number of bytes at Th_Variable.zData */ - char *zData; /* Data for scalar variables */ + char *zData; /* Data for scalar variables */ Th_Hash *pHash; /* Data for array variables */ }; /* ** Hash table API: @@ -104,51 +105,52 @@ static int thEndOfLine(const char *, int); static int thPushFrame(Th_Interp*, Th_Frame*); static void thPopFrame(Th_Interp*); -static void thFreeVariable(Th_HashEntry*, void*); -static void thFreeCommand(Th_HashEntry*, void*); +static int thFreeVariable(Th_HashEntry*, void*); +static int thFreeCommand(Th_HashEntry*, void*); /* ** The following are used by both the expression and language parsers. -** Given that the start of the input string (z, n) is a language +** Given that the start of the input string (z, n) is a language ** construct of the relevant type (a command enclosed in [], an escape ** sequence etc.), these functions determine the number of bytes ** of the input consumed by the construct. For example: ** ** int nByte; ** thNextCommand(interp, "[expr $a+1] $nIter", 18, &nByte); ** -** results in variable nByte being set to 11. Or, +** results in variable nByte being set to 11. Or, ** ** thNextVarname(interp, "$a+1", 4, &nByte); ** ** results in nByte being set to 2. */ static int thNextCommand(Th_Interp*, const char *z, int n, int *pN); static int thNextEscape (Th_Interp*, const char *z, int n, int *pN); static int thNextVarname(Th_Interp*, const char *z, int n, int *pN); static int thNextNumber (Th_Interp*, const char *z, int n, int *pN); +static int thNextInteger (Th_Interp*, const char *z, int n, int *pN); static int thNextSpace (Th_Interp*, const char *z, int n, int *pN); /* ** Given that the input string (z, n) contains a language construct of -** the relevant type (a command enclosed in [], an escape sequence +** the relevant type (a command enclosed in [], an escape sequence ** like "\xFF" or a variable reference like "${varname}", perform ** substitution on the string and store the resulting string in ** the interpreter result. */ static int thSubstCommand(Th_Interp*, const char *z, int n); static int thSubstEscape (Th_Interp*, const char *z, int n); static int thSubstVarname(Th_Interp*, const char *z, int n); /* -** Given that there is a th1 word located at the start of the input +** Given that there is a th1 word located at the start of the input ** string (z, n), determine the length in bytes of that word. If the -** isCmd argument is non-zero, then an unescaped ";" byte not -** located inside of a block or quoted string is considered to mark +** isCmd argument is non-zero, then an unescaped ";" byte not +** located inside of a block or quoted string is considered to mark ** the end of the word. */ static int thNextWord(Th_Interp*, const char *z, int n, int *pN, int isCmd); /* @@ -175,13 +177,13 @@ ** Append nAdd bytes of content copied from zAdd to the end of buffer ** pBuffer. If there is not enough space currently allocated, resize ** the allocation to make space. */ static int thBufferWrite( - Th_Interp *interp, - Buffer *pBuffer, - const char *zAdd, + Th_Interp *interp, + Buffer *pBuffer, + const char *zAdd, int nAdd ){ int nReq; if( nAdd<0 ){ @@ -257,12 +259,14 @@ ** (Th_Frame.paVar). Decrement the reference count of the Th_Variable ** structure that the entry points to. Free the Th_Variable if its ** reference count reaches 0. ** ** Argument pContext is a pointer to the interpreter structure. +** +** Returns non-zero if the Th_Variable was actually freed. */ -static void thFreeVariable(Th_HashEntry *pEntry, void *pContext){ +static int thFreeVariable(Th_HashEntry *pEntry, void *pContext){ Th_Variable *pValue = (Th_Variable *)pEntry->pData; pValue->nRef--; assert( pValue->nRef>=0 ); if( pValue->nRef==0 ){ Th_Interp *interp = (Th_Interp *)pContext; @@ -270,27 +274,33 @@ if( pValue->pHash ){ Th_HashIterate(interp, pValue->pHash, thFreeVariable, pContext); Th_HashDelete(interp, pValue->pHash); } Th_Free(interp, pValue); + pEntry->pData = 0; + return 1; } + return 0; } /* ** Argument pEntry points to an entry in the command hash table ** (Th_Interp.paCmd). Delete the Th_Command structure that the ** entry points to. ** ** Argument pContext is a pointer to the interpreter structure. +** +** Always returns non-zero. */ -static void thFreeCommand(Th_HashEntry *pEntry, void *pContext){ +static int thFreeCommand(Th_HashEntry *pEntry, void *pContext){ Th_Command *pCommand = (Th_Command *)pEntry->pData; if( pCommand->xDel ){ pCommand->xDel((Th_Interp *)pContext, pCommand->pContext); } Th_Free((Th_Interp *)pContext, pEntry->pData); pEntry->pData = 0; + return 1; } /* ** Push a new frame onto the stack. */ @@ -310,19 +320,19 @@ Th_HashDelete(interp, pFrame->paVar); interp->pFrame = pFrame->pCaller; } /* -** The first part of the string (zInput,nInput) contains an escape +** The first part of the string (zInput,nInput) contains an escape ** sequence. Set *pnEscape to the number of bytes in the escape sequence. ** If there is a parse error, return TH_ERROR and set the interpreter ** result to an error message. Otherwise return TH_OK. */ static int thNextEscape( Th_Interp *interp, - const char *zInput, - int nInput, + const char *zInput, + int nInput, int *pnEscape ){ int i = 2; assert(nInput>0); @@ -343,18 +353,18 @@ return TH_OK; } /* ** The first part of the string (zInput,nInput) contains a variable -** reference. Set *pnVarname to the number of bytes in the variable -** reference. If there is a parse error, return TH_ERROR and set the +** reference. Set *pnVarname to the number of bytes in the variable +** reference. If there is a parse error, return TH_ERROR and set the ** interpreter result to an error message. Otherwise return TH_OK. */ int thNextVarname( Th_Interp *interp, - const char *zInput, - int nInput, + const char *zInput, + int nInput, int *pnVarname ){ int i; assert(nInput>0); @@ -400,19 +410,19 @@ return TH_OK; } /* ** The first part of the string (zInput,nInput) contains a command -** enclosed in a "[]" block. Set *pnCommand to the number of bytes in -** the variable reference. If there is a parse error, return TH_ERROR -** and set the interpreter result to an error message. Otherwise return +** enclosed in a "[]" block. Set *pnCommand to the number of bytes in +** the variable reference. If there is a parse error, return TH_ERROR +** and set the interpreter result to an error message. Otherwise return ** TH_OK. */ int thNextCommand( Th_Interp *interp, - const char *zInput, - int nInput, + const char *zInput, + int nInput, int *pnCommand ){ int nBrace = 0; int nSquare = 0; int i; @@ -437,17 +447,17 @@ return TH_OK; } /* -** Set *pnSpace to the number of whitespace bytes at the start of +** Set *pnSpace to the number of whitespace bytes at the start of ** input string (zInput, nInput). Always return TH_OK. */ int thNextSpace( Th_Interp *interp, - const char *zInput, - int nInput, + const char *zInput, + int nInput, int *pnSpace ){ int i; for(i=0; i<nInput && th_isspace(zInput[i]); i++); *pnSpace = i; @@ -456,21 +466,21 @@ /* ** The first byte of the string (zInput,nInput) is not white-space. ** Set *pnWord to the number of bytes in the th1 word that starts ** with this byte. If a complete word cannot be parsed or some other -** error occurs, return TH_ERROR and set the interpreter result to +** error occurs, return TH_ERROR and set the interpreter result to ** an error message. Otherwise return TH_OK. ** -** If the isCmd argument is non-zero, then an unescaped ";" byte not -** located inside of a block or quoted string is considered to mark +** If the isCmd argument is non-zero, then an unescaped ";" byte not +** located inside of a block or quoted string is considered to mark ** the end of the word. */ static int thNextWord( Th_Interp *interp, - const char *zInput, - int nInput, + const char *zInput, + int nInput, int *pnWord, int isCmd ){ int iEnd = 0; @@ -501,16 +511,18 @@ } iEnd++; } if( nBrace>0 || nSq>0 ){ /* Parse error */ + Th_SetResult(interp, "parse error", -1); return TH_ERROR; } } if( iEnd>nInput ){ /* Parse error */ + Th_SetResult(interp, "parse error", -1); return TH_ERROR; } *pnWord = iEnd; return TH_OK; } @@ -530,12 +542,12 @@ return thEvalLocal(interp, &zWord[1], nWord-2); } /* ** The input string (zWord, nWord) contains a th1 variable reference -** (a '$' byte followed by a variable name). Perform substitution on -** the input string and store the resulting string in the interpreter +** (a '$' byte followed by a variable name). Perform substitution on +** the input string and store the resulting string in the interpreter ** result. */ static int thSubstVarname( Th_Interp *interp, const char *zWord, @@ -571,11 +583,11 @@ return Th_GetVar(interp, &zWord[1], nWord-1); } /* ** The input string (zWord, nWord) contains a th1 escape sequence. -** Perform substitution on the input string and store the resulting +** Perform substitution on the input string and store the resulting ** string in the interpreter result. */ static int thSubstEscape( Th_Interp *interp, const char *zWord, @@ -607,11 +619,11 @@ return TH_OK; } /* ** The input string (zWord, nWord) contains a th1 word. Perform -** substitution on the input string and store the resulting +** substitution on the input string and store the resulting ** string in the interpreter result. */ static int thSubstWord( Th_Interp *interp, const char *zWord, @@ -639,20 +651,20 @@ int (*xGet)(Th_Interp *, const char*, int, int *) = 0; int (*xSubst)(Th_Interp *, const char*, int) = 0; switch( zWord[i] ){ case '\\': - xGet = thNextEscape; xSubst = thSubstEscape; + xGet = thNextEscape; xSubst = thSubstEscape; break; case '[': if( !interp->isListMode ){ - xGet = thNextCommand; xSubst = thSubstCommand; + xGet = thNextCommand; xSubst = thSubstCommand; break; } case '$': if( !interp->isListMode ){ - xGet = thNextVarname; xSubst = thSubstVarname; + xGet = thNextVarname; xSubst = thSubstVarname; break; } default: { thBufferWrite(interp, &output, &zWord[i], 1); continue; /* Go to the next iteration of the for(...) loop */ @@ -684,11 +696,11 @@ ** Return true if one of the following is true of the buffer pointed ** to by zInput, length nInput: ** ** + It is empty, or ** + It contains nothing but white-space, or -** + It contains no non-white-space characters before the first +** + It contains no non-white-space characters before the first ** newline character. ** ** Otherwise return false. */ static int thEndOfLine(const char *zInput, int nInput){ @@ -724,16 +736,16 @@ ** // Free all memory allocated by Th_SplitList(). The arrays pointed ** // to by argv and argl are invalidated by this call. ** // ** Th_Free(interp, argv); ** -*/ +*/ static int thSplitList( Th_Interp *interp, /* Interpreter context */ - const char *zList, /* Pointer to buffer containing input list */ + const char *zList, /* Pointer to buffer containing input list */ int nList, /* Size of buffer pointed to by zList */ - char ***pazElem, /* OUT: Array of list elements */ + char ***pazElem, /* OUT: Array of list elements */ int **panElem, /* OUT: Lengths of each list element */ int *pnCount /* OUT: Number of list elements */ ){ int rc = TH_OK; @@ -773,14 +785,14 @@ assert((lenbuf.nBuf/sizeof(int))==nCount); assert((pazElem && panElem) || (!pazElem && !panElem)); if( pazElem && rc==TH_OK ){ int i; - char *zElem; + char *zElem; int *anElem; char **azElem = Th_Malloc(interp, - sizeof(char*) * nCount + /* azElem */ + sizeof(char*) * nCount + /* azElem */ sizeof(int) * nCount + /* anElem */ strbuf.nBuf /* space for list element strings */ ); anElem = (int *)&azElem[nCount]; zElem = (char *)&anElem[nCount]; @@ -794,11 +806,11 @@ *panElem = anElem; } if( pnCount ){ *pnCount = nCount; } - + finish: thBufferFree(interp, &strbuf); thBufferFree(interp, &lenbuf); return rc; } @@ -875,18 +887,18 @@ if( rc==TH_OK ){ Th_Command *p = (Th_Command *)(pEntry->pData); const char **azArg = (const char **)argv; rc = p->xProc(interp, p->pContext, argc, azArg, argl); } - + /* If an error occurred, add this command to the stack trace report. */ if( rc==TH_ERROR ){ char *zRes; int nRes; char *zStack = 0; int nStack = 0; - + zRes = Th_TakeResult(interp, &nRes); if( TH_OK==Th_GetVar(interp, (char *)"::th_stack_trace", -1) ){ zStack = Th_TakeResult(interp, &nStack); } Th_ListAppend(interp, &zStack, &nStack, zFirst, zInput-zFirst); @@ -911,15 +923,15 @@ ** ** Argument iFrame is interpreted as follows: ** ** * If iFrame is 0, this means the current frame. ** -** * If iFrame is negative, then the nth frame up the stack, where -** n is the absolute value of iFrame. A value of -1 means the +** * If iFrame is negative, then the nth frame up the stack, where +** n is the absolute value of iFrame. A value of -1 means the ** calling procedure. ** -** * If iFrame is +ve, then the nth frame from the bottom of the +** * If iFrame is +ve, then the nth frame from the bottom of the ** stack. An iFrame value of 1 means the toplevel (global) frame. */ static Th_Frame *getFrame(Th_Interp *interp, int iFrame){ Th_Frame *p = interp->pFrame; int i; @@ -947,28 +959,28 @@ /* ** Evaluate th1 script (zProgram, nProgram) in the frame identified by ** argument iFrame. Leave either an error message or a result in the -** interpreter result and return a th1 error code (TH_OK, TH_ERROR, +** interpreter result and return a th1 error code (TH_OK, TH_ERROR, ** TH_RETURN, TH_CONTINUE or TH_BREAK). */ int Th_Eval(Th_Interp *interp, int iFrame, const char *zProgram, int nProgram){ int rc = TH_OK; Th_Frame *pSavedFrame = interp->pFrame; - /* Set Th_Interp.pFrame to the frame that this script is to be + /* Set Th_Interp.pFrame to the frame that this script is to be ** evaluated in. The current frame is saved in pSavedFrame and will ** be restored before this function returns. */ interp->pFrame = getFrame(interp, iFrame); if( !interp->pFrame ){ rc = TH_ERROR; }else{ int nInput = nProgram; - + if( nInput<0 ){ nInput = th_strlen(zProgram); } rc = thEvalLocal(interp, zProgram, nInput); } @@ -994,13 +1006,13 @@ ** array key name. */ static int thAnalyseVarname( const char *zVarname, int nVarname, - const char **pzOuter, /* OUT: Pointer to scalar/array name */ + const char **pzOuter, /* OUT: Pointer to scalar/array name */ int *pnOuter, /* OUT: Number of bytes at *pzOuter */ - const char **pzInner, /* OUT: Pointer to array key (or null) */ + const char **pzInner, /* OUT: Pointer to array key (or null) */ int *pnInner, /* OUT: Number of bytes at *pzInner */ int *pisGlobal /* OUT: Set to true if this is a global ref */ ){ const char *zOuter = zVarname; int nOuter; @@ -1041,13 +1053,28 @@ *pnInner = nInner; *pisGlobal = isGlobal; return TH_OK; } +/* +** The Find structure is used to return extra information to callers of the +** thFindValue function. The fields within it are populated by thFindValue +** as soon as the necessary information is available. Callers should check +** each field of interest upon return. +*/ + +struct Find { + Th_HashEntry *pValueEntry; /* Pointer to the scalar or array hash entry */ + Th_HashEntry *pElemEntry; /* Pointer to array element hash entry, if any */ + const char *zElem; /* Name of array element, if applicable */ + int nElem; /* Length of array element name, if applicable */ +}; +typedef struct Find Find; + /* ** Input string (zVar, nVar) contains a variable name. This function locates -** the Th_Variable structure associated with the named variable. The +** the Th_Variable structure associated with the named variable. The ** variable name may be a global or local scalar or array variable ** ** If the create argument is non-zero and the named variable does not exist ** it is created. Otherwise, an error is left in the interpreter result ** and NULL returned. @@ -1054,16 +1081,19 @@ ** ** If the arrayok argument is false and the named variable is an array, ** an error is left in the interpreter result and NULL returned. If ** arrayok is true an array name is Ok. */ + static Th_Variable *thFindValue( Th_Interp *interp, - const char *zVar, /* Pointer to variable name */ - int nVar, /* Number of bytes at nVar */ - int create, /* If true, create the variable if not found */ - int arrayok /* If true, an array is Ok. Otherwise array==error */ + const char *zVar, /* Pointer to variable name */ + int nVar, /* Number of bytes at nVar */ + int create, /* If true, create the variable if not found */ + int arrayok, /* If true, an array is Ok. Otherwise array==error */ + int noerror, /* If false, set interpreter result to error */ + Find *pFind /* If non-zero, place output here */ ){ const char *zOuter; int nOuter; const char *zInner; int nInner; @@ -1072,16 +1102,24 @@ Th_HashEntry *pEntry; Th_Frame *pFrame = interp->pFrame; Th_Variable *pValue; thAnalyseVarname(zVar, nVar, &zOuter, &nOuter, &zInner, &nInner, &isGlobal); + if( pFind ){ + memset(pFind, 0, sizeof(Find)); + pFind->zElem = zInner; + pFind->nElem = nInner; + } if( isGlobal ){ while( pFrame->pCaller ) pFrame = pFrame->pCaller; } pEntry = Th_HashFind(interp, pFrame->paVar, zOuter, nOuter, create); - assert(pEntry || !create); + assert(pEntry || create<=0); + if( pFind ){ + pFind->pValueEntry = pEntry; + } if( !pEntry ){ goto no_such_var; } pValue = (Th_Variable *)pEntry->pData; @@ -1092,20 +1130,26 @@ pEntry->pData = (void *)pValue; } if( zInner ){ if( pValue->zData ){ - Th_ErrorMessage(interp, "variable is a scalar:", zOuter, nOuter); + if( !noerror ){ + Th_ErrorMessage(interp, "variable is a scalar:", zOuter, nOuter); + } return 0; } if( !pValue->pHash ){ if( !create ){ goto no_such_var; } pValue->pHash = Th_HashNew(interp); } pEntry = Th_HashFind(interp, pValue->pHash, zInner, nInner, create); + assert(pEntry || create<=0); + if( pFind ){ + pFind->pElemEntry = pEntry; + } if( !pEntry ){ goto no_such_var; } pValue = (Th_Variable *)pEntry->pData; if( !pValue ){ @@ -1114,34 +1158,38 @@ pValue->nRef = 1; pEntry->pData = (void *)pValue; } }else{ if( pValue->pHash && !arrayok ){ - Th_ErrorMessage(interp, "variable is an array:", zOuter, nOuter); + if( !noerror ){ + Th_ErrorMessage(interp, "variable is an array:", zOuter, nOuter); + } return 0; } } return pValue; no_such_var: - Th_ErrorMessage(interp, "no such variable:", zVar, nVar); + if( !noerror ){ + Th_ErrorMessage(interp, "no such variable:", zVar, nVar); + } return 0; } /* -** String (zVar, nVar) must contain the name of a scalar variable or -** array member. Look up the variable, store its current value in +** String (zVar, nVar) must contain the name of a scalar variable or +** array member. Look up the variable, store its current value in ** the interpreter result and return TH_OK. ** ** If the named variable does not exist, return TH_ERROR and leave ** an error message in the interpreter result. */ int Th_GetVar(Th_Interp *interp, const char *zVar, int nVar){ Th_Variable *pValue; - pValue = thFindValue(interp, zVar, nVar, 0, 0); + pValue = thFindValue(interp, zVar, nVar, 0, 0, 0, 0); if( !pValue ){ return TH_ERROR; } if( !pValue->zData ){ Th_ErrorMessage(interp, "no such variable:", zVar, nVar); @@ -1153,11 +1201,12 @@ /* ** Return true if variable (zVar, nVar) exists. */ int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){ - return thFindValue(interp, zVar, nVar, 0, 0)!=0; + Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0); + return pValue && (pValue->zData || pValue->pHash); } /* ** String (zVar, nVar) must contain the name of a scalar variable or ** array member. If the variable does not exist it is created. The @@ -1165,19 +1214,19 @@ ** ** If (zVar, nVar) refers to an existing array, TH_ERROR is returned ** and an error message left in the interpreter result. */ int Th_SetVar( - Th_Interp *interp, - const char *zVar, + Th_Interp *interp, + const char *zVar, int nVar, const char *zValue, int nValue ){ Th_Variable *pValue; - pValue = thFindValue(interp, zVar, nVar, 1, 0); + pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0); if( !pValue ){ return TH_ERROR; } if( nValue<0 ){ @@ -1201,13 +1250,13 @@ ** Create a variable link so that accessing variable (zLocal, nLocal) is ** the same as accessing variable (zLink, nLink) in stack frame iFrame. */ int Th_LinkVar( Th_Interp *interp, /* Interpreter */ - const char *zLocal, int nLocal, /* Local varname */ + const char *zLocal, int nLocal, /* Local varname */ int iFrame, /* Stack frame of linked var */ - const char *zLink, int nLink /* Linked varname */ + const char *zLink, int nLink /* Linked varname */ ){ Th_Frame *pSavedFrame = interp->pFrame; Th_Frame *pFrame; Th_HashEntry *pEntry; Th_Variable *pValue; @@ -1216,11 +1265,11 @@ if( !pFrame ){ return TH_ERROR; } pSavedFrame = interp->pFrame; interp->pFrame = pFrame; - pValue = thFindValue(interp, zLink, nLink, 1, 1); + pValue = thFindValue(interp, zLink, nLink, 1, 1, 0, 0); interp->pFrame = pSavedFrame; pEntry = Th_HashFind(interp, interp->pFrame->paVar, zLocal, nLocal, 1); if( pEntry->pData ){ Th_ErrorMessage(interp, "variable exists:", zLocal, nLocal); @@ -1237,25 +1286,68 @@ ** an array, or an array member. If the identified variable exists, it ** is deleted and TH_OK returned. Otherwise, an error message is left ** in the interpreter result and TH_ERROR is returned. */ int Th_UnsetVar(Th_Interp *interp, const char *zVar, int nVar){ + Find find; Th_Variable *pValue; + Th_HashEntry *pEntry; + int rc = TH_ERROR; - pValue = thFindValue(interp, zVar, nVar, 1, 1); + pValue = thFindValue(interp, zVar, nVar, 0, 1, 0, &find); if( !pValue ){ - return TH_ERROR; - } - - Th_Free(interp, pValue->zData); - pValue->zData = 0; - if( pValue->pHash ){ - Th_HashIterate(interp, pValue->pHash, thFreeVariable, (void *)interp); - Th_HashDelete(interp, pValue->pHash); - pValue->pHash = 0; - } - return TH_OK; + return rc; + } + + if( pValue->zData || pValue->pHash ){ + rc = TH_OK; + }else { + Th_ErrorMessage(interp, "no such variable:", zVar, nVar); + } + + /* + ** The variable may be shared by more than one frame; therefore, make sure + ** it is actually freed prior to freeing the parent structure. The values + ** for the variable must be freed now so the variable appears undefined in + ** all frames. The hash entry in the current frame must also be deleted + ** now; otherwise, if the current stack frame is later popped, it will try + ** to delete a variable which has already been freed. + */ + if( find.zElem ){ + pEntry = find.pElemEntry; + }else{ + pEntry = find.pValueEntry; + } + assert( pEntry ); + assert( pValue ); + if( thFreeVariable(pEntry, (void *)interp) ){ + if( find.zElem ){ + Th_Variable *pValue2 = find.pValueEntry->pData; + Th_HashFind(interp, pValue2->pHash, find.zElem, find.nElem, -1); + }else if( pEntry->pData ){ + Th_Free(interp, pEntry->pData); + pEntry->pData = 0; + } + }else{ + if( pValue->zData ){ + Th_Free(interp, pValue->zData); + pValue->zData = 0; + } + if( pValue->pHash ){ + Th_HashIterate(interp, pValue->pHash, thFreeVariable, (void *)interp); + Th_HashDelete(interp, pValue->pHash); + pValue->pHash = 0; + } + if( find.zElem ){ + Th_Variable *pValue2 = find.pValueEntry->pData; + Th_HashFind(interp, pValue2->pHash, find.zElem, find.nElem, -1); + } + } + if( !find.zElem ){ + Th_HashFind(interp, interp->pFrame->paVar, zVar, nVar, -1); + } + return rc; } /* ** Return an allocated buffer containing a copy of string (z, n). The ** caller is responsible for eventually calling Th_Free() to free @@ -1290,11 +1382,11 @@ if( interp ){ char *zRes = 0; int nRes = 0; Th_SetVar(interp, (char *)"::th_stack_trace", -1, 0, 0); - + Th_StringAppend(interp, &zRes, &nRes, zPre, -1); if( zRes[nRes-1]=='"' ){ Th_StringAppend(interp, &zRes, &nRes, z, n); Th_StringAppend(interp, &zRes, &nRes, (const char *)"\"", 1); }else{ @@ -1372,12 +1464,12 @@ return (char *)Th_Malloc(pInterp, 1); } } -/* -** Wrappers around the supplied malloc() and free() +/* +** Wrappers around the supplied malloc() and free() */ void *Th_Malloc(Th_Interp *pInterp, int nByte){ void *p = pInterp->pVtab->xMalloc(nByte); if( p ){ memset(p, 0, nByte); @@ -1389,16 +1481,16 @@ pInterp->pVtab->xFree(z); } } /* -** Install a new th1 command. +** Install a new th1 command. ** ** If a command of the same name already exists, it is deleted automatically. */ int Th_CreateCommand( - Th_Interp *interp, + Th_Interp *interp, const char *zName, /* New command name */ Th_CommandProc xProc, /* Command callback proc */ void *pContext, /* Value to pass as second arg to xProc */ void (*xDel)(Th_Interp *, void *) /* Command destructor callback */ ){ @@ -1416,27 +1508,27 @@ } pCommand->xProc = xProc; pCommand->pContext = pContext; pCommand->xDel = xDel; pEntry->pData = (void *)pCommand; - + return TH_OK; } /* -** Rename the existing command (zName, nName) to (zNew, nNew). If nNew is 0, +** Rename the existing command (zName, nName) to (zNew, nNew). If nNew is 0, ** the command is deleted instead of renamed. ** ** If successful, TH_OK is returned. If command zName does not exist, or -** if command zNew already exists, an error message is left in the +** if command zNew already exists, an error message is left in the ** interpreter result and TH_ERROR is returned. */ int Th_RenameCommand( - Th_Interp *interp, - const char *zName, /* Existing command name */ + Th_Interp *interp, + const char *zName, /* Existing command name */ int nName, /* Number of bytes at zName */ - const char *zNew, /* New command name */ + const char *zNew, /* New command name */ int nNew /* Number of bytes at zNew */ ){ Th_HashEntry *pEntry; Th_HashEntry *pNewEntry; @@ -1490,11 +1582,11 @@ ** If an error occurs (if (zList, nList) is not a valid list) an error ** message is left in the interpreter result and TH_ERROR returned. ** ** If successful, *pnCount is set to the number of elements in the list. ** panElem is set to point at an array of *pnCount integers - the lengths -** of the element values. *pazElem is set to point at an array of +** of the element values. *pazElem is set to point at an array of ** pointers to buffers containing the array element's data. ** ** To free the arrays allocated at *pazElem and *panElem, the caller ** should call Th_Free() on *pazElem only. Exactly one such call to ** Th_Free() must be made per call to Th_SplitList(). @@ -1516,13 +1608,13 @@ ** Th_Free(interp, azElem); ** */ int Th_SplitList( Th_Interp *interp, - const char *zList, /* Pointer to buffer containing list */ + const char *zList, /* Pointer to buffer containing list */ int nList, /* Number of bytes at zList */ - char ***pazElem, /* OUT: Array of pointers to element data */ + char ***pazElem, /* OUT: Array of pointers to element data */ int **panElem, /* OUT: Array of element data lengths */ int *pnCount /* OUT: Number of elements in list */ ){ int rc; interp->isListMode = 1; @@ -1533,16 +1625,16 @@ } return rc; } /* -** Append a new element to an existing th1 list. The element to append +** Append a new element to an existing th1 list. The element to append ** to the list is (zElem, nElem). ** ** A pointer to the existing list must be stored at *pzList when this -** function is called. The length must be stored in *pnList. The value -** of *pzList must either be NULL (in which case *pnList must be 0), or +** function is called. The length must be stored in *pnList. The value +** of *pzList must either be NULL (in which case *pnList must be 0), or ** a pointer to memory obtained from Th_Malloc(). ** ** This function calls Th_Free() to free the buffer at *pzList and sets ** *pzList to point to a new buffer containing the new list value. *pnList ** is similarly updated before returning. The return value is always TH_OK. @@ -1559,13 +1651,13 @@ ** Th_Free(interp, zList); ** */ int Th_ListAppend( Th_Interp *interp, /* Interpreter context */ - char **pzList, /* IN/OUT: Ptr to ptr to list */ + char **pzList, /* IN/OUT: Ptr to ptr to list */ int *pnList, /* IN/OUT: Current length of *pzList */ - const char *zElem, /* Data to append */ + const char *zElem, /* Data to append */ int nElem /* Length of nElem */ ){ Buffer output; int i; @@ -1614,13 +1706,13 @@ ** Append a new element to an existing th1 string. This function uses ** the same interface as the Th_ListAppend() function. */ int Th_StringAppend( Th_Interp *interp, /* Interpreter context */ - char **pzStr, /* IN/OUT: Ptr to ptr to list */ + char **pzStr, /* IN/OUT: Ptr to ptr to list */ int *pnStr, /* IN/OUT: Current length of *pzStr */ - const char *zElem, /* Data to append */ + const char *zElem, /* Data to append */ int nElem /* Length of nElem */ ){ char *zNew; int nNew; @@ -1638,11 +1730,11 @@ *pnStr = nNew; return TH_OK; } -/* +/* ** Delete an interpreter. */ void Th_DeleteInterp(Th_Interp *interp){ assert(interp->pFrame); assert(0==interp->pFrame->pCaller); @@ -1659,11 +1751,11 @@ /* Delete the interpreter structure itself. */ Th_Free(interp, (void *)interp); } -/* +/* ** Create a new interpreter. */ Th_Interp * Th_CreateInterp(Th_Vtab *pVtab){ Th_Interp *p; @@ -1682,10 +1774,11 @@ ** the expression module means the Th_Expr() and exprXXX() functions. */ typedef struct Operator Operator; struct Operator { const char *zOp; + int nOp; int eOp; int iPrecedence; int eArgType; }; typedef struct Expr Expr; @@ -1693,11 +1786,11 @@ Operator *pOp; Expr *pParent; Expr *pLeft; Expr *pRight; - char *zValue; /* Pointer to literal value */ + char *zValue; /* Pointer to literal value */ int nValue; /* Length of literal value buffer */ }; /* Unary operators */ #define OP_UNARY_MINUS 2 @@ -1739,62 +1832,95 @@ #define ARG_NUMBER 2 #define ARG_STRING 3 static Operator aOperator[] = { - {"(", OP_OPEN_BRACKET, -1, 0}, - {")", OP_CLOSE_BRACKET, -1, 0}, + {"(", 1, OP_OPEN_BRACKET, -1, 0}, + {")", 1, OP_CLOSE_BRACKET, -1, 0}, /* Note: all unary operators have (iPrecedence==1) */ - {"-", OP_UNARY_MINUS, 1, ARG_NUMBER}, - {"+", OP_UNARY_PLUS, 1, ARG_NUMBER}, - {"~", OP_BITWISE_NOT, 1, ARG_INTEGER}, - {"!", OP_LOGICAL_NOT, 1, ARG_INTEGER}, + {"-", 1, OP_UNARY_MINUS, 1, ARG_NUMBER}, + {"+", 1, OP_UNARY_PLUS, 1, ARG_NUMBER}, + {"~", 1, OP_BITWISE_NOT, 1, ARG_INTEGER}, + {"!", 1, OP_LOGICAL_NOT, 1, ARG_INTEGER}, /* Binary operators. It is important to the parsing in Th_Expr() that - * the two-character symbols ("==") appear before the one-character + * the two-character symbols ("==") appear before the one-character * ones ("="). And that the priorities of all binary operators are * integers between 2 and 12. */ - {"<<", OP_LEFTSHIFT, 4, ARG_INTEGER}, - {">>", OP_RIGHTSHIFT, 4, ARG_INTEGER}, - {"<=", OP_LE, 5, ARG_NUMBER}, - {">=", OP_GE, 5, ARG_NUMBER}, - {"==", OP_EQ, 6, ARG_NUMBER}, - {"!=", OP_NE, 6, ARG_NUMBER}, - {"eq", OP_SEQ, 7, ARG_STRING}, - {"ne", OP_SNE, 7, ARG_STRING}, - {"&&", OP_LOGICAL_AND, 11, ARG_INTEGER}, - {"||", OP_LOGICAL_OR, 12, ARG_INTEGER}, - - {"*", OP_MULTIPLY, 2, ARG_NUMBER}, - {"/", OP_DIVIDE, 2, ARG_NUMBER}, - {"%", OP_MODULUS, 2, ARG_INTEGER}, - {"+", OP_ADD, 3, ARG_NUMBER}, - {"-", OP_SUBTRACT, 3, ARG_NUMBER}, - {"<", OP_LT, 5, ARG_NUMBER}, - {">", OP_GT, 5, ARG_NUMBER}, - {"&", OP_BITWISE_AND, 8, ARG_INTEGER}, - {"^", OP_BITWISE_XOR, 9, ARG_INTEGER}, - {"|", OP_BITWISE_OR, 10, ARG_INTEGER}, - - {0,0,0,0} + {"<<", 2, OP_LEFTSHIFT, 4, ARG_INTEGER}, + {">>", 2, OP_RIGHTSHIFT, 4, ARG_INTEGER}, + {"<=", 2, OP_LE, 5, ARG_NUMBER}, + {">=", 2, OP_GE, 5, ARG_NUMBER}, + {"==", 2, OP_EQ, 6, ARG_NUMBER}, + {"!=", 2, OP_NE, 6, ARG_NUMBER}, + {"eq", 2, OP_SEQ, 7, ARG_STRING}, + {"ne", 2, OP_SNE, 7, ARG_STRING}, + {"&&", 2, OP_LOGICAL_AND, 11, ARG_INTEGER}, + {"||", 2, OP_LOGICAL_OR, 12, ARG_INTEGER}, + + {"*", 1, OP_MULTIPLY, 2, ARG_NUMBER}, + {"/", 1, OP_DIVIDE, 2, ARG_NUMBER}, + {"%", 1, OP_MODULUS, 2, ARG_INTEGER}, + {"+", 1, OP_ADD, 3, ARG_NUMBER}, + {"-", 1, OP_SUBTRACT, 3, ARG_NUMBER}, + {"<", 1, OP_LT, 5, ARG_NUMBER}, + {">", 1, OP_GT, 5, ARG_NUMBER}, + {"&", 1, OP_BITWISE_AND, 8, ARG_INTEGER}, + {"^", 1, OP_BITWISE_XOR, 9, ARG_INTEGER}, + {"|", 1, OP_BITWISE_OR, 10, ARG_INTEGER}, + + {0,0,0,0,0} }; /* -** The first part of the string (zInput,nInput) contains a number. -** Set *pnVarname to the number of bytes in the numeric string. +** The first part of the string (zInput,nInput) contains an integer. +** Set *pnVarname to the number of bytes in the numeric string. */ -static int thNextNumber( - Th_Interp *interp, - const char *zInput, - int nInput, +static int thNextInteger( + Th_Interp *interp, + const char *zInput, + int nInput, int *pnLiteral ){ int i; + int (*isdigit)(char) = th_isdigit; + char c; + + if( nInput<2) return TH_ERROR; + assert(zInput[0]=='0'); + c = zInput[1]; + if( c>='A' && c<='Z' ) c += 'a' - 'A'; + if( c=='x' ){ + isdigit = th_ishexdig; + }else if( c!='o' && c!='b' ){ + return TH_ERROR; + } + for(i=2; i<nInput; i++){ + c = zInput[i]; + if( !isdigit(c) ){ + break; + } + } + *pnLiteral = i; + return TH_OK; +} + +/* +** The first part of the string (zInput,nInput) contains a number. +** Set *pnVarname to the number of bytes in the numeric string. +*/ +static int thNextNumber( + Th_Interp *interp, + const char *zInput, + int nInput, + int *pnLiteral +){ + int i = 0; int seenDot = 0; - for(i=0; i<nInput; i++){ + for(; i<nInput; i++){ char c = zInput[i]; if( (seenDot || c!='.') && !th_isdigit(c) ) break; if( c=='.' ) seenDot = 1; } *pnLiteral = i; @@ -1855,11 +1981,11 @@ if( eArgType==ARG_NUMBER ){ if( (zLeft==0 || TH_OK==Th_ToInt(0, zLeft, nLeft, &iLeft)) && (zRight==0 || TH_OK==Th_ToInt(0, zRight, nRight, &iRight)) ){ eArgType = ARG_INTEGER; - }else if( + }else if( (zLeft && TH_OK!=Th_ToDouble(interp, zLeft, nLeft, &fLeft)) || (zRight && TH_OK!=Th_ToDouble(interp, zRight, nRight, &fRight)) ){ /* A type error. */ rc = TH_ERROR; @@ -1867,28 +1993,30 @@ }else if( eArgType==ARG_INTEGER ){ rc = Th_ToInt(interp, zLeft, nLeft, &iLeft); if( rc==TH_OK && zRight ){ rc = Th_ToInt(interp, zRight, nRight, &iRight); } - } + } } if( rc==TH_OK && eArgType==ARG_INTEGER ){ int iRes = 0; switch( pExpr->pOp->eOp ) { case OP_MULTIPLY: iRes = iLeft*iRight; break; case OP_DIVIDE: - if(!iRight){ + if( !iRight ){ Th_ErrorMessage(interp, "Divide by 0:", zLeft, nLeft); - return TH_ERROR; + rc = TH_ERROR; + goto finish; } iRes = iLeft/iRight; break; case OP_MODULUS: - if(!iRight){ + if( !iRight ){ Th_ErrorMessage(interp, "Modulo by 0:", zLeft, nLeft); - return TH_ERROR; + rc = TH_ERROR; + goto finish; } iRes = iLeft%iRight; break; case OP_ADD: iRes = iLeft+iRight; break; case OP_SUBTRACT: iRes = iLeft-iRight; break; @@ -1905,26 +2033,36 @@ case OP_BITWISE_OR: iRes = iLeft|iRight; break; case OP_LOGICAL_AND: iRes = iLeft&&iRight; break; case OP_LOGICAL_OR: iRes = iLeft||iRight; break; case OP_UNARY_MINUS: iRes = -iLeft; break; case OP_UNARY_PLUS: iRes = +iLeft; break; + case OP_BITWISE_NOT: iRes = ~iLeft; break; case OP_LOGICAL_NOT: iRes = !iLeft; break; default: assert(!"Internal error"); } Th_SetResultInt(interp, iRes); }else if( rc==TH_OK && eArgType==ARG_NUMBER ){ switch( pExpr->pOp->eOp ) { - case OP_MULTIPLY: Th_SetResultDouble(interp, fLeft*fRight); break; - case OP_DIVIDE: Th_SetResultDouble(interp, fLeft/fRight); break; - case OP_ADD: Th_SetResultDouble(interp, fLeft+fRight); break; - case OP_SUBTRACT: Th_SetResultDouble(interp, fLeft-fRight); break; - case OP_LT: Th_SetResultInt(interp, fLeft<fRight); break; - case OP_GT: Th_SetResultInt(interp, fLeft>fRight); break; - case OP_LE: Th_SetResultInt(interp, fLeft<=fRight); break; - case OP_GE: Th_SetResultInt(interp, fLeft>=fRight); break; - case OP_EQ: Th_SetResultInt(interp, fLeft==fRight); break; - case OP_NE: Th_SetResultInt(interp, fLeft!=fRight); break; + case OP_MULTIPLY: Th_SetResultDouble(interp, fLeft*fRight); break; + case OP_DIVIDE: + if( fRight==0.0 ){ + Th_ErrorMessage(interp, "Divide by 0:", zLeft, nLeft); + rc = TH_ERROR; + goto finish; + } + Th_SetResultDouble(interp, fLeft/fRight); + break; + case OP_ADD: Th_SetResultDouble(interp, fLeft+fRight); break; + case OP_SUBTRACT: Th_SetResultDouble(interp, fLeft-fRight); break; + case OP_LT: Th_SetResultInt(interp, fLeft<fRight); break; + case OP_GT: Th_SetResultInt(interp, fLeft>fRight); break; + case OP_LE: Th_SetResultInt(interp, fLeft<=fRight); break; + case OP_GE: Th_SetResultInt(interp, fLeft>=fRight); break; + case OP_EQ: Th_SetResultInt(interp, fLeft==fRight); break; + case OP_NE: Th_SetResultInt(interp, fLeft!=fRight); break; + case OP_UNARY_MINUS: Th_SetResultDouble(interp, -fLeft); break; + case OP_UNARY_PLUS: Th_SetResultDouble(interp, +fLeft); break; default: assert(!"Internal error"); } }else if( rc==TH_OK ){ int iEqual = 0; assert( eArgType==ARG_STRING ); @@ -1935,10 +2073,12 @@ case OP_SEQ: Th_SetResultInt(interp, iEqual); break; case OP_SNE: Th_SetResultInt(interp, !iEqual); break; default: assert(!"Internal error"); } } + + finish: Th_Free(interp, zLeft); Th_Free(interp, zRight); } @@ -1958,11 +2098,11 @@ #define ISTERM(x) (apToken[x] && (!apToken[x]->pOp || apToken[x]->pLeft)) for(jj=0; jj<nToken; jj++){ if( apToken[jj]->pOp && apToken[jj]->pOp->eOp==OP_OPEN_BRACKET ){ int nNest = 1; - int iLeft = jj; + int iLeft = jj; for(jj++; jj<nToken; jj++){ Operator *pOp = apToken[jj]->pOp; if( pOp && pOp->eOp==OP_OPEN_BRACKET ) nNest++; if( pOp && pOp->eOp==OP_CLOSE_BRACKET ) nNest--; @@ -1984,11 +2124,12 @@ } iLeft = 0; for(jj=nToken-1; jj>=0; jj--){ if( apToken[jj] ){ - if( apToken[jj]->pOp && apToken[jj]->pOp->iPrecedence==1 && iLeft>0 ){ + if( apToken[jj]->pOp && apToken[jj]->pOp->iPrecedence==1 + && iLeft>0 && ISTERM(iLeft) ){ apToken[jj]->pLeft = apToken[iLeft]; apToken[jj]->pLeft->pParent = apToken[jj]; apToken[iLeft] = 0; } iLeft = jj; @@ -1999,13 +2140,11 @@ for(jj=0; jj<nToken; jj++){ Expr *pToken = apToken[jj]; if( apToken[jj] ){ if( pToken->pOp && !pToken->pLeft && pToken->pOp->iPrecedence==i ){ int iRight = jj+1; - - iRight = jj+1; - for(iRight=jj+1; !apToken[iRight] && iRight<nToken; iRight++); + for(; !apToken[iRight] && iRight<nToken; iRight++); if( iRight==nToken || iLeft<0 || !ISTERM(iRight) || !ISTERM(iLeft) ){ return TH_ERROR; } pToken->pLeft = apToken[iLeft]; apToken[iLeft] = 0; @@ -2032,18 +2171,19 @@ /* ** Parse a string containing a TH expression to a list of tokens. */ static int exprParse( Th_Interp *interp, /* Interpreter to leave error message in */ - const char *zExpr, /* Pointer to input string */ + const char *zExpr, /* Pointer to input string */ int nExpr, /* Number of bytes at zExpr */ Expr ***papToken, /* OUT: Array of tokens. */ int *pnToken /* OUT: Size of token array */ ){ int i; int rc = TH_OK; + int nNest = 0; int nToken = 0; Expr **apToken = 0; for(i=0; rc==TH_OK && i<nExpr; ){ char c = zExpr[i]; @@ -2052,12 +2192,17 @@ }else{ Expr *pNew = (Expr *)Th_Malloc(interp, sizeof(Expr)); const char *z = &zExpr[i]; switch (c) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': + case '0': + if( thNextInteger(interp, z, nExpr-i, &pNew->nValue)==TH_OK ){ + break; + } + /* fall through */ + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': thNextNumber(interp, z, nExpr-i, &pNew->nValue); break; case '$': thNextVarname(interp, z, nExpr-i, &pNew->nValue); @@ -2079,20 +2224,40 @@ break; } default: { int j; - for(j=0; aOperator[j].zOp; j++){ - int nOp; - if( aOperator[j].iPrecedence==1 && nToken>0 ){ + const char *zOp; + for(j=0; (zOp=aOperator[j].zOp); j++){ + int nOp = aOperator[j].nOp; + int nRemain = nExpr - i; + int isMatch = 0; + if( nRemain>=nOp && 0==memcmp(zOp, &zExpr[i], nOp) ){ + isMatch = 1; + } + if( isMatch ){ + if( aOperator[j].eOp==OP_CLOSE_BRACKET ){ + nNest--; + }else if( nRemain>nOp ){ + if( aOperator[j].eOp==OP_OPEN_BRACKET ){ + nNest++; + } + }else{ + /* + ** This is not really a match because this operator cannot + ** legally appear at the end of the string. + */ + isMatch = 0; + } + } + if( nToken>0 && aOperator[j].iPrecedence==1 ){ Expr *pPrev = apToken[nToken-1]; if( !pPrev->pOp || pPrev->pOp->eOp==OP_CLOSE_BRACKET ){ continue; } } - nOp = th_strlen((const char *)aOperator[j].zOp); - if( (nExpr-i)>=nOp && 0==memcmp(aOperator[j].zOp, &zExpr[i], nOp) ){ + if( isMatch ){ pNew->pOp = &aOperator[j]; i += nOp; break; } } @@ -2107,11 +2272,11 @@ memcpy(pNew->zValue, z, pNew->nValue); i += pNew->nValue; } if( (nToken%16)==0 ){ /* Grow the apToken array. */ - Expr **apTokenOld = apToken; + Expr **apTokenOld = apToken; apToken = Th_Malloc(interp, sizeof(Expr *)*(nToken+16)); memcpy(apToken, apTokenOld, sizeof(Expr *)*nToken); } /* Put the new token at the end of the apToken array */ @@ -2121,10 +2286,14 @@ Th_Free(interp, pNew); rc = TH_ERROR; } } } + + if( nNest!=0 ){ + rc = TH_ERROR; + } *papToken = apToken; *pnToken = nToken; return rc; } @@ -2132,11 +2301,11 @@ /* ** Evaluate the string (zExpr, nExpr) as a Th expression. Store ** the result in the interpreter interp and return TH_OK if ** successful. If an error occurs, store an error message in ** the interpreter result and return an error code. -*/ +*/ int Th_Expr(Th_Interp *interp, const char *zExpr, int nExpr){ int rc; /* Return Code */ int i; /* Loop counter */ int nToken = 0; @@ -2149,11 +2318,11 @@ /* Parse the expression to a list of tokens. */ rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken); /* If the parsing was successful, create an expression tree from ** the parsed list of tokens. If successful, apToken[0] is set - ** to point to the root of the expression tree. + ** to point to the root of the expression tree. */ if( rc==TH_OK ){ rc = exprMakeTree(interp, apToken, nToken); } @@ -2187,16 +2356,17 @@ /* ** Iterate through all values currently stored in the hash table. Invoke ** the callback function xCallback for each entry. The second argument ** passed to xCallback is a copy of the fourth argument passed to this -** function. +** function. The return value from the callback function xCallback is +** ignored. */ void Th_HashIterate( - Th_Interp *interp, + Th_Interp *interp, Th_Hash *pHash, - void (*xCallback)(Th_HashEntry *pEntry, void *pContext), + int (*xCallback)(Th_HashEntry *pEntry, void *pContext), void *pContext ){ int i; for(i=0; i<TH_HASHSIZE; i++){ Th_HashEntry *pEntry; @@ -2207,14 +2377,15 @@ } } } /* -** Helper function for Th_HashDelete(). +** Helper function for Th_HashDelete(). Always returns non-zero. */ -static void xFreeHashEntry(Th_HashEntry *pEntry, void *pContext){ +static int xFreeHashEntry(Th_HashEntry *pEntry, void *pContext){ Th_Free((Th_Interp *)pContext, (void *)pEntry); + return 1; } /* ** Free a hash-table previously allocated by Th_HashNew(). */ @@ -2224,14 +2395,14 @@ Th_Free(interp, pHash); } } /* -** This function is used to insert or delete hash table items, or to +** This function is used to insert or delete hash table items, or to ** query a hash table for an existing item. ** -** If parameter op is less than zero, then the hash-table element +** If parameter op is less than zero, then the hash-table element ** identified by (zKey, nKey) is removed from the hash-table if it ** exists. NULL is returned. ** ** Otherwise, if the hash-table contains an item with key (zKey, nKey), ** a pointer to the associated Th_HashEntry is returned. If parameter @@ -2238,11 +2409,11 @@ ** op is greater than zero, then a new entry is added if one cannot ** be found. If op is zero, then NULL is returned if the item is ** not already present in the hash-table. */ Th_HashEntry *Th_HashFind( - Th_Interp *interp, + Th_Interp *interp, Th_Hash *pHash, const char *zKey, int nKey, int op /* -ve = delete, 0 = find, +ve = insert */ ){ @@ -2306,11 +2477,12 @@ ** '\f' 0x0C ** '\r' 0x0D ** ** Whitespace characters have the 0x01 flag set. Decimal digits have the ** 0x2 flag set. Single byte printable characters have the 0x4 flag set. -** Alphabet characters have the 0x8 bit set. +** Alphabet characters have the 0x8 bit set. Hexadecimal digits have the +** 0x20 flag set. ** ** The special list characters have the 0x10 flag set ** ** { } [ ] \ ; ' " ** @@ -2319,14 +2491,14 @@ */ static unsigned char aCharProp[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, /* 0x0. */ 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x1. */ 5, 4, 20, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x2. */ - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 20, 4, 4, 4, 4, /* 0x3. */ - 4, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, /* 0x4. */ + 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 4, 20, 4, 4, 4, 4, /* 0x3. */ + 4, 44, 44, 44, 44, 44, 44, 12, 12, 12, 12, 12, 12, 12, 12, 12, /* 0x4. */ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 20, 20, 20, 4, 4, /* 0x5. */ - 4, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, /* 0x6. */ + 4, 44, 44, 44, 44, 44, 44, 12, 12, 12, 12, 12, 12, 12, 12, 12, /* 0x6. */ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 20, 4, 20, 4, 4, /* 0x7. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x8. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x9. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xA. */ @@ -2350,15 +2522,26 @@ return (aCharProp[(unsigned char)c] & 0x11); } int th_isalnum(char c){ return (aCharProp[(unsigned char)c] & 0x0A); } +int th_isalpha(char c){ + return (aCharProp[(unsigned char)c] & 0x08); +} +int th_ishexdig(char c){ + return (aCharProp[(unsigned char)c] & 0x20); +} +int th_isoctdig(char c){ + return ((c|7) == '7'); +} +int th_isbindig(char c){ + return ((c|1) == '1'); +} #ifndef LONGDOUBLE_TYPE # define LONGDOUBLE_TYPE long double #endif -typedef char u8; /* ** Return TRUE if z is a pure numeric string. Return FALSE if the ** string contains any character which is not part of a number. If @@ -2458,33 +2641,58 @@ return z - zBegin; } /* ** Try to convert the string passed as arguments (z, n) to an integer. -** If successful, store the result in *piOut and return TH_OK. +** If successful, store the result in *piOut and return TH_OK. ** -** If the string cannot be converted to an integer, return TH_ERROR. -** If the interp argument is not NULL, leave an error message in the +** If the string cannot be converted to an integer, return TH_ERROR. +** If the interp argument is not NULL, leave an error message in the ** interpreter result too. */ int Th_ToInt(Th_Interp *interp, const char *z, int n, int *piOut){ int i = 0; int iOut = 0; + int base = 10; + int (*isdigit)(char) = th_isdigit; if( n<0 ){ n = th_strlen(z); } - if( n>0 && (z[0]=='-' || z[0]=='+') ){ + if( n>1 && (z[0]=='-' || z[0]=='+') ){ i = 1; + } + if( (n-i)>2 && z[i]=='0' ){ + if( z[i+1]=='x' || z[i+1]=='X' ){ + i += 2; + base = 16; + isdigit = th_ishexdig; + }else if( z[i+1]=='o' || z[i+1]=='O' ){ + i += 2; + base = 8; + isdigit = th_isoctdig; + }else if( z[i+1]=='b' || z[i+1]=='B' ){ + i += 2; + base = 2; + isdigit = th_isbindig; + } } for(; i<n; i++){ - if( !th_isdigit(z[i]) ){ + char c = z[i]; + if( !isdigit(c) ){ Th_ErrorMessage(interp, "expected integer, got: \"", z, n); return TH_ERROR; } - iOut = iOut * 10 + (z[i] - 48); + if( c>='a' ){ + c -= 'a'-10; + }else if( c>='A' ){ + c -= 'A'-10; + }else{ + c -= '0'; + } + iOut = iOut * base + c; } if( n>0 && z[0]=='-' ){ iOut *= -1; } @@ -2493,20 +2701,20 @@ return TH_OK; } /* ** Try to convert the string passed as arguments (z, n) to a double. -** If successful, store the result in *pfOut and return TH_OK. +** If successful, store the result in *pfOut and return TH_OK. ** -** If the string cannot be converted to a double, return TH_ERROR. -** If the interp argument is not NULL, leave an error message in the +** If the string cannot be converted to a double, return TH_ERROR. +** If the interp argument is not NULL, leave an error message in the ** interpreter result too. */ int Th_ToDouble( - Th_Interp *interp, - const char *z, - int n, + Th_Interp *interp, + const char *z, + int n, double *pfOut ){ if( !sqlite3IsNumber((const char *)z, 0) ){ Th_ErrorMessage(interp, "expected number, got: \"", z, n); return TH_ERROR; @@ -2528,13 +2736,13 @@ if( iVal<0 ){ isNegative = 1; iVal = iVal * -1; } *(--z) = '\0'; - *(--z) = (char)(48+(iVal%10)); - while( (iVal = (iVal/10))>0 ){ - *(--z) = (char)(48+(iVal%10)); + *(--z) = (char)(48+((unsigned)iVal%10)); + while( (iVal = ((unsigned)iVal/10))>0 ){ + *(--z) = (char)(48+((unsigned)iVal%10)); assert(z>zBuf); } if( isNegative ){ *(--z) = '-'; } @@ -2547,33 +2755,33 @@ ** the double fVal and return TH_OK. */ int Th_SetResultDouble(Th_Interp *interp, double fVal){ int i; /* Iterator variable */ double v = fVal; /* Input value */ - char zBuf[128]; /* Output buffer */ - char *z = zBuf; /* Output cursor */ + char zBuf[128]; /* Output buffer */ + char *z = zBuf; /* Output cursor */ int iDot = 0; /* Digit after which to place decimal point */ int iExp = 0; /* Exponent (NN in eNN) */ - const char *zExp; /* String representation of iExp */ + const char *zExp; /* String representation of iExp */ /* Precision: */ #define INSIGNIFICANT 0.000000000001 #define ROUNDER 0.0000000000005 double insignificant = INSIGNIFICANT; /* If the real value is negative, write a '-' character to the * output and transform v to the corresponding positive number. - */ + */ if( v<0.0 ){ *z++ = '-'; v *= -1.0; } - /* Normalize v to a value between 1.0 and 10.0. Integer + /* Normalize v to a value between 1.0 and 10.0. Integer * variable iExp is set to the exponent. i.e the original * value is (v * 10^iExp) (or the negative thereof). - */ + */ if( v>0.0 ){ while( (v+ROUNDER)>=10.0 ) { iExp++; v *= 0.1; } while( (v+ROUNDER)<1.0 ) { iExp--; v *= 10.0; } } v += ROUNDER; Index: src/th.h ================================================================== --- src/th.h +++ src/th.h @@ -1,87 +1,87 @@ /* This header file defines the external interface to the custom Scripting -** Language (TH) interpreter. TH is very similar to TCL but is not an +** Language (TH) interpreter. TH is very similar to Tcl but is not an ** exact clone. */ /* ** Before creating an interpreter, the application must allocate and ** populate an instance of the following structure. It must remain valid ** for the lifetime of the interpreter. */ +typedef struct Th_Vtab Th_Vtab; struct Th_Vtab { void *(*xMalloc)(unsigned int); void (*xFree)(void *); }; -typedef struct Th_Vtab Th_Vtab; /* ** Opaque handle for interpeter. */ typedef struct Th_Interp Th_Interp; -/* -** Create and delete interpreters. +/* +** Create and delete interpreters. */ Th_Interp * Th_CreateInterp(Th_Vtab *pVtab); void Th_DeleteInterp(Th_Interp *); -/* +/* ** Evaluate an TH program in the stack frame identified by parameter ** iFrame, according to the following rules: ** ** * If iFrame is 0, this means the current frame. ** -** * If iFrame is negative, then the nth frame up the stack, where n is +** * If iFrame is negative, then the nth frame up the stack, where n is ** the absolute value of iFrame. A value of -1 means the calling ** procedure. ** ** * If iFrame is +ve, then the nth frame from the bottom of the stack. ** An iFrame value of 1 means the toplevel (global) frame. */ int Th_Eval(Th_Interp *interp, int iFrame, const char *zProg, int nProg); /* -** Evaluate a TH expression. The result is stored in the +** Evaluate a TH expression. The result is stored in the ** interpreter result. */ int Th_Expr(Th_Interp *interp, const char *, int); -/* +/* ** Access TH variables in the current stack frame. If the variable name -** begins with "::", the lookup is in the top level (global) frame. +** begins with "::", the lookup is in the top level (global) frame. */ int Th_ExistsVar(Th_Interp *, const char *, int); int Th_GetVar(Th_Interp *, const char *, int); int Th_SetVar(Th_Interp *, const char *, int, const char *, int); int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int); int Th_UnsetVar(Th_Interp *, const char *, int); typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *); -/* -** Register new commands. +/* +** Register new commands. */ int Th_CreateCommand( - Th_Interp *interp, - const char *zName, + Th_Interp *interp, + const char *zName, /* int (*xProc)(Th_Interp *, void *, int, const char **, int *), */ Th_CommandProc xProc, void *pContext, void (*xDel)(Th_Interp *, void *) ); -/* +/* ** Delete or rename commands. */ int Th_RenameCommand(Th_Interp *, const char *, int, const char *, int); -/* -** Push a new stack frame (local variable context) onto the interpreter -** stack, call the function supplied as parameter xCall with the two -** context arguments, +/* +** Push a new stack frame (local variable context) onto the interpreter +** stack, call the function supplied as parameter xCall with the two +** context arguments, ** ** xCall(interp, pContext1, pContext2) ** ** , then pop the frame off of the interpreter stack. The value returned ** by the xCall() function is returned as the result of this function. @@ -93,21 +93,21 @@ int (*xCall)(Th_Interp *, void *pContext1, void *pContext2), void *pContext1, void *pContext2 ); -/* +/* ** Valid return codes for xProc callbacks. */ #define TH_OK 0 #define TH_ERROR 1 #define TH_BREAK 2 #define TH_RETURN 3 #define TH_CONTINUE 4 -/* -** Set and get the interpreter result. +/* +** Set and get the interpreter result. */ int Th_SetResult(Th_Interp *, const char *, int); const char *Th_GetResult(Th_Interp *, int *); char *Th_TakeResult(Th_Interp *, int *); @@ -115,26 +115,26 @@ ** Set an error message as the interpreter result. This also ** sets the global stack-trace variable $::th_stack_trace. */ int Th_ErrorMessage(Th_Interp *, const char *, const char *, int); -/* +/* ** Access the memory management functions associated with the specified ** interpreter. */ void *Th_Malloc(Th_Interp *, int); void Th_Free(Th_Interp *, void *); -/* +/* ** Functions for handling TH lists. */ int Th_ListAppend(Th_Interp *, char **, int *, const char *, int); int Th_SplitList(Th_Interp *, const char *, int, char ***, int **, int *); int Th_StringAppend(Th_Interp *, char **, int *, const char *, int); -/* +/* ** Functions for handling numbers and pointers. */ int Th_ToInt(Th_Interp *, const char *, int, int *); int Th_ToDouble(Th_Interp *, const char *, int, double *); int Th_SetResultInt(Th_Interp *, int); @@ -145,11 +145,15 @@ */ int th_strlen(const char *); int th_isdigit(char); int th_isspace(char); int th_isalnum(char); +int th_isalpha(char); int th_isspecial(char); +int th_ishexdig(char); +int th_isoctdig(char); +int th_isbindig(char); char *th_strdup(Th_Interp *interp, const char *z, int n); /* ** Interfaces to register the language extensions. */ @@ -157,12 +161,16 @@ int th_register_sqlite(Th_Interp *interp); /* th_sqlite.c */ int th_register_vfs(Th_Interp *interp); /* th_vfs.c */ int th_register_testvfs(Th_Interp *interp); /* th_testvfs.c */ #ifdef FOSSIL_ENABLE_TCL -int th_register_tcl(Th_Interp *interp, void *pContext); /* th_tcl.c */ -int unloadTcl(Th_Interp *interp, void *pContext); /* th_tcl.c */ +/* +** Interfaces to the full Tcl core library from "th_tcl.c". +*/ +int th_register_tcl(Th_Interp *, void *); +int unloadTcl(Th_Interp *, void *); +int evaluateTclWithEvents(Th_Interp *, void *, const char *, int, int, int); #endif /* ** General purpose hash table from th_lang.c. */ @@ -174,15 +182,15 @@ int nKey; Th_HashEntry *pNext; /* Internal use only */ }; Th_Hash *Th_HashNew(Th_Interp *); void Th_HashDelete(Th_Interp *, Th_Hash *); -void Th_HashIterate(Th_Interp*,Th_Hash*,void (*x)(Th_HashEntry*, void*),void*); +void Th_HashIterate(Th_Interp*,Th_Hash*,int (*x)(Th_HashEntry*, void*),void*); Th_HashEntry *Th_HashFind(Th_Interp*, Th_Hash*, const char*, int, int); /* ** Useful functions from th_lang.c. */ int Th_WrongNumArgs(Th_Interp *interp, const char *zMsg); -typedef struct Th_SubCommand {char *zName; Th_CommandProc xProc;} Th_SubCommand; -int Th_CallSubCommand(Th_Interp*,void*,int,const char**,int*,Th_SubCommand*); +typedef struct Th_SubCommand {const char *zName; Th_CommandProc xProc;} Th_SubCommand; +int Th_CallSubCommand(Th_Interp*,void*,int,const char**,int*,const Th_SubCommand*); Index: src/th_lang.c ================================================================== --- src/th_lang.c +++ src/th_lang.c @@ -1,12 +1,12 @@ /* -** This file contains the implementation of all of the TH language -** built-in commands. +** This file contains the implementation of all of the TH language +** built-in commands. ** -** All built-in commands are implemented using the public interface -** declared in th.h, so this file serves as both a part of the language +** All built-in commands are implemented using the public interface +** declared in th.h, so this file serves as both a part of the language ** implementation and an example of how to extend the language with ** new commands. */ #include "config.h" @@ -18,19 +18,19 @@ Th_ErrorMessage(interp, "wrong # args: should be \"", zMsg, -1); return TH_ERROR; } /* -** Syntax: +** Syntax: ** ** catch script ?varname? */ static int catch_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ int rc; if( argc!=2 && argc!=3 ){ @@ -47,19 +47,19 @@ Th_SetResultInt(interp, rc); return TH_OK; } /* -** TH Syntax: +** TH Syntax: ** ** if expr1 body1 ?elseif expr2 body2? ? ?else? bodyN? */ static int if_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ int rc = TH_OK; int iCond; /* Result of evaluating expression */ @@ -94,19 +94,19 @@ wrong_args: return Th_WrongNumArgs(interp, "if ..."); } /* -** TH Syntax: +** TH Syntax: ** ** expr expr */ static int expr_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ if( argc!=2 ){ return Th_WrongNumArgs(interp, "expr expression"); } @@ -113,11 +113,11 @@ return Th_Expr(interp, argv[1], argl[1]); } /* -** Evaluate the th1 script (zBody, nBody) in the local stack frame. +** Evaluate the th1 script (zBody, nBody) in the local stack frame. ** Return the result of the evaluation, except if the result ** is TH_CONTINUE, return TH_OK instead. */ static int eval_loopbody(Th_Interp *interp, const char *zBody, int nBody){ int rc = Th_Eval(interp, 0, zBody, nBody); @@ -126,19 +126,19 @@ } return rc; } /* -** TH Syntax: +** TH Syntax: ** ** for init condition incr script */ static int for_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ int rc; int iCond; @@ -147,11 +147,11 @@ } /* Evaluate the 'init' script */ rc = Th_Eval(interp, 0, argv[1], -1); - while( rc==TH_OK + while( rc==TH_OK && TH_OK==(rc = Th_Expr(interp, argv[2], -1)) && TH_OK==(rc = Th_ToInt(interp, Th_GetResult(interp, 0), -1, &iCond)) && iCond && TH_OK==(rc = eval_loopbody(interp, argv[4], argl[4])) ){ @@ -161,45 +161,45 @@ if( rc==TH_BREAK ) rc = TH_OK; return rc; } /* -** TH Syntax: +** TH Syntax: ** ** list ?arg1 ?arg2? ...? */ static int list_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ char *zList = 0; int nList = 0; int i; for(i=1; i<argc; i++){ Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]); } - + Th_SetResult(interp, zList, nList); Th_Free(interp, zList); return TH_OK; } /* -** TH Syntax: +** TH Syntax: ** ** lindex list index */ static int lindex_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ int iElem; int rc; @@ -227,19 +227,19 @@ return rc; } /* -** TH Syntax: +** TH Syntax: ** ** llength list */ static int llength_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ int nElem; int rc; @@ -254,19 +254,19 @@ return rc; } /* -** TH Syntax: +** TH Syntax: ** ** set varname ?value? */ static int set_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ if( argc!=2 && argc!=3 ){ return Th_WrongNumArgs(interp, "set varname ?value?"); } @@ -277,30 +277,30 @@ return Th_GetVar(interp, argv[1], argl[1]); } /* ** When a new command is created using the built-in [proc] command, an -** instance of the following structure is allocated and populated. A -** pointer to the structure is passed as the context (second) argument +** instance of the following structure is allocated and populated. A +** pointer to the structure is passed as the context (second) argument ** to function proc_call1() when the new command is executed. */ typedef struct ProcDefn ProcDefn; struct ProcDefn { int nParam; /* Number of formal (non "args") parameters */ - char **azParam; /* Parameter names */ + char **azParam; /* Parameter names */ int *anParam; /* Lengths of parameter names */ - char **azDefault; /* Default values */ + char **azDefault; /* Default values */ int *anDefault; /* Lengths of default values */ int hasArgs; /* True if there is an "args" parameter */ - char *zProgram; /* Body of proc */ + char *zProgram; /* Body of proc */ int nProgram; /* Number of bytes at zProgram */ - char *zUsage; /* Usage message */ + char *zUsage; /* Usage message */ int nUsage; /* Number of bytes at zUsage */ }; -/* This structure is used to temporarily store arguments passed to an -** invocation of a command created using [proc]. A pointer to an +/* This structure is used to temporarily store arguments passed to an +** invocation of a command created using [proc]. A pointer to an ** instance is passed as the second argument to the proc_call2() function. */ typedef struct ProcArgs ProcArgs; struct ProcArgs { int argc; @@ -323,11 +323,11 @@ ProcArgs *pArgs = (ProcArgs *)pContext2; /* Check if there are the right number of arguments. If there are ** not, generate a usage message for the command. */ - if( (pArgs->argc>(p->nParam+1) && !p->hasArgs) + if( (pArgs->argc>(p->nParam+1) && !p->hasArgs) || (pArgs->argc<=(p->nParam) && !p->azDefault[pArgs->argc-1]) ){ char *zUsage = 0; int nUsage = 0; Th_StringAppend(interp, &zUsage, &nUsage, pArgs->argv[0], pArgs->argl[0]); @@ -374,12 +374,12 @@ ** created using the [proc] command. The second argument, pContext, ** is a pointer to the associated ProcDefn structure. */ static int proc_call1( Th_Interp *interp, - void *pContext, - int argc, + void *pContext, + int argc, const char **argv, int *argl ){ int rc; @@ -400,36 +400,36 @@ } return rc; } /* -** This function is registered as the delete callback for all commands -** created using the built-in [proc] command. It is called automatically -** when a command created using [proc] is deleted. +** This function is registered as the delete callback for all commands +** created using the built-in [proc] command. It is called automatically +** when a command created using [proc] is deleted. ** ** It frees the ProcDefn structure allocated when the command was created. -*/ +*/ static void proc_del(Th_Interp *interp, void *pContext){ ProcDefn *p = (ProcDefn *)pContext; Th_Free(interp, (void *)p->zUsage); Th_Free(interp, (void *)p); } /* -** TH Syntax: +** TH Syntax: ** ** proc name arglist code */ static int proc_command( - Th_Interp *interp, - void *ctx, + Th_Interp *interp, + void *ctx, int argc, - const char **argv, + const char **argv, int *argl ){ int rc; - char *zName; + const char *zName; ProcDefn *p; int nByte; int i; char *zSpace; @@ -436,11 +436,11 @@ char **azParam; int *anParam; int nParam; - char *zUsage = 0; /* Build up a usage message here */ + char *zUsage = 0; /* Build up a usage message here */ int nUsage = 0; /* Number of bytes at zUsage */ if( argc!=4 ){ return Th_WrongNumArgs(interp, "proc name arglist code"); } @@ -448,14 +448,14 @@ return TH_ERROR; } /* Allocate the new ProcDefn structure. */ nByte = sizeof(ProcDefn) + /* ProcDefn structure */ - (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */ - (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */ + (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */ + (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */ argl[3] + /* zProgram */ - argl[2]; /* Space for copies of parameter names and default values */ + argl[2]; /* Space for copies of parameter names and default values */ p = (ProcDefn *)Th_Malloc(interp, nByte); /* If the last parameter in the parameter list is "args", then set the ** ProcDefn.hasArgs flag. The "args" parameter does not require an ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays. @@ -472,11 +472,11 @@ p->anDefault = (int *)&p->azDefault[nParam]; p->zProgram = (char *)&p->anDefault[nParam]; memcpy(p->zProgram, argv[3], argl[3]); p->nProgram = argl[3]; zSpace = &p->zProgram[p->nProgram]; - + for(i=0; i<nParam; i++){ char **az; int *an; int n; if( Th_SplitList(interp, azParam[i], anParam[i], &az, &an, &n) ){ @@ -521,11 +521,11 @@ } p->zUsage = zUsage; p->nUsage = nUsage; /* Register the new command with the th1 interpreter. */ - zName = (char *)argv[1]; + zName = argv[1]; rc = Th_CreateCommand(interp, zName, proc_call1, (void *)p, proc_del); if( rc==TH_OK ){ Th_SetResult(interp, 0, 0); } @@ -537,40 +537,40 @@ Th_Free(interp, zUsage); return TH_ERROR; } /* -** TH Syntax: +** TH Syntax: ** ** rename oldcmd newcmd */ static int rename_command( - Th_Interp *interp, - void *ctx, + Th_Interp *interp, + void *ctx, int argc, - const char **argv, + const char **argv, int *argl ){ if( argc!=3 ){ return Th_WrongNumArgs(interp, "rename oldcmd newcmd"); } return Th_RenameCommand(interp, argv[1], argl[1], argv[2], argl[2]); } /* -** TH Syntax: +** TH Syntax: ** ** break ?value...? ** continue ?value...? ** ok ?value...? ** error ?value...? */ static int simple_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ if( argc!=1 && argc!=2 ){ return Th_WrongNumArgs(interp, "return ?value?"); } @@ -579,19 +579,19 @@ } return FOSSIL_PTR_TO_INT(ctx); } /* -** TH Syntax: +** TH Syntax: ** ** return ?-code code? ?value? */ static int return_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ int iCode = TH_RETURN; if( argc<1 || argc>4 ){ return Th_WrongNumArgs(interp, "return ?-code code? ?value?"); @@ -638,11 +638,11 @@ iRes = nLeft-nRight; } if( iRes<0 ) iRes = -1; if( iRes>0 ) iRes = 1; - + return Th_SetResultInt(interp, iRes); } /* ** TH Syntax: @@ -650,33 +650,34 @@ ** string first NEEDLE HAYSTACK */ static int string_first_command( Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl ){ - const char *zNeedle; int nNeedle; - const char *zHaystack; int nHaystack; - int i; int iRes = -1; if( argc!=4 ){ return Th_WrongNumArgs(interp, "string first needle haystack"); } - zNeedle = argv[2]; nNeedle = argl[2]; - zHaystack = argv[3]; nHaystack = argl[3]; - for(i=0; i<(nHaystack-nNeedle); i++){ - if( 0==memcmp(zNeedle, &zHaystack[i], nNeedle) ){ - iRes = i; - break; + if( nNeedle && nHaystack && nNeedle<=nHaystack ){ + const char *zNeedle = argv[2]; + const char *zHaystack = argv[3]; + int i; + + for(i=0; i<=(nHaystack-nNeedle); i++){ + if( 0==memcmp(zNeedle, &zHaystack[i], nNeedle) ){ + iRes = i; + break; + } } } - + return Th_SetResultInt(interp, iRes); } /* ** TH Syntax: @@ -711,33 +712,34 @@ ** string last NEEDLE HAYSTACK */ static int string_last_command( Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl ){ - const char *zNeedle; int nNeedle; - const char *zHaystack; int nHaystack; - int i; int iRes = -1; if( argc!=4 ){ - return Th_WrongNumArgs(interp, "string first needle haystack"); + return Th_WrongNumArgs(interp, "string last needle haystack"); } - zNeedle = argv[2]; nNeedle = argl[2]; - zHaystack = argv[3]; nHaystack = argl[3]; - for(i=nHaystack-nNeedle-1; i>=0; i--){ - if( 0==memcmp(zNeedle, &zHaystack[i], nNeedle) ){ - iRes = i; - break; + if( nNeedle && nHaystack && nNeedle<=nHaystack ){ + const char *zNeedle = argv[2]; + const char *zHaystack = argv[3]; + int i; + + for(i=nHaystack-nNeedle; i>=0; i--){ + if( 0==memcmp(zNeedle, &zHaystack[i], nNeedle) ){ + iRes = i; + break; + } } } - + return Th_SetResultInt(interp, iRes); } /* ** TH Syntax: @@ -867,11 +869,11 @@ ** TH Syntax: ** ** unset VAR */ static int unset_command( - Th_Interp *interp, + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl ){ @@ -880,21 +882,21 @@ } return Th_UnsetVar(interp, argv[1], argl[1]); } int Th_CallSubCommand( - Th_Interp *interp, + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl, - Th_SubCommand *aSub + const Th_SubCommand *aSub ){ if( argc>1 ){ int i; for(i=0; aSub[i].zName; i++){ - char *zName = (char *)aSub[i].zName; + const char *zName = aSub[i].zName; if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){ return aSub[i].xProc(interp, ctx, argc, argv, argl); } } } @@ -916,17 +918,17 @@ ** string length STRING ** string range STRING FIRST LAST ** string repeat STRING COUNT */ static int string_command( - Th_Interp *interp, + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl ){ - Th_SubCommand aSub[] = { + static const Th_SubCommand aSub[] = { { "compare", string_compare_command }, { "first", string_first_command }, { "is", string_is_command }, { "last", string_last_command }, { "length", string_length_command }, @@ -944,34 +946,34 @@ ** TH Syntax: ** ** info exists VARNAME */ static int info_command( - Th_Interp *interp, + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl ){ - Th_SubCommand aSub[] = { + static const Th_SubCommand aSub[] = { { "exists", info_exists_command }, { 0, 0 } }; return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub); } /* -** Convert the script level frame specification (used by the commands -** [uplevel] and [upvar]) in (zFrame, nFrame) to an integer frame as +** Convert the script level frame specification (used by the commands +** [uplevel] and [upvar]) in (zFrame, nFrame) to an integer frame as ** used by Th_LinkVar() and Th_Eval(). If successful, write the integer ** frame level to *piFrame and return TH_OK. Otherwise, return TH_ERROR ** and leave an error message in the interpreter result. */ static int thToFrame( - Th_Interp *interp, - const char *zFrame, - int nFrame, + Th_Interp *interp, + const char *zFrame, + int nFrame, int *piFrame ){ int iFrame; if( th_isdigit(zFrame[0]) ){ int rc = Th_ToInt(interp, zFrame, nFrame, &iFrame); @@ -992,11 +994,11 @@ ** TH Syntax: ** ** uplevel ?LEVEL? SCRIPT */ static int uplevel_command( - Th_Interp *interp, + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl ){ @@ -1010,19 +1012,19 @@ } return Th_Eval(interp, iFrame, argv[argc-1], -1); } /* -** TH Syntax: +** TH Syntax: ** ** upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR ...? */ static int upvar_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ int iVar = 1; int iFrame = -1; int rc = TH_OK; @@ -1030,32 +1032,32 @@ if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){ iVar++; } if( argc==iVar || (argc-iVar)%2 ){ - return Th_WrongNumArgs(interp, + return Th_WrongNumArgs(interp, "upvar frame othervar myvar ?othervar myvar...?"); } for(i=iVar; rc==TH_OK && i<argc; i=i+2){ rc = Th_LinkVar(interp, argv[i+1], argl[i+1], iFrame, argv[i], argl[i]); } return rc; } /* -** TH Syntax: +** TH Syntax: ** ** breakpoint ARGS ** ** This command does nothing at all. Its purpose in life is to serve ** as a point for setting breakpoints in a debugger. */ static int breakpoint_command( - Th_Interp *interp, - void *ctx, - int argc, - const char **argv, + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, int *argl ){ int cnt = 0; cnt++; return TH_OK; @@ -1078,11 +1080,11 @@ {"if", if_command, 0}, {"info", info_command, 0}, {"lindex", lindex_command, 0}, {"list", list_command, 0}, {"llength", llength_command, 0}, - {"proc", proc_command, 0}, + {"proc", proc_command, 0}, {"rename", rename_command, 0}, {"set", set_command, 0}, {"string", string_command, 0}, {"unset", unset_command, 0}, {"uplevel", uplevel_command, 0}, @@ -1089,17 +1091,17 @@ {"upvar", upvar_command, 0}, {"breakpoint", breakpoint_command, 0}, {"return", return_command, 0}, - {"break", simple_command, (void *)TH_BREAK}, - {"continue", simple_command, (void *)TH_CONTINUE}, - {"error", simple_command, (void *)TH_ERROR}, + {"break", simple_command, (void *)TH_BREAK}, + {"continue", simple_command, (void *)TH_CONTINUE}, + {"error", simple_command, (void *)TH_ERROR}, {0, 0, 0} }; - int i; + size_t i; /* Add the language commands. */ for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){ void *ctx; if ( !aCommand[i].zName || !aCommand[i].xProc ) continue; Index: src/th_main.c ================================================================== --- src/th_main.c +++ src/th_main.c @@ -28,15 +28,48 @@ ** interpreter creation and initialization process. */ #define TH_INIT_NONE ((u32)0x00000000) /* No flags. */ #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */ #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */ -#define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH commands re-added? */ +#define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */ #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */ -#define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */ +#define TH_INIT_MASK ((u32)0x0000000F) /* All possible init flags. */ + +/* +** Useful and/or "well-known" combinations of flag values. +*/ +#define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */ +#define TH_INIT_HOOK (TH_INIT_NEED_CONFIG | TH_INIT_FORCE_SETUP) +#define TH_INIT_FORBID_MASK (TH_INIT_FORCE_TCL) /* Illegal from a script. */ +#endif + +/* +** Flags set by functions in this file to keep track of integration state +** information. These flags should not be used outside of this file. +*/ +#define TH_STATE_CONFIG ((u32)0x00000010) /* We opened the config. */ +#define TH_STATE_REPOSITORY ((u32)0x00000020) /* We opened the repository. */ +#define TH_STATE_MASK ((u32)0x00000030) /* All possible state flags. */ + +#ifdef FOSSIL_ENABLE_TH1_HOOKS +/* +** These are the "well-known" TH1 error messages that occur when no hook is +** registered to be called prior to executing a command or processing a web +** page, respectively. If one of these errors is seen, it will not be sent +** or displayed to the remote user or local interactive user, respectively. +*/ +#define NO_COMMAND_HOOK_ERROR "no such command: command_hook" +#define NO_WEBPAGE_HOOK_ERROR "no such command: webpage_hook" #endif +/* +** These macros are used within this file to detect if the repository and +** configuration ("user") database are currently open. +*/ +#define Th_IsRepositoryOpen() (g.repositoryOpen) +#define Th_IsConfigOpen() (g.zConfigDbName!=0) + /* ** Global variable counting the number of outstanding calls to malloc() ** made by the th1 implementation. This is used to catch memory leaks ** in the interpreter. Obviously, it also means th1 is not threadsafe. */ @@ -57,10 +90,17 @@ nOutstandingMalloc--; } free(p); } static Th_Vtab vtab = { xMalloc, xFree }; + +/* +** Returns the number of outstanding TH1 memory allocations. +*/ +int Th_GetOutstandingMalloc(){ + return nOutstandingMalloc; +} /* ** Generate a TH1 trace message if debugging is enabled. */ void Th_Trace(const char *zFormat, ...){ @@ -67,10 +107,22 @@ va_list ap; va_start(ap, zFormat); blob_vappendf(&g.thLog, zFormat, ap); va_end(ap); } + +/* +** Forces input and output to be done via the CGI subsystem. +*/ +void Th_ForceCgi(int fullHttpReply){ + g.httpOut = stdout; + g.httpIn = stdin; + fossil_binary_mode(g.httpOut); + fossil_binary_mode(g.httpIn); + g.cgiOutput = 1; + g.fullHttpReply = fullHttpReply; +} /* ** Checks if the TH1 trace log needs to be enabled. If so, prepares ** it for use. */ @@ -90,26 +142,49 @@ fossil_print("\n------------------ BEGIN TRACE LOG ------------------\n"); fossil_print("%s", blob_str(&g.thLog)); fossil_print("\n------------------- END TRACE LOG -------------------\n"); } } + +/* +** TH1 command: httpize STRING +** +** Escape all characters of STRING which have special meaning in URI +** components. Return a new string result. +*/ +static int httpizeCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + char *zOut; + if( argc!=2 ){ + return Th_WrongNumArgs(interp, "httpize STRING"); + } + zOut = httpize((char*)argv[1], argl[1]); + Th_SetResult(interp, zOut, -1); + free(zOut); + return TH_OK; +} /* ** True if output is enabled. False if disabled. */ static int enableOutput = 1; /* -** TH command: enable_output BOOLEAN +** TH1 command: enable_output BOOLEAN ** ** Enable or disable the puts and hputs commands. */ static int enableOutputCmd( - Th_Interp *interp, - void *p, - int argc, - const char **argv, + Th_Interp *interp, + void *p, + int argc, + const char **argv, int *argl ){ int rc; if( argc<2 || argc>3 ){ return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN"); @@ -120,22 +195,23 @@ } return rc; } /* -** Return a name for a TH1 return code. +** Returns a name for a TH1 return code. */ const char *Th_ReturnCodeName(int rc, int nullIfOk){ static char zRc[32]; + switch( rc ){ case TH_OK: return nullIfOk ? 0 : "TH_OK"; case TH_ERROR: return "TH_ERROR"; case TH_BREAK: return "TH_BREAK"; case TH_RETURN: return "TH_RETURN"; case TH_CONTINUE: return "TH_CONTINUE"; default: { - sqlite3_snprintf(sizeof(zRc),zRc,"return code %d",rc); + sqlite3_snprintf(sizeof(zRc), zRc, "TH1 return code %d", rc); } } return zRc; } @@ -172,20 +248,91 @@ sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0); enableOutput = savedEnable; } /* -** TH command: puts STRING -** TH command: html STRING +** Convert name to an rid. This function was copied from name_to_typed_rid() +** in name.c; however, it has been modified to report TH1 script errors instead +** of "fatal errors". +*/ +int th1_name_to_typed_rid( + Th_Interp *interp, + const char *zName, + const char *zType +){ + int rid; + + if( zName==0 || zName[0]==0 ) return 0; + rid = symbolic_name_to_rid(zName, zType); + if( rid<0 ){ + Th_SetResult(interp, "ambiguous name", -1); + }else if( rid==0 ){ + Th_SetResult(interp, "name not found", -1); + } + return rid; +} + +/* +** Attempt to lookup the specified check-in and file name into an rid. +** This function was copied from artifact_from_ci_and_filename() in +** info.c; however, it has been modified to report TH1 script errors +** instead of "fatal errors". +*/ +int th1_artifact_from_ci_and_filename( + Th_Interp *interp, + const char *zCI, + const char *zFilename +){ + int cirid; + Blob err; + Manifest *pManifest; + ManifestFile *pFile; + + if( zCI==0 ){ + Th_SetResult(interp, "invalid check-in", -1); + return 0; + } + if( zFilename==0 ){ + Th_SetResult(interp, "invalid file name", -1); + return 0; + } + cirid = th1_name_to_typed_rid(interp, zCI, "*"); + blob_zero(&err); + pManifest = manifest_get(cirid, CFTYPE_MANIFEST, &err); + if( pManifest==0 ){ + if( blob_size(&err)>0 ){ + Th_SetResult(interp, blob_str(&err), blob_size(&err)); + }else{ + Th_SetResult(interp, "manifest not found", -1); + } + blob_reset(&err); + return 0; + } + blob_reset(&err); + manifest_file_rewind(pManifest); + while( (pFile = manifest_file_next(pManifest,0))!=0 ){ + if( fossil_strcmp(zFilename, pFile->zName)==0 ){ + int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); + manifest_destroy(pManifest); + return rid; + } + } + Th_SetResult(interp, "file name not found in manifest", -1); + return 0; +} + +/* +** TH1 command: puts STRING +** TH1 command: html STRING ** -** Output STRING escaped for HTML (html) or unchanged (puts). +** Output STRING escaped for HTML (html) or unchanged (puts). */ static int putsCmd( - Th_Interp *interp, - void *pConvert, - int argc, - const char **argv, + Th_Interp *interp, + void *pConvert, + int argc, + const char **argv, int *argl ){ if( argc!=2 ){ return Th_WrongNumArgs(interp, "puts STRING"); } @@ -192,19 +339,19 @@ sendText((char*)argv[1], argl[1], *(unsigned int*)pConvert); return TH_OK; } /* -** TH command: wiki STRING +** TH1 command: wiki STRING ** ** Render the input string as wiki. */ static int wikiCmd( - Th_Interp *interp, - void *p, - int argc, - const char **argv, + Th_Interp *interp, + void *p, + int argc, + const char **argv, int *argl ){ int flags = WIKI_INLINE | WIKI_NOBADLINKS | *(unsigned int*)p; if( argc!=2 ){ return Th_WrongNumArgs(interp, "wiki STRING"); @@ -217,20 +364,20 @@ } return TH_OK; } /* -** TH command: htmlize STRING +** TH1 command: htmlize STRING ** ** Escape all characters of STRING which have special meaning in HTML. ** Return a new string result. */ static int htmlizeCmd( - Th_Interp *interp, - void *p, - int argc, - const char **argv, + Th_Interp *interp, + void *p, + int argc, + const char **argv, int *argl ){ char *zOut; if( argc!=2 ){ return Th_WrongNumArgs(interp, "htmlize STRING"); @@ -240,95 +387,169 @@ free(zOut); return TH_OK; } /* -** TH command: date +** TH1 command: date ** ** Return a string which is the current time and date. If the ** -local option is used, the date appears using localtime instead ** of UTC. */ static int dateCmd( - Th_Interp *interp, - void *p, - int argc, - const char **argv, + Th_Interp *interp, + void *p, + int argc, + const char **argv, int *argl ){ char *zOut; if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){ - zOut = db_text("??", "SELECT datetime('now','localtime')"); + zOut = db_text("??", "SELECT datetime('now'%s)", timeline_utc()); }else{ zOut = db_text("??", "SELECT datetime('now')"); } Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } /* -** TH command: hascap STRING... +** TH1 command: hascap STRING... +** TH1 command: anoncap STRING... ** -** Return true if the user has all of the capabilities listed in STRING. +** Return true if the current user (hascap) or if the anonymous user +** (anoncap) has all of the capabilities listed in STRING. */ static int hascapCmd( - Th_Interp *interp, - void *p, - int argc, - const char **argv, + Th_Interp *interp, + void *p, + int argc, + const char **argv, int *argl ){ int rc = 0, i; if( argc<2 ){ return Th_WrongNumArgs(interp, "hascap STRING ..."); } for(i=1; i<argc && rc==0; i++){ - rc = login_has_capability((char*)argv[i],argl[i]); + rc = login_has_capability((char*)argv[i],argl[i],*(int*)p); } if( g.thTrace ){ Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc); } Th_SetResultInt(interp, rc); return TH_OK; } /* -** TH command: hasfeature STRING +** TH1 command: searchable STRING... +** +** Return true if searching in any of the document classes identified +** by STRING is enabled for the repository and user has the necessary +** capabilities to perform the search. +** +** Document classes: +** +** c Check-in comments +** d Embedded documentation +** t Tickets +** w Wiki +** +** To be clear, only one of the document classes identified by each STRING +** needs to be searchable in order for that argument to be true. But +** all arguments must be true for this routine to return true. Hence, to +** see if ALL document classes are searchable: +** +** if {[searchable c d t w]} {...} +** +** But to see if ANY document class is searchable: +** +** if {[searchable cdtw]} {...} +** +** This command is useful for enabling or disabling a "Search" entry +** on the menu bar. +*/ +static int searchableCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + int rc = 1, i, j; + unsigned int searchCap = search_restrict(SRCH_ALL); + if( argc<2 ){ + return Th_WrongNumArgs(interp, "hascap STRING ..."); + } + for(i=1; i<argc && rc; i++){ + int match = 0; + for(j=0; j<argl[i]; j++){ + switch( argv[i][j] ){ + case 'c': match |= searchCap & SRCH_CKIN; break; + case 'd': match |= searchCap & SRCH_DOC; break; + case 't': match |= searchCap & SRCH_TKT; break; + case 'w': match |= searchCap & SRCH_WIKI; break; + } + } + if( !match ) rc = 0; + } + if( g.thTrace ){ + Th_Trace("[searchable %#h] => %d<br />\n", argl[1], argv[1], rc); + } + Th_SetResultInt(interp, rc); + return TH_OK; +} + +/* +** TH1 command: hasfeature STRING ** ** Return true if the fossil binary has the given compile-time feature ** enabled. The set of features includes: ** ** "ssl" = FOSSIL_ENABLE_SSL +** "th1Docs" = FOSSIL_ENABLE_TH1_DOCS +** "th1Hooks" = FOSSIL_ENABLE_TH1_HOOKS ** "tcl" = FOSSIL_ENABLE_TCL ** "useTclStubs" = USE_TCL_STUBS ** "tclStubs" = FOSSIL_ENABLE_TCL_STUBS ** "tclPrivateStubs" = FOSSIL_ENABLE_TCL_PRIVATE_STUBS ** "json" = FOSSIL_ENABLE_JSON ** "markdown" = FOSSIL_ENABLE_MARKDOWN +** "unicodeCmdLine" = !BROKEN_MINGW_CMDLINE ** */ static int hasfeatureCmd( - Th_Interp *interp, - void *p, - int argc, - const char **argv, + Th_Interp *interp, + void *p, + int argc, + const char **argv, int *argl ){ int rc = 0; - char const * zArg; + const char *zArg; if( argc!=2 ){ return Th_WrongNumArgs(interp, "hasfeature STRING"); } - zArg = (char const*)argv[1]; + zArg = (const char *)argv[1]; if(NULL==zArg){ /* placeholder for following ifdefs... */ } #if defined(FOSSIL_ENABLE_SSL) else if( 0 == fossil_strnicmp( zArg, "ssl\0", 4 ) ){ rc = 1; } +#endif +#if defined(FOSSIL_ENABLE_TH1_DOCS) + else if( 0 == fossil_strnicmp( zArg, "th1Docs\0", 8 ) ){ + rc = 1; + } +#endif +#if defined(FOSSIL_ENABLE_TH1_HOOKS) + else if( 0 == fossil_strnicmp( zArg, "th1Hooks\0", 9 ) ){ + rc = 1; + } #endif #if defined(FOSSIL_ENABLE_TCL) else if( 0 == fossil_strnicmp( zArg, "tcl\0", 4 ) ){ rc = 1; } @@ -350,10 +571,15 @@ #endif #if defined(FOSSIL_ENABLE_JSON) else if( 0 == fossil_strnicmp( zArg, "json\0", 5 ) ){ rc = 1; } +#endif +#if !defined(BROKEN_MINGW_CMDLINE) + else if( 0 == fossil_strnicmp( zArg, "unicodeCmdLine\0", 15 ) ){ + rc = 1; + } #endif else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){ rc = 1; } if( g.thTrace ){ @@ -363,11 +589,11 @@ return TH_OK; } /* -** TH command: tclReady +** TH1 command: tclReady ** ** Return true if the fossil binary has the Tcl integration feature ** enabled and it is currently available for use by TH1 scripts. ** */ @@ -394,38 +620,39 @@ return TH_OK; } /* -** TH command: anycap STRING +** TH1 command: anycap STRING ** -** Return true if the user has any one of the capabilities listed in STRING. +** Return true if the current user user +** has any one of the capabilities listed in STRING. */ static int anycapCmd( - Th_Interp *interp, - void *p, - int argc, - const char **argv, + Th_Interp *interp, + void *p, + int argc, + const char **argv, int *argl ){ int rc = 0; int i; if( argc!=2 ){ return Th_WrongNumArgs(interp, "anycap STRING"); } for(i=0; rc==0 && i<argl[1]; i++){ - rc = login_has_capability((char*)&argv[1][i],1); + rc = login_has_capability((char*)&argv[1][i],1,0); } if( g.thTrace ){ Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc); } Th_SetResultInt(interp, rc); return TH_OK; } /* -** TH1 command: combobox NAME TEXT-LIST NUMLINES +** TH1 command: combobox NAME TEXT-LIST NUMLINES ** ** Generate an HTML combobox. NAME is both the name of the ** CGI parameter and the name of a variable that contains the ** currently selected value. TEXT-LIST is a list of possible ** values for the combobox. NUMLINES is 1 for a true combobox. @@ -432,13 +659,13 @@ ** If NUMLINES is greater than one then the display is a listbox ** with the number of lines given. */ static int comboboxCmd( Th_Interp *interp, - void *p, - int argc, - const char **argv, + void *p, + int argc, + const char **argv, int *argl ){ if( argc!=4 ){ return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES"); } @@ -463,11 +690,11 @@ sendText(z, -1, 0); free(z); blob_reset(&name); for(i=0; i<nElem; i++){ zH = htmlize((char*)azElem[i], aszElem[i]); - if( zValue && aszElem[i]==nValue + if( zValue && aszElem[i]==nValue && memcmp(zValue, azElem[i], nValue)==0 ){ z = mprintf("<option value=\"%s\" selected=\"selected\">%s</option>", zH, zH); }else{ z = mprintf("<option value=\"%s\">%s</option>", zH, zH); @@ -481,20 +708,20 @@ } return TH_OK; } /* -** TH1 command: linecount STRING MAX MIN +** TH1 command: linecount STRING MAX MIN ** ** Return one more than the number of \n characters in STRING. But ** never return less than MIN or more than MAX. */ static int linecntCmd( Th_Interp *interp, - void *p, - int argc, - const char **argv, + void *p, + int argc, + const char **argv, int *argl ){ const char *z; int size, n, i; int iMin, iMax; @@ -516,37 +743,341 @@ Th_SetResultInt(interp, n); return TH_OK; } /* -** TH1 command: repository ?BOOLEAN? +** TH1 command: repository ?BOOLEAN? ** ** Return the fully qualified file name of the open repository or an empty ** string if one is not currently open. Optionally, it will attempt to open ** the repository if the boolean argument is non-zero. */ static int repositoryCmd( Th_Interp *interp, - void *p, - int argc, - const char **argv, + void *p, + int argc, + const char **argv, int *argl ){ - int openRepository; - if( argc!=1 && argc!=2 ){ return Th_WrongNumArgs(interp, "repository ?BOOLEAN?"); } if( argc==2 ){ + int openRepository = 0; if( Th_ToInt(interp, argv[1], argl[1], &openRepository) ){ return TH_ERROR; } if( openRepository ) db_find_and_open_repository(OPEN_OK_NOT_FOUND, 0); } Th_SetResult(interp, g.zRepositoryName, -1); return TH_OK; } + +/* +** TH1 command: checkout ?BOOLEAN? +** +** Return the fully qualified directory name of the current checkout or an +** empty string if it is not available. Optionally, it will attempt to find +** the current checkout, opening the configuration ("user") database and the +** repository as necessary, if the boolean argument is non-zero. +*/ +static int checkoutCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + if( argc!=1 && argc!=2 ){ + return Th_WrongNumArgs(interp, "checkout ?BOOLEAN?"); + } + if( argc==2 ){ + int openCheckout = 0; + if( Th_ToInt(interp, argv[1], argl[1], &openCheckout) ){ + return TH_ERROR; + } + if( openCheckout ) db_open_local(0); + } + Th_SetResult(interp, g.zLocalRoot, -1); + return TH_OK; +} + +/* +** TH1 command: trace STRING +** +** Generate a TH1 trace message if debugging is enabled. +*/ +static int traceCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + if( argc!=2 ){ + return Th_WrongNumArgs(interp, "trace STRING"); + } + if( g.thTrace ){ + Th_Trace("%s", argv[1]); + } + Th_SetResult(interp, 0, 0); + return TH_OK; +} + +/* +** TH1 command: globalState NAME ?DEFAULT? +** +** Returns a string containing the value of the specified global state +** variable -OR- the specified default value. Currently, the supported +** items are: +** +** "checkout" = The active local checkout directory, if any. +** "configuration" = The active configuration database file name, +** if any. +** "executable" = The fully qualified executable file name. +** "flags" = The TH1 initialization flags. +** "log" = The error log file name, if any. +** "repository" = The active local repository file name, if +** any. +** "top" = The base path for the active server instance, +** if applicable. +** "user" = The active user name, if any. +** "vfs" = The SQLite VFS in use, if overridden. +** +** Attempts to query for unsupported global state variables will result +** in a script error. Additional global state variables may be exposed +** in the future. +** +** See also: checkout, repository, setting +*/ +static int globalStateCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + const char *zDefault = 0; + if( argc!=2 && argc!=3 ){ + return Th_WrongNumArgs(interp, "globalState NAME ?DEFAULT?"); + } + if( argc==3 ){ + zDefault = argv[2]; + } + if( fossil_strnicmp(argv[1], "checkout\0", 9)==0 ){ + Th_SetResult(interp, g.zLocalRoot ? g.zLocalRoot : zDefault, -1); + return TH_OK; + }else if( fossil_strnicmp(argv[1], "configuration\0", 14)==0 ){ + Th_SetResult(interp, g.zConfigDbName ? g.zConfigDbName : zDefault, -1); + return TH_OK; + }else if( fossil_strnicmp(argv[1], "executable\0", 11)==0 ){ + Th_SetResult(interp, g.nameOfExe ? g.nameOfExe : zDefault, -1); + return TH_OK; + }else if( fossil_strnicmp(argv[1], "flags\0", 6)==0 ){ + Th_SetResultInt(interp, g.th1Flags); + return TH_OK; + }else if( fossil_strnicmp(argv[1], "log\0", 4)==0 ){ + Th_SetResult(interp, g.zErrlog ? g.zErrlog : zDefault, -1); + return TH_OK; + }else if( fossil_strnicmp(argv[1], "repository\0", 11)==0 ){ + Th_SetResult(interp, g.zRepositoryName ? g.zRepositoryName : zDefault, -1); + return TH_OK; + }else if( fossil_strnicmp(argv[1], "top\0", 4)==0 ){ + Th_SetResult(interp, g.zTop ? g.zTop : zDefault, -1); + return TH_OK; + }else if( fossil_strnicmp(argv[1], "user\0", 5)==0 ){ + Th_SetResult(interp, g.zLogin ? g.zLogin : zDefault, -1); + return TH_OK; + }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){ + Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1); + return TH_OK; + }else{ + Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]); + return TH_ERROR; + } +} + +/* +** TH1 command: getParameter NAME ?DEFAULT? +** +** Return the value of the specified query parameter or the specified default +** value when there is no matching query parameter. +*/ +static int getParameterCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + const char *zDefault = 0; + if( argc!=2 && argc!=3 ){ + return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?"); + } + if( argc==3 ){ + zDefault = argv[2]; + } + Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1); + return TH_OK; +} + +/* +** TH1 command: setParameter NAME VALUE +** +** Sets the value of the specified query parameter. +*/ +static int setParameterCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + if( argc!=3 ){ + return Th_WrongNumArgs(interp, "setParameter NAME VALUE"); + } + cgi_replace_parameter(mprintf("%s", argv[1]), mprintf("%s", argv[2])); + return TH_OK; +} + +/* +** TH1 command: reinitialize ?FLAGS? +** +** Reinitializes the TH1 interpreter using the specified flags. +*/ +static int reinitializeCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + u32 flags = TH_INIT_DEFAULT; + if( argc!=1 && argc!=2 ){ + return Th_WrongNumArgs(interp, "reinitialize ?FLAGS?"); + } + if( argc==2 ){ + int iFlags; + if( Th_ToInt(interp, argv[1], argl[1], &iFlags) ){ + return TH_ERROR; + }else{ + flags = (u32)iFlags; + } + } + Th_FossilInit(flags & ~TH_INIT_FORBID_MASK); + Th_SetResult(interp, 0, 0); + return TH_OK; +} + +/* +** TH1 command: render STRING +** +** Renders the template and writes the results. +*/ +static int renderCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + int rc; + if( argc!=2 ){ + return Th_WrongNumArgs(interp, "render STRING"); + } + rc = Th_Render(argv[1]); + Th_SetResult(interp, 0, 0); + return rc; +} + +/* +** TH1 command: styleHeader TITLE +** +** Render the configured style header. +*/ +static int styleHeaderCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + if( argc!=2 ){ + return Th_WrongNumArgs(interp, "styleHeader TITLE"); + } + if( Th_IsRepositoryOpen() ){ + style_header("%s", argv[1]); + Th_SetResult(interp, 0, 0); + return TH_OK; + }else{ + Th_SetResult(interp, "repository unavailable", -1); + return TH_ERROR; + } +} + +/* +** TH1 command: styleFooter +** +** Render the configured style footer. +*/ +static int styleFooterCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + if( argc!=1 ){ + return Th_WrongNumArgs(interp, "styleFooter"); + } + if( Th_IsRepositoryOpen() ){ + style_footer(); + Th_SetResult(interp, 0, 0); + return TH_OK; + }else{ + Th_SetResult(interp, "repository unavailable", -1); + return TH_ERROR; + } +} + +/* +** TH1 command: artifact ID ?FILENAME? +** +** Attempts to locate the specified artifact and return its contents. An +** error is generated if the repository is not open or the artifact cannot +** be found. +*/ +static int artifactCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + if( argc!=2 && argc!=3 ){ + return Th_WrongNumArgs(interp, "artifact ID ?FILENAME?"); + } + if( Th_IsRepositoryOpen() ){ + int rid; + Blob content; + if( argc==3 ){ + rid = th1_artifact_from_ci_and_filename(interp, argv[1], argv[2]); + }else{ + rid = th1_name_to_typed_rid(interp, argv[1], "*"); + } + if( rid!=0 && content_get(rid, &content) ){ + Th_SetResult(interp, blob_str(&content), blob_size(&content)); + blob_reset(&content); + return TH_OK; + }else{ + return TH_ERROR; + } + }else{ + Th_SetResult(interp, "repository unavailable", -1); + return TH_ERROR; + } +} #ifdef _WIN32 # include <windows.h> #else # include <sys/time.h> @@ -576,27 +1107,27 @@ getrusage(RUSAGE_SELF, &s); if( piUser ){ *piUser = ((sqlite3_uint64)s.ru_utime.tv_sec)*1000000 + s.ru_utime.tv_usec; } if( piKernel ){ - *piKernel = + *piKernel = ((sqlite3_uint64)s.ru_stime.tv_sec)*1000000 + s.ru_stime.tv_usec; } #endif } /* -** TH1 command: utime +** TH1 command: utime ** ** Return the number of microseconds of CPU time consumed by the current ** process in user space. */ static int utimeCmd( Th_Interp *interp, - void *p, - int argc, - const char **argv, + void *p, + int argc, + const char **argv, int *argl ){ sqlite3_uint64 x; char zUTime[50]; getCpuTimes(&x, 0); @@ -604,20 +1135,20 @@ Th_SetResult(interp, zUTime, -1); return TH_OK; } /* -** TH1 command: stime +** TH1 command: stime ** ** Return the number of microseconds of CPU time consumed by the current ** process in system space. */ static int stimeCmd( Th_Interp *interp, - void *p, - int argc, - const char **argv, + void *p, + int argc, + const char **argv, int *argl ){ sqlite3_uint64 x; char zUTime[50]; getCpuTimes(0, &x); @@ -626,20 +1157,20 @@ return TH_OK; } /* -** TH1 command: randhex N +** TH1 command: randhex N ** -** Return N*2 random hexadecimal digits with N<50. If N is omitted, +** Return N*2 random hexadecimal digits with N<50. If N is omitted, ** use a value of 10. */ static int randhexCmd( Th_Interp *interp, - void *p, - int argc, - const char **argv, + void *p, + int argc, + const char **argv, int *argl ){ int n; unsigned char aRand[50]; unsigned char zOut[100]; @@ -660,11 +1191,11 @@ Th_SetResult(interp, (const char *)zOut, -1); return TH_OK; } /* -** TH1 command: query SQL CODE +** TH1 command: query SQL CODE ** ** Run the SQL query given by the SQL argument. For each row in the result ** set, run CODE. ** ** In SQL, parameters such as $var are filled in using the value of variable @@ -671,13 +1202,13 @@ ** "var". Result values are stored in variables with the column name prior ** to each invocation of CODE. */ static int queryCmd( Th_Interp *interp, - void *p, - int argc, - const char **argv, + void *p, + int argc, + const char **argv, int *argl ){ sqlite3_stmt *pStmt; int rc; const char *zSql; @@ -737,16 +1268,16 @@ rc = sqlite3_finalize(pStmt); if( rc!=SQLITE_OK ){ Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1); return TH_ERROR; } - } + } return res; } /* -** TH1 command: setting name +** TH1 command: setting name ** ** Gets and returns the value of the specified Fossil setting. */ #define SETTING_WRONGNUMARGS "setting ?-strict? ?--? name" static int settingCmd( @@ -787,11 +1318,11 @@ } return rc; } /* -** TH1 command: regexp ?-nocase? ?--? exp string +** TH1 command: regexp ?-nocase? ?--? exp string ** ** Checks the string against the specified regular expression and returns ** non-zero if it matches. If the regular expression is invalid or cannot ** be compiled, an error will be generated. */ @@ -828,10 +1359,166 @@ rc = TH_ERROR; } re_free(pRe); return rc; } + +/* +** TH1 command: http ?-asynchronous? ?--? url ?payload? +** +** Perform an HTTP or HTTPS request for the specified URL. If a +** payload is present, it will be interpreted as text/plain and +** the POST method will be used; otherwise, the GET method will +** be used. Upon success, if the -asynchronous option is used, an +** empty string is returned as the result; otherwise, the response +** from the server is returned as the result. Synchronous requests +** are not currently implemented. +*/ +#define HTTP_WRONGNUMARGS "http ?-asynchronous? ?--? url ?payload?" +static int httpCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + int nArg = 1; + int fAsynchronous = 0; + const char *zType, *zRegexp; + Blob payload; + ReCompiled *pRe = 0; + UrlData urlData; + + if( argc<2 || argc>5 ){ + return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS); + } + if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){ + fAsynchronous = 1; nArg++; + } + if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; + if( nArg+1!=argc && nArg+2!=argc ){ + return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); + } + memset(&urlData, '\0', sizeof(urlData)); + url_parse_local(argv[nArg], 0, &urlData); + if( urlData.isSsh || urlData.isFile ){ + Th_ErrorMessage(interp, "url must be http:// or https://", 0, 0); + return TH_ERROR; + } + zRegexp = db_get("th1-uri-regexp", 0); + if( zRegexp && zRegexp[0] ){ + const char *zErr = re_compile(&pRe, zRegexp, 0); + if( zErr ){ + Th_SetResult(interp, zErr, -1); + return TH_ERROR; + } + } + if( !pRe || !re_match(pRe, (const unsigned char *)urlData.canonical, -1) ){ + Th_SetResult(interp, "url not allowed", -1); + re_free(pRe); + return TH_ERROR; + } + re_free(pRe); + blob_zero(&payload); + if( nArg+2==argc ){ + blob_append(&payload, argv[nArg+1], argl[nArg+1]); + zType = "POST"; + }else{ + zType = "GET"; + } + if( fAsynchronous ){ + const char *zSep, *zParams; + Blob hdr; + zParams = strrchr(argv[nArg], '?'); + if( strlen(urlData.path)>0 && zParams!=argv[nArg] ){ + zSep = ""; + }else{ + zSep = "/"; + } + blob_zero(&hdr); + blob_appendf(&hdr, "%s %s%s%s HTTP/1.0\r\n", + zType, zSep, urlData.path, zParams ? zParams : ""); + if( urlData.proxyAuth ){ + blob_appendf(&hdr, "Proxy-Authorization: %s\r\n", urlData.proxyAuth); + } + if( urlData.passwd && urlData.user && urlData.passwd[0]=='#' ){ + char *zCredentials = mprintf("%s:%s", urlData.user, &urlData.passwd[1]); + char *zEncoded = encode64(zCredentials, -1); + blob_appendf(&hdr, "Authorization: Basic %s\r\n", zEncoded); + fossil_free(zEncoded); + fossil_free(zCredentials); + } + blob_appendf(&hdr, "Host: %s\r\n" + "User-Agent: %s\r\n", urlData.hostname, get_user_agent()); + if( zType[0]=='P' ){ + blob_appendf(&hdr, "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %d\r\n\r\n", blob_size(&payload)); + }else{ + blob_appendf(&hdr, "\r\n"); + } + if( transport_open(&urlData) ){ + Th_ErrorMessage(interp, transport_errmsg(&urlData), 0, 0); + blob_reset(&hdr); + blob_reset(&payload); + return TH_ERROR; + } + transport_send(&urlData, &hdr); + transport_send(&urlData, &payload); + blob_reset(&hdr); + blob_reset(&payload); + transport_close(&urlData); + Th_SetResult(interp, 0, 0); /* NOTE: Asynchronous, no results. */ + return TH_OK; + }else{ + Th_ErrorMessage(interp, + "synchronous requests are not yet implemented", 0, 0); + blob_reset(&payload); + return TH_ERROR; + } +} + +/* +** Attempts to open the configuration ("user") database. Optionally, also +** attempts to try to find the repository and open it. +*/ +void Th_OpenConfig( + int openRepository +){ + if( openRepository && !Th_IsRepositoryOpen() ){ + db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0); + if( Th_IsRepositoryOpen() ){ + g.th1Flags |= TH_STATE_REPOSITORY; + }else{ + g.th1Flags &= ~TH_STATE_REPOSITORY; + } + } + if( !Th_IsConfigOpen() ){ + db_open_config(0); + if( Th_IsConfigOpen() ){ + g.th1Flags |= TH_STATE_CONFIG; + }else{ + g.th1Flags &= ~TH_STATE_CONFIG; + } + } +} + +/* +** Attempts to close the configuration ("user") database. Optionally, also +** attempts to close the repository. +*/ +void Th_CloseConfig( + int closeRepository +){ + if( g.th1Flags & TH_STATE_CONFIG ){ + db_close_config(); + g.th1Flags &= ~TH_STATE_CONFIG; + } + if( closeRepository && (g.th1Flags & TH_STATE_REPOSITORY) ){ + db_close(1); + g.th1Flags &= ~TH_STATE_REPOSITORY; + } +} /* ** Make sure the interpreter has been initialized. Initialize it if ** it has not been already. ** @@ -842,46 +1529,64 @@ int needConfig = flags & TH_INIT_NEED_CONFIG; int forceReset = flags & TH_INIT_FORCE_RESET; int forceTcl = flags & TH_INIT_FORCE_TCL; int forceSetup = flags & TH_INIT_FORCE_SETUP; static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY }; + static int anonFlag = LOGIN_ANON; + static int zeroInt = 0; static struct _Command { const char *zName; Th_CommandProc xProc; void *pContext; } aCommand[] = { + {"anoncap", hascapCmd, (void*)&anonFlag}, {"anycap", anycapCmd, 0}, + {"artifact", artifactCmd, 0}, + {"checkout", checkoutCmd, 0}, {"combobox", comboboxCmd, 0}, {"date", dateCmd, 0}, {"decorate", wikiCmd, (void*)&aFlags[2]}, {"enable_output", enableOutputCmd, 0}, - {"hascap", hascapCmd, 0}, + {"getParameter", getParameterCmd, 0}, + {"globalState", globalStateCmd, 0}, + {"httpize", httpizeCmd, 0}, + {"hascap", hascapCmd, (void*)&zeroInt}, {"hasfeature", hasfeatureCmd, 0}, {"html", putsCmd, (void*)&aFlags[0]}, {"htmlize", htmlizeCmd, 0}, + {"http", httpCmd, 0}, {"linecount", linecntCmd, 0}, {"puts", putsCmd, (void*)&aFlags[1]}, {"query", queryCmd, 0}, {"randhex", randhexCmd, 0}, {"regexp", regexpCmd, 0}, + {"reinitialize", reinitializeCmd, 0}, + {"render", renderCmd, 0}, {"repository", repositoryCmd, 0}, + {"searchable", searchableCmd, 0}, + {"setParameter", setParameterCmd, 0}, {"setting", settingCmd, 0}, + {"styleHeader", styleHeaderCmd, 0}, + {"styleFooter", styleFooterCmd, 0}, {"tclReady", tclReadyCmd, 0}, + {"trace", traceCmd, 0}, {"stime", stimeCmd, 0}, {"utime", utimeCmd, 0}, {"wiki", wikiCmd, (void*)&aFlags[0]}, {0, 0, 0} }; + if( g.thTrace ){ + Th_Trace("th1-init 0x%x => 0x%x<br />\n", g.th1Flags, flags); + } if( needConfig ){ /* ** This function uses several settings which may be defined in the ** repository and/or the global configuration. Since the caller ** passed a non-zero value for the needConfig parameter, make sure ** the necessary database connections are open prior to continuing. */ - db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0); - db_open_config(0); + Th_OpenConfig(1); } if( forceReset || forceTcl || g.interp==0 ){ int created = 0; int i; if( g.interp==0 ){ @@ -890,11 +1595,12 @@ } if( forceReset || created ){ th_register_language(g.interp); /* Basic scripting commands. */ } #ifdef FOSSIL_ENABLE_TCL - if( forceTcl || getenv("TH1_ENABLE_TCL")!=0 || db_get_boolean("tcl", 0) ){ + if( forceTcl || fossil_getenv("TH1_ENABLE_TCL")!=0 || + db_get_boolean("tcl", 0) ){ if( !g.tcl.setup ){ g.tcl.setup = db_get("tcl-setup", 0); /* Grab Tcl setup script. */ } th_register_tcl(g.interp, &g.tcl); /* Tcl integration commands. */ } @@ -923,10 +1629,12 @@ if( g.thTrace ){ Th_Trace("th1-setup {%h} => %h<br />\n", g.th1Setup, Th_ReturnCodeName(rc, 0)); } } + g.th1Flags &= ~TH_INIT_MASK; + g.th1Flags |= (flags & TH_INIT_MASK); } /* ** Store a string value in a variable in the interpreter. */ @@ -937,10 +1645,54 @@ Th_Trace("set %h {%h}<br />\n", zName, zValue); } Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue)); } } + +/* +** Appends an element to a TH1 list value. This function is called by the +** transfer subsystem; therefore, it must be very careful to avoid doing +** any unnecessary work. To that end, the TH1 subsystem will not be called +** or initialized if the list pointer is zero (i.e. which will be the case +** when TH1 transfer hooks are disabled). +*/ +void Th_AppendToList( + char **pzList, + int *pnList, + const char *zElem, + int nElem +){ + if( pzList && zElem ){ + Th_FossilInit(TH_INIT_DEFAULT); + Th_ListAppend(g.interp, pzList, pnList, zElem, nElem); + } +} + +/* +** Stores a list value in the specified TH1 variable using the specified +** array of strings as the source of the element values. +*/ +void Th_StoreList( + const char *zName, + char **pzList, + int nList +){ + Th_FossilInit(TH_INIT_DEFAULT); + if( pzList ){ + char *zValue = 0; + int nValue = 0; + int i; + for(i=0; i<nList; i++){ + Th_ListAppend(g.interp, &zValue, &nValue, pzList[i], -1); + } + if( g.thTrace ){ + Th_Trace("set %h {%h}<br />\n", zName, zValue); + } + Th_SetVar(g.interp, zName, -1, zValue, nValue); + Th_Free(g.interp, zValue); + } +} /* ** Store an integer value in a variable in the interpreter. */ void Th_StoreInt(const char *zName, int iValue){ @@ -1034,14 +1786,223 @@ if( z[0]!='>' ) return 0; i += 2; } return i; } + +#ifdef FOSSIL_ENABLE_TH1_HOOKS +/* +** This function determines if TH1 hooks are enabled for the repository. It +** may be necessary to open the repository and/or the configuration ("user") +** database from within this function. Before this function returns, any +** database opened will be closed again. This is very important because some +** commands do not expect the repository and/or the configuration ("user") +** database to be open prior to their own code doing so. +*/ +int Th_AreHooksEnabled(void){ + int rc; + if( fossil_getenv("TH1_ENABLE_HOOKS")!=0 ){ + return 1; + } + Th_OpenConfig(1); + rc = db_get_boolean("th1-hooks", 0); + Th_CloseConfig(1); + return rc; +} + +/* +** This function is called by Fossil just prior to dispatching a command. +** Returning a value other than TH_OK from this function (i.e. via an +** evaluated script raising an error or calling [break]/[continue]) will +** cause the actual command execution to be skipped. +*/ +int Th_CommandHook( + const char *zName, + char cmdFlags +){ + int rc = TH_OK; + if( !Th_AreHooksEnabled() ) return rc; + Th_FossilInit(TH_INIT_HOOK); + Th_Store("cmd_name", zName); + Th_StoreList("cmd_args", g.argv, g.argc); + Th_StoreInt("cmd_flags", cmdFlags); + rc = Th_Eval(g.interp, 0, "command_hook", -1); + if( rc==TH_ERROR ){ + int nResult = 0; + char *zResult = (char*)Th_GetResult(g.interp, &nResult); + /* + ** Make sure that the TH1 script error was not caused by a "missing" + ** command hook handler as that is not actually an error condition. + */ + if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){ + sendError(zResult, nResult, 0); + }else{ + /* + ** There is no command hook handler "installed". This situation + ** is NOT actually an error. + */ + rc = TH_OK; + } + } + /* + ** If the script returned TH_ERROR (e.g. the "command_hook" TH1 command does + ** not exist because commands are not being hooked), return TH_OK because we + ** do not want to skip executing essential commands unless the called command + ** (i.e. "command_hook") explicitly forbids this by successfully returning + ** TH_BREAK or TH_CONTINUE. + */ + if( g.thTrace ){ + Th_Trace("[command_hook {%h}] => %h<br />\n", zName, + Th_ReturnCodeName(rc, 0)); + } + /* + ** Does our call to Th_FossilInit() result in opening a database? If so, + ** clean it up now. This is very important because some commands do not + ** expect the repository and/or the configuration ("user") database to be + ** open prior to their own code doing so. + */ + if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1); + return rc; +} + +/* +** This function is called by Fossil just after dispatching a command. +** Returning a value other than TH_OK from this function (i.e. via an +** evaluated script raising an error or calling [break]/[continue]) may +** cause an error message to be displayed to the local interactive user. +** Currently, TH1 error messages generated by this function are ignored. +*/ +int Th_CommandNotify( + const char *zName, + char cmdFlags +){ + int rc = TH_OK; + if( !Th_AreHooksEnabled() ) return rc; + Th_FossilInit(TH_INIT_HOOK); + Th_Store("cmd_name", zName); + Th_StoreList("cmd_args", g.argv, g.argc); + Th_StoreInt("cmd_flags", cmdFlags); + rc = Th_Eval(g.interp, 0, "command_notify", -1); + if( g.thTrace ){ + Th_Trace("[command_notify {%h}] => %h<br />\n", zName, + Th_ReturnCodeName(rc, 0)); + } + /* + ** Does our call to Th_FossilInit() result in opening a database? If so, + ** clean it up now. This is very important because some commands do not + ** expect the repository and/or the configuration ("user") database to be + ** open prior to their own code doing so. + */ + if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1); + return rc; +} + +/* +** This function is called by Fossil just prior to processing a web page. +** Returning a value other than TH_OK from this function (i.e. via an +** evaluated script raising an error or calling [break]/[continue]) will +** cause the actual web page processing to be skipped. +*/ +int Th_WebpageHook( + const char *zName, + char cmdFlags +){ + int rc = TH_OK; + if( !Th_AreHooksEnabled() ) return rc; + Th_FossilInit(TH_INIT_HOOK); + Th_Store("web_name", zName); + Th_StoreList("web_args", g.argv, g.argc); + Th_StoreInt("web_flags", cmdFlags); + rc = Th_Eval(g.interp, 0, "webpage_hook", -1); + if( rc==TH_ERROR ){ + int nResult = 0; + char *zResult = (char*)Th_GetResult(g.interp, &nResult); + /* + ** Make sure that the TH1 script error was not caused by a "missing" + ** webpage hook handler as that is not actually an error condition. + */ + if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){ + sendError(zResult, nResult, 1); + }else{ + /* + ** There is no webpage hook handler "installed". This situation + ** is NOT actually an error. + */ + rc = TH_OK; + } + } + /* + ** If the script returned TH_ERROR (e.g. the "webpage_hook" TH1 command does + ** not exist because commands are not being hooked), return TH_OK because we + ** do not want to skip processing essential web pages unless the called + ** command (i.e. "webpage_hook") explicitly forbids this by successfully + ** returning TH_BREAK or TH_CONTINUE. + */ + if( g.thTrace ){ + Th_Trace("[webpage_hook {%h}] => %h<br />\n", zName, + Th_ReturnCodeName(rc, 0)); + } + /* + ** Does our call to Th_FossilInit() result in opening a database? If so, + ** clean it up now. This is very important because some commands do not + ** expect the repository and/or the configuration ("user") database to be + ** open prior to their own code doing so. + */ + if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1); + return rc; +} + +/* +** This function is called by Fossil just after processing a web page. +** Returning a value other than TH_OK from this function (i.e. via an +** evaluated script raising an error or calling [break]/[continue]) may +** cause an error message to be displayed to the remote user. +** Currently, TH1 error messages generated by this function are ignored. +*/ +int Th_WebpageNotify( + const char *zName, + char cmdFlags +){ + int rc = TH_OK; + if( !Th_AreHooksEnabled() ) return rc; + Th_FossilInit(TH_INIT_HOOK); + Th_Store("web_name", zName); + Th_StoreList("web_args", g.argv, g.argc); + Th_StoreInt("web_flags", cmdFlags); + rc = Th_Eval(g.interp, 0, "webpage_notify", -1); + if( g.thTrace ){ + Th_Trace("[webpage_notify {%h}] => %h<br />\n", zName, + Th_ReturnCodeName(rc, 0)); + } + /* + ** Does our call to Th_FossilInit() result in opening a database? If so, + ** clean it up now. This is very important because some commands do not + ** expect the repository and/or the configuration ("user") database to be + ** open prior to their own code doing so. + */ + if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1); + return rc; +} +#endif + + +#ifdef FOSSIL_ENABLE_TH1_DOCS +/* +** This function determines if TH1 docs are enabled for the repository. +*/ +int Th_AreDocsEnabled(void){ + if( fossil_getenv("TH1_ENABLE_DOCS")!=0 ){ + return 1; + } + return db_get_boolean("th1-docs", 0); +} +#endif + /* ** The z[] input contains text mixed with TH1 scripts. -** The TH1 scripts are contained within <th1>...</th1>. +** The TH1 scripts are contained within <th1>...</th1>. ** TH1 variables are $aaa or $<aaa>. The first form of ** variable is literal. The second is run through htmlize ** before being inserted. ** ** This routine processes the template and writes the results @@ -1099,42 +2060,115 @@ return rc; } /* ** COMMAND: test-th-render +** +** Usage: %fossil test-th-render FILE +** +** Read the content of the file named "FILE" as if it were a header or +** footer or ticket rendering script, evaluate it, and show the results +** on standard output. +** +** Options: +** +** --cgi Include a CGI response header in the output +** --http Include an HTTP response header in the output +** --open-config Open the configuration database */ void test_th_render(void){ + int forceCgi = 0, fullHttpReply = 0; Blob in; Th_InitTraceLog(); - if( find_option("th-open-config", 0, 0)!=0 ){ - db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0); - db_open_config(0); + forceCgi = find_option("cgi", 0, 0)!=0; + fullHttpReply = find_option("http", 0, 0)!=0; + if( fullHttpReply ) forceCgi = 1; + if( forceCgi ) Th_ForceCgi(fullHttpReply); + if( find_option("open-config", 0, 0)!=0 ){ + Th_OpenConfig(1); } + verify_all_options(); if( g.argc<3 ){ usage("FILE"); } blob_zero(&in); blob_read_from_file(&in, g.argv[2]); Th_Render(blob_str(&in)); Th_PrintTraceLog(); + if( forceCgi ) cgi_reply(); } /* ** COMMAND: test-th-eval +** +** Usage: %fossil test-th-eval SCRIPT +** +** Evaluate SCRIPT as if it were a header or footer or ticket rendering +** script, evaluate it, and show the results on standard output. +** +** Options: +** +** --cgi Include a CGI response header in the output +** --http Include an HTTP response header in the output +** --open-config Open the configuration database */ void test_th_eval(void){ int rc; const char *zRc; + int forceCgi, fullHttpReply; Th_InitTraceLog(); - if( find_option("th-open-config", 0, 0)!=0 ){ - db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0); - db_open_config(0); + forceCgi = find_option("cgi", 0, 0)!=0; + fullHttpReply = find_option("http", 0, 0)!=0; + if( fullHttpReply ) forceCgi = 1; + if( forceCgi ) Th_ForceCgi(fullHttpReply); + if( find_option("open-config", 0, 0)!=0 ){ + Th_OpenConfig(1); } if( g.argc!=3 ){ usage("script"); } Th_FossilInit(TH_INIT_DEFAULT); rc = Th_Eval(g.interp, 0, g.argv[2], -1); zRc = Th_ReturnCodeName(rc, 1); fossil_print("%s%s%s\n", zRc, zRc ? ": " : "", Th_GetResult(g.interp, 0)); Th_PrintTraceLog(); + if( forceCgi ) cgi_reply(); +} + +#ifdef FOSSIL_ENABLE_TH1_HOOKS +/* +** COMMAND: test-th-hook +*/ +void test_th_hook(void){ + int rc = TH_OK; + int nResult = 0; + char *zResult; + int forceCgi, fullHttpReply; + Th_InitTraceLog(); + forceCgi = find_option("cgi", 0, 0)!=0; + fullHttpReply = find_option("http", 0, 0)!=0; + if( fullHttpReply ) forceCgi = 1; + if( forceCgi ) Th_ForceCgi(fullHttpReply); + if( g.argc<5 ){ + usage("TYPE NAME FLAGS"); + } + if( fossil_stricmp(g.argv[2], "cmdhook")==0 ){ + rc = Th_CommandHook(g.argv[3], (char)atoi(g.argv[4])); + }else if( fossil_stricmp(g.argv[2], "cmdnotify")==0 ){ + rc = Th_CommandNotify(g.argv[3], (char)atoi(g.argv[4])); + }else if( fossil_stricmp(g.argv[2], "webhook")==0 ){ + rc = Th_WebpageHook(g.argv[3], (char)atoi(g.argv[4])); + }else if( fossil_stricmp(g.argv[2], "webnotify")==0 ){ + rc = Th_WebpageNotify(g.argv[3], (char)atoi(g.argv[4])); + }else{ + fossil_fatal("Unknown TH1 hook %s\n", g.argv[2]); + } + zResult = (char*)Th_GetResult(g.interp, &nResult); + sendText("RESULT (", -1, 0); + sendText(Th_ReturnCodeName(rc, 0), -1, 0); + sendText("): ", -1, 0); + sendText(zResult, nResult, 0); + sendText("\n", -1, 0); + Th_PrintTraceLog(); + if( forceCgi ) cgi_reply(); } +#endif Index: src/th_tcl.c ================================================================== --- src/th_tcl.c +++ src/th_tcl.c @@ -20,10 +20,11 @@ */ #include "config.h" #ifdef FOSSIL_ENABLE_TCL +#include "sqlite3.h" #include "th.h" #include "tcl.h" /* ** These macros are designed to reduce the redundant code required to marshal @@ -30,24 +31,24 @@ ** arguments from TH1 to Tcl. */ #define USE_ARGV_TO_OBJV() \ int objc; \ Tcl_Obj **objv; \ - int i; + int obji; #define COPY_ARGV_TO_OBJV() \ objc = argc-1; \ objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \ - for(i=1; i<argc; i++){ \ - objv[i-1] = Tcl_NewStringObj(argv[i], argl[i]); \ - Tcl_IncrRefCount(objv[i-1]); \ + for(obji=1; obji<argc; obji++){ \ + objv[obji-1] = Tcl_NewStringObj(argv[obji], argl[obji]); \ + Tcl_IncrRefCount(objv[obji-1]); \ } -#define FREE_ARGV_TO_OBJV() \ - for(i=1; i<argc; i++){ \ - Tcl_DecrRefCount(objv[i-1]); \ - } \ +#define FREE_ARGV_TO_OBJV() \ + for(obji=1; obji<argc; obji++){ \ + Tcl_DecrRefCount(objv[obji-1]); \ + } \ ckfree((char *)objv); /* ** Fetch the Tcl interpreter from the specified void pointer, cast to a Tcl ** context. @@ -109,20 +110,20 @@ # define TCL_MINOR_OFFSET (8) # endif # endif /* defined(__CYGWIN__) */ # endif /* defined(_WIN32) */ # ifndef TCL_FINDEXECUTABLE_NAME -# define TCL_FINDEXECUTABLE_NAME "_Tcl_FindExecutable" +# define TCL_FINDEXECUTABLE_NAME "_Tcl_FindExecutable\0" # endif # ifndef TCL_CREATEINTERP_NAME -# define TCL_CREATEINTERP_NAME "_Tcl_CreateInterp" +# define TCL_CREATEINTERP_NAME "_Tcl_CreateInterp\0" # endif # ifndef TCL_DELETEINTERP_NAME -# define TCL_DELETEINTERP_NAME "_Tcl_DeleteInterp" +# define TCL_DELETEINTERP_NAME "_Tcl_DeleteInterp\0" # endif # ifndef TCL_FINALIZE_NAME -# define TCL_FINALIZE_NAME "_Tcl_Finalize" +# define TCL_FINALIZE_NAME "_Tcl_Finalize\0" # endif #endif /* defined(USE_TCL_STUBS) */ /* ** The function types for Tcl_FindExecutable and Tcl_CreateInterp are needed @@ -165,11 +166,12 @@ /* ** HACK: Using some preprocessor magic and a private static variable, redirect ** the Tcl API calls [found within this file] to the function pointers ** that will be contained in our private Tcl stubs table. This takes ** advantage of the fact that the Tcl headers always define the Tcl API -** functions in terms of the "tclStubsPtr" variable. +** functions in terms of the "tclStubsPtr" variable when the define +** USE_TCL_STUBS is present during compilation. */ #define tclStubsPtr privateTclStubsPtr static const TclStubs *tclStubsPtr = NULL; /* @@ -248,10 +250,32 @@ ** interpreter. Stores the created Tcl interpreter in the Tcl context supplied ** by the caller. This must be declared here because quite a few functions in ** this file need to use it before it can be defined. */ static int createTclInterp(Th_Interp *interp, void *pContext); + +/* +** Returns a name for a Tcl return code. +*/ +static const char *getTclReturnCodeName( + int rc, + int nullIfOk +){ + static char zRc[32]; + + switch( rc ){ + case TCL_OK: return nullIfOk ? 0 : "TCL_OK"; + case TCL_ERROR: return "TCL_ERROR"; + case TCL_BREAK: return "TCL_BREAK"; + case TCL_RETURN: return "TCL_RETURN"; + case TCL_CONTINUE: return "TCL_CONTINUE"; + default: { + sqlite3_snprintf(sizeof(zRc), zRc, "Tcl return code %d", rc); + } + } + return zRc; +} /* ** Returns the Tcl interpreter result as a string with the associated length. ** If the Tcl interpreter or the Tcl result are NULL, the length will be 0. ** If the length pointer is NULL, the length will not be stored. @@ -311,17 +335,17 @@ int rc ){ struct TclContext *tclContext = (struct TclContext *)ctx; tcl_NotifyProc *xNotifyProc; - if ( !tclContext ){ + if( !tclContext ){ Th_ErrorMessage(interp, "invalid Tcl context", (const char *)"", 0); return TH_ERROR; } xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval; - if ( xNotifyProc ){ + if( xNotifyProc ){ rc = xNotifyProc(bIsPost ? tclContext->pPostContext : tclContext->pPreContext, interp, ctx, argc, argv, argl, rc); } return rc; @@ -766,10 +790,52 @@ } } Tcl_DecrRefCount(listPtr); return rc; } + +/* +** Evaluate a Tcl script, creating the Tcl interpreter if necessary. If the +** Tcl script succeeds, start a Tcl event loop until there are no more events +** remaining to process -OR- the script calls [exit]. If the bWait argument +** is zero, only process events that are already in the queue; otherwise, +** process events until the script terminates the Tcl event loop. +*/ +void fossil_print(const char *zFormat, ...); /* printf.h */ + +int evaluateTclWithEvents( + Th_Interp *interp, + void *pContext, + const char *zScript, + int nScript, + int bWait, + int bVerbose +){ + struct TclContext *tclContext = (struct TclContext *)pContext; + Tcl_Interp *tclInterp; + int rc; + int flags = TCL_ALL_EVENTS; + + if( createTclInterp(interp, pContext)!=TH_OK ){ + return TH_ERROR; + } + tclInterp = tclContext->interp; + rc = Tcl_EvalEx(tclInterp, zScript, nScript, TCL_EVAL_GLOBAL); + if( rc!=TCL_OK ){ + if( bVerbose ){ + const char *zResult = getTclResult(tclInterp, 0); + fossil_print("%s: ", getTclReturnCodeName(rc, 0)); + fossil_print("%s\n", zResult); + } + return rc; + } + if( !bWait ) flags |= TCL_DONT_WAIT; + while( Tcl_DoOneEvent(flags) ){ + /* do nothing */ + } + return rc; +} /* ** Creates and initializes a Tcl interpreter for use with the specified TH1 ** interpreter. Stores the created Tcl interpreter in the Tcl context supplied ** by the caller. @@ -783,16 +849,16 @@ char **argv; char *argv0 = 0; Tcl_Interp *tclInterp; char *setup; - if ( !tclContext ){ + if( !tclContext ){ Th_ErrorMessage(interp, "invalid Tcl context", (const char *)"", 0); return TH_ERROR; } - if ( tclContext->interp ){ + if( tclContext->interp ){ return TH_OK; } if( loadTcl(interp, &tclContext->library, &tclContext->xFindExecutable, &tclContext->xCreateInterp, &tclContext->xDeleteInterp, &tclContext->xFinalize)!=TH_OK ){ @@ -882,11 +948,11 @@ tcl_FinalizeProc *xFinalize; #if defined(USE_TCL_STUBS) void *library; #endif /* defined(USE_TCL_STUBS) */ - if ( !tclContext ){ + if( !tclContext ){ Th_ErrorMessage(interp, "invalid Tcl context", (const char *)"", 0); return TH_ERROR; } /* @@ -897,11 +963,11 @@ xFinalize = tclContext->xFinalize; /* ** If the Tcl interpreter has been created, formally delete it now. */ tclInterp = tclContext->interp; - if ( tclInterp ){ + if( tclInterp ){ Tcl_DeleteInterp(tclInterp); tclContext->interp = tclInterp = 0; } /* ** If the Tcl library is not finalized prior to unloading it, a deadlock @@ -939,15 +1005,15 @@ int i; /* Add the Tcl integration commands to TH1. */ for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){ void *ctx; - if ( !aCommand[i].zName || !aCommand[i].xProc ) continue; + if( !aCommand[i].zName || !aCommand[i].xProc ) continue; ctx = aCommand[i].pContext; /* Use Tcl interpreter for context? */ if( !ctx ) ctx = pContext; Th_CreateCommand(interp, aCommand[i].zName, aCommand[i].xProc, ctx, 0); } return TH_OK; } #endif /* FOSSIL_ENABLE_TCL */ Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -16,58 +16,38 @@ ******************************************************************************* ** ** This file contains code to implement the timeline web page ** */ +#include "config.h" #include <string.h> #include <time.h> -#include "config.h" #include "timeline.h" /* -** Shorten a UUID so that is the minimum length needed to contain -** at least one digit in the range 'a'..'f'. The minimum length is 10. +** The value of one second in julianday notation +*/ +#define ONE_SECOND (1.0/86400.0) + +/* +** Add an appropriate tag to the output if "rid" is unpublished (private) */ -static void shorten_uuid(char *zDest, const char *zSrc){ - int i; - for(i=0; i<10 && zSrc[i]<='9'; i++){} - memcpy(zDest, zSrc, 10); - if( i==10 && zSrc[i] ){ - do{ - zDest[i] = zSrc[i]; - i++; - }while( zSrc[i-1]<='9' ); - }else{ - i = 10; - } - zDest[i] = 0; -} - +#define UNPUB_TAG "<em>(unpublished)</em>" +void tag_private_status(int rid){ + if( content_is_private(rid) ){ + cgi_printf("%s", UNPUB_TAG); + } +} /* ** Generate a hyperlink to a version. */ void hyperlink_to_uuid(const char *zUuid){ - char z[UUID_SIZE+1]; - shorten_uuid(z, zUuid); - if( g.perm.Hyperlink ){ - @ %z(xhref("class='timelineHistLink'","%R/info/%s",z))[%s(z)]</a> - }else{ - @ <span class="timelineHistDsp">[%s(z)]</span> - } -} - -/* -** Generate a hyperlink to a diff between two versions. -*/ -void hyperlink_to_diff(const char *zV1, const char *zV2){ - if( g.perm.Hyperlink ){ - if( zV2==0 ){ - @ %z(href("%R/diff?v2=%s",zV1))[diff]</a> - }else{ - @ %z(href("%R/diff?v1=%s&v2=%s",zV1,zV2))[diff]</a> - } + if( g.perm.Hyperlink ){ + @ %z(xhref("class='timelineHistLink'","%R/info/%!S",zUuid))[%S(zUuid)]</a> + }else{ + @ <span class="timelineHistDsp">[%S(zUuid)]</span> } } /* ** Generate a hyperlink to a date & time. @@ -111,10 +91,12 @@ #define TIMELINE_DISJOINT 0x0010 /* Elements are not contiguous */ #define TIMELINE_FCHANGES 0x0020 /* Detail file changes */ #define TIMELINE_BRCOLOR 0x0040 /* Background color by branch name */ #define TIMELINE_UCOLOR 0x0080 /* Background color by user */ #define TIMELINE_FRENAMES 0x0100 /* Detail only file name changes */ +#define TIMELINE_UNHIDE 0x0200 /* Unhide check-ins with "hidden" tag */ +#define TIMELINE_SHOWRID 0x0400 /* Show RID values in addition to UUIDs */ #endif /* ** Hash a string and use the hash to determine a background color. */ @@ -126,11 +108,11 @@ int mx, mn; /* Components of HSV */ static char zColor[10]; /* The resulting color */ static int ix[2] = {0,0}; /* Color chooser parameters */ if( ix[0]==0 ){ - if( db_get_boolean("white-foreground", 0) ){ + if( skin_detail_boolean("white-foreground") ){ ix[0] = 140; ix[1] = 40; }else{ ix[0] = 216; ix[1] = 16; @@ -181,11 +163,11 @@ void test_hash_color_page(void){ const char *zBr; char zNm[10]; int i, cnt; login_check_credentials(); - if( !g.perm.Read ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } style_header("Hash Color Test"); for(i=cnt=0; i<10; i++){ sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); zBr = P(zNm); @@ -232,10 +214,11 @@ void www_print_timeline( Stmt *pQuery, /* Query to implement the timeline */ int tmFlags, /* Flags controlling display behavior */ const char *zThisUser, /* Suppress links to this user */ const char *zThisTag, /* Suppress links to this tag */ + int selectedRid, /* Highlight the line with this RID value */ void (*xExtra)(int) /* Routine to call on each line of display */ ){ int mxWikiLen; Blob comment; int prevTagid = 0; @@ -246,16 +229,21 @@ int fchngQueryInit = 0; /* True if fchngQuery is initialized */ Stmt fchngQuery; /* Query for file changes on check-ins */ static Stmt qbranch; int pendingEndTr = 0; /* True if a </td></tr> is needed */ int vid = 0; /* Current checkout version */ - + int dateFormat = 0; /* 0: HH:MM (default) */ + const char *zDateFmt; + if( fossil_strcmp(g.zIpAddr, "127.0.0.1")==0 && db_open_local(0) ){ vid = db_lget_int("checkout", 0); } zPrevDate[0] = 0; mxWikiLen = db_get_int("timeline-max-comment", 0); + dateFormat = db_get_int("timeline-date-format", 0); + zDateFmt = P("datefmt"); + if( zDateFmt ) dateFormat = atoi(zDateFmt); if( tmFlags & TIMELINE_GRAPH ){ pGraph = graph_init(); /* style is not moved to css, because this is ** a technical div for the timeline graph */ @@ -282,12 +270,15 @@ int tagid = db_column_int(pQuery, 9); const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; const char *zBr = 0; /* Branch */ int commentColumn = 3; /* Column containing comment text */ int modPending; /* Pending moderation */ - char zTime[8]; + char zTime[20]; + if( zDate==0 ){ + zDate = "YYYY-MM-DD HH:MM:SS"; /* Something wrong with the repo */ + } modPending = moderation_pending(rid); if( tagid ){ if( modPending ) tagid = -tagid; if( tagid==prevTagid ){ if( tmFlags & TIMELINE_BRIEF ){ @@ -304,29 +295,62 @@ @ event%s(suppressCnt>1?"s":"") omitted.</span> suppressCnt = 0; } if( pendingEndTr ){ @ </td></tr> + if( pendingEndTr>1 ){ + @ <tr class="timelineSpacer"></tr> + } pendingEndTr = 0; } if( fossil_strcmp(zType,"div")==0 ){ if( !prevWasDivider ){ - @ <tr><td colspan="3"><hr /></td></tr> + @ <tr><td colspan="3"><hr class="timelineMarker"/></td></tr> } prevWasDivider = 1; continue; } prevWasDivider = 0; - if( memcmp(zDate, zPrevDate, 10) ){ - sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate); - @ <tr><td> - @ <div class="divider">%s(zPrevDate)</div> - @ </td><td></td><td></td></tr> - } - memcpy(zTime, &zDate[11], 5); - zTime[5] = 0; - if( rid == vid ){ + /* Date format codes: + ** (0) HH:MM + ** (1) HH:MM:SS + ** (2) YYYY-MM-DD HH:MM + ** (3) YYMMDD HH:MM + ** (4) (off) + */ + if( dateFormat<2 ){ + if( fossil_strnicmp(zDate, zPrevDate, 10) ){ + sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate); + @ <tr><td> + @ <div class="divider timelineDate">%s(zPrevDate)</div> + @ </td><td></td><td></td></tr> + } + memcpy(zTime, &zDate[11], 5+dateFormat*3); + zTime[5+dateFormat*3] = 0; + }else if( 2==dateFormat ){ + /* YYYY-MM-DD HH:MM */ + sqlite3_snprintf(sizeof(zTime), zTime, "%.16s", zDate); + }else if( 3==dateFormat ){ + /* YYMMDD HH:MM */ + int pos = 0; + zTime[pos++] = zDate[2]; zTime[pos++] = zDate[3]; /* YY */ + zTime[pos++] = zDate[5]; zTime[pos++] = zDate[6]; /* MM */ + zTime[pos++] = zDate[8]; zTime[pos++] = zDate[9]; /* DD */ + zTime[pos++] = ' '; + zTime[pos++] = zDate[11]; zTime[pos++] = zDate[12]; /* HH */ + zTime[pos++] = ':'; + zTime[pos++] = zDate[14]; zTime[pos++] = zDate[15]; /* MM */ + zTime[pos++] = 0; + }else{ + zTime[0] = 0; + } + pendingEndTr = 1; + if( rid==selectedRid ){ + @ <tr class="timelineSpacer"></tr> + @ <tr class="timelineSelected"> + pendingEndTr = 2; + }else if( rid==vid ){ @ <tr class="timelineCurrent"> }else { @ <tr> } @ <td class="timelineTime">%s(zTime)</td> @@ -350,30 +374,30 @@ } } } if( zType[0]=='c' && (pGraph || (tmFlags & TIMELINE_BRCOLOR)!=0) ){ int nParent = 0; - int aParent[32]; + int aParent[GR_MAX_RAIL]; int gidx; static Stmt qparent; db_static_prepare(&qparent, "SELECT pid FROM plink" " WHERE cid=:rid AND pid NOT IN phantom" " ORDER BY isprim DESC /*sort*/" ); db_bind_int(&qparent, ":rid", rid); - while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){ + while( db_step(&qparent)==SQLITE_ROW && nParent<ArraySize(aParent) ){ aParent[nParent++] = db_column_int(&qparent, 0); } db_reset(&qparent); gidx = graph_add_row(pGraph, rid, nParent, aParent, zBr, zBgClr, zUuid, isLeaf); db_reset(&qbranch); @ <div id="m%d(gidx)"></div> } @</td> - if( zBgClr && zBgClr[0] ){ + if( zBgClr && zBgClr[0] && rid!=selectedRid ){ @ <td class="timelineTableCell" style="background-color: %h(zBgClr);"> }else{ @ <td class="timelineTableCell"> } if( pGraph && zType[0]!='c' ){ @@ -395,10 +419,13 @@ } }else if( zType[0]=='e' && tagid ){ hyperlink_to_event_tagid(tagid<0?-tagid:tagid); }else if( (tmFlags & TIMELINE_ARTID)!=0 ){ hyperlink_to_uuid(zUuid); + } + if( tmFlags & TIMELINE_SHOWRID ){ + @ (%d(rid)) } db_column_blob(pQuery, commentColumn, &comment); if( zType[0]!='c' ){ /* Comments for anything other than a check-in are generated by ** "fossil rebuild" and expect to be rendered as text/x-fossil-wiki */ @@ -406,31 +433,31 @@ }else if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){ Blob truncated; blob_zero(&truncated); blob_append(&truncated, blob_buffer(&comment), mxWikiLen); blob_append(&truncated, "...", 3); - @ <span class="timelineComment">%w(blob_str(&truncated))</span> + @ <span class="timelineComment">%W(blob_str(&truncated))</span> blob_reset(&truncated); }else{ - @ <span class="timelineComment">%w(blob_str(&comment))</span> + @ <span class="timelineComment">%W(blob_str(&comment))</span> } blob_reset(&comment); /* Generate the "user: USERNAME" at the end of the comment, together ** with a hyperlink to another timeline for that user. */ if( zTagList && zTagList[0]==0 ) zTagList = 0; if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){ - char *zLink = mprintf("%R/timeline?u=%h&c=%t&nd", zDispUser, zDate); + char *zLink = mprintf("%R/timeline?u=%h&c=%t&nd&n=200", zDispUser, zDate); @ (user: %z(href("%z",zLink))%h(zDispUser)</a>%s(zTagList?",":"\051") }else{ @ (user: %h(zDispUser)%s(zTagList?",":"\051") } /* Generate a "detail" link for tags. */ if( (zType[0]=='g' || zType[0]=='w' || zType[0]=='t') && g.perm.Hyperlink ){ - @ [%z(href("%R/info/%S",zUuid))details</a>] + @ [%z(href("%R/info/%!S",zUuid))details</a>] } /* Generate the "tags: TAGLIST" at the end of the comment, together ** with hyperlinks to the tag list. */ @@ -443,11 +470,11 @@ while( z && z[0] ){ for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){} if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){ blob_appendf(&links, "%z%#h</a>%.2s", - href("%R/timeline?r=%#t&nd&c=%t",i,z,zDate), i,z, &z[i] + href("%R/timeline?r=%#t&nd&c=%t&n=200",i,z,zDate), i,z, &z[i] ); }else{ blob_appendf(&links, "%#h", i+2, z); } if( z[i]==0 ) break; @@ -457,11 +484,11 @@ blob_reset(&links); }else{ @ tags: %h(zTagList)) } } - + tag_private_status(rid); /* Generate extra hyperlinks at the end of the comment */ if( xExtra ){ xExtra(rid); } @@ -472,64 +499,78 @@ ){ int inUl = 0; if( !fchngQueryInit ){ db_prepare(&fchngQuery, "SELECT (pid==0) AS isnew," - " (fid==0) AS isdel," + " fid," " (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name," " (SELECT uuid FROM blob WHERE rid=fid)," " (SELECT uuid FROM blob WHERE rid=pid)," " (SELECT name FROM filename WHERE fnid=mlink.pfnid) AS oldnm" " FROM mlink" " WHERE mid=:mid AND (pid!=fid OR pfnid>0)" " AND (fid>0 OR" " fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=:mid))" + " AND NOT mlink.isaux" " ORDER BY 3 /*sort*/" ); fchngQueryInit = 1; } db_bind_int(&fchngQuery, ":mid", rid); while( db_step(&fchngQuery)==SQLITE_ROW ){ const char *zFilename = db_column_text(&fchngQuery, 2); int isNew = db_column_int(&fchngQuery, 0); - int isDel = db_column_int(&fchngQuery, 1); + int fid = db_column_int(&fchngQuery, 1); + int isDel = fid==0; const char *zOldName = db_column_text(&fchngQuery, 5); const char *zOld = db_column_text(&fchngQuery, 4); const char *zNew = db_column_text(&fchngQuery, 3); + const char *zUnpub = ""; + char *zA; + char zId[20]; if( !inUl ){ @ <ul class="filelist"> inUl = 1; + } + if( tmFlags & TIMELINE_SHOWRID ){ + sqlite3_snprintf(sizeof(zId), zId, " (%d) ", fid); + }else{ + zId[0] = 0; } if( (tmFlags & TIMELINE_FRENAMES)!=0 ){ if( !isNew && !isDel && zOldName!=0 ){ - @ <li> %h(zOldName) → %h(zFilename) + @ <li> %h(zOldName) → %h(zFilename)%s(zId) } continue; } + zA = href("%R/artifact/%!S",fid?zNew:zOld); + if( content_is_private(fid) ){ + zUnpub = UNPUB_TAG; + } if( isNew ){ - @ <li> %h(zFilename) (new file)   - @ %z(href("%R/artifact/%S",zNew))[view]</a></li> + @ <li> %s(zA)%h(zFilename)</a>%s(zId) %s(zUnpub) (new file)   + @ %z(href("%R/artifact/%!S",zNew))[view]</a></li> }else if( isDel ){ - @ <li> %h(zFilename) (deleted)</li> + @ <li> %s(zA)%h(zFilename)</a> (deleted)</li> }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){ - @ <li> %h(zOldName) → %h(zFilename) - @ %z(href("%R/artifact/%S",zNew))[view]</a></li> + @ <li> %h(zOldName) → %s(zA)%h(zFilename)</a>%s(zId) + @ %s(zUnpub) %z(href("%R/artifact/%!S",zNew))[view]</a></li> }else{ if( zOldName!=0 ){ - @ <li> %h(zOldName) → %h(zFilename) + @ <li>%h(zOldName) → %s(zA)%h(zFilename)%s(zId)</a> %s(zUnpub) }else{ - @ <li> %h(zFilename)   + @ <li>%s(zA)%h(zFilename)</a>%s(zId)   %s(zUnpub) } - @ %z(href("%R/fdiff?v1=%S&v2=%S&sbs=1",zOld,zNew))[diff]</a></li> + @ %z(href("%R/fdiff?sbs=1&v1=%!S&v2=%!S",zOld,zNew))[diff]</a></li> } + fossil_free(zA); } db_reset(&fchngQuery); if( inUl ){ @ </ul> } } - pendingEndTr = 1; } if( suppressCnt ){ @ <span class="timelineDisabled">... %d(suppressCnt) similar @ event%s(suppressCnt>1?"s":"") omitted.</span> suppressCnt = 0; @@ -545,20 +586,51 @@ }else{ int w; /* style is not moved to css, because this is ** a technical div for the timeline graph */ - w = (pGraph->mxRail+1)*pGraph->iRailPitch + 10; + w = pGraph->mxRail*pGraph->iRailPitch + 28; @ <tr><td></td><td> @ <div id="grbtm" style="width:%d(w)px;"></div> @ </td><td></td></tr> } } @ </table> if( fchngQueryInit ) db_finalize(&fchngQuery); timeline_output_graph_javascript(pGraph, (tmFlags & TIMELINE_DISJOINT)!=0, 0); } + +/* +** Change the RGB background color given in the argument in a foreground +** color with the same hue. +*/ +static const char *bg_to_fg(const char *zIn){ + int i; + unsigned int x[3]; + unsigned int mx = 0; + static int whiteFg = -1; + static char zRes[10]; + if( strlen(zIn)!=7 || zIn[0]!='#' ) return zIn; + zIn++; + for(i=0; i<3; i++){ + x[i] = hex_digit_value(zIn[0])*16 + hex_digit_value(zIn[1]); + zIn += 2; + if( x[i]>mx ) mx = x[i]; + } + if( whiteFg<0 ) whiteFg = skin_detail_boolean("white-foreground"); + if( whiteFg ){ + /* Make the color lighter */ + static const unsigned int t = 215; + if( mx<t ) for(i=0; i<3; i++) x[i] += t - mx; + }else{ + /* Make the color darker */ + static const unsigned int t = 128; + if( mx>t ) for(i=0; i<3; i++) x[i] -= mx - t; + } + sqlite3_snprintf(sizeof(zRes),zRes,"#%02x%02x%02x",x[0],x[1],x[2]); + return zRes; +} /* ** Generate all of the necessary javascript to generate a timeline ** graph. */ @@ -569,13 +641,30 @@ ){ if( pGraph && pGraph->nErr==0 && pGraph->nRow>0 ){ GraphRow *pRow; int i; char cSep; - @ <script type="text/JavaScript"> - @ /* <![CDATA[ */ - @ var railPitch=%d(pGraph->iRailPitch); + int mergeOffset; /* Pixel offset from rail to merge riser */ + int iRailPitch; /* Pixels between consecutive rails */ + int showArrowheads; /* True to draw arrowheads. False to omit. */ + int circleNodes; /* True for circle nodes. False for square nodes */ + int colorGraph; /* Use colors for graph lines */ + + iRailPitch = pGraph->iRailPitch; + showArrowheads = skin_detail_boolean("timeline-arrowheads"); + circleNodes = skin_detail_boolean("timeline-circle-nodes"); + colorGraph = skin_detail_boolean("timeline-color-graph-lines"); + + /* Number of pixels that the thin merge lines are offset from the + ** the center of the think rail lines. If zero, then the vertical + ** merge lines overlap with the thicker rail lines. + */ + mergeOffset = iRailPitch>=14 ? 4 : iRailPitch>=13 ? 3 : 0; + if( PB("nomo") ) mergeOffset = 0; + + @ <script> + @ var railPitch=%d(iRailPitch); /* the rowinfo[] array contains all the information needed to generate ** the graph. Each entry contains information for a single row: ** ** id: The id of the <div> element for the row. This is an integer. @@ -590,12 +679,12 @@ ** for the upward portion of a merge arrow. The merge arrow goes up ** to the row identified by mu:. If this value is zero then ** node has no merge children and no merge-out line is drawn. ** mu: The id of the row which is the top of the merge-out arrow. ** u: Draw a thick child-line out of the top of this node and up to - ** the node with an id equal to this value. 0 if there is no - ** thick-line riser. + ** the node with an id equal to this value. 0 if it is straight to + ** the top of the page, -1 if there is no thick-line riser. ** f: 0x01: a leaf node. ** au: An array of integers that define thick-line risers for branches. ** The integers are in pairs. For each pair, the first integer is ** is the rail on which the riser should run and the second integer ** is the id of the node upto which the riser should run. @@ -610,11 +699,17 @@ for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){ int mo = pRow->mergeOut; if( mo<0 ){ mo = 0; }else{ - mo = (mo/4)*pGraph->iRailPitch - 3 + 4*(mo&3); + int x = (mo/4)*iRailPitch; + switch( mo&3 ){ + case 0: x -= mergeOffset-2; break; + case 1: x += 1; break; + case 2: x += mergeOffset+1; break; + } + mo = x; } cgi_printf("{id:%d,bg:\"%s\",r:%d,d:%d,mo:%d,mu:%d,u:%d,f:%d,au:", pRow->idx, /* id */ pRow->zBgClr, /* bg */ pRow->iRail, /* r */ @@ -632,16 +727,22 @@ cgi_printf("%c%d,%d", cSep, i, pRow->aiRiser[i]); cSep = ','; } } if( cSep=='[' ) cgi_printf("["); - cgi_printf("],mi:"); + cgi_printf("],"); + if( colorGraph && pRow->zBgClr[0]=='#' ){ + cgi_printf("fg:\"%s\",", bg_to_fg(pRow->zBgClr)); + } /* mi */ + cgi_printf("mi:"); cSep = '['; for(i=0; i<GR_MAX_RAIL; i++){ if( pRow->mergeIn[i] ){ - int mi = i*pGraph->iRailPitch - 8 + 4*pRow->mergeIn[i]; + int mi = i*iRailPitch; + if( pRow->mergeIn[i]==1 ) mi -= mergeOffset-1; + if( pRow->mergeIn[i]==3 ) mi += mergeOffset; if( pRow->mergeDown & (1<<i) ) mi = -mi; cgi_printf("%c%d", cSep, mi); cSep = ','; } } @@ -648,16 +749,16 @@ if( cSep=='[' ) cgi_printf("["); cgi_printf("],h:\"%s\"}%s", pRow->zUuid, pRow->pNext ? ",\n" : "];\n"); } cgi_printf("var nrail = %d\n", pGraph->mxRail+1); graph_free(pGraph); - @ var canvasDiv = gebi("canvas"); - @ var canvasStyle = window.getComputedStyle && window.getComputedStyle(canvasDiv,null); - @ var lineColor = (canvasStyle && canvasStyle.getPropertyValue('color')) || 'black'; - @ var bgColor = (canvasStyle && canvasStyle.getPropertyValue('background-color')) || 'white'; - @ if( bgColor=='transparent' ) bgColor = 'white'; - @ var boxColor = lineColor; + @ var cDiv = gebi("canvas"); + @ var csty = window.getComputedStyle && window.getComputedStyle(cDiv,null); + @ var lineClr = (csty && csty.getPropertyValue('color')) || 'black'; + @ var bgClr = (csty && csty.getPropertyValue('background-color')) ||'white'; + @ if( bgClr=='transparent' ) bgClr = 'white'; + @ var boxColor = lineClr; @ function drawBox(color,x0,y0,x1,y1){ @ var n = document.createElement("div"); @ if( x0>x1 ){ var t=x0; x0=x1; x1=t; } @ if( y0>y1 ){ var t=y0; y0=y1; y1=t; } @ var w = x1-x0+1; @@ -667,11 +768,11 @@ @ n.style.left = x0+"px"; @ n.style.top = y0+"px"; @ n.style.width = w+"px"; @ n.style.height = h+"px"; @ n.style.backgroundColor = color; - @ canvasDiv.appendChild(n); + @ cDiv.appendChild(n); @ return n; @ } @ function absoluteY(id){ @ var obj = gebi(id); @ if( !obj ) return; @@ -692,45 +793,84 @@ @ left += obj.offsetLeft; @ }while( obj = obj.offsetParent ); @ } @ return left; @ } - @ function drawUpArrow(x,y0,y1){ - @ drawBox(lineColor,x,y0,x+1,y1); - @ if( y0+10>=y1 ){ - @ drawBox(lineColor,x-1,y0+1,x+2,y0+2); - @ drawBox(lineColor,x-2,y0+3,x+3,y0+4); - @ }else{ - @ drawBox(lineColor,x-1,y0+2,x+2,y0+4); - @ drawBox(lineColor,x-2,y0+5,x+3,y0+7); - @ } - @ } + if( showArrowheads ){ + @ function drawUpArrow(x,y0,y1,clr){ + @ drawBox(clr,x,y0+4,x+1,y1); + @ var n = document.createElement("div"), + @ l = x-2, + @ t = y0; + @ n.style.position = "absolute"; + @ n.style.left = l+"px"; + @ n.style.top = t+"px"; + @ n.style.width = 0; + @ n.style.height = 0; + @ n.style.transform = "scale(.999)"; + @ n.style.borderWidth = 0; + @ n.style.borderStyle = "solid"; + @ n.style.borderColor = "transparent"; + @ n.style.borderRightWidth = "3px"; + @ n.style.borderBottomColor = clr; + @ n.style.borderLeftWidth = "3px"; + @ if( y0+10>=y1 ){ + @ n.style.borderBottomWidth = "5px"; + @ } else { + @ n.style.borderBottomWidth = "7px"; + @ } + @ cDiv.appendChild(n); + @ } + }else{ + @ function drawUpArrow(x,y0,y1,clr){ + @ drawBox(clr,x,y0+1,x+1,y1); + @ } + } @ function drawThinArrow(y,xFrom,xTo){ + @ var n = document.createElement("div"), + @ t = y-2; + @ n.style.position = "absolute"; + @ n.style.top = t+"px"; + @ n.style.width = 0; + @ n.style.height = "1px"; + @ n.style.transform = "scale(.999)"; + @ n.style.borderWidth = 0; + @ n.style.borderStyle = "solid"; + @ n.style.borderColor = "transparent"; + @ n.style.borderTopWidth = "2px"; + @ n.style.borderBottomWidth = "2px"; @ if( xFrom<xTo ){ - @ drawBox(lineColor,xFrom,y,xTo,y); - @ drawBox(lineColor,xTo-3,y-1,xTo-2,y+1); - @ drawBox(lineColor,xTo-4,y-2,xTo-4,y+2); + @ drawBox(lineClr,xFrom,y,xTo-3,y); + @ n.style.left = xTo-3+"px"; + @ n.style.borderLeftWidth = "3px"; + @ n.style.borderLeftColor = lineClr; @ }else{ - @ drawBox(lineColor,xTo,y,xFrom,y); - @ drawBox(lineColor,xTo+2,y-1,xTo+3,y+1); - @ drawBox(lineColor,xTo+4,y-2,xTo+4,y+2); + @ drawBox(lineClr,xTo+3,y,xFrom,y); + @ n.style.left = xTo+1+"px"; + @ n.style.borderRightWidth = "3px"; + @ n.style.borderRightColor = lineClr; @ } + @ cDiv.appendChild(n); @ } @ function drawThinLine(x0,y0,x1,y1){ - @ drawBox(lineColor,x0,y0,x1,y1); + @ drawBox(lineClr,x0,y0,x1,y1); @ } @ function drawNodeBox(color,x0,y0,x1,y1){ - @ drawBox(color,x0,y0,x1,y1).style.cursor = "pointer"; + @ var n = drawBox(color,x0,y0,x1,y1); + @ n.style.cursor = "pointer"; + if( circleNodes ){ + @ n.style.borderRadius = "6px"; + } @ } @ function drawNode(p, left, btm){ @ drawNodeBox(boxColor,p.x-5,p.y-5,p.x+6,p.y+6); - @ drawNodeBox(p.bg||bgColor,p.x-4,p.y-4,p.x+5,p.y+5); - @ if( p.u>0 ) drawUpArrow(p.x, rowinfo[p.u-1].y+6, p.y-5); + @ drawNodeBox(p.bg||bgClr,p.x-4,p.y-4,p.x+5,p.y+5); + @ if( p.u>0 ) drawUpArrow(p.x,rowinfo[p.u-1].y+6,p.y-6,p.fg||lineClr); @ if( p.f&1 ) drawNodeBox(boxColor,p.x-1,p.y-1,p.x+2,p.y+2); if( !omitDescenders ){ - @ if( p.u==0 ) drawUpArrow(p.x, 0, p.y-5); - @ if( p.d ) drawUpArrow(p.x, p.y+6, btm); + @ if( p.u==0 ) drawUpArrow(p.x,0,p.y-6,p.fg||lineClr); + @ if( p.d ) drawUpArrow(p.x,p.y+6,btm,p.fg||lineClr); } @ if( p.mo>0 ){ @ var x1 = p.mo + left - 1; @ var y1 = p.y-3; @ var x0 = x1>p.x ? p.x+7 : p.x-6; @@ -739,26 +879,42 @@ @ if( x1>=p.x-5 && x1<=p.x+5 ){ @ y1 = p.y-5; @ }else{ @ drawThinLine(x0,y1,x1,y1); @ } + if( mergeOffset==0 ) cgi_printf("if( p.mo!=p.u-1 ) "); @ drawThinLine(x1,y0,x1,y1); @ } @ var n = p.au.length; @ for(var i=0; i<n; i+=2){ @ var x1 = p.au[i]*railPitch + left; @ var x0 = x1>p.x ? p.x+7 : p.x-6; @ var u = rowinfo[p.au[i+1]-1]; @ if(u.id<p.id){ - @ drawBox(lineColor,x0,p.y,x1,p.y+1); - @ drawUpArrow(x1, u.y+6, p.y); + @ drawBox(u.fg||lineClr,x0,p.y,x1+1,p.y+1); + @ drawUpArrow(x1,u.y+6,p.y,u.fg||lineClr); @ }else{ @ drawBox("#600000",x0,p.y,x1,p.y+1); @ drawBox("#600000",x1-1,p.y,x1,u.y+1); - @ drawBox("#600000",x1,u.y,u.x-6,u.y+1); - @ drawBox("#600000",u.x-9,u.y-1,u.x-8,u.y+2); - @ drawBox("#600000",u.x-11,u.y-2,u.x-10,u.y+3); + @ drawBox("#600000",x1,u.y,u.x-10,u.y+1); + @ var n = document.createElement("div"), + @ t = u.y-2, + @ l = u.x-11; + @ n.style.position = "absolute"; + @ n.style.top = t+"px"; + @ n.style.left = l+"px"; + @ n.style.width = 0; + @ n.style.height = 0; + @ n.style.transform = "scale(.999)"; + @ n.style.borderWidth = 0; + @ n.style.borderStyle = "solid"; + @ n.style.borderColor = "transparent"; + @ n.style.borderTopWidth = "3px"; + @ n.style.borderBottomWidth = "3px"; + @ n.style.borderLeftWidth = "7px"; + @ n.style.borderLeftColor = "#600000"; + @ cDiv.appendChild(n); @ } @ } @ for(var j in p.mi){ @ var y0 = p.y+5; @ var mx = p.mi[j]; @@ -820,10 +976,13 @@ @ } @ } @ function clickOnRow(p){ @ if( selRow==null ){ @ selBox = drawBox("red",p.x-2,p.y-2,p.x+3,p.y+3); + if( circleNodes ){ + @ selBox.style.borderRadius="6px"; + } @ selRow = p; @ }else if( selRow==p ){ @ var canvasDiv = gebi("canvas"); @ canvasDiv.removeChild(selBox); @ selBox = null; @@ -830,11 +989,15 @@ @ selRow = null; @ }else{ if( fileDiff ){ @ location.href="%R/fdiff?v1="+selRow.h+"&v2="+p.h+"&sbs=1"; }else{ - @ location.href="%R/vdiff?from="+selRow.h+"&to="+p.h+"&sbs=1"; + if( db_get_boolean("show-version-diffs", 0)==0 ){ + @ location.href="%R/vdiff?from="+selRow.h+"&to="+p.h+"&sbs=0"; + }else{ + @ location.href="%R/vdiff?from="+selRow.h+"&to="+p.h+"&sbs=1"; + } } @ } @ } @ var lastId = "m"+rowinfo[rowinfo.length-1].id; @ var lastY = 0; @@ -845,11 +1008,10 @@ @ lastY = h; @ } @ setTimeout("checkHeight();", 1000); @ } @ checkHeight(); - @ /* ]]> */ @ </script> } } /* @@ -870,24 +1032,24 @@ @ tagid INTEGER, @ short TEXT, @ sortby REAL @ ) ; - db_multi_exec(zSql); + db_multi_exec("%s", zSql/*safe-for-%s*/); } /* ** Return a pointer to a constant string that forms the basis ** for a timeline query for the WWW interface. */ const char *timeline_query_for_www(void){ - static char *zBase = 0; + static const char *zBase = 0; static const char zBaseSql[] = @ SELECT @ blob.rid AS blobRid, @ uuid AS uuid, - @ datetime(event.mtime,'localtime') AS timestamp, + @ datetime(event.mtime%s) AS timestamp, @ coalesce(ecomment, comment) AS comment, @ coalesce(euser, user) AS user, @ blob.rid IN leaf AS leaf, @ bgcolor AS bgColor, @ event.type AS eventType, @@ -899,11 +1061,11 @@ @ event.mtime AS mtime @ FROM event CROSS JOIN blob @ WHERE blob.rid=event.objid ; if( zBase==0 ){ - zBase = mprintf(zBaseSql, TAG_BRANCH, TAG_BRANCH); + zBase = mprintf(zBaseSql /*works-like: "%s"*/, timeline_utc()); } return zBase; } /* @@ -931,43 +1093,39 @@ if( z==0 ) return -1.0; if( fossil_isdate(z) ){ mtime = db_double(0.0, "SELECT julianday(%Q,'utc')", z); if( mtime>0.0 ) return mtime; } - rid = symbolic_name_to_rid(z, "ci"); - if( rid==0 ) return -1.0; - mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); + rid = symbolic_name_to_rid(z, "*"); + if( rid ){ + mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); + }else{ + mtime = db_double(-1.0, + "SELECT max(event.mtime) FROM event, tag, tagxref" + " WHERE tag.tagname GLOB 'event-%q*'" + " AND tagxref.tagid=tag.tagid AND tagxref.tagtype" + " AND event.objid=tagxref.rid", + z + ); + } return mtime; } /* -** The value of one second in julianday notation -*/ -#define ONE_SECOND (1.0/86400.0) - -/* -** zDate is a localtime date. Insert records into the -** "timeline" table to cause <hr> to be inserted before and after -** entries of that date. If zDate==NULL then put dividers around -** the event identified by rid. -*/ -static void timeline_add_dividers(double rDate, int rid){ - char *zToDel = 0; - if( rDate==0 ){ - rDate = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); - } - db_multi_exec( - "INSERT INTO timeline(rid,sortby,etype)" - "VALUES(-1,%.16g,'div')", - rDate-ONE_SECOND - ); - db_multi_exec( - "INSERT INTO timeline(rid,sortby,etype)" - "VALUES(-2,%.17g,'div')", - rDate+ONE_SECOND - ); - fossil_free(zToDel); +** zDate is a localtime date. Insert records into the +** "timeline" table to cause <hr> to be inserted on zDate. +*/ +static int timeline_add_divider(double rDate){ + int rid = db_int(-1, + "SELECT rid FROM timeline ORDER BY abs(sortby-%.16g) LIMIT 1", rDate + ); + if( rid>0 ) return rid; + db_multi_exec( + "INSERT INTO timeline(rid,sortby,etype) VALUES(-1,%.16g,'div')", + rDate + ); + return -1; } /* ** Return all possible names for file zUuid. */ @@ -975,11 +1133,11 @@ Stmt q; Blob out; const char *zSep = ""; db_prepare(&q, "SELECT DISTINCT filename.name FROM mlink, filename" - " WHERE mlink.fid=(SELECT rid FROM blob WHERE uuid='%s')" + " WHERE mlink.fid=(SELECT rid FROM blob WHERE uuid=%Q)" " AND filename.fnid=mlink.fnid", zUuid ); blob_zero(&out); while( db_step(&q)==SQLITE_ROW ){ @@ -990,72 +1148,112 @@ } db_finalize(&q); return blob_str(&out); } + +/* +** Add the select/option box to the timeline submenu that is used to +** set the y= parameter that determines which elements to display +** on the timeline. +*/ +static void timeline_y_submenu(int isDisabled){ + static int i = 0; + static const char *az[12]; + if( i==0 ){ + az[0] = "all"; + az[1] = "Any Type"; + i = 2; + if( g.perm.Read ){ + az[i++] = "ci"; + az[i++] = "Check-ins"; + az[i++] = "g"; + az[i++] = "Tags"; + } + if( g.perm.RdWiki ){ + az[i++] = "e"; + az[i++] = "Tech Notes"; + } + if( g.perm.RdTkt ){ + az[i++] = "t"; + az[i++] = "Tickets"; + } + if( g.perm.RdWiki ){ + az[i++] = "w"; + az[i++] = "Wiki"; + } + assert( i<=ArraySize(az) ); + } + if( i>2 ){ + style_submenu_multichoice("y", i/2, az, isDisabled); + } +} /* ** WEBPAGE: timeline ** ** Query parameters: ** ** a=TIMEORTAG after this event ** b=TIMEORTAG before this event ** c=TIMEORTAG "circa" this event +** m=TIMEORTAG mark this event ** n=COUNT max number of events in output ** p=UUID artifact and up to COUNT parents and ancestors ** d=UUID artifact and up to COUNT descendants ** dp=UUID The same as d=UUID&p=UUID ** t=TAGID show only check-ins with the given tagid ** r=TAGID show check-ins related to tagid ** u=USER only if belonging to this user -** y=TYPE 'ci', 'w', 't', 'e' +** y=TYPE 'ci', 'w', 't', 'e', or (default) 'all' ** s=TEXT string search (comment and brief) ** ng Suppress the graph if present ** nd Suppress "divider" lines ** v Show details of files changed ** f=UUID Show family (immediate parents and children) of UUID ** from=UUID Path from... ** to=UUID ... to this -** nomerge ... avoid merge links on the path -** uf=FUUID Show only checkins that use given file version +** shortest ... show only the shortest path +** uf=FUUID Show only check-ins that use given file version ** brbg Background color from branch name ** ubg Background color from user -** namechng Show only checkins that filename changes +** namechng Show only check-ins that filename changes ** ym=YYYY-MM Shown only events for the given year/month. +** datefmt=N Override the date format ** ** p= and d= can appear individually or together. If either p= or d= ** appear, then u=, y=, a=, and b= are ignored. ** -** If a= and b= appear, only a= is used. If neither appear, the most -** recent events are chosen. +** If both a= and b= appear then both upper and lower bounds are honored. ** -** If n= is missing, the default count is 20. +** If n= is missing, the default count is 50 for most queries but +** drops to 11 for c= queries. */ void page_timeline(void){ Stmt q; /* Query used to generate the timeline */ Blob sql; /* text of SQL used to generate timeline */ Blob desc; /* Description of the timeline */ - int nEntry = atoi(PD("n","20")); /* Max number of entries on timeline */ + int nEntry; /* Max number of entries on timeline */ int p_rid = name_to_typed_rid(P("p"),"ci"); /* artifact p and its parents */ int d_rid = name_to_typed_rid(P("d"),"ci"); /* artifact d and descendants */ int f_rid = name_to_typed_rid(P("f"),"ci"); /* artifact f and close family */ const char *zUser = P("u"); /* All entries by this user if not NULL */ const char *zType = PD("y","all"); /* Type of events. All if NULL */ const char *zAfter = P("a"); /* Events after this time */ const char *zBefore = P("b"); /* Events before this time */ const char *zCirca = P("c"); /* Events near this time */ + const char *zMark = P("m"); /* Mark this event or an event this time */ const char *zTagName = P("t"); /* Show events with this tag */ const char *zBrName = P("r"); /* Show events related to this tag */ const char *zSearch = P("s"); /* Search string */ - const char *zUses = P("uf"); /* Only show checkins hold this file */ - const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */ - const char *zYearWeek = P("yw"); /* Show checkins for the given YYYY-WW (weak-of-year) */ + const char *zUses = P("uf"); /* Only show check-ins hold this file */ + const char *zYearMonth = P("ym"); /* Show check-ins for the given YYYY-MM */ + const char *zYearWeek = P("yw"); /* Check-ins for YYYY-WW (week-of-year) */ int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */ - int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */ + int renameOnly = P("namechng")!=0; /* Show only check-ins that rename files */ int tagid; /* Tag ID */ - int tmFlags; /* Timeline flags */ + int tmFlags = 0; /* Timeline flags */ const char *zThisTag = 0; /* Suppress links to this tag */ const char *zThisUser = 0; /* Suppress links to this user */ HQuery url; /* URL for various branch links */ int from_rid = name_to_typed_rid(P("from"),"ci"); /* from= for paths */ int to_rid = name_to_typed_rid(P("to"),"ci"); /* to= for path timelines */ @@ -1062,57 +1260,93 @@ int noMerge = P("shortest")==0; /* Follow merge links if shorter */ int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */ int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */ int pd_rid; double rBefore, rAfter, rCirca; /* Boundary times */ + const char *z; + char *zOlderButton = 0; /* URL for Older button at the bottom */ + int selectedRid = -9999999; /* Show a highlight on this RID */ + int disableY = 0; /* Disable type selector on submenu */ + + /* Set number of rows to display */ + z = P("n"); + if( z ){ + if( fossil_strcmp(z,"all")==0 ){ + nEntry = 0; + }else{ + nEntry = atoi(z); + if( nEntry<=0 ){ + cgi_replace_query_parameter("n","10"); + nEntry = 10; + } + } + }else if( zCirca ){ + cgi_replace_query_parameter("n","11"); + nEntry = 11; + }else{ + cgi_replace_query_parameter("n","50"); + nEntry = 50; + } /* To view the timeline, must have permission to read project data. */ pd_rid = name_to_typed_rid(P("dp"),"ci"); if( pd_rid ){ p_rid = d_rid = pd_rid; } login_check_credentials(); if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){ - login_needed(); + login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); return; } url_initialize(&url, "timeline"); + cgi_query_parameters_to_url(&url); if( zTagName && g.perm.Read ){ tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName); zThisTag = zTagName; }else if( zBrName && g.perm.Read ){ tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName); zThisTag = zBrName; }else{ tagid = 0; + } + if( zMark && zMark[0]==0 ){ + if( zAfter ) zMark = zAfter; + if( zBefore ) zMark = zBefore; + if( zCirca ) zMark = zCirca; + } + if( tagid>0 + && db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry + ){ + nEntry = -1; + zCirca = 0; } if( zType[0]=='a' ){ - tmFlags = TIMELINE_BRIEF | TIMELINE_GRAPH; + tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH; }else{ - tmFlags = TIMELINE_GRAPH; + tmFlags |= TIMELINE_GRAPH; } - if( P("ng")!=0 || zSearch!=0 ){ + if( PB("ng") || zSearch!=0 ){ tmFlags &= ~TIMELINE_GRAPH; - url_add_parameter(&url, "ng", 0); } - if( P("brbg")!=0 ){ + if( PB("brbg") ){ tmFlags |= TIMELINE_BRCOLOR; - url_add_parameter(&url, "brbg", 0); + } + if( PB("unhide") ){ + tmFlags |= TIMELINE_UNHIDE; } - if( P("ubg")!=0 ){ + if( PB("ubg") ){ tmFlags |= TIMELINE_UCOLOR; - url_add_parameter(&url, "ubg", 0); } if( zUses!=0 ){ int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses); if( ufid ){ zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid); - url_add_parameter(&url, "uf", zUses); db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)"); compute_uses_file("usesfile", ufid, 0); zType = "ci"; + disableY = 1; }else{ zUses = 0; } } if( renameOnly ){ @@ -1119,24 +1353,30 @@ db_multi_exec( "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);" "INSERT OR IGNORE INTO rnfile" " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;" ); + disableY = 1; } style_header("Timeline"); login_anonymous_available(); timeline_temp_table(); blob_zero(&sql); blob_zero(&desc); blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1); blob_append(&sql, timeline_query_for_www(), -1); - if( P("fc")!=0 || P("v")!=0 || P("detail")!=0 ){ + if( PB("fc") || PB("v") || PB("detail") ){ tmFlags |= TIMELINE_FCHANGES; - url_add_parameter(&url, "v", 0); } - if( !useDividers ) url_add_parameter(&url, "nd", 0); + if( (tmFlags & TIMELINE_UNHIDE)==0 ){ + blob_append_sql(&sql, + " AND NOT EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", + TAG_HIDDEN + ); + } if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){ /* If from= and to= are present, display all nodes on a path connecting ** the two */ PathNode *p = 0; const char *zFrom = 0; @@ -1153,57 +1393,68 @@ zFrom = P("me"); zTo = P("you"); } blob_append(&sql, " AND event.objid IN (0", -1); while( p ){ - blob_appendf(&sql, ",%d", p->rid); + blob_append_sql(&sql, ",%d", p->rid); p = p->u.pTo; } blob_append(&sql, ")", -1); path_reset(); blob_append(&desc, "All nodes on the path from ", -1); blob_appendf(&desc, "%z[%h]</a>", href("%R/info/%h", zFrom), zFrom); blob_append(&desc, " to ", -1); blob_appendf(&desc, "%z[%h]</a>", href("%R/info/%h",zTo), zTo); tmFlags |= TIMELINE_DISJOINT; - db_multi_exec("%s", blob_str(&sql)); + db_multi_exec("%s", blob_sql_text(&sql)); }else if( (p_rid || d_rid) && g.perm.Read ){ /* If p= or d= is present, ignore all other parameters other than n= */ char *zUuid; int np, nd; + tmFlags |= TIMELINE_DISJOINT; if( p_rid && d_rid ){ if( p_rid!=d_rid ) p_rid = d_rid; if( P("n")==0 ) nEntry = 10; } db_multi_exec( "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)" ); zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", p_rid ? p_rid : d_rid); - blob_appendf(&sql, " AND event.objid IN ok"); + blob_append_sql(&sql, " AND event.objid IN ok"); nd = 0; if( d_rid ){ compute_descendants(d_rid, nEntry+1); nd = db_int(0, "SELECT count(*)-1 FROM ok"); - if( nd>=0 ) db_multi_exec("%s", blob_str(&sql)); + if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql)); if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s"); - if( useDividers ) timeline_add_dividers(0, d_rid); + if( useDividers ) selectedRid = d_rid; db_multi_exec("DELETE FROM ok"); } if( p_rid ){ compute_ancestors(p_rid, nEntry+1, 0); np = db_int(0, "SELECT count(*)-1 FROM ok"); if( np>0 ){ if( nd>0 ) blob_appendf(&desc, " and "); blob_appendf(&desc, "%d ancestors", np); - db_multi_exec("%s", blob_str(&sql)); + db_multi_exec("%s", blob_sql_text(&sql)); + } + if( useDividers ) selectedRid = p_rid; + } + blob_appendf(&desc, " of %z[%S]</a>", + href("%R/info/%!S", zUuid), zUuid); + if( d_rid ){ + if( p_rid ){ + /* If both p= and d= are set, we don't have the uuid of d yet. */ + zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid); } - if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid); } - blob_appendf(&desc, " of %z[%.10s]</a>", - href("%R/info/%s", zUuid), zUuid); + style_submenu_entry("n","Max:",4,0); + timeline_y_submenu(1); + style_submenu_binary("v","With Files","Without Files", + zType[0]!='a' && zType[0]!='c'); }else if( f_rid && g.perm.Read ){ /* If f= is present, ignore all other parameters other than n= */ char *zUuid; db_multi_exec( "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);" @@ -1210,69 +1461,81 @@ "INSERT INTO ok VALUES(%d);" "INSERT OR IGNORE INTO ok SELECT pid FROM plink WHERE cid=%d;" "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;", f_rid, f_rid, f_rid ); - blob_appendf(&sql, " AND event.objid IN ok"); - db_multi_exec("%s", blob_str(&sql)); - if( useDividers ) timeline_add_dividers(0, f_rid); + blob_append_sql(&sql, " AND event.objid IN ok"); + db_multi_exec("%s", blob_sql_text(&sql)); + if( useDividers ) selectedRid = f_rid; blob_appendf(&desc, "Parents and children of check-in "); zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid); - blob_appendf(&desc, "%z[%.10s]</a>", href("%R/info/%s", zUuid), zUuid); + blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid); tmFlags |= TIMELINE_DISJOINT; + style_submenu_binary("v","With Files","Without Files", + zType[0]!='a' && zType[0]!='c'); + if( (tmFlags & TIMELINE_UNHIDE)==0 ){ + timeline_submenu(&url, "Unhide", "unhide", "", 0); + } }else{ /* Otherwise, a timeline based on a span of time */ - int n; + int n, nBefore, nAfter; const char *zEType = "timeline item"; char *zDate; - char *zNEntry = mprintf("%d", nEntry); - url_add_parameter(&url, "n", zNEntry); if( zUses ){ - blob_appendf(&sql, " AND event.objid IN usesfile "); + blob_append_sql(&sql, " AND event.objid IN usesfile "); } if( renameOnly ){ - blob_appendf(&sql, " AND event.objid IN rnfile "); + blob_append_sql(&sql, " AND event.objid IN rnfile "); } if( zYearMonth ){ - blob_appendf(&sql, " AND %Q=strftime('%%Y-%%m',event.mtime) ", + blob_append_sql(&sql, " AND %Q=strftime('%%Y-%%m',event.mtime) ", zYearMonth); } else if( zYearWeek ){ - blob_appendf(&sql, " AND %Q=strftime('%%Y-%%W',event.mtime) ", + blob_append_sql(&sql, " AND %Q=strftime('%%Y-%%W',event.mtime) ", zYearWeek); } if( tagid>0 ){ - blob_appendf(&sql, - "AND (EXISTS(SELECT 1 FROM tagxref" - " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid); + blob_append_sql(&sql, + " AND (EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", tagid); if( zBrName ){ - url_add_parameter(&url, "r", zBrName); - /* The next two blob_appendf() calls add SQL that causes checkins that + /* The next two blob_appendf() calls add SQL that causes check-ins that ** are not part of the branch which are parents or children of the ** branch to be included in the report. This related check-ins are ** useful in helping to visualize what has happened on a quiescent ** branch that is infrequently merged with a much more activate branch. */ - blob_appendf(&sql, - " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=cid" - " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)", - tagid - ); - if( P("mionly")==0 ){ - blob_appendf(&sql, - " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=pid" - " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)", - tagid - ); - }else{ - url_add_parameter(&url, "mionly", "1"); - } - }else{ - url_add_parameter(&url, "t", zTagName); - } - blob_appendf(&sql, ")"); + blob_append_sql(&sql, + " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=cid" + " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)\n", + tagid + ); + if( (tmFlags & TIMELINE_UNHIDE)==0 ){ + blob_append_sql(&sql, + " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid" + " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)\n", + TAG_HIDDEN + ); + } + if( P("mionly")==0 ){ + blob_append_sql(&sql, + " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=pid" + " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)\n", + tagid + ); + if( (tmFlags & TIMELINE_UNHIDE)==0 ){ + blob_append_sql(&sql, + " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid" + " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)\n", + TAG_HIDDEN + ); + } + } + } + blob_append_sql(&sql, ")"); } if( (zType[0]=='w' && !g.perm.RdWiki) || (zType[0]=='t' && !g.perm.RdTkt) || (zType[0]=='e' && !g.perm.RdWiki) || (zType[0]=='c' && !g.perm.Read) @@ -1281,110 +1544,116 @@ zType = "all"; } if( zType[0]=='a' ){ if( !g.perm.Read || !g.perm.RdWiki || !g.perm.RdTkt ){ char cSep = '('; - blob_appendf(&sql, " AND event.type IN "); + blob_append_sql(&sql, " AND event.type IN "); if( g.perm.Read ){ - blob_appendf(&sql, "%c'ci','g'", cSep); + blob_append_sql(&sql, "%c'ci','g'", cSep); cSep = ','; } if( g.perm.RdWiki ){ - blob_appendf(&sql, "%c'w','e'", cSep); + blob_append_sql(&sql, "%c'w','e'", cSep); cSep = ','; } if( g.perm.RdTkt ){ - blob_appendf(&sql, "%c't'", cSep); + blob_append_sql(&sql, "%c't'", cSep); cSep = ','; } - blob_appendf(&sql, ")"); + blob_append_sql(&sql, ")"); } }else{ /* zType!="all" */ - blob_appendf(&sql, " AND event.type=%Q", zType); - url_add_parameter(&url, "y", zType); + blob_append_sql(&sql, " AND event.type=%Q", zType); if( zType[0]=='c' ){ - zEType = "checkin"; + zEType = "check-in"; }else if( zType[0]=='w' ){ zEType = "wiki edit"; }else if( zType[0]=='t' ){ zEType = "ticket change"; }else if( zType[0]=='e' ){ - zEType = "event"; + zEType = "technical note"; }else if( zType[0]=='g' ){ zEType = "tag"; } } if( zUser ){ - blob_appendf(&sql, " AND (event.user=%Q OR event.euser=%Q)", + int n = db_int(0,"SELECT count(*) FROM event" + " WHERE user=%Q OR euser=%Q", zUser, zUser); + if( n<=nEntry ){ + zCirca = zBefore = zAfter = 0; + nEntry = -1; + } + blob_append_sql(&sql, " AND (event.user=%Q OR event.euser=%Q)", zUser, zUser); - url_add_parameter(&url, "u", zUser); zThisUser = zUser; } - if ( zSearch ){ - blob_appendf(&sql, + if( zSearch ){ + blob_append_sql(&sql, " AND (event.comment LIKE '%%%q%%' OR event.brief LIKE '%%%q%%')", zSearch, zSearch); - url_add_parameter(&url, "s", zSearch); } rBefore = symbolic_name_to_mtime(zBefore); rAfter = symbolic_name_to_mtime(zAfter); rCirca = symbolic_name_to_mtime(zCirca); if( rAfter>0.0 ){ if( rBefore>0.0 ){ - blob_appendf(&sql, + blob_append_sql(&sql, " AND event.mtime>=%.17g AND event.mtime<=%.17g" " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND); - url_add_parameter(&url, "a", zAfter); - url_add_parameter(&url, "b", zBefore); - nEntry = 1000000; + nEntry = -1; }else{ - blob_appendf(&sql, + blob_append_sql(&sql, " AND event.mtime>=%.17g ORDER BY event.mtime ASC", rAfter-ONE_SECOND); - url_add_parameter(&url, "a", zAfter); } + zCirca = 0; + url_add_parameter(&url, "c", 0); }else if( rBefore>0.0 ){ - blob_appendf(&sql, + blob_append_sql(&sql, " AND event.mtime<=%.17g ORDER BY event.mtime DESC", rBefore+ONE_SECOND); - url_add_parameter(&url, "b", zBefore); + zCirca = 0; + url_add_parameter(&url, "c", 0); }else if( rCirca>0.0 ){ Blob sql2; - blob_init(&sql2, blob_str(&sql), -1); - blob_appendf(&sql2, - " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d", - rCirca, (nEntry+1)/2 - ); - db_multi_exec("%s", blob_str(&sql2)); + blob_init(&sql2, blob_sql_text(&sql), -1); + blob_append_sql(&sql2, + " AND event.mtime<=%f ORDER BY event.mtime DESC", rCirca); + if( nEntry>0 ){ + blob_append_sql(&sql2," LIMIT %d", (nEntry+1)/2); + nEntry -= (nEntry+1)/2; + } + if( PB("showsql") ){ + @ <pre>%h(blob_sql_text(&sql2))</pre> + } + db_multi_exec("%s", blob_sql_text(&sql2)); blob_reset(&sql2); - blob_appendf(&sql, + blob_append_sql(&sql, " AND event.mtime>=%f ORDER BY event.mtime ASC", rCirca ); - nEntry -= (nEntry+1)/2; - if( useDividers ) timeline_add_dividers(rCirca, 0); - url_add_parameter(&url, "c", zCirca); + if( zMark==0 ) zMark = zCirca; }else{ - blob_appendf(&sql, " ORDER BY event.mtime DESC"); + blob_append_sql(&sql, " ORDER BY event.mtime DESC"); } - blob_appendf(&sql, " LIMIT %d", nEntry); - db_multi_exec("%s", blob_str(&sql)); + if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry); + db_multi_exec("%s", blob_sql_text(&sql)); n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/"); if( zYearMonth ){ blob_appendf(&desc, "%s events for %h", zEType, zYearMonth); }else if( zYearWeek ){ blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek); - }else if( zAfter==0 && zBefore==0 && zCirca==0 ){ + }else if( zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){ blob_appendf(&desc, "%d most recent %ss", n, zEType); }else{ blob_appendf(&desc, "%d %ss", n, zEType); } if( zUses ){ char *zFilenames = names_of_file(zUses); blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames, - href("%R/artifact/%S",zUses), zUses); + href("%R/artifact/%!S",zUses), zUses); tmFlags |= TIMELINE_DISJOINT; } if( renameOnly ){ blob_appendf(&desc, " that contain filename changes"); tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES; @@ -1414,71 +1683,84 @@ } if( zSearch ){ blob_appendf(&desc, " matching \"%h\"", zSearch); } if( g.perm.Hyperlink ){ - if( zAfter || n==nEntry ){ - zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/"); - timeline_submenu(&url, "Older", "b", zDate, "a"); - free(zDate); - } - if( zBefore || (zAfter && n==nEntry) ){ - zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/"); - timeline_submenu(&url, "Newer", "a", zDate, "b"); - free(zDate); - }else if( tagid==0 ){ - if( zType[0]!='a' ){ - timeline_submenu(&url, "All Types", "y", "all", 0); - } - if( zType[0]!='w' && g.perm.RdWiki ){ - timeline_submenu(&url, "Wiki Only", "y", "w", 0); - } - if( zType[0]!='c' && g.perm.Read ){ - timeline_submenu(&url, "Checkins Only", "y", "ci", 0); - } - if( zType[0]!='t' && g.perm.RdTkt ){ - timeline_submenu(&url, "Tickets Only", "y", "t", 0); - } - if( zType[0]!='e' && g.perm.RdWiki ){ - timeline_submenu(&url, "Events Only", "y", "e", 0); - } - if( zType[0]!='g' && g.perm.Read ){ - timeline_submenu(&url, "Tags Only", "y", "g", 0); - } - } - if( nEntry>20 ){ - timeline_submenu(&url, "20 Entries", "n", "20", 0); - } - if( nEntry<200 ){ - timeline_submenu(&url, "200 Entries", "n", "200", 0); - } - if( zType[0]=='a' || zType[0]=='c' ){ - if( tmFlags & TIMELINE_FCHANGES ){ - timeline_submenu(&url, "Hide Files", "v", 0, 0); - }else{ - timeline_submenu(&url, "Show Files", "v", "", 0); - } - } - } - } - if( P("showsql") ){ - @ <blockquote>%h(blob_str(&sql))</blockquote> + if( zCirca && rCirca ){ + nBefore = db_int(0, + "SELECT count(*) FROM timeline WHERE etype!='div'" + " AND sortby<=%f /*scan*/", rCirca); + nAfter = db_int(0, + "SELECT count(*) FROM timeline WHERE etype!='div'" + " AND sortby>=%f /*scan*/", rCirca); + zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/"); + if( nBefore>=nEntry ){ + timeline_submenu(&url, "Older", "b", zDate, "c"); + zOlderButton = fossil_strdup(url_render(&url, "b", zDate, "c", 0)); + } + if( nAfter>=nEntry ){ + timeline_submenu(&url, "Newer", "a", zDate, "c"); + } + free(zDate); + }else{ + if( zAfter || n==nEntry ){ + zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/"); + timeline_submenu(&url, "Older", "b", zDate, "a"); + zOlderButton = fossil_strdup(url_render(&url, "b", zDate, "a", 0)); + free(zDate); + } + if( zBefore || (zAfter && n==nEntry) ){ + zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/"); + timeline_submenu(&url, "Newer", "a", zDate, "b"); + free(zDate); + } + } + if( zType[0]=='a' || zType[0]=='c' ){ + if( (tmFlags & TIMELINE_UNHIDE)==0 ){ + timeline_submenu(&url, "Unhide", "unhide", "", 0); + } + } + style_submenu_entry("n","Max:",4,0); + timeline_y_submenu(disableY); + style_submenu_binary("v","With Files","Without Files", + zType[0]!='a' && zType[0]!='c'); + } + } + if( PB("showsql") ){ + @ <pre>%h(blob_sql_text(&sql))</pre> + } + if( search_restrict(SRCH_CKIN)!=0 ){ + style_submenu_element("Search", 0, "%R/search?y=c"); + } + if( PB("showid") ) tmFlags |= TIMELINE_SHOWRID; + if( useDividers && zMark && zMark[0] ){ + double r = symbolic_name_to_mtime(zMark); + if( r>0.0 ) selectedRid = timeline_add_divider(r); } blob_zero(&sql); db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/"); @ <h2>%b(&desc)</h2> blob_reset(&desc); - www_print_timeline(&q, tmFlags, zThisUser, zThisTag, 0); + www_print_timeline(&q, tmFlags, zThisUser, zThisTag, selectedRid, 0); db_finalize(&q); + if( zOlderButton ){ + @ %z(xhref("class='button'","%z",zOlderButton))Older</a> + } style_footer(); } /* ** The input query q selects various records. Print a human-readable ** summary of those records. ** -** Limit the number of entries printed to nLine. +** Limit number of lines or entries printed to nLimit. If nLimit is zero +** there is no limit. If nLimit is greater than zero, limit the number of +** complete entries printed. If nLimit is less than zero, attempt to limit +** the number of lines printed (this is basically the legacy behavior). +** The line limit, if used, is approximate because it is only checked on a +** per-entry basis. If verbose mode, the file name details are considered +** to be part of the entry. ** ** The query should return these columns: ** ** 0. rid ** 1. uuid @@ -1487,40 +1769,50 @@ ** 4. Number of non-merge children ** 5. Number of parents ** 6. mtime ** 7. branch */ -void print_timeline(Stmt *q, int mxLine, int verboseFlag){ +void print_timeline(Stmt *q, int nLimit, int width, int verboseFlag){ + int nAbsLimit = (nLimit >= 0) ? nLimit : -nLimit; int nLine = 0; + int nEntry = 0; char zPrevDate[20]; - const char *zCurrentUuid=0; + const char *zCurrentUuid = 0; int fchngQueryInit = 0; /* True if fchngQuery is initialized */ Stmt fchngQuery; /* Query for file changes on check-ins */ - zPrevDate[0] = 0; + int rc; + zPrevDate[0] = 0; if( g.localOpen ){ int rid = db_lget_int("checkout", 0); zCurrentUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); } - while( db_step(q)==SQLITE_ROW && nLine<=mxLine ){ + while( (rc=db_step(q))==SQLITE_ROW ){ int rid = db_column_int(q, 0); const char *zId = db_column_text(q, 1); const char *zDate = db_column_text(q, 2); const char *zCom = db_column_text(q, 3); int nChild = db_column_int(q, 4); int nParent = db_column_int(q, 5); char *zFree = 0; int n = 0; char zPrefix[80]; - char zUuid[UUID_SIZE+1]; - sqlite3_snprintf(sizeof(zUuid), zUuid, "%.10s", zId); - if( memcmp(zDate, zPrevDate, 10) ){ + if( nAbsLimit!=0 ){ + if( nLimit<0 && nLine>=nAbsLimit ){ + fossil_print("--- line limit (%d) reached ---\n", nAbsLimit); + break; /* line count limit hit, stop. */ + }else if( nEntry>=nAbsLimit ){ + fossil_print("--- entry limit (%d) reached ---\n", nAbsLimit); + break; /* entry count limit hit, stop. */ + } + } + if( fossil_strnicmp(zDate, zPrevDate, 10) ){ fossil_print("=== %.10s ===\n", zDate); memcpy(zPrevDate, zDate, 10); - nLine++; + nLine++; /* record another line */ } if( zCom==0 ) zCom = ""; fossil_print("%.8s ", &zDate[11]); zPrefix[0] = 0; if( nParent>1 ){ @@ -1537,15 +1829,20 @@ sqlite3_snprintf(sizeof(zPrefix)-n, &zPrefix[n], zBrType); n = strlen(zPrefix); } if( fossil_strcmp(zCurrentUuid,zId)==0 ){ sqlite3_snprintf(sizeof(zPrefix)-n, &zPrefix[n], "*CURRENT* "); - n += strlen(zPrefix); + n += strlen(zPrefix+n); + } + if( content_is_private(rid) ){ + sqlite3_snprintf(sizeof(zPrefix)-n, &zPrefix[n], "*UNPUBLISHED* "); + n += strlen(zPrefix+n); } - zFree = sqlite3_mprintf("[%.10s] %s%s", zUuid, zPrefix, zCom); - nLine += comment_print(zFree, 9, 79); - sqlite3_free(zFree); + zFree = mprintf("[%S] %s%s", zId, zPrefix, zCom); + /* record another X lines */ + nLine += comment_print(zFree, zCom, 9, width, g.comFmtFlags); + fossil_free(zFree); if(verboseFlag){ if( !fchngQueryInit ){ db_prepare(&fchngQuery, "SELECT (pid==0) AS isnew," @@ -1569,13 +1866,23 @@ }else if( isDel ){ fossil_print(" DELETED %s\n",zFilename); }else{ fossil_print(" EDITED %s\n", zFilename); } + nLine++; /* record another line */ } db_reset(&fchngQuery); } + nEntry++; /* record another complete entry */ + } + if( rc==SQLITE_DONE ){ + /* Did the underlying query actually have all entries? */ + if( nAbsLimit==0 ){ + fossil_print("+++ end of timeline (%d) +++\n", nEntry); + }else{ + fossil_print("+++ no more data (%d) +++\n", nEntry); + } } if( fchngQueryInit ) db_finalize(&fchngQuery); } /* @@ -1585,11 +1892,11 @@ const char *timeline_query_for_tty(void){ static const char zBaseSql[] = @ SELECT @ blob.rid AS rid, @ uuid, - @ datetime(event.mtime,'localtime') AS mDateTime, + @ datetime(event.mtime%s) AS mDateTime, @ coalesce(ecomment,comment) @ || ' (user: ' || coalesce(euser,user,'?') @ || (SELECT case when length(x)>0 then ' tags: ' || x else '' end @ FROM (SELECT group_concat(substr(tagname,5), ', ') AS x @ FROM tag, tagxref @@ -1600,17 +1907,17 @@ @ AS primPlinkCount, @ (SELECT count(*) FROM plink WHERE cid=blob.rid) AS plinkCount, @ event.mtime AS mtime, @ tagxref.value AS branch @ FROM tag CROSS JOIN event CROSS JOIN blob - @ LEFT JOIN tagxref ON tagxref.tagid=tag.tagid + @ LEFT JOIN tagxref ON tagxref.tagid=tag.tagid @ AND tagxref.tagtype>0 @ AND tagxref.rid=blob.rid @ WHERE blob.rid=event.objid @ AND tag.tagname='branch' ; - return zBaseSql; + return mprintf(zBaseSql /*works-like: "%s"*/, timeline_utc()); } /* ** Return true if the input string is a date in the ISO 8601 format: ** YYYY-MM-DD. @@ -1624,11 +1931,11 @@ } /* ** COMMAND: timeline ** -** Usage: %fossil timeline ?WHEN? ?BASELINE|DATETIME? ?OPTIONS? +** Usage: %fossil timeline ?WHEN? ?CHECKIN|DATETIME? ?OPTIONS? ** ** Print a summary of activity going backwards in date and time ** specified or from the current date and time if no arguments ** are given. The WHEN argument can be any unique abbreviation ** of one of these keywords: @@ -1636,53 +1943,85 @@ ** before ** after ** descendants | children ** ancestors | parents ** -** The BASELINE can be any unique prefix of 4 characters or more. +** The CHECKIN can be any unique prefix of 4 characters or more. ** The DATETIME should be in the ISO8601 format. For ** examples: "2007-08-18 07:21:21". You can also say "current" ** for the current version or "now" for the current time. ** ** Options: -** -n|--limit N Output the first N changes (default 20) +** -n|--limit N Output the first N entries (default 20 lines). +** N=0 means no limit. +** -p|--path PATH Output items affecting PATH only. +** PATH can be a file or a sub directory. +** --offset P skip P changes ** -t|--type TYPE Output items from the given types only, such as: ** ci = file commits only -** e = events only +** e = technical notes only ** t = tickets only ** w = wiki commits only ** -v|--verbose Output the list of files changed by each commit ** and the type of each change (edited, deleted, -** etc.) after the checkin comment. +** etc.) after the check-in comment. +** -W|--width <num> Width of lines (default is to auto-detect). Must be +** >20 or 0 (= no limit, resulting in a single line per +** entry). +** -R REPO_FILE Specifies the repository db to use. Default is +** the current checkout's repository. */ void timeline_cmd(void){ Stmt q; - int n, k; + int n, k, width; const char *zLimit; + const char *zWidth; + const char *zOffset; const char *zType; char *zOrigin; char *zDate; Blob sql; int objid = 0; Blob uuid; int mode = 0 ; /* 0:none 1: before 2:after 3:children 4:parents */ int verboseFlag = 0 ; + int iOffset; + const char *zFilePattern = 0; + Blob treeName; + verboseFlag = find_option("verbose","v", 0)!=0; if( !verboseFlag){ verboseFlag = find_option("showfiles","f", 0)!=0; /* deprecated */ } db_find_and_open_repository(0, 0); zLimit = find_option("limit","n",1); + zWidth = find_option("width","W",1); zType = find_option("type","t",1); - if ( !zLimit ){ + zFilePattern = find_option("path","p",1); + + if( !zLimit ){ zLimit = find_option("count",0,1); } if( zLimit ){ n = atoi(zLimit); }else{ - n = 20; + n = -20; + } + if( zWidth ){ + width = atoi(zWidth); + if( (width!=0) && (width<=20) ){ + fossil_fatal("-W|--width value must be >20 or 0"); + } + }else{ + width = -1; } + zOffset = find_option("offset",0,1); + iOffset = zOffset ? atoi(zOffset) : 0; + + /* We should be done with options.. */ + verify_all_options(); + if( g.argc>=4 ){ k = strlen(g.argv[2]); if( strncmp(g.argv[2],"before",k)==0 ){ mode = 1; }else if( strncmp(g.argv[2],"after",k)==0 && k>1 ){ @@ -1694,11 +2033,12 @@ }else if( strncmp(g.argv[2],"ancestors",k)==0 && k>1 ){ mode = 4; }else if( strncmp(g.argv[2],"parents",k)==0 ){ mode = 4; }else if(!zType && !zLimit){ - usage("?WHEN? ?BASELINE|DATETIME? ?-n|--limit N? ?-t|--type TYPE?"); + usage("?WHEN? ?CHECKIN|DATETIME? ?-n|--limit #? ?-t|--type TYPE? " + "?-W|--width WIDTH? ?-p|--path PATH"); } if( '-' != *g.argv[3] ){ zOrigin = g.argv[3]; }else{ zOrigin = "now"; @@ -1733,72 +2073,108 @@ if( mode==0 ){ if( isIsoDate(zOrigin) ) zShift = ",'+1 day'"; } zDate = mprintf("(SELECT julianday(%Q%s, 'utc'))", zOrigin, zShift); } + + if( zFilePattern ){ + if( zType==0 ){ + /* When zFilePattern is specified and type is not specified, only show + * file check-ins */ + zType="ci"; + } + file_tree_name(zFilePattern, &treeName, 1); + if( fossil_strcmp(blob_str(&treeName), ".")==0 ){ + /* When zTreeName refers to g.zLocalRoot, it's like not specifying + * zFilePattern. */ + zFilePattern = 0; + } + } + if( mode==0 ) mode = 1; blob_zero(&sql); blob_append(&sql, timeline_query_for_tty(), -1); - blob_appendf(&sql, " AND event.mtime %s %s", + blob_append_sql(&sql, "\n AND event.mtime %s %s", (mode==1 || mode==4) ? "<=" : ">=", - zDate + zDate /*safe-for-%s*/ ); if( mode==3 || mode==4 ){ db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); if( mode==3 ){ compute_descendants(objid, n); }else{ compute_ancestors(objid, n, 0); } - blob_appendf(&sql, " AND blob.rid IN ok"); + blob_append_sql(&sql, "\n AND blob.rid IN ok"); } if( zType && (zType[0]!='a') ){ - blob_appendf(&sql, " AND event.type=%Q ", zType); + blob_append_sql(&sql, "\n AND event.type=%Q ", zType); + } + if( zFilePattern ){ + blob_append(&sql, + "\n AND EXISTS(SELECT 1 FROM mlink\n" + " WHERE mlink.mid=event.objid\n" + " AND mlink.fnid IN ", -1); + if( filenames_are_case_sensitive() ){ + blob_append_sql(&sql, + "(SELECT fnid FROM filename" + " WHERE name=%Q" + " OR name GLOB '%q/*')", + blob_str(&treeName), blob_str(&treeName)); + }else{ + blob_append_sql(&sql, + "(SELECT fnid FROM filename" + " WHERE name=%Q COLLATE nocase" + " OR lower(name) GLOB lower('%q/*'))", + blob_str(&treeName), blob_str(&treeName)); + } + blob_append(&sql, ")", -1); + } + blob_append_sql(&sql, "\nORDER BY event.mtime DESC"); + if( iOffset>0 ){ + /* Don't handle LIMIT here, otherwise print_timeline() + * will not determine the end-marker correctly! */ + blob_append_sql(&sql, "\n LIMIT -1 OFFSET %d", iOffset); } - blob_appendf(&sql, " ORDER BY event.mtime DESC"); - db_prepare(&q, blob_str(&sql)); + db_prepare(&q, "%s", blob_sql_text(&sql)); blob_reset(&sql); - print_timeline(&q, n, verboseFlag); + print_timeline(&q, n, width, verboseFlag); db_finalize(&q); } /* -** This is a version of the "localtime()" function from the standard -** C library. It converts a unix timestamp (seconds since 1970) into -** a broken-out local time structure. +** Return one of two things: +** +** ",'localtime'" if the timeline-utc property is set to 0. ** -** This modified version of localtime() works like the library localtime() -** by default. Except if the timeline-utc property is set, this routine -** uses gmttime() instead. Thus by setting the timeline-utc property, we -** can get all localtimes to be displayed at UTC time. +** "" (empty string) otherwise. */ -struct tm *fossil_localtime(const time_t *clock){ +const char *timeline_utc(){ if( g.fTimeFormat==0 ){ if( db_get_int("timeline-utc", 1) ){ g.fTimeFormat = 1; }else{ g.fTimeFormat = 2; } } - if( clock==0 ) return 0; if( g.fTimeFormat==1 ){ - return gmtime(clock); + return ""; }else{ - return localtime(clock); + return ",'localtime'"; } } /* ** COMMAND: test-timewarp-list ** ** Usage: %fossil test-timewarp-list ?-v|---verbose? ** -** Display all instances of child checkins that appear earlier in time +** Display all instances of child check-ins that appear earlier in time ** than their parent. If the -v|--verbose option is provided, both the -** parent and child checking and their times are shown. +** parent and child check-ins and their times are shown. */ void test_timewarp_cmd(void){ Stmt q; int verboseFlag; @@ -1833,11 +2209,14 @@ */ void test_timewarp_page(void){ Stmt q; login_check_credentials(); - if( !g.perm.Read || !g.perm.Hyperlink ){ login_needed(); return; } + if( !g.perm.Read || !g.perm.Hyperlink ){ + login_needed(g.anon.Read && g.anon.Hyperlink); + return; + } style_header("Instances of timewarp"); @ <ul> db_prepare(&q, "SELECT blob.uuid " " FROM plink p, plink c, blob" @@ -1845,433 +2224,10 @@ " AND blob.rid=c.cid" ); while( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); @ <li> - @ <a href="%s(g.zTop)/timeline?p=%S(zUuid)&d=%S(zUuid)">%S(zUuid)</a> + @ <a href="%R/timeline?dp=%!S(zUuid)&unhide">%S(zUuid)</a> } db_finalize(&q); - style_footer(); -} - -/* -** Helper for stats_report_by_month_year(), which generates a list of -** week numbers. zTimeframe should be either a timeframe in the form YYYY -** or YYYY-MM. -*/ -static void stats_report_output_week_links(const char * zTimeframe){ - Stmt stWeek = empty_Stmt; - char yearPart[5] = {0,0,0,0,0}; - memcpy(yearPart, zTimeframe, 4); - db_prepare(&stWeek, - "SELECT DISTINCT strftime('%%W',mtime) AS wk, " - "count(*) AS n, " - "substr(date(mtime),1,%d) AS ym " - "FROM event " - "WHERE ym=%Q AND mtime < current_timestamp " - "GROUP BY wk ORDER BY wk", - strlen(zTimeframe), - zTimeframe); - while( SQLITE_ROW == db_step(&stWeek) ){ - const char * zWeek = db_column_text(&stWeek,0); - const int nCount = db_column_int(&stWeek,1); - cgi_printf("<a href='%s/timeline?" - "yw=%t-%t&n=%d'>%s</a>", - g.zTop, yearPart, zWeek, - nCount, zWeek); - } - db_finalize(&stWeek); -} - -/* -** Implements the "byyear" and "bymonth" reports for /reports. -** If includeMonth is true then it generates the "bymonth" report, -** else the "byyear" report. If zUserName is not NULL and not empty -** then the report is restricted to events created by the named user -** account. -*/ -static void stats_report_by_month_year(char includeMonth, - char includeWeeks, - const char * zUserName){ - Stmt query = empty_Stmt; - int nRowNumber = 0; /* current TR number */ - int nEventTotal = 0; /* Total event count */ - int rowClass = 0; /* counter for alternating - row colors */ - Blob sql = empty_blob; /* SQL */ - const char * zTimeLabel = includeMonth ? "Year/Month" : "Year"; - char zPrevYear[5] = {0}; /* For keeping track of when - we change years while looping */ - int nEventsPerYear = 0; /* Total event count for the - current year */ - char showYearTotal = 0; /* Flag telling us when to show - the per-year event totals */ - Blob header = empty_blob; /* Page header text */ - int nMaxEvents = 1; /* for calculating length of graph - bars. */ - int iterations = 0; /* number of weeks/months we iterate - over */ - blob_appendf(&header, "Timeline Events by year%s", - (includeMonth ? "/month" : "")); - blob_appendf(&sql, - "SELECT substr(date(mtime),1,%d) AS timeframe, " - "count(*) AS eventCount " - "FROM event ", - includeMonth ? 7 : 4); - if(zUserName&&*zUserName){ - blob_appendf(&sql, " WHERE user=%Q ", zUserName); - blob_appendf(&header," for user %q", zUserName); - } - blob_append(&sql, - " GROUP BY timeframe" - " ORDER BY timeframe DESC", - -1); - db_prepare(&query, blob_str(&sql)); - blob_reset(&sql); - @ <h1>%b(&header)</h1> - @ <table class='statistics-report-table-events' border='0' cellpadding='2' - @ cellspacing='0' id='statsTable'> - @ <thead> - @ <th>%s(zTimeLabel)</th> - @ <th>Events</th> - @ <th width='90%%'><!-- relative commits graph --></th> - @ </thead><tbody> - blob_reset(&header); - /* - Run the query twice. The first time we calculate the maximum - number of events for a given row. Maybe someone with better SQL - Fu can re-implement this with a single query. - */ - while( SQLITE_ROW == db_step(&query) ){ - const int nCount = db_column_int(&query, 1); - if(nCount>nMaxEvents){ - nMaxEvents = nCount; - } - ++iterations; - } - db_reset(&query); - while( SQLITE_ROW == db_step(&query) ){ - const char * zTimeframe = db_column_text(&query, 0); - const int nCount = db_column_int(&query, 1); - const int nSize = nCount - ? (int)(100 * nCount / nMaxEvents) - : 1; - showYearTotal = 0; - if(includeMonth){ - /* For Month/year view, add a separator for each distinct year. */ - if(!*zPrevYear || - (0!=fossil_strncmp(zPrevYear,zTimeframe,4))){ - showYearTotal = *zPrevYear; - if(showYearTotal){ - rowClass = ++nRowNumber % 2; - @ <tr class='row%d(rowClass)'> - @ <td></td> - @ <td colspan='2'>Yearly total: %d(nEventsPerYear)</td> - @</tr> - } - nEventsPerYear = 0; - memcpy(zPrevYear,zTimeframe,4); - rowClass = ++nRowNumber % 2; - @ <tr class='row%d(rowClass)'> - @ <th colspan='3' class='statistics-report-row-year'>%s(zPrevYear)</th> - @ </tr> - } - } - rowClass = ++nRowNumber % 2; - nEventTotal += nCount; - nEventsPerYear += nCount; - @<tr class='row%d(rowClass)'> - @ <td> - if(includeMonth){ - cgi_printf("<a href='%s/timeline?" - "ym=%t&n=%d", - g.zTop, zTimeframe, nCount ); - /* Reminder: n=nCount is not actually correct for bymonth unless - that was the only user who caused events. - */ - if( zUserName && *zUserName ){ - cgi_printf("&u=%t", zUserName); - } - cgi_printf("' target='_new'>%s</a>",zTimeframe); - }else { - cgi_printf("<a href='?view=byweek&y=%s", zTimeframe); - if(zUserName && *zUserName){ - cgi_printf("&u=%t", zUserName); - } - cgi_printf("'>%s</a>", zTimeframe); - } - @ </td><td>%d(nCount)</td> - @ <td> - @ <div class='statistics-report-graph-line' - @ style='height:16px;width:%d(nSize)%%;'> - @ </div></td> - @</tr> - if(includeWeeks){ - /* This part works fine for months but it terribly slow (4.5s on my PC), - so it's only shown for by-year for now. Suggestions/patches for - a better/faster layout are welcomed. */ - @ <tr class='row%d(rowClass)'> - @ <td colspan='2' class='statistics-report-week-number-label'>Week #:</td> - @ <td class='statistics-report-week-of-year-list'> - stats_report_output_week_links(zTimeframe); - @ </td></tr> - } - - /* - Potential improvement: calculate the min/max event counts and - use percent-based graph bars. - */ - } - db_finalize(&query); - if(includeMonth && !showYearTotal && *zPrevYear){ - /* Add final year total separator. */ - rowClass = ++nRowNumber % 2; - @ <tr class='row%d(rowClass)'> - @ <td></td> - @ <td colspan='2'>Yearly total: %d(nEventsPerYear)</td> - @</tr> - } - @ </tbody></table> - if(nEventTotal){ - char const * zAvgLabel = includeMonth ? "month" : "year"; - int nAvg = iterations ? (nEventTotal/iterations) : 0; - @ <br><div>Total events: %d(nEventTotal) - @ <br>Average per active %s(zAvgLabel): %d(nAvg) - @ </div> - } - if( !includeMonth ){ - output_table_sorting_javascript("statsTable","tnx"); - } -} - -/* -** Implements the "byuser" view for /reports. -*/ -static void stats_report_by_user(){ - Stmt query = empty_Stmt; - int nRowNumber = 0; /* current TR number */ - int nEventTotal = 0; /* Total event count */ - int rowClass = 0; /* counter for alternating - row colors */ - Blob sql = empty_blob; /* SQL */ - int nMaxEvents = 1; /* max number of events for - all rows. */ - blob_append(&sql, - "SELECT user, " - "COUNT(*) AS eventCount " - "FROM event " - "GROUP BY user ORDER BY eventCount DESC", - -1); - db_prepare(&query, blob_str(&sql)); - blob_reset(&sql); - @ <h1>Timeline Events by User</h1> - @ <table class='statistics-report-table-events' border='0' - @ cellpadding='2' cellspacing='0' id='statsTable'> - @ <thead><tr> - @ <th>User</th> - @ <th>Events</th> - @ <th width='90%%'><!-- relative commits graph --></th> - @ </tr></thead><tbody> - while( SQLITE_ROW == db_step(&query) ){ - const int nCount = db_column_int(&query, 1); - if(nCount>nMaxEvents){ - nMaxEvents = nCount; - } - } - db_reset(&query); - while( SQLITE_ROW == db_step(&query) ){ - const char * zUser = db_column_text(&query, 0); - const int nCount = db_column_int(&query, 1); - const int nSize = nCount - ? (int)(100 * nCount / nMaxEvents) - : 0; - if(!nCount) continue /* arguable! Possible? */; - rowClass = ++nRowNumber % 2; - nEventTotal += nCount; - @<tr class='row%d(rowClass)'> - @ <td> - @ <a href="?view=bymonth&user=%h(zUser)">%h(zUser)</a> - @ </td><td>%d(nCount)</td> - @ <td> - @ <div class='statistics-report-graph-line' - @ style='height:16px;width:%d(nSize)%%;'> - @ </div></td> - @</tr> - /* - Potential improvement: calculate the min/max event counts and - use percent-based graph bars. - */ - } - @ </tbody></table> - db_finalize(&query); - output_table_sorting_javascript("statsTable","tnx"); -} - - -/* -** Helper for stats_report_by_month_year(), which generates a list of -** week numbers. zTimeframe should be either a timeframe in the form YYYY -** or YYYY-MM. -*/ -static void stats_report_year_weeks(const char * zUserName){ - const char * zYear = P("y"); - int nYear = zYear ? strlen(zYear) : 0; - int i = 0; - Stmt qYears = empty_Stmt; - char * zDefaultYear = NULL; - Blob sql = empty_blob; - int nMaxEvents = 1; /* max number of events for - all rows. */ - int iterations = 0; /* # of active time periods. */ - - cgi_printf("Select year: "); - blob_append(&sql, - "SELECT DISTINCT substr(date(mtime),1,4) AS y " - "FROM event WHERE 1 ", -1); - if(zUserName&&*zUserName){ - blob_appendf(&sql,"AND user=%Q ", zUserName); - } - blob_append(&sql,"GROUP BY y ORDER BY y", -1); - db_prepare(&qYears, blob_str(&sql)); - blob_reset(&sql); - while( SQLITE_ROW == db_step(&qYears) ){ - const char * zT = db_column_text(&qYears, 0); - if( i++ ){ - cgi_printf(" "); - } - cgi_printf("<a href='?view=byweek&y=%s", zT); - if(zUserName && *zUserName){ - cgi_printf("&user=%t",zUserName); - } - cgi_printf("'>%s</a>",zT); - } - db_finalize(&qYears); - cgi_printf("<br/>"); - if(!zYear || !*zYear){ - zDefaultYear = db_text("????", "SELECT strftime('%%Y')"); - zYear = zDefaultYear; - nYear = 4; - } - if(4 == nYear){ - Stmt stWeek = empty_Stmt; - int rowCount = 0; - int total = 0; - Blob header = empty_blob; - blob_appendf(&header, "Timeline events for the calendar weeks " - "of %h", zYear); - blob_appendf(&sql, - "SELECT DISTINCT strftime('%%%%W',mtime) AS wk, " - "count(*) AS n " - "FROM event " - "WHERE %Q=substr(date(mtime),1,4) " - "AND mtime < current_timestamp ", - zYear); - if(zUserName&&*zUserName){ - blob_appendf(&sql, " AND user=%Q ", zUserName); - blob_appendf(&header," for user %h", zUserName); - } - blob_appendf(&sql, "GROUP BY wk ORDER BY wk DESC"); - cgi_printf("<h1>%h</h1>", blob_str(&header)); - blob_reset(&header); - cgi_printf("<table class='statistics-report-table-events' " - "border='0' cellpadding='2' width='100%%' " - "cellspacing='0' id='statsTable'>"); - cgi_printf("<thead><tr>" - "<th>Week</th>" - "<th>Events</th>" - "<th width='90%%'><!-- relative commits graph --></th>" - "</tr></thead>" - "<tbody>"); - db_prepare(&stWeek, blob_str(&sql)); - blob_reset(&sql); - while( SQLITE_ROW == db_step(&stWeek) ){ - const int nCount = db_column_int(&stWeek, 1); - if(nCount>nMaxEvents){ - nMaxEvents = nCount; - } - ++iterations; - } - db_reset(&stWeek); - while( SQLITE_ROW == db_step(&stWeek) ){ - const char * zWeek = db_column_text(&stWeek,0); - const int nCount = db_column_int(&stWeek,1); - const int nSize = nCount - ? (int)(100 * nCount / nMaxEvents) - : 0; - total += nCount; - cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); - cgi_printf("<td><a href='%s/timeline?yw=%t-%s&n=%d", - g.zTop, zYear, zWeek, nCount); - if(zUserName && *zUserName){ - cgi_printf("&u=%t",zUserName); - } - cgi_printf("'>%s</a></td>",zWeek); - - cgi_printf("<td>%d</td>",nCount); - cgi_printf("<td>"); - if(nCount){ - cgi_printf("<div class='statistics-report-graph-line'" - "style='height:16px;width:%d%%;'></div>", - nSize); - } - cgi_printf("</td></tr>"); - } - db_finalize(&stWeek); - free(zDefaultYear); - cgi_printf("</tbody></table>"); - if(total){ - int nAvg = iterations ? (total/iterations) : 0; - cgi_printf("<br><div>Total events: %d<br>" - "Average per active week: %d</div>", - total, nAvg); - } - output_table_sorting_javascript("statsTable","tnx"); - } -} - -/* -** WEBPAGE: reports -** -** Shows activity reports for the repository. -** -** Query Parameters: -** -** view=REPORT_NAME Valid values: bymonth, byyear, byuser -** user=NAME Restricts statistics to the given user -*/ -void stats_report_page(){ - HQuery url; /* URL for various branch links */ - const char * zView = P("view"); /* Which view/report to show. */ - const char *zUserName = P("user"); - if(!zUserName) zUserName = P("u"); - url_initialize(&url, "reports"); - - if(zUserName && *zUserName){ - url_add_parameter(&url,"user", zUserName); - timeline_submenu(&url, "(Remove User Flag)", "view", zView, "user"); - } - timeline_submenu(&url, "By Year", "view", "byyear", 0); - timeline_submenu(&url, "By Month", "view", "bymonth", 0); - timeline_submenu(&url, "By Week", "view", "byweek", 0); - timeline_submenu(&url, "By User", "view", "byuser", "user"); - url_reset(&url); - style_header("Activity Reports"); - if(0==fossil_strcmp(zView,"byyear")){ - stats_report_by_month_year(0, 0, zUserName); - }else if(0==fossil_strcmp(zView,"bymonth")){ - stats_report_by_month_year(1, 0, zUserName); - }else if(0==fossil_strcmp(zView,"byweek")){ - stats_report_year_weeks(zUserName); - }else if(0==fossil_strcmp(zView,"byuser")){ - stats_report_by_user(); - }else{ - @ <h1>Select a report to show:</h1> - @ <ul> - @ <li><a href='?view=byyear'>Events by year</a></li> - @ <li><a href='?view=bymonth'>Events by month</a></li> - @ <li><a href='?view=byweek'>Events by calendar week</a></li> - @ <li><a href='?view=byuser'>Events by user</a></li> - @ </ul> - } - style_footer(); } Index: src/tkt.c ================================================================== --- src/tkt.c +++ src/tkt.c @@ -61,11 +61,11 @@ } return -1; } /* -** Obtain a list of all fields of the TICKET and TICKETCHNG tables. Put them +** Obtain a list of all fields of the TICKET and TICKETCHNG tables. Put them ** in sorted order in aField[]. ** ** The haveTicket and haveTicketChng variables are set to 1 if the TICKET and ** TICKETCHANGE tables exist, respectively. */ @@ -137,12 +137,13 @@ const char *zName; Stmt q; int i, n, size, j; zName = PD("name","-none-"); - db_prepare(&q, "SELECT datetime(tkt_mtime,'localtime') AS tkt_datetime, *" - " FROM ticket WHERE tkt_uuid GLOB '%q*'", zName); + db_prepare(&q, "SELECT datetime(tkt_mtime%s) AS tkt_datetime, *" + " FROM ticket WHERE tkt_uuid GLOB '%q*'", + timeline_utc(), zName); if( db_step(&q)==SQLITE_ROW ){ n = db_column_count(&q); for(i=0; i<n; i++){ const char *zVal = db_column_text(&q, i); const char *zName = db_column_name(&q, i); @@ -202,13 +203,13 @@ tktid = db_last_insert_rowid(); } blob_zero(&sql1); blob_zero(&sql2); blob_zero(&sql3); - blob_appendf(&sql1, "UPDATE OR REPLACE ticket SET tkt_mtime=:mtime"); + blob_append_sql(&sql1, "UPDATE OR REPLACE ticket SET tkt_mtime=:mtime"); if( haveTicketCTime ){ - blob_appendf(&sql1, ", tkt_ctime=coalesce(tkt_ctime,:mtime)"); + blob_append_sql(&sql1, ", tkt_ctime=coalesce(tkt_ctime,:mtime)"); } aUsed = fossil_malloc( nField ); memset(aUsed, 0, nField); for(i=0; i<p->nField; i++){ const char *zName = p->aField[i].zName; @@ -217,55 +218,56 @@ if( j<0 ) continue; aUsed[j] = 1; if( aField[j].mUsed & USEDBY_TICKET ){ if( zName[0]=='+' ){ zName++; - blob_appendf(&sql1,", %s=coalesce(%s,'') || %Q", + blob_append_sql(&sql1,", \"%w\"=coalesce(\"%w\",'') || %Q", zName, zName, p->aField[i].zValue); }else{ - blob_appendf(&sql1,", %s=%Q", zName, p->aField[i].zValue); + blob_append_sql(&sql1,", \"%w\"=%Q", zName, p->aField[i].zValue); } } if( aField[j].mUsed & USEDBY_TICKETCHNG ){ - blob_appendf(&sql2, ",%s", zName); - blob_appendf(&sql3, ",%Q", p->aField[i].zValue); + blob_append_sql(&sql2, ",\"%w\"", zName); + blob_append_sql(&sql3, ",%Q", p->aField[i].zValue); } if( rid>0 ){ wiki_extract_links(p->aField[i].zValue, rid, 1, p->rDate, i==0, 0); } } - blob_appendf(&sql1, " WHERE tkt_id=%d", tktid); - db_prepare(&q, "%s", blob_str(&sql1)); + blob_append_sql(&sql1, " WHERE tkt_id=%d", tktid); + db_prepare(&q, "%s", blob_sql_text(&sql1)); db_bind_double(&q, ":mtime", p->rDate); db_step(&q); db_finalize(&q); blob_reset(&sql1); if( blob_size(&sql2)>0 || haveTicketChngRid ){ int fromTkt = 0; if( haveTicketChngRid ){ blob_append(&sql2, ",tkt_rid", -1); - blob_appendf(&sql3, ",%d", rid); + blob_append_sql(&sql3, ",%d", rid); } for(i=0; i<nField; i++){ if( aUsed[i]==0 && (aField[i].mUsed & USEDBY_BOTH)==USEDBY_BOTH ){ const char *z = aField[i].zName; if( z[0]=='+' ) z++; fromTkt = 1; - blob_appendf(&sql2, ",%s", z); - blob_appendf(&sql3, ",%s", z); + blob_append_sql(&sql2, ",\"%w\"", z); + blob_append_sql(&sql3, ",\"%w\"", z); } } if( fromTkt ){ db_prepare(&q, "INSERT INTO ticketchng(tkt_id,tkt_mtime%s)" "SELECT %d,:mtime%s FROM ticket WHERE tkt_id=%d", - blob_str(&sql2), tktid, blob_str(&sql3), tktid); + blob_sql_text(&sql2), tktid, + blob_sql_text(&sql3), tktid); }else{ db_prepare(&q, "INSERT INTO ticketchng(tkt_id,tkt_mtime%s)" "VALUES(%d,:mtime%s)", - blob_str(&sql2), tktid, blob_str(&sql3)); + blob_sql_text(&sql2), tktid, blob_sql_text(&sql3)); } db_bind_double(&q, ":mtime", p->rDate); db_step(&q); db_finalize(&q); } @@ -272,10 +274,33 @@ blob_reset(&sql2); blob_reset(&sql3); fossil_free(aUsed); return tktid; } + +/* +** Returns non-zero if moderation is required for ticket changes and ticket +** attachments. +*/ +int ticket_need_moderation( + int localUser /* Are we being called for a local interactive user? */ +){ + /* + ** If the FOSSIL_FORCE_TICKET_MODERATION variable is set, *ALL* changes for + ** tickets will be required to go through moderation (even those performed + ** by the local interactive user via the command line). This can be useful + ** for local (or remote) testing of the moderation subsystem and its impact + ** on the contents and status of tickets. + */ + if( fossil_getenv("FOSSIL_FORCE_TICKET_MODERATION")!=0 ){ + return 1; + } + if( localUser ){ + return 0; + } + return g.perm.ModTkt==0 && db_get_boolean("modreq-tkt",0)==1; +} /* ** Rebuild an entire entry in the TICKET table */ void ticket_rebuild_entry(const char *zTktUuid){ @@ -288,10 +313,11 @@ fossil_free(zTag); getAllTicketFields(); if( haveTicket==0 ) return; tktid = db_int(0, "SELECT tkt_id FROM ticket WHERE tkt_uuid=%Q", zTktUuid); + search_doc_touch('t', tktid, 0); if( haveTicketChng ){ db_multi_exec("DELETE FROM ticketchng WHERE tkt_id=%d;", tktid); } db_multi_exec("DELETE FROM ticket WHERE tkt_id=%d", tktid); tktid = 0; @@ -321,13 +347,14 @@ } /* ** Create the TH1 interpreter and load the "change" code. */ -int ticket_change(void){ +int ticket_change(const char *zUuid){ const char *zConfig; Th_FossilInit(TH_INIT_DEFAULT); + Th_Store("uuid", zUuid); zConfig = ticket_change_code(); return Th_Eval(g.interp, 0, zConfig, -1); } /* @@ -343,11 +370,11 @@ zSql = ticket_table_schema(); if( separateConnection ){ db_end_transaction(0); db_init_database(g.zRepositoryName, zSql, 0); }else{ - db_multi_exec("%s", zSql); + db_multi_exec("%s", zSql/*safe-for-%s*/); } } /* ** Repopulate the TICKET and TICKETCHNG tables from scratch using all @@ -401,11 +428,11 @@ @ <font color="blue"> @ <p>Database fields:</p><ul> for(i=0; i<nField; i++){ @ <li>aField[%d(i)].zName = "%h(aField[i].zName)"; @ originally = "%h(aField[i].zValue)"; - @ currently = "%h(PD(aField[i].zName,""))""; + @ currently = "%h(PD(aField[i].zName,""))"; if( aField[i].zAppend ){ @ zAppend = "%h(aField[i].zAppend)"; } @ mUsed = %d(aField[i].mUsed); } @@ -422,37 +449,37 @@ const char *zScript; char *zFullName; const char *zUuid = PD("name",""); login_check_credentials(); - if( !g.perm.RdTkt ){ login_needed(); return; } - if( g.perm.WrTkt || g.perm.ApndTkt ){ + if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; } + if( g.anon.WrTkt || g.anon.ApndTkt ){ style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T", g.zTop, PD("name","")); } if( g.perm.Hyperlink ){ - style_submenu_element("History", "History Of This Ticket", + style_submenu_element("History", "History Of This Ticket", "%s/tkthistory/%T", g.zTop, zUuid); - style_submenu_element("Timeline", "Timeline Of This Ticket", + style_submenu_element("Timeline", "Timeline Of This Ticket", "%s/tkttimeline/%T", g.zTop, zUuid); - style_submenu_element("Check-ins", "Check-ins Of This Ticket", + style_submenu_element("Check-ins", "Check-ins Of This Ticket", "%s/tkttimeline/%T?y=ci", g.zTop, zUuid); } - if( g.perm.NewTkt ){ + if( g.anon.NewTkt ){ style_submenu_element("New Ticket", "Create a new ticket", "%s/tktnew", g.zTop); } - if( g.perm.ApndTkt && g.perm.Attach ){ + if( g.anon.ApndTkt && g.anon.Attach ){ style_submenu_element("Attach", "Add An Attachment", "%s/attachadd?tkt=%T&from=%s/tktview/%t", g.zTop, zUuid, g.zTop, zUuid); } if( P("plaintext") ){ - style_submenu_element("Formatted", "Formatted", "%R/tktview/%S", zUuid); + style_submenu_element("Formatted", "Formatted", "%R/tktview/%s", zUuid); }else{ style_submenu_element("Plaintext", "Plaintext", - "%R/tktview/%S?plaintext", zUuid); + "%R/tktview/%s?plaintext", zUuid); } style_header("View Ticket"); if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1); ticket_init(); initializeVariablesFromCGI(); @@ -462,33 +489,33 @@ if( P("showfields")!=0 ) showAllFields(); if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1); Th_Render(zScript); if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1); - zFullName = db_text(0, + zFullName = db_text(0, "SELECT tkt_uuid FROM ticket" " WHERE tkt_uuid GLOB '%q*'", zUuid); if( zFullName ){ attachment_list(zFullName, "<hr /><h2>Attachments:</h2><ul>"); } - + style_footer(); } /* -** TH command: append_field FIELD STRING +** TH1 command: append_field FIELD STRING ** ** FIELD is the name of a database column to which we might want ** to append text. STRING is the text to be appended to that ** column. The append does not actually occur until the ** submit_ticket command is run. */ static int appendRemarkCmd( - Th_Interp *interp, - void *p, - int argc, - const char **argv, + Th_Interp *interp, + void *p, + int argc, + const char **argv, int *argl ){ int idx; if( argc!=3 ){ @@ -513,33 +540,39 @@ } /* ** Write a ticket into the repository. */ -static void ticket_put( +static int ticket_put( Blob *pTicket, /* The text of the ticket change record */ const char *zTktId, /* The ticket to which this change is applied */ int needMod /* True if moderation is needed */ ){ + int result; int rid = content_put_ex(pTicket, 0, 0, 0, needMod); if( rid==0 ){ fossil_fatal("trouble committing ticket: %s", g.zErrMsg); } if( needMod ){ moderation_table_create(); db_multi_exec( - "INSERT INTO modreq(objid, tktid) VALUES(%d,'%s')", + "INSERT INTO modreq(objid, tktid) VALUES(%d,%Q)", rid, zTktId ); }else{ db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid); db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid); } manifest_crosslink_begin(); - manifest_crosslink(rid, pTicket); + result = (manifest_crosslink(rid, pTicket, MC_NONE)==0); assert( blob_is_reset(pTicket) ); - manifest_crosslink_end(); + if( !result ){ + result = manifest_crosslink_end(MC_PERMIT_HOOKS); + }else{ + manifest_crosslink_end(MC_NONE); + } + return result; } /* ** Subscript command: submit_ticket ** @@ -548,21 +581,22 @@ ** taken from TH variables. If the content is unchanged, the field is ** omitted from the artifact. Fields whose names begin with "private_" ** are concealed using the db_conceal() function. */ static int submitTicketCmd( - Th_Interp *interp, - void *pUuid, - int argc, - const char **argv, + Th_Interp *interp, + void *pUuid, + int argc, + const char **argv, int *argl ){ char *zDate; const char *zUuid; int i; int nJ = 0; Blob tktchng, cksum; + int needMod; login_verify_csrf_secret(); if( !captcha_is_correct() ){ @ <p class="generalError">Error: Incorrect security code.</p> return TH_OK; @@ -599,41 +633,44 @@ nJ++; } } } if( *(char**)pUuid ){ - zUuid = db_text(0, + zUuid = db_text(0, "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%q*'", P("name") ); }else{ zUuid = db_text(0, "SELECT lower(hex(randomblob(20)))"); } *(const char**)pUuid = zUuid; blob_appendf(&tktchng, "K %s\n", zUuid); - blob_appendf(&tktchng, "U %F\n", g.zLogin ? g.zLogin : ""); + blob_appendf(&tktchng, "U %F\n", login_name()); md5sum_blob(&tktchng, &cksum); blob_appendf(&tktchng, "Z %b\n", &cksum); if( nJ==0 ){ blob_reset(&tktchng); return TH_OK; } + needMod = ticket_need_moderation(0); if( g.zPath[0]=='d' ){ + const char *zNeedMod = needMod ? "required" : "skipped"; /* If called from /debug_tktnew or /debug_tktedit... */ @ <font color="blue"> @ <p>Ticket artifact that would have been submitted:</p> @ <blockquote><pre>%h(blob_str(&tktchng))</pre></blockquote> + @ <blockquote><pre>Moderation would be %h(zNeedMod).</pre></blockquote> @ <hr /></font> return TH_OK; - }else if( g.thTrace ){ - Th_Trace("submit_ticket {\n<blockquote><pre>\n%h\n</pre></blockquote>\n" - "}<br />\n", - blob_str(&tktchng)); }else{ - ticket_put(&tktchng, zUuid, - (g.perm.ModTkt==0 && db_get_boolean("modreq-tkt",0)==1)); + if( g.thTrace ){ + Th_Trace("submit_ticket {\n<blockquote><pre>\n%h\n</pre></blockquote>\n" + "}<br />\n", + blob_str(&tktchng)); + } + ticket_put(&tktchng, zUuid, needMod); } - return ticket_change(); + return ticket_change(zUuid); } /* ** WEBPAGE: tktnew @@ -650,15 +687,16 @@ void tktnew_page(void){ const char *zScript; char *zNewUuid = 0; login_check_credentials(); - if( !g.perm.NewTkt ){ login_needed(); return; } + if( !g.perm.NewTkt ){ login_needed(g.anon.NewTkt); return; } if( P("cancel") ){ cgi_redirect("home"); } style_header("New Ticket"); + ticket_standard_submenu(T_ALL_BUT(T_NEW)); if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1); ticket_init(); initializeVariablesFromCGI(); getAllTicketFields(); initializeVariablesFromDb(); @@ -667,11 +705,11 @@ login_insert_csrf_secret(); if( P("date_override") && g.perm.Setup ){ @ <input type="hidden" name="date_override" value="%h(P("date_override"))"> } zScript = ticket_newpage_code(); - Th_Store("login", g.zLogin ? g.zLogin : "nobody"); + Th_Store("login", login_name()); Th_Store("date", db_text(0, "SELECT datetime('now')")); Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zNewUuid, 0); if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br />\n", -1); if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){ @@ -700,11 +738,14 @@ int nName; const char *zName; int nRec; login_check_credentials(); - if( !g.perm.ApndTkt && !g.perm.WrTkt ){ login_needed(); return; } + if( !g.perm.ApndTkt && !g.perm.WrTkt ){ + login_needed(g.anon.ApndTkt || g.anon.WrTkt); + return; + } zName = P("name"); if( P("cancel") ){ cgi_redirectf("tktview?name=%T", zName); } style_header("Edit Ticket"); @@ -735,11 +776,11 @@ if( g.zPath[0]=='d' ) showAllFields(); form_begin(0, "%R/%s", g.zPath); @ <input type="hidden" name="name" value="%s(zName)" /> login_insert_csrf_secret(); zScript = ticket_editpage_code(); - Th_Store("login", g.zLogin ? g.zLogin : "nobody"); + Th_Store("login", login_name()); Th_Store("date", db_text(0, "SELECT datetime('now')")); Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0); Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0); if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br />\n", -1); if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){ @@ -801,11 +842,14 @@ int tagid; char zGlobPattern[50]; const char *zType; login_check_credentials(); - if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } + if( !g.perm.Hyperlink || !g.perm.RdTkt ){ + login_needed(g.anon.Hyperlink && g.anon.RdTkt); + return; + } zUuid = PD("name",""); zType = PD("y","a"); if( zType[0]!='c' ){ style_submenu_element("Check-ins", "Check-ins", "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); @@ -816,16 +860,15 @@ style_submenu_element("History", "History", "%s/tkthistory/%s", g.zTop, zUuid); style_submenu_element("Status", "Status", "%s/info/%s", g.zTop, zUuid); if( zType[0]=='c' ){ - zTitle = mprintf("Check-Ins Associated With Ticket %h", zUuid); + zTitle = mprintf("Check-ins Associated With Ticket %h", zUuid); }else{ zTitle = mprintf("Timeline Of Ticket %h", zUuid); } - style_header(zTitle); - free(zTitle); + style_header("%z", zTitle); sqlite3_snprintf(6, zGlobPattern, "%s", zUuid); canonical16(zGlobPattern, strlen(zGlobPattern)); tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid); if( tagid==0 ){ @@ -854,14 +897,13 @@ " WHERE target=%Q) " "ORDER BY mtime DESC", timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid ); } - db_prepare(&q, zSQL); - free(zSQL); + db_prepare(&q, "%z", zSQL/*safe-for-%s*/); www_print_timeline(&q, TIMELINE_ARTID|TIMELINE_DISJOINT|TIMELINE_GRAPH, - 0, 0, 0); + 0, 0, 0, 0); db_finalize(&q); style_footer(); } /* @@ -876,11 +918,14 @@ const char *zUuid; int tagid; int nChng = 0; login_check_credentials(); - if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } + if( !g.perm.Hyperlink || !g.perm.RdTkt ){ + login_needed(g.anon.Hyperlink && g.anon.RdTkt); + return; + } zUuid = PD("name",""); zTitle = mprintf("History Of Ticket %h", zUuid); style_submenu_element("Status", "Status", "%s/info/%s", g.zTop, zUuid); style_submenu_element("Check-ins", "Check-ins", @@ -887,71 +932,67 @@ "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid); style_submenu_element("Timeline", "Timeline", "%s/tkttimeline?name=%s", g.zTop, zUuid); if( P("plaintext")!=0 ){ style_submenu_element("Formatted", "Formatted", - "%R/tkthistory/%S", zUuid); + "%R/tkthistory/%s", zUuid); }else{ style_submenu_element("Plaintext", "Plaintext", - "%R/tkthistory/%S?plaintext", zUuid); + "%R/tkthistory/%s?plaintext", zUuid); } - style_header(zTitle); - free(zTitle); + style_header("%z", zTitle); tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid); if( tagid==0 ){ @ No such ticket: %h(zUuid) style_footer(); return; } db_prepare(&q, - "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" + "SELECT datetime(mtime%s), objid, uuid, NULL, NULL, NULL" " FROM event, blob" " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" " AND blob.rid=event.objid" " UNION " - "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user" + "SELECT datetime(mtime%s), attachid, uuid, src, filename, user" " FROM attachment, blob" " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" " AND blob.rid=attachid" " ORDER BY 1", - tagid, tagid + timeline_utc(), tagid, timeline_utc(), tagid ); while( db_step(&q)==SQLITE_ROW ){ Manifest *pTicket; - char zShort[12]; const char *zDate = db_column_text(&q, 0); int rid = db_column_int(&q, 1); const char *zChngUuid = db_column_text(&q, 2); const char *zFile = db_column_text(&q, 4); - memcpy(zShort, zChngUuid, 10); - zShort[10] = 0; if( nChng==0 ){ @ <ol> } nChng++; if( zFile!=0 ){ const char *zSrc = db_column_text(&q, 3); const char *zUser = db_column_text(&q, 5); if( zSrc==0 || zSrc[0]==0 ){ - @ + @ @ <li><p>Delete attachment "%h(zFile)" }else{ - @ + @ @ <li><p>Add attachment - @ "%z(href("%R/artifact/%S",zSrc))%s(zFile)</a>" + @ "%z(href("%R/artifact/%!S",zSrc))%s(zFile)</a>" } - @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>] + @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>] @ (rid %d(rid)) by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate, ".</p>"); }else{ pTicket = manifest_get(rid, CFTYPE_TICKET, 0); if( pTicket ){ @ @ <li><p>Ticket change - @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>] + @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>] @ (rid %d(rid)) by hyperlink_to_user(pTicket->zUser,zDate," on"); hyperlink_to_date(zDate, ":"); @ </p> ticket_output_change_artifact(pTicket, "a"); @@ -1048,14 +1089,16 @@ ** Instead of the report title its possible to use the report ** number. Using the special report number 0 list all columns, ** defined in the ticket table. ** ** %fossil ticket list fields +** %fossil ticket ls fields ** ** list all fields, defined for ticket in the fossil repository ** ** %fossil ticket list reports +** %fossil ticket ls reports ** ** list all ticket reports, defined in the fossil repository ** ** %fossil ticket set TICKETUUID (FIELD VALUE)+ ?-q|--quote? ** %fossil ticket change TICKETUUID (FIELD VALUE)+ ?-q|--quote? @@ -1094,11 +1137,11 @@ /* do some ints, we want to be inside a checkout */ db_find_and_open_repository(0, 0); user_select(); zUser = find_option("user-override",0,1); - if( zUser==0 ) zUser = g.zLogin; + if( zUser==0 ) zUser = login_name(); zDate = find_option("date-override",0,1); if( zDate==0 ) zDate = "now"; zDate = date_in_standard_format(zDate); zTktUuid = find_option("uuid-override",0,1); if( zTktUuid && (strlen(zTktUuid)!=40 || !validate16(zTktUuid,40)) ){ @@ -1118,11 +1161,11 @@ n = strlen(g.argv[2]); if( n==1 && g.argv[2][0]=='s' ){ /* set/show cannot be distinguished, so show the usage */ usage("add|change|list|set|show|history"); } - if( strncmp(g.argv[2],"list",n)==0 ){ + if(( strncmp(g.argv[2],"list",n)==0 ) || ( strncmp(g.argv[2],"ls",n)==0 )){ if( g.argc==3 ){ usage("list fields|reports"); }else{ n = strlen(g.argv[3]); if( !strncmp(g.argv[3],"fields",n) ){ @@ -1143,11 +1186,11 @@ }else{ /* add a new ticket or set fields on existing tickets */ tTktShowEncoding tktEncoding; tktEncoding = find_option("quote","q",0) ? tktFossilize : tktNoTab; - + if( strncmp(g.argv[2],"show",n)==0 ){ if( g.argc==3 ){ usage("show REPORTNR"); }else{ const char *zRep = 0; @@ -1178,12 +1221,12 @@ eCmd = set; } if( g.argc==3 ){ usage("set|change|history TICKETUUID"); } - zTktUuid = db_text(0, - "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3] + zTktUuid = db_text(0, + "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%q*'", g.argv[3] ); if( !zTktUuid ){ fossil_fatal("unknown ticket: '%s'!",g.argv[3]); } i=4; @@ -1202,44 +1245,40 @@ /* we just handle history separately here, does not get out */ if( eCmd==history ){ Stmt q; int tagid; - if ( i != g.argc ){ + if( i != g.argc ){ fossil_fatal("no other parameters expected to %s!",g.argv[2]); } tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'", zTktUuid); if( tagid==0 ){ fossil_fatal("no such ticket %h", zTktUuid); - } + } db_prepare(&q, - "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" + "SELECT datetime(mtime%s), objid, NULL, NULL, NULL" " FROM event, blob" " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" " AND blob.rid=event.objid" " UNION " - "SELECT datetime(mtime,'localtime'), attachid, uuid, src, " - " filename, user" + "SELECT datetime(mtime%s), attachid, filename, " + " src, user" " FROM attachment, blob" " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" " AND blob.rid=attachid" " ORDER BY 1 DESC", - tagid, tagid + timeline_utc(), tagid, timeline_utc(), tagid ); while( db_step(&q)==SQLITE_ROW ){ Manifest *pTicket; - char zShort[12]; const char *zDate = db_column_text(&q, 0); int rid = db_column_int(&q, 1); - const char *zChngUuid = db_column_text(&q, 2); - const char *zFile = db_column_text(&q, 4); - memcpy(zShort, zChngUuid, 10); - zShort[10] = 0; + const char *zFile = db_column_text(&q, 2); if( zFile!=0 ){ const char *zSrc = db_column_text(&q, 3); - const char *zUser = db_column_text(&q, 5); + const char *zUser = db_column_text(&q, 4); if( zSrc==0 || zSrc[0]==0 ){ fossil_print("Delete attachment %s\n", zFile); }else{ fossil_print("Add attachment %s\n", zFile); } @@ -1256,18 +1295,18 @@ const char *z; z = pTicket->aField[i].zName; blob_set(&val, pTicket->aField[i].zValue); if( z[0]=='+' ){ fossil_print(" Append to "); - z++; - }else{ - fossil_print(" Change "); - } - fossil_print("%h: ",z); - if( blob_size(&val)>50 || contains_newline(&val)) { - fossil_print("\n ",blob_str(&val)); - comment_print(blob_str(&val),4,79); + z++; + }else{ + fossil_print(" Change "); + } + fossil_print("%h: ",z); + if( blob_size(&val)>50 || contains_newline(&val)) { + fossil_print("\n "); + comment_print(blob_str(&val),0,4,-1,g.comFmtFlags); }else{ fossil_print("%s\n",blob_str(&val)); } blob_reset(&val); } @@ -1298,20 +1337,20 @@ if( tktEncoding == tktFossilize ){ zFValue=mprintf("%s",zFValue); defossilize(zFValue); } append = (zFName[0] == '+'); - if (append){ + if( append ){ zFName++; } j = fieldId(zFName); if( j == -1 ){ fossil_fatal("unknown field name '%s'!",zFName); }else{ - if (append) { + if( append ){ aField[j].zAppend = zFValue; - } else { + }else{ aField[j].zValue = zFValue; } } } @@ -1322,17 +1361,17 @@ /* append defined elements */ for(i=0; i<nField; i++){ char *zValue = 0; char *zPfx; - if (aField[i].zAppend && aField[i].zAppend[0] ){ + if( aField[i].zAppend && aField[i].zAppend[0] ){ zPfx = " +"; zValue = aField[i].zAppend; - } else if( aField[i].zValue && aField[i].zValue[0] ){ + }else if( aField[i].zValue && aField[i].zValue[0] ){ zPfx = " "; zValue = aField[i].zValue; - } else { + }else{ continue; } if( memcmp(aField[i].zName, "private_", 8)==0 ){ zValue = db_conceal(zValue, strlen(zValue)); blob_appendf(&tktchng, "J%s%s %s\n", zPfx, aField[i].zName, zValue); @@ -1343,11 +1382,69 @@ } blob_appendf(&tktchng, "K %s\n", zTktUuid); blob_appendf(&tktchng, "U %F\n", zUser); md5sum_blob(&tktchng, &cksum); blob_appendf(&tktchng, "Z %b\n", &cksum); - ticket_put(&tktchng, zTktUuid, 0); - printf("ticket %s succeeded for %s\n", + if( ticket_put(&tktchng, zTktUuid, ticket_need_moderation(1)) ){ + fossil_fatal("%s\n", g.zErrMsg); + }else{ + fossil_print("ticket %s succeeded for %s\n", (eCmd==set?"set":"add"),zTktUuid); + } } } } + + +#if INTERFACE +/* Standard submenu items for wiki pages */ +#define T_SRCH 0x00001 +#define T_REPLIST 0x00002 +#define T_NEW 0x00004 +#define T_ALL 0x00007 +#define T_ALL_BUT(x) (T_ALL&~(x)) +#endif + +/* +** Add some standard submenu elements for ticket screens. +*/ +void ticket_standard_submenu(unsigned int ok){ + if( (ok & T_SRCH)!=0 && search_restrict(SRCH_TKT)!=0 ){ + style_submenu_element("Search","Search","%R/tktsrch"); + } + if( (ok & T_REPLIST)!=0 ){ + style_submenu_element("Reports","Reports","%R/reportlist"); + } + if( (ok & T_NEW)!=0 && g.anon.NewTkt ){ + style_submenu_element("New","New","%R/tktnew"); + } +} + +/* +** WEBPAGE: ticket +** +** This is intended to be the primary "Ticket" page. Render as +** either ticket-search (if search is enabled) or as the +** /reportlist page (if ticket search is disabled). +*/ +void tkt_home_page(void){ + login_check_credentials(); + if( search_restrict(SRCH_TKT)!=0 ){ + tkt_srchpage(); + }else{ + view_list(); + } +} + +/* +** WEBPAGE: tktsrch +** Usage: /tktsrch?s=PATTERN +** +** Full-text search of all current tickets +*/ +void tkt_srchpage(void){ + login_check_credentials(); + style_header("Ticket Search"); + ticket_standard_submenu(T_ALL_BUT(T_SRCH)); + search_screen(SRCH_TKT, 0); + style_footer(); +} Index: src/tktsetup.c ================================================================== --- src/tktsetup.c +++ src/tktsetup.c @@ -27,11 +27,12 @@ ** WEBPAGE: tktsetup */ void tktsetup_page(void){ login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } style_header("Ticket Setup"); @ <table border="0" cellspacing="20"> setup_menu_entry("Table", "tktsetup_tab", @@ -115,16 +116,17 @@ void (*xRebuild)(void), /* Run after successful update */ int height /* Height of the edit box */ ){ const char *z; int isSubmit; - + login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } - if( P("setup") ){ + if( PB("setup") ){ cgi_redirect("tktsetup"); } isSubmit = P("submit")!=0; z = P("x"); if( z==0 ){ @@ -314,43 +316,43 @@ @ <td colspan="3"> @ Enter a one-line summary of the ticket:<br /> @ <input type="text" name="title" size="60" value="$<title>" /> @ </td> @ </tr> -@ +@ @ <tr> @ <td align="right">Type:</td> @ <td align="left"><th1>combobox type $type_choices 1</th1></td> @ <td align="left">What type of ticket is this?</td> @ </tr> -@ +@ @ <tr> @ <td align="right">Version:</td> @ <td align="left"> @ <input type="text" name="foundin" size="20" value="$<foundin>" /> @ </td> @ <td align="left">In what version or build number do you observe @ the problem?</td> @ </tr> -@ +@ @ <tr> @ <td align="right">Severity:</td> @ <td align="left"><th1>combobox severity $severity_choices 1</th1></td> @ <td align="left">How debilitating is the problem? How badly does the problem @ affect the operation of the product?</td> @ </tr> -@ +@ @ <tr> @ <td align="right">EMail:</td> @ <td align="left"> @ <input type="text" name="private_contact" value="$<private_contact>" @ size="30" /> @ </td> @ <td align="left"><u>Not publicly visible</u> @ Used by developers to contact you with questions.</td> @ </tr> -@ +@ @ <tr> @ <td colspan="3"> @ Enter a detailed description of the problem. @ For code defects, be sure to provide details on exactly how @ the problem can be reproduced. Provide as much detail as @@ -359,11 +361,11 @@ @ <br /> @ <th1>set nline [linecount $comment 50 10]</th1> @ <textarea name="icomment" cols="80" rows="$nline" @ wrap="virtual" class="wikiedit">$<icomment></textarea><br /> @ </tr> -@ +@ @ <th1>enable_output [info exists preview]</th1> @ <tr><td colspan="3"> @ Description Preview:<br /><hr /> @ <th1> @ if {$mutype eq "Wiki"} { @@ -378,28 +380,28 @@ @ wiki "<nowiki>$icomment\n</nowiki>" @ } @ </th1> @ <hr /></td></tr> @ <th1>enable_output 1</th1> -@ +@ @ <tr> @ <td><td align="left"> @ <input type="submit" name="preview" value="Preview" /> @ </td> @ <td align="left">See how the description will appear after formatting.</td> @ </tr> -@ +@ @ <th1>enable_output [info exists preview]</th1> @ <tr> @ <td><td align="left"> @ <input type="submit" name="submit" value="Submit" /> @ </td> -@ <td align="left">After filling in the information above, press this +@ <td align="left">After filling in the information above, press this @ button to create the new ticket</td> @ </tr> @ <th1>enable_output 1</th1> -@ +@ @ <tr> @ <td><td align="left"> @ <input type="submit" name="cancel" value="Cancel" /> @ </td> @ <td>Abandon and forget this ticket</td> @@ -435,15 +437,24 @@ static const char zDefaultView[] = @ <table cellpadding="5"> @ <tr><td class="tktDspLabel">Ticket UUID:</td> @ <th1> -@ if {[hascap s]} { -@ html "<td class='tktDspValue' colspan='3'>$tkt_uuid " -@ html "($tkt_id)</td></tr>\n" +@ if {[info exists tkt_uuid]} { +@ if {[hascap s]} { +@ html "<td class='tktDspValue' colspan='3'>$tkt_uuid " +@ html "($tkt_id)</td></tr>\n" +@ } else { +@ html "<td class='tktDspValue' colspan='3'>$tkt_uuid</td></tr>\n" +@ } @ } else { -@ html "<td class='tktDspValue' colspan='3'>$tkt_uuid</td></tr>\n" +@ if {[hascap s]} { +@ html "<td class='tktDspValue' colspan='3'>Deleted " +@ html "(0)</td></tr>\n" +@ } else { +@ html "<td class='tktDspValue' colspan='3'>Deleted</td></tr>\n" +@ } @ } @ </th1> @ <tr><td class="tktDspLabel">Title:</td> @ <td class="tktDspValue" colspan="3"> @ $<title> @@ -465,11 +476,15 @@ @ </td> @ <td class="tktDspLabel">Resolution:</td><td class="tktDspValue"> @ $<resolution> @ </td></tr> @ <tr><td class="tktDspLabel">Last Modified:</td><td class="tktDspValue"> -@ $<tkt_datetime> +@ <th1> +@ if {[info exists tkt_datetime]} { +@ html $tkt_datetime +@ } +@ </th1> @ </td> @ <th1>enable_output [hascap e]</th1> @ <td class="tktDspLabel">Contact:</td><td class="tktDspValue"> @ $<private_contact> @ </td> @@ -477,22 +492,24 @@ @ </tr> @ <tr><td class="tktDspLabel">Version Found In:</td> @ <td colspan="3" valign="top" class="tktDspValue"> @ $<foundin> @ </td></tr> -@ +@ @ <th1> -@ if {[info exists comment] && [string length $comment]>10} { -@ html { -@ <tr><td class="tktDspLabel">Description:</td></tr> -@ <tr><td colspan="5" class="tktDspValue"> -@ } -@ if {[info exists plaintext]} { -@ set r [randhex] -@ wiki "<verbatim-$r links>\n$comment\n</verbatim-$r>" -@ } else { -@ wiki $comment +@ if {[info exists comment]} { +@ if {[string length $comment]>10} { +@ html { +@ <tr><td class="tktDspLabel">Description:</td></tr> +@ <tr><td colspan="5" class="tktDspValue"> +@ } +@ if {[info exists plaintext]} { +@ set r [randhex] +@ wiki "<verbatim-$r links>\n$comment\n</verbatim-$r>" +@ } else { +@ wiki $comment +@ } @ } @ } @ set seenRow 0 @ set alwaysPlaintext [info exists plaintext] @ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin, @@ -577,55 +594,55 @@ @ </th1> @ <table cellpadding="5"> @ <tr><td class="tktDspLabel">Title:</td><td> @ <input type="text" name="title" value="$<title>" size="60" /> @ </td></tr> -@ +@ @ <tr><td class="tktDspLabel">Status:</td><td> @ <th1>combobox status $status_choices 1</th1> @ </td></tr> -@ +@ @ <tr><td class="tktDspLabel">Type:</td><td> @ <th1>combobox type $type_choices 1</th1> @ </td></tr> -@ +@ @ <tr><td class="tktDspLabel">Severity:</td><td> @ <th1>combobox severity $severity_choices 1</th1> @ </td></tr> -@ +@ @ <tr><td class="tktDspLabel">Priority:</td><td> @ <th1>combobox priority $priority_choices 1</th1> @ </td></tr> -@ +@ @ <tr><td class="tktDspLabel">Resolution:</td><td> @ <th1>combobox resolution $resolution_choices 1</th1> @ </td></tr> -@ +@ @ <tr><td class="tktDspLabel">Subsystem:</td><td> @ <th1>combobox subsystem $subsystem_choices 1</th1> @ </td></tr> -@ +@ @ <th1>enable_output [hascap e]</th1> @ <tr><td class="tktDspLabel">Contact:</td><td> @ <input type="text" name="private_contact" size="40" @ value="$<private_contact>" /> @ </td></tr> @ <th1>enable_output 1</th1> -@ +@ @ <tr><td class="tktDspLabel">Version Found In:</td><td> @ <input type="text" name="foundin" size="50" value="$<foundin>" /> @ </td></tr> -@ +@ @ <tr><td colspan="2"> @ Append Remark with format @ <th1>combobox mutype {Wiki HTML {Plain Text} {[links only]}} 1</th1> @ from @ <input type="text" name="username" value="$<username>" size="30" />:<br /> @ <textarea name="icomment" cols="80" rows="15" @ wrap="virtual" class="wikiedit">$<icomment></textarea> @ </td></tr> -@ +@ @ <th1>enable_output [info exists preview]</th1> @ <tr><td colspan="2"> @ Description Preview:<br><hr> @ <th1> @ if {$mutype eq "Wiki"} { @@ -641,34 +658,34 @@ @ } @ </th1> @ <hr> @ </td></tr> @ <th1>enable_output 1</th1> -@ +@ @ <tr> @ <td align="right"> @ <input type="submit" name="preview" value="Preview" /> @ </td> @ <td align="left">See how the description will appear after formatting.</td> @ </tr> -@ +@ @ <th1>enable_output [info exists preview]</th1> @ <tr> @ <td align="right"> @ <input type="submit" name="submit" value="Submit" /> @ </td> @ <td align="left">Apply the changes shown above</td> @ </tr> @ <th1>enable_output 1</th1> -@ +@ @ <tr> @ <td align="right"> @ <input type="submit" name="cancel" value="Cancel" /> @ </td> @ <td>Abandon this edit</td> @ </tr> -@ +@ @ </table> ; /* ** Return the code used to generate the edit ticket page @@ -698,28 +715,28 @@ /* ** The default report list page */ static const char zDefaultReportList[] = @ <th1> -@ if {[hascap n]} { +@ if {[anoncap n]} { @ html "<p>Enter a new ticket:</p>" @ html "<ul><li><a href='tktnew'>New ticket</a></li></ul>" @ } @ </th1> -@ +@ @ <p>Choose a report format from the following list:</p> @ <ol> @ <th1>html $report_items</th1> @ </ol> -@ +@ @ <th1> -@ if {[hascap t q]} { +@ if {[anoncap t q]} { @ html "<p>Other options:</p>\n<ul>\n" -@ if {[hascap t]} { +@ if {[anoncap t]} { @ html "<li><a href='rptnew'>New report format</a></li>\n" @ } -@ if {[hascap q]} { +@ if {[anoncap q]} { @ html "<li><a href='modreq'>Tend to pending moderation requests</a></li>\n" @ } @ } @ </th1> ; @@ -750,11 +767,11 @@ } /* ** The default template ticket report format: */ -static char zDefaultReport[] = +static char zDefaultReport[] = @ SELECT @ CASE WHEN status IN ('Open','Verified') THEN '#f2dcdc' @ WHEN status='Review' THEN '#e8e8e8' @ WHEN status='Fixed' THEN '#cfe8bd' @ WHEN status='Tested' THEN '#bde5d6' @@ -799,11 +816,11 @@ } /* ** The default template ticket key: */ -static const char zDefaultKey[] = +static const char zDefaultKey[] = @ #ffffff Key: @ #f2dcdc Active @ #e8e8e8 Review @ #cfe8bd Fixed @ #bde5d6 Tested @@ -843,11 +860,12 @@ ** WEBPAGE: tktsetup_timeline */ void tktsetup_timeline_page(void){ login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } if( P("setup") ){ cgi_redirect("tktsetup"); } @@ -880,7 +898,7 @@ @ <input type="submit" name="setup" value="Cancel" /> @ </p> @ </div></form> db_end_transaction(0); style_footer(); - + } Index: src/translate.c ================================================================== --- src/translate.c +++ src/translate.c @@ -13,11 +13,11 @@ ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** -** SYNOPSIS: +** SYNOPSIS: ** ** Input lines that begin with the "@" character are translated into ** either cgi_printf() statements or string literals and the ** translated code is written on standard output. ** @@ -34,22 +34,22 @@ ** punctuation. ** ** Enhancement #1: ** ** If the last non-whitespace character prior to the first "@" of a -** @-block is "=" or "," then the @-block is a string literal initializer +** @-block is "=" or "," then the @-block is a string literal initializer ** rather than text that is to be output via cgi_printf(). Render it ** as such. ** ** Enhancement #2: ** -** Comments of the form: "/* @-comment: CC" cause CC to become a +** Comments of the form: "/* @-comment: CC" cause CC to become a ** comment character for the @-substitution. Typical values for CC are -** "--" (for SQL text) or "#" (for TCL script) or "//" (for C++ code). +** "--" (for SQL text) or "#" (for Tcl script) or "//" (for C++ code). ** Lines of subsequent @-blocks that begin with CC are omitted from the ** output. -** +** */ #include <stdio.h> #include <ctype.h> #include <stdlib.h> #include <string.h> @@ -122,11 +122,11 @@ indent = i - 2; if( indent<0 ) indent = 0; omitline = 0; for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){ if( zLine[i]==c1 && (c2==' ' || zLine[i+1]==c2) ){ - omitline = 1; break; + omitline = 1; break; } if( zLine[i]=='"' || zLine[i]=='\\' ){ zOut[j++] = '\\'; } zOut[j++] = zLine[i]; } while( j>0 && isspace(zOut[j-1]) ){ j--; } @@ -137,11 +137,11 @@ fprintf(out,"%*s\"%s\\n\"\n",indent, "", zOut); } }else{ /* Otherwise (if the last non-whitespace was not '=') then generate ** a cgi_printf() statement whose format is the text following the '@'. - ** Substrings of the form "%C(...)" (where C is any sequence of + ** Substrings of the form "%C(...)" (where C is any sequence of ** characters other than \000 and '(') will put "%C" in the ** format and add the "(...)" as an argument to the cgi_printf call. */ int indent; int nC; @@ -174,11 +174,11 @@ fprintf(out,"%*scgi_printf(\"%s\\n\"",indent-2,"", zOut); inPrint = 1; }else{ fprintf(out,"\n%*s\"%s\\n\"",indent+5, "", zOut); } - } + } } } int main(int argc, char **argv){ if( argc==2 ){ Index: src/undo.c ================================================================== --- src/undo.c +++ src/undo.c @@ -12,11 +12,11 @@ ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* -** +** ** This file implements the undo/redo functionality. */ #include "config.h" #include "undo.h" @@ -45,19 +45,19 @@ int old_link; Blob current; Blob new; zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname); old_link = db_column_int(&q, 3); - new_link = file_wd_islink(zFullname); new_exists = file_wd_size(zFullname)>=0; + new_link = file_wd_islink(0); if( new_exists ){ if( new_link ){ blob_read_link(¤t, zFullname); }else{ - blob_read_from_file(¤t, zFullname); + blob_read_from_file(¤t, zFullname); } - new_exe = file_wd_isexe(zFullname); + new_exe = file_wd_isexe(0); }else{ blob_zero(¤t); new_exe = 0; } blob_zero(&new); @@ -86,11 +86,11 @@ file_delete(zFullname); } blob_reset(&new); free(zFullname); db_finalize(&q); - db_prepare(&q, + db_prepare(&q, "UPDATE undo SET content=:c, existsflag=%d, isExe=%d, isLink=%d," " redoflag=NOT redoflag" " WHERE pathname=%Q", new_exists, new_exe, new_link, zPathname ); @@ -127,11 +127,10 @@ ** Undo or redo all undoable or redoable changes. */ static void undo_all(int redoFlag){ int ucid; int ncid; - const char *zDb = db_name("localdb"); undo_all_filesystem(redoFlag); db_multi_exec( "CREATE TEMP TABLE undo_vfile_2 AS SELECT * FROM vfile;" "DELETE FROM vfile;" "INSERT INTO vfile SELECT * FROM undo_vfile;" @@ -143,11 +142,11 @@ "INSERT INTO vmerge SELECT * FROM undo_vmerge;" "DELETE FROM undo_vmerge;" "INSERT INTO undo_vmerge SELECT * FROM undo_vmerge_2;" "DROP TABLE undo_vmerge_2;" ); - if(db_exists("SELECT 1 FROM %s.sqlite_master WHERE name='undo_stash'", zDb) ){ + if( db_table_exists("localdb", "undo_stash") ){ if( redoFlag ){ db_multi_exec( "DELETE FROM stash WHERE stashid IN (SELECT stashid FROM undo_stash);" "DELETE FROM stashfile" " WHERE stashid NOT IN (SELECT stashid FROM stash);" @@ -174,11 +173,11 @@ @ DROP TABLE IF EXISTS undo_vfile; @ DROP TABLE IF EXISTS undo_vmerge; @ DROP TABLE IF EXISTS undo_stash; @ DROP TABLE IF EXISTS undo_stashfile; ; - db_multi_exec(zSql); + db_multi_exec(zSql /*works-like:""*/); db_lset_int("undo_available", 0); db_lset_int("undo_checkout", 0); } /* @@ -218,34 +217,34 @@ ** Begin capturing a snapshot that can be undone. */ void undo_begin(void){ int cid; const char *zDb = db_name("localdb"); - static const char zSql[] = - @ CREATE TABLE %s.undo( + static const char zSql[] = + @ CREATE TABLE "%w".undo( @ pathname TEXT UNIQUE, -- Name of the file @ redoflag BOOLEAN, -- 0 for undoable. 1 for redoable @ existsflag BOOLEAN, -- True if the file exists @ isExe BOOLEAN, -- True if the file is executable @ isLink BOOLEAN, -- True if the file is symlink @ content BLOB -- Saved content @ ); - @ CREATE TABLE %s.undo_vfile AS SELECT * FROM vfile; - @ CREATE TABLE %s.undo_vmerge AS SELECT * FROM vmerge; + @ CREATE TABLE "%w".undo_vfile AS SELECT * FROM vfile; + @ CREATE TABLE "%w".undo_vmerge AS SELECT * FROM vmerge; ; if( undoDisable ) return; undo_reset(); - db_multi_exec(zSql, zDb, zDb, zDb); + db_multi_exec(zSql/*works-like:"%w,%w,%w"*/, zDb, zDb, zDb); cid = db_lget_int("checkout", 0); db_lset_int("undo_checkout", cid); db_lset_int("undo_available", 1); db_lset("undo_cmdline", undoCmd); undoActive = 1; } /* -** Permanently disable undo +** Permanently disable undo */ void undo_disable(void){ undoDisable = 1; } @@ -280,11 +279,11 @@ " VALUES(%Q,0,%d,%d,%d,:c)", zPathname, existsFlag, file_wd_isexe(zFullname), isLink ); if( existsFlag ){ if( isLink ){ - blob_read_link(&content, zFullname); + blob_read_link(&content, zFullname); }else{ blob_read_from_file(&content, zFullname); } db_bind_blob(&q, ":c", &content); } @@ -301,18 +300,18 @@ ** Make the current state of stashid undoable. */ void undo_save_stash(int stashid){ const char *zDb = db_name("localdb"); db_multi_exec( - "CREATE TABLE IF NOT EXISTS %s.undo_stash" + "CREATE TABLE IF NOT EXISTS \"%w\".undo_stash" " AS SELECT * FROM stash WHERE 0;" "INSERT INTO undo_stash" " SELECT * FROM stash WHERE stashid=%d;", zDb, stashid ); db_multi_exec( - "CREATE TABLE IF NOT EXISTS %s.undo_stashfile" + "CREATE TABLE IF NOT EXISTS \"%w\".undo_stashfile" " AS SELECT * FROM stashfile WHERE 0;" "INSERT INTO undo_stashfile" " SELECT * FROM stashfile WHERE stashid=%d;", zDb, stashid ); @@ -364,11 +363,11 @@ ** (2) fossil merge (6) fossil stash drop ** (3) fossil revert (7) fossil stash goto ** (4) fossil stash pop ** ** If FILENAME is specified then restore the content of the named -** file(s) but otherwise leave the update or merge or revert in effect. +** file(s) but otherwise leave the update or merge or revert in effect. ** The redo command undoes the effect of the most recent undo. ** ** If the -n|--dry-run option is present, no changes are made and instead ** the undo or redo command explains what actions the undo or redo would ** have done had the -n|--dry-run been omitted. @@ -411,11 +410,11 @@ if( nChng==0 ){ fossil_print("The following file changes would occur if the " "command above is %sne:\n\n", zCmd); } nChng++; - fossil_print("%s %s\n", + fossil_print("%s %s\n", db_column_int(&q,0) ? "UPDATE" : "DELETE", db_column_text(&q, 1) ); } db_finalize(&q); Index: src/unicode.c ================================================================== --- src/unicode.c +++ src/unicode.c @@ -31,101 +31,107 @@ int unicode_isalnum(int c){ /* Each unsigned integer in the following array corresponds to a contiguous ** range of unicode codepoints that are not either letters or numbers (i.e. ** codepoints for which this function should return 0). ** - ** The most significant 22 bits in each 32-bit value contain the first + ** The most significant 22 bits in each 32-bit value contain the first ** codepoint in the range. The least significant 10 bits are used to store - ** the size of the range (always at least 1). In other words, the value - ** ((C<<22) + N) represents a range of N codepoints starting with codepoint - ** C. It is not possible to represent a range larger than 1023 codepoints + ** the size of the range (always at least 1). In other words, the value + ** ((C<<22) + N) represents a range of N codepoints starting with codepoint + ** C. It is not possible to represent a range larger than 1023 codepoints ** using this format. */ static const unsigned int aEntry[] = { 0x00000030, 0x0000E807, 0x00016C06, 0x0001EC2F, 0x0002AC07, 0x0002D001, 0x0002D803, 0x0002EC01, 0x0002FC01, 0x00035C01, 0x0003DC01, 0x000B0804, 0x000B480E, 0x000B9407, 0x000BB401, 0x000BBC81, 0x000DD401, 0x000DF801, 0x000E1002, 0x000E1C01, - 0x000FD801, 0x00120808, 0x00156806, 0x00162402, 0x00163C01, - 0x00164437, 0x0017CC02, 0x00180005, 0x00181816, 0x00187802, - 0x00192C15, 0x0019A804, 0x0019C001, 0x001B5001, 0x001B580F, - 0x001B9C07, 0x001BF402, 0x001C000E, 0x001C3C01, 0x001C4401, - 0x001CC01B, 0x001E980B, 0x001FAC09, 0x001FD804, 0x00205804, - 0x00206C09, 0x00209403, 0x0020A405, 0x0020C00F, 0x00216403, - 0x00217801, 0x0023901B, 0x00240004, 0x0024E803, 0x0024F812, - 0x00254407, 0x00258804, 0x0025C001, 0x00260403, 0x0026F001, - 0x0026F807, 0x00271C02, 0x00272C03, 0x00275C01, 0x00278802, - 0x0027C802, 0x0027E802, 0x00280403, 0x0028F001, 0x0028F805, - 0x00291C02, 0x00292C03, 0x00294401, 0x0029C002, 0x0029D401, - 0x002A0403, 0x002AF001, 0x002AF808, 0x002B1C03, 0x002B2C03, - 0x002B8802, 0x002BC002, 0x002C0403, 0x002CF001, 0x002CF807, - 0x002D1C02, 0x002D2C03, 0x002D5802, 0x002D8802, 0x002DC001, - 0x002E0801, 0x002EF805, 0x002F1803, 0x002F2804, 0x002F5C01, - 0x002FCC08, 0x00300403, 0x0030F807, 0x00311803, 0x00312804, - 0x00315402, 0x00318802, 0x0031FC01, 0x00320802, 0x0032F001, - 0x0032F807, 0x00331803, 0x00332804, 0x00335402, 0x00338802, - 0x00340802, 0x0034F807, 0x00351803, 0x00352804, 0x00355C01, - 0x00358802, 0x0035E401, 0x00360802, 0x00372801, 0x00373C06, - 0x00375801, 0x00376008, 0x0037C803, 0x0038C401, 0x0038D007, - 0x0038FC01, 0x00391C09, 0x00396802, 0x003AC401, 0x003AD006, - 0x003AEC02, 0x003B2006, 0x003C041F, 0x003CD00C, 0x003DC417, - 0x003E340B, 0x003E6424, 0x003EF80F, 0x003F380D, 0x0040AC14, - 0x00412806, 0x00415804, 0x00417803, 0x00418803, 0x00419C07, - 0x0041C404, 0x0042080C, 0x00423C01, 0x00426806, 0x0043EC01, - 0x004D740C, 0x004E400A, 0x00500001, 0x0059B402, 0x005A0001, - 0x005A6C02, 0x005BAC03, 0x005C4803, 0x005CC805, 0x005D4802, - 0x005DC802, 0x005ED023, 0x005F6004, 0x005F7401, 0x0060000F, - 0x0062A401, 0x0064800C, 0x0064C00C, 0x00650001, 0x00651002, - 0x0066C011, 0x00672002, 0x00677822, 0x00685C05, 0x00687802, - 0x0069540A, 0x0069801D, 0x0069FC01, 0x006A8007, 0x006AA006, - 0x006C0005, 0x006CD011, 0x006D6823, 0x006E0003, 0x006E840D, - 0x006F980E, 0x006FF004, 0x00709014, 0x0070EC05, 0x0071F802, - 0x00730008, 0x00734019, 0x0073B401, 0x0073C803, 0x00770027, + 0x000FD801, 0x00120808, 0x00156806, 0x00162402, 0x00163403, + 0x00164437, 0x0017CC02, 0x0018001D, 0x00187802, 0x00192C15, + 0x0019A804, 0x0019C001, 0x001B5001, 0x001B580F, 0x001B9C07, + 0x001BF402, 0x001C000E, 0x001C3C01, 0x001C4401, 0x001CC01B, + 0x001E980B, 0x001FAC09, 0x001FD804, 0x00205804, 0x00206C09, + 0x00209403, 0x0020A405, 0x0020C00F, 0x00216403, 0x00217801, + 0x00239020, 0x0024E803, 0x0024F812, 0x00254407, 0x00258804, + 0x0025C001, 0x00260403, 0x0026F001, 0x0026F807, 0x00271C02, + 0x00272C03, 0x00275C01, 0x00278802, 0x0027C802, 0x0027E802, + 0x00280403, 0x0028F001, 0x0028F805, 0x00291C02, 0x00292C03, + 0x00294401, 0x0029C002, 0x0029D401, 0x002A0403, 0x002AF001, + 0x002AF808, 0x002B1C03, 0x002B2C03, 0x002B8802, 0x002BC002, + 0x002C0403, 0x002CF001, 0x002CF807, 0x002D1C02, 0x002D2C03, + 0x002D5802, 0x002D8802, 0x002DC001, 0x002E0801, 0x002EF805, + 0x002F1803, 0x002F2804, 0x002F5C01, 0x002FCC08, 0x00300004, + 0x0030F807, 0x00311803, 0x00312804, 0x00315402, 0x00318802, + 0x0031FC01, 0x00320403, 0x0032F001, 0x0032F807, 0x00331803, + 0x00332804, 0x00335402, 0x00338802, 0x00340403, 0x0034F807, + 0x00351803, 0x00352804, 0x00355C01, 0x00358802, 0x0035E401, + 0x00360802, 0x00372801, 0x00373C06, 0x00375801, 0x00376008, + 0x0037C803, 0x0038C401, 0x0038D007, 0x0038FC01, 0x00391C09, + 0x00396802, 0x003AC401, 0x003AD006, 0x003AEC02, 0x003B2006, + 0x003C041F, 0x003CD00C, 0x003DC417, 0x003E340B, 0x003E6424, + 0x003EF80F, 0x003F380D, 0x0040AC14, 0x00412806, 0x00415804, + 0x00417803, 0x00418803, 0x00419C07, 0x0041C404, 0x0042080C, + 0x00423C01, 0x00426806, 0x0043EC01, 0x004D740C, 0x004E400A, + 0x00500001, 0x0059B402, 0x005A0001, 0x005A6C02, 0x005BAC03, + 0x005C4803, 0x005CC805, 0x005D4802, 0x005DC802, 0x005ED023, + 0x005F6004, 0x005F7401, 0x0060000F, 0x0062A401, 0x0064800C, + 0x0064C00C, 0x00650001, 0x00651002, 0x0066C011, 0x00672002, + 0x00677822, 0x00685C05, 0x00687802, 0x0069540A, 0x0069801D, + 0x0069FC01, 0x006A8007, 0x006AA006, 0x006AC00F, 0x006C0005, + 0x006CD011, 0x006D6823, 0x006E0003, 0x006E840D, 0x006F980E, + 0x006FF004, 0x00709014, 0x0070EC05, 0x0071F802, 0x00730008, + 0x00734019, 0x0073B401, 0x0073C803, 0x0073E002, 0x00770036, 0x0077F004, 0x007EF401, 0x007EFC03, 0x007F3403, 0x007F7403, - 0x007FB403, 0x007FF402, 0x00800065, 0x0081A806, 0x0081E805, - 0x00822805, 0x0082801A, 0x00834021, 0x00840002, 0x00840C04, + 0x007FB403, 0x007FF402, 0x00800065, 0x0081980A, 0x0081E805, + 0x00822805, 0x0082801E, 0x00834021, 0x00840002, 0x00840C04, 0x00842002, 0x00845001, 0x00845803, 0x00847806, 0x00849401, 0x00849C01, 0x0084A401, 0x0084B801, 0x0084E802, 0x00850005, - 0x00852804, 0x00853C01, 0x00864264, 0x00900027, 0x0091000B, - 0x0092704E, 0x00940200, 0x009C0475, 0x009E53B9, 0x00AD400A, - 0x00B39406, 0x00B3BC03, 0x00B3E404, 0x00B3F802, 0x00B5C001, - 0x00B5FC01, 0x00B7804F, 0x00B8C00C, 0x00BA001A, 0x00BA6C59, - 0x00BC00D6, 0x00BFC00C, 0x00C00005, 0x00C02019, 0x00C0A807, - 0x00C0D802, 0x00C0F403, 0x00C26404, 0x00C28001, 0x00C3EC01, - 0x00C64002, 0x00C6580A, 0x00C70024, 0x00C8001F, 0x00C8A81E, - 0x00C94001, 0x00C98020, 0x00CA2827, 0x00CB003F, 0x00CC0100, - 0x01370040, 0x02924037, 0x0293F802, 0x02983403, 0x0299BC10, - 0x029A7C01, 0x029BC008, 0x029C0017, 0x029C8002, 0x029E2402, - 0x02A00801, 0x02A01801, 0x02A02C01, 0x02A08C09, 0x02A0D804, - 0x02A1D004, 0x02A20002, 0x02A2D011, 0x02A33802, 0x02A38012, - 0x02A3E003, 0x02A4980A, 0x02A51C0D, 0x02A57C01, 0x02A60004, - 0x02A6CC1B, 0x02A77802, 0x02A8A40E, 0x02A90C01, 0x02A93002, - 0x02A97004, 0x02A9DC03, 0x02A9EC01, 0x02AAC001, 0x02AAC803, - 0x02AADC02, 0x02AAF802, 0x02AB0401, 0x02AB7802, 0x02ABAC07, - 0x02ABD402, 0x02AF8C0B, 0x03600001, 0x036DFC02, 0x036FFC02, - 0x037FFC02, 0x03E3FC01, 0x03EC7801, 0x03ECA401, 0x03EEC810, - 0x03F4F802, 0x03F7F002, 0x03F8001A, 0x03F88007, 0x03F8C023, - 0x03F95013, 0x03F9A004, 0x03FBFC01, 0x03FC040F, 0x03FC6807, - 0x03FCEC06, 0x03FD6C0B, 0x03FF8007, 0x03FFA007, 0x03FFE405, - 0x04040003, 0x0404DC09, 0x0405E411, 0x0406400C, 0x0407402E, - 0x040E7C01, 0x040F4001, 0x04215C01, 0x04247C01, 0x0424FC01, - 0x04280403, 0x04281402, 0x04283004, 0x0428E003, 0x0428FC01, - 0x04294009, 0x0429FC01, 0x042CE407, 0x04400003, 0x0440E016, - 0x04420003, 0x0442C012, 0x04440003, 0x04449C0E, 0x04450004, - 0x04460003, 0x0446CC0E, 0x04471404, 0x045AAC0D, 0x0491C004, - 0x05BD442E, 0x05BE3C04, 0x074000F6, 0x07440027, 0x0744A4B5, - 0x07480046, 0x074C0057, 0x075B0401, 0x075B6C01, 0x075BEC01, - 0x075C5401, 0x075CD401, 0x075D3C01, 0x075DBC01, 0x075E2401, - 0x075EA401, 0x075F0C01, 0x07BBC002, 0x07C0002C, 0x07C0C064, - 0x07C2800F, 0x07C2C40E, 0x07C3040F, 0x07C3440F, 0x07C4401F, - 0x07C4C03C, 0x07C5C02B, 0x07C7981D, 0x07C8402B, 0x07C90009, - 0x07C94002, 0x07CC0021, 0x07CCC006, 0x07CCDC46, 0x07CE0014, - 0x07CE8025, 0x07CF1805, 0x07CF8011, 0x07D0003F, 0x07D10001, - 0x07D108B6, 0x07D3E404, 0x07D4003E, 0x07D50004, 0x07D54018, - 0x07D7EC46, 0x07D9140B, 0x07DA0046, 0x07DC0074, 0x38000401, - 0x38008060, 0x380400F0, 0x3C000001, 0x3FFFF401, 0x40000001, - 0x43FFF401, + 0x00852804, 0x00853C01, 0x0086426B, 0x00900027, 0x0091000B, + 0x0092704E, 0x00940276, 0x009E53E0, 0x00ADD820, 0x00AE6022, + 0x00AEF40C, 0x00AF2808, 0x00B39406, 0x00B3BC03, 0x00B3E404, + 0x00B3F802, 0x00B5C001, 0x00B5FC01, 0x00B7804F, 0x00B8C013, + 0x00BA001A, 0x00BA6C59, 0x00BC00D6, 0x00BFC00C, 0x00C00005, + 0x00C02019, 0x00C0A807, 0x00C0D802, 0x00C0F403, 0x00C26404, + 0x00C28001, 0x00C3EC01, 0x00C64002, 0x00C6580A, 0x00C70024, + 0x00C8001F, 0x00C8A81E, 0x00C94001, 0x00C98020, 0x00CA2827, + 0x00CB003F, 0x00CC0100, 0x01370040, 0x02924037, 0x0293F802, + 0x02983403, 0x0299BC10, 0x029A7C01, 0x029BC008, 0x029C0017, + 0x029C8002, 0x029E2402, 0x02A00801, 0x02A01801, 0x02A02C01, + 0x02A08C09, 0x02A0D804, 0x02A1D004, 0x02A20002, 0x02A2D011, + 0x02A33802, 0x02A38012, 0x02A3E003, 0x02A4980A, 0x02A51C0D, + 0x02A57C01, 0x02A60004, 0x02A6CC1B, 0x02A77802, 0x02A79401, + 0x02A8A40E, 0x02A90C01, 0x02A93002, 0x02A97004, 0x02A9DC03, + 0x02A9EC03, 0x02AAC001, 0x02AAC803, 0x02AADC02, 0x02AAF802, + 0x02AB0401, 0x02AB7802, 0x02ABAC07, 0x02ABD402, 0x02AD6C01, + 0x02AF8C0B, 0x03600001, 0x036DFC02, 0x036FFC02, 0x037FFC01, + 0x03EC7801, 0x03ECA401, 0x03EEC810, 0x03F4F802, 0x03F7F002, + 0x03F8001A, 0x03F8800E, 0x03F8C023, 0x03F95013, 0x03F9A004, + 0x03FBFC01, 0x03FC040F, 0x03FC6807, 0x03FCEC06, 0x03FD6C0B, + 0x03FF8007, 0x03FFA007, 0x03FFE405, 0x04040003, 0x0404DC09, + 0x0405E411, 0x04063001, 0x0406400C, 0x04068001, 0x0407402E, + 0x040B8001, 0x040DD805, 0x040E7C01, 0x040F4001, 0x0415BC01, + 0x04215C01, 0x0421DC02, 0x04247C01, 0x0424FC01, 0x04280403, + 0x04281402, 0x04283004, 0x0428E003, 0x0428FC01, 0x04294009, + 0x0429FC01, 0x042B2001, 0x042B9402, 0x042BC007, 0x042CE407, + 0x042E6404, 0x04400003, 0x0440E016, 0x0441FC04, 0x0442C012, + 0x04440003, 0x04449C0E, 0x04450004, 0x0445CC03, 0x04460003, + 0x0446CC0E, 0x04471404, 0x04473401, 0x0448B012, 0x044B7C0C, + 0x044C0403, 0x044CF001, 0x044CF807, 0x044D1C02, 0x044D2C03, + 0x044D5C01, 0x044D8802, 0x044D9807, 0x044DC005, 0x0452C014, + 0x04531801, 0x0456BC07, 0x0456E012, 0x0458C014, 0x045AAC0D, + 0x0491C005, 0x05A9B802, 0x05ABC006, 0x05ACC010, 0x05AD1002, + 0x05BD442E, 0x05BE3C04, 0x06F27008, 0x074000F6, 0x07440027, + 0x0744A4B5, 0x07480046, 0x074C0057, 0x075B0401, 0x075B6C01, + 0x075BEC01, 0x075C5401, 0x075CD401, 0x075D3C01, 0x075DBC01, + 0x075E2401, 0x075EA401, 0x075F0C01, 0x07A34007, 0x07BBC002, + 0x07C0002C, 0x07C0C064, 0x07C2800F, 0x07C2C40F, 0x07C3040F, + 0x07C34425, 0x07C4401F, 0x07C4C03C, 0x07C5C02B, 0x07C7981D, + 0x07C8402B, 0x07C90009, 0x07C94002, 0x07CC002D, 0x07CCC04E, + 0x07CE004F, 0x07CF5024, 0x07D000FF, 0x07D4004B, 0x07D5402A, + 0x07D5EC29, 0x07D6949E, 0x07D9148B, 0x07DB800D, 0x07DBC004, + 0x07DC0074, 0x07DE0055, 0x07E0000C, 0x07E04038, 0x07E1400A, + 0x07E18028, 0x07E2401E, 0x38000401, 0x38008060, 0x380400F0, }; static const unsigned int aAscii[4] = { 0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001, }; @@ -160,35 +166,35 @@ ** SMALL LETTER E WITH DIAERESIS" - return 65 ("LATIN SMALL LETTER ** E"). The resuls of passing a codepoint that corresponds to an ** uppercase letter are undefined. */ static int unicode_remove_diacritic(int c){ - unsigned short aDia[] = { - 0, 1797, 1848, 1859, 1891, 1928, 1940, 1995, - 2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286, - 2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732, - 2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336, - 3456, 3696, 3712, 3728, 3744, 3896, 3912, 3928, - 3968, 4008, 4040, 4106, 4138, 4170, 4202, 4234, - 4266, 4296, 4312, 4344, 4408, 4424, 4472, 4504, - 6148, 6198, 6264, 6280, 6360, 6429, 6505, 6529, - 61448, 61468, 61534, 61592, 61642, 61688, 61704, 61726, - 61784, 61800, 61836, 61880, 61914, 61948, 61998, 62122, - 62154, 62200, 62218, 62302, 62364, 62442, 62478, 62536, - 62554, 62584, 62604, 62640, 62648, 62656, 62664, 62730, - 62924, 63050, 63082, 63274, 63390, + static const unsigned short aDia[] = { + 0, 1797, 1848, 1859, 1891, 1928, 1940, 1995, + 2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286, + 2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732, + 2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336, + 3456, 3696, 3712, 3728, 3744, 3896, 3912, 3928, + 3968, 4008, 4040, 4106, 4138, 4170, 4202, 4234, + 4266, 4296, 4312, 4344, 4408, 4424, 4472, 4504, + 6148, 6198, 6264, 6280, 6360, 6429, 6505, 6529, + 61448, 61468, 61534, 61592, 61642, 61688, 61704, 61726, + 61784, 61800, 61836, 61880, 61914, 61948, 61998, 62122, + 62154, 62200, 62218, 62302, 62364, 62442, 62478, 62536, + 62554, 62584, 62604, 62640, 62648, 62656, 62664, 62730, + 62924, 63050, 63082, 63274, 63390, }; - char aChar[] = { - '\0', 'a', 'c', 'e', 'i', 'n', 'o', 'u', 'y', 'y', 'a', 'c', - 'd', 'e', 'e', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'o', 'r', - 's', 't', 'u', 'u', 'w', 'y', 'z', 'o', 'u', 'a', 'i', 'o', - 'u', 'g', 'k', 'o', 'j', 'g', 'n', 'a', 'e', 'i', 'o', 'r', - 'u', 's', 't', 'h', 'a', 'e', 'o', 'y', '\0', '\0', '\0', '\0', - '\0', '\0', '\0', '\0', 'a', 'b', 'd', 'd', 'e', 'f', 'g', 'h', - 'h', 'i', 'k', 'l', 'l', 'm', 'n', 'p', 'r', 'r', 's', 't', - 'u', 'v', 'w', 'w', 'x', 'y', 'z', 'h', 't', 'w', 'y', 'a', - 'e', 'i', 'o', 'u', 'y', + static const char aChar[] = { + '\0', 'a', 'c', 'e', 'i', 'n', 'o', 'u', 'y', 'y', 'a', 'c', + 'd', 'e', 'e', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'o', 'r', + 's', 't', 'u', 'u', 'w', 'y', 'z', 'o', 'u', 'a', 'i', 'o', + 'u', 'g', 'k', 'o', 'j', 'g', 'n', 'a', 'e', 'i', 'o', 'r', + 'u', 's', 't', 'h', 'a', 'e', 'o', 'y', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', 'a', 'b', 'd', 'd', 'e', 'f', 'g', 'h', + 'h', 'i', 'k', 'l', 'l', 'm', 'n', 'p', 'r', 'r', 's', 't', + 'u', 'v', 'w', 'w', 'x', 'y', 'z', 'h', 't', 'w', 'y', 'a', + 'e', 'i', 'o', 'u', 'y', }; unsigned int key = (((unsigned int)c)<<3) | 0x00000007; int iRes = 0; int iHi = sizeof(aDia)/sizeof(aDia[0]) - 1; @@ -202,11 +208,11 @@ iHi = iTest-1; } } assert( key>=aDia[iRes] ); return ((c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : (int)aChar[iRes]); -}; +} /* ** Return true if the argument interpreted as a unicode codepoint ** is a diacritical modifier character. @@ -253,12 +259,12 @@ unsigned char flags; unsigned char nRange; } aEntry[] = { {65, 14, 26}, {181, 64, 1}, {192, 14, 23}, {216, 14, 7}, {256, 1, 48}, {306, 1, 6}, - {313, 1, 16}, {330, 1, 46}, {376, 116, 1}, - {377, 1, 6}, {383, 104, 1}, {385, 50, 1}, + {313, 1, 16}, {330, 1, 46}, {376, 126, 1}, + {377, 1, 6}, {383, 114, 1}, {385, 50, 1}, {386, 1, 4}, {390, 44, 1}, {391, 0, 1}, {393, 42, 2}, {395, 0, 1}, {398, 32, 1}, {399, 38, 1}, {400, 40, 1}, {401, 0, 1}, {403, 42, 1}, {404, 46, 1}, {406, 52, 1}, {407, 48, 1}, {408, 0, 1}, {412, 52, 1}, @@ -267,61 +273,64 @@ {428, 0, 1}, {430, 60, 1}, {431, 0, 1}, {433, 58, 2}, {435, 1, 4}, {439, 62, 1}, {440, 0, 1}, {444, 0, 1}, {452, 2, 1}, {453, 0, 1}, {455, 2, 1}, {456, 0, 1}, {458, 2, 1}, {459, 1, 18}, {478, 1, 18}, - {497, 2, 1}, {498, 1, 4}, {502, 122, 1}, - {503, 134, 1}, {504, 1, 40}, {544, 110, 1}, + {497, 2, 1}, {498, 1, 4}, {502, 132, 1}, + {503, 144, 1}, {504, 1, 40}, {544, 120, 1}, {546, 1, 18}, {570, 70, 1}, {571, 0, 1}, - {573, 108, 1}, {574, 68, 1}, {577, 0, 1}, - {579, 106, 1}, {580, 28, 1}, {581, 30, 1}, + {573, 118, 1}, {574, 68, 1}, {577, 0, 1}, + {579, 116, 1}, {580, 28, 1}, {581, 30, 1}, {582, 1, 10}, {837, 36, 1}, {880, 1, 4}, - {886, 0, 1}, {902, 18, 1}, {904, 16, 3}, - {908, 26, 1}, {910, 24, 2}, {913, 14, 17}, - {931, 14, 9}, {962, 0, 1}, {975, 4, 1}, - {976, 140, 1}, {977, 142, 1}, {981, 146, 1}, - {982, 144, 1}, {984, 1, 24}, {1008, 136, 1}, - {1009, 138, 1}, {1012, 130, 1}, {1013, 128, 1}, - {1015, 0, 1}, {1017, 152, 1}, {1018, 0, 1}, - {1021, 110, 3}, {1024, 34, 16}, {1040, 14, 32}, - {1120, 1, 34}, {1162, 1, 54}, {1216, 6, 1}, - {1217, 1, 14}, {1232, 1, 88}, {1329, 22, 38}, - {4256, 66, 38}, {4295, 66, 1}, {4301, 66, 1}, - {7680, 1, 150}, {7835, 132, 1}, {7838, 96, 1}, - {7840, 1, 96}, {7944, 150, 8}, {7960, 150, 6}, - {7976, 150, 8}, {7992, 150, 8}, {8008, 150, 6}, - {8025, 151, 8}, {8040, 150, 8}, {8072, 150, 8}, - {8088, 150, 8}, {8104, 150, 8}, {8120, 150, 2}, - {8122, 126, 2}, {8124, 148, 1}, {8126, 100, 1}, - {8136, 124, 4}, {8140, 148, 1}, {8152, 150, 2}, - {8154, 120, 2}, {8168, 150, 2}, {8170, 118, 2}, - {8172, 152, 1}, {8184, 112, 2}, {8186, 114, 2}, - {8188, 148, 1}, {8486, 98, 1}, {8490, 92, 1}, - {8491, 94, 1}, {8498, 12, 1}, {8544, 8, 16}, - {8579, 0, 1}, {9398, 10, 26}, {11264, 22, 47}, - {11360, 0, 1}, {11362, 88, 1}, {11363, 102, 1}, - {11364, 90, 1}, {11367, 1, 6}, {11373, 84, 1}, - {11374, 86, 1}, {11375, 80, 1}, {11376, 82, 1}, - {11378, 0, 1}, {11381, 0, 1}, {11390, 78, 2}, - {11392, 1, 100}, {11499, 1, 4}, {11506, 0, 1}, - {42560, 1, 46}, {42624, 1, 24}, {42786, 1, 14}, - {42802, 1, 62}, {42873, 1, 4}, {42877, 76, 1}, - {42878, 1, 10}, {42891, 0, 1}, {42893, 74, 1}, - {42896, 1, 4}, {42912, 1, 10}, {42922, 72, 1}, - {65313, 14, 26}, + {886, 0, 1}, {895, 36, 1}, {902, 18, 1}, + {904, 16, 3}, {908, 26, 1}, {910, 24, 2}, + {913, 14, 17}, {931, 14, 9}, {962, 0, 1}, + {975, 4, 1}, {976, 150, 1}, {977, 152, 1}, + {981, 156, 1}, {982, 154, 1}, {984, 1, 24}, + {1008, 146, 1}, {1009, 148, 1}, {1012, 140, 1}, + {1013, 138, 1}, {1015, 0, 1}, {1017, 162, 1}, + {1018, 0, 1}, {1021, 120, 3}, {1024, 34, 16}, + {1040, 14, 32}, {1120, 1, 34}, {1162, 1, 54}, + {1216, 6, 1}, {1217, 1, 14}, {1232, 1, 96}, + {1329, 22, 38}, {4256, 66, 38}, {4295, 66, 1}, + {4301, 66, 1}, {7680, 1, 150}, {7835, 142, 1}, + {7838, 106, 1}, {7840, 1, 96}, {7944, 160, 8}, + {7960, 160, 6}, {7976, 160, 8}, {7992, 160, 8}, + {8008, 160, 6}, {8025, 161, 8}, {8040, 160, 8}, + {8072, 160, 8}, {8088, 160, 8}, {8104, 160, 8}, + {8120, 160, 2}, {8122, 136, 2}, {8124, 158, 1}, + {8126, 110, 1}, {8136, 134, 4}, {8140, 158, 1}, + {8152, 160, 2}, {8154, 130, 2}, {8168, 160, 2}, + {8170, 128, 2}, {8172, 162, 1}, {8184, 122, 2}, + {8186, 124, 2}, {8188, 158, 1}, {8486, 108, 1}, + {8490, 102, 1}, {8491, 104, 1}, {8498, 12, 1}, + {8544, 8, 16}, {8579, 0, 1}, {9398, 10, 26}, + {11264, 22, 47}, {11360, 0, 1}, {11362, 98, 1}, + {11363, 112, 1}, {11364, 100, 1}, {11367, 1, 6}, + {11373, 94, 1}, {11374, 96, 1}, {11375, 90, 1}, + {11376, 92, 1}, {11378, 0, 1}, {11381, 0, 1}, + {11390, 88, 2}, {11392, 1, 100}, {11499, 1, 4}, + {11506, 0, 1}, {42560, 1, 46}, {42624, 1, 28}, + {42786, 1, 14}, {42802, 1, 62}, {42873, 1, 4}, + {42877, 86, 1}, {42878, 1, 10}, {42891, 0, 1}, + {42893, 82, 1}, {42896, 1, 4}, {42902, 1, 20}, + {42922, 76, 1}, {42923, 72, 1}, {42924, 74, 1}, + {42925, 78, 1}, {42928, 84, 1}, {42929, 80, 1}, + {65313, 14, 26}, }; static const unsigned short aiOff[] = { - 1, 2, 8, 15, 16, 26, 28, 32, - 37, 38, 40, 48, 63, 64, 69, 71, - 79, 80, 116, 202, 203, 205, 206, 207, - 209, 210, 211, 213, 214, 217, 218, 219, - 775, 7264, 10792, 10795, 23228, 23256, 30204, 54721, - 54753, 54754, 54756, 54787, 54793, 54809, 57153, 57274, - 57921, 58019, 58363, 61722, 65268, 65341, 65373, 65406, - 65408, 65410, 65415, 65424, 65436, 65439, 65450, 65462, - 65472, 65476, 65478, 65480, 65482, 65488, 65506, 65511, - 65514, 65521, 65527, 65528, 65529, + 1, 2, 8, 15, 16, 26, 28, 32, + 37, 38, 40, 48, 63, 64, 69, 71, + 79, 80, 116, 202, 203, 205, 206, 207, + 209, 210, 211, 213, 214, 217, 218, 219, + 775, 7264, 10792, 10795, 23217, 23221, 23228, 23231, + 23254, 23256, 23278, 30204, 54721, 54753, 54754, 54756, + 54787, 54793, 54809, 57153, 57274, 57921, 58019, 58363, + 61722, 65268, 65341, 65373, 65406, 65408, 65410, 65415, + 65424, 65436, 65439, 65450, 65462, 65472, 65476, 65478, + 65480, 65482, 65488, 65506, 65511, 65514, 65521, 65527, + 65528, 65529, }; int ret = c; assert( c>=0 ); @@ -354,12 +363,15 @@ } } if( bRemoveDiacritic ) ret = unicode_remove_diacritic(ret); } - + else if( c>=66560 && c<66600 ){ ret = c + 40; + } + else if( c>=71840 && c<71872 ){ + ret = c + 32; } return ret; } Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -34,11 +34,11 @@ */ static int internalUpdate = 0; static int internalConflictCnt = 0; /* -** Do an update to version vid. +** Do an update to version vid. ** ** Start an undo session but do not terminate it. Do not autosync. */ int update_to(int vid){ int savedArgc; @@ -62,38 +62,47 @@ /* ** COMMAND: update ** ** Usage: %fossil update ?OPTIONS? ?VERSION? ?FILES...? ** -** Change the version of the current checkout to VERSION. Any uncommitted -** changes are retained and applied to the new checkout. +** Change the version of the current checkout to VERSION. Any +** uncommitted changes are retained and applied to the new checkout. ** -** The VERSION argument can be a specific version or tag or branch name. -** If the VERSION argument is omitted, then the leaf of the subtree -** that begins at the current version is used, if there is only a single -** leaf. VERSION can also be "current" to select the leaf of the current -** version or "latest" to select the most recent check-in. +** The VERSION argument can be a specific version or tag or branch +** name. If the VERSION argument is omitted, then the leaf of the +** subtree that begins at the current version is used, if there is +** only a single leaf. VERSION can also be "current" to select the +** leaf of the current version or "latest" to select the most recent +** check-in. ** ** If one or more FILES are listed after the VERSION then only the -** named files are candidates to be updated. If FILES is omitted, all -** files in the current checkout are subject to be updated. Using -** a directory name for one of the FILES arguments is the same as -** using every subdirectory and file beneath that directory. -** -** The -n or --dry-run option causes this command to do a "dry run". It -** prints out what would have happened but does not actually make any -** changes to the current checkout or the repository. -** -** The -v or --verbose option prints status information about unchanged -** files in addition to those file that actually do change. +** named files are candidates to be updated, and any updates to them +** will be treated as edits to the current version. Using a directory +** name for one of the FILES arguments is the same as using every +** subdirectory and file beneath that directory. +** +** If FILES is omitted, all files in the current checkout are subject +** to being updated and the version of the current checkout is changed +** to VERSION. Any uncommitted changes are retained and applied to the +** new checkout. +** +** The -n or --dry-run option causes this command to do a "dry run". +** It prints out what would have happened but does not actually make +** any changes to the current checkout or the repository. +** +** The -v or --verbose option prints status information about +** unchanged files in addition to those file that actually do change. ** ** Options: ** --case-sensitive <BOOL> override case-sensitive setting ** --debug print debug information on stdout ** --latest acceptable in place of VERSION, update to latest version +** --force-missing force update if missing content after sync ** -n|--dry-run If given, display instead of run actions ** -v|--verbose print status information about all files +** -W|--width <num> Width of lines (default is to auto-detect). Must be >20 +** or 0 (= no limit, resulting in a single line per entry). ** ** See also: revert */ void update_cmd(void){ int vid; /* Current version */ @@ -100,43 +109,59 @@ int tid=0; /* Target version - version we are changing to */ Stmt q; int latestFlag; /* --latest. Pick the latest version if true */ int dryRunFlag; /* -n or --dry-run. Do a dry run */ int verboseFlag; /* -v or --verbose. Output extra information */ + int forceMissingFlag; /* --force-missing. Continue if missing content */ int debugFlag; /* --debug option */ int setmtimeFlag; /* --setmtime. Set mtimes on files */ int nChng; /* Number of file renames */ int *aChng; /* Array of file renames */ int i; /* Loop counter */ int nConflict = 0; /* Number of merge conflicts */ int nOverwrite = 0; /* Number of unmanaged files overwritten */ int nUpdate = 0; /* Number of changes of any kind */ + int width; /* Width of printed comment lines */ Stmt mtimeXfer; /* Statement to transfer mtimes */ + const char *zWidth; /* Width option string value */ if( !internalUpdate ){ undo_capture_command_line(); url_proxy_options(); + } + zWidth = find_option("width","W",1); + if( zWidth ){ + width = atoi(zWidth); + if( (width!=0) && (width<=20) ){ + fossil_fatal("-W|--width value must be >20 or 0"); + } + }else{ + width = -1; } latestFlag = find_option("latest",0, 0)!=0; dryRunFlag = find_option("dry-run","n",0)!=0; if( !dryRunFlag ){ dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */ } verboseFlag = find_option("verbose","v",0)!=0; + forceMissingFlag = find_option("force-missing",0,0)!=0; debugFlag = find_option("debug",0,0)!=0; setmtimeFlag = find_option("setmtime",0,0)!=0; - capture_case_sensitive_option(); + + /* We should be done with options.. */ + verify_all_options(); + db_must_be_within_tree(); vid = db_lget_int("checkout", 0); - if( vid==0 ){ - fossil_fatal("cannot find current version"); - } user_select(); if( !dryRunFlag && !internalUpdate ){ - autosync(SYNC_PULL + SYNC_VERBOSE*verboseFlag); + if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, + db_get_int("autosync-tries", 1)) ){ + fossil_fatal("Cannot proceed with update"); + } } - + /* Create any empty directories now, as well as after the update, ** so changes in settings are reflected now */ if( !dryRunFlag ) ensure_empty_dirs_created(); if( internalUpdate ){ @@ -157,11 +182,11 @@ }else if( !is_a_version(tid) ){ fossil_fatal("no such version: %s", g.argv[2]); } } } - + /* If no VERSION is specified on the command-line, then look for a ** descendent of the current version. If there are multiple descendants, ** look for one from the same branch as the current version. If there ** are still multiple descendants, show them all and refuse to update ** until the user selects one. @@ -182,35 +207,37 @@ " WHERE tagid=%d AND rid=%d))", TAG_BRANCH, TAG_BRANCH, vid ); if( db_int(0, "SELECT count(*) FROM leaves")>1 ){ compute_leaves(vid, closeCode); - db_prepare(&q, + db_prepare(&q, "%s " " AND event.objid IN leaves" " ORDER BY event.mtime DESC", timeline_query_for_tty() ); - print_timeline(&q, 100, 0); + print_timeline(&q, -100, width, 0); db_finalize(&q); fossil_fatal("Multiple descendants"); } } tid = db_int(0, "SELECT rid FROM leaves, event" " WHERE event.objid=leaves.rid" - " ORDER BY event.mtime DESC"); + " ORDER BY event.mtime DESC"); if( tid==0 ) tid = vid; } if( tid==0 ){ - fossil_panic("unable to find a version to update to."); + return; } db_begin_transaction(); vfile_check_signature(vid, CKSIG_ENOTFILE); if( !dryRunFlag && !internalUpdate ) undo_begin(); - load_vfile_from_rid(tid); + if( load_vfile_from_rid(tid) && !forceMissingFlag ){ + fossil_fatal("missing content, unable to update"); + }; /* ** The record.fn field is used to match files against each other. The ** FV table contains one row for each each unique filename in ** in the current checkout, the pivot, and the version being merged. @@ -243,21 +270,23 @@ ); /* Compute file name changes on V->T. Record name changes in files that ** have changed locally. */ - find_filename_changes(vid, tid, 1, &nChng, &aChng, debugFlag ? "V->T": 0); - if( nChng ){ - for(i=0; i<nChng; i++){ - db_multi_exec( - "UPDATE fv" - " SET fnt=(SELECT name FROM filename WHERE fnid=%d)" - " WHERE fn=(SELECT name FROM filename WHERE fnid=%d) AND chnged", - aChng[i*2+1], aChng[i*2] - ); - } - fossil_free(aChng); + if( vid ){ + find_filename_changes(vid, tid, 1, &nChng, &aChng, debugFlag ? "V->T": 0); + if( nChng ){ + for(i=0; i<nChng; i++){ + db_multi_exec( + "UPDATE fv" + " SET fnt=(SELECT name FROM filename WHERE fnid=%d)" + " WHERE fn=(SELECT name FROM filename WHERE fnid=%d) AND chnged", + aChng[i*2+1], aChng[i*2] + ); + } + fossil_free(aChng); + } } /* Add files found in the target version T but missing from the current ** version V. */ @@ -328,39 +357,41 @@ zSep = ""; for(i=3; i<g.argc; i++){ file_tree_name(g.argv[i], &treename, 1); if( file_wd_isdir(g.argv[i])==1 ){ if( blob_size(&treename) != 1 || blob_str(&treename)[0] != '.' ){ - blob_appendf(&sql, "%sfn NOT GLOB '%b/*' ", zSep, &treename); + blob_append_sql(&sql, "%sfn NOT GLOB '%q/*' ", + zSep /*safe-for-%s*/, blob_str(&treename)); }else{ blob_reset(&sql); break; } }else{ - blob_appendf(&sql, "%sfn<>%B ", zSep, &treename); + blob_append_sql(&sql, "%sfn<>%Q ", + zSep /*safe-for-%s*/, blob_str(&treename)); } zSep = "AND "; blob_reset(&treename); } - db_multi_exec(blob_str(&sql)); + db_multi_exec("%s", blob_sql_text(&sql)); blob_reset(&sql); } /* ** Alter the content of the checkout so that it conforms with the ** target */ - db_prepare(&q, + db_prepare(&q, "SELECT fn, idv, ridv, idt, ridt, chnged, fnt," " isexe, islinkv, islinkt, deleted FROM fv ORDER BY 1" ); db_prepare(&mtimeXfer, "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)" " WHERE id=:idt" ); assert( g.zLocalRoot!=0 ); - assert( strlen(g.zLocalRoot)>1 ); + assert( strlen(g.zLocalRoot)>0 ); assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' ); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); /* The filename from root */ int idv = db_column_int(&q, 1); /* VFILE entry for current */ int ridv = db_column_int(&q, 2); /* RecordID for current */ @@ -439,11 +470,11 @@ }else{ fossil_print("MERGE %s\n", zName); } if( islinkv || islinkt /* || file_wd_islink(zFullPath) */ ){ fossil_print("***** Cannot merge symlink %s\n", zNewName); - nConflict++; + nConflict++; }else{ unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; undo_save(zName); content_get(ridt, &t); content_get(ridv, &v); @@ -513,11 +544,11 @@ fossil_warning("uncommitted %s against %S.", zLabel, db_column_text(&q, 0)); nMerge++; } db_finalize(&q); - + if( nConflict ){ if( internalUpdate ){ internalConflictCnt = nConflict; nConflict = 0; }else{ @@ -530,11 +561,11 @@ } if( nMerge ){ fossil_warning("WARNING: %d uncommitted prior merges", nMerge); } } - + /* ** Clean up the mid and pid VFILE entries. Then commit the changes. */ if( dryRunFlag ){ db_end_transaction(1); /* With --dry-run, rollback changes */ @@ -584,41 +615,41 @@ Blob path; const char *zPath; blob_zero(&path); blob_appendf(&path, "%s/%s", g.zLocalRoot, zDir); - zPath = blob_str(&path); + zPath = blob_str(&path); /* Handle various cases of existence of the directory */ switch( file_wd_isdir(zPath) ){ case 0: { /* doesn't exist */ if( file_mkdir(zPath, 0)!=0 ) { fossil_warning("couldn't create directory %s as " "required by empty-dirs setting", zDir); - } + } break; } case 1: { /* exists, and is a directory */ /* do nothing - required directory exists already */ break; } case 2: { /* exists, but isn't a directory */ fossil_warning("file %s found, but a directory is required " - "by empty-dirs setting", zDir); + "by empty-dirs setting", zDir); } } blob_reset(&path); } } } /* -** Get the contents of a file within the checking "revision". If +** Get the contents of a file within the check-in "revision". If ** revision==NULL then get the file content for the current checkout. */ int historical_version_of_file( - const char *revision, /* The checkin containing the file */ + const char *revision, /* The check-in containing the file */ const char *file, /* Full treename of the file */ Blob *content, /* Put the content here */ int *pIsLink, /* Set to true if file is link. */ int *pIsExe, /* Set to true if file is executable */ int *pIsBin, /* Set to true if file is binary */ @@ -625,22 +656,24 @@ int errCode /* Error code if file not found. Panic if 0. */ ){ Manifest *pManifest; ManifestFile *pFile; int rid=0; - + if( revision ){ rid = name_to_typed_rid(revision,"ci"); + }else if( !g.localOpen ){ + rid = name_to_typed_rid(db_get("main-branch","trunk"),"ci"); }else{ rid = db_lget_int("checkout", 0); } if( !is_a_version(rid) ){ if( errCode>0 ) return errCode; - fossil_fatal("no such checkin: %s", revision); + fossil_fatal("no such check-in: %s", revision); } pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0); - + if( pManifest ){ pFile = manifest_file_find(pManifest, file); if( pFile ){ int rc; rid = uuid_to_rid(pFile->zUuid, 0); @@ -653,17 +686,17 @@ } return rc; } manifest_destroy(pManifest); if( errCode<=0 ){ - fossil_fatal("file %s does not exist in checkin: %s", file, revision); + fossil_fatal("file %s does not exist in check-in: %s", file, revision); } }else if( errCode<=0 ){ if( revision==0 ){ revision = db_text("current", "SELECT uuid FROM blob WHERE rid=%d", rid); } - fossil_fatal("could not parse manifest for checkin: %s", revision); + fossil_fatal("could not parse manifest for check-in: %s", revision); } return errCode; } @@ -695,14 +728,14 @@ Blob record; int i; int errCode; Stmt q; - undo_capture_command_line(); + undo_capture_command_line(); zRevision = find_option("revision", "r", 1); verify_all_options(); - + if( g.argc<2 ){ usage("?OPTIONS? [FILE] ..."); } if( zRevision && g.argc<3 ){ fossil_fatal("the --revision option does not work for the entire tree"); @@ -720,16 +753,12 @@ db_multi_exec( "REPLACE INTO torevert VALUES(%B);" "INSERT OR IGNORE INTO torevert" " SELECT pathname" " FROM vfile" - " WHERE origname IN(%B)" - " UNION ALL" - " SELECT origname" - " FROM vfile" - " WHERE pathname IN(%B) AND origname IS NOT NULL;", - &fname, &fname, &fname + " WHERE origname=%B;", + &fname, &fname ); blob_reset(&fname); } }else{ int vid; @@ -738,17 +767,19 @@ db_multi_exec( "DELETE FROM vmerge;" "INSERT OR IGNORE INTO torevert " " SELECT pathname" " FROM vfile " - " WHERE chnged OR deleted OR rid=0 OR pathname!=origname " - " UNION ALL " - " SELECT origname" - " FROM vfile" - " WHERE origname!=pathname;" + " WHERE chnged OR deleted OR rid=0 OR pathname!=origname;" ); } + db_multi_exec( + "INSERT OR IGNORE INTO torevert" + " SELECT origname" + " FROM vfile" + " WHERE origname!=pathname AND pathname IN (SELECT name FROM torevert);" + ); blob_zero(&record); db_prepare(&q, "SELECT name FROM torevert"); if( zRevision==0 ){ int vid = db_lget_int("checkout", 0); zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); @@ -769,20 +800,20 @@ undo_save(zFile); file_delete(zFull); fossil_print("DELETE: %s\n", zFile); } db_multi_exec( - "UPDATE vfile" + "UPDATE OR REPLACE vfile" " SET pathname=origname, origname=NULL" - " WHERE pathname=%Q AND origname!=pathname AND origname IS NOT NULL;" + " WHERE pathname=%Q AND origname!=pathname;" "DELETE FROM vfile WHERE pathname=%Q", zFile, zFile ); }else{ sqlite3_int64 mtime; undo_save(zFile); - if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(zFull)) ){ + if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(0)) ){ file_delete(zFull); } if( isLink ){ symlink_create(blob_str(&record), zFull); }else{ Index: src/url.c ================================================================== --- src/url.c +++ src/url.c @@ -17,10 +17,21 @@ ** ** This file contains code for parsing URLs that appear on the command-line */ #include "config.h" #include "url.h" +#include <stdio.h> + +#ifdef _WIN32 +#include <io.h> +#ifndef isatty +#define isatty(d) _isatty(d) +#endif +#ifndef fileno +#define fileno(s) _fileno(s) +#endif +#endif #if INTERFACE /* ** Flags for url_parse() */ @@ -28,10 +39,33 @@ #define URL_REMEMBER 0x002 /* Remember the url for later reuse */ #define URL_ASK_REMEMBER_PW 0x004 /* Ask whether to remember prompted pw */ #define URL_REMEMBER_PW 0x008 /* Should remember pw */ #define URL_PROMPTED 0x010 /* Prompted for PW already */ +/* +** The URL related data used with this subsystem. +*/ +struct UrlData { + int isFile; /* True if a "file:" url */ + int isHttps; /* True if a "https:" url */ + int isSsh; /* True if an "ssh:" url */ + char *name; /* Hostname for http: or filename for file: */ + char *hostname; /* The HOST: parameter on http headers */ + char *protocol; /* "http" or "https" */ + int port; /* TCP port number for http: or https: */ + int dfltPort; /* The default port for the given protocol */ + char *path; /* Pathname for http: */ + char *user; /* User id for http: */ + char *passwd; /* Password for http: */ + char *canonical; /* Canonical representation of the URL */ + char *proxyAuth; /* Proxy-Authorizer: string */ + char *fossil; /* The fossil query parameter on ssh: */ + unsigned flags; /* Boolean flags controlling URL processing */ + int useProxy; /* Used to remember that a proxy is in use */ + char *proxyUrlPath; + int proxyOrigPort; /* Tunneled port number for https through proxy */ +}; #endif /* INTERFACE */ /* ** Convert a string to lower-case. @@ -42,46 +76,41 @@ z++; } } /* -** Parse the given URL, which describes a sync server. Populate variables -** in the global "g" structure as follows: -** -** g.urlIsFile True if FILE: -** g.urlIsHttps True if HTTPS: -** g.urlIsSsh True if SSH: -** g.urlProtocol "http" or "https" or "file" -** g.urlName Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE: -** g.urlPort TCP port number for HTTP or HTTPS. -** g.urlDfltPort Default TCP port number (80 or 443). -** g.urlPath Path name for HTTP or HTTPS. -** g.urlUser Userid. -** g.urlPasswd Password. -** g.urlHostname HOST:PORT or just HOST if port is the default. -** g.urlCanonical The URL in canonical form, omitting the password -** -** HTTP url format as follows (HTTPS is the same with a different scheme): -** -** http://userid:password@host:port/path -** -** SSH url format is: -** -** ssh://userid:password@host:port/path?fossil=path/to/fossil.exe +** Parse the given URL. Populate members of the provided UrlData structure +** as follows: +** +** isFile True if FILE: +** isHttps True if HTTPS: +** isSsh True if SSH: +** protocol "http" or "https" or "file" +** name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE: +** port TCP port number for HTTP or HTTPS. +** dfltPort Default TCP port number (80 or 443). +** path Path name for HTTP or HTTPS. +** user Userid. +** passwd Password. +** hostname HOST:PORT or just HOST if port is the default. +** canonical The URL in canonical form, omitting the password ** */ -void url_parse(const char *zUrl, unsigned int urlFlags){ +void url_parse_local( + const char *zUrl, + unsigned int urlFlags, + UrlData *pUrlData +){ int i, j, c; char *zFile = 0; - int bPrompted = 0; - int bSetUrl = 1; - + if( zUrl==0 ){ zUrl = db_get("last-sync-url", 0); if( zUrl==0 ) return; - g.urlPasswd = unobscure(db_get("last-sync-pw", 0)); - bSetUrl = 0; + if( pUrlData->passwd==0 ){ + pUrlData->passwd = unobscure(db_get("last-sync-pw", 0)); + } } if( strncmp(zUrl, "http://", 7)==0 || strncmp(zUrl, "https://", 8)==0 || strncmp(zUrl, "ssh://", 6)==0 @@ -89,162 +118,200 @@ int iStart; char *zLogin; char *zExe; char cQuerySep = '?'; - g.urlIsFile = 0; + pUrlData->isFile = 0; + pUrlData->useProxy = 0; if( zUrl[4]=='s' ){ - g.urlIsHttps = 1; - g.urlProtocol = "https"; - g.urlDfltPort = 443; + pUrlData->isHttps = 1; + pUrlData->protocol = "https"; + pUrlData->dfltPort = 443; iStart = 8; }else if( zUrl[0]=='s' ){ - g.urlIsSsh = 1; - g.urlProtocol = "ssh"; - g.urlDfltPort = 22; - g.urlFossil = "fossil"; - g.urlShell = 0; + pUrlData->isSsh = 1; + pUrlData->protocol = "ssh"; + pUrlData->dfltPort = 22; + pUrlData->fossil = "fossil"; iStart = 6; }else{ - g.urlIsHttps = 0; - g.urlProtocol = "http"; - g.urlDfltPort = 80; + pUrlData->isHttps = 0; + pUrlData->protocol = "http"; + pUrlData->dfltPort = 80; iStart = 7; } for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!='@'; i++){} if( c=='@' ){ /* Parse up the user-id and password */ for(j=iStart; j<i && zUrl[j]!=':'; j++){} - g.urlUser = mprintf("%.*s", j-iStart, &zUrl[iStart]); - dehttpize(g.urlUser); - if( j<i ){ - g.urlPasswd = mprintf("%.*s", i-j-1, &zUrl[j+1]); - dehttpize(g.urlPasswd); - } - if( g.urlIsSsh && g.urlPasswd ){ - zLogin = mprintf("%t:*@", g.urlUser); - }else{ - zLogin = mprintf("%t@", g.urlUser); - } - for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){} - g.urlName = mprintf("%.*s", j-i-1, &zUrl[i+1]); - i = j; - }else{ - for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!=':'; i++){} - g.urlName = mprintf("%.*s", i-iStart, &zUrl[iStart]); - zLogin = mprintf(""); - } - url_tolower(g.urlName); - if( c==':' ){ - g.urlPort = 0; - i++; - while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){ - g.urlPort = g.urlPort*10 + c - '0'; - i++; - } - g.urlHostname = mprintf("%s:%d", g.urlName, g.urlPort); - }else{ - g.urlPort = g.urlDfltPort; - g.urlHostname = g.urlName; - } - dehttpize(g.urlName); - g.urlPath = mprintf("%s", &zUrl[i]); - for(i=0; g.urlPath[i] && g.urlPath[i]!='?'; i++){} - if( g.urlPath[i] ){ - g.urlPath[i] = 0; - i++; - } - zExe = mprintf(""); - while( g.urlPath[i]!=0 ){ - char *zName, *zValue; - zName = &g.urlPath[i]; - zValue = zName; - while( g.urlPath[i] && g.urlPath[i]!='=' ){ i++; } - if( g.urlPath[i]=='=' ){ - g.urlPath[i] = 0; - i++; - zValue = &g.urlPath[i]; - while( g.urlPath[i] && g.urlPath[i]!='&' ){ i++; } - } - if( g.urlPath[i] ){ - g.urlPath[i] = 0; - i++; - } - if( fossil_strcmp(zName,"fossil")==0 ){ - g.urlFossil = zValue; - dehttpize(g.urlFossil); - zExe = mprintf("%cfossil=%T", cQuerySep, g.urlFossil); - cQuerySep = '&'; - } - if( fossil_strcmp(zName,"shell")==0 ){ - g.urlShell = zValue; - dehttpize(g.urlShell); - zExe = mprintf("%cshell=%T", cQuerySep, g.urlFossil); - cQuerySep = '&'; - } - } - - dehttpize(g.urlPath); - if( g.urlDfltPort==g.urlPort ){ - g.urlCanonical = mprintf( - "%s://%s%T%T%s", - g.urlProtocol, zLogin, g.urlName, g.urlPath, zExe - ); - }else{ - g.urlCanonical = mprintf( - "%s://%s%T:%d%T%s", - g.urlProtocol, zLogin, g.urlName, g.urlPort, g.urlPath, zExe - ); - } - if( g.urlIsSsh && g.urlPath[1] ) g.urlPath++; - free(zLogin); - }else if( strncmp(zUrl, "file:", 5)==0 ){ - g.urlIsFile = 1; + pUrlData->user = mprintf("%.*s", j-iStart, &zUrl[iStart]); + dehttpize(pUrlData->user); + if( j<i ){ + if( ( urlFlags & URL_REMEMBER ) && pUrlData->isSsh==0 ){ + urlFlags |= URL_ASK_REMEMBER_PW; + } + pUrlData->passwd = mprintf("%.*s", i-j-1, &zUrl[j+1]); + dehttpize(pUrlData->passwd); + } + if( pUrlData->isSsh ){ + urlFlags &= ~URL_ASK_REMEMBER_PW; + } + zLogin = mprintf("%t@", pUrlData->user); + for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){} + pUrlData->name = mprintf("%.*s", j-i-1, &zUrl[i+1]); + i = j; + }else{ + int inSquare = 0; + int n; + for(i=iStart; (c=zUrl[i])!=0 && c!='/' && (inSquare || c!=':'); i++){ + if( c=='[' ) inSquare = 1; + if( c==']' ) inSquare = 0; + } + pUrlData->name = mprintf("%.*s", i-iStart, &zUrl[iStart]); + n = strlen(pUrlData->name); + if( pUrlData->name[0]=='[' && n>2 && pUrlData->name[n-1]==']' ){ + pUrlData->name++; + pUrlData->name[n-2] = 0; + } + zLogin = mprintf(""); + } + url_tolower(pUrlData->name); + if( c==':' ){ + pUrlData->port = 0; + i++; + while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){ + pUrlData->port = pUrlData->port*10 + c - '0'; + i++; + } + pUrlData->hostname = mprintf("%s:%d", pUrlData->name, pUrlData->port); + }else{ + pUrlData->port = pUrlData->dfltPort; + pUrlData->hostname = pUrlData->name; + } + dehttpize(pUrlData->name); + pUrlData->path = mprintf("%s", &zUrl[i]); + for(i=0; pUrlData->path[i] && pUrlData->path[i]!='?'; i++){} + if( pUrlData->path[i] ){ + pUrlData->path[i] = 0; + i++; + } + zExe = mprintf(""); + while( pUrlData->path[i]!=0 ){ + char *zName, *zValue; + zName = &pUrlData->path[i]; + zValue = zName; + while( pUrlData->path[i] && pUrlData->path[i]!='=' ){ i++; } + if( pUrlData->path[i]=='=' ){ + pUrlData->path[i] = 0; + i++; + zValue = &pUrlData->path[i]; + while( pUrlData->path[i] && pUrlData->path[i]!='&' ){ i++; } + } + if( pUrlData->path[i] ){ + pUrlData->path[i] = 0; + i++; + } + if( fossil_strcmp(zName,"fossil")==0 ){ + pUrlData->fossil = zValue; + dehttpize(pUrlData->fossil); + zExe = mprintf("%cfossil=%T", cQuerySep, pUrlData->fossil); + cQuerySep = '&'; + } + } + + dehttpize(pUrlData->path); + if( pUrlData->dfltPort==pUrlData->port ){ + pUrlData->canonical = mprintf( + "%s://%s%T%T%s", + pUrlData->protocol, zLogin, pUrlData->name, pUrlData->path, zExe + ); + }else{ + pUrlData->canonical = mprintf( + "%s://%s%T:%d%T%s", + pUrlData->protocol, zLogin, pUrlData->name, pUrlData->port, + pUrlData->path, zExe + ); + } + if( pUrlData->isSsh && pUrlData->path[1] ) pUrlData->path++; + free(zLogin); + }else if( strncmp(zUrl, "file:", 5)==0 ){ + pUrlData->isFile = 1; if( zUrl[5]=='/' && zUrl[6]=='/' ){ i = 7; }else{ i = 5; } zFile = mprintf("%s", &zUrl[i]); }else if( file_isfile(zUrl) ){ - g.urlIsFile = 1; + pUrlData->isFile = 1; zFile = mprintf("%s", zUrl); }else if( file_isdir(zUrl)==1 ){ zFile = mprintf("%s/FOSSIL", zUrl); if( file_isfile(zFile) ){ - g.urlIsFile = 1; + pUrlData->isFile = 1; }else{ free(zFile); + zFile = 0; fossil_fatal("unknown repository: %s", zUrl); } }else{ fossil_fatal("unknown repository: %s", zUrl); } - g.urlFlags = urlFlags; - if( g.urlIsFile ){ + if( urlFlags ) pUrlData->flags = urlFlags; + if( pUrlData->isFile ){ Blob cfile; - dehttpize(zFile); + dehttpize(zFile); file_canonical_name(zFile, &cfile, 0); free(zFile); - g.urlProtocol = "file"; - g.urlPath = ""; - g.urlName = mprintf("%b", &cfile); - g.urlCanonical = mprintf("file://%T", g.urlName); + zFile = 0; + pUrlData->protocol = "file"; + pUrlData->path = ""; + pUrlData->name = mprintf("%b", &cfile); + pUrlData->canonical = mprintf("file://%T", pUrlData->name); blob_reset(&cfile); - }else if( g.urlUser!=0 && g.urlPasswd==0 && (urlFlags & URL_PROMPT_PW) ){ - url_prompt_for_password(); - bPrompted = 1; - } - if( urlFlags & URL_REMEMBER ){ - if( bSetUrl ){ - db_set("last-sync-url", g.urlCanonical, 0); - } - if( !bPrompted && g.urlPasswd && g.urlUser ){ - db_set("last-sync-pw", obscure(g.urlPasswd), 0); + }else if( pUrlData->user!=0 && pUrlData->passwd==0 && (urlFlags & URL_PROMPT_PW) ){ + url_prompt_for_password_local(pUrlData); + }else if( pUrlData->user!=0 && ( urlFlags & URL_ASK_REMEMBER_PW ) ){ + if( isatty(fileno(stdin)) ){ + if( save_password_prompt(pUrlData->passwd) ){ + pUrlData->flags = urlFlags |= URL_REMEMBER_PW; + }else{ + pUrlData->flags = urlFlags &= ~URL_REMEMBER_PW; + } } } } + +/* +** Parse the given URL, which describes a sync server. Populate variables +** in the global "g" structure as follows: +** +** g.url.isFile True if FILE: +** g.url.isHttps True if HTTPS: +** g.url.isSsh True if SSH: +** g.url.protocol "http" or "https" or "file" +** g.url.name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE: +** g.url.port TCP port number for HTTP or HTTPS. +** g.url.dfltPort Default TCP port number (80 or 443). +** g.url.path Path name for HTTP or HTTPS. +** g.url.user Userid. +** g.url.passwd Password. +** g.url.hostname HOST:PORT or just HOST if port is the default. +** g.url.canonical The URL in canonical form, omitting the password +** +** HTTP url format as follows (HTTPS is the same with a different scheme): +** +** http://userid:password@host:port/path +** +** SSH url format is: +** +** ssh://userid@host:port/path?fossil=path/to/fossil.exe +** +*/ +void url_parse(const char *zUrl, unsigned int urlFlags){ + url_parse_local(zUrl, urlFlags, &g.url); +} /* ** COMMAND: test-urlparser ** ** Usage: %fossil test-urlparser URL ?options? @@ -264,25 +331,25 @@ if( g.argc!=3 && g.argc!=4 ){ usage("URL"); } url_parse(g.argv[2], fg); for(i=0; i<2; i++){ - fossil_print("g.urlIsFile = %d\n", g.urlIsFile); - fossil_print("g.urlIsHttps = %d\n", g.urlIsHttps); - fossil_print("g.urlIsSsh = %d\n", g.urlIsSsh); - fossil_print("g.urlProtocol = %s\n", g.urlProtocol); - fossil_print("g.urlName = %s\n", g.urlName); - fossil_print("g.urlPort = %d\n", g.urlPort); - fossil_print("g.urlDfltPort = %d\n", g.urlDfltPort); - fossil_print("g.urlHostname = %s\n", g.urlHostname); - fossil_print("g.urlPath = %s\n", g.urlPath); - fossil_print("g.urlUser = %s\n", g.urlUser); - fossil_print("g.urlPasswd = %s\n", g.urlPasswd); - fossil_print("g.urlCanonical = %s\n", g.urlCanonical); - fossil_print("g.urlFossil = %s\n", g.urlFossil); - fossil_print("g.urlFlags = 0x%02x\n", g.urlFlags); - if( g.urlIsFile || g.urlIsSsh ) break; + fossil_print("g.url.isFile = %d\n", g.url.isFile); + fossil_print("g.url.isHttps = %d\n", g.url.isHttps); + fossil_print("g.url.isSsh = %d\n", g.url.isSsh); + fossil_print("g.url.protocol = %s\n", g.url.protocol); + fossil_print("g.url.name = %s\n", g.url.name); + fossil_print("g.url.port = %d\n", g.url.port); + fossil_print("g.url.dfltPort = %d\n", g.url.dfltPort); + fossil_print("g.url.hostname = %s\n", g.url.hostname); + fossil_print("g.url.path = %s\n", g.url.path); + fossil_print("g.url.user = %s\n", g.url.user); + fossil_print("g.url.passwd = %s\n", g.url.passwd); + fossil_print("g.url.canonical = %s\n", g.url.canonical); + fossil_print("g.url.fossil = %s\n", g.url.fossil); + fossil_print("g.url.flags = 0x%02x\n", g.url.flags); + if( g.url.isFile || g.url.isSsh ) break; if( i==0 ){ fossil_print("********\n"); url_enable_proxy("Using proxy: "); } } @@ -305,10 +372,11 @@ ** feature. */ void url_proxy_options(void){ zProxyOpt = find_option("proxy", 0, 1); if( find_option("nosync",0,0) ) g.fNoSync = 1; + if( find_option("ipv4",0,0) ) g.fIPv4 = 1; } /* ** If the "proxy" setting is defined, then change the URL settings ** (initialized by a prior call to url_parse()) so that the HTTP @@ -321,36 +389,43 @@ void url_enable_proxy(const char *zMsg){ const char *zProxy; zProxy = zProxyOpt; if( zProxy==0 ){ zProxy = db_get("proxy", 0); - if( zProxy==0 || zProxy[0]==0 || is_truth(zProxy) ){ + if( zProxy==0 || zProxy[0]==0 || is_false(zProxy) ){ zProxy = fossil_getenv("http_proxy"); } } if( zProxy && zProxy[0] && !is_false(zProxy) - && !g.urlIsSsh && !g.urlIsFile ){ - char *zOriginalUrl = g.urlCanonical; - char *zOriginalHost = g.urlHostname; - char *zOriginalUser = g.urlUser; - char *zOriginalPasswd = g.urlPasswd; - unsigned uOriginalFlags = g.urlFlags; - g.urlUser = 0; - g.urlPasswd = ""; + && !g.url.isSsh && !g.url.isFile ){ + char *zOriginalUrl = g.url.canonical; + char *zOriginalHost = g.url.hostname; + int fOriginalIsHttps = g.url.isHttps; + char *zOriginalUser = g.url.user; + char *zOriginalPasswd = g.url.passwd; + char *zOriginalUrlPath = g.url.path; + int iOriginalPort = g.url.port; + unsigned uOriginalFlags = g.url.flags; + g.url.user = 0; + g.url.passwd = ""; url_parse(zProxy, 0); - if( zMsg ) fossil_print("%s%s\n", zMsg, g.urlCanonical); - g.urlPath = zOriginalUrl; - g.urlHostname = zOriginalHost; - if( g.urlUser ){ - char *zCredentials1 = mprintf("%s:%s", g.urlUser, g.urlPasswd); + if( zMsg ) fossil_print("%s%s\n", zMsg, g.url.canonical); + g.url.path = zOriginalUrl; + g.url.hostname = zOriginalHost; + if( g.url.user ){ + char *zCredentials1 = mprintf("%s:%s", g.url.user, g.url.passwd); char *zCredentials2 = encode64(zCredentials1, -1); - g.urlProxyAuth = mprintf("Basic %z", zCredentials2); + g.url.proxyAuth = mprintf("Basic %z", zCredentials2); free(zCredentials1); } - g.urlUser = zOriginalUser; - g.urlPasswd = zOriginalPasswd; - g.urlFlags = uOriginalFlags; + g.url.user = zOriginalUser; + g.url.passwd = zOriginalPasswd; + g.url.isHttps = fOriginalIsHttps; + g.url.useProxy = 1; + g.url.proxyUrlPath = zOriginalUrlPath; + g.url.proxyOrigPort = iOriginalPort; + g.url.flags = uOriginalFlags; } } #if INTERFACE /* @@ -357,47 +432,71 @@ ** An instance of this object is used to build a URL with query parameters. */ struct HQuery { Blob url; /* The URL */ const char *zBase; /* The base URL */ - int nParam; /* Number of parameters. Max 10 */ - const char *azName[15]; /* Parameter names */ - const char *azValue[15]; /* Parameter values */ + int nParam; /* Number of parameters. */ + int nAlloc; /* Number of allocated slots */ + const char **azName; /* Parameter names */ + const char **azValue; /* Parameter values */ }; #endif /* ** Initialize the URL object. */ void url_initialize(HQuery *p, const char *zBase){ + memset(p, 0, sizeof(*p)); blob_zero(&p->url); p->zBase = zBase; - p->nParam = 0; } /* ** Resets the given URL object, deallocating any memory ** it uses. */ void url_reset(HQuery *p){ blob_reset(&p->url); + fossil_free(p->azName); + fossil_free(p->azValue); url_initialize(p, p->zBase); } /* -** Add a fixed parameter to an HQuery. +** Add a fixed parameter to an HQuery. Or remove the parameters if zValue==0. */ void url_add_parameter(HQuery *p, const char *zName, const char *zValue){ - assert( p->nParam < count(p->azName) ); - assert( p->nParam < count(p->azValue) ); - p->azName[p->nParam] = zName; - p->azValue[p->nParam] = zValue; + int i; + for(i=0; i<p->nParam; i++){ + if( fossil_strcmp(p->azName[i],zName)==0 ){ + if( zValue==0 ){ + p->nParam--; + p->azValue[i] = p->azValue[p->nParam]; + p->azName[i] = p->azName[p->nParam]; + }else{ + p->azValue[i] = zValue; + } + return; + } + } + assert( i==p->nParam ); + if( zValue==0 ) return; + if( i>=p->nAlloc ){ + p->nAlloc = p->nAlloc*2 + 10; + p->azName = fossil_realloc(p->azName, sizeof(p->azName[0])*p->nAlloc); + p->azValue = fossil_realloc(p->azValue, sizeof(p->azValue[0])*p->nAlloc); + } + p->azName[i] = zName; + p->azValue[i] = zValue; p->nParam++; } /* ** Render the URL with a parameter override. +** +** Returned memory is transient and is overwritten on the next call to this +** routine for the same HQuery, or until the HQuery object is destroyed. */ char *url_render( HQuery *p, /* Base URL */ const char *zName1, /* First override */ const char *zValue1, /* First override value */ @@ -404,11 +503,11 @@ const char *zName2, /* Second override */ const char *zValue2 /* Second override value */ ){ const char *zSep = "?"; int i; - + blob_reset(&p->url); blob_appendf(&p->url, "%s/%s", g.zTop, p->zBase); for(i=0; i<p->nParam; i++){ const char *z = p->azValue[i]; if( zName1 && fossil_strcmp(zName1,p->azName[i])==0 ){ @@ -435,65 +534,63 @@ } return blob_str(&p->url); } /* -** Prompt the user for the password for g.urlUser. Store the result -** in g.urlPasswd. -*/ -void url_prompt_for_password(void){ - if( g.urlIsSsh || g.urlIsFile ) return; - if( isatty(fileno(stdin)) - && (g.urlFlags & URL_PROMPT_PW)!=0 - && (g.urlFlags & URL_PROMPTED)==0 - ){ - char *zPrompt = mprintf("\rpassword for %s: ", g.urlUser); - Blob x; - fossil_force_newline(); - prompt_for_password(zPrompt, &x, 0); - free(zPrompt); - g.urlPasswd = mprintf("%b", &x); - blob_reset(&x); - g.urlFlags |= URL_PROMPTED; - if( g.urlPasswd[0] - && (g.urlFlags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0 - ){ - char c; - prompt_user("remember password (Y/n)? ", &x); - c = blob_str(&x)[0]; - blob_reset(&x); - if( c!='n' && c!='N' ){ - g.urlFlags |= URL_REMEMBER_PW; - if( g.urlFlags & URL_REMEMBER ){ - db_set("last-sync-pw", obscure(g.urlPasswd), 0); - } +** Prompt the user for the password that corresponds to the "user" member of +** the provided UrlData structure. Store the result into the "passwd" member +** of the provided UrlData structure. +*/ +void url_prompt_for_password_local(UrlData *pUrlData){ + if( pUrlData->isSsh || pUrlData->isFile ) return; + if( isatty(fileno(stdin)) + && (pUrlData->flags & URL_PROMPT_PW)!=0 + && (pUrlData->flags & URL_PROMPTED)==0 + ){ + pUrlData->flags |= URL_PROMPTED; + pUrlData->passwd = prompt_for_user_password(pUrlData->user); + if( pUrlData->passwd[0] + && (pUrlData->flags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0 + ){ + if( save_password_prompt(pUrlData->passwd) ){ + pUrlData->flags |= URL_REMEMBER_PW; + }else{ + pUrlData->flags &= ~URL_REMEMBER_PW; } } }else{ fossil_fatal("missing or incorrect password for user \"%s\"", - g.urlUser); + pUrlData->user); } } /* -** Remember the URL if requested. +** Prompt the user for the password for g.url.user. Store the result +** in g.url.passwd. +*/ +void url_prompt_for_password(void){ + url_prompt_for_password_local(&g.url); +} + +/* +** Remember the URL and password if requested. */ void url_remember(void){ - db_set("last-sync-url", g.urlCanonical, 0); - if( g.urlFlags & URL_REMEMBER_PW ){ - db_set("last-sync-pw", obscure(g.urlPasswd), 0); + if( g.url.flags & URL_REMEMBER ){ + db_set("last-sync-url", g.url.canonical, 0); + if( g.url.user!=0 && g.url.passwd!=0 && ( g.url.flags & URL_REMEMBER_PW ) ){ + db_set("last-sync-pw", obscure(g.url.passwd), 0); + } } - g.urlFlags |= URL_REMEMBER; } /* Preemptively prompt for a password if a username is given in the ** URL but no password. */ void url_get_password_if_needed(void){ - if( (g.urlUser && g.urlUser[0]) - && (g.urlPasswd==0 || g.urlPasswd[0]==0) - && isatty(fileno(stdin)) - && g.urlIsSsh==0 + if( (g.url.user && g.url.user[0]) + && (g.url.passwd==0 || g.url.passwd[0]==0) + && isatty(fileno(stdin)) ){ url_prompt_for_password(); } } Index: src/user.c ================================================================== --- src/user.c +++ src/user.c @@ -34,11 +34,11 @@ if( z[i]=='\r' || z[i]=='\n' ){ while( i>0 && fossil_isspace(z[i-1]) ){ i--; } z[i] = 0; break; } - if( z[i]<' ' ) z[i] = ' '; + if( z[i]>0 && z[i]<' ' ) z[i] = ' '; } blob_append(pBlob, z, -1); } #if defined(_WIN32) || defined(__BIONIC__) @@ -128,10 +128,41 @@ break; } } blob_reset(&secondTry); } + +/* +** Prompt to save Fossil user password +*/ +int save_password_prompt(const char *passwd){ + Blob x; + char c; + const char *old = db_get("last-sync-pw", 0); + if( (old!=0) && fossil_strcmp(unobscure(old), passwd)==0 ){ + return 0; + } + prompt_user("remember password (Y/n)? ", &x); + c = blob_str(&x)[0]; + blob_reset(&x); + return ( c!='n' && c!='N' ); +} + +/* +** Prompt for Fossil user password +*/ +char *prompt_for_user_password(const char *zUser){ + char *zPrompt = mprintf("\rpassword for %s: ", zUser); + char *zPw; + Blob x; + fossil_force_newline(); + prompt_for_password(zPrompt, &x, 0); + free(zPrompt); + zPw = mprintf("%b", &x); + blob_reset(&x); + return zPw; +} /* ** Prompt the user to enter a single line of text. */ void prompt_user(const char *zPrompt, Blob *pIn){ @@ -166,10 +197,11 @@ ** ** Query or set the default user. The default user is the ** user for command-line interaction. ** ** %fossil user list +** %fossil user ls ** ** List all users known to the repository ** ** %fossil user new ?USERNAME? ?CONTACT-INFO? ?PASSWORD? ** @@ -217,12 +249,12 @@ "VALUES(%B,%Q,%B,%B,now())", &login, zPw, &caps, &contact ); free(zPw); }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){ - user_select(); if( g.argc==3 ){ + user_select(); fossil_print("%s\n", g.zLogin); }else{ if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){ fossil_fatal("no such user: %s", g.argv[3]); } @@ -230,11 +262,11 @@ db_lset("default-user", g.argv[3]); }else{ db_set("default-user", g.argv[3], 0); } } - }else if( n>=2 && strncmp(g.argv[2],"list",n)==0 ){ + }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) || ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){ Stmt q; db_prepare(&q, "SELECT login, info FROM user ORDER BY login"); while( db_step(&q)==SQLITE_ROW ){ fossil_print("%-12s %s\n", db_column_text(&q, 0), db_column_text(&q, 1)); } @@ -263,11 +295,11 @@ free(zSecret); } }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){ int uid; if( g.argc!=4 && g.argc!=5 ){ - usage("user capabilities USERNAME ?PERMISSIONS?"); + usage("capabilities USERNAME ?PERMISSIONS?"); } uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]); if( uid==0 ){ fossil_fatal("no such user: %s", g.argv[3]); } @@ -344,11 +376,11 @@ if( attempt_user(fossil_getenv("LOGNAME")) ) return; if( attempt_user(fossil_getenv("USERNAME")) ) return; url_parse(0, 0); - if( g.urlUser && attempt_user(g.urlUser) ) return; + if( g.url.user && attempt_user(g.url.user) ) return; fossil_print( "Cannot figure out who you are! Consider using the --user\n" "command line option, setting your USER environment variable,\n" "or setting a default user with \"fossil user default USER\".\n" @@ -392,11 +424,11 @@ Stmt q; int cnt = 0; int rc; login_check_credentials(); - if( !g.perm.Admin ){ login_needed(); return; } + if( !g.perm.Admin ){ login_needed(0); return; } create_accesslog_table(); if( P("delall") && P("delallbtn") ){ db_multi_exec("DELETE FROM accesslog"); cgi_redirectf("%s/access_log?y=%d&n=%d&o=%o", g.zTop, y, n, skip); @@ -419,29 +451,29 @@ cgi_redirectf("%s/access_log?y=%d&n=%d", g.zTop, y, n); return; } style_header("Access Log"); blob_zero(&sql); - blob_append(&sql, - "SELECT uname, ipaddr, datetime(mtime, 'localtime'), success" - " FROM accesslog", -1 + blob_append_sql(&sql, + "SELECT uname, ipaddr, datetime(mtime%s), success" + " FROM accesslog", timeline_utc() ); if( y==1 ){ blob_append(&sql, " WHERE success", -1); }else if( y==2 ){ blob_append(&sql, " WHERE NOT success", -1); } - blob_appendf(&sql," ORDER BY rowid DESC LIMIT %d OFFSET %d", n+1, skip); + blob_append_sql(&sql," ORDER BY rowid DESC LIMIT %d OFFSET %d", n+1, skip); if( skip ){ style_submenu_element("Newer", "Newer entries", "%s/access_log?o=%d&n=%d&y=%d", g.zTop, skip>=n ? skip-n : 0, n, y); } - rc = db_prepare_ignore_error(&q, blob_str(&sql)); - @ <center><table border="1" cellpadding="5"> - @ <tr><th width="33%%">Date</th><th width="34%%">User</th> - @ <th width="33%%">IP Address</th></tr> + rc = db_prepare_ignore_error(&q, "%s", blob_sql_text(&sql)); + @ <center><table border="1" cellpadding="5" id='logtable'> + @ <thead><tr><th width="33%%">Date</th><th width="34%%">User</th> + @ <th width="33%%">IP Address</th></tr></thead><tbody> while( rc==SQLITE_OK && db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); const char *zIP = db_column_text(&q, 1); const char *zDate = db_column_text(&q, 2); int bSuccess = db_column_int(&q, 3); @@ -460,11 +492,11 @@ } if( skip>0 || cnt>n ){ style_submenu_element("All", "All entries", "%s/access_log?n=10000000", g.zTop); } - @ </table></center> + @ </tbody></table></center> db_finalize(&q); @ <hr> @ <form method="post" action="%s(g.zTop)/access_log"> @ <label><input type="checkbox" name="delold"> @ Delete all but the most recent 200 entries</input></label> @@ -483,7 +515,8 @@ @ <form method="post" action="%s(g.zTop)/access_log"> @ <label><input type="checkbox" name="delall"> @ Delete all entries</input></label> @ <input type="submit" name="delallbtn" value="Delete"></input> @ </form> + output_table_sorting_javascript("logtable", "Ttt", 1); style_footer(); } Index: src/utf8.c ================================================================== --- src/utf8.c +++ src/utf8.c @@ -25,11 +25,11 @@ #ifdef _WIN32 # include <windows.h> #endif #include "cygsup.h" -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) /* ** Translate MBCS to UTF-8. Return a pointer to the translated text. ** Call fossil_mbcs_free() to deallocate any memory used to store the ** returned pointer when done. */ @@ -54,18 +54,22 @@ ** returned pointer when done. */ char *fossil_unicode_to_utf8(const void *zUnicode){ #if defined(_WIN32) || defined(__CYGWIN__) int nByte = WideCharToMultiByte(CP_UTF8, 0, zUnicode, -1, 0, 0, 0, 0); - char *zUtf = sqlite3_malloc( nByte ); - if( zUtf==0 ){ - return 0; - } + char *zUtf = fossil_malloc( nByte ); WideCharToMultiByte(CP_UTF8, 0, zUnicode, -1, zUtf, nByte, 0, 0); return zUtf; #else - return fossil_strdup(zUnicode); /* TODO: implement for unix */ + static Stmt q; + char *zUtf8; + db_static_prepare(&q, "SELECT :utf8"); + db_bind_text16(&q, ":utf8", zUnicode); + db_step(&q); + zUtf8 = fossil_strdup(db_column_text(&q, 0)); + db_reset(&q); + return zUtf8; #endif } /* ** Translate UTF-8 to unicode for use in system calls. Return a pointer to the @@ -73,31 +77,25 @@ ** used to store the returned pointer when done. */ void *fossil_utf8_to_unicode(const char *zUtf8){ #if defined(_WIN32) || defined(__CYGWIN__) int nByte = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, 0, 0); - wchar_t *zUnicode = sqlite3_malloc( nByte * 2 ); - if( zUnicode==0 ){ - return 0; - } + wchar_t *zUnicode = fossil_malloc( nByte * 2 ); MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode, nByte); return zUnicode; #else + assert( 0 ); /* Never used in unix */ return fossil_strdup(zUtf8); /* TODO: implement for unix */ #endif } /* ** Deallocate any memory that was previously allocated by ** fossil_unicode_to_utf8(). */ void fossil_unicode_free(void *pOld){ -#if defined(_WIN32) || defined(__CYGWIN__) - sqlite3_free(pOld); -#else fossil_free(pOld); -#endif } #if defined(__APPLE__) && !defined(WITHOUT_ICONV) # include <iconv.h> #endif @@ -129,11 +127,11 @@ pUtf = qUtf = zUtf; while( *pUtf ) { if( *pUtf == (char)0xef ){ wchar_t c = ((pUtf[1]&0x3f)<<6)|(pUtf[2]&0x3f); /* Only really convert it when the resulting char is in range. */ - if ( c && ((c < ' ') || wcschr(L"\"*:<>?|", c)) ){ + if( c && ((c < ' ') || wcschr(L"\"*:<>?|", c)) ){ *qUtf++ = c; pUtf+=3; continue; } } *qUtf++ = *pUtf++; } @@ -179,11 +177,12 @@ ** Call fossil_filename_free() to deallocate any memory used to store the ** returned pointer when done. ** ** On Windows, characters in the range U+0001 to U+0031 and the ** characters '"', '*', ':', '<', '>', '?' and '|' are invalid -** to be used. Therefore, translate those to characters in the +** to be used, except in the 'extended path' prefix ('?') and +** as drive specifier (':'). Therefore, translate those to characters ** in the range U+F001 - U+F07F (private use area), so those ** characters never arrive in any Windows API. The filenames might ** look strange in Windows explorer, but in the cygwin shell ** everything looks as expected. ** @@ -191,24 +190,62 @@ ** */ void *fossil_utf8_to_filename(const char *zUtf8){ #ifdef _WIN32 int nChar = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, 0, 0); - wchar_t *zUnicode = sqlite3_malloc( nChar * 2 ); + /* Overallocate 6 chars, making some room for extended paths */ + wchar_t *zUnicode = sqlite3_malloc( (nChar+6) * sizeof(wchar_t) ); wchar_t *wUnicode = zUnicode; if( zUnicode==0 ){ return 0; } MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode, nChar); - /* If path starts with "<drive>:/" or "<drive>:\", don't translate the ':' */ + /* + ** If path starts with "//?/" or "\\?\" (extended path), translate + ** any slashes to backslashes but leave the '?' intact + */ + if( (zUtf8[0]=='\\' || zUtf8[0]=='/') && (zUtf8[1]=='\\' || zUtf8[1]=='/') + && zUtf8[2]=='?' && (zUtf8[3]=='\\' || zUtf8[3]=='/')) { + wUnicode[0] = wUnicode[1] = wUnicode[3] = '\\'; + zUtf8 += 4; + wUnicode += 4; + } + /* + ** If there is no "\\?\" prefix but there is a drive or UNC + ** path prefix and the path is larger than MAX_PATH chars, + ** no Win32 API function can handle that unless it is + ** prefixed with the extended path prefix. See: + ** <http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath> + **/ if( fossil_isalpha(zUtf8[0]) && zUtf8[1]==':' - && (zUtf8[2]=='\\' || zUtf8[2]=='/')) { - zUnicode[2] = '\\'; + && (zUtf8[2]=='\\' || zUtf8[2]=='/') ){ + if( wUnicode==zUnicode && nChar>MAX_PATH){ + memmove(wUnicode+4, wUnicode, nChar*sizeof(wchar_t)); + memcpy(wUnicode, L"\\\\?\\", 4*sizeof(wchar_t)); + wUnicode += 4; + } + /* + ** If (remainder of) path starts with "<drive>:/" or "<drive>:\", + ** leave the ':' intact but translate the backslash to a slash. + */ + wUnicode[2] = '\\'; wUnicode += 3; + }else if( wUnicode==zUnicode && nChar>MAX_PATH + && (zUtf8[0]=='\\' || zUtf8[0]=='/') + && (zUtf8[1]=='\\' || zUtf8[1]=='/') && zUtf8[2]!='?'){ + memmove(wUnicode+6, wUnicode, nChar*sizeof(wchar_t)); + memcpy(wUnicode, L"\\\\?\\UNC", 7*sizeof(wchar_t)); + wUnicode += 7; } + /* + ** In the remainder of the path, translate invalid characters to + ** characters in the Unicode private use area. This is what makes + ** Win32 fossil.exe work well in a Cygwin environment even when a + ** filename contains characters which are invalid for Win32. + */ while( *wUnicode != '\0' ){ - if ( (*wUnicode < ' ') || wcschr(L"\"*:<>?|", *wUnicode) ){ + if( (*wUnicode < ' ') || wcschr(L"\"*:<>?|", *wUnicode) ){ *wUnicode |= 0xF000; }else if( *wUnicode == '/' ){ *wUnicode = '\\'; } ++wUnicode; @@ -272,10 +309,11 @@ int fossil_utf8_to_console(const char *zUtf8, int nByte, int toStdErr){ #ifdef _WIN32 int nChar, written = 0; wchar_t *zUnicode; /* Unicode version of zUtf8 */ DWORD dummy; + Blob blob; static int istty[2] = { -1, -1 }; if( istty[toStdErr] == -1 ){ istty[toStdErr] = _isatty(toStdErr + 1) != 0; } @@ -282,16 +320,24 @@ if( !istty[toStdErr] ){ /* stdout/stderr is not a console. */ return -1; } - nChar = MultiByteToWideChar(CP_UTF8, 0, zUtf8, nByte, NULL, 0); + /* If blob to be written to the Windows console is not + * UTF-8, convert it to UTF-8 first. + */ + blob_init(&blob, zUtf8, nByte); + blob_to_utf8_no_bom(&blob, 1); + nChar = MultiByteToWideChar(CP_UTF8, 0, blob_buffer(&blob), + blob_size(&blob), NULL, 0); zUnicode = malloc( (nChar + 1) *sizeof(zUnicode[0]) ); if( zUnicode==0 ){ return 0; } - nChar = MultiByteToWideChar(CP_UTF8, 0, zUtf8, nByte, zUnicode, nChar); + nChar = MultiByteToWideChar(CP_UTF8, 0, blob_buffer(&blob), + blob_size(&blob), zUnicode, nChar); + blob_reset(&blob); /* Split WriteConsoleW call into multiple chunks, if necessary. See: * <https://connect.microsoft.com/VisualStudio/feedback/details/635230> */ while( written < nChar ){ int size = nChar-written; if( size > 26000 ) size = 26000; Index: src/util.c ================================================================== --- src/util.c +++ src/util.c @@ -78,10 +78,19 @@ free(zNewCmd); #else /* On unix, evaluate the command directly. */ if( g.fSystemTrace ) fprintf(stderr, "SYSTEM: %s\n", zOrigCmd); + + /* Unix systems should never shell-out while processing an HTTP request, + ** either via CGI, SCGI, or direct HTTP. The following assert verifies + ** this. And the following assert proves that Fossil is not vulnerable + ** to the ShellShock or BashDoor bug. + */ + assert( g.cgiOutput==0 ); + + /* The regular system() call works to get a shell on unix */ rc = system(zOrigCmd); #endif return rc; } @@ -295,11 +304,11 @@ */ int fossil_timer_is_active( int timerId ){ if(timerId<1 || timerId>FOSSIL_TIMER_COUNT){ return 0; }else{ - int const rc = fossilTimerList[timerId-1].id; + const int rc = fossilTimerList[timerId-1].id; assert(!rc || (rc == timerId)); return fossilTimerList[timerId-1].id; } } @@ -312,5 +321,25 @@ return 1; #else return fcntl(fd, F_GETFL)!=(-1) || errno!=EBADF; #endif } + +/* +** Returns TRUE if zSym is exactly UUID_SIZE bytes long and contains +** only lower-case ASCII hexadecimal values. +*/ +int fossil_is_uuid(const char *zSym){ + return zSym + && (UUID_SIZE==strlen(zSym)) + && validate16(zSym, UUID_SIZE); +} + +/* +** Return true if the input string is NULL or all whitespace. +** Return false if the input string contains text. +*/ +int fossil_all_whitespace(const char *z){ + if( z==0 ) return 1; + while( fossil_isspace(z[0]) ){ z++; } + return z[0]==0; +} Index: src/verify.c ================================================================== --- src/verify.c +++ src/verify.c @@ -63,11 +63,11 @@ */ static Bag toVerify; static int inFinalVerify = 0; /* -** This routine is called just prior to each commit operation. +** This routine is called just prior to each commit operation. ** ** Invoke verify_rid() on every record that has been added or modified ** in the repository, in order to make sure that the repository is sane. */ static int verify_at_commit(void){ @@ -84,11 +84,11 @@ return 0; } /* ** Arrange to verify a particular record prior to committing. -** +** ** If the record rid is less than 1, then just initialize the ** verification system but do not record anything as needing ** verification. */ void verify_before_commit(int rid){ Index: src/vfile.c ================================================================== --- src/vfile.c +++ src/vfile.c @@ -45,18 +45,18 @@ ** does not exist, then return 0. ** ** For this routine, the UUID must be exact. For a match against ** user input with mixed case, use resolve_uuid(). ** -** If the UUID is not found and phantomize is 1 or 2, then attempt to +** If the UUID is not found and phantomize is 1 or 2, then attempt to ** create a phantom record. A private phantom is created for 2 and ** a public phantom is created for 1. */ int uuid_to_rid(const char *zUuid, int phantomize){ int rid, sz; char z[UUID_SIZE+1]; - + sz = strlen(zUuid); if( sz!=UUID_SIZE || !validate16(zUuid, sz) ){ return 0; } memcpy(z, zUuid, UUID_SIZE+1); @@ -68,34 +68,36 @@ return rid; } /* -** Load a vfile from a record ID. +** Load a vfile from a record ID. Return the number of files with +** missing content. */ -void load_vfile_from_rid(int vid){ - int rid, size; +int load_vfile_from_rid(int vid){ + int rid, size, nMissing; Stmt ins, ridq; Manifest *p; ManifestFile *pFile; if( db_exists("SELECT 1 FROM vfile WHERE vid=%d", vid) ){ - return; + return 0; } db_begin_transaction(); p = manifest_get(vid, CFTYPE_MANIFEST, 0); if( p==0 ) { db_end_transaction(1); - return; + return 0; } db_prepare(&ins, "INSERT INTO vfile(vid,isexe,islink,rid,mrid,pathname) " " VALUES(:vid,:isexe,:islink,:id,:id,:name)"); db_prepare(&ridq, "SELECT rid,size FROM blob WHERE uuid=:uuid"); db_bind_int(&ins, ":vid", vid); manifest_file_rewind(p); + nMissing = 0; while( (pFile = manifest_file_next(p,0))!=0 ){ if( pFile->zUuid==0 || uuid_is_shunned(pFile->zUuid) ) continue; db_bind_text(&ridq, ":uuid", pFile->zUuid); if( db_step(&ridq)==SQLITE_ROW ){ rid = db_column_int(&ridq, 0); @@ -105,10 +107,11 @@ size = 0; } db_reset(&ridq); if( rid==0 || size<0 ){ fossil_warning("content missing for %s", pFile->zName); + nMissing++; continue; } db_bind_int(&ins, ":isexe", ( manifest_file_mperm(pFile)==PERM_EXE )); db_bind_int(&ins, ":id", rid); db_bind_text(&ins, ":name", pFile->zName); @@ -118,10 +121,11 @@ } db_finalize(&ridq); db_finalize(&ins); manifest_destroy(p); db_end_transaction(0); + return nMissing; } #if INTERFACE /* ** The cksigFlags parameter to vfile_check_signature() is an OR-ed @@ -140,11 +144,11 @@ ** the file has changed due to a merge. 3 means the file was added ** by a merge. ** ** If VFILE.DELETED is true or if VFILE.RID is zero, then the file was either ** removed from configuration management via "fossil rm" or added via -** "fossil add", respectively, and in both cases we always know that +** "fossil add", respectively, and in both cases we always know that ** the file has changed without having the check the size, mtime, ** or on-disk content. ** ** If the size of the file has changed, then we always know that the file ** changed without having to look at the mtime or on-disk content. @@ -185,12 +189,12 @@ zName = db_column_text(&q, 1); rid = db_column_int(&q, 2); isDeleted = db_column_int(&q, 3); oldChnged = chnged = db_column_int(&q, 4); oldMtime = db_column_int64(&q, 7); - currentSize = file_wd_size(zName); origSize = db_column_int64(&q, 6); + currentSize = file_wd_size(zName); currentMtime = file_wd_mtime(0); if( chnged==0 && (isDeleted || rid==0) ){ /* "fossil rm" or "fossil add" always change the file */ chnged = 1; }else if( !file_wd_isfile_or_link(0) && currentSize>=0 ){ @@ -315,12 +319,12 @@ } if( verbose ) fossil_print("%s\n", &zName[nRepos]); if( file_wd_isdir(zName) == 1 ){ /*TODO(dchest): remove directories? */ fossil_fatal("%s is directory, cannot overwrite\n", zName); - } - if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){ + } + if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(0)) ){ file_delete(zName); } if( isLink ){ symlink_create(blob_str(&content), zName); }else{ @@ -392,14 +396,14 @@ "merge", "original", "output", }; int i, j, n; - - if( strglob("ci-comment-????????????.txt", zName) ) return 1; + + if( sqlite3_strglob("ci-comment-????????????.txt", zName)==0 ) return 1; for(; zName[0]!=0; zName++){ - if( zName[0]=='/' && strglob("/ci-comment-????????????.txt", zName) ){ + if( zName[0]=='/' && sqlite3_strglob("/ci-comment-????????????.txt", zName)==0 ){ return 1; } if( zName[0]!='-' ) continue; for(i=0; i<sizeof(azTemp)/sizeof(azTemp[0]); i++){ n = (int)strlen(azTemp[i]); @@ -407,11 +411,11 @@ if( zName[n+1]==0 ) return 1; if( zName[n+1]=='-' ){ for(j=n+2; zName[j] && fossil_isdigit(zName[j]); j++){} if( zName[j]==0 ) return 1; } - } + } } return 0; } #if INTERFACE @@ -486,15 +490,25 @@ blob_appendf(pPath, "/%s", zUtf8); zPath = blob_str(pPath); if( glob_match(pIgnore1, &zPath[nPrefix+1]) || glob_match(pIgnore2, &zPath[nPrefix+1]) ){ /* do nothing */ +#ifdef _DIRENT_HAVE_D_TYPE + }else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK) + ? (file_wd_isdir(zPath)==1) : (pEntry->d_type==DT_DIR) ){ +#else }else if( file_wd_isdir(zPath)==1 ){ +#endif if( !vfile_top_of_checkout(zPath) ){ vfile_scan(pPath, nPrefix, scanFlags, pIgnore1, pIgnore2); } +#ifdef _DIRENT_HAVE_D_TYPE + }else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK) + ? (file_wd_isfile_or_link(zPath)) : (pEntry->d_type==DT_REG) ){ +#else }else if( file_wd_isfile_or_link(zPath) ){ +#endif if( (scanFlags & SCAN_TEMP)==0 || is_temporary_file(zUtf8) ){ db_bind_text(&ins, ":file", &zPath[nPrefix+1]); db_step(&ins); db_reset(&ins); } @@ -593,11 +607,16 @@ zPath = blob_str(pPath); if( glob_match(pIgnore1, &zPath[nPrefix+1]) || glob_match(pIgnore2, &zPath[nPrefix+1]) || glob_match(pIgnore3, &zPath[nPrefix+1]) ){ /* do nothing */ +#ifdef _DIRENT_HAVE_D_TYPE + }else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK) + ? (file_wd_isdir(zPath)==1) : (pEntry->d_type==DT_DIR) ){ +#else }else if( file_wd_isdir(zPath)==1 ){ +#endif if( (scanFlags & SCAN_NESTED) || !vfile_top_of_checkout(zPath) ){ char *zSavePath = mprintf("%s", zPath); int count = vfile_dir_scan(pPath, nPrefix, scanFlags, pIgnore1, pIgnore2, pIgnore3); db_bind_text(&ins, ":file", &zSavePath[nPrefix+1]); @@ -605,11 +624,16 @@ db_step(&ins); db_reset(&ins); fossil_free(zSavePath); result += count; /* found X normal files? */ } +#ifdef _DIRENT_HAVE_D_TYPE + }else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK) + ? (file_wd_isfile_or_link(zPath)) : (pEntry->d_type==DT_REG) ){ +#else }else if( file_wd_isfile_or_link(zPath) ){ +#endif db_bind_text(&upd, ":file", zOrigPath); db_step(&upd); db_reset(&upd); result++; /* found 1 normal file */ } @@ -654,11 +678,11 @@ FILE *in; Stmt q; char zBuf[4096]; db_must_be_within_tree(); - db_prepare(&q, + db_prepare(&q, "SELECT %Q || pathname, pathname, origname, is_selected(id), rid" " FROM vfile" " WHERE (NOT deleted OR NOT is_selected(id)) AND vid=%d" " ORDER BY if_selected(id, pathname, origname) /*scan*/", g.zLocalRoot, vid @@ -673,11 +697,11 @@ md5sum_step_text(zName, -1); if( file_wd_islink(zFullpath) ){ /* Instead of file content, use link destination path */ Blob pathBuf; - sqlite3_snprintf(sizeof(zBuf), zBuf, " %ld\n", + sqlite3_snprintf(sizeof(zBuf), zBuf, " %ld\n", blob_read_link(&pathBuf, zFullpath)); md5sum_step_text(zBuf, -1); md5sum_step_text(blob_str(&pathBuf), -1); blob_reset(&pathBuf); }else{ @@ -743,13 +767,13 @@ void vfile_compare_repository_to_disk(int vid){ int rc; Stmt q; Blob disk, repo; char *zOut; - + db_must_be_within_tree(); - db_prepare(&q, + db_prepare(&q, "SELECT %Q || pathname, pathname, rid FROM vfile" " WHERE NOT deleted AND vid=%d AND is_selected(id)" " ORDER BY if_selected(id, pathname, origname) /*scan*/", g.zLocalRoot, vid ); @@ -809,11 +833,11 @@ Blob file; Stmt q; char zBuf[100]; db_must_be_within_tree(); - + db_prepare(&q, "SELECT pathname, origname, rid, is_selected(id)" " FROM vfile" " WHERE (NOT deleted OR NOT is_selected(id))" " AND rid>0 AND vid=%d" " ORDER BY if_selected(id,pathname,origname) /*scan*/", @@ -847,11 +871,11 @@ ** ** If pManOut is not NULL then fill it with the checksum found in the ** "R" card near the end of the manifest. ** ** In a well-formed manifest, the two checksums computed here, pOut and -** pManOut, should be identical. +** pManOut, should be identical. */ void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){ int fid; Blob file; Blob err; Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -16,22 +16,22 @@ ** ******************************************************************************* ** ** This file contains code to do formatting of wiki text. */ +#include "config.h" #include <assert.h> #include <ctype.h> -#include "config.h" #include "wiki.h" /* ** Return true if the input string is a well-formed wiki page name. ** ** Well-formed wiki page names do not begin or end with whitespace, ** and do not contain tabs or other control characters and do not ** contain more than a single space character in a row. Well-formed -** names must be between 3 and 100 characters in length, inclusive. +** names must be between 1 and 100 characters in length, inclusive. */ int wiki_name_is_wellformed(const unsigned char *z){ int i; if( z[0]<=0x20 ){ return 0; @@ -39,11 +39,11 @@ for(i=1; z[i]; i++){ if( z[i]<0x20 ) return 0; if( z[i]==0x20 && z[i-1]==0x20 ) return 0; } if( z[i-1]==' ' ) return 0; - if( i<3 || i>100 ) return 0; + if( i<1 || i>100 ) return 0; return 1; } /* ** Output rules for well-formed wiki pages @@ -52,11 +52,11 @@ @ <ul> @ <li> Must not begin or end with a space.</li> @ <li> Must not contain any control characters, including tab or @ newline.</li> @ <li> Must not have two or more spaces in a row internally.</li> - @ <li> Must be between 3 and 100 characters in length.</li> + @ <li> Must be between 1 and 100 characters in length.</li> @ </ul> } /* ** Check a wiki name. If it is not well-formed, then issue an error @@ -134,22 +134,28 @@ } return "text/x-fossil-wiki"; } /* -** Render wiki text according to its mimetype +** Render wiki text according to its mimetype. +** +** text/x-fossil-wiki Fossil wiki +** text/x-markdown Markdown +** anything else... Plain text */ void wiki_render_by_mimetype(Blob *pWiki, const char *zMimetype){ if( zMimetype==0 || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ wiki_convert(pWiki, 0, 0); }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ Blob title = BLOB_INITIALIZER; Blob tail = BLOB_INITIALIZER; markdown_to_html(pWiki, &title, &tail); +#if 0 if( blob_size(&title)>0 ){ @ <h1>%s(blob_str(&title))</h1> } +#endif @ %s(blob_str(&tail)) blob_reset(&title); blob_reset(&tail); }else{ @ <pre> @@ -156,10 +162,148 @@ @ %h(blob_str(pWiki)) @ </pre> } } +/* +** WEBPAGE: md_rules +** +** Show a summary of the Markdown wiki formatting rules. +*/ +void markdown_rules_page(void){ + Blob x; + int fTxt = P("txt")!=0; + style_header("Markdown Formatting Rules"); + if( fTxt ){ + style_submenu_element("Formatted", "Formatted", "%R/md_rules"); + }else{ + style_submenu_element("Plain-Text", "Plain-Text", "%R/md_rules?txt=1"); + } + blob_init(&x, builtin_text("markdown.md"), -1); + wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown"); + blob_reset(&x); + style_footer(); +} + +/* +** Returns non-zero if moderation is required for wiki changes and wiki +** attachments. +*/ +int wiki_need_moderation( + int localUser /* Are we being called for a local interactive user? */ +){ + /* + ** If the FOSSIL_FORCE_WIKI_MODERATION variable is set, *ALL* changes for + ** wiki pages will be required to go through moderation (even those performed + ** by the local interactive user via the command line). This can be useful + ** for local (or remote) testing of the moderation subsystem and its impact + ** on the contents and status of wiki pages. + */ + if( fossil_getenv("FOSSIL_FORCE_WIKI_MODERATION")!=0 ){ + return 1; + } + if( localUser ){ + return 0; + } + return g.perm.ModWiki==0 && db_get_boolean("modreq-wiki",0)==1; +} + +/* Standard submenu items for wiki pages */ +#define W_SRCH 0x00001 +#define W_LIST 0x00002 +#define W_HELP 0x00004 +#define W_NEW 0x00008 +#define W_BLOG 0x00010 +#define W_SANDBOX 0x00020 +#define W_ALL 0x0001f +#define W_ALL_BUT(x) (W_ALL&~(x)) + +/* +** Add some standard submenu elements for wiki screens. +*/ +static void wiki_standard_submenu(unsigned int ok){ + if( (ok & W_SRCH)!=0 && search_restrict(SRCH_WIKI)!=0 ){ + style_submenu_element("Search","Search","%R/wikisrch"); + } + if( (ok & W_LIST)!=0 ){ + style_submenu_element("List","List","%R/wcontent"); + } + if( (ok & W_HELP)!=0 ){ + style_submenu_element("Help","Help","%R/wikihelp"); + } + if( (ok & W_NEW)!=0 && g.anon.NewWiki ){ + style_submenu_element("New","New","%R/wikinew"); + } +#if 0 + if( (ok & W_BLOG)!=0 +#endif + if( (ok & W_SANDBOX)!=0 ){ + style_submenu_element("Sandbox", "Sandbox", "%R/wiki?name=Sandbox"); + } +} + +/* +** WEBPAGE: wikihelp +** A generic landing page for wiki. +*/ +void wiki_helppage(void){ + login_check_credentials(); + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } + style_header("Wiki Help"); + wiki_standard_submenu(W_ALL_BUT(W_HELP)); + @ <h2>Wiki Links</h2> + @ <ul> + { char *zWikiHomePageName = db_get("index-page",0); + if( zWikiHomePageName ){ + @ <li> %z(href("%R%s",zWikiHomePageName)) + @ %h(zWikiHomePageName)</a> wiki home page.</li> + } + } + { char *zHomePageName = db_get("project-name",0); + if( zHomePageName ){ + @ <li> %z(href("%R/wiki?name=%t",zHomePageName)) + @ %h(zHomePageName)</a> project home page.</li> + } + } + @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> + @ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for + @ %z(href("%R/md_rules"))Markdown Wiki</a>.</li> + @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a> + @ to experiment.</li> + if( g.anon.NewWiki ){ + @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li> + if( g.anon.Write ){ + @ <li> Create a %z(href("%R/technoteedit"))new tech-note</a>.</li> + } + } + @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> + @ available on this server.</li> + if( g.anon.ModWiki ){ + @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li> + } + if( search_restrict(SRCH_WIKI)!=0 ){ + @ <li> %z(href("%R/wikisrch"))Search</a> for wiki pages containing key + @ words</li> + } + @ </ul> + style_footer(); + return; +} + +/* +** WEBPAGE: wikisrch +** Usage: /wikisrch?s=PATTERN +** +** Full-text search of all current wiki text +*/ +void wiki_srchpage(void){ + login_check_credentials(); + style_header("Wiki Search"); + wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX); + search_screen(SRCH_WIKI, 0); + style_footer(); +} /* ** WEBPAGE: wiki ** URL: /wiki?name=PAGENAME */ @@ -166,72 +310,46 @@ void wiki_page(void){ char *zTag; int rid = 0; int isSandbox; char *zUuid; + unsigned submenuFlags = W_ALL; Blob wiki; Manifest *pWiki = 0; const char *zPageName; const char *zMimetype = 0; char *zBody = mprintf("%s","<i>Empty Page</i>"); login_check_credentials(); - if( !g.perm.RdWiki ){ login_needed(); return; } + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } zPageName = P("name"); if( zPageName==0 ){ - style_header("Wiki"); - @ <ul> - { char *zWikiHomePageName = db_get("index-page",0); - if( zWikiHomePageName ){ - @ <li> %z(href("%R%s",zWikiHomePageName)) - @ %h(zWikiHomePageName)</a> wiki home page.</li> - } - } - { char *zHomePageName = db_get("project-name",0); - if( zHomePageName ){ - @ <li> %z(href("%R/wiki?name=%t",zHomePageName)) - @ %h(zHomePageName)</a> project home page.</li> - } - } - @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> - @ <li> %z(href("%R/wiki_rules"))Formatting rules</a> for wiki.</li> - @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a> - @ to experiment.</li> - if( g.perm.NewWiki ){ - @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li> - if( g.perm.Write ){ - @ <li> Create a %z(href("%R/eventedit"))new event</a>.</li> - } - } - @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> - @ available on this server.</li> - if( g.perm.ModWiki ){ - @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li> - } - @ <li> - form_begin(0, "%R/wfind"); - @ <div>Search wiki titles: <input type="text" name="title"/> - @   <input type="submit" /></div></form> - @ </li> - @ </ul> - style_footer(); + if( search_restrict(SRCH_WIKI)!=0 ){ + wiki_srchpage(); + }else{ + wiki_helppage(); + } return; } if( check_name(zPageName) ) return; isSandbox = is_sandbox(zPageName); if( isSandbox ){ + submenuFlags &= ~W_SANDBOX; zBody = db_get("sandbox",zBody); zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); rid = 0; }else{ - zTag = mprintf("wiki-%s", zPageName); - rid = db_int(0, - "SELECT rid FROM tagxref" - " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" - " ORDER BY mtime DESC", zTag - ); - free(zTag); + const char *zUuid = P("id"); + if( zUuid==0 || (rid = symbolic_name_to_rid(zUuid,"w"))==0 ){ + zTag = mprintf("wiki-%s", zPageName); + rid = db_int(0, + "SELECT rid FROM tagxref" + " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" + " ORDER BY mtime DESC", zTag + ); + free(zTag); + } pWiki = manifest_get(rid, CFTYPE_WIKI, 0); if( pWiki ){ zBody = pWiki->zWiki; zMimetype = pWiki->zMimetype; } @@ -241,13 +359,13 @@ if( rid ){ style_submenu_element("Diff", "Last change", "%R/wdiff?name=%T&a=%d", zPageName, rid); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); style_submenu_element("Details", "Details", - "%R/info/%S", zUuid); + "%R/info/%s", zUuid); } - if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){ + if( (rid && g.anon.WrWiki) || (!rid && g.anon.NewWiki) ){ if( db_get_boolean("wysiwyg-wiki", 0) ){ style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T&wysiwyg=1", g.zTop, zPageName); }else{ @@ -254,27 +372,28 @@ style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T", g.zTop, zPageName); } } - if( rid && g.perm.ApndWiki && g.perm.Attach ){ + if( rid && g.anon.ApndWiki && g.anon.Attach ){ style_submenu_element("Attach", "Add An Attachment", "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", g.zTop, zPageName, g.zTop, zPageName); } - if( rid && g.perm.ApndWiki ){ - style_submenu_element("Append", "Add A Comment", + if( rid && g.anon.ApndWiki ){ + style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T&mimetype=%s", g.zTop, zPageName, zMimetype); } if( g.perm.Hyperlink ){ style_submenu_element("History", "History", "%s/whistory?name=%T", g.zTop, zPageName); } } - style_set_current_page("%s?name=%T", g.zPath, zPageName); - style_header(zPageName); + style_set_current_page("%T?name=%T", g.zPath, zPageName); + style_header("%s", zPageName); + wiki_standard_submenu(submenuFlags); blob_init(&wiki, zBody, -1); wiki_render_by_mimetype(&wiki, zMimetype); blob_reset(&wiki); attachment_list(zPageName, "<hr /><h2>Attachments:</h2><ul>"); manifest_destroy(pWiki); @@ -282,41 +401,41 @@ } /* ** Write a wiki artifact into the repository */ -static void wiki_put(Blob *pWiki, int parent){ +static void wiki_put(Blob *pWiki, int parent, int needMod){ int nrid; - if( g.perm.ModWiki || db_get_boolean("modreq-wiki",0)==0 ){ + if( !needMod ){ nrid = content_put_ex(pWiki, 0, 0, 0, 0); if( parent) content_deltify(parent, nrid, 0); }else{ nrid = content_put_ex(pWiki, 0, 0, 0, 1); moderation_table_create(); db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid); } db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid); - manifest_crosslink(nrid, pWiki); + manifest_crosslink(nrid, pWiki, MC_NONE); } /* ** Formal names and common names for the various wiki styles. */ -static const char *azStyles[] = { +static const char *const azStyles[] = { "text/x-fossil-wiki", "Fossil Wiki", "text/x-markdown", "Markdown", "text/plain", "Plain Text" }; /* ** Output a selection box from which the user can select the ** wiki mimetype. */ -static void mimetype_option_menu(const char *zMimetype){ +void mimetype_option_menu(const char *zMimetype){ unsigned i; - @ Markup style: <select name="mimetype" size="1"> + @ <select name="mimetype" size="1"> for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){ if( fossil_strcmp(zMimetype,azStyles[i])==0 ){ @ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option> }else{ @ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option> @@ -372,27 +491,27 @@ zPageName = PD("name",""); if( check_name(zPageName) ) return; isSandbox = is_sandbox(zPageName); if( isSandbox ){ if( !g.perm.WrWiki ){ - login_needed(); + login_needed(g.anon.WrWiki); return; } if( zBody==0 ){ zBody = db_get("sandbox",""); zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); } }else{ zTag = mprintf("wiki-%s", zPageName); - rid = db_int(0, + rid = db_int(0, "SELECT rid FROM tagxref" " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" " ORDER BY mtime DESC", zTag ); free(zTag); if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){ - login_needed(); + login_needed(rid ? g.anon.WrWiki : g.anon.NewWiki); return; } if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){ zBody = pWiki->zWiki; zMimetype = pWiki->zMimetype; @@ -420,18 +539,18 @@ if( rid ){ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); blob_appendf(&wiki, "P %s\n", zUuid); free(zUuid); } - if( g.zLogin ){ - blob_appendf(&wiki, "U %F\n", g.zLogin); + if( !login_is_nobody() ){ + blob_appendf(&wiki, "U %F\n", login_name()); } blob_appendf(&wiki, "W %d\n%s\n", strlen(zBody), zBody); md5sum_blob(&wiki, &cksum); blob_appendf(&wiki, "Z %b\n", &cksum); blob_reset(&cksum); - wiki_put(&wiki, 0); + wiki_put(&wiki, 0, wiki_need_moderation(0)); } db_end_transaction(0); cgi_redirectf("wiki?name=%T", zPageName); } if( P("cancel")!=0 ){ @@ -439,11 +558,11 @@ return; } if( zBody==0 ){ zBody = mprintf("<i>Empty Page</i>"); } - style_set_current_page("%s?name=%T", g.zPath, zPageName); + style_set_current_page("%T?name=%T", g.zPath, zPageName); style_header("Edit: %s", zPageName); if( !goodCaptcha ){ @ <p class="generalError">Error: Incorrect security code.</p> } blob_zero(&wiki); @@ -460,13 +579,13 @@ if( n<20 ) n = 20; if( n>30 ) n = 30; if( !isWysiwyg ){ /* Traditional markup-only editing */ form_begin(0, "%R/wikiedit"); - @ <div> + @ <div>Markup style: mimetype_option_menu(zMimetype); - @ <br /><textarea name="w" class="wikiedit" cols="80" + @ <br /><textarea name="w" class="wikiedit" cols="80" @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> @ <br /> if( db_get_boolean("wysiwyg-wiki", 0) ){ @ <input type="submit" name="edit-wysiwyg" value="Wysiwyg Editor" @ onclick='return confirm("Switching to WYSIWYG-mode\nwill erase your markup\nedits. Continue?")' /> @@ -512,13 +631,13 @@ void wikinew_page(void){ const char *zName; const char *zMimetype; login_check_credentials(); if( !g.perm.NewWiki ){ - login_needed(); + login_needed(g.anon.NewWiki); return; - } + } zName = PD("name",""); zMimetype = wiki_filter_mimetypes(P("mimetype")); if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){ if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 && db_get_boolean("wysiwyg-wiki", 0) @@ -527,15 +646,17 @@ }else{ cgi_redirectf("wikiedit?name=%T&mimetype=%s", zName, zMimetype); } } style_header("Create A New Wiki Page"); + wiki_standard_submenu(W_ALL_BUT(W_NEW)); @ <p>Rules for wiki page names:</p> well_formed_wiki_name_rules(); form_begin(0, "%R/wikinew"); @ <p>Name of new wiki page: @ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br /> + @ Markup style: mimetype_option_menu("text/x-fossil-wiki"); @ <br /><input type="submit" value="Create" /> @ </p></form> if( zName[0] ){ @ <p><span class="wikiError"> @@ -557,26 +678,26 @@ zDate = db_text(0, "SELECT datetime('now')"); zRemark = PD("r",""); zUser = PD("u",g.zLogin); if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ zId = db_text(0, "SELECT lower(hex(randomblob(8)))"); - blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>On %s UTC %h", - zId, zDate, g.zLogin); - if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){ + blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>On %s UTC %h", + zId, zDate, login_name()); + if( zUser[0] && fossil_strcmp(zUser,login_name()) ){ blob_appendf(p, " (claiming to be %h)", zUser); } blob_appendf(p, " added:</i><br />\n%s</div id=\"%s\">", zRemark, zId); }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ - blob_appendf(p, "\n\n------\n*On %s UTC %h", zDate, g.zLogin); - if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){ + blob_appendf(p, "\n\n------\n*On %s UTC %h", zDate, login_name()); + if( zUser[0] && fossil_strcmp(zUser,login_name()) ){ blob_appendf(p, " (claiming to be %h)", zUser); } blob_appendf(p, " added:*\n\n%s\n", zRemark); }else{ blob_appendf(p, "\n\n------------------------------------------------\n" - "On %s UTC %s", zDate, g.zLogin); - if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){ + "On %s UTC %s", zDate, login_name()); + if( zUser[0] && fossil_strcmp(zUser,login_name()) ){ blob_appendf(p, " (claiming to be %s)", zUser); } blob_appendf(p, " added:\n\n%s\n", zRemark); } fossil_free(zDate); @@ -601,11 +722,11 @@ zMimetype = wiki_filter_mimetypes(P("mimetype")); if( check_name(zPageName) ) return; isSandbox = is_sandbox(zPageName); if( !isSandbox ){ zTag = mprintf("wiki-%s", zPageName); - rid = db_int(0, + rid = db_int(0, "SELECT rid FROM tagxref" " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" " ORDER BY mtime DESC", zTag ); free(zTag); @@ -613,11 +734,11 @@ fossil_redirect_home(); return; } } if( !g.perm.ApndWiki ){ - login_needed(); + login_needed(g.anon.ApndWiki); return; } if( P("submit")!=0 && P("r")!=0 && P("u")!=0 && (goodCaptcha = captcha_is_correct()) ){ @@ -627,11 +748,11 @@ Blob wiki; Manifest *pWiki = 0; blob_zero(&body); if( isSandbox ){ - blob_appendf(&body, db_get("sandbox","")); + blob_append(&body, db_get("sandbox",""), -1); appendRemark(&body, zMimetype); db_set("sandbox", blob_str(&body), 0); }else{ login_verify_csrf_secret(); pWiki = manifest_get(rid, CFTYPE_WIKI, 0); @@ -650,28 +771,28 @@ if( rid ){ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); blob_appendf(&wiki, "P %s\n", zUuid); free(zUuid); } - if( g.zLogin ){ - blob_appendf(&wiki, "U %F\n", g.zLogin); + if( !login_is_nobody() ){ + blob_appendf(&wiki, "U %F\n", login_name()); } appendRemark(&body, zMimetype); blob_appendf(&wiki, "W %d\n%s\n", blob_size(&body), blob_str(&body)); md5sum_blob(&wiki, &cksum); blob_appendf(&wiki, "Z %b\n", &cksum); blob_reset(&cksum); - wiki_put(&wiki, rid); + wiki_put(&wiki, rid, wiki_need_moderation(0)); db_end_transaction(0); } cgi_redirectf("wiki?name=%T", zPageName); } if( P("cancel")!=0 ){ cgi_redirectf("wiki?name=%T", zPageName); return; } - style_set_current_page("%s?name=%T", g.zPath, zPageName); + style_set_current_page("%T?name=%T", g.zPath, zPageName); style_header("Append Comment To: %s", zPageName); if( !goodCaptcha ){ @ <p class="generalError">Error: Incorrect security code.</p> } if( P("preview")!=0 ){ @@ -690,11 +811,11 @@ @ <input type="hidden" name="mimetype" value="%h(zMimetype)" /> @ Your Name: @ <input type="text" name="u" size="20" value="%h(zUser)" /><br /> zFormat = mimetype_common_name(zMimetype); @ Comment to append (formatted as %s(zFormat)):<br /> - @ <textarea name="r" class="wikiedit" cols="80" + @ <textarea name="r" class="wikiedit" cols="80" @ rows="10" wrap="virtual">%h(PD("r",""))</textarea> @ <br /> @ <input type="submit" name="preview" value="Preview Your Comment" /> @ <input type="submit" name="submit" value="Append Your Changes" /> @ <input type="submit" name="cancel" value="Cancel" /> @@ -724,31 +845,25 @@ ** ** Show the complete change history for a single wiki page. */ void whistory_page(void){ Stmt q; - char *zTitle; - char *zSQL; const char *zPageName; login_check_credentials(); - if( !g.perm.Hyperlink ){ login_needed(); return; } + if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; } zPageName = PD("name",""); - zTitle = mprintf("History Of %s", zPageName); - style_header(zTitle); - free(zTitle); + style_header("History Of %s", zPageName); - zSQL = mprintf("%s AND event.objid IN " + db_prepare(&q, "%s AND event.objid IN " " (SELECT rid FROM tagxref WHERE tagid=" "(SELECT tagid FROM tag WHERE tagname='wiki-%q')" " UNION SELECT attachid FROM attachment" " WHERE target=%Q)" "ORDER BY mtime DESC", timeline_query_for_www(), zPageName, zPageName); - db_prepare(&q, zSQL); - free(zSQL); zWikiPageName = zPageName; - www_print_timeline(&q, TIMELINE_ARTID, 0, 0, wiki_history_extra); + www_print_timeline(&q, TIMELINE_ARTID, 0, 0, 0, wiki_history_extra); db_finalize(&q); style_footer(); } /* @@ -756,26 +871,23 @@ ** URL: /whistory?name=PAGENAME&a=RID1&b=RID2 ** ** Show the difference between two wiki pages. */ void wdiff_page(void){ - char *zTitle; int rid1, rid2; const char *zPageName; Manifest *pW1, *pW2 = 0; Blob w1, w2, d; u64 diffFlags; login_check_credentials(); rid1 = atoi(PD("a","0")); - if( !g.perm.Hyperlink ){ login_needed(); return; } + if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; } if( rid1==0 ) fossil_redirect_home(); rid2 = atoi(PD("b","0")); zPageName = PD("name",""); - zTitle = mprintf("Changes To %s", zPageName); - style_header(zTitle); - free(zTitle); + style_header("Changes To %s", zPageName); if( rid2==0 ){ rid2 = db_int(0, "SELECT objid FROM event JOIN tagxref ON objid=rid AND tagxref.tagid=" "(SELECT tagid FROM tag WHERE tagname='wiki-%q')" @@ -809,14 +921,15 @@ ** - tagxref (whatever that really is!) ** ** Used by wcontent_page() and the JSON wiki code. */ void wiki_prepare_page_list( Stmt * pStmt ){ - db_prepare(pStmt, + db_prepare(pStmt, "SELECT" " substr(tagname, 6) as name," - " (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC) as tagXref" + " (SELECT value FROM tagxref WHERE tagid=tag.tagid" + " ORDER BY mtime DESC) as tagXref" " FROM tag WHERE tagname GLOB 'wiki-*'" " ORDER BY lower(tagname) /*sort*/" ); } /* @@ -829,17 +942,18 @@ void wcontent_page(void){ Stmt q; int showAll = P("all")!=0; login_check_credentials(); - if( !g.perm.RdWiki ){ login_needed(); return; } + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } style_header("Available Wiki Pages"); if( showAll ){ style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); }else{ style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); } + wiki_standard_submenu(W_ALL_BUT(W_LIST)); @ <ul> wiki_prepare_page_list(&q); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); int size = db_column_int(&q, 1); @@ -860,20 +974,20 @@ ** URL: /wfind?title=TITLE ** List all wiki pages whose titles contain the search text */ void wfind_page(void){ Stmt q; - const char * zTitle; + const char *zTitle; login_check_credentials(); - if( !g.perm.RdWiki ){ login_needed(); return; } + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } zTitle = PD("title","*"); style_header("Wiki Pages Found"); @ <ul> - db_prepare(&q, + db_prepare(&q, "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'" " ORDER BY lower(tagname) /*sort*/" , - zTitle); + zTitle); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); @ <li>%z(href("%R/wiki?name=%T",zName))%h(zName)</a></li> } db_finalize(&q); @@ -891,21 +1005,22 @@ @ <li>Blank lines are paragraph breaks</li> @ <li>Bullets are "*" surrounded by two spaces at the beginning of the @ line.</li> @ <li>Enumeration items are "#" surrounded by two spaces at the beginning of @ a line.</li> - @ <li>Indented pargraphs begin with a tab or two spaces.</li> + @ <li>Indented paragraphs begin with a tab or two spaces.</li> @ <li>Hyperlinks are contained with square brackets: "[target]" or @ "[target|name]".</li> @ <li>Most ordinary HTML works.</li> @ <li><verbatim> and <nowiki>.</li> @ </ol> @ <p>We call the first five rules above "wiki" formatting rules. The @ last two rules are the HTML formatting rule.</p> @ <h2>Formatting Rule Details</h2> @ <ol> - @ <li> <p><span class="wikiruleHead">Paragraphs</span>. Any sequence of one or more blank lines forms + @ <li> <p><span class="wikiruleHead">Paragraphs</span>. + @ Any sequence of one or more blank lines forms @ a paragraph break. Centered or right-justified paragraphs are not @ supported by wiki markup, but you can do these things if you need them @ using HTML.</p></li> @ <li> <p><span class="wikiruleHead">Bullet Lists</span>. @ A bullet list item is a line that begins with a single "*" character @@ -917,21 +1032,22 @@ @ surrounded on both sides by two or more spaces or by a tab. Only a single @ level of enumeration list is supported by wiki. For nested lists or for @ enumerations that count using letters or roman numerials, use HTML.</p></li> @ <li> <p><span class="wikiruleHead">Indented Paragraphs</span>. @ Any paragraph that begins with two or more spaces or a tab and - @ which is not a bullet or enumeration list item is rendered + @ which is not a bullet or enumeration list item is rendered @ indented. Only a single level of indentation is supported by wiki; use @ HTML for deeper indentation.</p></li> @ <li> <p><span class="wikiruleHead">Hyperlinks</span>. @ Text within square brackets ("[...]") becomes a hyperlink. The @ target can be a wiki page name, the artifact ID of a check-in or ticket, @ the name of an image, or a URL. By default, the target is displayed @ as the text of the hyperlink. But you can specify alternative text @ after the target name separated by a "|" character.</p> - @ <p>You can also link to internal anchor names using [#anchor-name], providing - @ you have added the necessary "<a name="anchor-name"></a>" + @ <p>You can also link to internal anchor names using [#anchor-name], + @ providing + @ you have added the necessary "<a name='anchor-name'></a>" @ tag to your wiki page.</p></li> @ <li> <p><span class="wikiruleHead">HTML</span>. @ The following standard HTML elements may be used: show_allowed_wiki_markup(); @ . There are two non-standard elements available: @@ -955,12 +1071,17 @@ ** given by the zPageName parameter. isNew must be true to create ** a new page. If no previous page with the name zPageName exists ** and isNew is false, then this routine throws an error. ** ** The content of the new page is given by the blob pContent. +** +** zMimeType specifies the N-card for the wiki page. If it is 0, +** empty, or "text/x-fossil-wiki" (the default format) then it is +** ignored. */ -int wiki_cmd_commit(char const * zPageName, int isNew, Blob *pContent){ +int wiki_cmd_commit(const char *zPageName, int isNew, Blob *pContent, + const char *zMimeType, int localUser){ Blob wiki; /* Wiki page content */ Blob cksum; /* wiki checksum */ int rid; /* artifact ID of parent page */ char *zDate; /* timestamp */ char *zUuid; /* uuid for rid */ @@ -987,26 +1108,30 @@ blob_zero(&wiki); zDate = date_in_standard_format("now"); blob_appendf(&wiki, "D %s\n", zDate); free(zDate); blob_appendf(&wiki, "L %F\n", zPageName ); + if( zMimeType && *zMimeType + && 0!=fossil_strcmp(zMimeType,"text/x-fossil-wiki") ){ + blob_appendf(&wiki, "N %F\n", zMimeType); + } if( rid ){ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); blob_appendf(&wiki, "P %s\n", zUuid); free(zUuid); } user_select(); - if( g.zLogin ){ - blob_appendf(&wiki, "U %F\n", g.zLogin); + if( !login_is_nobody() ){ + blob_appendf(&wiki, "U %F\n", login_name()); } blob_appendf( &wiki, "W %d\n%s\n", blob_size(pContent), blob_str(pContent) ); md5sum_blob(&wiki, &cksum); blob_appendf(&wiki, "Z %b\n", &cksum); blob_reset(&cksum); db_begin_transaction(); - wiki_put(&wiki, 0); + wiki_put(&wiki, 0, wiki_need_moderation(localUser)); db_end_transaction(0); return 1; } /* @@ -1019,21 +1144,24 @@ ** %fossil wiki export PAGENAME ?FILE? ** ** Sends the latest version of the PAGENAME wiki ** entry to the given file or standard output. ** -** %fossil wiki commit PAGENAME ?FILE? +** %fossil wiki commit PAGENAME ?FILE? [-mimetype TEXT-FORMAT] ** ** Commit changes to a wiki page from FILE or from standard -** input. +** input. The -mimetype (-M) flag specifies the mime type, +** defaulting to the type used by the previous version of +** the page or (for new pages) text/x-fossil-wiki. ** -** %fossil wiki create PAGENAME ?FILE? +** %fossil wiki create PAGENAME ?FILE? [-mimetype TEXT-FORMAT] ** ** Create a new wiki page with initial content taken from ** FILE or from standard input. ** ** %fossil wiki list +** %fossil wiki ls ** ** Lists all wiki entries, one per line, ordered ** case-insensitively by name. ** */ @@ -1047,26 +1175,25 @@ if( n==0 ){ goto wiki_cmd_usage; } if( strncmp(g.argv[2],"export",n)==0 ){ - char const *zPageName; /* Name of the wiki page to export */ - char const *zFile; /* Name of the output file (0=stdout) */ + const char *zPageName; /* Name of the wiki page to export */ + const char *zFile; /* Name of the output file (0=stdout) */ int rid; /* Artifact ID of the wiki page */ int i; /* Loop counter */ char *zBody = 0; /* Wiki page content */ Blob body; /* Wiki page content */ Manifest *pWiki = 0; /* Parsed wiki page content */ - if( (g.argc!=4) && (g.argc!=5) ){ usage("export PAGENAME ?FILE?"); } zPageName = g.argv[3]; rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x" " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" " ORDER BY x.mtime DESC LIMIT 1", - zPageName + zPageName ); if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){ zBody = pWiki->zWiki; } if( zBody==0 ){ @@ -1079,54 +1206,67 @@ blob_append(&body, "\n", 1); blob_write_to_file(&body, zFile); blob_reset(&body); manifest_destroy(pWiki); return; - }else - if( strncmp(g.argv[2],"commit",n)==0 - || strncmp(g.argv[2],"create",n)==0 ){ - char *zPageName; - Blob content; + }else if( strncmp(g.argv[2],"commit",n)==0 + || strncmp(g.argv[2],"create",n)==0 ){ + const char *zPageName; /* page name */ + Blob content; /* Input content */ + int rid; + Manifest *pWiki = 0; /* Parsed wiki page content */ + const char *zMimeType = find_option("mimetype", "M", 1); if( g.argc!=4 && g.argc!=5 ){ - usage("commit PAGENAME ?FILE?"); + usage("commit|create PAGENAME ?FILE? [-mimetype TEXT-FORMAT]"); } zPageName = g.argv[3]; if( g.argc==4 ){ blob_read_from_channel(&content, stdin, -1); }else{ blob_read_from_file(&content, g.argv[4]); + } + if(!zMimeType || !*zMimeType){ + /* Try to deduce the mime type based on the prior version. */ + rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x" + " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" + " ORDER BY x.mtime DESC LIMIT 1", + zPageName + ); + if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 + && (pWiki->zMimetype && *pWiki->zMimetype)){ + zMimeType = pWiki->zMimetype; + } } if( g.argv[2][1]=='r' ){ - wiki_cmd_commit(zPageName, 1, &content); + wiki_cmd_commit(zPageName, 1, &content, zMimeType, 1); fossil_print("Created new wiki page %s.\n", zPageName); }else{ - wiki_cmd_commit(zPageName, 0, &content); + wiki_cmd_commit(zPageName, 0, &content, zMimeType, 1); fossil_print("Updated wiki page %s.\n", zPageName); } + manifest_destroy(pWiki); blob_reset(&content); - }else - if( strncmp(g.argv[2],"delete",n)==0 ){ + }else if( strncmp(g.argv[2],"delete",n)==0 ){ if( g.argc!=5 ){ usage("delete PAGENAME"); } fossil_fatal("delete not yet implemented."); - }else - if( strncmp(g.argv[2],"list",n)==0 ){ + }else if(( strncmp(g.argv[2],"list",n)==0 ) + || ( strncmp(g.argv[2],"ls",n)==0 )){ Stmt q; - db_prepare(&q, + db_prepare(&q, "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'" " ORDER BY lower(tagname) /*sort*/" ); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); fossil_print( "%s\n",zName); } db_finalize(&q); - }else - { + }else{ goto wiki_cmd_usage; } return; wiki_cmd_usage: usage("export|create|commit|list ..."); } Index: src/wikiformat.c ================================================================== --- src/wikiformat.c +++ src/wikiformat.c @@ -15,12 +15,12 @@ ** ******************************************************************************* ** ** This file contains code to do formatting of wiki text. */ -#include <assert.h> #include "config.h" +#include <assert.h> #include "wikiformat.h" #if INTERFACE /* ** Allowed wiki transformation operations @@ -35,103 +35,112 @@ /* ** These are the only markup attributes allowed. */ -#define ATTR_ALIGN 1 -#define ATTR_ALT 2 -#define ATTR_BGCOLOR 3 -#define ATTR_BORDER 4 -#define ATTR_CELLPADDING 5 -#define ATTR_CELLSPACING 6 -#define ATTR_CLASS 7 -#define ATTR_CLEAR 8 -#define ATTR_COLOR 9 -#define ATTR_COLSPAN 10 -#define ATTR_COMPACT 11 -#define ATTR_FACE 12 -#define ATTR_HEIGHT 13 -#define ATTR_HREF 14 -#define ATTR_HSPACE 15 -#define ATTR_ID 16 -#define ATTR_LINKS 17 -#define ATTR_NAME 18 -#define ATTR_ROWSPAN 19 -#define ATTR_SIZE 20 -#define ATTR_SRC 21 -#define ATTR_START 22 -#define ATTR_STYLE 23 -#define ATTR_TARGET 24 -#define ATTR_TYPE 25 -#define ATTR_VALIGN 26 -#define ATTR_VALUE 27 -#define ATTR_VSPACE 28 -#define ATTR_WIDTH 29 -#define AMSK_ALIGN 0x00000001 -#define AMSK_ALT 0x00000002 -#define AMSK_BGCOLOR 0x00000004 -#define AMSK_BORDER 0x00000008 -#define AMSK_CELLPADDING 0x00000010 -#define AMSK_CELLSPACING 0x00000020 -#define AMSK_CLASS 0x00000040 -#define AMSK_CLEAR 0x00000080 -#define AMSK_COLOR 0x00000100 -#define AMSK_COLSPAN 0x00000200 -#define AMSK_COMPACT 0x00000400 -#define AMSK_FACE 0x00000800 -#define AMSK_HEIGHT 0x00001000 -#define AMSK_HREF 0x00002000 -#define AMSK_HSPACE 0x00004000 -#define AMSK_ID 0x00008000 -#define AMSK_LINKS 0x00010000 -#define AMSK_NAME 0x00020000 -#define AMSK_ROWSPAN 0x00040000 -#define AMSK_SIZE 0x00080000 -#define AMSK_SRC 0x00100000 -#define AMSK_START 0x00200000 -#define AMSK_STYLE 0x00400000 -#define AMSK_TARGET 0x00800000 -#define AMSK_TYPE 0x01000000 -#define AMSK_VALIGN 0x02000000 -#define AMSK_VALUE 0x04000000 -#define AMSK_VSPACE 0x08000000 -#define AMSK_WIDTH 0x10000000 +enum allowed_attr_t { + ATTR_ALIGN = 1, + ATTR_ALT, + ATTR_BGCOLOR, + ATTR_BORDER, + ATTR_CELLPADDING, + ATTR_CELLSPACING, + ATTR_CLASS, + ATTR_CLEAR, + ATTR_COLOR, + ATTR_COLSPAN, + ATTR_COMPACT, + ATTR_FACE, + ATTR_HEIGHT, + ATTR_HREF, + ATTR_HSPACE, + ATTR_ID, + ATTR_LINKS, + ATTR_NAME, + ATTR_ROWSPAN, + ATTR_SIZE, + ATTR_SRC, + ATTR_START, + ATTR_STYLE, + ATTR_TARGET, + ATTR_TYPE, + ATTR_VALIGN, + ATTR_VALUE, + ATTR_VSPACE, + ATTR_WIDTH +}; + +enum amsk_t { + AMSK_ALIGN = 0x00000001, + AMSK_ALT = 0x00000002, + AMSK_BGCOLOR = 0x00000004, + AMSK_BORDER = 0x00000008, + AMSK_CELLPADDING = 0x00000010, + AMSK_CELLSPACING = 0x00000020, + AMSK_CLASS = 0x00000040, + AMSK_CLEAR = 0x00000080, + AMSK_COLOR = 0x00000100, + AMSK_COLSPAN = 0x00000200, + AMSK_COMPACT = 0x00000400, + /* re-use = 0x00000800, */ + AMSK_FACE = 0x00001000, + AMSK_HEIGHT = 0x00002000, + AMSK_HREF = 0x00004000, + AMSK_HSPACE = 0x00008000, + AMSK_ID = 0x00010000, + AMSK_LINKS = 0x00020000, + AMSK_NAME = 0x00040000, + AMSK_ROWSPAN = 0x00080000, + AMSK_SIZE = 0x00100000, + AMSK_SRC = 0x00200000, + AMSK_START = 0x00400000, + AMSK_STYLE = 0x00800000, + AMSK_TARGET = 0x01000000, + AMSK_TYPE = 0x02000000, + AMSK_VALIGN = 0x04000000, + AMSK_VALUE = 0x08000000, + AMSK_VSPACE = 0x10000000, + AMSK_WIDTH = 0x20000000 +}; static const struct AllowedAttribute { const char *zName; unsigned int iMask; } aAttribute[] = { + /* These indexes MUST line up with their + corresponding allowed_attr_t enum values. + */ { 0, 0 }, - { "align", AMSK_ALIGN, }, - { "alt", AMSK_ALT, }, - { "bgcolor", AMSK_BGCOLOR, }, - { "border", AMSK_BORDER, }, - { "cellpadding", AMSK_CELLPADDING, }, - { "cellspacing", AMSK_CELLSPACING, }, - { "class", AMSK_CLASS, }, - { "clear", AMSK_CLEAR, }, - { "color", AMSK_COLOR, }, - { "colspan", AMSK_COLSPAN, }, - { "compact", AMSK_COMPACT, }, - { "face", AMSK_FACE, }, - { "height", AMSK_HEIGHT, }, - { "href", AMSK_HREF, }, - { "hspace", AMSK_HSPACE, }, - { "id", AMSK_ID, }, - { "links", AMSK_LINKS, }, - { "name", AMSK_NAME, }, - { "rowspan", AMSK_ROWSPAN, }, - { "size", AMSK_SIZE, }, - { "src", AMSK_SRC, }, - { "start", AMSK_START, }, - { "style", AMSK_STYLE, }, - { "target", AMSK_TARGET, }, - { "type", AMSK_TYPE, }, - { "valign", AMSK_VALIGN, }, - { "value", AMSK_VALUE, }, - { "vspace", AMSK_VSPACE, }, - { "width", AMSK_WIDTH, }, + { "align", AMSK_ALIGN }, + { "alt", AMSK_ALT }, + { "bgcolor", AMSK_BGCOLOR }, + { "border", AMSK_BORDER }, + { "cellpadding", AMSK_CELLPADDING }, + { "cellspacing", AMSK_CELLSPACING }, + { "class", AMSK_CLASS }, + { "clear", AMSK_CLEAR }, + { "color", AMSK_COLOR }, + { "colspan", AMSK_COLSPAN }, + { "compact", AMSK_COMPACT }, + { "face", AMSK_FACE }, + { "height", AMSK_HEIGHT }, + { "href", AMSK_HREF }, + { "hspace", AMSK_HSPACE }, + { "id", AMSK_ID }, + { "links", AMSK_LINKS }, + { "name", AMSK_NAME }, + { "rowspan", AMSK_ROWSPAN }, + { "size", AMSK_SIZE }, + { "src", AMSK_SRC }, + { "start", AMSK_START }, + { "style", AMSK_STYLE }, + { "target", AMSK_TARGET }, + { "type", AMSK_TYPE }, + { "valign", AMSK_VALIGN }, + { "value", AMSK_VALUE }, + { "vspace", AMSK_VSPACE }, + { "width", AMSK_WIDTH }, }; /* ** Use binary search to locate a tag in the aAttribute[] table. */ @@ -164,62 +173,69 @@ ** in aAllowedMarkup[]. */ #define MARKUP_INVALID 0 #define MARKUP_A 1 #define MARKUP_ADDRESS 2 -#define MARKUP_B 3 -#define MARKUP_BIG 4 -#define MARKUP_BLOCKQUOTE 5 -#define MARKUP_BR 6 -#define MARKUP_CENTER 7 -#define MARKUP_CITE 8 -#define MARKUP_CODE 9 -#define MARKUP_COL 10 -#define MARKUP_COLGROUP 11 -#define MARKUP_DD 12 -#define MARKUP_DFN 13 -#define MARKUP_DIV 14 -#define MARKUP_DL 15 -#define MARKUP_DT 16 -#define MARKUP_EM 17 -#define MARKUP_FONT 18 -#define MARKUP_H1 19 -#define MARKUP_H2 20 -#define MARKUP_H3 21 -#define MARKUP_H4 22 -#define MARKUP_H5 23 -#define MARKUP_H6 24 -#define MARKUP_HR 25 -#define MARKUP_I 26 -#define MARKUP_IMG 27 -#define MARKUP_KBD 28 -#define MARKUP_LI 29 -#define MARKUP_NOBR 30 -#define MARKUP_NOWIKI 31 -#define MARKUP_OL 32 -#define MARKUP_P 33 -#define MARKUP_PRE 34 -#define MARKUP_S 35 -#define MARKUP_SAMP 36 -#define MARKUP_SMALL 37 -#define MARKUP_SPAN 38 -#define MARKUP_STRIKE 39 -#define MARKUP_STRONG 40 -#define MARKUP_SUB 41 -#define MARKUP_SUP 42 -#define MARKUP_TABLE 43 -#define MARKUP_TBODY 44 -#define MARKUP_TD 45 -#define MARKUP_TFOOT 46 -#define MARKUP_TH 47 -#define MARKUP_THEAD 48 -#define MARKUP_TR 49 -#define MARKUP_TT 50 -#define MARKUP_U 51 -#define MARKUP_UL 52 -#define MARKUP_VAR 53 -#define MARKUP_VERBATIM 54 +#define MARKUP_HTML5_ARTICLE 3 +#define MARKUP_HTML5_ASIDE 4 +#define MARKUP_B 5 +#define MARKUP_BIG 6 +#define MARKUP_BLOCKQUOTE 7 +#define MARKUP_BR 8 +#define MARKUP_CENTER 9 +#define MARKUP_CITE 10 +#define MARKUP_CODE 11 +#define MARKUP_COL 12 +#define MARKUP_COLGROUP 13 +#define MARKUP_DD 14 +#define MARKUP_DFN 15 +#define MARKUP_DIV 16 +#define MARKUP_DL 17 +#define MARKUP_DT 18 +#define MARKUP_EM 19 +#define MARKUP_FONT 20 +#define MARKUP_HTML5_FOOTER 21 +#define MARKUP_H1 22 +#define MARKUP_H2 23 +#define MARKUP_H3 24 +#define MARKUP_H4 25 +#define MARKUP_H5 26 +#define MARKUP_H6 27 +#define MARKUP_HTML5_HEADER 28 +#define MARKUP_HR 29 +#define MARKUP_I 30 +#define MARKUP_IMG 31 +#define MARKUP_KBD 32 +#define MARKUP_LI 33 +#define MARKUP_HTML5_NAV 34 +#define MARKUP_NOBR 35 +#define MARKUP_NOWIKI 36 +#define MARKUP_OL 37 +#define MARKUP_P 38 +#define MARKUP_PRE 39 +#define MARKUP_S 40 +#define MARKUP_SAMP 41 +#define MARKUP_HTML5_SECTION 42 +#define MARKUP_SMALL 43 +#define MARKUP_SPAN 44 +#define MARKUP_STRIKE 45 +#define MARKUP_STRONG 46 +#define MARKUP_SUB 47 +#define MARKUP_SUP 48 +#define MARKUP_TABLE 49 +#define MARKUP_TBODY 50 +#define MARKUP_TD 51 +#define MARKUP_TFOOT 52 +#define MARKUP_TH 53 +#define MARKUP_THEAD 54 +#define MARKUP_TITLE 55 +#define MARKUP_TR 56 +#define MARKUP_TT 57 +#define MARKUP_U 58 +#define MARKUP_UL 59 +#define MARKUP_VAR 60 +#define MARKUP_VERBATIM 61 /* ** The various markup is divided into the following types: */ #define MUTYPE_SINGLE 0x0001 /* <img>, <br>, or <hr> */ @@ -251,10 +267,15 @@ } aMarkup[] = { { 0, MARKUP_INVALID, 0, 0 }, { "a", MARKUP_A, MUTYPE_HYPERLINK, AMSK_HREF|AMSK_NAME|AMSK_CLASS|AMSK_TARGET|AMSK_STYLE }, { "address", MARKUP_ADDRESS, MUTYPE_BLOCK, AMSK_STYLE }, + { "article", MARKUP_HTML5_ARTICLE, MUTYPE_BLOCK, + AMSK_ID|AMSK_CLASS|AMSK_STYLE }, + { "aside", MARKUP_HTML5_ASIDE, MUTYPE_BLOCK, + AMSK_ID|AMSK_CLASS|AMSK_STYLE }, + { "b", MARKUP_B, MUTYPE_FONT, AMSK_STYLE }, { "big", MARKUP_BIG, MUTYPE_FONT, AMSK_STYLE }, { "blockquote", MARKUP_BLOCKQUOTE, MUTYPE_BLOCK, AMSK_STYLE }, { "br", MARKUP_BR, MUTYPE_SINGLE, AMSK_CLEAR }, { "center", MARKUP_CENTER, MUTYPE_BLOCK, AMSK_STYLE }, @@ -272,10 +293,13 @@ AMSK_COMPACT|AMSK_STYLE }, { "dt", MARKUP_DT, MUTYPE_LI, AMSK_STYLE }, { "em", MARKUP_EM, MUTYPE_FONT, AMSK_STYLE }, { "font", MARKUP_FONT, MUTYPE_FONT, AMSK_COLOR|AMSK_FACE|AMSK_SIZE|AMSK_STYLE }, + { "footer", MARKUP_HTML5_FOOTER, MUTYPE_BLOCK, + AMSK_ID|AMSK_CLASS|AMSK_STYLE }, + { "h1", MARKUP_H1, MUTYPE_BLOCK, AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE }, { "h2", MARKUP_H2, MUTYPE_BLOCK, AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE }, { "h3", MARKUP_H3, MUTYPE_BLOCK, @@ -284,10 +308,14 @@ AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE }, { "h5", MARKUP_H5, MUTYPE_BLOCK, AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE }, { "h6", MARKUP_H6, MUTYPE_BLOCK, AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE }, + + { "header", MARKUP_HTML5_HEADER, MUTYPE_BLOCK, + AMSK_ID|AMSK_CLASS|AMSK_STYLE }, + { "hr", MARKUP_HR, MUTYPE_SINGLE, AMSK_ALIGN|AMSK_COLOR|AMSK_SIZE|AMSK_WIDTH| AMSK_STYLE|AMSK_CLASS }, { "i", MARKUP_I, MUTYPE_FONT, AMSK_STYLE }, { "img", MARKUP_IMG, MUTYPE_SINGLE, @@ -294,19 +322,23 @@ AMSK_ALIGN|AMSK_ALT|AMSK_BORDER|AMSK_HEIGHT| AMSK_HSPACE|AMSK_SRC|AMSK_VSPACE|AMSK_WIDTH|AMSK_STYLE }, { "kbd", MARKUP_KBD, MUTYPE_FONT, AMSK_STYLE }, { "li", MARKUP_LI, MUTYPE_LI, AMSK_TYPE|AMSK_VALUE|AMSK_STYLE }, + { "nav", MARKUP_HTML5_NAV, MUTYPE_BLOCK, + AMSK_ID|AMSK_CLASS|AMSK_STYLE }, { "nobr", MARKUP_NOBR, MUTYPE_FONT, 0 }, { "nowiki", MARKUP_NOWIKI, MUTYPE_SPECIAL, 0 }, { "ol", MARKUP_OL, MUTYPE_LIST, AMSK_START|AMSK_TYPE|AMSK_COMPACT|AMSK_STYLE }, { "p", MARKUP_P, MUTYPE_BLOCK, AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE }, { "pre", MARKUP_PRE, MUTYPE_BLOCK, AMSK_STYLE }, { "s", MARKUP_S, MUTYPE_FONT, AMSK_STYLE }, { "samp", MARKUP_SAMP, MUTYPE_FONT, AMSK_STYLE }, + { "section", MARKUP_HTML5_SECTION, MUTYPE_BLOCK, + AMSK_ID|AMSK_CLASS|AMSK_STYLE }, { "small", MARKUP_SMALL, MUTYPE_FONT, AMSK_STYLE }, { "span", MARKUP_SPAN, MUTYPE_BLOCK, AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE }, { "strike", MARKUP_STRIKE, MUTYPE_FONT, AMSK_STYLE }, { "strong", MARKUP_STRONG, MUTYPE_FONT, AMSK_STYLE }, @@ -326,10 +358,11 @@ { "th", MARKUP_TH, MUTYPE_TD, AMSK_ALIGN|AMSK_BGCOLOR|AMSK_COLSPAN| AMSK_ROWSPAN|AMSK_VALIGN|AMSK_CLASS|AMSK_STYLE }, { "thead", MARKUP_THEAD, MUTYPE_BLOCK, AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE }, + { "title", MARKUP_TITLE, MUTYPE_BLOCK, 0 }, { "tr", MARKUP_TR, MUTYPE_TR, AMSK_ALIGN|AMSK_BGCOLOR|AMSK_VALIGN|AMSK_CLASS|AMSK_STYLE }, { "tt", MARKUP_TT, MUTYPE_FONT, AMSK_STYLE }, { "u", MARKUP_U, MUTYPE_FONT, AMSK_STYLE }, { "ul", MARKUP_UL, MUTYPE_LIST, @@ -339,11 +372,10 @@ AMSK_ID|AMSK_TYPE }, }; void show_allowed_wiki_markup( void ){ int i; /* loop over allowedAttr */ - for( i=1 ; i<=sizeof(aMarkup)/sizeof(aMarkup[0]) - 1 ; i++ ){ @ <%s(aMarkup[i].zName)> } } @@ -730,11 +762,11 @@ ** Parse this element into the p structure. ** ** The content of z[] might be modified by converting characters ** to lowercase and by inserting some "\000" characters. */ -static void parseMarkup(ParsedMarkup *p, char *z){ +static int parseMarkup(ParsedMarkup *p, char *z){ int i, j, c; int iACode; char *zValue; int seen = 0; char zTag[100]; @@ -762,15 +794,15 @@ p->aAttr[0].zValue = &z[i]; while( fossil_isalnum(z[i]) ){ i++; } p->aAttr[0].cTerm = c = z[i]; z[i++] = 0; p->nAttr = 1; - if( c=='>' ) return; + if( c=='>' ) return 0; } while( fossil_isspace(z[i]) ){ i++; } while( c!='>' && p->nAttr<8 && fossil_isalpha(z[i]) ){ - int attrOk; /* True to preserver attribute. False to ignore it */ + int attrOk; /* True to preserve attribute. False to ignore it */ j = 0; while( fossil_isalnum(z[i]) ){ if( j<sizeof(zTag)-1 ) zTag[j++] = fossil_tolower(z[i]); i++; } @@ -809,10 +841,11 @@ p->nAttr++; } while( fossil_isspace(z[i]) ){ i++; } if( z[i]=='>' || (z[i]=='/' && z[i+1]=='>') ) break; } + return seen; } /* ** Render markup on the given blob. */ @@ -848,12 +881,14 @@ static void unparseMarkup(ParsedMarkup *p){ int i, n; for(i=0; i<p->nAttr; i++){ char *z = p->aAttr[i].zValue; if( z==0 ) continue; - n = strlen(z); - z[n] = p->aAttr[i].cTerm; + if( p->aAttr[i].cTerm ){ + n = strlen(z); + z[n] = p->aAttr[i].cTerm; + } } } /* ** Return the value of attribute attrId. Return NULL if there is no @@ -926,11 +961,11 @@ static void popStack(Renderer *p){ if( p->nStack ){ int iCode; p->nStack--; iCode = p->aStack[p->nStack].iCode; - if( iCode!=MARKUP_DIV && p->pOut ){ + if( (iCode!=MARKUP_DIV || p->aStack[p->nStack].zId==0) && p->pOut ){ blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName); } } } @@ -1012,11 +1047,11 @@ */ static void startAutoParagraph(Renderer *p){ if( p->wantAutoParagraph==0 ) return; if( p->state & WIKI_LINKSONLY ) return; if( p->wikiList==MARKUP_OL || p->wikiList==MARKUP_UL ) return; - blob_appendf(p->pOut, "<p>", -1); + blob_append(p->pOut, "<p>", -1); p->wantAutoParagraph = 0; p->inAutoParagraph = 1; } /* @@ -1087,11 +1122,11 @@ if( once ){ const char *zClosedExpr = db_get("ticket-closed-expr", "status='Closed'"); db_static_prepare(&q, "SELECT %s FROM ticket " " WHERE tkt_uuid>=:lwr AND tkt_uuid<:upr", - zClosedExpr + zClosedExpr /*safe-for-%s*/ ); once = 0; } db_bind_text(&q, ":lwr", zLower); db_bind_text(&q, ":upr", zUpper); @@ -1172,11 +1207,11 @@ || strncmp(zTarget, "ftp:", 4)==0 || strncmp(zTarget, "mailto:", 7)==0 ){ blob_appendf(p->pOut, "<a href=\"%s\">", zTarget); }else if( zTarget[0]=='/' ){ - blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget); + blob_appendf(p->pOut, "<a href=\"%R%h\">", zTarget); }else if( zTarget[0]=='.' && (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/')) && (p->state & WIKI_LINKSONLY)==0 ){ blob_appendf(p->pOut, "<a href=\"%h\">", zTarget); }else if( zTarget[0]=='#' ){ @@ -1209,16 +1244,18 @@ } }else if( !in_this_repo(zTarget) ){ if( (p->state & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){ zTerm = ""; }else{ - blob_appendf(p->pOut, "<span class=\"brokenlink\">[", zTarget); + blob_appendf(p->pOut, "<span class=\"brokenlink\">["); zTerm = "]</span>"; } }else if( g.perm.Hyperlink ){ blob_appendf(p->pOut, "%z[",href("%R/info/%s", zTarget)); zTerm = "]</a>"; + }else{ + zTerm = ""; } }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-' && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){ blob_appendf(p->pOut, "<a href=\"%R/timeline?c=%T\">", zTarget); }else if( (z = validWikiPageName(p, zTarget))!=0 ){ @@ -1294,11 +1331,11 @@ if( p->wikiList ){ popStackToTag(p, p->wikiList); p->wikiList = 0; } endAutoParagraph(p); - blob_appendf(p->pOut, "\n\n", 1); + blob_append(p->pOut, "\n\n", 1); p->wantAutoParagraph = 1; } p->state |= AT_PARAGRAPH|AT_NEWLINE; break; } @@ -1446,11 +1483,20 @@ break; } case TOKEN_MARKUP: { const char *zId; int iDiv; - parseMarkup(&markup, z); + int mAttr = parseMarkup(&markup, z); + + /* Convert <title> to <h1 align='center'> */ + if( markup.iCode==MARKUP_TITLE && !p->inVerbatim ){ + markup.iCode = MARKUP_H1; + markup.nAttr = 1; + markup.aAttr[0].iACode = AMSK_ALIGN; + markup.aAttr[0].zValue = "center"; + markup.aAttr[0].cTerm = 0; + } /* Markup of the form </div id=ID> where there is a matching ** ID somewhere on the stack. Exit any contained verbatim. ** Pop the stack up to the matching <div>. Discard the </div> */ @@ -1524,11 +1570,11 @@ popStackToTag(p, markup.iCode); }else /* Push <div> markup onto the stack together with the id=ID attribute. */ - if( markup.iCode==MARKUP_DIV ){ + if( markup.iCode==MARKUP_DIV && (mAttr & ATTR_ID)!=0 ){ pushStackWithId(p, markup.iCode, markupId(&markup), (p->state & ALLOW_WIKI)!=0); }else /* Enter <verbatim> processing. With verbatim enabled, all other @@ -1718,14 +1764,22 @@ z = blob_str(pIn); for(i=0; fossil_isspace(z[i]); i++){} if( z[i]!='<' ) return 0; i++; if( strncmp(&z[i],"title>", 6)!=0 ) return 0; - iStart = i+6; + for(iStart=i+6; fossil_isspace(z[iStart]); iStart++){} for(i=iStart; z[i] && (z[i]!='<' || strncmp(&z[i],"",8)!=0); i++){} - if( z[i]!='<' ) return 0; - blob_init(pTitle, &z[iStart], i-iStart); + if( strncmp(&z[i],"",8)!=0 ){ + blob_init(pTitle, 0, 0); + blob_init(pTail, &z[iStart], -1); + return 1; + } + if( i-iStart>0 ){ + blob_init(pTitle, &z[iStart], i-iStart); + }else{ + blob_init(pTitle, 0, 0); + } blob_init(pTail, &z[i+8], -1); return 1; } /* @@ -1914,17 +1968,26 @@ ** z points to the start of a token. Return the number of ** characters in that token. */ static int nextHtmlToken(const char *z){ int n; - if( z[0]=='<' ){ + char c; + if( (c=z[0])=='<' ){ n = markupLength(z); if( n<=0 ) n = 1; - }else if( fossil_isspace(z[0]) ){ + }else if( fossil_isspace(c) ){ for(n=1; z[n] && fossil_isspace(z[n]); n++){} + }else if( c=='&' ){ + n = z[1]=='#' ? 2 : 1; + while( fossil_isalnum(z[n]) ) n++; + if( z[n]==';' ) n++; }else{ - for(n=1; z[n] && z[n]!='<' && !fossil_isspace(z[n]); n++){} + n = 1; + for(n=1; 1; n++){ + if( (c = z[n]) > '<' ) continue; + if( c=='<' || c=='&' || fossil_isspace(c) || c==0 ) break; + } } return n; } /* @@ -2041,9 +2104,127 @@ for(i=2; i markup. +** If there is no , then create a blank first line. +*/ +void html_to_plaintext(const char *zIn, Blob *pOut){ + int n; + int i, j; + int inTitle = 0; /* True between <title>... */ + int seenText = 0; /* True after first non-whitespace seen */ + int nNL = 0; /* Number of \n characters at the end of pOut */ + int nWS = 0; /* True if pOut ends with whitespace */ + while( fossil_isspace(zIn[0]) ) zIn++; + while( zIn[0] ){ + n = nextHtmlToken(zIn); + if( zIn[0]=='<' && n>1 ){ + int isCloseTag; + int eTag; + int eType; + char zTag[32]; + isCloseTag = zIn[1]=='/'; + for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){ + zTag[i] = fossil_tolower(zIn[j]); + } + zTag[i] = 0; + eTag = findTag(zTag); + eType = aMarkup[eTag].iType; + if( eTag==MARKUP_INVALID && fossil_strnicmp(zIn," ' ' within */ + for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++; + } + if( !nWS ){ + blob_append(pOut, nNL ? "\n" : " ", 1); + nWS = 1; + } + } + }else if( zIn[0]=='&' ){ + char c = '?'; + if( zIn[1]=='#' ){ + int x = atoi(&zIn[1]); + if( x>0 && x<=127 ) c = x; + }else{ + static const struct { int n; char c; char *z; } aEntity[] = { + { 5, '&', "&" }, + { 4, '<', "<" }, + { 4, '>', ">" }, + { 6, ' ', " " }, + }; + int jj; + for(jj=0; jj<ArraySize(aEntity); jj++){ + if( aEntity[jj].n==n && strncmp(aEntity[jj].z,zIn,n)==0 ){ + c = aEntity[jj].c; + break; + } + } + } + if( fossil_isspace(c) ){ + if( nWS==0 && seenText ) blob_append(pOut, &c, 1); + nWS = 1; + nNL = c=='\n'; + }else{ + if( !seenText && !inTitle ) blob_append(pOut, "\n", 1); + seenText = 1; + nNL = nWS = 0; + blob_append(pOut, &c, 1); + } + }else{ + if( !seenText && !inTitle ) blob_append(pOut, "\n", 1); + seenText = 1; + nNL = nWS = 0; + blob_append(pOut, zIn, n); + } + zIn += n; + } + if( nNL==0 ) blob_append(pOut, "\n", 1); +} + +/* +** COMMAND: test-html-to-text +*/ +void test_html_to_text(void){ + Blob in, out; + int i; + + for(i=2; i<g.argc; i++){ + blob_read_from_file(&in, g.argv[i]); + blob_zero(&out); + html_to_plaintext(blob_str(&in), &out); + blob_reset(&in); fossil_puts(blob_str(&out), 0); blob_reset(&out); } } ADDED src/winfile.c Index: src/winfile.c ================================================================== --- /dev/null +++ src/winfile.c @@ -0,0 +1,292 @@ +/* +** Copyright (c) 2006 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file implements several non-trivial file handling wrapper functions +** on Windows using the Win32 API. +*/ +#include "config.h" +#ifdef _WIN32 +/* This code is for win32 only */ +#include <sys/stat.h> +#include <windows.h> +#include "winfile.h" + +#ifndef LABEL_SECURITY_INFORMATION +# define LABEL_SECURITY_INFORMATION (0x00000010L) +#endif + +/* +** Fill stat buf with information received from stat() or lstat(). +** lstat() is called on Unix if isWd is TRUE and allow-symlinks setting is on. +** +*/ +int win32_stat(const wchar_t *zFilename, struct fossilStat *buf, int isWd){ + WIN32_FILE_ATTRIBUTE_DATA attr; + int rc = GetFileAttributesExW(zFilename, GetFileExInfoStandard, &attr); + if( rc ){ + ULARGE_INTEGER ull; + ull.LowPart = attr.ftLastWriteTime.dwLowDateTime; + ull.HighPart = attr.ftLastWriteTime.dwHighDateTime; + buf->st_mode = (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? + S_IFDIR : S_IFREG; + buf->st_size = (((i64)attr.nFileSizeHigh)<<32) | attr.nFileSizeLow; + buf->st_mtime = ull.QuadPart / 10000000ULL - 11644473600ULL; + } + return !rc; +} + +/* +** Wrapper around the access() system call. This code was copied from Tcl +** 8.6 and then modified. +*/ +int win32_access(const wchar_t *zFilename, int flags){ + int rc = 0; + PSECURITY_DESCRIPTOR pSd = NULL; + unsigned long size = 0; + PSID pSid = NULL; + BOOL sidDefaulted; + BOOL impersonated = FALSE; + SID_IDENTIFIER_AUTHORITY unmapped = {{0, 0, 0, 0, 0, 22}}; + GENERIC_MAPPING genMap; + HANDLE hToken = NULL; + DWORD desiredAccess = 0, grantedAccess = 0; + BOOL accessYesNo = FALSE; + PPRIVILEGE_SET pPrivSet = NULL; + DWORD privSetSize = 0; + DWORD attr = GetFileAttributesW(zFilename); + + if( attr==INVALID_FILE_ATTRIBUTES ){ + /* + * File might not exist. + */ + + if( GetLastError()!=ERROR_SHARING_VIOLATION ){ + rc = -1; goto done; + } + } + + if( flags==F_OK ){ + /* + * File exists, nothing else to check. + */ + + goto done; + } + + if( (flags & W_OK) + && (attr & FILE_ATTRIBUTE_READONLY) + && !(attr & FILE_ATTRIBUTE_DIRECTORY) ){ + /* + * The attributes say the file is not writable. If the file is a + * regular file (i.e., not a directory), then the file is not + * writable, full stop. For directories, the read-only bit is + * (mostly) ignored by Windows, so we can't ascertain anything about + * directory access from the attrib data. + */ + + rc = -1; goto done; + } + + /* + * It looks as if the permissions are ok, but if we are on NT, 2000 or XP, + * we have a more complex permissions structure so we try to check that. + * The code below is remarkably complex for such a simple thing as finding + * what permissions the OS has set for a file. + */ + + /* + * First find out how big the buffer needs to be. + */ + + GetFileSecurityW(zFilename, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, + 0, 0, &size); + + /* + * Should have failed with ERROR_INSUFFICIENT_BUFFER + */ + + if( GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ + /* + * Most likely case is ERROR_ACCESS_DENIED, which we will convert to + * EACCES - just what we want! + */ + + rc = -1; goto done; + } + + /* + * Now size contains the size of buffer needed. + */ + + pSd = (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(), 0, size); + + if( pSd==NULL ){ + rc = -1; goto done; + } + + /* + * Call GetFileSecurity() for real. + */ + + if( !GetFileSecurityW(zFilename, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, + pSd, size, &size) ){ + /* + * Error getting owner SD + */ + + rc = -1; goto done; + } + + /* + * As of Samba 3.0.23 (10-Jul-2006), unmapped users and groups are + * assigned to SID domains S-1-22-1 and S-1-22-2, where "22" is the + * top-level authority. If the file owner and group is unmapped then + * the ACL access check below will only test against world access, + * which is likely to be more restrictive than the actual access + * restrictions. Since the ACL tests are more likely wrong than + * right, skip them. Moreover, the unix owner access permissions are + * usually mapped to the Windows attributes, so if the user is the + * file owner then the attrib checks above are correct (as far as they + * go). + */ + + if( !GetSecurityDescriptorOwner(pSd, &pSid, &sidDefaulted) || + memcmp(GetSidIdentifierAuthority(pSid), &unmapped, + sizeof(SID_IDENTIFIER_AUTHORITY))==0 ){ + goto done; /* Attrib tests say access allowed. */ + } + + /* + * Perform security impersonation of the user and open the resulting + * thread token. + */ + + if( !ImpersonateSelf(SecurityImpersonation) ){ + /* + * Unable to perform security impersonation. + */ + + rc = -1; goto done; + } + impersonated = TRUE; + + if( !OpenThreadToken(GetCurrentThread(), + TOKEN_DUPLICATE | TOKEN_QUERY, FALSE, &hToken) ){ + /* + * Unable to get current thread's token. + */ + + rc = -1; goto done; + } + + /* + * Setup desiredAccess according to the access priveleges we are + * checking. + */ + + if( flags & R_OK ){ + desiredAccess |= FILE_GENERIC_READ; + } + if( flags & W_OK){ + desiredAccess |= FILE_GENERIC_WRITE; + } + + memset(&genMap, 0, sizeof(GENERIC_MAPPING)); + genMap.GenericRead = FILE_GENERIC_READ; + genMap.GenericWrite = FILE_GENERIC_WRITE; + genMap.GenericExecute = FILE_GENERIC_EXECUTE; + genMap.GenericAll = FILE_ALL_ACCESS; + + AccessCheck(pSd, hToken, desiredAccess, &genMap, 0, + &privSetSize, &grantedAccess, &accessYesNo); + /* + * Should have failed with ERROR_INSUFFICIENT_BUFFER + */ + + if( GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ + rc = -1; goto done; + } + pPrivSet = (PPRIVILEGE_SET)HeapAlloc(GetProcessHeap(), 0, privSetSize); + + if( pPrivSet==NULL ){ + rc = -1; goto done; + } + + /* + * Perform access check using the token. + */ + + if( !AccessCheck(pSd, hToken, desiredAccess, &genMap, pPrivSet, + &privSetSize, &grantedAccess, &accessYesNo) ){ + /* + * Unable to perform access check. + */ + + rc = -1; goto done; + } + if( !accessYesNo ) rc = -1; + +done: + + if( hToken != NULL ){ + CloseHandle(hToken); + } + if( impersonated ){ + RevertToSelf(); + impersonated = FALSE; + } + if( pPrivSet!=NULL ){ + HeapFree(GetProcessHeap(), 0, pPrivSet); + } + if( pSd!=NULL ){ + HeapFree(GetProcessHeap(), 0, pSd); + } + return rc; +} + +/* +** Wrapper around the chdir() system call. +*/ +int win32_chdir(const wchar_t *zChDir, int bChroot){ + int rc = (int)!SetCurrentDirectoryW(zChDir); + return rc; +} + +/* +** Get the current working directory. +** +** On windows, the name is converted from unicode to UTF8 and all '\\' +** characters are converted to '/'. +*/ +void win32_getcwd(char *zBuf, int nBuf){ + int i; + char *zUtf8; + wchar_t *zWide = fossil_malloc( sizeof(wchar_t)*nBuf ); + if( GetCurrentDirectoryW(nBuf, zWide)==0 ){ + fossil_fatal("cannot find current working directory."); + } + zUtf8 = fossil_filename_to_utf8(zWide); + fossil_free(zWide); + for(i=0; zUtf8[i]; i++) if( zUtf8[i]=='\\' ) zUtf8[i] = '/'; + strncpy(zBuf, zUtf8, nBuf); + fossil_filename_free(zUtf8); +} +#endif /* _WIN32 -- This code is for win32 only */ Index: src/winhttp.c ================================================================== --- src/winhttp.c +++ src/winhttp.c @@ -32,10 +32,11 @@ typedef struct HttpRequest HttpRequest; struct HttpRequest { int id; /* ID counter */ SOCKET s; /* Socket on which to receive data */ SOCKADDR_IN addr; /* Address from which data is coming */ + int flags; /* Flags passed to win32_http_server() */ const char *zOptions; /* --notfound and/or --localauth options */ }; /* ** Prefix for a temporary file. @@ -57,10 +58,21 @@ } zHdr++; } return 0; } + +/* +** Issue a fatal error. +*/ +static NORETURN void winhttp_fatal( + const char *zOp, + const char *zService, + const char *zErr +){ + fossil_fatal("unable to %s service '%s': %s", zOp, zService, zErr); +} /* ** Process a single incoming HTTP request. */ static void win32_http_request(void *pAppData){ @@ -67,15 +79,18 @@ HttpRequest *p = (HttpRequest*)pAppData; FILE *in = 0, *out = 0; int amt, got; int wanted = 0; char *z; + char zCmdFName[MAX_PATH]; char zRequestFName[MAX_PATH]; char zReplyFName[MAX_PATH]; char zCmd[2000]; /* Command-line to process the request */ char zHdr[2000]; /* The HTTP request header */ + sqlite3_snprintf(MAX_PATH, zCmdFName, + "%s_cmd%d.txt", zTempPrefix, p->id); sqlite3_snprintf(MAX_PATH, zRequestFName, "%s_in%d.txt", zTempPrefix, p->id); sqlite3_snprintf(MAX_PATH, zReplyFName, "%s_out%d.txt", zTempPrefix, p->id); amt = 0; @@ -108,13 +123,33 @@ } wanted -= got; } fclose(out); out = 0; - sqlite3_snprintf(sizeof(zCmd), zCmd, "\"%s\" http \"%s\" %s %s %s --nossl%s", - g.nameOfExe, g.zRepositoryName, zRequestFName, zReplyFName, - inet_ntoa(p->addr.sin_addr), p->zOptions + /* + ** The repository name is only needed if there was no open checkout. This + ** is designed to allow the open checkout for the interactive user to work + ** with the local Fossil server started via the "ui" command. + */ + if( (p->flags & HTTP_SERVER_HAD_CHECKOUT)==0 ){ + assert( g.zRepositoryName && g.zRepositoryName[0] ); + sqlite3_snprintf(sizeof(zCmd), zCmd, "%s%s\n%s\n%s\n%s", + get_utf8_bom(0), zRequestFName, zReplyFName, inet_ntoa(p->addr.sin_addr), + g.zRepositoryName + ); + }else{ + sqlite3_snprintf(sizeof(zCmd), zCmd, "%s%s\n%s\n%s", + get_utf8_bom(0), zRequestFName, zReplyFName, inet_ntoa(p->addr.sin_addr) + ); + } + out = fossil_fopen(zCmdFName, "wb"); + if( out==0 ) goto end_request; + fwrite(zCmd, 1, strlen(zCmd), out); + fclose(out); + + sqlite3_snprintf(sizeof(zCmd), zCmd, "\"%s\" http -args \"%s\" --nossl%s", + g.nameOfExe, zCmdFName, p->zOptions ); fossil_system(zCmd); in = fossil_fopen(zReplyFName, "rb"); if( in ){ while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){ @@ -126,10 +161,11 @@ if( out ) fclose(out); if( in ) fclose(in); closesocket(p->s); file_delete(zRequestFName); file_delete(zReplyFName); + file_delete(zCmdFName); free(p); } /* ** Process a single incoming SCGI request. @@ -168,14 +204,15 @@ fwrite(zHdr, 1, got, out); wanted += got; } fclose(out); out = 0; + assert( g.zRepositoryName && g.zRepositoryName[0] ); sqlite3_snprintf(sizeof(zCmd), zCmd, - "\"%s\" http \"%s\" %s %s %s --scgi --nossl%s", - g.nameOfExe, g.zRepositoryName, zRequestFName, zReplyFName, - inet_ntoa(p->addr.sin_addr), p->zOptions + "\"%s\" http \"%s\" \"%s\" %s \"%s\" --scgi --nossl%s", + g.nameOfExe, zRequestFName, zReplyFName, inet_ntoa(p->addr.sin_addr), + g.zRepositoryName, p->zOptions ); fossil_system(zCmd); in = fossil_fopen(zReplyFName, "rb"); if( in ){ while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){ @@ -223,10 +260,13 @@ blob_appendf(&options, " --files-urlenc %T", zFileGlob); } if( g.useLocalauth ){ blob_appendf(&options, " --localauth"); } + if( flags & HTTP_SERVER_REPOLIST ){ + blob_appendf(&options, " --repolist"); + } if( WSAStartup(MAKEWORD(1,1), &wd) ){ fossil_fatal("unable to initialize winsock"); } while( iPort<=mxPort ){ s = socket(AF_INET, SOCK_STREAM, 0); @@ -271,11 +311,11 @@ zTempPrefix = mprintf("%sfossil_server_P%d_", fossil_unicode_to_utf8(zTmpPath), iPort); fossil_print("Listening for %s requests on TCP port %d\n", (flags&HTTP_SERVER_SCGI)!=0?"SCGI":"HTTP", iPort); if( zBrowser ){ - zBrowser = mprintf(zBrowser, iPort); + zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); fossil_print("Launch webbrowser: %s\n", zBrowser); fossil_system(zBrowser); } fossil_print("Type Ctrl-C to stop the HTTP server\n"); /* Set the service status to running and pass the listener socket to the @@ -306,10 +346,11 @@ } p = fossil_malloc( sizeof(*p) ); p->id = ++idCnt; p->s = client; p->addr = client_addr; + p->flags = flags; p->zOptions = blob_str(&options); if( flags & HTTP_SERVER_SCGI ){ _beginthread(win32_scgi_request, 0, (void*)p); }else{ _beginthread(win32_http_request, 0, (void*)p); @@ -606,10 +647,14 @@ ** ** Enables automatic login if the --localauth option is present ** and the "localauth" setting is off and the connection is from ** localhost. ** +** --repolist +** +** If REPOSITORY is directory, URL "/" lists all repositories. +** ** --scgi ** ** Create an SCGI server instead of an HTTP server ** ** @@ -652,47 +697,52 @@ if( strncmp(zMethod, "create", n)==0 ){ SC_HANDLE hScm; SC_HANDLE hSvc; SERVICE_DESCRIPTIONW svcDescr = {L"Fossil - Distributed Software Configuration Management"}; - char *zErrFmt = "unable to create service '%s': %s"; DWORD dwStartType = SERVICE_DEMAND_START; const char *zDisplay = find_option("display", "D", 1); const char *zStart = find_option("start", "S", 1); const char *zUsername = find_option("username", "U", 1); const char *zPassword = find_option("password", "W", 1); const char *zPort = find_option("port", "P", 1); const char *zNotFound = find_option("notfound", 0, 1); const char *zFileGlob = find_option("files", 0, 1); const char *zLocalAuth = find_option("localauth", 0, 0); - const char *zRepository = find_option("repository", "R", 1); + const char *zRepository = find_repository_option(); int useSCGI = find_option("scgi", 0, 0)!=0; + int allowRepoList = find_option("repolist",0,0)!=0; Blob binPath; verify_all_options(); if( g.argc==4 ){ zSvcName = g.argv[3]; }else if( g.argc>4 ){ - fossil_fatal("to much arguments for create method."); + fossil_fatal("too many arguments for create method."); } /* Process service creation specific options. */ if( !zDisplay ){ zDisplay = zSvcName; + } + /* Per MSDN, the password parameter cannot be NULL. Must use empty + ** string instead (i.e. in the call to CreateServiceW). */ + if( !zPassword ){ + zPassword = ""; } if( zStart ){ if( strncmp(zStart, "auto", strlen(zStart))==0 ){ dwStartType = SERVICE_AUTO_START; }else if( strncmp(zStart, "manual", strlen(zStart))==0 ){ dwStartType = SERVICE_DEMAND_START; }else{ - fossil_fatal(zErrFmt, zSvcName, + winhttp_fatal("create", zSvcName, "specify 'auto' or 'manual' for the '-S|--start' option"); } } /* Process options for Fossil running as server. */ if( zPort && (atoi(zPort)<=0) ){ - fossil_fatal(zErrFmt, zSvcName, + winhttp_fatal("create", zSvcName, "port number must be in the range 1 - 65535."); } if( !zRepository ){ db_must_be_within_tree(); }else if( file_isdir(zRepository)==1 ){ @@ -705,17 +755,18 @@ /* Build the fully-qualified path to the service binary file. */ blob_zero(&binPath); blob_appendf(&binPath, "\"%s\" server", g.nameOfExe); if( zPort ) blob_appendf(&binPath, " --port %s", zPort); if( useSCGI ) blob_appendf(&binPath, " --scgi"); + if( allowRepoList ) blob_appendf(&binPath, " --repolist"); if( zNotFound ) blob_appendf(&binPath, " --notfound \"%s\"", zNotFound); if( zFileGlob ) blob_appendf(&binPath, " --files-urlenc %T", zFileGlob); if( zLocalAuth ) blob_append(&binPath, " --localauth", -1); blob_appendf(&binPath, " \"%s\"", g.zRepositoryName); /* Create the service. */ hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); - if( !hScm ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !hScm ) winhttp_fatal("create", zSvcName, win32_get_last_errmsg()); hSvc = CreateServiceW( hScm, /* Handle to the SCM */ fossil_utf8_to_unicode(zSvcName), /* Name of the service */ fossil_utf8_to_unicode(zDisplay), /* Display name */ SERVICE_ALL_ACCESS, /* Desired access */ @@ -724,14 +775,14 @@ SERVICE_ERROR_NORMAL, /* Error control */ fossil_utf8_to_unicode(blob_str(&binPath)), /* Binary path */ NULL, /* Load ordering group */ NULL, /* Tag value */ NULL, /* Service dependencies */ - fossil_utf8_to_unicode(zUsername), /* Service account */ + zUsername ? fossil_utf8_to_unicode(zUsername) : 0, /* Account */ fossil_utf8_to_unicode(zPassword) /* Account password */ ); - if( !hSvc ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !hSvc ) winhttp_fatal("create", zSvcName, win32_get_last_errmsg()); /* Set the service description. */ ChangeServiceConfig2W(hSvc, SERVICE_CONFIG_DESCRIPTION, &svcDescr); fossil_print("Service '%s' successfully created.\n", zSvcName); CloseServiceHandle(hSvc); CloseServiceHandle(hScm); @@ -738,29 +789,28 @@ }else if( strncmp(zMethod, "delete", n)==0 ){ SC_HANDLE hScm; SC_HANDLE hSvc; SERVICE_STATUS sstat; - char *zErrFmt = "unable to delete service '%s': %s"; verify_all_options(); if( g.argc==4 ){ zSvcName = g.argv[3]; }else if( g.argc>4 ){ - fossil_fatal("to much arguments for delete method."); + fossil_fatal("too many arguments for delete method."); } hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); - if( !hScm ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !hScm ) winhttp_fatal("delete", zSvcName, win32_get_last_errmsg()); hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), SERVICE_ALL_ACCESS); - if( !hSvc ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !hSvc ) winhttp_fatal("delete", zSvcName, win32_get_last_errmsg()); QueryServiceStatus(hSvc, &sstat); if( sstat.dwCurrentState!=SERVICE_STOPPED ){ fossil_print("Stopping service '%s'", zSvcName); if( sstat.dwCurrentState!=SERVICE_STOP_PENDING ){ if( !ControlService(hSvc, SERVICE_CONTROL_STOP, &sstat) ){ - fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + winhttp_fatal("delete", zSvcName, win32_get_last_errmsg()); } } while( sstat.dwCurrentState!=SERVICE_STOPPED ){ Sleep(100); fossil_print("."); @@ -770,11 +820,11 @@ } if( !DeleteService(hSvc) ){ if( GetLastError()==ERROR_SERVICE_MARKED_FOR_DELETE ){ fossil_warning("Service '%s' already marked for delete.\n", zSvcName); }else{ - fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + winhttp_fatal("delete", zSvcName, win32_get_last_errmsg()); } }else{ fossil_print("Service '%s' successfully deleted.\n", zSvcName); } CloseServiceHandle(hSvc); @@ -786,11 +836,10 @@ SERVICE_STATUS sstat; LPQUERY_SERVICE_CONFIGW pSvcConfig; LPSERVICE_DESCRIPTIONW pSvcDescr; BOOL bStatus; DWORD nRequired; - const char *zErrFmt = "unable to show service '%s': %s"; static const char *const zSvcTypes[] = { "Driver service", "File system driver service", "Service runs in its own process", "Service shares a process with other services", @@ -813,24 +862,24 @@ verify_all_options(); if( g.argc==4 ){ zSvcName = g.argv[3]; }else if( g.argc>4 ){ - fossil_fatal("to much arguments for show method."); + fossil_fatal("too many arguments for show method."); } hScm = OpenSCManagerW(NULL, NULL, GENERIC_READ); - if( !hScm ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !hScm ) winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), GENERIC_READ); - if( !hSvc ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !hSvc ) winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); /* Get the service configuration */ bStatus = QueryServiceConfigW(hSvc, NULL, 0, &nRequired); if( !bStatus && GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ - fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); } pSvcConfig = fossil_malloc(nRequired); bStatus = QueryServiceConfigW(hSvc, pSvcConfig, nRequired, &nRequired); - if( !bStatus ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !bStatus ) winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); /* Translate the service type */ switch( pSvcConfig->dwServiceType ){ case SERVICE_KERNEL_DRIVER: zSvcType = zSvcTypes[0]; break; case SERVICE_FILE_SYSTEM_DRIVER: zSvcType = zSvcTypes[1]; break; case SERVICE_WIN32_OWN_PROCESS: zSvcType = zSvcTypes[2]; break; @@ -847,19 +896,19 @@ } /* Get the service description. */ bStatus = QueryServiceConfig2W(hSvc, SERVICE_CONFIG_DESCRIPTION, NULL, 0, &nRequired); if( !bStatus && GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ - fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); } pSvcDescr = fossil_malloc(nRequired); bStatus = QueryServiceConfig2W(hSvc, SERVICE_CONFIG_DESCRIPTION, (LPBYTE)pSvcDescr, nRequired, &nRequired); - if( !bStatus ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !bStatus ) winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); /* Retrieves the current status of the specified service. */ bStatus = QueryServiceStatus(hSvc, &sstat); - if( !bStatus ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !bStatus ) winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); /* Translate the current state. */ switch( sstat.dwCurrentState ){ case SERVICE_STOPPED: zSvcState = zSvcStates[0]; break; case SERVICE_START_PENDING: zSvcState = zSvcStates[1]; break; case SERVICE_STOP_PENDING: zSvcState = zSvcStates[2]; break; @@ -889,29 +938,28 @@ }else if( strncmp(zMethod, "start", n)==0 ){ SC_HANDLE hScm; SC_HANDLE hSvc; SERVICE_STATUS sstat; - char *zErrFmt = "unable to start service '%s': %s"; verify_all_options(); if( g.argc==4 ){ zSvcName = g.argv[3]; }else if( g.argc>4 ){ - fossil_fatal("to much arguments for start method."); + fossil_fatal("too many arguments for start method."); } hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); - if( !hScm ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !hScm ) winhttp_fatal("start", zSvcName, win32_get_last_errmsg()); hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), SERVICE_ALL_ACCESS); - if( !hSvc ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !hSvc ) winhttp_fatal("start", zSvcName, win32_get_last_errmsg()); QueryServiceStatus(hSvc, &sstat); if( sstat.dwCurrentState!=SERVICE_RUNNING ){ fossil_print("Starting service '%s'", zSvcName); if( sstat.dwCurrentState!=SERVICE_START_PENDING ){ if( !StartServiceW(hSvc, 0, NULL) ){ - fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + winhttp_fatal("start", zSvcName, win32_get_last_errmsg()); } } while( sstat.dwCurrentState!=SERVICE_RUNNING ){ Sleep(100); fossil_print("."); @@ -926,29 +974,28 @@ }else if( strncmp(zMethod, "stop", n)==0 ){ SC_HANDLE hScm; SC_HANDLE hSvc; SERVICE_STATUS sstat; - char *zErrFmt = "unable to stop service '%s': %s"; verify_all_options(); if( g.argc==4 ){ zSvcName = g.argv[3]; }else if( g.argc>4 ){ - fossil_fatal("to much arguments for stop method."); + fossil_fatal("too many arguments for stop method."); } hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); - if( !hScm ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !hScm ) winhttp_fatal("stop", zSvcName, win32_get_last_errmsg()); hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), SERVICE_ALL_ACCESS); - if( !hSvc ) fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + if( !hSvc ) winhttp_fatal("stop", zSvcName, win32_get_last_errmsg()); QueryServiceStatus(hSvc, &sstat); if( sstat.dwCurrentState!=SERVICE_STOPPED ){ fossil_print("Stopping service '%s'", zSvcName); if( sstat.dwCurrentState!=SERVICE_STOP_PENDING ){ if( !ControlService(hSvc, SERVICE_CONTROL_STOP, &sstat) ){ - fossil_fatal(zErrFmt, zSvcName, win32_get_last_errmsg()); + winhttp_fatal("stop", zSvcName, win32_get_last_errmsg()); } } while( sstat.dwCurrentState!=SERVICE_STOPPED ){ Sleep(100); fossil_print("."); Index: src/wysiwyg.c ================================================================== --- src/wysiwyg.c +++ src/wysiwyg.c @@ -16,13 +16,13 @@ ******************************************************************************* ** ** This file contains code that generates WYSIWYG text editors on ** web pages. */ +#include "config.h" #include <assert.h> #include <ctype.h> -#include "config.h" #include "wysiwyg.h" /* ** Output code for a WYSIWYG editor. The caller must have already generated @@ -228,18 +228,18 @@ @ <div id="wysiwygBox" @ style="resize:both; overflow:auto; width: %d(w)em; height: %d(h)em;" @ contenteditable="true">%s(zContent)</div> @ <script> @ var oDoc; - @ + @ @ /* Initialize the document editor */ @ function initDoc() { @ oDoc = document.getElementById("wysiwygBox"); @ if (!isWysiwyg()) { setDocMode(true); } @ } - @ - @ /* Return true if the document editor is in WYSIWYG mode. Return + @ + @ /* Return true if the document editor is in WYSIWYG mode. Return @ ** false if it is in Markup mode */ @ function isWysiwyg() { @ return document.getElementById("editMode").selectedIndex==0; @ } @ @@ -247,22 +247,34 @@ @ ** to the server */ @ function wysiwygSubmit() { @ if(oDoc.style.whiteSpace=="pre-wrap"){setDocMode(0);} @ document.getElementById("wysiwygValue").value=oDoc.innerHTML; @ } - @ - @ /* Run the editing command if in WYSIWYG mode */ + @ + @ /* Run the editing command if in WYSIWYG mode */ @ function formatDoc(sCmd, sValue) { @ if (isWysiwyg()){ - @ document.execCommand("styleWithCSS", false, false); + @ try { + @ // First, try the W3C draft standard way, which has + @ // been working on all non-IE browsers for a while. + @ // It is also supported by IE11 and higher. + @ document.execCommand("styleWithCSS", false, false); + @ } catch (e) { + @ try { + @ // For IE9 or IE10, this should work. + @ document.execCommand("useCSS", 0, true); + @ } catch (e) { + @ // Ok, that apparently did not work, do nothing. + @ } + @ } @ document.execCommand(sCmd, false, sValue); @ oDoc.focus(); @ } @ } - @ + @ @ /* Change the editing mode. Convert to markup if the argument - @ ** is true and wysiwyg if the argument is false. */ + @ ** is true and wysiwyg if the argument is false. */ @ function setDocMode(bToMarkup) { @ var oContent; @ if (bToMarkup) { @ /* WYSIWYG -> Markup */ @ var linebreak = new RegExp("</p><p>","ig"); Index: src/xfer.c ================================================================== --- src/xfer.c +++ src/xfer.c @@ -94,11 +94,11 @@ db_reset(&q); } } /* -** The aToken[0..nToken-1] blob array is a parse of a "file" line +** The aToken[0..nToken-1] blob array is a parse of a "file" line ** message. This routine finishes parsing that message and does ** a record insert of the file. ** ** The file line is in one of the following two forms: ** @@ -113,20 +113,25 @@ ** be initialized to an empty string. ** ** Any artifact successfully received by this routine is considered to ** be public and is therefore removed from the "private" table. */ -static void xfer_accept_file(Xfer *pXfer, int cloneFlag){ +static void xfer_accept_file( + Xfer *pXfer, + int cloneFlag, + char **pzUuidList, + int *pnUuidList +){ int n; int rid; int srcid = 0; Blob content, hash; int isPriv; - + isPriv = pXfer->nextIsPrivate; pXfer->nextIsPrivate = 0; - if( pXfer->nToken<3 + if( pXfer->nToken<3 || pXfer->nToken>4 || !blob_is_uuid(&pXfer->aToken[1]) || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n) || n<0 || (pXfer->nToken==4 && !blob_is_uuid(&pXfer->aToken[2])) @@ -155,10 +160,12 @@ srcid = 0; pXfer->nFileRcvd++; } rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, 0, isPriv); + Th_AppendToList(pzUuidList, pnUuidList, blob_str(&pXfer->aToken[1]), + blob_size(&pXfer->aToken[1])); remote_has(rid); blob_reset(&content); return; } if( pXfer->nToken==4 ){ @@ -165,10 +172,12 @@ Blob src, next; srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv); if( content_get(srcid, &src)==0 ){ rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, 0, isPriv); + Th_AppendToList(pzUuidList, pnUuidList, blob_str(&pXfer->aToken[1]), + blob_size(&pXfer->aToken[1])); pXfer->nDanglingFile++; db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid); if( !isPriv ) content_make_public(rid); blob_reset(&src); blob_reset(&content); @@ -185,24 +194,25 @@ sha1sum_blob(&content, &hash); if( !blob_eq_str(&pXfer->aToken[1], blob_str(&hash), -1) ){ blob_appendf(&pXfer->err, "content does not match sha1 hash"); } rid = content_put_ex(&content, blob_str(&hash), 0, 0, isPriv); + Th_AppendToList(pzUuidList, pnUuidList, blob_str(&hash), blob_size(&hash)); blob_reset(&hash); if( rid==0 ){ blob_appendf(&pXfer->err, "%s", g.zErrMsg); blob_reset(&content); }else{ if( !isPriv ) content_make_public(rid); - manifest_crosslink(rid, &content); + manifest_crosslink(rid, &content, MC_NO_ERRORS); } assert( blob_is_reset(&content) ); remote_has(rid); } /* -** The aToken[0..nToken-1] blob array is a parse of a "cfile" line +** The aToken[0..nToken-1] blob array is a parse of a "cfile" line ** message. This routine finishes parsing that message and does ** a record insert of the file. The difference between "file" and ** "cfile" is that with "cfile" the content is already compressed. ** ** The file line is in one of the following two forms: @@ -220,21 +230,25 @@ ** be initialized to an empty string. ** ** Any artifact successfully received by this routine is considered to ** be public and is therefore removed from the "private" table. */ -static void xfer_accept_compressed_file(Xfer *pXfer){ +static void xfer_accept_compressed_file( + Xfer *pXfer, + char **pzUuidList, + int *pnUuidList +){ int szC; /* CSIZE */ int szU; /* USIZE */ int rid; int srcid = 0; Blob content; int isPriv; - + isPriv = pXfer->nextIsPrivate; pXfer->nextIsPrivate = 0; - if( pXfer->nToken<4 + if( pXfer->nToken<4 || pXfer->nToken>5 || !blob_is_uuid(&pXfer->aToken[1]) || !blob_is_int(&pXfer->aToken[pXfer->nToken-2], &szU) || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &szC) || szC<0 || szU<0 @@ -261,10 +275,12 @@ srcid = 0; pXfer->nFileRcvd++; } rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, szC, isPriv); + Th_AppendToList(pzUuidList, pnUuidList, blob_str(&pXfer->aToken[1]), + blob_size(&pXfer->aToken[1])); remote_has(rid); blob_reset(&content); } /* @@ -284,11 +300,11 @@ ){ static const char *const azQuery[] = { "SELECT pid FROM plink x" " WHERE cid=%d" " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)", - + "SELECT pid, min(mtime) FROM mlink, event ON mlink.mid=event.objid" " WHERE fid=%d" " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)" }; int i; @@ -295,11 +311,11 @@ Blob src, delta; int size = 0; int srcId = 0; for(i=0; srcId==0 && i<count(azQuery); i++){ - srcId = db_int(0, azQuery[i], rid); + srcId = db_int(0, azQuery[i] /*works-like:"%d"*/, rid); } if( srcId>0 && (pXfer->syncPrivate || !content_is_private(srcId)) && content_get(srcId, &src) ){ @@ -321,11 +337,11 @@ } return size; } /* -** Try to send a file as a native delta. +** Try to send a file as a native delta. ** If successful, return the number of bytes in the delta. ** If we cannot generate an appropriate delta, then send ** nothing and return zero. ** ** Never send a delta against a private artifact. @@ -403,14 +419,14 @@ } if( uuid_is_shunned(blob_str(pUuid)) ){ blob_reset(&uuid); return; } - if( (pXfer->maxTime != -1 && time(NULL) >= pXfer->maxTime) || + if( (pXfer->maxTime != -1 && time(NULL) >= pXfer->maxTime) || pXfer->mxSend<=blob_size(pXfer->pOut) ){ const char *zFormat = isPriv ? "igot %b 1\n" : "igot %b\n"; - blob_appendf(pXfer->pOut, zFormat, pUuid); + blob_appendf(pXfer->pOut, zFormat /*works-like:"%b"*/, pUuid); pXfer->nIGotSent++; blob_reset(&uuid); return; } if( nativeDelta ){ @@ -438,18 +454,18 @@ } remote_has(rid); blob_reset(&uuid); #if 0 if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){ - blob_appendf(pXfer->pOut, "\n", 1); + blob_append(pXfer->pOut, "\n", 1); } #endif } /* ** Send the file identified by rid as a compressed artifact. Basically, -** send the content exactly as it appears in the BLOB table using +** send the content exactly as it appears in the BLOB table using ** a "cfile" card. */ static void send_compressed_file(Xfer *pXfer, int rid){ const char *zContent; const char *zUuid; @@ -498,11 +514,11 @@ pXfer->nFileSent++; } blob_appendf(pXfer->pOut, "%d %d\n", szU, szC); blob_append(pXfer->pOut, zContent, szC); if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){ - blob_appendf(pXfer->pOut, "\n", 1); + blob_append(pXfer->pOut, "\n", 1); } if( !isPrivate && srcIsPrivate ){ blob_reset(&fullContent); } } @@ -515,12 +531,12 @@ ** Except: do not request shunned artifacts. And do not request ** private artifacts if we are not doing a private transfer. */ static void request_phantoms(Xfer *pXfer, int maxReq){ Stmt q; - db_prepare(&q, - "SELECT uuid FROM phantom JOIN blob USING(rid)" + db_prepare(&q, + "SELECT uuid FROM phantom CROSS JOIN blob USING(rid) /*scan*/" " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid) %s", (pXfer->syncPrivate ? "" : " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)") ); while( db_step(&q)==SQLITE_ROW && maxReq-- > 0 ){ @@ -551,12 +567,12 @@ ** Check the signature on an application/x-fossil payload received by ** the HTTP server. The signature is a line of the following form: ** ** login LOGIN NONCE SIGNATURE ** -** The NONCE is the SHA1 hash of the remainder of the input. -** SIGNATURE is the SHA1 checksum of the NONCE concatenated +** The NONCE is the SHA1 hash of the remainder of the input. +** SIGNATURE is the SHA1 checksum of the NONCE concatenated ** with the users password. ** ** The parameters to this routine are ephemeral blobs holding the ** LOGIN, NONCE and SIGNATURE. ** @@ -564,11 +580,11 @@ ** If everything checks out, the USER.CAP column for the USER table ** is consulted to set privileges in the global g variable. ** ** If anything fails to check out, no changes are made to privileges. ** -** Signature generation on the client side is handled by the +** Signature generation on the client side is handled by the ** http_exchange() routine. ** ** Return non-zero for a login failure and zero for success. */ int check_login(Blob *pLogin, Blob *pNonce, Blob *pSig){ @@ -578,11 +594,12 @@ defossilize(zLogin); if( fossil_strcmp(zLogin, "nobody")==0 || fossil_strcmp(zLogin,"anonymous")==0 ){ return 0; /* Anybody is allowed to sync as "nobody" or "anonymous" */ } - if( fossil_strcmp(P("REMOTE_USER"), zLogin)==0 ){ + if( fossil_strcmp(P("REMOTE_USER"), zLogin)==0 + && db_get_boolean("remote_user_ok",0) ){ return 0; /* Accept Basic Authorization */ } db_prepare(&q, "SELECT pw, cap, uid FROM user" " WHERE login=%Q" @@ -694,17 +711,18 @@ blob_reset(&cksum); rid = content_put(&cluster); blob_reset(&cluster); nUncl -= nRow; nRow = 0; - blob_appendf(&deleteWhere, ",%d", rid); + blob_append_sql(&deleteWhere, ",%d", rid); } } db_finalize(&q); db_multi_exec( - "DELETE FROM unclustered WHERE rid NOT IN (0 %s)", - blob_str(&deleteWhere) + "DELETE FROM unclustered WHERE rid NOT IN (0 %s)" + " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=unclustered.rid)", + blob_sql_text(&deleteWhere) ); blob_reset(&deleteWhere); if( nRow>0 ){ md5sum_blob(&cluster, &cksum); blob_appendf(&cluster, "Z %b\n", &cksum); @@ -738,21 +756,21 @@ */ static int send_unclustered(Xfer *pXfer){ Stmt q; int cnt = 0; if( pXfer->resync ){ - db_prepare(&q, + db_prepare(&q, "SELECT uuid, rid FROM blob" " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)" " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)" " AND blob.rid<=%d" " ORDER BY blob.rid DESC", pXfer->resync ); }else{ - db_prepare(&q, + db_prepare(&q, "SELECT uuid FROM unclustered JOIN blob USING(rid)" " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)" " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)" ); @@ -772,11 +790,11 @@ /* ** Send an igot message for every artifact. */ static void send_all(Xfer *pXfer){ Stmt q; - db_prepare(&q, + db_prepare(&q, "SELECT uuid FROM blob " " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)" " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)" ); @@ -820,33 +838,70 @@ static void server_private_xfer_not_authorized(void){ @ error not\sauthorized\sto\ssync\sprivate\scontent } /* -** Run the specified TH1 script, if any, and returns the return code or TH_OK -** when there is no script. -*/ -static int run_script(const char *zScript){ - if( !zScript ){ - return TH_OK; /* No script, return success. */ - } - Th_FossilInit(TH_INIT_DEFAULT); /* Make sure TH1 is ready. */ - return Th_Eval(g.interp, 0, zScript, -1); -} - -/* -** Run the pre-transfer TH1 script, if any, and returns the return code. -*/ -static int run_common_script(void){ - return run_script(db_get("xfer-common-script", 0)); -} - -/* -** Run the post-push TH1 script, if any, and returns the return code. -*/ -static int run_push_script(void){ - return run_script(db_get("xfer-push-script", 0)); +** Return the common TH1 code to evaluate prior to evaluating any other +** TH1 transfer notification scripts. +*/ +const char *xfer_common_code(void){ + return db_get("xfer-common-script", 0); +} + +/* +** Return the TH1 code to evaluate when a push is processed. +*/ +const char *xfer_push_code(void){ + return db_get("xfer-push-script", 0); +} + +/* +** Return the TH1 code to evaluate when a commit is processed. +*/ +const char *xfer_commit_code(void){ + return db_get("xfer-commit-script", 0); +} + +/* +** Return the TH1 code to evaluate when a ticket change is processed. +*/ +const char *xfer_ticket_code(void){ + return db_get("xfer-ticket-script", 0); +} + +/* +** Run the specified TH1 script, if any, and returns 1 on error. +*/ +int xfer_run_script( + const char *zScript, + const char *zUuidOrList, + int bIsList +){ + int rc = TH_OK; + if( !zScript ) return rc; + Th_FossilInit(TH_INIT_DEFAULT); + Th_Store(bIsList ? "uuids" : "uuid", zUuidOrList ? zUuidOrList : ""); + rc = Th_Eval(g.interp, 0, zScript, -1); + if( rc!=TH_OK ){ + fossil_error(1, "%s", Th_GetResult(g.interp, 0)); + } + return rc; +} + +/* +** Runs the pre-transfer TH1 script, if any, and returns its return code. +** This script may be run multiple times. If the script performs actions +** that cannot be redone, it should use an internal [if] guard similar to +** the following: +** +** if {![info exists common_done]} { +** # ... code here +** set common_done 1 +** } +*/ +int xfer_run_common_script(void){ + return xfer_run_script(xfer_common_code(), 0, 0); } /* ** If this variable is set, disable login checks. Used for debugging ** only. @@ -875,10 +930,16 @@ int isClone = 0; int nGimme = 0; int size; int recvConfig = 0; char *zNow; + int rc; + const char *zScript = 0; + char *zUuidList = 0; + int nUuidList = 0; + char **pzUuidList = 0; + int *pnUuidList = 0; if( fossil_strcmp(PD("REQUEST_METHOD","POST"),"POST") ){ fossil_redirect_home(); } g.zLogin = "anonymous"; @@ -904,14 +965,20 @@ db_begin_transaction(); db_multi_exec( "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" ); manifest_crosslink_begin(); - if( run_common_script()==TH_ERROR ){ + rc = xfer_run_common_script(); + if( rc==TH_ERROR ){ cgi_reset_content(); - @ error common\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0)) + @ error common\sscript\sfailed:\s%F(g.zErrMsg) nErr++; + } + zScript = xfer_push_code(); + if( zScript ){ /* NOTE: Are TH1 transfer hooks enabled? */ + pzUuidList = &zUuidList; + pnUuidList = &nUuidList; } while( blob_line(xfer.pIn, &xfer.line) ){ if( blob_buffer(&xfer.line)[0]=='#' ) continue; if( blob_size(&xfer.line)==0 ) continue; xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken)); @@ -926,11 +993,11 @@ cgi_reset_content(); @ error not\sauthorized\sto\swrite nErr++; break; } - xfer_accept_file(&xfer, 0); + xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList); if( blob_size(&xfer.err) ){ cgi_reset_content(); @ error %T(blob_str(&xfer.err)) nErr++; break; @@ -947,11 +1014,11 @@ cgi_reset_content(); @ error not\sauthorized\sto\swrite nErr++; break; } - xfer_accept_compressed_file(&xfer); + xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList); if( blob_size(&xfer.err) ){ cgi_reset_content(); @ error %T(blob_str(&xfer.err)) nErr++; break; @@ -992,12 +1059,12 @@ }else{ server_private_xfer_not_authorized(); } } }else - - + + /* pull SERVERCODE PROJECTCODE ** push SERVERCODE PROJECTCODE ** ** The client wants either send or receive. The server should ** verify that the project code matches. @@ -1101,14 +1168,14 @@ ){ cgi_reset_content(); @ error login\sfailed nErr++; break; - } + } } }else - + /* reqconfig NAME ** ** Request a configuration value */ if( blob_eq(&xfer.aToken[0], "reqconfig") @@ -1126,11 +1193,11 @@ /* Old style configuration transfer */ send_legacy_config_card(&xfer, zName); } } }else - + /* config NAME SIZE \n CONTENT ** ** Receive a configuration value from the client. This is only ** permitted for high-privilege users. */ @@ -1153,11 +1220,11 @@ configure_receive(zName, &content, CONFIGSET_ALL); blob_reset(&content); blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR); }else - + /* cookie TEXT ** ** A cookie contains a arbitrary-length argument that is server-defined. ** The argument must be encoded so as not to contain any whitespace. @@ -1228,19 +1295,26 @@ { cgi_reset_content(); @ error bad\scommand:\s%F(blob_str(&xfer.line)) } blobarray_reset(xfer.aToken, xfer.nToken); + blob_reset(&xfer.line); } if( isPush ){ - if( run_push_script()==TH_ERROR ){ - cgi_reset_content(); - @ error push\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0)) - nErr++; + if( rc==TH_OK ){ + rc = xfer_run_script(zScript, zUuidList, 1); + if( rc==TH_ERROR ){ + cgi_reset_content(); + @ error push\sscript\sfailed:\s%F(g.zErrMsg) + nErr++; + } } request_phantoms(&xfer, 500); } + if( zUuidList ){ + Th_Free(g.interp, zUuidList); + } if( isClone && nGimme==0 ){ /* The initial "clone" message from client to server contains no ** "gimme" cards. On that initial message, send the client an "igot" ** card for every artifact currently in the repository. This will ** cause the client to create phantoms for all artifacts, which will @@ -1255,20 +1329,22 @@ if( xfer.syncPrivate ) send_private(&xfer); } if( recvConfig ){ configure_finalize_receive(); } - manifest_crosslink_end(); + db_multi_exec("DROP TABLE onremote"); + manifest_crosslink_end(MC_PERMIT_HOOKS); /* Send the server timestamp last, in case prior processing happened ** to use up a significant fraction of our time window. */ zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')"); @ # timestamp %s(zNow) free(zNow); db_end_transaction(0); + configure_rebuild(); } /* ** COMMAND: test-xfer ** @@ -1327,11 +1403,11 @@ static double fossil_fabs(double x){ return x>0.0 ? x : -x; } /* -** Sync to the host identified in g.urlName and g.urlPath. This +** Sync to the host identified in g.url.name and g.url.path. This ** routine is called by the client. ** ** Records are pushed to the server if pushFlag is true. Records ** are pulled if pullFlag is true. A full sync occurs if both are ** true. @@ -1366,11 +1442,11 @@ int nArtifactRcvd = 0; /* Total artifacts received */ const char *zOpType = 0;/* Push, Pull, Sync, Clone */ double rSkew = 0.0; /* Maximum time skew */ if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH; - if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE))==0 + if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE))==0 && configRcvMask==0 && configSendMask==0 ) return 0; transport_stats(0, 0, 1); socket_global_init(); memset(&xfer, 0, sizeof(xfer)); @@ -1381,15 +1457,10 @@ if( syncFlags & SYNC_PRIVATE ){ g.perm.Private = 1; xfer.syncPrivate = 1; } - db_begin_transaction(); - db_record_repository_filename(0); - db_multi_exec( - "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" - ); blobarray_zero(xfer.aToken, count(xfer.aToken)); blob_zero(&send); blob_zero(&recv); blob_zero(&xfer.err); blob_zero(&xfer.line); @@ -1424,28 +1495,33 @@ blob_appendf(&send, "push %s %s\n", zSCode, zPCode); nCardSent++; if( (syncFlags & SYNC_PULL)==0 ) zOpType = "Push"; if( (syncFlags & SYNC_RESYNC)!=0 ) xfer.resync = 0x7fffffff; } - manifest_crosslink_begin(); - transport_global_startup(); if( syncFlags & SYNC_VERBOSE ){ - fossil_print(zLabelFormat, "", "Bytes", "Cards", "Artifacts", "Deltas"); + fossil_print(zLabelFormat /*works-like:"%s%s%s%s%d"*/, + "", "Bytes", "Cards", "Artifacts", "Deltas"); } while( go ){ int newPhantom = 0; char *zRandomness; + db_begin_transaction(); + db_record_repository_filename(0); + db_multi_exec( + "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" + ); + manifest_crosslink_begin(); /* Send make the most recently received cookie. Let the server ** figure out if this is a cookie that it cares about. */ zCookie = db_get("cookie", 0); if( zCookie ){ blob_appendf(&send, "cookie %s\n", zCookie); } - + /* Generate gimme cards for phantoms and leaf cards ** for all leaves. */ if( (syncFlags & SYNC_PULL)!=0 || ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1) @@ -1457,11 +1533,11 @@ nCardSent += send_unclustered(&xfer); if( syncFlags & SYNC_PRIVATE ) send_private(&xfer); } /* Send configuration parameter requests. On a clone, delay sending - ** this until the second cycle since the login card might fail on + ** this until the second cycle since the login card might fail on ** the first cycle. */ if( configRcvMask && ((syncFlags & SYNC_CLONE)==0 || nCycle>0) ){ const char *zName; if( zOpType==0 ) zOpType = "Pull"; @@ -1504,35 +1580,40 @@ */ zRandomness = db_text(0, "SELECT hex(randomblob(20))"); blob_appendf(&send, "# %s\n", zRandomness); free(zRandomness); + if( syncFlags & SYNC_VERBOSE ){ + fossil_print("waiting for server..."); + } + fflush(stdout); /* Exchange messages with the server */ + if( http_exchange(&send, &recv, (syncFlags & SYNC_CLONE)==0 || nCycle>0, + MAX_REDIRECTS) ){ + nErr++; + go = 2; + break; + } + + /* Output current stats */ if( syncFlags & SYNC_VERBOSE ){ - fossil_print(zValueFormat, "Sent:", + fossil_print(zValueFormat /*works-like:"%s%d%d%d%d"*/, "Sent:", blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent, xfer.nFileSent, xfer.nDeltaSent); }else{ nRoundtrip++; nArtifactSent += xfer.nFileSent + xfer.nDeltaSent; - fossil_print(zBriefFormat, nRoundtrip, nArtifactSent, nArtifactRcvd); + fossil_print(zBriefFormat /*works-like:"%d%d%d"*/, + nRoundtrip, nArtifactSent, nArtifactRcvd); } nCardSent = 0; nCardRcvd = 0; xfer.nFileSent = 0; xfer.nDeltaSent = 0; xfer.nGimmeSent = 0; xfer.nIGotSent = 0; - if( syncFlags & SYNC_VERBOSE ){ - fossil_print("waiting for server..."); - } - fflush(stdout); - if( http_exchange(&send, &recv, (syncFlags & SYNC_CLONE)==0 || nCycle>0, - MAX_REDIRECTS) ){ - nErr++; - break; - } + lastPctDone = -1; blob_reset(&send); rArrivalTime = db_double(0.0, "SELECT julianday('now')"); /* Send the send-private pragma if we are trying to sync private data */ @@ -1585,21 +1666,21 @@ ** file UUID DELTASRC SIZE \n CONTENT ** ** Receive a file transmitted from the server. */ if( blob_eq(&xfer.aToken[0],"file") ){ - xfer_accept_file(&xfer, (syncFlags & SYNC_CLONE)!=0); + xfer_accept_file(&xfer, (syncFlags & SYNC_CLONE)!=0, 0, 0); nArtifactRcvd++; }else /* cfile UUID USIZE CSIZE \n CONTENT ** cfile UUID DELTASRC USIZE CSIZE \n CONTENT ** ** Receive a compressed file transmitted from the server. */ if( blob_eq(&xfer.aToken[0],"cfile") ){ - xfer_accept_compressed_file(&xfer); + xfer_accept_compressed_file(&xfer, 0, 0); nArtifactRcvd++; }else /* gimme UUID ** @@ -1614,20 +1695,20 @@ if( syncFlags & SYNC_PUSH ){ int rid = rid_from_uuid(&xfer.aToken[1], 0, 0); if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0); } }else - + /* igot UUID ?PRIVATEFLAG? ** ** Server announces that it has a particular file. If this is ** not a file that we have and we are pulling, then create a ** phantom to cause this file to be requested on the next cycle. ** Always remember that the server has this file so that we do ** not transmit it by accident. ** - ** If the PRIVATE argument exists and is 1, then the file is + ** If the PRIVATE argument exists and is 1, then the file is ** private. Pretend it does not exists if we are not pulling ** private files. */ if( xfer.nToken>=2 && blob_eq(&xfer.aToken[0], "igot") @@ -1644,34 +1725,30 @@ rid = content_new(blob_str(&xfer.aToken[1]), isPriv); if( rid ) newPhantom = 1; } remote_has(rid); }else - - + + /* push SERVERCODE PRODUCTCODE ** ** Should only happen in response to a clone. This message tells ** the client what product to use for the new database. */ if( blob_eq(&xfer.aToken[0],"push") && xfer.nToken==3 && (syncFlags & SYNC_CLONE)!=0 - && blob_is_uuid(&xfer.aToken[1]) && blob_is_uuid(&xfer.aToken[2]) ){ - if( blob_eq_str(&xfer.aToken[1], zSCode, -1) ){ - fossil_fatal("server loop"); - } if( zPCode==0 ){ zPCode = mprintf("%b", &xfer.aToken[2]); db_set("project-code", zPCode, 0); } if( cloneSeqno>0 ) blob_appendf(&send, "clone 3 %d\n", cloneSeqno); nCardSent++; }else - + /* config NAME SIZE \n CONTENT ** ** Receive a configuration value from the server. ** ** The received configuration setting is silently ignored if it was @@ -1689,11 +1766,11 @@ nArtifactRcvd++; blob_reset(&content); blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR); }else - + /* cookie TEXT ** ** The server might include a cookie in its reply. The client ** should remember this cookie and send it back to the server ** in its next query. @@ -1731,15 +1808,15 @@ ** ** Print a message. Similar to "error" but does not stop processing. ** ** If the "login failed" message is seen, clear the sync password prior ** to the next cycle. - */ + */ if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){ char *zMsg = blob_terminate(&xfer.aToken[1]); defossilize(zMsg); - if( (syncFlags & SYNC_PUSH) && zMsg && strglob("pull only *", zMsg) ){ + if( (syncFlags & SYNC_PUSH) && zMsg && sqlite3_strglob("pull only *", zMsg)==0 ){ syncFlags &= ~SYNC_PUSH; zMsg = 0; } if( zMsg && zMsg[0] ){ fossil_force_newline(); @@ -1748,11 +1825,11 @@ }else /* pragma NAME VALUE... ** ** The server can send pragmas to try to convey meta-information to - ** the client. These are informational only. Unknown pragmas are + ** the client. These are informational only. Unknown pragmas are ** silently ignored. */ if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){ }else @@ -1761,25 +1838,32 @@ ** Report an error and abandon the sync session. ** ** Except, when cloning we will sometimes get an error on the ** first message exchange because the project-code is unknown ** and so the login card on the request was invalid. The project-code - ** is returned in the reply before the error card, so second and + ** is returned in the reply before the error card, so second and ** subsequent messages should be OK. Nevertheless, we need to ignore ** the error card on the first message of a clone. - */ + */ if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){ if( (syncFlags & SYNC_CLONE)==0 || nCycle>0 ){ char *zMsg = blob_terminate(&xfer.aToken[1]); defossilize(zMsg); fossil_force_newline(); fossil_print("Error: %s\n", zMsg); if( fossil_strcmp(zMsg, "login failed")==0 ){ if( nCycle<2 ){ - g.urlPasswd = 0; + g.url.passwd = 0; go = 1; - if( g.cgiOutput==0 ) url_prompt_for_password(); + if( g.cgiOutput==0 ){ + g.url.flags |= URL_PROMPT_PW; + g.url.flags &= ~URL_PROMPTED; + url_prompt_for_password(); + url_remember(); + } + }else{ + nErr++; } }else{ blob_appendf(&xfer.err, "server says: %s\n", zMsg); nErr++; } @@ -1814,15 +1898,16 @@ ){ configure_finalize_receive(); } origConfigRcvMask = 0; if( nCardRcvd>0 && (syncFlags & SYNC_VERBOSE) ){ - fossil_print(zValueFormat, "Received:", + fossil_print(zValueFormat /*works-like:"%s%d%d%d%d"*/, "Received:", blob_size(&recv), nCardRcvd, xfer.nFileRcvd, xfer.nDeltaRcvd + xfer.nDanglingFile); }else{ - fossil_print(zBriefFormat, nRoundtrip, nArtifactSent, nArtifactRcvd); + fossil_print(zBriefFormat /*works-like:"%d%d%d"*/, + nRoundtrip, nArtifactSent, nArtifactRcvd); } blob_reset(&recv); nCycle++; /* If we received one or more files on the previous exchange but @@ -1840,11 +1925,11 @@ xfer.nFileRcvd = 0; xfer.nDeltaRcvd = 0; xfer.nDanglingFile = 0; /* If we have one or more files queued to send, then go - ** another round + ** another round */ if( xfer.nFileSent+xfer.nDeltaSent>0 ){ go = 1; } @@ -1854,11 +1939,19 @@ /* Stop the cycle if the server sends a "clone_seqno 0" card and ** we have gone at least two rounds. Always go at least two rounds ** on a clone in order to be sure to retrieve the configuration ** information which is only sent on the second round. */ - if( cloneSeqno<=0 && nCycle>1 ) go = 0; + if( cloneSeqno<=0 && nCycle>1 ) go = 0; + db_multi_exec("DROP TABLE onremote"); + if( go ){ + manifest_crosslink_end(MC_PERMIT_HOOKS); + }else{ + manifest_crosslink_end(MC_PERMIT_HOOKS); + content_enable_dephantomize(1); + } + db_end_transaction(0); }; transport_stats(&nSent, &nRcvd, 1); if( (rSkew*24.0*3600.0) > 10.0 ){ fossil_warning("*** time skew *** server is fast by %s", db_timespan_name(rSkew)); @@ -1869,15 +1962,17 @@ g.clockSkewSeen = 1; } fossil_force_newline(); fossil_print( - "%s finished with %lld bytes sent, %lld bytes received\n", - zOpType, nSent, nRcvd); - transport_close(); - transport_global_shutdown(); - db_multi_exec("DROP TABLE onremote"); - manifest_crosslink_end(); - content_enable_dephantomize(1); - db_end_transaction(0); + "%s done, sent: %lld received: %lld ip: %s\n", + zOpType, nSent, nRcvd, g.zIpAddr); + transport_close(&g.url); + transport_global_shutdown(&g.url); + if( nErr && go==2 ){ + db_multi_exec("DROP TABLE onremote"); + manifest_crosslink_end(MC_PERMIT_HOOKS); + content_enable_dephantomize(1); + db_end_transaction(0); + } return nErr; } Index: src/xfersetup.c ================================================================== --- src/xfersetup.c +++ src/xfersetup.c @@ -27,20 +27,68 @@ ** WEBPAGE: xfersetup */ void xfersetup_page(void){ login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } style_header("Transfer Setup"); - @ <table border="0" cellspacing="20"> + + @ <table class="xfersetup"> setup_menu_entry("Common", "xfersetup_com", "Common TH1 code run before all transfer request processing."); setup_menu_entry("Push", "xfersetup_push", "Specific TH1 code to run after \"push\" transfer requests."); + setup_menu_entry("Commit", "xfersetup_commit", + "Specific TH1 code to run after processing a commit."); + setup_menu_entry("Ticket", "xfersetup_ticket", + "Specific TH1 code to run after processing a ticket change."); @ </table> + + url_parse(0, 0); + if( g.url.protocol ){ + unsigned syncFlags; + const char *zButton; + char *zWarning; + + if( db_get_boolean("dont-push", 0) ){ + syncFlags = SYNC_PULL; + zButton = "Pull"; + zWarning = 0; + }else{ + syncFlags = SYNC_PUSH | SYNC_PULL; + zButton = "Synchronize"; + zWarning = mprintf("WARNING: Pushing to \"%s\" is enabled.", + g.url.canonical); + } + @ <p>Press the <strong>%h(zButton)</strong> button below to + @ synchronize with the <em>%h(g.url.canonical)</em> repository now.<br/> + @ This may be useful when testing the various transfer scripts.</p> + @ <p>You can use the <code>http -async</code> command in your scripts, but + @ make sure the <code>th1-uri-regexp</code> setting is set first.</p> + if( zWarning ){ + @ + @ <big><b>%h(zWarning)</b></big> + free(zWarning); + } + @ + @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div> + login_insert_csrf_secret(); + @ <input type="submit" name="sync" value="%h(zButton)" /> + @ </div></form> + @ + if( P("sync") ){ + user_select(); + url_enable_proxy(0); + @ <pre class="xfersetup"> + client_sync(syncFlags, 0, 0); + @ </pre> + } + } + style_footer(); } /* ** Common implementation for the transfer setup editor pages. @@ -57,11 +105,12 @@ const char *z; int isSubmit; login_check_credentials(); if( !g.perm.Setup ){ - login_needed(); + login_needed(0); + return; } if( P("setup") ){ cgi_redirect("xfersetup"); } isSubmit = P("submit")!=0; @@ -88,18 +137,18 @@ } @ <form action="%s(g.zTop)/%s(g.zPath)" method="post"><div> login_insert_csrf_secret(); @ <p>%s(zDesc)</p> @ <textarea name="x" rows="%d(height)" cols="80">%h(z)</textarea> - @ <blockquote><p> + @ <p> @ <input type="submit" name="submit" value="Apply Changes" /> @ <input type="submit" name="clear" value="Revert To Default" /> @ <input type="submit" name="setup" value="Cancel" /> - @ </p></blockquote> + @ </p> @ </div></form> - @ <hr /> if ( zDfltValue ){ + @ <hr /> @ <h2>Default %s(zTitle)</h2> @ <blockquote><pre> @ %h(zDfltValue) @ </pre></blockquote> } @@ -132,17 +181,58 @@ /* ** WEBPAGE: xfersetup_push */ void xfersetup_push_page(void){ static const char zDesc[] = - @ Enter TH1 script that runs after processing "push" transfer requests. + @ Enter TH1 script that runs after processing <strong>push</strong> + @ transfer requests. ; xfersetup_generic( "Transfer Push Script", "xfer-push-script", zDefaultXferPush, zDesc, 0, + 0, + 30 + ); +} + +static const char *zDefaultXferCommit = 0; + +/* +** WEBPAGE: xfersetup_commit +*/ +void xfersetup_commit_page(void){ + static const char zDesc[] = + @ Enter TH1 script that runs when a commit is processed. + ; + xfersetup_generic( + "Transfer Commit Script", + "xfer-commit-script", + zDefaultXferCommit, + zDesc, + 0, + 0, + 30 + ); +} + +static const char *zDefaultXferTicket = 0; + +/* +** WEBPAGE: xfersetup_ticket +*/ +void xfersetup_ticket_page(void){ + static const char zDesc[] = + @ Enter TH1 script that runs when a ticket change is processed. + ; + xfersetup_generic( + "Transfer Ticket Script", + "xfer-ticket-script", + zDefaultXferTicket, + zDesc, + 0, 0, 30 ); } Index: src/zip.c ================================================================== --- src/zip.c +++ src/zip.c @@ -15,13 +15,18 @@ ** ******************************************************************************* ** ** This file contains code used to generate ZIP archives. */ +#include "config.h" #include <assert.h> -#include <zlib.h> -#include "config.h" +#if defined(FOSSIL_ENABLE_MINIZ) +# define MINIZ_HEADER_FILE_ONLY +# include "miniz.c" +#else +# include <zlib.h> +#endif #include "zip.h" /* ** Write a 16- or 32-bit integer as little-endian into the given buffer. */ @@ -81,11 +86,11 @@ ** Set the date and time from a julian day number. */ void zip_set_timedate(double rDate){ char *zDate = db_text(0, "SELECT datetime(%.17g)", rDate); zip_set_timedate_from_str(zDate); - free(zDate); + fossil_free(zDate); unixTime = (rDate - 2440587.5)*86400.0; } /* ** If the given filename includes one or more directory entries, make @@ -158,17 +163,17 @@ put16(&zHdr[8], iMethod); put16(&zHdr[10], dosTime); put16(&zHdr[12], dosDate); put16(&zHdr[26], nameLen); put16(&zHdr[28], 13); - + put16(&zExTime[0], 0x5455); put16(&zExTime[2], 9); zExTime[4] = 3; put32(&zExTime[5], unixTime); put32(&zExTime[9], unixTime); - + /* Write the header and filename. */ iStart = blob_size(&body); blob_append(&body, zHdr, 30); @@ -202,19 +207,19 @@ blob_append(&body, zOutBuf, toOut); }while( stream.avail_out==0 ); nByte = stream.total_in; nByteCompr = stream.total_out; deflateEnd(&stream); - + /* Go back and write the header, now that we know the compressed file size. */ z = &blob_buffer(&body)[iStart]; put32(&z[14], iCRC); put32(&z[18], nByteCompr); put32(&z[22], nByte); } - + /* Make an entry in the tables of contents */ memset(zBuf, 0, sizeof(zBuf)); put32(&zBuf[0], 0x02014b50); put16(&zBuf[4], 0x0317); @@ -267,13 +272,13 @@ blob_reset(&toc); *pZip = body; blob_zero(&body); nEntry = 0; for(i=0; i<nDir; i++){ - free(azDir[i]); + fossil_free(azDir[i]); } - free(azDir); + fossil_free(azDir); nDir = 0; azDir = 0; } /* @@ -322,11 +327,11 @@ Blob mfile, hash, file; Manifest *pManifest; ManifestFile *pFile; Blob filename; int nPrefix; - + content_get(rid, &mfile); if( blob_size(&mfile)==0 ){ blob_zero(pZip); return; } @@ -345,12 +350,13 @@ zip_set_timedate(pManifest->rDate); if( db_get_boolean("manifest", 0) ){ blob_append(&filename, "manifest", -1); zName = blob_str(&filename); zip_add_folders(zName); - zip_add_file(zName, &mfile, 0); sha1sum_blob(&mfile, &hash); + sterilize_manifest(&mfile); + zip_add_file(zName, &mfile, 0); blob_reset(&mfile); blob_append(&hash, "\n", 1); blob_resize(&filename, nPrefix); blob_append(&filename, "manifest.uuid", -1); zName = blob_str(&filename); @@ -393,10 +399,14 @@ int rid; Blob zip; const char *zName; zName = find_option("name", 0, 1); db_find_and_open_repository(0, 0); + + /* We should be done with options.. */ + verify_all_options(); + if( g.argc!=4 ){ usage("VERSION OUTPUTFILE"); } rid = name_to_typed_rid(g.argv[2],"ci"); if( zName==0 ){ @@ -418,36 +428,74 @@ ** WEBPAGE: zip ** URL: /zip/RID.zip ** ** Generate a ZIP archive for the baseline. ** Return that ZIP archive as the HTTP reply content. +** +** Optional URL Parameters: +** +** - name=NAME[.zip] is the name of the output file. Defaults to +** something project/version-specific. The base part of the +** name, up to the last dot, is used as the top-most directory +** name in the output file. +** +** - uuid=the version to zip (may be a tag/branch name). +** Defaults to "trunk". +** */ void baseline_zip_page(void){ int rid; char *zName, *zRid; int nName, nRid; Blob zip; + char *zKey; login_check_credentials(); - if( !g.perm.Zip ){ login_needed(); return; } + if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } + load_control(); zName = mprintf("%s", PD("name","")); nName = strlen(zName); zRid = mprintf("%s", PD("uuid","trunk")); nRid = strlen(zRid); - for(nName=strlen(zName)-1; nName>5; nName--){ - if( zName[nName]=='.' ){ - zName[nName] = 0; - break; + if( nName>4 && fossil_strcmp(&zName[nName-4], ".zip")==0 ){ + /* Special case: Remove the ".zip" suffix. */ + nName -= 4; + zName[nName] = 0; + }else{ + /* If the file suffix is not ".zip" then just remove the + ** suffix up to and including the last "." */ + for(nName=strlen(zName)-1; nName>5; nName--){ + if( zName[nName]=='.' ){ + zName[nName] = 0; + break; + } } } rid = name_to_typed_rid(nRid?zRid:zName,"ci"); if( rid==0 ){ @ Not found return; + } + if( referred_from_login() ){ + style_header("ZIP Archive Download"); + @ <form action='%R/zip'> + cgi_query_parameters_to_hidden(); + @ <p>ZIP Archive named <b>%h(zName).zip</b> holding the content + @ of check-in <b>%h(zRid)</b>: + @ <input type="submit" value="Download" /> + @ </form> + style_footer(); + return; } if( nRid==0 && nName>10 ) zName[10] = 0; - zip_of_baseline(rid, &zip, zName); - free( zName ); - free( zRid ); + zKey = db_text(0, "SELECT '/zip/'||uuid||'/%q' FROM blob WHERE rid=%d",zName,rid); + blob_zero(&zip); + if( cache_read(&zip, zKey)==0 ){ + zip_of_baseline(rid, &zip, zName); + cache_write(&zip, zKey); + } + fossil_free( zName ); + fossil_free( zRid ); + fossil_free( zKey ); cgi_set_content(&zip); cgi_set_content_type("application/zip"); } ADDED test/comment.test Index: test/comment.test ================================================================== --- /dev/null +++ test/comment.test @@ -0,0 +1,318 @@ +# +# Copyright (c) 2014 D. Richard Hipp +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the Simplified BSD License (also +# known as the "2-Clause License" or "FreeBSD License".) +# +# This program is distributed in the hope that it will be useful, +# but without any warranty; without even the implied warranty of +# merchantability or fitness for a particular purpose. +# +# Author contact information: +# drh@hwaci.com +# http://www.hwaci.com/drh/ +# +############################################################################ +# +# Test comment formatting and printing. +# + +fossil test-comment-format "" "" +test comment-1 {$RESULT eq "\n(1 lines output)"} + +############################################################################### + +fossil test-comment-format --decode "" "" +test comment-2 {$RESULT eq "\n(1 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 " " "this is a short comment." +test comment-3 {$RESULT eq " this is a short comment.\n(1 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 --decode " " "this is a short comment." +test comment-4 {$RESULT eq " this is a short comment.\n(1 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 "*PREFIX* " "this is a short comment." +test comment-5 {$RESULT eq "*PREFIX* this is a short c\n omment.\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 --decode "*PREFIX* " "this is a short comment." +test comment-6 {$RESULT eq "*PREFIX* this is a short c\n omment.\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 "" "this\\sis\\sa\\sshort\\scomment." +test comment-7 {$RESULT eq "this\\sis\\sa\\sshort\\scommen\nt.\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 --decode "" "this\\sis\\sa\\sshort\\scomment." +test comment-8 {$RESULT eq "this is a short comment.\n(1 lines output)"} + +############################################################################### + +fossil test-comment-format --width 78 --decode --trimspace "HH:MM:SS " "this is a long comment that should span multiple lines if the test is working correctly." +test comment-9 {$RESULT eq "HH:MM:SS this is a long comment that should span multiple lines if the test is\n working correctly.\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 78 --decode --trimspace "HH:MM:SS " "this is a long comment that should span multiple lines if the test is working correctly. more text here describing the issue.\\nanother line here..................................................................................*" +test comment-10 {$RESULT eq "HH:MM:SS this is a long comment that should span multiple lines if the test is\n working correctly. more text here describing the issue.\n another line here....................................................\n ..............................*\n(4 lines output)"} + +############################################################################### + +fossil test-comment-format --width 78 "HH:MM:SS " "....................................................................................*" +test comment-11 {$RESULT eq "HH:MM:SS .....................................................................\n ...............*\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 78 "HH:MM:SS " ".....................................................................*" 78 +test comment-12 {$RESULT eq "HH:MM:SS .....................................................................\n *\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 "*TEST* " "this\tis a test." +test comment-13 {$RESULT eq "*TEST* this\tis a te\n st.\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 "*TEST* " "this is a test......................................................................................................................." +test comment-14 {$RESULT eq "*TEST* this is a test.......................................\n .....................................................\n ...........................\n(3 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --wordbreak "*TEST* " "this is a test......................................................................................................................." +test comment-15 {$RESULT eq "*TEST* this is a\n test.................................................\n .....................................................\n .................\n(4 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 "*TEST* " "this is a test......................................................................................................................." +test comment-16 {$RESULT eq "*TEST* this is a\n test.................................................\n .....................................................\n .................\n(4 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --wordbreak "*TEST* " "this is a test......................................................................................................................." +test comment-17 {$RESULT eq "*TEST* this is a\n test.................................................\n .....................................................\n .................\n(4 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 "*TEST* " "one two three four five six seven eight nine ten eleven twelve" +test comment-18 {$RESULT eq "*TEST* one two three four five six seven eight nine ten elev\n en twelve\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --wordbreak "*TEST* " "one two three four five six seven eight nine ten eleven twelve" +test comment-19 {$RESULT eq "*TEST* one two three four five six seven eight nine ten\n eleven twelve\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 "*TEST* " "one two three four five six seven eight nine ten eleven twelve" +test comment-20 {$RESULT eq "*TEST* one two three four five\n six seven eight nine ten\n eleven twelve\n(3 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --wordbreak "*TEST* " "one two three four five six seven eight nine ten eleven twelve" +test comment-21 {$RESULT eq "*TEST* one two three four five\n six seven eight nine ten\n eleven twelve\n(3 lines output)"} + +############################################################################### + +fossil test-comment-format --legacy "" "" +test comment-22 {$RESULT eq "\n(1 lines output)"} + +############################################################################### + +fossil test-comment-format --legacy --decode "" "" +test comment-23 {$RESULT eq "\n(1 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 --legacy " " "this is a short comment." +test comment-24 {$RESULT eq " this is a short comment.\n(1 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 --legacy --decode " " "this is a short comment." +test comment-25 {$RESULT eq " this is a short comment.\n(1 lines output)"} + +############################################################################### + +fossil test-comment-format --width 25 --legacy "*PREFIX* " "this is a short comment." +test comment-26 {$RESULT eq "*PREFIX* this is a short\n comment.\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 25 --legacy --decode "*PREFIX* " "this is a short comment." +test comment-27 {$RESULT eq "*PREFIX* this is a short\n comment.\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 --legacy "" "this\\sis\\sa\\sshort\\scomment." +test comment-28 {$RESULT eq "this\\sis\\sa\\sshort\\scommen\nt.\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 --legacy --decode "" "this\\sis\\sa\\sshort\\scomment." +test comment-29 {$RESULT eq "this is a short comment.\n(1 lines output)"} + +############################################################################### + +fossil test-comment-format --width 78 --legacy --decode "HH:MM:SS " "this is a long comment that should span multiple lines if the test is working correctly." +test comment-30 {$RESULT eq "HH:MM:SS this is a long comment that should span multiple lines if the test\n is working correctly.\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 78 --legacy --decode "HH:MM:SS " "this is a long comment that should span multiple lines if the test is working correctly. more text here describing the issue.\\nanother line here..................................................................................*" +test comment-31 {$RESULT eq "HH:MM:SS this is a long comment that should span multiple lines if the test\n is working correctly. more text here describing the issue. another\n line\n here.................................................................\n .................*\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 78 --legacy "HH:MM:SS " "....................................................................................*" +test comment-32 {$RESULT eq "HH:MM:SS .....................................................................\n ...............*\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 78 --legacy "HH:MM:SS " ".....................................................................*" +test comment-33 {$RESULT eq "HH:MM:SS .....................................................................\n *\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 26 --legacy "*TEST* " "this\tis a test." +test comment-34 {$RESULT eq "*TEST* this is a test.\n(1 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --legacy "*TEST* " "this is a test......................................................................................................................." +test comment-35 {$RESULT eq "*TEST* this is a\n test.................................................\n .....................................................\n .................\n(4 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --legacy --wordbreak "*TEST* " "this is a test......................................................................................................................." +test comment-36 {$RESULT eq "*TEST* this is a\n test.................................................\n .....................................................\n .................\n(4 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --legacy "*TEST* " "this is a test......................................................................................................................." +test comment-37 {$RESULT eq "*TEST* this is a\n test.................................................\n .....................................................\n .................\n(4 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --legacy --wordbreak "*TEST* " "this is a test......................................................................................................................." +test comment-38 {$RESULT eq "*TEST* this is a\n test.................................................\n .....................................................\n .................\n(4 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --legacy "*TEST* " "one two three four five six seven eight nine ten eleven twelve" +test comment-39 {$RESULT eq "*TEST* one two three four five six seven eight nine ten\n eleven twelve\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --legacy --wordbreak "*TEST* " "one two three four five six seven eight nine ten eleven twelve" +test comment-40 {$RESULT eq "*TEST* one two three four five six seven eight nine ten\n eleven twelve\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --legacy "*TEST* " "one two three four five six seven eight nine ten eleven twelve" +test comment-41 {$RESULT eq "*TEST* one two three four five six seven eight nine ten\n eleven twelve\n(2 lines output)"} + +############################################################################### + +fossil test-comment-format --width 60 --legacy --wordbreak "*TEST* " "one two three four five six seven eight nine ten eleven twelve" +test comment-42 {$RESULT eq "*TEST* one two three four five six seven eight nine ten\n eleven twelve\n(2 lines output)"} + +############################################################################### + +set orig "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\\nxxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\\nxxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\\nxxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\\nxxxxxxx." +fossil test-comment-format --width 73 --decode --origbreak "" $orig +test comment-43 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\nxxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\nxxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 73 --decode --origbreak "" $orig $orig +test comment-44 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\nxxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\nxxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 73 --decode --origbreak "" "00:00:00 \[0000000000\] *CURRENT* $orig" $orig +test comment-45 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \nxxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\nxxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\nxxxxxxx.\n(6 lines output)"} + +############################################################################### + +fossil test-comment-format --width 82 --indent 9 --decode --origbreak " " $orig +test comment-46 {$RESULT eq " xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n xxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\n xxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\n xxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\n xxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 82 --indent 9 --decode --origbreak " " $orig $orig +test comment-47 {$RESULT eq " xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n xxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\n xxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\n xxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\n xxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 82 --indent 9 --decode --origbreak "00:00:00 " "\[0000000000\] *CURRENT* $orig" $orig +test comment-48 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \n xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n xxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\n xxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\n xxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\n xxxxxxx.\n(6 lines output)"} + +############################################################################### + +fossil test-comment-format --width 72 --decode --trimspace --origbreak "" $orig +test comment-49 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\nxxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\nxxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 72 --decode --trimspace --origbreak "" $orig $orig +test comment-50 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\nxxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\nxxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 72 --decode --trimspace --origbreak "" "00:00:00 \[0000000000\] *CURRENT* $orig" $orig +test comment-51 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \nxxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\nxxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\nxxxxxxx.\n(6 lines output)"} + +############################################################################### + +fossil test-comment-format --width 81 --indent 9 --decode --trimspace --origbreak " " $orig +test comment-52 {$RESULT eq " xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n xxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\n xxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\n xxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\n xxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 81 --indent 9 --decode --trimspace --origbreak " " $orig $orig +test comment-53 {$RESULT eq " xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n xxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\n xxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\n xxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\n xxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 81 --indent 9 --decode --trimspace --origbreak "00:00:00 " "\[0000000000\] *CURRENT* $orig" $orig +test comment-54 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \n xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n xxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\n xxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\n xxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\n xxxxxxx.\n(6 lines output)"} + +############################################################################### + +fossil test-comment-format --width 72 --decode --trimcrlf --origbreak "" $orig +test comment-55 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\nxxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\nxxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 72 --decode --trimcrlf --origbreak "" $orig $orig +test comment-56 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\nxxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\nxxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 72 --decode --trimcrlf --origbreak "" "00:00:00 \[0000000000\] *CURRENT* $orig" $orig +test comment-57 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \nxxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\nxxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\nxxxxxxx.\n(6 lines output)"} + +############################################################################### + +fossil test-comment-format --width 81 --indent 9 --decode --trimcrlf --origbreak " " $orig +test comment-58 {$RESULT eq " xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n xxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\n xxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\n xxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\n xxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 81 --indent 9 --decode --trimcrlf --origbreak " " $orig $orig +test comment-59 {$RESULT eq " xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n xxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\n xxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\n xxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\n xxxxxxx.\n(5 lines output)"} + +############################################################################### + +fossil test-comment-format --width 81 --indent 9 --decode --trimcrlf --origbreak "00:00:00 " "\[0000000000\] *CURRENT* $orig" $orig +test comment-60 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \n xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n xxxxxxx xxxxx xxxx xxxx xx xxxxxxx xxxxxxx (xxxxxx xxxxxxxxx x xxxxx).\n xxx'x xxx xxx xx xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx xx xx xxxx. x\n xxxxx x xxxxxx xxxx xxxx xxxx xxxx xxxx x xxxxx xx xxx x xxxxxxxx\n xxxxxxx.\n(6 lines output)"} Index: test/diff-test-1.wiki ================================================================== --- test/diff-test-1.wiki +++ test/diff-test-1.wiki @@ -17,11 +17,11 @@ The edit of a line with multibyte characters is the first chunk. * <a href="../../../fdiff?v1=57b0d8183cab0e3d&v2=37b3ef49d73cdfe6" target="testwindow">Large diff of sqlite3.c</a>. This diff was very slow prior to the performance enhancement change [9e15437e97]. * <a href="../../../info/bda00cbada#chunk49" target="testwindow"> - A difficult indentation change. + A difficult indentation change.</a> (show-version-diffs must be enabled) * <a href="../../../fdiff?v1=955cc67ace8fb622&v2=e2e1c87b86664b45#chunk13" target="testwindow">Another tricky indentation.</a> Notice especially lines 59398 and 59407 on the left. * <a href="../../../fdiff?v2=955cc67ace8fb622&v1=e2e1c87b86664b45#chunk13" target="testwindow">Inverse of the previous.</a> Index: test/file1.test ================================================================== --- test/file1.test +++ test/file1.test @@ -24,14 +24,39 @@ fossil test-simplify-name $path test simplify-name-$testname.$i {$::RESULT=="\[$path\] -> \[$result\]"} incr i } } + +proc relative-name {testname args} { + set i 1 + foreach {subdir path result} $args { + fossil test-relative-name --chdir $subdir $path + test relative-name-$testname.$i {$::RESULT==$result} + incr i + } +} simplify-name 100 . . .// . .. .. ..///// .. simplify-name 101 {} {} / / ///////// / ././././ . simplify-name 102 x x /x /x ///x //x simplify-name 103 a/b a/b /a/b /a/b a///b a/b ///a///b///// //a/b simplify-name 104 a/b/../c/ a/c /a/b/../c /a/c /a/b//../c /a/c /a/b/..///c /a/c simplify-name 105 a/b/../../x/y x/y /a/b/../../x/y /x/y simplify-name 106 a/b/../../../x/y ../x/y /a/b/../../../x/y /../x/y simplify-name 107 a/./b/.././../x/y x/y a//.//b//..//.//..//x//y/// x/y + +if {$::tcl_platform(os)=="Windows NT"} { + simplify-name 108 //?/a:/a/b a:/a/b //?/UNC/a/b //a/b //?/ {} + simplify-name 109 \\\\?\\a:\\a\\b a:/a/b \\\\?\\UNC\\a\\b //a/b \\\\?\\ {} +} + +# Those directories are only needed for the testcase being able to "--chdir" to it. +file mkdir test1 +file mkdir test1/test2 + +relative-name 100 . . . test1 [pwd] .. test1 [pwd]/ .. test1 [pwd]/test ../test +relative-name 101 test1/test2 [pwd] ../.. test1/test2 [pwd]/ ../.. test1/test2 [pwd]/test ../../test +relative-name 102 test1 [pwd]/test ../test . [pwd]/file1 ./file1 . [pwd]/file1/file2 ./file1/file2 + +catch {file delete test1/test2} +catch {file delete test1} ADDED test/fileStat.th1 Index: test/fileStat.th1 ================================================================== --- /dev/null +++ test/fileStat.th1 @@ -0,0 +1,102 @@ +<th1> + proc doSomeTclSetup {} { + # + # NOTE: Copy repository file name to the Tcl interpreter. This is + # done first (once) because it will be necessary for almost + # everything else later on. + # + tclInvoke set repository [repository] + + # + # NOTE: Create some procedures in the Tcl interpreter to perform + # useful operations. This could also do things like load + # packages, etc. + # + tclEval { + # + # NOTE: Returns an [exec] command for Fossil, using the provided + # sub-command and arguments, suitable for use with [eval] + # or [catch]. + # + proc getFossilCommand { repository user args } { + global env + + lappend result exec [info nameofexecutable] + + if {[info exists env(GATEWAY_INTERFACE)]} then { + # + # NOTE: This option is required when calling + # out to the Fossil executable from a + # CGI process. + # + lappend result -nocgi + } + + eval lappend result $args + + if {[string length $repository] > 0} then { + # + # NOTE: This is almost certainly required + # when calling out to the Fossil + # executable on the server because + # there is almost never an open + # checkout. + # + lappend result -R $repository + } + + if {[string length $user] > 0} then { + lappend result -U $user + } + + # th1Eval [list html $result<br>] + + return $result + } + } + } + + proc getLatestTrunkCheckIn {} { + tclEval { + # + # NOTE: Get the unique Id of the latest check-in on trunk. + # + return [lindex [regexp -line -inline -nocase -- \ + {^uuid:\s+([0-9A-F]{40}) } [eval [getFossilCommand \ + $repository "" info trunk]]] end] + } + } + + proc theSumOfAllFiles { id } { + # + # NOTE: Copy check-in Id value to the Tcl interpreter. + # + tclInvoke set id $id + + tclEval { + set count 0 + + foreach line [split [eval [getFossilCommand \ + $repository "" artifact $id]] \n] { + # + # NOTE: Is this an "F" (file) card? + # + if {[string range $line 0 1] eq "F "} then { + incr count + } + } + + return $count + } + } + + doSomeTclSetup; # perform some extra setup for the Tcl interpreter. + + set checkIn [getLatestTrunkCheckIn] + set totalFiles [theSumOfAllFiles $checkIn] +</th1> + +<br /> +As of trunk check-in <th1>decorate \[$checkIn\]</th1>, this +repository contains <th1>html $totalFiles</th1> files. +<br /> ADDED test/fileage-test-1.wiki Index: test/fileage-test-1.wiki ================================================================== --- /dev/null +++ test/fileage-test-1.wiki @@ -0,0 +1,14 @@ + +This page contains URLs for file-age computations that have given +trouble in the past. Shif-click on on the links, one-by-one, to verify +that the current implementation works correctly: + + * [/fileage?name=c9df0dcdaa402] - Verify that the many + execute permission changes that occurred about 24 hours before + check-in c9df0dcdaa402 do not appear as file changes. + + * [/tree?ci=c9df0dcdaa40&mtime=0&type=tree] - Verify that all + three skin files (css.txt, footer.txt, and header.txt) appear + in all of the skin/*/ folders. + + * On both of the above, check for excessive computation time. Index: test/graph-test-1.wiki ================================================================== --- test/graph-test-1.wiki +++ test/graph-test-1.wiki @@ -1,9 +1,9 @@ <title>Graph Test One This page contains examples a list of URLs of timelines with -interesting graphs. Click on all URLs, one by one, to verify +interesting graphs. Click on all URLs, one by one, to verify the correct operation of the graph drawing logic. * 20-element timeline, check-ins only, before 2010-11-08 * @@ -12,13 +12,13 @@ 20-element timeline, check-ins only, file changes, before 2010-11-08 * 40-element timeline, check-ins only, before 2010-11-08 * 1000-element timeline, check-ins only, before 2010-11-08 - * + * 10-elements circa 2010-11-07 10:23:00, with dividers - * 10-elements circa 2010-11-07 10:23:00, without dividers * Parents and children of check-in 3ea66260b5555 * * multiple branch risers, n=18. * multiple branch risers, n=9. - * + * Experimental branch with related check-ins. - * + * Experimental branch with merge-ins only. - * + * Experimental branch check-ins only. - * - Experimental branch using and related check-ins - 1000 elements. - * + * Check-ins tagged "release" and related check-ins - * + * Check-ins tagged "release" and merge-ins - * + * Only check-ins tagged "release" * History of source file "Makefile". * 20 elements after 1970-01-01. * All check-ins - a huge graph. * - This malformed commit has a + This malformed commit has a merge parent which is not a valid checkin. - * From e663bac6f7 to a298a0e2f9 by shortest path. - * From e663bac6f7 to a298a0e2f9 without merge links. * Common ancestor path of e663bac6f7 to a298a0e2f9. - * + * Merge on the same branch does not result in a leaf. + * + This timeline has a hidden commit. Click Unhide to reveal. + * Isolated check-ins. External: * Timewarp due to a mis-configured system clock. + * Show all three separate deletions of "id.test". + (Scroll down for the third deletion.) + * Merge arrows to the left and to the right + * Previous, with a scrunched graph + * Previous, with a severely scrunched graph Index: test/merge2.test ================================================================== --- test/merge2.test +++ test/merge2.test @@ -20,10 +20,11 @@ set filelist [glob $testdir/*] foreach f $filelist { if {[file isdir $f]} continue set base [file root [file tail $f]] + if {[string match "utf16*" $base]} continue set f1 [read_file $f] write_file t1 $f1 for {set i 0} {$i<100} {incr i} { expr {srand($i*2)} write_file t2 [set f2 [random_changes $f1 2 4 0 0.1]] Index: test/merge5.test ================================================================== --- test/merge5.test +++ test/merge5.test @@ -52,10 +52,11 @@ # Construct a test repository # exec sqlite3 m5.fossil <$testdir/${testfile}_repo.sql fossil rebuild m5.fossil fossil open m5.fossil +fossil user default drh --user drh fossil update baseline checkout-test 10 { da5c8346496f3421cb58f84b6e59e9531d9d424d one.txt ed24d19d726d173f18dbf4a9a0f8514daa3e3ca4 three.txt 278a402316510f6ae4a77186796a6bde78c7dbc1 two.txt ADDED test/merge6.test Index: test/merge6.test ================================================================== --- /dev/null +++ test/merge6.test @@ -0,0 +1,67 @@ +# +# Copyright (c) 2014 D. Richard Hipp +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the Simplified BSD License (also +# known as the "2-Clause License" or "FreeBSD License".) +# +# This program is distributed in the hope that it will be useful, +# but without any warranty; without even the implied warranty of +# merchantability or fitness for a particular purpose. +# +# Author contact information: +# drh@hwaci.com +# http://www.hwaci.com/drh/ +# +############################################################################ +# +# Tests of the "merge" command +# + +#################################################################### +# TEST 1: Handle multiple merges each with one or more ADDED files # +#################################################################### + +repo_init +fossil ls + +test merge_multi-0 {[string map [list \r\n \n] [string trim $RESULT]] eq {}} + +write_file f1 "f1 line" +fossil add f1 +fossil commit -m "base file" +fossil ls + +test merge_multi-1 {[string map [list \r\n \n] [string trim $RESULT]] eq {f1}} + +fossil update trunk +write_file f2 "f2 line" +fossil add f2 +fossil commit -m "branch for file f2" -b branch_for_f2 +fossil ls + +test merge_multi-2 {[string map [list \r\n \n] [string trim $RESULT]] eq {f1 +f2}} + +fossil update trunk +write_file f3 "f3 line" +write_file f4 "f4 line" +fossil add f3 +fossil add f4 +fossil commit -m "branch for files f3 and f4" -b branch_for_f3_f4 +fossil ls + +test merge_multi-3 {[string map [list \r\n \n] [string trim $RESULT]] eq {f1 +f3 +f4}} + +fossil update trunk +fossil merge branch_for_f2 +fossil merge branch_for_f3_f4 +fossil commit -m "new trunk files f2, f3, and f4 via merge" +fossil ls + +test merge_multi-4 {[string map [list \r\n \n] [string trim $RESULT]] eq {f1 +f2 +f3 +f4}} Index: test/merge_renames.test ================================================================== --- test/merge_renames.test +++ test/merge_renames.test @@ -1,30 +1,23 @@ # # Tests for merging with renames -# +# # catch {exec $::fossilexe info} res puts res=$res if {![regexp {use --repository} $res]} { puts stderr "Cannot run this test within an open checkout" return } - -# Fossil will write data on $HOME, running 'fossil new' here. -# We need not to clutter the $HOME of the test caller. -set env(HOME) [pwd] - - ###################################### # Test 1 # # Reported: Ticket [554f44ee74e3d] # ###################################### -fossil new rep.fossil -fossil open rep.fossil +repo_init write_file f1 "line" fossil add f1 fossil commit -m "c1" fossil tag add pivot current @@ -44,11 +37,11 @@ write_file f1 "line6" fossil commit -m "c4" fossil update pivot fossil mv f1 f2 -exec mv f1 f2 +file rename -force f1 f2 fossil commit -b rename -m "c5" fossil merge trunk fossil commit -m "trunk merged" @@ -73,20 +66,64 @@ test merge_renames-1 0 } else { test merge_renames-1 1 } -fossil close -f -exec rm rep.fossil - ###################################### # Test 2 # # Reported: Ticket [74413366fe5067] # ###################################### -fossil new rep.fossil -fossil open rep.fossil +repo_init + +write_file f1 "line" +fossil add f1 +fossil commit -m "base file" +fossil tag add pivot current + +write_file f2 "line2" +fossil add f2 +fossil commit -m "newfile" + +fossil mv f2 f2new +file rename -force f2 f2new +fossil commit -m "rename" + +fossil update pivot +write_file f1 "line3" +fossil commit -b branch -m "change" + +fossil merge trunk +fossil commit -m "trunk merged" + +fossil update trunk + +fossil merge branch +puts $RESULT + +# Not a nice way to check, but I don't know more tcl now +set deletes 0 +foreach {status filename} $RESULT { + if {$status=="DELETE"} { + set deletes [expr $deletes + 1] + } +} + +if {$deletes!=0} { + # failed + protOut "Error, the merge should not delete any file" + test merge_renames-2 0 +} else { + test merge_renames-2 1 +} + +###################################### +# Test 3 # +# Reported: Ticket [30b28cf351] # +###################################### + +repo_init write_file f1 "line" fossil add f1 fossil commit -m "base file" fossil tag add pivot current @@ -94,11 +131,11 @@ write_file f2 "line2" fossil add f2 fossil commit -m "newfile" fossil mv f2 f2new -exec mv f2 f2new +file rename -force f2 f2new fossil commit -m "rename" fossil update pivot write_file f1 "line3" fossil commit -b branch -m "change" @@ -125,73 +162,51 @@ test merge_renames-2 0 } else { test merge_renames-2 1 } -fossil close -f -exec rm rep.fossil - -###################################### -# Test 3 # -# Reported: Ticket [30b28cf351] # -###################################### - -fossil new rep.fossil -fossil open rep.fossil - -write_file f1 "line" -fossil add f1 -fossil commit -m "base file" -fossil tag add pivot current - -write_file f2 "line2" -fossil add f2 -fossil commit -m "newfile" - -fossil mv f2 f2new -exec mv f2 f2new -fossil commit -m "rename" - -fossil update pivot -write_file f1 "line3" -fossil commit -b branch -m "change" - -fossil merge trunk -fossil commit -m "trunk merged" - -fossil update trunk - -fossil merge branch -puts $RESULT - -# Not a nice way to check, but I don't know more tcl now -set deletes 0 -foreach {status filename} $RESULT { - if {$status=="DELETE"} { - set deletes [expr $deletes + 1] - } -} - -if {$deletes!=0} { - # failed - protOut "Error, the merge should not delete any file" - test merge_renames-2 0 -} else { - test merge_renames-2 1 -} - -fossil close -f -exec rm rep.fossil - ###################################### # Test 4 # # Reported: Ticket [67176c3aa4] # ###################################### # TO BE WRITTEN. +###################################### +# Test 5 # +# Handle Rename/Add via Merge # +###################################### + +repo_init + +write_file f1 "old f1 line" +fossil add f1 +fossil commit -m "base file" + +write_file f3 "f3 line" +fossil add f3 +fossil commit -m "branch file" -b branch_for_f3 + +fossil update trunk +fossil mv f1 f2 +file rename -force f1 f2 +write_file f1 "new f1 line" +fossil add f1 +fossil commit -m "rename and add file with old name" + +fossil update branch_for_f3 +fossil merge trunk +fossil commit -m "trunk merged, should have 3 files" + +fossil ls + +test merge_renames-5 {[string map [list \r\n \n] [string trim $RESULT]] eq {f1 +f2 +f3}} +###################################### +# # Tests for troubles not specifically linked with renames but that I'd like to # write: # [c26c63eb1b] - 'merge --backout' does not handle conflicts properly -# [953031915f] - Lack of warning when overwriting extra files -# [4df5f38f1e] - Troubles merging a file delete with a file change +# [953031915f] - Lack of warning when overwriting extra files +# [4df5f38f1e] - Troubles merging a file delete with a file change Index: test/release-checklist.wiki ================================================================== --- test/release-checklist.wiki +++ test/release-checklist.wiki @@ -18,10 +18,22 @@
          • Click on each of the links in in the [./diff-test-1.wiki] document and verify that all diffs are rendered correctly. +

          • +Click on the following link to verify that it works: [./test-page%2b%2b.wiki | ./test-page++.wiki] +(NB: Many web servers automatically block +or rewrite URLs that contain "+" characters, even when those "+" +characters are encoded as "%2B". On such web servers, the URL +above will not work. This test is only guaranteed to work +when running "fossil ui".) + +

          • +Shift-click on each of the links in [./fileage-test-1.wiki] and verify +correct operation of the file-age computation. +

          • Verify correct name-change tracking behavior (no net changes) for:

            fossil test-name-changes --debug b120bc8b262ac 374920b20944b
            Index: test/revert.test ================================================================== --- test/revert.test +++ test/revert.test @@ -1,65 +1,30 @@ # # Tests for 'fossil revert' # # -catch {exec $::fossilexe info} res -puts res=$res -if {![regexp {use --repository} $res]} { - puts stderr "Cannot run this test within an open checkout" - return -} - -# Fossil will write data on $HOME, running 'fossil new' here. -# We need not to clutter the $HOME of the test caller. -# -set env(HOME) [pwd] - - -# Normalize file status lists (like those returned by 'fossil changes') -# so they can be compared using simple string comparison -# -proc normalize-status-list {list} { - set normalized [list] - set matches [regexp -all -inline -line {^\s*([A-Z]+)\s+(.*)$} $list] - foreach {_ status file} $matches { - lappend normalized [list $status [string trim $file]] - } - set normalized [lsort -index 1 $normalized] - return $normalized -} - # Test 'fossil revert' against expected results from 'fossil changes' and -# 'fossil addremove --test', as well as by verifying the existence of files +# 'fossil addremove -n', as well as by verifying the existence of files # on the file system. 'fossil undo' is called after each test # -proc revert-test {testid args} { +proc revert-test {testid revertArgs expectedRevertOutput args} { global RESULT set passed 1 - if {[llength $args] % 2} { - set revertArgs [lindex $args 0] - set args [lrange $args 1 end] - } else { - set revertArgs {} - } set args [dict merge { -changes {} -addremove {} -exists {} -notexists {} } $args] - fossil revert {*}$revertArgs + set result [fossil revert {*}$revertArgs] + test_status_list revert-$testid $result $expectedRevertOutput - set statusListTests [list -changes changes -addremove {addremove --test}] + set statusListTests [list -changes changes -addremove {addremove -n}] foreach {key fossilArgs} $statusListTests { - set expected [normalize-status-list [dict get $args $key]] - set result [normalize-status-list [fossil {*}$fossilArgs]] - if {$result ne $expected} { - set passed 0 - protOut " Expected:\n [join $expected "\n "]" - protOut " Got:\n [join $result "\n "]" - } + set expected [dict get $args $key] + set result [fossil {*}$fossilArgs] + test_status_list revert-$testid$key $result $expected } set fileExistsTests [list -exists 1 does -notexists 0 should] foreach {key expected verb} $fileExistsTests { foreach path [dict get $args $key] { @@ -66,20 +31,24 @@ if {[file exists $path] != $expected} { set passed 0 protOut " Failure: File $verb not exist: $path" } } + test revert-$testid$key $passed } fossil undo - test revert-$testid $passed +} + +catch {exec $::fossilexe info} res +puts res=$res +if {![regexp {use --repository} $res]} { + puts stderr "Cannot run this test within an open checkout" + return } -# Create the repo -# -fossil new rep.fossil -fossil open rep.fossil +repo_init # Prepare first commit # write_file f1 "f1" write_file f2 "f2" @@ -91,63 +60,105 @@ # # Add f0 write_file f0 "f0" fossil add f0 # Remove f1 -exec rm f1 +file delete f1 fossil rm f1 # Edit f2 write_file f2 "f2.1" # Rename f3 to f3n -exec mv f3 f3n +file rename -force f3 f3n fossil mv f3 f3n # Test 'fossil revert' with no arguments # -revert-test 1 -addremove { +revert-test 1-1 {} { + UNMANAGE: f0 + REVERTED: f1 + REVERTED: f2 + REVERTED: f3 + DELETE: f3n +} -addremove { ADDED f0 } -exists {f0 f1 f2 f3} -notexists f3n # Test with a single filename argument # -revert-test 2 f0 -changes { +revert-test 1-2 f0 { + UNMANAGE: f0 +} -changes { DELETED f1 EDITED f2 RENAMED f3n } -addremove { ADDED f0 } -exists {f0 f2 f3n} -notexists f3 -revert-test 3 f1 -changes { +revert-test 1-3 f1 { + REVERTED: f1 +} -changes { ADDED f0 EDITED f2 RENAMED f3n } -exists {f0 f1 f2 f3n} -notexists f3 -revert-test 4 f2 -changes { +revert-test 1-4 f2 { + REVERTED: f2 +} -changes { ADDED f0 DELETED f1 RENAMED f3n } -exists {f0 f2 f3n} -notexists {f1 f3} # Both files involved in a rename are reverted regardless of which filename # is used as an argument to 'fossil revert' # -revert-test 5 f3 -changes { +revert-test 1-5 f3 { + REVERTED: f3 + DELETE: f3n +} -changes { ADDED f0 DELETED f1 EDITED f2 } -exists {f0 f2 f3} -notexists {f1 f3n} -revert-test 6 f3n -changes { +revert-test 1-6 f3n { + REVERTED: f3 + DELETE: f3n +} -changes { ADDED f0 DELETED f1 EDITED f2 } -exists {f0 f2 f3} -notexists {f1 f3n} # Test with multiple filename arguments # -revert-test 7 {f0 f2 f3n} -changes { +revert-test 1-7 {f0 f2 f3n} { + UNMANAGE: f0 + REVERTED: f2 + REVERTED: f3 + DELETE: f3n +} -changes { DELETED f1 } -addremove { ADDED f0 } -exists {f0 f2 f3} -notexists {f1 f3n} + + +# Test reverting the combination of a renamed file and an added file that +# uses the renamed file's original filename. +# +repo_init +write_file f1 "f1" +fossil add f1 +fossil commit -m "add f1" + +write_file f1n "f1n" +fossil mv f1 f1n +write_file f1 "f1b" +fossil add f1 + +revert-test 2-1 {} { + REVERTED: f1 + DELETE: f1n +} -exists {f1} -notexists {f1n} ADDED test/subdir-b/readme.txt Index: test/subdir-b/readme.txt ================================================================== --- /dev/null +++ test/subdir-b/readme.txt @@ -0,0 +1,3 @@ +This file exists in order to create the "subdir-b" subdirectory. There is +exists sibling directory "subdir" that is a prefix of this subdirectory. +This file exists for self-testing. ADDED test/subdir/one/two/three/four/five/six/readme.txt Index: test/subdir/one/two/three/four/five/six/readme.txt ================================================================== --- /dev/null +++ test/subdir/one/two/three/four/five/six/readme.txt @@ -0,0 +1,2 @@ +This file exists in order to provide Fossil with a test case of a file +that is deeply nested below many subdirectories. ADDED test/test-page++.wiki Index: test/test-page++.wiki ================================================================== --- /dev/null +++ test/test-page++.wiki @@ -0,0 +1,7 @@ +Test Page + +The purpose of this page is to test Fossil's ability to deal with +embedded documentation pages that contain characters that should be +escaped in URLs. + +Here is a link to the [./release-checklist.wiki | release checklist]. Index: test/tester.tcl ================================================================== --- test/tester.tcl +++ test/tester.tcl @@ -21,10 +21,11 @@ # # Where ../test/tester.tcl is the name of this file and ../bld/fossil # is the name of the executable to be tested. # +set testrundir [pwd] set testdir [file normalize [file dir $argv0]] set fossilexe [file normalize [lindex $argv 0]] set argv [lrange $argv 1 end] set i [lsearch $argv -halt] @@ -40,37 +41,52 @@ set PROT 1 set argv [lreplace $argv $i $i] } else { set PROT 0 } + +set i [lsearch $argv -verbose] +if {$i>=0} { + set VERBOSE 1 + set argv [lreplace $argv $i $i] +} else { + set VERBOSE 0 +} if {[llength $argv]==0} { foreach f [lsort [glob $testdir/*.test]] { set base [file root [file tail $f]] lappend argv $base } } + +set tempPath [expr {[info exists env(TEMP)] ? \ + $env(TEMP) : [file dirname [info script]]}] + +if {$tcl_platform(platform) eq "windows"} then { + set tempPath [string map [list \\ /] $tempPath] +} # start protocol # proc protInit {cmd} { if {$::PROT} { - set out [open "prot" w] + set out [open [file join $::testrundir prot] w] fconfigure $out -translation platform - puts $out "starting tests with:$cmd" + puts $out "starting tests with: $cmd" close $out } } # write protocol # proc protOut {msg} { - puts "$msg" + puts stdout $msg if {$::PROT} { - set out [open "prot" a] + set out [open [file join $::testrundir prot] a] fconfigure $out -translation platform - puts $out "$msg" + puts $out $msg close $out } } # Run the fossil program @@ -85,15 +101,19 @@ flush stdout set rc [catch {eval exec $cmd} result] global RESULT CODE set CODE $rc - if {$rc} {puts "ERROR: $result"} + if {$rc} { + protOut "ERROR: $result" + } elseif {$::VERBOSE} { + protOut "RESULT: $result" + } set RESULT $result } -# Read a file into memory. +# Read a file into memory. # proc read_file {filename} { set in [open $filename r] fconfigure $in -translation binary set txt [read $in [file size $filename]] @@ -120,10 +140,123 @@ regsub -all { +\n} $x \n x set y [read_file $b] regsub -all { +\n} $y \n y return [expr {$x==$y}] } + +# Create and open a new Fossil repository and clean the checkout +# +proc repo_init {{filename ".rep.fossil"}} { + if {$::env(HOME) ne [pwd]} { + catch {exec $::fossilexe info} res + if {![regexp {use --repository} $res]} { + error "In an open checkout: cannot initialize a new repository here." + } + # Fossil will write data on $HOME, running 'fossil new' here. + # We need not to clutter the $HOME of the test caller. + # + set ::env(HOME) [pwd] + } + catch {exec $::fossilexe close -f} + file delete $filename + exec $::fossilexe new $filename + exec $::fossilexe open $filename + exec $::fossilexe clean -f + exec $::fossilexe set mtime-changes off +} + +# Normalize file status lists (like those returned by 'fossil changes') +# so they can be compared using simple string comparison +# +proc normalize_status_list {list} { + set normalized [list] + set matches [regexp -all -inline -line {^\s*([A-Z_]+:?)\x20+(\S.*)$} $list] + foreach {_ status file} $matches { + lappend normalized [list $status [string trim $file]] + } + set normalized [lsort -index 1 $normalized] + return $normalized +} + +# Perform a test comparing two status lists +# +proc test_status_list {name result expected} { + set expected [normalize_status_list $expected] + set result [normalize_status_list $result] + if {$result eq $expected} { + test $name 1 + } else { + protOut " Expected:\n [join $expected "\n "]" + protOut " Got:\n [join $result "\n "]" + test $name 0 + } +} + +# Append all arguments into a single value and then returns it. +# +proc appendArgs {args} { + eval append result $args +} + +# Return the name of the versioned settings file containing the TH1 +# setup script. +# +proc getTh1SetupFileName {} { + # + # NOTE: This uses the "testdir" global variable provided by the + # test suite; alternatively, the root of the source tree + # could be obtained directly from Fossil. + # + return [file normalize [file join [file dirname $::testdir] \ + .fossil-settings th1-setup]] +} + +# Return the saved name of the versioned settings file containing +# the TH1 setup script. +# +proc getSavedTh1SetupFileName {} { + return [appendArgs [getTh1SetupFileName] . [pid]] +} + +# Sets the TH1 setup script to the one provided. Prior to calling +# this, the [saveTh1SetupFile] procedure should be called in order to +# preserve the existing TH1 setup script. Prior to completing the test, +# the [restoreTh1SetupFile] procedure should be called to restore the +# original TH1 setup script. +# +proc writeTh1SetupFile { data } { + return [write_file [getTh1SetupFileName] $data] +} + +# Saves the TH1 setup script file by renaming it, based on the current +# process ID. +# +proc saveTh1SetupFile {} { + set oldFileName [getTh1SetupFileName] + if {[file exists $oldFileName]} then { + set newFileName [getSavedTh1SetupFileName] + catch {file delete $newFileName} + file rename $oldFileName $newFileName + } +} + +# Restores the original TH1 setup script file by renaming it back, based +# on the current process ID. +# +proc restoreTh1SetupFile {} { + set oldFileName [getSavedTh1SetupFileName] + set newFileName [getTh1SetupFileName] + if {[file exists $oldFileName]} then { + catch {file delete $newFileName} + file rename $oldFileName $newFileName + } else { + # + # NOTE: There was no TH1 setup script file, delete the test one. + # + file delete $newFileName + } +} # Perform a test # set test_count 0 proc test {name expr} { @@ -195,10 +328,76 @@ } append out \n$line } return [string range $out 1 end] } + +# Executes the "fossil http" command. The entire content of the HTTP request +# is read from the data file name, with [subst] being performed on it prior to +# submission. Temporary input and output files are created and deleted. The +# result will be the contents of the temoprary output file. +proc test_fossil_http { repository dataFileName url } { + set suffix [appendArgs [pid] - [getSeqNo] - [clock seconds] .txt] + set inFileName [file join $::tempPath [appendArgs test-http-in- $suffix]] + set outFileName [file join $::tempPath [appendArgs test-http-out- $suffix]] + set data [subst [read_file $dataFileName]] + + write_file $inFileName $data + fossil http $inFileName $outFileName 127.0.0.1 $repository --localauth + set result [expr {[file exists $outFileName] ? [read_file $outFileName] : ""}] + + if {1} then { + catch {file delete $inFileName} + catch {file delete $outFileName} + } + + return $result +} + +# obtains and increments a "sequence number" for this test run. +proc getSeqNo {} { + upvar #0 seqNo seqNo + if {![info exists seqNo]} { + set seqNo 0 + } + return [incr seqNo] +} + +# fixup the whitespace in the result to make it easier to compare. +proc normalize_result {} { + return [string map [list \r\n \n] [string trim $::RESULT]] +} + +# returns the first line of the normalized result. +proc first_data_line {} { + return [lindex [split [normalize_result] \n] 0] +} + +# returns the second line of the normalized result. +proc second_data_line {} { + return [lindex [split [normalize_result] \n] 1] +} + +# returns the third line of the normalized result. +proc third_data_line {} { + return [lindex [split [normalize_result] \n] 2] +} + +# returns the last line of the normalized result. +proc last_data_line {} { + return [lindex [split [normalize_result] \n] end] +} + +# returns the second to last line of the normalized result. +proc next_to_last_data_line {} { + return [lindex [split [normalize_result] \n] end-1] +} + +# returns the third to last line of the normalized result. +proc third_to_last_data_line {} { + return [lindex [split [normalize_result] \n] end-2] +} protInit $fossilexe foreach testfile $argv { set dir [file root [file tail $testfile]] file delete -force $dir ADDED test/th1-docs-input.txt Index: test/th1-docs-input.txt ================================================================== --- /dev/null +++ test/th1-docs-input.txt @@ -0,0 +1,4 @@ +GET ${url} HTTP/1.1 +Host: localhost +User-Agent: Fossil + ADDED test/th1-docs.test Index: test/th1-docs.test ================================================================== --- /dev/null +++ test/th1-docs.test @@ -0,0 +1,56 @@ +# +# Copyright (c) 2015 D. Richard Hipp +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the Simplified BSD License (also +# known as the "2-Clause License" or "FreeBSD License".) +# +# This program is distributed in the hope that it will be useful, +# but without any warranty; without even the implied warranty of +# merchantability or fitness for a particular purpose. +# +# Author contact information: +# drh@hwaci.com +# http://www.hwaci.com/drh/ +# +############################################################################ +# +# TH1 Docs +# + +fossil test-th-eval "hasfeature th1Docs" + +if {$::RESULT ne "1"} then { + puts "Fossil was not compiled with TH1 docs support."; return +} + +fossil test-th-eval "hasfeature tcl" + +if {$::RESULT ne "1"} then { + puts "Fossil was not compiled with Tcl support."; return +} + +############################################################################### + +set env(TH1_ENABLE_DOCS) 1; # TH1 docs must be enabled for this test. +set env(TH1_ENABLE_TCL) 1; # Tcl integration must be enabled for this test. + +############################################################################### + +set data [fossil info] +regexp -line -- {^repository: (.*)$} $data dummy repository + +if {[string length $repository] == 0 || ![file exists $repository]} then { + error "unable to locate repository" +} + +set dataFileName [file join $::testdir th1-docs-input.txt] + +############################################################################### + +set RESULT [test_fossil_http \ + $repository $dataFileName /doc/trunk/test/fileStat.th1] + +test th1-docs-1a {[regexp {Fossil: test/fileStat.th1} $RESULT]} +test th1-docs-1b {[regexp {>\[[0-9a-f]{40}\]<} $RESULT]} +test th1-docs-1c {[regexp { contains \d+ files\.} $RESULT]} ADDED test/th1-hooks-input.txt Index: test/th1-hooks-input.txt ================================================================== --- /dev/null +++ test/th1-hooks-input.txt @@ -0,0 +1,4 @@ +GET ${url} HTTP/1.1 +Host: localhost +User-Agent: Fossil + ADDED test/th1-hooks.test Index: test/th1-hooks.test ================================================================== --- /dev/null +++ test/th1-hooks.test @@ -0,0 +1,192 @@ +# +# Copyright (c) 2011 D. Richard Hipp +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the Simplified BSD License (also +# known as the "2-Clause License" or "FreeBSD License".) +# +# This program is distributed in the hope that it will be useful, +# but without any warranty; without even the implied warranty of +# merchantability or fitness for a particular purpose. +# +# Author contact information: +# drh@hwaci.com +# http://www.hwaci.com/drh/ +# +############################################################################ +# +# TH1 Hooks +# + +fossil test-th-eval "hasfeature th1Hooks" + +if {$::RESULT ne "1"} then { + puts "Fossil was not compiled with TH1 hooks support."; return +} + +############################################################################### + +set env(TH1_ENABLE_HOOKS) 1; # TH1 hooks must be enabled for this test. + +############################################################################### + +set testTh1Setup { + proc initialize_hook_log {} { + if {![info exists ::hook_log]} { + set ::hook_log "" + } + } + + proc append_hook_log { args } { + initialize_hook_log + if {[string length $::hook_log] > 0} { + set ::hook_log "$::hook_log " + } + for {set i 0} {$i < [llength $args]} {set i [expr {$i + 1}]} { + set ::hook_log $::hook_log[lindex $args $i] + } + } + + proc emit_hook_log {} { + initialize_hook_log + html "\n

            $::hook_log

            \n" + } + + proc command_hook {} { + append_hook_log command_hook " " $::cmd_name + if {$::cmd_name eq "test1"} { + puts [repository]; continue + } elseif {$::cmd_name eq "test2"} { + error "unsupported command" + } elseif {$::cmd_name eq "test3"} { + emit_hook_log + break "TH_BREAK return code" + } elseif {$::cmd_name eq "test4"} { + emit_hook_log + return -code 2 "TH_RETURN return code" + } elseif {$::cmd_name eq "timeline"} { + set length [llength $::cmd_args] + set length [expr {$length - 1}] + if {[lindex $::cmd_args $length] eq "custom"} { + emit_hook_log + return "custom timeline" + } elseif {[lindex $::cmd_args $length] eq "now"} { + emit_hook_log + return "now timeline" + } else { + emit_hook_log + error "unsupported timeline" + } + } + } + + proc command_notify {} { + append_hook_log command_notify " " $::cmd_name + emit_hook_log + } + + proc webpage_hook {} { + append_hook_log webpage_hook " " $::web_name + if {$::web_name eq "test1"} { + puts [repository]; continue + } + } + + proc webpage_notify {} { + append_hook_log webpage_notify " " $::web_name + emit_hook_log + } +} + +############################################################################### + +set data [fossil info] +regexp -line -- {^repository: (.*)$} $data dummy repository + +if {[string length $repository] == 0 || ![file exists $repository]} then { + error "unable to locate repository" +} + +set dataFileName [file join $::testdir th1-hooks-input.txt] + +############################################################################### + +saveTh1SetupFile; writeTh1SetupFile $testTh1Setup + +############################################################################### + +fossil timeline custom; # NOTE: Bad "WHEN" argument. +test th1-cmd-hooks-1a {[normalize_result] eq \ +{

            command_hook timeline

            ++++ no more data (0) +++ + +

            command_hook timeline command_notify timeline

            }} + +############################################################################### + +fossil timeline +test th1-cmd-hooks-2a {[first_data_line] eq \ + {

            command_hook timeline

            }} + +test th1-cmd-hooks-2b {[second_data_line] eq {ERROR: unsupported timeline}} + +############################################################################### + +fossil timeline now +test th1-cmd-hooks-3a {[first_data_line] eq \ + {

            command_hook timeline

            }} + +test th1-cmd-hooks-3b \ + {[regexp -- {=== \d{4}-\d{2}-\d{2} ===} [second_data_line]]} + +test th1-cmd-hooks-3c \ + {[regexp -- {--- line limit \(\d+\) reached ---} [third_to_last_data_line]]} + +test th1-cmd-hooks-3d {[last_data_line] eq \ + {

            command_hook timeline command_notify timeline

            }} + +############################################################################### + +fossil test1 +test th1-custom-cmd-1a {[next_to_last_data_line] eq $repository} + +test th1-custom-cmd-1b {[last_data_line] eq \ + {

            command_hook test1 command_notify test1

            }} + +############################################################################### + +fossil test2 +test th1-custom-cmd-2a {[first_data_line] eq {ERROR: unsupported command}} + +############################################################################### + +fossil test3 +test th1-custom-cmd-3a {[string trim $RESULT] eq \ + {

            command_hook test3

            }} + +############################################################################### + +fossil test4 +test th1-custom-cmd-4a {[string trim $RESULT] eq \ + {

            command_hook test4

            }} + +############################################################################### + +set RESULT [test_fossil_http $repository $dataFileName /timeline] +test th1-web-hooks-1a {[regexp {Fossil: Timeline} $RESULT]} + +test th1-web-hooks-1b {[regexp [appendArgs \ + {

            command_hook http webpage_hook timeline} " " \ + {webpage_notify timeline

            }] $RESULT]} + +############################################################################### + +set RESULT [test_fossil_http $repository $dataFileName /test1] +test th1-custom-web-1a {[next_to_last_data_line] eq $repository} + +test th1-custom-web-1b {[last_data_line] eq \ + {

            command_hook http webpage_hook test1 webpage_notify test1

            }} + +############################################################################### + +restoreTh1SetupFile Index: test/th1-tcl.test ================================================================== --- test/th1-tcl.test +++ test/th1-tcl.test @@ -32,11 +32,11 @@ set env(TH1_ENABLE_TCL) 1; # Tcl integration must be enabled for this test. ############################################################################### -fossil test-th-render --th-open-config \ +fossil test-th-render --open-config \ [file nativename [file join $dir th1-tcl1.txt]] test th1-tcl-1 {[regexp -- {^tclReady\(before\) = 0 tclReady\(after\) = 1 \d+ @@ -59,60 +59,60 @@ three words now $} [string map [list \r\n \n] $RESULT]]} ############################################################################### -fossil test-th-render --th-open-config \ +fossil test-th-render --open-config \ [file nativename [file join $dir th1-tcl2.txt]] test th1-tcl-2 {[regexp -- {^\d+ $} [string map [list \r\n \n] $RESULT]]} ############################################################################### -fossil test-th-render --th-open-config \ +fossil test-th-render --open-config \ [file nativename [file join $dir th1-tcl3.txt]] test th1-tcl-3 {$RESULT eq {

            ERROR:\ invalid command name "bad_command"

            }} ############################################################################### -fossil test-th-render --th-open-config \ +fossil test-th-render --open-config \ [file nativename [file join $dir th1-tcl4.txt]] test th1-tcl-4 {$RESULT eq {

            ERROR:\ divide by zero

            }} ############################################################################### -fossil test-th-render --th-open-config \ +fossil test-th-render --open-config \ [file nativename [file join $dir th1-tcl5.txt]] test th1-tcl-5 {$RESULT eq {

            ERROR:\ Tcl command not found: bad_command

            } || $RESULT eq {
            ERROR: invalid command name "bad_command"

            }} ############################################################################### -fossil test-th-render --th-open-config \ +fossil test-th-render --open-config \ [file nativename [file join $dir th1-tcl6.txt]] test th1-tcl-6 {$RESULT eq {

            ERROR:\ no such command: bad_command

            }} ############################################################################### -fossil test-th-render --th-open-config \ +fossil test-th-render --open-config \ [file nativename [file join $dir th1-tcl7.txt]] test th1-tcl-7 {$RESULT eq {

            ERROR:\ syntax error in expression: "2**0"

            }} ############################################################################### -fossil test-th-render --th-open-config \ +fossil test-th-render --open-config \ [file nativename [file join $dir th1-tcl8.txt]] test th1-tcl-8 {$RESULT eq {

            ERROR:\ cannot invoke Tcl command: tailcall

            } || $RESULT eq {
            ERROR: tailcall can only be called from a proc or\ @@ -119,11 +119,11 @@ lambda

            } || $RESULT eq {

            ERROR: This test\ requires Tcl 8.6 or higher.

            }} ############################################################################### -fossil test-th-render --th-open-config \ +fossil test-th-render --open-config \ [file nativename [file join $dir th1-tcl9.txt]] test th1-tcl-9 {[string trim $RESULT] eq [list [file tail $fossilexe] 3 \ -[list test-th-render --th-open-config [file nativename [file join $dir \ +[list test-th-render --open-config [file nativename [file join $dir \ th1-tcl9.txt]]]]} Index: test/th1.test ================================================================== --- test/th1.test +++ test/th1.test @@ -16,54 +16,837 @@ ############################################################################ # # TH1 Commands # -fossil test-th-eval --th-open-config "setting abc" +fossil test-th-eval --open-config "setting th1-hooks" +set th1Hooks [expr {$RESULT eq "1"}] + +############################################################################### + +fossil test-th-eval --open-config "setting abc" test th1-setting-1 {$RESULT eq ""} ############################################################################### -fossil test-th-eval --th-open-config "setting -- abc" +fossil test-th-eval --open-config "setting -- abc" test th1-setting-2 {$RESULT eq ""} ############################################################################### -fossil test-th-eval --th-open-config "setting -strict abc" +fossil test-th-eval --open-config "setting -strict abc" test th1-setting-3 {$RESULT eq {TH_ERROR: no value for setting "abc"}} ############################################################################### -fossil test-th-eval --th-open-config "setting -strict -- abc" +fossil test-th-eval --open-config "setting -strict -- abc" test th1-setting-4 {$RESULT eq {TH_ERROR: no value for setting "abc"}} ############################################################################### -fossil test-th-eval --th-open-config "setting autosync" -test th1-setting-5 {$RESULT eq 1} +fossil test-th-eval --open-config "setting autosync" +test th1-setting-5 {$RESULT eq 0 || $RESULT eq 1} ############################################################################### -fossil test-th-eval --th-open-config "setting -strict autosync" -test th1-setting-6 {$RESULT eq 1} +fossil test-th-eval --open-config "setting -strict autosync" +test th1-setting-6 {$RESULT eq 0 || $RESULT eq 1} ############################################################################### -fossil test-th-eval --th-open-config "setting --" +fossil test-th-eval --open-config "setting --" test th1-setting-7 {$RESULT eq \ {TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}} ############################################################################### -fossil test-th-eval --th-open-config "setting -strict --" +fossil test-th-eval --open-config "setting -strict --" test th1-setting-8 {$RESULT eq \ {TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}} ############################################################################### -fossil test-th-eval --th-open-config "setting -- --" +fossil test-th-eval --open-config "setting -- --" test th1-setting-9 {$RESULT eq {}} ############################################################################### -fossil test-th-eval --th-open-config "setting -strict -- --" +fossil test-th-eval --open-config "setting -strict -- --" test th1-setting-10 {$RESULT eq {TH_ERROR: no value for setting "--"}} + +############################################################################### + +fossil test-th-eval "expr 42/0" +test th1-divide-by-zero-1 {$RESULT eq {TH_ERROR: Divide by 0: 42}} + +############################################################################### + +fossil test-th-eval "expr 42/0.0" +test th1-divide-by-zero-2 {$RESULT eq {TH_ERROR: Divide by 0: 42}} + +############################################################################### + +fossil test-th-eval "expr 42.0/0" +test th1-divide-by-zero-3 {$RESULT eq {TH_ERROR: Divide by 0: 42.0}} + +############################################################################### + +fossil test-th-eval "expr 42.0/0.0" +test th1-divide-by-zero-4 {$RESULT eq {TH_ERROR: Divide by 0: 42.0}} + +############################################################################### + +fossil test-th-eval "expr 42%0" +test th1-modulus-by-zero-1 {$RESULT eq {TH_ERROR: Modulo by 0: 42}} + +############################################################################### + +fossil test-th-eval "expr 42%0.0" +test th1-modulus-by-zero-2 {$RESULT eq {TH_ERROR: expected integer, got: "0.0"}} + +############################################################################### + +fossil test-th-eval "expr 42.0%0" +test th1-modulus-by-zero-3 {$RESULT eq \ +{TH_ERROR: expected integer, got: "42.0"}} + +############################################################################### + +fossil test-th-eval "expr 42.0%0.0" +test th1-modulus-by-zero-4 {$RESULT eq \ +{TH_ERROR: expected integer, got: "42.0"}} + +############################################################################### + +fossil test-th-eval "set var 1; info exists var" +test th1-info-exists-1 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "set var 1; unset var; info exists var" +test th1-info-exists-2 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "set var 1; unset var; set var 2; info exists var" +test th1-info-exists-3 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "set var 1; expr {\$var+0}" +test th1-info-exists-4 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "set var 1; unset var; expr {\$var+0}" +test th1-info-exists-5 {$RESULT eq {TH_ERROR: no such variable: var}} + +############################################################################### + +fossil test-th-eval "catch {bad}; info exists var; set th_stack_trace" +test th1-info-exists-6 {$RESULT eq {bad}} + +############################################################################### + +fossil test-th-eval "set var(1) 1; info exists var" +test th1-info-exists-7 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "set var(1) 1; unset var(1); info exists var" +test th1-info-exists-8 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "set var(1) 1; unset var; info exists var" +test th1-info-exists-9 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "set var(1) 1; info exists var(1)" +test th1-info-exists-10 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "set var(1) 1; unset var(1); info exists var(1)" +test th1-info-exists-11 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "set var(1) 1; unset var; info exists var(1)" +test th1-info-exists-12 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "set var 1; unset var" +test th1-unset-1 {$RESULT eq {var}} + +############################################################################### + +fossil test-th-eval "unset var" +test th1-unset-2 {$RESULT eq {TH_ERROR: no such variable: var}} + +############################################################################### + +fossil test-th-eval "set var 1; unset var; unset var" +test th1-unset-3 {$RESULT eq {TH_ERROR: no such variable: var}} + +############################################################################### + +fossil test-th-eval "set gv 1; proc p {} {upvar 1 gv lv; unset lv}; p; unset gv" +test th1-unset-4 {$RESULT eq {TH_ERROR: no such variable: gv}} + +############################################################################### + +fossil test-th-eval "set gv 1; upvar 0 gv gv2; info exists gv2" +test th1-unset-5 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "set gv 1; upvar 0 gv gv2; unset gv; unset gv2" +test th1-unset-6 {$RESULT eq {TH_ERROR: no such variable: gv2}} + +############################################################################### + +fossil test-th-eval "set gv 1; upvar 0 gv gv2(1); unset gv; unset gv2(1)" +test th1-unset-7 {$RESULT eq {TH_ERROR: no such variable: gv2(1)}} + +############################################################################### + +fossil test-th-eval "set gv(1) 1; upvar 0 gv(1) gv2; unset gv(1); unset gv2" +test th1-unset-8 {$RESULT eq {TH_ERROR: no such variable: gv2}} + +############################################################################### + +fossil test-th-eval "string first {} {}" +test th1-string-first-1 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "string first {} {a}" +test th1-string-first-2 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "string first {a} {}" +test th1-string-first-3 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "string first {a} {a}" +test th1-string-first-4 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "string first {a} {aa}" +test th1-string-first-5 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "string first {aa} {a}" +test th1-string-first-6 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "string first {ab} {abc}" +test th1-string-first-7 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "string first {bc} {abc}" +test th1-string-first-8 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "string first {AB} {abc}" +test th1-string-first-9 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "string last {} {}" +test th1-string-last-1 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "string last {} {a}" +test th1-string-last-2 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "string last {a} {}" +test th1-string-last-3 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "string last {a} {a}" +test th1-string-last-4 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "string last {a} {aa}" +test th1-string-last-5 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "string last {aa} {a}" +test th1-string-last-6 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "string last {ab} {abc}" +test th1-string-last-7 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "string last {bc} {abc}" +test th1-string-last-8 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "string last {AB} {abc}" +test th1-string-last-9 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "expr -2147483649.0" +test th1-expr-1 {$RESULT eq {-2147483649.0}} + +############################################################################### + +fossil test-th-eval "expr -2147483649" +test th1-expr-2 {$RESULT eq {2147483647}} + +############################################################################### + +fossil test-th-eval "expr -2147483648" +test th1-expr-3 {$RESULT eq {-2147483648}} + +############################################################################### + +fossil test-th-eval "expr -2147483647" +test th1-expr-4 {$RESULT eq {-2147483647}} + +############################################################################### + +fossil test-th-eval "expr -1" +test th1-expr-5 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "expr 0" +test th1-expr-6 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "expr 0.0" +test th1-expr-7 {$RESULT eq {0.0}} + +############################################################################### + +fossil test-th-eval "expr 1" +test th1-expr-8 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "expr 2147483647" +test th1-expr-9 {$RESULT eq {2147483647}} + +############################################################################### + +fossil test-th-eval "expr 2147483648" +test th1-expr-10 {$RESULT eq {2147483648}} + +############################################################################### + +fossil test-th-eval "expr 2147483649" +test th1-expr-11 {$RESULT eq {2147483649}} + +############################################################################### + +fossil test-th-eval "expr +2147483649" +test th1-expr-12 {$RESULT eq {-2147483647}} + +############################################################################### + +fossil test-th-eval "expr +2147483649.0" +test th1-expr-13 {$RESULT eq {2147483649.0}} + +############################################################################### + +fossil test-th-eval "expr ~(-1)" +test th1-expr-14 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "expr ~-1" +test th1-expr-15 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "expr ~0" +test th1-expr-16 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "expr ~+0" +test th1-expr-17 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "expr ~-0" +test th1-expr-18 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "expr ~(+0)" +test th1-expr-19 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "expr ~(-0)" +test th1-expr-20 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "expr ~1" +test th1-expr-21 {$RESULT eq {-2}} + +############################################################################### + +fossil test-th-eval "expr ~+1" +test th1-expr-22 {$RESULT eq {-2}} + +############################################################################### + +fossil test-th-eval "expr ~(+1)" +test th1-expr-23 {$RESULT eq {-2}} + +############################################################################### + +fossil test-th-eval "expr 0+0b11" +test th1-expr-24 {$RESULT eq 3} + +############################################################################### + +fossil test-th-eval "expr 0+0o15" +test th1-expr-25 {$RESULT eq 13} + +############################################################################### + +fossil test-th-eval "expr 0+0x15" +test th1-expr-26 {$RESULT eq 21} + +############################################################################### + +fossil test-th-eval "expr 0+0b2" +test th1-expr-27 {$RESULT eq {TH_ERROR: expected number, got: "0b2"}} + +############################################################################### + +fossil test-th-eval "expr 0+0o8" +test th1-expr-28 {$RESULT eq {TH_ERROR: expected number, got: "0o8"}} + +############################################################################### + +fossil test-th-eval "expr 0+0xg" +test th1-expr-29 {$RESULT eq {TH_ERROR: syntax error in expression: "0+0xg"}} + +############################################################################### + +fossil test-th-eval "expr 0+0b1." +test th1-expr-30 {$RESULT eq {TH_ERROR: syntax error in expression: "0+0b1."}} + +############################################################################### + +fossil test-th-eval "expr 0+0o1." +test th1-expr-31 {$RESULT eq {TH_ERROR: syntax error in expression: "0+0o1."}} + +############################################################################### + +fossil test-th-eval "expr 0+0x1." +test th1-expr-32 {$RESULT eq {TH_ERROR: syntax error in expression: "0+0x1."}} + +############################################################################### + +fossil test-th-eval "expr 0ne5" +test th1-expr-33 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "expr 0b1+5" +test th1-expr-34 {$RESULT eq {6}} + +############################################################################### + +fossil test-th-eval "expr 0+0b" +test th1-expr-35 {$RESULT eq {TH_ERROR: expected number, got: "0b"}} + +############################################################################### + +fossil test-th-eval "expr (-1)+1" +test th1-expr-36 {$RESULT eq {0}} + +############################################################################### + +fossil test-th-eval "expr (((-1)))" +test th1-expr-37 {$RESULT eq {-1}} + +############################################################################### + +fossil test-th-eval "expr (((1)))" +test th1-expr-38 {$RESULT eq {1}} + +############################################################################### + +fossil test-th-eval "expr (((1))" +test th1-expr-39 {$RESULT eq {TH_ERROR: syntax error in expression: "(((1))"}} + +############################################################################### + +fossil test-th-eval "expr ((1)))" +test th1-expr-40 {$RESULT eq {TH_ERROR: syntax error in expression: "((1)))"}} + +############################################################################### + +fossil test-th-eval "expr (((1)*2)*2)" +test th1-expr-41 {$RESULT eq {4}} + +############################################################################### + +fossil test-th-eval "expr +" +test th1-expr-42 {$RESULT eq {TH_ERROR: syntax error in expression: "+"}} + +############################################################################### + +fossil test-th-eval "expr -" +test th1-expr-43 {$RESULT eq {TH_ERROR: syntax error in expression: "-"}} + +############################################################################### + +fossil test-th-eval "expr ++" +test th1-expr-44 {$RESULT eq {TH_ERROR: syntax error in expression: "++"}} + +############################################################################### + +fossil test-th-eval "expr --" +test th1-expr-45 {$RESULT eq {TH_ERROR: syntax error in expression: "--"}} + +############################################################################### + +fossil test-th-eval "lindex list +" +test th1-expr-46 {$RESULT eq {TH_ERROR: expected integer, got: "+"}} + +############################################################################### + +fossil test-th-eval "lindex list -" +test th1-expr-47 {$RESULT eq {TH_ERROR: expected integer, got: "-"}} + +############################################################################### + +fossil test-th-eval "lindex list +0x" +test th1-expr-48 {$RESULT eq {TH_ERROR: expected integer, got: "+0x"}} + +############################################################################### + +fossil test-th-eval "lindex list -0x" +test th1-expr-49 {$RESULT eq {TH_ERROR: expected integer, got: "-0x"}} + +############################################################################### + +fossil test-th-eval "checkout 1"; # NOTE: Assumes running "in tree". +test th1-checkout-1 {[string length $RESULT] > 0} + +############################################################################### + +fossil test-th-eval "checkout"; # NOTE: Assumes running "in tree". +test th1-checkout-2 {[string length $RESULT] > 0} + +############################################################################### + +set savedPwd [pwd]; cd / +fossil test-th-eval "checkout 1" +cd $savedPwd; unset savedPwd +test th1-checkout-3 {[string length $RESULT] == 0} + +############################################################################### + +set savedPwd [pwd]; cd / +fossil test-th-eval "checkout" +cd $savedPwd; unset savedPwd +test th1-checkout-4 {[string length $RESULT] == 0} + +############################################################################### + +fossil test-th-eval "render {}" +test th1-render-1 {$RESULT eq {}} + +############################################################################### + +fossil test-th-eval "render {$ before set x 123 after $ }" +test th1-render-2 {$RESULT eq {no such variable: x before after 123 }} + +############################################################################### + +fossil test-th-eval "trace {}" +test th1-trace-1 {$RESULT eq {}} + +############################################################################### + +fossil test-th-eval --th-trace "trace {}" +if {$th1Hooks} { + test th1-trace-2 {[string map [list \r\n \n] [string trim $RESULT]] eq \ +{------------------ BEGIN TRACE LOG ------------------ +th1-init 0x0 => 0x0
            + +------------------- END TRACE LOG -------------------}} +} else { + test th1-trace-2 {[string map [list \r\n \n] [string trim $RESULT]] eq \ + {------------------ BEGIN TRACE LOG ------------------ +th1-init 0x0 => 0x0
            +th1-setup {} => TH_OK
            + +------------------- END TRACE LOG -------------------}} +} + +############################################################################### + +fossil test-th-eval "trace {this is a trace message.}" +test th1-trace-3 {$RESULT eq {}} + +############################################################################### + +fossil test-th-eval --th-trace "trace {this is a trace message.}" +if {$th1Hooks} { + test th1-trace-4 {[string map [list \r\n \n] [string trim $RESULT]] eq \ + {------------------ BEGIN TRACE LOG ------------------ +th1-init 0x0 => 0x0
            +this is a trace message. +------------------- END TRACE LOG -------------------}} +} else { + test th1-trace-4 {[string map [list \r\n \n] [string trim $RESULT]] eq \ + {------------------ BEGIN TRACE LOG ------------------ +th1-init 0x0 => 0x0
            +th1-setup {} => TH_OK
            +this is a trace message. +------------------- END TRACE LOG -------------------}} +} + +############################################################################### + +fossil test-th-eval "styleHeader {Page Title Here}" +test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}} + +############################################################################### + +fossil test-th-eval --open-config "styleHeader {Page Title Here}" +test th1-header-2 {[regexp -- {Fossil: Page Title Here} $RESULT]} + +############################################################################### + +fossil test-th-eval "styleFooter" +test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}} + +############################################################################### + +fossil test-th-eval --open-config "styleFooter" +test th1-footer-2 {$RESULT eq {}} + +############################################################################### + +fossil test-th-eval --open-config --cgi "styleHeader {}; styleFooter" +test th1-footer-3 {[regexp -- {} $RESULT]} + +############################################################################### + +fossil test-th-eval "getParameter" +test th1-get-parameter-1 {$RESULT eq \ + {TH_ERROR: wrong # args: should be "getParameter NAME ?DEFAULT?"}} + +############################################################################### + +fossil test-th-eval "getParameter test1" +test th1-get-parameter-2 {$RESULT eq {}} + +############################################################################### + +fossil test-th-eval "getParameter test1 defValue1" +test th1-get-parameter-3 {$RESULT eq {defValue1}} + +############################################################################### + +fossil test-th-eval "setParameter" +test th1-set-parameter-1 {$RESULT eq \ + {TH_ERROR: wrong # args: should be "setParameter NAME VALUE"}} + +############################################################################### + +fossil test-th-eval "setParameter test1 value1; getParameter test1" +test th1-set-parameter-2 {$RESULT eq {value1}} + +############################################################################### + +fossil test-th-eval "setParameter test2 value2; getParameter test1" +test th1-set-parameter-3 {$RESULT eq {}} + +############################################################################### + +fossil test-th-eval "setParameter test3 value3; getParameter test3" +test th1-set-parameter-4 {$RESULT eq {value3}} + +############################################################################### + +fossil test-th-eval "setParameter test3 value3; getParameter test3 defValue3" +test th1-set-parameter-5 {$RESULT eq {value3}} + +############################################################################### + +fossil test-th-eval "setParameter test4 value4; setParameter test4 value5; getParameter test4" +test th1-set-parameter-6 {$RESULT eq {value5}} + +############################################################################### + +fossil test-th-eval "setParameter test4 value4; setParameter test4 value5; getParameter test4 defValue4" +test th1-set-parameter-7 {$RESULT eq {value5}} + +############################################################################### + +fossil test-th-eval "artifact" +test th1-artifact-1 {$RESULT eq \ + {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}} + +############################################################################### + +fossil test-th-eval "artifact tip" +test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}} + +############################################################################### + +fossil test-th-eval --open-config "artifact tip" +test th1-artifact-3 {[regexp -- {F test/th1\.test [0-9a-f]{40}} $RESULT]} + +############################################################################### + +fossil test-th-eval "artifact 0000000000" +test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}} + +############################################################################### + +fossil test-th-eval --open-config "artifact 0000000000" +test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}} + +############################################################################### + +fossil test-th-eval "artifact tip test/th1.test" +test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}} + +############################################################################### + +fossil test-th-eval --open-config "artifact tip test/th1.test" +test th1-artifact-7 {[regexp -- {th1-artifact-7} $RESULT]} + +############################################################################### + +fossil test-th-eval "artifact 0000000000 test/th1.test" +test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}} + +############################################################################### + +fossil test-th-eval --open-config "artifact 0000000000 test/th1.test" +test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}} + +############################################################################### + +fossil test-th-eval "globalState checkout" +test th1-globalState-1 {[string length $RESULT] > 0} + +############################################################################### + +fossil test-th-eval "globalState checkout" +test th1-globalState-2 {$RESULT eq [fossil test-th-eval checkout]} + +############################################################################### + +fossil test-th-eval "globalState configuration" +test th1-globalState-3 {[string length $RESULT] == 0} + +############################################################################### + +fossil test-th-eval --open-config "globalState configuration" +test th1-globalState-4 {[string length $RESULT] > 0} + +############################################################################### + +fossil test-th-eval "globalState executable" +test th1-globalState-5 {[file rootname [file tail $RESULT]] eq "fossil"} + +############################################################################### + +fossil test-th-eval "globalState log" +test th1-globalState-6 {[string length $RESULT] == 0} + +############################################################################### + +fossil test-th-eval --errorlog foserrors.log "globalState log" +test th1-globalState-7 {$RESULT eq "foserrors.log"} + +############################################################################### + +fossil test-th-eval "globalState repository" +test th1-globalState-8 {[string length $RESULT] > 0} + +############################################################################### + +fossil test-th-eval "globalState repository" +test th1-globalState-9 {$RESULT eq [fossil test-th-eval repository]} + +############################################################################### + +fossil test-th-eval "globalState top" +test th1-globalState-10 {[string length $RESULT] == 0} + +############################################################################### + +fossil test-th-eval "globalState user" +test th1-globalState-11 {[string length $RESULT] == 0} + +############################################################################### + +fossil test-th-eval --user fossil-th1-test "globalState user" +test th1-globalState-12 {$RESULT eq "fossil-th1-test"} + +############################################################################### + +fossil test-th-eval "globalState vfs" +test th1-globalState-13 {[string length $RESULT] == 0} + +############################################################################### + +fossil test-th-eval "globalState vfs" +test th1-globalState-14 {[string length $RESULT] == 0} + +############################################################################### + +if {$tcl_platform(platform) eq "windows"} then { + set altVfs win32-longpath +} else { + set altVfs unix-dotfile +} + +############################################################################### + +fossil test-th-eval --vfs $altVfs "globalState vfs" +test th1-globalState-15 {$RESULT eq $altVfs} + +############################################################################### + +fossil test-th-eval "globalState flags" +test th1-globalState-16 {$RESULT eq "0"} + +############################################################################### + +fossil test-th-eval "reinitialize; globalState configuration" +test th1-reinitialize-1 {$RESULT eq ""} + +############################################################################### + +fossil test-th-eval "reinitialize 1; globalState configuration" +test th1-reinitialize-2 {$RESULT ne ""} Index: test/utf.test ================================================================== --- test/utf.test +++ test/utf.test @@ -16,14 +16,10 @@ ############################################################################ # # Test UTF-8/UTF-16 detection # -proc appendArgs {args} { - eval append result $args -} - proc swap_byte_order {str} { set result "" for {set i 0} {$i < [string length $str]} {incr i} { set c [scan [string index $str $i] %c] set c [expr {(($c << 8) & 0xFF00) | (($c >> 8) & 0xFF)}] @@ -217,10 +213,34 @@ 152 \xF0\x80\x80\r\n \ 153 \xF0\x80\x80\x80\x00 \ 154 \xF0\x80\x80\x80\r \ 155 \xF0\x80\x80\x80\n \ 156 \xF0\x80\x80\x80\r\n \ + 157 \xC0\x80\x00 \ + 158 \xC0\x80\r \ + 159 \xC0\x80\n \ + 160 \xC0\x80\r\n \ + 161 \xC0\x81\x00 \ + 162 \xC0\x81\r \ + 163 \xC0\x81\n \ + 164 \xC0\x81\r\n \ + 165 \xC1\x80\x00 \ + 166 \xC1\x80\r \ + 167 \xC1\x80\n \ + 168 \xC1\x80\r\n \ + 169 \xE0\x80\x80\x00 \ + 170 \xE0\x80\x80\r \ + 171 \xE0\x80\x80\n \ + 172 \xE0\x80\x80\r\n \ + 173 \xF4\x8F\xBF\xBF\x00 \ + 174 \xF4\x8F\xBF\xBF\r \ + 175 \xF4\x8F\xBF\xBF\n \ + 176 \xF4\x8F\xBF\xBF\r\n \ + 177 \xF4\x90\x80\x80\x00 \ + 178 \xF4\x90\x80\x80\r \ + 179 \xF4\x90\x80\x80\n \ + 180 \xF4\x90\x80\x80\r\n \ ] array set extraData [list \ 0 "" \ 1 Z \ @@ -291,20 +311,12 @@ } } flush $f; close $f } -set tempPath [expr {[info exists env(TEMP)] ? \ - $env(TEMP) : [file dirname [info script]]}] - -if {$tcl_platform(platform) eq "windows"} then { - set tempPath [string map [list \\ /] $tempPath] -} - createTestFiles $tempPath 100 # createTestResults $tempPath 100 - ########################### BEGIN GENERATED SECTION ########################### utf-check 100 utf-check-100-0-0-0.jnk \ {File "%TEMP%/utf-check-100-0-0-0.jnk" has 0 bytes. Starts with UTF-8 BOM: no @@ -4475,11 +4487,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 361 utf-check-361-0-130-1.jnk \ {File "%TEMP%/utf-check-361-0-130-1.jnk" has 2 bytes. @@ -4491,11 +4503,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 362 utf-check-362-0-131-0.jnk \ {File "%TEMP%/utf-check-362-0-131-0.jnk" has 1 bytes. @@ -4507,11 +4519,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 363 utf-check-363-0-131-1.jnk \ {File "%TEMP%/utf-check-363-0-131-1.jnk" has 2 bytes. @@ -4523,11 +4535,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 364 utf-check-364-0-132-0.jnk \ {File "%TEMP%/utf-check-364-0-132-0.jnk" has 1 bytes. @@ -4539,11 +4551,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 365 utf-check-365-0-132-1.jnk \ {File "%TEMP%/utf-check-365-0-132-1.jnk" has 2 bytes. @@ -4555,11 +4567,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 366 utf-check-366-0-133-0.jnk \ {File "%TEMP%/utf-check-366-0-133-0.jnk" has 2 bytes. @@ -4571,11 +4583,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 367 utf-check-367-0-133-1.jnk \ {File "%TEMP%/utf-check-367-0-133-1.jnk" has 3 bytes. @@ -4587,11 +4599,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 368 utf-check-368-0-134-0.jnk \ {File "%TEMP%/utf-check-368-0-134-0.jnk" has 2 bytes. @@ -4603,11 +4615,11 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 369 utf-check-369-0-134-1.jnk \ {File "%TEMP%/utf-check-369-0-134-1.jnk" has 3 bytes. @@ -4619,11 +4631,11 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 370 utf-check-370-0-135-0.jnk \ {File "%TEMP%/utf-check-370-0-135-0.jnk" has 2 bytes. @@ -4635,11 +4647,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 371 utf-check-371-0-135-1.jnk \ {File "%TEMP%/utf-check-371-0-135-1.jnk" has 3 bytes. @@ -4651,11 +4663,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 372 utf-check-372-0-136-0.jnk \ {File "%TEMP%/utf-check-372-0-136-0.jnk" has 3 bytes. @@ -4667,11 +4679,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 373 utf-check-373-0-136-1.jnk \ {File "%TEMP%/utf-check-373-0-136-1.jnk" has 4 bytes. @@ -4683,11 +4695,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 374 utf-check-374-0-137-0.jnk \ {File "%TEMP%/utf-check-374-0-137-0.jnk" has 2 bytes. @@ -4699,11 +4711,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 375 utf-check-375-0-137-1.jnk \ {File "%TEMP%/utf-check-375-0-137-1.jnk" has 3 bytes. @@ -4715,11 +4727,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 376 utf-check-376-0-138-0.jnk \ {File "%TEMP%/utf-check-376-0-138-0.jnk" has 2 bytes. @@ -4731,11 +4743,11 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 377 utf-check-377-0-138-1.jnk \ {File "%TEMP%/utf-check-377-0-138-1.jnk" has 3 bytes. @@ -4747,11 +4759,11 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 378 utf-check-378-0-139-0.jnk \ {File "%TEMP%/utf-check-378-0-139-0.jnk" has 2 bytes. @@ -4763,11 +4775,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 379 utf-check-379-0-139-1.jnk \ {File "%TEMP%/utf-check-379-0-139-1.jnk" has 3 bytes. @@ -4779,11 +4791,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 380 utf-check-380-0-140-0.jnk \ {File "%TEMP%/utf-check-380-0-140-0.jnk" has 3 bytes. @@ -4795,11 +4807,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 381 utf-check-381-0-140-1.jnk \ {File "%TEMP%/utf-check-381-0-140-1.jnk" has 4 bytes. @@ -4811,11 +4823,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 382 utf-check-382-0-141-0.jnk \ {File "%TEMP%/utf-check-382-0-141-0.jnk" has 2 bytes. @@ -4827,11 +4839,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 383 utf-check-383-0-141-1.jnk \ {File "%TEMP%/utf-check-383-0-141-1.jnk" has 3 bytes. @@ -4843,11 +4855,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 384 utf-check-384-0-142-0.jnk \ {File "%TEMP%/utf-check-384-0-142-0.jnk" has 2 bytes. @@ -4859,11 +4871,11 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 385 utf-check-385-0-142-1.jnk \ {File "%TEMP%/utf-check-385-0-142-1.jnk" has 3 bytes. @@ -4875,11 +4887,11 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 386 utf-check-386-0-143-0.jnk \ {File "%TEMP%/utf-check-386-0-143-0.jnk" has 2 bytes. @@ -4891,11 +4903,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 387 utf-check-387-0-143-1.jnk \ {File "%TEMP%/utf-check-387-0-143-1.jnk" has 3 bytes. @@ -4907,11 +4919,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 388 utf-check-388-0-144-0.jnk \ {File "%TEMP%/utf-check-388-0-144-0.jnk" has 3 bytes. @@ -4923,11 +4935,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 389 utf-check-389-0-144-1.jnk \ {File "%TEMP%/utf-check-389-0-144-1.jnk" has 4 bytes. @@ -4939,11 +4951,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 390 utf-check-390-0-145-0.jnk \ {File "%TEMP%/utf-check-390-0-145-0.jnk" has 3 bytes. @@ -4955,11 +4967,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 391 utf-check-391-0-145-1.jnk \ {File "%TEMP%/utf-check-391-0-145-1.jnk" has 4 bytes. @@ -4971,11 +4983,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 392 utf-check-392-0-146-0.jnk \ {File "%TEMP%/utf-check-392-0-146-0.jnk" has 3 bytes. @@ -4987,11 +4999,11 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 393 utf-check-393-0-146-1.jnk \ {File "%TEMP%/utf-check-393-0-146-1.jnk" has 4 bytes. @@ -5003,11 +5015,11 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 394 utf-check-394-0-147-0.jnk \ {File "%TEMP%/utf-check-394-0-147-0.jnk" has 3 bytes. @@ -5019,11 +5031,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 395 utf-check-395-0-147-1.jnk \ {File "%TEMP%/utf-check-395-0-147-1.jnk" has 4 bytes. @@ -5035,11 +5047,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 396 utf-check-396-0-148-0.jnk \ {File "%TEMP%/utf-check-396-0-148-0.jnk" has 4 bytes. @@ -5051,11 +5063,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 397 utf-check-397-0-148-1.jnk \ {File "%TEMP%/utf-check-397-0-148-1.jnk" has 5 bytes. @@ -5067,11 +5079,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 398 utf-check-398-0-149-0.jnk \ {File "%TEMP%/utf-check-398-0-149-0.jnk" has 4 bytes. @@ -5083,11 +5095,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 399 utf-check-399-0-149-1.jnk \ {File "%TEMP%/utf-check-399-0-149-1.jnk" has 5 bytes. @@ -5099,11 +5111,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 400 utf-check-400-0-150-0.jnk \ {File "%TEMP%/utf-check-400-0-150-0.jnk" has 4 bytes. @@ -5115,11 +5127,11 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 401 utf-check-401-0-150-1.jnk \ {File "%TEMP%/utf-check-401-0-150-1.jnk" has 5 bytes. @@ -5131,11 +5143,11 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 402 utf-check-402-0-151-0.jnk \ {File "%TEMP%/utf-check-402-0-151-0.jnk" has 4 bytes. @@ -5147,11 +5159,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 403 utf-check-403-0-151-1.jnk \ {File "%TEMP%/utf-check-403-0-151-1.jnk" has 5 bytes. @@ -5163,11 +5175,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 404 utf-check-404-0-152-0.jnk \ {File "%TEMP%/utf-check-404-0-152-0.jnk" has 5 bytes. @@ -5179,11 +5191,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 405 utf-check-405-0-152-1.jnk \ {File "%TEMP%/utf-check-405-0-152-1.jnk" has 6 bytes. @@ -5195,11 +5207,11 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 406 utf-check-406-0-153-0.jnk \ {File "%TEMP%/utf-check-406-0-153-0.jnk" has 5 bytes. @@ -5327,76 +5339,6524 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 414 utf-check-414-1-0-0.jnk \ -{File "%TEMP%/utf-check-414-1-0-0.jnk" has 3 bytes. +utf-check 414 utf-check-414-0-157-0.jnk \ +{File "%TEMP%/utf-check-414-0-157-0.jnk" has 3 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 415 utf-check-415-0-157-1.jnk \ +{File "%TEMP%/utf-check-415-0-157-1.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 416 utf-check-416-0-158-0.jnk \ +{File "%TEMP%/utf-check-416-0-158-0.jnk" has 3 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 417 utf-check-417-0-158-1.jnk \ +{File "%TEMP%/utf-check-417-0-158-1.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 418 utf-check-418-0-159-0.jnk \ +{File "%TEMP%/utf-check-418-0-159-0.jnk" has 3 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 419 utf-check-419-0-159-1.jnk \ +{File "%TEMP%/utf-check-419-0-159-1.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 420 utf-check-420-0-160-0.jnk \ +{File "%TEMP%/utf-check-420-0-160-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 421 utf-check-421-0-160-1.jnk \ +{File "%TEMP%/utf-check-421-0-160-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 422 utf-check-422-0-161-0.jnk \ +{File "%TEMP%/utf-check-422-0-161-0.jnk" has 3 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 423 utf-check-423-0-161-1.jnk \ +{File "%TEMP%/utf-check-423-0-161-1.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 424 utf-check-424-0-162-0.jnk \ +{File "%TEMP%/utf-check-424-0-162-0.jnk" has 3 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 425 utf-check-425-0-162-1.jnk \ +{File "%TEMP%/utf-check-425-0-162-1.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 426 utf-check-426-0-163-0.jnk \ +{File "%TEMP%/utf-check-426-0-163-0.jnk" has 3 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 427 utf-check-427-0-163-1.jnk \ +{File "%TEMP%/utf-check-427-0-163-1.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 428 utf-check-428-0-164-0.jnk \ +{File "%TEMP%/utf-check-428-0-164-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 429 utf-check-429-0-164-1.jnk \ +{File "%TEMP%/utf-check-429-0-164-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 430 utf-check-430-0-165-0.jnk \ +{File "%TEMP%/utf-check-430-0-165-0.jnk" has 3 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 431 utf-check-431-0-165-1.jnk \ +{File "%TEMP%/utf-check-431-0-165-1.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 432 utf-check-432-0-166-0.jnk \ +{File "%TEMP%/utf-check-432-0-166-0.jnk" has 3 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 433 utf-check-433-0-166-1.jnk \ +{File "%TEMP%/utf-check-433-0-166-1.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 434 utf-check-434-0-167-0.jnk \ +{File "%TEMP%/utf-check-434-0-167-0.jnk" has 3 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 435 utf-check-435-0-167-1.jnk \ +{File "%TEMP%/utf-check-435-0-167-1.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 436 utf-check-436-0-168-0.jnk \ +{File "%TEMP%/utf-check-436-0-168-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 437 utf-check-437-0-168-1.jnk \ +{File "%TEMP%/utf-check-437-0-168-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 438 utf-check-438-0-169-0.jnk \ +{File "%TEMP%/utf-check-438-0-169-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 439 utf-check-439-0-169-1.jnk \ +{File "%TEMP%/utf-check-439-0-169-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 440 utf-check-440-0-170-0.jnk \ +{File "%TEMP%/utf-check-440-0-170-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 441 utf-check-441-0-170-1.jnk \ +{File "%TEMP%/utf-check-441-0-170-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 442 utf-check-442-0-171-0.jnk \ +{File "%TEMP%/utf-check-442-0-171-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 443 utf-check-443-0-171-1.jnk \ +{File "%TEMP%/utf-check-443-0-171-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 444 utf-check-444-0-172-0.jnk \ +{File "%TEMP%/utf-check-444-0-172-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 445 utf-check-445-0-172-1.jnk \ +{File "%TEMP%/utf-check-445-0-172-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 446 utf-check-446-0-173-0.jnk \ +{File "%TEMP%/utf-check-446-0-173-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 447 utf-check-447-0-173-1.jnk \ +{File "%TEMP%/utf-check-447-0-173-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 448 utf-check-448-0-174-0.jnk \ +{File "%TEMP%/utf-check-448-0-174-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 449 utf-check-449-0-174-1.jnk \ +{File "%TEMP%/utf-check-449-0-174-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 450 utf-check-450-0-175-0.jnk \ +{File "%TEMP%/utf-check-450-0-175-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 451 utf-check-451-0-175-1.jnk \ +{File "%TEMP%/utf-check-451-0-175-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 452 utf-check-452-0-176-0.jnk \ +{File "%TEMP%/utf-check-452-0-176-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 453 utf-check-453-0-176-1.jnk \ +{File "%TEMP%/utf-check-453-0-176-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 454 utf-check-454-0-177-0.jnk \ +{File "%TEMP%/utf-check-454-0-177-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 455 utf-check-455-0-177-1.jnk \ +{File "%TEMP%/utf-check-455-0-177-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 456 utf-check-456-0-178-0.jnk \ +{File "%TEMP%/utf-check-456-0-178-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 457 utf-check-457-0-178-1.jnk \ +{File "%TEMP%/utf-check-457-0-178-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 458 utf-check-458-0-179-0.jnk \ +{File "%TEMP%/utf-check-458-0-179-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 459 utf-check-459-0-179-1.jnk \ +{File "%TEMP%/utf-check-459-0-179-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 460 utf-check-460-0-180-0.jnk \ +{File "%TEMP%/utf-check-460-0-180-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 461 utf-check-461-0-180-1.jnk \ +{File "%TEMP%/utf-check-461-0-180-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 462 utf-check-462-1-0-0.jnk \ +{File "%TEMP%/utf-check-462-1-0-0.jnk" has 3 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 463 utf-check-463-1-0-1.jnk \ +{File "%TEMP%/utf-check-463-1-0-1.jnk" has 4 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 464 utf-check-464-1-1-0.jnk \ +{File "%TEMP%/utf-check-464-1-1-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 465 utf-check-465-1-1-1.jnk \ +{File "%TEMP%/utf-check-465-1-1-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 466 utf-check-466-1-2-0.jnk \ +{File "%TEMP%/utf-check-466-1-2-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 467 utf-check-467-1-2-1.jnk \ +{File "%TEMP%/utf-check-467-1-2-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 468 utf-check-468-1-3-0.jnk \ +{File "%TEMP%/utf-check-468-1-3-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 469 utf-check-469-1-3-1.jnk \ +{File "%TEMP%/utf-check-469-1-3-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 470 utf-check-470-1-4-0.jnk \ +{File "%TEMP%/utf-check-470-1-4-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 471 utf-check-471-1-4-1.jnk \ +{File "%TEMP%/utf-check-471-1-4-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 472 utf-check-472-1-5-0.jnk \ +{File "%TEMP%/utf-check-472-1-5-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 473 utf-check-473-1-5-1.jnk \ +{File "%TEMP%/utf-check-473-1-5-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 474 utf-check-474-1-6-0.jnk \ +{File "%TEMP%/utf-check-474-1-6-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 475 utf-check-475-1-6-1.jnk \ +{File "%TEMP%/utf-check-475-1-6-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 476 utf-check-476-1-7-0.jnk \ +{File "%TEMP%/utf-check-476-1-7-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 477 utf-check-477-1-7-1.jnk \ +{File "%TEMP%/utf-check-477-1-7-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 478 utf-check-478-1-8-0.jnk \ +{File "%TEMP%/utf-check-478-1-8-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 479 utf-check-479-1-8-1.jnk \ +{File "%TEMP%/utf-check-479-1-8-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 480 utf-check-480-1-9-0.jnk \ +{File "%TEMP%/utf-check-480-1-9-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 481 utf-check-481-1-9-1.jnk \ +{File "%TEMP%/utf-check-481-1-9-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 482 utf-check-482-1-10-0.jnk \ +{File "%TEMP%/utf-check-482-1-10-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 483 utf-check-483-1-10-1.jnk \ +{File "%TEMP%/utf-check-483-1-10-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 484 utf-check-484-1-11-0.jnk \ +{File "%TEMP%/utf-check-484-1-11-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 485 utf-check-485-1-11-1.jnk \ +{File "%TEMP%/utf-check-485-1-11-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 486 utf-check-486-1-12-0.jnk \ +{File "%TEMP%/utf-check-486-1-12-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 487 utf-check-487-1-12-1.jnk \ +{File "%TEMP%/utf-check-487-1-12-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 488 utf-check-488-1-13-0.jnk \ +{File "%TEMP%/utf-check-488-1-13-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 489 utf-check-489-1-13-1.jnk \ +{File "%TEMP%/utf-check-489-1-13-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 490 utf-check-490-1-14-0.jnk \ +{File "%TEMP%/utf-check-490-1-14-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 491 utf-check-491-1-14-1.jnk \ +{File "%TEMP%/utf-check-491-1-14-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 492 utf-check-492-1-15-0.jnk \ +{File "%TEMP%/utf-check-492-1-15-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 493 utf-check-493-1-15-1.jnk \ +{File "%TEMP%/utf-check-493-1-15-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 494 utf-check-494-1-16-0.jnk \ +{File "%TEMP%/utf-check-494-1-16-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 495 utf-check-495-1-16-1.jnk \ +{File "%TEMP%/utf-check-495-1-16-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 496 utf-check-496-1-17-0.jnk \ +{File "%TEMP%/utf-check-496-1-17-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 497 utf-check-497-1-17-1.jnk \ +{File "%TEMP%/utf-check-497-1-17-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 498 utf-check-498-1-18-0.jnk \ +{File "%TEMP%/utf-check-498-1-18-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 499 utf-check-499-1-18-1.jnk \ +{File "%TEMP%/utf-check-499-1-18-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 500 utf-check-500-1-19-0.jnk \ +{File "%TEMP%/utf-check-500-1-19-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 501 utf-check-501-1-19-1.jnk \ +{File "%TEMP%/utf-check-501-1-19-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 502 utf-check-502-1-20-0.jnk \ +{File "%TEMP%/utf-check-502-1-20-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 503 utf-check-503-1-20-1.jnk \ +{File "%TEMP%/utf-check-503-1-20-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 504 utf-check-504-1-21-0.jnk \ +{File "%TEMP%/utf-check-504-1-21-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 505 utf-check-505-1-21-1.jnk \ +{File "%TEMP%/utf-check-505-1-21-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 506 utf-check-506-1-22-0.jnk \ +{File "%TEMP%/utf-check-506-1-22-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 507 utf-check-507-1-22-1.jnk \ +{File "%TEMP%/utf-check-507-1-22-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 508 utf-check-508-1-23-0.jnk \ +{File "%TEMP%/utf-check-508-1-23-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 509 utf-check-509-1-23-1.jnk \ +{File "%TEMP%/utf-check-509-1-23-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 510 utf-check-510-1-24-0.jnk \ +{File "%TEMP%/utf-check-510-1-24-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 511 utf-check-511-1-24-1.jnk \ +{File "%TEMP%/utf-check-511-1-24-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 512 utf-check-512-1-25-0.jnk \ +{File "%TEMP%/utf-check-512-1-25-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 513 utf-check-513-1-25-1.jnk \ +{File "%TEMP%/utf-check-513-1-25-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 514 utf-check-514-1-26-0.jnk \ +{File "%TEMP%/utf-check-514-1-26-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 515 utf-check-515-1-26-1.jnk \ +{File "%TEMP%/utf-check-515-1-26-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 516 utf-check-516-1-27-0.jnk \ +{File "%TEMP%/utf-check-516-1-27-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 517 utf-check-517-1-27-1.jnk \ +{File "%TEMP%/utf-check-517-1-27-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 518 utf-check-518-1-28-0.jnk \ +{File "%TEMP%/utf-check-518-1-28-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 519 utf-check-519-1-28-1.jnk \ +{File "%TEMP%/utf-check-519-1-28-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 520 utf-check-520-1-29-0.jnk \ +{File "%TEMP%/utf-check-520-1-29-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 521 utf-check-521-1-29-1.jnk \ +{File "%TEMP%/utf-check-521-1-29-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 522 utf-check-522-1-30-0.jnk \ +{File "%TEMP%/utf-check-522-1-30-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 523 utf-check-523-1-30-1.jnk \ +{File "%TEMP%/utf-check-523-1-30-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 524 utf-check-524-1-31-0.jnk \ +{File "%TEMP%/utf-check-524-1-31-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 525 utf-check-525-1-31-1.jnk \ +{File "%TEMP%/utf-check-525-1-31-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 526 utf-check-526-1-32-0.jnk \ +{File "%TEMP%/utf-check-526-1-32-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 527 utf-check-527-1-32-1.jnk \ +{File "%TEMP%/utf-check-527-1-32-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 528 utf-check-528-1-33-0.jnk \ +{File "%TEMP%/utf-check-528-1-33-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 529 utf-check-529-1-33-1.jnk \ +{File "%TEMP%/utf-check-529-1-33-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 530 utf-check-530-1-34-0.jnk \ +{File "%TEMP%/utf-check-530-1-34-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 531 utf-check-531-1-34-1.jnk \ +{File "%TEMP%/utf-check-531-1-34-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 532 utf-check-532-1-35-0.jnk \ +{File "%TEMP%/utf-check-532-1-35-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 533 utf-check-533-1-35-1.jnk \ +{File "%TEMP%/utf-check-533-1-35-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 534 utf-check-534-1-36-0.jnk \ +{File "%TEMP%/utf-check-534-1-36-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 535 utf-check-535-1-36-1.jnk \ +{File "%TEMP%/utf-check-535-1-36-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 536 utf-check-536-1-37-0.jnk \ +{File "%TEMP%/utf-check-536-1-37-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 537 utf-check-537-1-37-1.jnk \ +{File "%TEMP%/utf-check-537-1-37-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 538 utf-check-538-1-38-0.jnk \ +{File "%TEMP%/utf-check-538-1-38-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 539 utf-check-539-1-38-1.jnk \ +{File "%TEMP%/utf-check-539-1-38-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 540 utf-check-540-1-39-0.jnk \ +{File "%TEMP%/utf-check-540-1-39-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 541 utf-check-541-1-39-1.jnk \ +{File "%TEMP%/utf-check-541-1-39-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 542 utf-check-542-1-40-0.jnk \ +{File "%TEMP%/utf-check-542-1-40-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 543 utf-check-543-1-40-1.jnk \ +{File "%TEMP%/utf-check-543-1-40-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 544 utf-check-544-1-41-0.jnk \ +{File "%TEMP%/utf-check-544-1-41-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 545 utf-check-545-1-41-1.jnk \ +{File "%TEMP%/utf-check-545-1-41-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 546 utf-check-546-1-42-0.jnk \ +{File "%TEMP%/utf-check-546-1-42-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 547 utf-check-547-1-42-1.jnk \ +{File "%TEMP%/utf-check-547-1-42-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 548 utf-check-548-1-43-0.jnk \ +{File "%TEMP%/utf-check-548-1-43-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 549 utf-check-549-1-43-1.jnk \ +{File "%TEMP%/utf-check-549-1-43-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 550 utf-check-550-1-44-0.jnk \ +{File "%TEMP%/utf-check-550-1-44-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 551 utf-check-551-1-44-1.jnk \ +{File "%TEMP%/utf-check-551-1-44-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 552 utf-check-552-1-45-0.jnk \ +{File "%TEMP%/utf-check-552-1-45-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 553 utf-check-553-1-45-1.jnk \ +{File "%TEMP%/utf-check-553-1-45-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 554 utf-check-554-1-46-0.jnk \ +{File "%TEMP%/utf-check-554-1-46-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 555 utf-check-555-1-46-1.jnk \ +{File "%TEMP%/utf-check-555-1-46-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 556 utf-check-556-1-47-0.jnk \ +{File "%TEMP%/utf-check-556-1-47-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 557 utf-check-557-1-47-1.jnk \ +{File "%TEMP%/utf-check-557-1-47-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 558 utf-check-558-1-48-0.jnk \ +{File "%TEMP%/utf-check-558-1-48-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 559 utf-check-559-1-48-1.jnk \ +{File "%TEMP%/utf-check-559-1-48-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 560 utf-check-560-1-49-0.jnk \ +{File "%TEMP%/utf-check-560-1-49-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 561 utf-check-561-1-49-1.jnk \ +{File "%TEMP%/utf-check-561-1-49-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 562 utf-check-562-1-50-0.jnk \ +{File "%TEMP%/utf-check-562-1-50-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 563 utf-check-563-1-50-1.jnk \ +{File "%TEMP%/utf-check-563-1-50-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 564 utf-check-564-1-51-0.jnk \ +{File "%TEMP%/utf-check-564-1-51-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 565 utf-check-565-1-51-1.jnk \ +{File "%TEMP%/utf-check-565-1-51-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 566 utf-check-566-1-52-0.jnk \ +{File "%TEMP%/utf-check-566-1-52-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 567 utf-check-567-1-52-1.jnk \ +{File "%TEMP%/utf-check-567-1-52-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 568 utf-check-568-1-53-0.jnk \ +{File "%TEMP%/utf-check-568-1-53-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 569 utf-check-569-1-53-1.jnk \ +{File "%TEMP%/utf-check-569-1-53-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 570 utf-check-570-1-54-0.jnk \ +{File "%TEMP%/utf-check-570-1-54-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 571 utf-check-571-1-54-1.jnk \ +{File "%TEMP%/utf-check-571-1-54-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 572 utf-check-572-1-55-0.jnk \ +{File "%TEMP%/utf-check-572-1-55-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 573 utf-check-573-1-55-1.jnk \ +{File "%TEMP%/utf-check-573-1-55-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 574 utf-check-574-1-56-0.jnk \ +{File "%TEMP%/utf-check-574-1-56-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 575 utf-check-575-1-56-1.jnk \ +{File "%TEMP%/utf-check-575-1-56-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 576 utf-check-576-1-57-0.jnk \ +{File "%TEMP%/utf-check-576-1-57-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 577 utf-check-577-1-57-1.jnk \ +{File "%TEMP%/utf-check-577-1-57-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 578 utf-check-578-1-58-0.jnk \ +{File "%TEMP%/utf-check-578-1-58-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 579 utf-check-579-1-58-1.jnk \ +{File "%TEMP%/utf-check-579-1-58-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 580 utf-check-580-1-59-0.jnk \ +{File "%TEMP%/utf-check-580-1-59-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 581 utf-check-581-1-59-1.jnk \ +{File "%TEMP%/utf-check-581-1-59-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 582 utf-check-582-1-60-0.jnk \ +{File "%TEMP%/utf-check-582-1-60-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 583 utf-check-583-1-60-1.jnk \ +{File "%TEMP%/utf-check-583-1-60-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 584 utf-check-584-1-61-0.jnk \ +{File "%TEMP%/utf-check-584-1-61-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 585 utf-check-585-1-61-1.jnk \ +{File "%TEMP%/utf-check-585-1-61-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 586 utf-check-586-1-62-0.jnk \ +{File "%TEMP%/utf-check-586-1-62-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 587 utf-check-587-1-62-1.jnk \ +{File "%TEMP%/utf-check-587-1-62-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 588 utf-check-588-1-63-0.jnk \ +{File "%TEMP%/utf-check-588-1-63-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 589 utf-check-589-1-63-1.jnk \ +{File "%TEMP%/utf-check-589-1-63-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 590 utf-check-590-1-64-0.jnk \ +{File "%TEMP%/utf-check-590-1-64-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 591 utf-check-591-1-64-1.jnk \ +{File "%TEMP%/utf-check-591-1-64-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 592 utf-check-592-1-65-0.jnk \ +{File "%TEMP%/utf-check-592-1-65-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 593 utf-check-593-1-65-1.jnk \ +{File "%TEMP%/utf-check-593-1-65-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 594 utf-check-594-1-66-0.jnk \ +{File "%TEMP%/utf-check-594-1-66-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 595 utf-check-595-1-66-1.jnk \ +{File "%TEMP%/utf-check-595-1-66-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 596 utf-check-596-1-67-0.jnk \ +{File "%TEMP%/utf-check-596-1-67-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 597 utf-check-597-1-67-1.jnk \ +{File "%TEMP%/utf-check-597-1-67-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 598 utf-check-598-1-68-0.jnk \ +{File "%TEMP%/utf-check-598-1-68-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 599 utf-check-599-1-68-1.jnk \ +{File "%TEMP%/utf-check-599-1-68-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 600 utf-check-600-1-69-0.jnk \ +{File "%TEMP%/utf-check-600-1-69-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 601 utf-check-601-1-69-1.jnk \ +{File "%TEMP%/utf-check-601-1-69-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 602 utf-check-602-1-70-0.jnk \ +{File "%TEMP%/utf-check-602-1-70-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 603 utf-check-603-1-70-1.jnk \ +{File "%TEMP%/utf-check-603-1-70-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 604 utf-check-604-1-71-0.jnk \ +{File "%TEMP%/utf-check-604-1-71-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 605 utf-check-605-1-71-1.jnk \ +{File "%TEMP%/utf-check-605-1-71-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 606 utf-check-606-1-72-0.jnk \ +{File "%TEMP%/utf-check-606-1-72-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 607 utf-check-607-1-72-1.jnk \ +{File "%TEMP%/utf-check-607-1-72-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 608 utf-check-608-1-73-0.jnk \ +{File "%TEMP%/utf-check-608-1-73-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 609 utf-check-609-1-73-1.jnk \ +{File "%TEMP%/utf-check-609-1-73-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 610 utf-check-610-1-74-0.jnk \ +{File "%TEMP%/utf-check-610-1-74-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 611 utf-check-611-1-74-1.jnk \ +{File "%TEMP%/utf-check-611-1-74-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 612 utf-check-612-1-75-0.jnk \ +{File "%TEMP%/utf-check-612-1-75-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 613 utf-check-613-1-75-1.jnk \ +{File "%TEMP%/utf-check-613-1-75-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 614 utf-check-614-1-76-0.jnk \ +{File "%TEMP%/utf-check-614-1-76-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 615 utf-check-615-1-76-1.jnk \ +{File "%TEMP%/utf-check-615-1-76-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 616 utf-check-616-1-77-0.jnk \ +{File "%TEMP%/utf-check-616-1-77-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 617 utf-check-617-1-77-1.jnk \ +{File "%TEMP%/utf-check-617-1-77-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 618 utf-check-618-1-78-0.jnk \ +{File "%TEMP%/utf-check-618-1-78-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 619 utf-check-619-1-78-1.jnk \ +{File "%TEMP%/utf-check-619-1-78-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 620 utf-check-620-1-79-0.jnk \ +{File "%TEMP%/utf-check-620-1-79-0.jnk" has 11 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 621 utf-check-621-1-79-1.jnk \ +{File "%TEMP%/utf-check-621-1-79-1.jnk" has 12 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 622 utf-check-622-1-80-0.jnk \ +{File "%TEMP%/utf-check-622-1-80-0.jnk" has 8196 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 623 utf-check-623-1-80-1.jnk \ +{File "%TEMP%/utf-check-623-1-80-1.jnk" has 8197 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 624 utf-check-624-1-81-0.jnk \ +{File "%TEMP%/utf-check-624-1-81-0.jnk" has 8197 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 625 utf-check-625-1-81-1.jnk \ +{File "%TEMP%/utf-check-625-1-81-1.jnk" has 8198 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 626 utf-check-626-1-82-0.jnk \ +{File "%TEMP%/utf-check-626-1-82-0.jnk" has 8197 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 627 utf-check-627-1-82-1.jnk \ +{File "%TEMP%/utf-check-627-1-82-1.jnk" has 8198 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 628 utf-check-628-1-83-0.jnk \ +{File "%TEMP%/utf-check-628-1-83-0.jnk" has 8198 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 629 utf-check-629-1-83-1.jnk \ +{File "%TEMP%/utf-check-629-1-83-1.jnk" has 8199 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 630 utf-check-630-1-84-0.jnk \ +{File "%TEMP%/utf-check-630-1-84-0.jnk" has 8199 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 631 utf-check-631-1-84-1.jnk \ +{File "%TEMP%/utf-check-631-1-84-1.jnk" has 8200 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 632 utf-check-632-1-85-0.jnk \ +{File "%TEMP%/utf-check-632-1-85-0.jnk" has 8200 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 633 utf-check-633-1-85-1.jnk \ +{File "%TEMP%/utf-check-633-1-85-1.jnk" has 8201 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 634 utf-check-634-1-86-0.jnk \ +{File "%TEMP%/utf-check-634-1-86-0.jnk" has 8200 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 635 utf-check-635-1-86-1.jnk \ +{File "%TEMP%/utf-check-635-1-86-1.jnk" has 8201 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 636 utf-check-636-1-87-0.jnk \ +{File "%TEMP%/utf-check-636-1-87-0.jnk" has 8201 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 637 utf-check-637-1-87-1.jnk \ +{File "%TEMP%/utf-check-637-1-87-1.jnk" has 8202 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 638 utf-check-638-1-88-0.jnk \ +{File "%TEMP%/utf-check-638-1-88-0.jnk" has 8197 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 639 utf-check-639-1-88-1.jnk \ +{File "%TEMP%/utf-check-639-1-88-1.jnk" has 8198 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 640 utf-check-640-1-89-0.jnk \ +{File "%TEMP%/utf-check-640-1-89-0.jnk" has 8198 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 641 utf-check-641-1-89-1.jnk \ +{File "%TEMP%/utf-check-641-1-89-1.jnk" has 8199 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 642 utf-check-642-1-90-0.jnk \ +{File "%TEMP%/utf-check-642-1-90-0.jnk" has 8198 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 643 utf-check-643-1-90-1.jnk \ +{File "%TEMP%/utf-check-643-1-90-1.jnk" has 8199 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 644 utf-check-644-1-91-0.jnk \ +{File "%TEMP%/utf-check-644-1-91-0.jnk" has 8199 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 645 utf-check-645-1-91-1.jnk \ +{File "%TEMP%/utf-check-645-1-91-1.jnk" has 8200 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 646 utf-check-646-1-92-0.jnk \ +{File "%TEMP%/utf-check-646-1-92-0.jnk" has 8200 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 647 utf-check-647-1-92-1.jnk \ +{File "%TEMP%/utf-check-647-1-92-1.jnk" has 8201 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 648 utf-check-648-1-93-0.jnk \ +{File "%TEMP%/utf-check-648-1-93-0.jnk" has 8201 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 649 utf-check-649-1-93-1.jnk \ +{File "%TEMP%/utf-check-649-1-93-1.jnk" has 8202 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 650 utf-check-650-1-94-0.jnk \ +{File "%TEMP%/utf-check-650-1-94-0.jnk" has 8201 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 651 utf-check-651-1-94-1.jnk \ +{File "%TEMP%/utf-check-651-1-94-1.jnk" has 8202 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 652 utf-check-652-1-95-0.jnk \ +{File "%TEMP%/utf-check-652-1-95-0.jnk" has 8202 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 653 utf-check-653-1-95-1.jnk \ +{File "%TEMP%/utf-check-653-1-95-1.jnk" has 8203 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 654 utf-check-654-1-96-0.jnk \ +{File "%TEMP%/utf-check-654-1-96-0.jnk" has 8197 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 655 utf-check-655-1-96-1.jnk \ +{File "%TEMP%/utf-check-655-1-96-1.jnk" has 8198 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 656 utf-check-656-1-97-0.jnk \ +{File "%TEMP%/utf-check-656-1-97-0.jnk" has 8198 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 657 utf-check-657-1-97-1.jnk \ +{File "%TEMP%/utf-check-657-1-97-1.jnk" has 8199 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 658 utf-check-658-1-98-0.jnk \ +{File "%TEMP%/utf-check-658-1-98-0.jnk" has 8198 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 659 utf-check-659-1-98-1.jnk \ +{File "%TEMP%/utf-check-659-1-98-1.jnk" has 8199 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 660 utf-check-660-1-99-0.jnk \ +{File "%TEMP%/utf-check-660-1-99-0.jnk" has 8199 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 661 utf-check-661-1-99-1.jnk \ +{File "%TEMP%/utf-check-661-1-99-1.jnk" has 8200 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 662 utf-check-662-1-100-0.jnk \ +{File "%TEMP%/utf-check-662-1-100-0.jnk" has 8200 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 663 utf-check-663-1-100-1.jnk \ +{File "%TEMP%/utf-check-663-1-100-1.jnk" has 8201 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 664 utf-check-664-1-101-0.jnk \ +{File "%TEMP%/utf-check-664-1-101-0.jnk" has 8201 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 665 utf-check-665-1-101-1.jnk \ +{File "%TEMP%/utf-check-665-1-101-1.jnk" has 8202 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 666 utf-check-666-1-102-0.jnk \ +{File "%TEMP%/utf-check-666-1-102-0.jnk" has 8201 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 667 utf-check-667-1-102-1.jnk \ +{File "%TEMP%/utf-check-667-1-102-1.jnk" has 8202 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 668 utf-check-668-1-103-0.jnk \ +{File "%TEMP%/utf-check-668-1-103-0.jnk" has 8202 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 669 utf-check-669-1-103-1.jnk \ +{File "%TEMP%/utf-check-669-1-103-1.jnk" has 8203 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 670 utf-check-670-1-104-0.jnk \ +{File "%TEMP%/utf-check-670-1-104-0.jnk" has 8198 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 671 utf-check-671-1-104-1.jnk \ +{File "%TEMP%/utf-check-671-1-104-1.jnk" has 8199 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 672 utf-check-672-1-105-0.jnk \ +{File "%TEMP%/utf-check-672-1-105-0.jnk" has 8199 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 673 utf-check-673-1-105-1.jnk \ +{File "%TEMP%/utf-check-673-1-105-1.jnk" has 8200 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 674 utf-check-674-1-106-0.jnk \ +{File "%TEMP%/utf-check-674-1-106-0.jnk" has 8199 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 675 utf-check-675-1-106-1.jnk \ +{File "%TEMP%/utf-check-675-1-106-1.jnk" has 8200 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 676 utf-check-676-1-107-0.jnk \ +{File "%TEMP%/utf-check-676-1-107-0.jnk" has 8200 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 677 utf-check-677-1-107-1.jnk \ +{File "%TEMP%/utf-check-677-1-107-1.jnk" has 8201 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 678 utf-check-678-1-108-0.jnk \ +{File "%TEMP%/utf-check-678-1-108-0.jnk" has 8201 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 679 utf-check-679-1-108-1.jnk \ +{File "%TEMP%/utf-check-679-1-108-1.jnk" has 8202 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 680 utf-check-680-1-109-0.jnk \ +{File "%TEMP%/utf-check-680-1-109-0.jnk" has 8202 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 681 utf-check-681-1-109-1.jnk \ +{File "%TEMP%/utf-check-681-1-109-1.jnk" has 8203 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 682 utf-check-682-1-110-0.jnk \ +{File "%TEMP%/utf-check-682-1-110-0.jnk" has 8202 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 683 utf-check-683-1-110-1.jnk \ +{File "%TEMP%/utf-check-683-1-110-1.jnk" has 8203 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 684 utf-check-684-1-111-0.jnk \ +{File "%TEMP%/utf-check-684-1-111-0.jnk" has 8203 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 685 utf-check-685-1-111-1.jnk \ +{File "%TEMP%/utf-check-685-1-111-1.jnk" has 8204 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 686 utf-check-686-1-112-0.jnk \ +{File "%TEMP%/utf-check-686-1-112-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 687 utf-check-687-1-112-1.jnk \ +{File "%TEMP%/utf-check-687-1-112-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 688 utf-check-688-1-113-0.jnk \ +{File "%TEMP%/utf-check-688-1-113-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 689 utf-check-689-1-113-1.jnk \ +{File "%TEMP%/utf-check-689-1-113-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 690 utf-check-690-1-114-0.jnk \ +{File "%TEMP%/utf-check-690-1-114-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 691 utf-check-691-1-114-1.jnk \ +{File "%TEMP%/utf-check-691-1-114-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 692 utf-check-692-1-115-0.jnk \ +{File "%TEMP%/utf-check-692-1-115-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 693 utf-check-693-1-115-1.jnk \ +{File "%TEMP%/utf-check-693-1-115-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 694 utf-check-694-1-116-0.jnk \ +{File "%TEMP%/utf-check-694-1-116-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 695 utf-check-695-1-116-1.jnk \ +{File "%TEMP%/utf-check-695-1-116-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 696 utf-check-696-1-117-0.jnk \ +{File "%TEMP%/utf-check-696-1-117-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 697 utf-check-697-1-117-1.jnk \ +{File "%TEMP%/utf-check-697-1-117-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 698 utf-check-698-1-118-0.jnk \ +{File "%TEMP%/utf-check-698-1-118-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 699 utf-check-699-1-118-1.jnk \ +{File "%TEMP%/utf-check-699-1-118-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 700 utf-check-700-1-119-0.jnk \ +{File "%TEMP%/utf-check-700-1-119-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 701 utf-check-701-1-119-1.jnk \ +{File "%TEMP%/utf-check-701-1-119-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 702 utf-check-702-1-120-0.jnk \ +{File "%TEMP%/utf-check-702-1-120-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 703 utf-check-703-1-120-1.jnk \ +{File "%TEMP%/utf-check-703-1-120-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 704 utf-check-704-1-121-0.jnk \ +{File "%TEMP%/utf-check-704-1-121-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 705 utf-check-705-1-121-1.jnk \ +{File "%TEMP%/utf-check-705-1-121-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 706 utf-check-706-1-122-0.jnk \ +{File "%TEMP%/utf-check-706-1-122-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 707 utf-check-707-1-122-1.jnk \ +{File "%TEMP%/utf-check-707-1-122-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 708 utf-check-708-1-123-0.jnk \ +{File "%TEMP%/utf-check-708-1-123-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 709 utf-check-709-1-123-1.jnk \ +{File "%TEMP%/utf-check-709-1-123-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 710 utf-check-710-1-124-0.jnk \ +{File "%TEMP%/utf-check-710-1-124-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 711 utf-check-711-1-124-1.jnk \ +{File "%TEMP%/utf-check-711-1-124-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 712 utf-check-712-1-125-0.jnk \ +{File "%TEMP%/utf-check-712-1-125-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 713 utf-check-713-1-125-1.jnk \ +{File "%TEMP%/utf-check-713-1-125-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 714 utf-check-714-1-126-0.jnk \ +{File "%TEMP%/utf-check-714-1-126-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 715 utf-check-715-1-126-1.jnk \ +{File "%TEMP%/utf-check-715-1-126-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 716 utf-check-716-1-127-0.jnk \ +{File "%TEMP%/utf-check-716-1-127-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 717 utf-check-717-1-127-1.jnk \ +{File "%TEMP%/utf-check-717-1-127-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 718 utf-check-718-1-128-0.jnk \ +{File "%TEMP%/utf-check-718-1-128-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 719 utf-check-719-1-128-1.jnk \ +{File "%TEMP%/utf-check-719-1-128-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 720 utf-check-720-1-129-0.jnk \ +{File "%TEMP%/utf-check-720-1-129-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 721 utf-check-721-1-129-1.jnk \ +{File "%TEMP%/utf-check-721-1-129-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 722 utf-check-722-1-130-0.jnk \ +{File "%TEMP%/utf-check-722-1-130-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 723 utf-check-723-1-130-1.jnk \ +{File "%TEMP%/utf-check-723-1-130-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 724 utf-check-724-1-131-0.jnk \ +{File "%TEMP%/utf-check-724-1-131-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 725 utf-check-725-1-131-1.jnk \ +{File "%TEMP%/utf-check-725-1-131-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 726 utf-check-726-1-132-0.jnk \ +{File "%TEMP%/utf-check-726-1-132-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 727 utf-check-727-1-132-1.jnk \ +{File "%TEMP%/utf-check-727-1-132-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 728 utf-check-728-1-133-0.jnk \ +{File "%TEMP%/utf-check-728-1-133-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 729 utf-check-729-1-133-1.jnk \ +{File "%TEMP%/utf-check-729-1-133-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 730 utf-check-730-1-134-0.jnk \ +{File "%TEMP%/utf-check-730-1-134-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 731 utf-check-731-1-134-1.jnk \ +{File "%TEMP%/utf-check-731-1-134-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 732 utf-check-732-1-135-0.jnk \ +{File "%TEMP%/utf-check-732-1-135-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 733 utf-check-733-1-135-1.jnk \ +{File "%TEMP%/utf-check-733-1-135-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 734 utf-check-734-1-136-0.jnk \ +{File "%TEMP%/utf-check-734-1-136-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 735 utf-check-735-1-136-1.jnk \ +{File "%TEMP%/utf-check-735-1-136-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 736 utf-check-736-1-137-0.jnk \ +{File "%TEMP%/utf-check-736-1-137-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 737 utf-check-737-1-137-1.jnk \ +{File "%TEMP%/utf-check-737-1-137-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 738 utf-check-738-1-138-0.jnk \ +{File "%TEMP%/utf-check-738-1-138-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 739 utf-check-739-1-138-1.jnk \ +{File "%TEMP%/utf-check-739-1-138-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 740 utf-check-740-1-139-0.jnk \ +{File "%TEMP%/utf-check-740-1-139-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 741 utf-check-741-1-139-1.jnk \ +{File "%TEMP%/utf-check-741-1-139-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 742 utf-check-742-1-140-0.jnk \ +{File "%TEMP%/utf-check-742-1-140-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 743 utf-check-743-1-140-1.jnk \ +{File "%TEMP%/utf-check-743-1-140-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 744 utf-check-744-1-141-0.jnk \ +{File "%TEMP%/utf-check-744-1-141-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 745 utf-check-745-1-141-1.jnk \ +{File "%TEMP%/utf-check-745-1-141-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 746 utf-check-746-1-142-0.jnk \ +{File "%TEMP%/utf-check-746-1-142-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 747 utf-check-747-1-142-1.jnk \ +{File "%TEMP%/utf-check-747-1-142-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 748 utf-check-748-1-143-0.jnk \ +{File "%TEMP%/utf-check-748-1-143-0.jnk" has 5 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 749 utf-check-749-1-143-1.jnk \ +{File "%TEMP%/utf-check-749-1-143-1.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 750 utf-check-750-1-144-0.jnk \ +{File "%TEMP%/utf-check-750-1-144-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 751 utf-check-751-1-144-1.jnk \ +{File "%TEMP%/utf-check-751-1-144-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 752 utf-check-752-1-145-0.jnk \ +{File "%TEMP%/utf-check-752-1-145-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 753 utf-check-753-1-145-1.jnk \ +{File "%TEMP%/utf-check-753-1-145-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 754 utf-check-754-1-146-0.jnk \ +{File "%TEMP%/utf-check-754-1-146-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 755 utf-check-755-1-146-1.jnk \ +{File "%TEMP%/utf-check-755-1-146-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 756 utf-check-756-1-147-0.jnk \ +{File "%TEMP%/utf-check-756-1-147-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 757 utf-check-757-1-147-1.jnk \ +{File "%TEMP%/utf-check-757-1-147-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 758 utf-check-758-1-148-0.jnk \ +{File "%TEMP%/utf-check-758-1-148-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 759 utf-check-759-1-148-1.jnk \ +{File "%TEMP%/utf-check-759-1-148-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 760 utf-check-760-1-149-0.jnk \ +{File "%TEMP%/utf-check-760-1-149-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 761 utf-check-761-1-149-1.jnk \ +{File "%TEMP%/utf-check-761-1-149-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 762 utf-check-762-1-150-0.jnk \ +{File "%TEMP%/utf-check-762-1-150-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 763 utf-check-763-1-150-1.jnk \ +{File "%TEMP%/utf-check-763-1-150-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 764 utf-check-764-1-151-0.jnk \ +{File "%TEMP%/utf-check-764-1-151-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 765 utf-check-765-1-151-1.jnk \ +{File "%TEMP%/utf-check-765-1-151-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 766 utf-check-766-1-152-0.jnk \ +{File "%TEMP%/utf-check-766-1-152-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 767 utf-check-767-1-152-1.jnk \ +{File "%TEMP%/utf-check-767-1-152-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 768 utf-check-768-1-153-0.jnk \ +{File "%TEMP%/utf-check-768-1-153-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 769 utf-check-769-1-153-1.jnk \ +{File "%TEMP%/utf-check-769-1-153-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 770 utf-check-770-1-154-0.jnk \ +{File "%TEMP%/utf-check-770-1-154-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 771 utf-check-771-1-154-1.jnk \ +{File "%TEMP%/utf-check-771-1-154-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 772 utf-check-772-1-155-0.jnk \ +{File "%TEMP%/utf-check-772-1-155-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 773 utf-check-773-1-155-1.jnk \ +{File "%TEMP%/utf-check-773-1-155-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 774 utf-check-774-1-156-0.jnk \ +{File "%TEMP%/utf-check-774-1-156-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 775 utf-check-775-1-156-1.jnk \ +{File "%TEMP%/utf-check-775-1-156-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 776 utf-check-776-1-157-0.jnk \ +{File "%TEMP%/utf-check-776-1-157-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 777 utf-check-777-1-157-1.jnk \ +{File "%TEMP%/utf-check-777-1-157-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 778 utf-check-778-1-158-0.jnk \ +{File "%TEMP%/utf-check-778-1-158-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 779 utf-check-779-1-158-1.jnk \ +{File "%TEMP%/utf-check-779-1-158-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 780 utf-check-780-1-159-0.jnk \ +{File "%TEMP%/utf-check-780-1-159-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 781 utf-check-781-1-159-1.jnk \ +{File "%TEMP%/utf-check-781-1-159-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 782 utf-check-782-1-160-0.jnk \ +{File "%TEMP%/utf-check-782-1-160-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 783 utf-check-783-1-160-1.jnk \ +{File "%TEMP%/utf-check-783-1-160-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 784 utf-check-784-1-161-0.jnk \ +{File "%TEMP%/utf-check-784-1-161-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 785 utf-check-785-1-161-1.jnk \ +{File "%TEMP%/utf-check-785-1-161-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 786 utf-check-786-1-162-0.jnk \ +{File "%TEMP%/utf-check-786-1-162-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 787 utf-check-787-1-162-1.jnk \ +{File "%TEMP%/utf-check-787-1-162-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 788 utf-check-788-1-163-0.jnk \ +{File "%TEMP%/utf-check-788-1-163-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 789 utf-check-789-1-163-1.jnk \ +{File "%TEMP%/utf-check-789-1-163-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 790 utf-check-790-1-164-0.jnk \ +{File "%TEMP%/utf-check-790-1-164-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 791 utf-check-791-1-164-1.jnk \ +{File "%TEMP%/utf-check-791-1-164-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 792 utf-check-792-1-165-0.jnk \ +{File "%TEMP%/utf-check-792-1-165-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 793 utf-check-793-1-165-1.jnk \ +{File "%TEMP%/utf-check-793-1-165-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 794 utf-check-794-1-166-0.jnk \ +{File "%TEMP%/utf-check-794-1-166-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 795 utf-check-795-1-166-1.jnk \ +{File "%TEMP%/utf-check-795-1-166-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 796 utf-check-796-1-167-0.jnk \ +{File "%TEMP%/utf-check-796-1-167-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 797 utf-check-797-1-167-1.jnk \ +{File "%TEMP%/utf-check-797-1-167-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 798 utf-check-798-1-168-0.jnk \ +{File "%TEMP%/utf-check-798-1-168-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 799 utf-check-799-1-168-1.jnk \ +{File "%TEMP%/utf-check-799-1-168-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 800 utf-check-800-1-169-0.jnk \ +{File "%TEMP%/utf-check-800-1-169-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 801 utf-check-801-1-169-1.jnk \ +{File "%TEMP%/utf-check-801-1-169-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 802 utf-check-802-1-170-0.jnk \ +{File "%TEMP%/utf-check-802-1-170-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 803 utf-check-803-1-170-1.jnk \ +{File "%TEMP%/utf-check-803-1-170-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 804 utf-check-804-1-171-0.jnk \ +{File "%TEMP%/utf-check-804-1-171-0.jnk" has 7 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 805 utf-check-805-1-171-1.jnk \ +{File "%TEMP%/utf-check-805-1-171-1.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 806 utf-check-806-1-172-0.jnk \ +{File "%TEMP%/utf-check-806-1-172-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 807 utf-check-807-1-172-1.jnk \ +{File "%TEMP%/utf-check-807-1-172-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 808 utf-check-808-1-173-0.jnk \ +{File "%TEMP%/utf-check-808-1-173-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 809 utf-check-809-1-173-1.jnk \ +{File "%TEMP%/utf-check-809-1-173-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 810 utf-check-810-1-174-0.jnk \ +{File "%TEMP%/utf-check-810-1-174-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 811 utf-check-811-1-174-1.jnk \ +{File "%TEMP%/utf-check-811-1-174-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 812 utf-check-812-1-175-0.jnk \ +{File "%TEMP%/utf-check-812-1-175-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 813 utf-check-813-1-175-1.jnk \ +{File "%TEMP%/utf-check-813-1-175-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 814 utf-check-814-1-176-0.jnk \ +{File "%TEMP%/utf-check-814-1-176-0.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 815 utf-check-815-1-176-1.jnk \ +{File "%TEMP%/utf-check-815-1-176-1.jnk" has 10 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 816 utf-check-816-1-177-0.jnk \ +{File "%TEMP%/utf-check-816-1-177-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 817 utf-check-817-1-177-1.jnk \ +{File "%TEMP%/utf-check-817-1-177-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 818 utf-check-818-1-178-0.jnk \ +{File "%TEMP%/utf-check-818-1-178-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: yes +Starts with UTF-16 BOM: no +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 819 utf-check-819-1-178-1.jnk \ +{File "%TEMP%/utf-check-819-1-178-1.jnk" has 9 bytes. Starts with UTF-8 BOM: yes Starts with UTF-16 BOM: no Looks like UTF-8: yes Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 415 utf-check-415-1-0-1.jnk \ -{File "%TEMP%/utf-check-415-1-0-1.jnk" has 4 bytes. +utf-check 820 utf-check-820-1-179-0.jnk \ +{File "%TEMP%/utf-check-820-1-179-0.jnk" has 8 bytes. Starts with UTF-8 BOM: yes Starts with UTF-16 BOM: no Looks like UTF-8: yes Has flag LOOK_NUL: no Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 416 utf-check-416-1-1-0.jnk \ -{File "%TEMP%/utf-check-416-1-1-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 417 utf-check-417-1-1-1.jnk \ -{File "%TEMP%/utf-check-417-1-1-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 418 utf-check-418-1-2-0.jnk \ -{File "%TEMP%/utf-check-418-1-2-0.jnk" has 4 bytes. +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 821 utf-check-821-1-179-1.jnk \ +{File "%TEMP%/utf-check-821-1-179-1.jnk" has 9 bytes. Starts with UTF-8 BOM: yes Starts with UTF-16 BOM: no Looks like UTF-8: yes Has flag LOOK_NUL: no Has flag LOOK_CR: no @@ -5403,48 +11863,16 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 419 utf-check-419-1-2-1.jnk \ -{File "%TEMP%/utf-check-419-1-2-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 420 utf-check-420-1-3-0.jnk \ -{File "%TEMP%/utf-check-420-1-3-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 421 utf-check-421-1-3-1.jnk \ -{File "%TEMP%/utf-check-421-1-3-1.jnk" has 6 bytes. +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 822 utf-check-822-1-180-0.jnk \ +{File "%TEMP%/utf-check-822-1-180-0.jnk" has 9 bytes. Starts with UTF-8 BOM: yes Starts with UTF-16 BOM: no Looks like UTF-8: yes Has flag LOOK_NUL: no Has flag LOOK_CR: yes @@ -5451,272 +11879,16 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 422 utf-check-422-1-4-0.jnk \ -{File "%TEMP%/utf-check-422-1-4-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 423 utf-check-423-1-4-1.jnk \ -{File "%TEMP%/utf-check-423-1-4-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 424 utf-check-424-1-5-0.jnk \ -{File "%TEMP%/utf-check-424-1-5-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 425 utf-check-425-1-5-1.jnk \ -{File "%TEMP%/utf-check-425-1-5-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 426 utf-check-426-1-6-0.jnk \ -{File "%TEMP%/utf-check-426-1-6-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 427 utf-check-427-1-6-1.jnk \ -{File "%TEMP%/utf-check-427-1-6-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 428 utf-check-428-1-7-0.jnk \ -{File "%TEMP%/utf-check-428-1-7-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 429 utf-check-429-1-7-1.jnk \ -{File "%TEMP%/utf-check-429-1-7-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 430 utf-check-430-1-8-0.jnk \ -{File "%TEMP%/utf-check-430-1-8-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 431 utf-check-431-1-8-1.jnk \ -{File "%TEMP%/utf-check-431-1-8-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 432 utf-check-432-1-9-0.jnk \ -{File "%TEMP%/utf-check-432-1-9-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 433 utf-check-433-1-9-1.jnk \ -{File "%TEMP%/utf-check-433-1-9-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 434 utf-check-434-1-10-0.jnk \ -{File "%TEMP%/utf-check-434-1-10-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 435 utf-check-435-1-10-1.jnk \ -{File "%TEMP%/utf-check-435-1-10-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 436 utf-check-436-1-11-0.jnk \ -{File "%TEMP%/utf-check-436-1-11-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 437 utf-check-437-1-11-1.jnk \ -{File "%TEMP%/utf-check-437-1-11-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 438 utf-check-438-1-12-0.jnk \ -{File "%TEMP%/utf-check-438-1-12-0.jnk" has 6 bytes. +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 823 utf-check-823-1-180-1.jnk \ +{File "%TEMP%/utf-check-823-1-180-1.jnk" has 10 bytes. Starts with UTF-8 BOM: yes Starts with UTF-16 BOM: no Looks like UTF-8: yes Has flag LOOK_NUL: no Has flag LOOK_CR: yes @@ -5723,115 +11895,5139 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 439 utf-check-439-1-12-1.jnk \ -{File "%TEMP%/utf-check-439-1-12-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 440 utf-check-440-1-13-0.jnk \ -{File "%TEMP%/utf-check-440-1-13-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 441 utf-check-441-1-13-1.jnk \ -{File "%TEMP%/utf-check-441-1-13-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 442 utf-check-442-1-14-0.jnk \ -{File "%TEMP%/utf-check-442-1-14-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 443 utf-check-443-1-14-1.jnk \ -{File "%TEMP%/utf-check-443-1-14-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 444 utf-check-444-1-15-0.jnk \ -{File "%TEMP%/utf-check-444-1-15-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 445 utf-check-445-1-15-1.jnk \ -{File "%TEMP%/utf-check-445-1-15-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 824 utf-check-824-2-0-0.jnk \ +{File "%TEMP%/utf-check-824-2-0-0.jnk" has 2 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 825 utf-check-825-2-0-1.jnk \ +{File "%TEMP%/utf-check-825-2-0-1.jnk" has 3 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 826 utf-check-826-2-1-0.jnk \ +{File "%TEMP%/utf-check-826-2-1-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 827 utf-check-827-2-1-1.jnk \ +{File "%TEMP%/utf-check-827-2-1-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 828 utf-check-828-2-2-0.jnk \ +{File "%TEMP%/utf-check-828-2-2-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 829 utf-check-829-2-2-1.jnk \ +{File "%TEMP%/utf-check-829-2-2-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 830 utf-check-830-2-3-0.jnk \ +{File "%TEMP%/utf-check-830-2-3-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 831 utf-check-831-2-3-1.jnk \ +{File "%TEMP%/utf-check-831-2-3-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 832 utf-check-832-2-4-0.jnk \ +{File "%TEMP%/utf-check-832-2-4-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 833 utf-check-833-2-4-1.jnk \ +{File "%TEMP%/utf-check-833-2-4-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 834 utf-check-834-2-5-0.jnk \ +{File "%TEMP%/utf-check-834-2-5-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 835 utf-check-835-2-5-1.jnk \ +{File "%TEMP%/utf-check-835-2-5-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 836 utf-check-836-2-6-0.jnk \ +{File "%TEMP%/utf-check-836-2-6-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 837 utf-check-837-2-6-1.jnk \ +{File "%TEMP%/utf-check-837-2-6-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 838 utf-check-838-2-7-0.jnk \ +{File "%TEMP%/utf-check-838-2-7-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 839 utf-check-839-2-7-1.jnk \ +{File "%TEMP%/utf-check-839-2-7-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 840 utf-check-840-2-8-0.jnk \ +{File "%TEMP%/utf-check-840-2-8-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 841 utf-check-841-2-8-1.jnk \ +{File "%TEMP%/utf-check-841-2-8-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 842 utf-check-842-2-9-0.jnk \ +{File "%TEMP%/utf-check-842-2-9-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 843 utf-check-843-2-9-1.jnk \ +{File "%TEMP%/utf-check-843-2-9-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 844 utf-check-844-2-10-0.jnk \ +{File "%TEMP%/utf-check-844-2-10-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 845 utf-check-845-2-10-1.jnk \ +{File "%TEMP%/utf-check-845-2-10-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 846 utf-check-846-2-11-0.jnk \ +{File "%TEMP%/utf-check-846-2-11-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 847 utf-check-847-2-11-1.jnk \ +{File "%TEMP%/utf-check-847-2-11-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 848 utf-check-848-2-12-0.jnk \ +{File "%TEMP%/utf-check-848-2-12-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 849 utf-check-849-2-12-1.jnk \ +{File "%TEMP%/utf-check-849-2-12-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 850 utf-check-850-2-13-0.jnk \ +{File "%TEMP%/utf-check-850-2-13-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 851 utf-check-851-2-13-1.jnk \ +{File "%TEMP%/utf-check-851-2-13-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 852 utf-check-852-2-14-0.jnk \ +{File "%TEMP%/utf-check-852-2-14-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 853 utf-check-853-2-14-1.jnk \ +{File "%TEMP%/utf-check-853-2-14-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 854 utf-check-854-2-15-0.jnk \ +{File "%TEMP%/utf-check-854-2-15-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 855 utf-check-855-2-15-1.jnk \ +{File "%TEMP%/utf-check-855-2-15-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 856 utf-check-856-2-16-0.jnk \ +{File "%TEMP%/utf-check-856-2-16-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 857 utf-check-857-2-16-1.jnk \ +{File "%TEMP%/utf-check-857-2-16-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 858 utf-check-858-2-17-0.jnk \ +{File "%TEMP%/utf-check-858-2-17-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 859 utf-check-859-2-17-1.jnk \ +{File "%TEMP%/utf-check-859-2-17-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 860 utf-check-860-2-18-0.jnk \ +{File "%TEMP%/utf-check-860-2-18-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 861 utf-check-861-2-18-1.jnk \ +{File "%TEMP%/utf-check-861-2-18-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 862 utf-check-862-2-19-0.jnk \ +{File "%TEMP%/utf-check-862-2-19-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 863 utf-check-863-2-19-1.jnk \ +{File "%TEMP%/utf-check-863-2-19-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 864 utf-check-864-2-20-0.jnk \ +{File "%TEMP%/utf-check-864-2-20-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 865 utf-check-865-2-20-1.jnk \ +{File "%TEMP%/utf-check-865-2-20-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 866 utf-check-866-2-21-0.jnk \ +{File "%TEMP%/utf-check-866-2-21-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 867 utf-check-867-2-21-1.jnk \ +{File "%TEMP%/utf-check-867-2-21-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 868 utf-check-868-2-22-0.jnk \ +{File "%TEMP%/utf-check-868-2-22-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 869 utf-check-869-2-22-1.jnk \ +{File "%TEMP%/utf-check-869-2-22-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 870 utf-check-870-2-23-0.jnk \ +{File "%TEMP%/utf-check-870-2-23-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 871 utf-check-871-2-23-1.jnk \ +{File "%TEMP%/utf-check-871-2-23-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 872 utf-check-872-2-24-0.jnk \ +{File "%TEMP%/utf-check-872-2-24-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 873 utf-check-873-2-24-1.jnk \ +{File "%TEMP%/utf-check-873-2-24-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 874 utf-check-874-2-25-0.jnk \ +{File "%TEMP%/utf-check-874-2-25-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 875 utf-check-875-2-25-1.jnk \ +{File "%TEMP%/utf-check-875-2-25-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 876 utf-check-876-2-26-0.jnk \ +{File "%TEMP%/utf-check-876-2-26-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 877 utf-check-877-2-26-1.jnk \ +{File "%TEMP%/utf-check-877-2-26-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 878 utf-check-878-2-27-0.jnk \ +{File "%TEMP%/utf-check-878-2-27-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 879 utf-check-879-2-27-1.jnk \ +{File "%TEMP%/utf-check-879-2-27-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 880 utf-check-880-2-28-0.jnk \ +{File "%TEMP%/utf-check-880-2-28-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 881 utf-check-881-2-28-1.jnk \ +{File "%TEMP%/utf-check-881-2-28-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 882 utf-check-882-2-29-0.jnk \ +{File "%TEMP%/utf-check-882-2-29-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 883 utf-check-883-2-29-1.jnk \ +{File "%TEMP%/utf-check-883-2-29-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 884 utf-check-884-2-30-0.jnk \ +{File "%TEMP%/utf-check-884-2-30-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 885 utf-check-885-2-30-1.jnk \ +{File "%TEMP%/utf-check-885-2-30-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 886 utf-check-886-2-31-0.jnk \ +{File "%TEMP%/utf-check-886-2-31-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 887 utf-check-887-2-31-1.jnk \ +{File "%TEMP%/utf-check-887-2-31-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 888 utf-check-888-2-32-0.jnk \ +{File "%TEMP%/utf-check-888-2-32-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 889 utf-check-889-2-32-1.jnk \ +{File "%TEMP%/utf-check-889-2-32-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 890 utf-check-890-2-33-0.jnk \ +{File "%TEMP%/utf-check-890-2-33-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 891 utf-check-891-2-33-1.jnk \ +{File "%TEMP%/utf-check-891-2-33-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 892 utf-check-892-2-34-0.jnk \ +{File "%TEMP%/utf-check-892-2-34-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 893 utf-check-893-2-34-1.jnk \ +{File "%TEMP%/utf-check-893-2-34-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 894 utf-check-894-2-35-0.jnk \ +{File "%TEMP%/utf-check-894-2-35-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 895 utf-check-895-2-35-1.jnk \ +{File "%TEMP%/utf-check-895-2-35-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 896 utf-check-896-2-36-0.jnk \ +{File "%TEMP%/utf-check-896-2-36-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 897 utf-check-897-2-36-1.jnk \ +{File "%TEMP%/utf-check-897-2-36-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 898 utf-check-898-2-37-0.jnk \ +{File "%TEMP%/utf-check-898-2-37-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 899 utf-check-899-2-37-1.jnk \ +{File "%TEMP%/utf-check-899-2-37-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 900 utf-check-900-2-38-0.jnk \ +{File "%TEMP%/utf-check-900-2-38-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 901 utf-check-901-2-38-1.jnk \ +{File "%TEMP%/utf-check-901-2-38-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 902 utf-check-902-2-39-0.jnk \ +{File "%TEMP%/utf-check-902-2-39-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 903 utf-check-903-2-39-1.jnk \ +{File "%TEMP%/utf-check-903-2-39-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 904 utf-check-904-2-40-0.jnk \ +{File "%TEMP%/utf-check-904-2-40-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 905 utf-check-905-2-40-1.jnk \ +{File "%TEMP%/utf-check-905-2-40-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 906 utf-check-906-2-41-0.jnk \ +{File "%TEMP%/utf-check-906-2-41-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 907 utf-check-907-2-41-1.jnk \ +{File "%TEMP%/utf-check-907-2-41-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 908 utf-check-908-2-42-0.jnk \ +{File "%TEMP%/utf-check-908-2-42-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 909 utf-check-909-2-42-1.jnk \ +{File "%TEMP%/utf-check-909-2-42-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 910 utf-check-910-2-43-0.jnk \ +{File "%TEMP%/utf-check-910-2-43-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 911 utf-check-911-2-43-1.jnk \ +{File "%TEMP%/utf-check-911-2-43-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 912 utf-check-912-2-44-0.jnk \ +{File "%TEMP%/utf-check-912-2-44-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 913 utf-check-913-2-44-1.jnk \ +{File "%TEMP%/utf-check-913-2-44-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 914 utf-check-914-2-45-0.jnk \ +{File "%TEMP%/utf-check-914-2-45-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 915 utf-check-915-2-45-1.jnk \ +{File "%TEMP%/utf-check-915-2-45-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 916 utf-check-916-2-46-0.jnk \ +{File "%TEMP%/utf-check-916-2-46-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 917 utf-check-917-2-46-1.jnk \ +{File "%TEMP%/utf-check-917-2-46-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 918 utf-check-918-2-47-0.jnk \ +{File "%TEMP%/utf-check-918-2-47-0.jnk" has 16 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 919 utf-check-919-2-47-1.jnk \ +{File "%TEMP%/utf-check-919-2-47-1.jnk" has 17 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 920 utf-check-920-2-48-0.jnk \ +{File "%TEMP%/utf-check-920-2-48-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 921 utf-check-921-2-48-1.jnk \ +{File "%TEMP%/utf-check-921-2-48-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 922 utf-check-922-2-49-0.jnk \ +{File "%TEMP%/utf-check-922-2-49-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 923 utf-check-923-2-49-1.jnk \ +{File "%TEMP%/utf-check-923-2-49-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 924 utf-check-924-2-50-0.jnk \ +{File "%TEMP%/utf-check-924-2-50-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 925 utf-check-925-2-50-1.jnk \ +{File "%TEMP%/utf-check-925-2-50-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 926 utf-check-926-2-51-0.jnk \ +{File "%TEMP%/utf-check-926-2-51-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 927 utf-check-927-2-51-1.jnk \ +{File "%TEMP%/utf-check-927-2-51-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 928 utf-check-928-2-52-0.jnk \ +{File "%TEMP%/utf-check-928-2-52-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 929 utf-check-929-2-52-1.jnk \ +{File "%TEMP%/utf-check-929-2-52-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 930 utf-check-930-2-53-0.jnk \ +{File "%TEMP%/utf-check-930-2-53-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 931 utf-check-931-2-53-1.jnk \ +{File "%TEMP%/utf-check-931-2-53-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 932 utf-check-932-2-54-0.jnk \ +{File "%TEMP%/utf-check-932-2-54-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 933 utf-check-933-2-54-1.jnk \ +{File "%TEMP%/utf-check-933-2-54-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 934 utf-check-934-2-55-0.jnk \ +{File "%TEMP%/utf-check-934-2-55-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 935 utf-check-935-2-55-1.jnk \ +{File "%TEMP%/utf-check-935-2-55-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 936 utf-check-936-2-56-0.jnk \ +{File "%TEMP%/utf-check-936-2-56-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 937 utf-check-937-2-56-1.jnk \ +{File "%TEMP%/utf-check-937-2-56-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 938 utf-check-938-2-57-0.jnk \ +{File "%TEMP%/utf-check-938-2-57-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 939 utf-check-939-2-57-1.jnk \ +{File "%TEMP%/utf-check-939-2-57-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 940 utf-check-940-2-58-0.jnk \ +{File "%TEMP%/utf-check-940-2-58-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 941 utf-check-941-2-58-1.jnk \ +{File "%TEMP%/utf-check-941-2-58-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 942 utf-check-942-2-59-0.jnk \ +{File "%TEMP%/utf-check-942-2-59-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 943 utf-check-943-2-59-1.jnk \ +{File "%TEMP%/utf-check-943-2-59-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 944 utf-check-944-2-60-0.jnk \ +{File "%TEMP%/utf-check-944-2-60-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 945 utf-check-945-2-60-1.jnk \ +{File "%TEMP%/utf-check-945-2-60-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 946 utf-check-946-2-61-0.jnk \ +{File "%TEMP%/utf-check-946-2-61-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 947 utf-check-947-2-61-1.jnk \ +{File "%TEMP%/utf-check-947-2-61-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 948 utf-check-948-2-62-0.jnk \ +{File "%TEMP%/utf-check-948-2-62-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 949 utf-check-949-2-62-1.jnk \ +{File "%TEMP%/utf-check-949-2-62-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 950 utf-check-950-2-63-0.jnk \ +{File "%TEMP%/utf-check-950-2-63-0.jnk" has 16 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 951 utf-check-951-2-63-1.jnk \ +{File "%TEMP%/utf-check-951-2-63-1.jnk" has 17 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 952 utf-check-952-2-64-0.jnk \ +{File "%TEMP%/utf-check-952-2-64-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 953 utf-check-953-2-64-1.jnk \ +{File "%TEMP%/utf-check-953-2-64-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 954 utf-check-954-2-65-0.jnk \ +{File "%TEMP%/utf-check-954-2-65-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 955 utf-check-955-2-65-1.jnk \ +{File "%TEMP%/utf-check-955-2-65-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 956 utf-check-956-2-66-0.jnk \ +{File "%TEMP%/utf-check-956-2-66-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 957 utf-check-957-2-66-1.jnk \ +{File "%TEMP%/utf-check-957-2-66-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 958 utf-check-958-2-67-0.jnk \ +{File "%TEMP%/utf-check-958-2-67-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 959 utf-check-959-2-67-1.jnk \ +{File "%TEMP%/utf-check-959-2-67-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 960 utf-check-960-2-68-0.jnk \ +{File "%TEMP%/utf-check-960-2-68-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 961 utf-check-961-2-68-1.jnk \ +{File "%TEMP%/utf-check-961-2-68-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 962 utf-check-962-2-69-0.jnk \ +{File "%TEMP%/utf-check-962-2-69-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 963 utf-check-963-2-69-1.jnk \ +{File "%TEMP%/utf-check-963-2-69-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 964 utf-check-964-2-70-0.jnk \ +{File "%TEMP%/utf-check-964-2-70-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 965 utf-check-965-2-70-1.jnk \ +{File "%TEMP%/utf-check-965-2-70-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 966 utf-check-966-2-71-0.jnk \ +{File "%TEMP%/utf-check-966-2-71-0.jnk" has 16 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 967 utf-check-967-2-71-1.jnk \ +{File "%TEMP%/utf-check-967-2-71-1.jnk" has 17 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 968 utf-check-968-2-72-0.jnk \ +{File "%TEMP%/utf-check-968-2-72-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 969 utf-check-969-2-72-1.jnk \ +{File "%TEMP%/utf-check-969-2-72-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 970 utf-check-970-2-73-0.jnk \ +{File "%TEMP%/utf-check-970-2-73-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 971 utf-check-971-2-73-1.jnk \ +{File "%TEMP%/utf-check-971-2-73-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 972 utf-check-972-2-74-0.jnk \ +{File "%TEMP%/utf-check-972-2-74-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 973 utf-check-973-2-74-1.jnk \ +{File "%TEMP%/utf-check-973-2-74-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 974 utf-check-974-2-75-0.jnk \ +{File "%TEMP%/utf-check-974-2-75-0.jnk" has 16 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 975 utf-check-975-2-75-1.jnk \ +{File "%TEMP%/utf-check-975-2-75-1.jnk" has 17 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 976 utf-check-976-2-76-0.jnk \ +{File "%TEMP%/utf-check-976-2-76-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 977 utf-check-977-2-76-1.jnk \ +{File "%TEMP%/utf-check-977-2-76-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 978 utf-check-978-2-77-0.jnk \ +{File "%TEMP%/utf-check-978-2-77-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 979 utf-check-979-2-77-1.jnk \ +{File "%TEMP%/utf-check-979-2-77-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 980 utf-check-980-2-78-0.jnk \ +{File "%TEMP%/utf-check-980-2-78-0.jnk" has 16 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 981 utf-check-981-2-78-1.jnk \ +{File "%TEMP%/utf-check-981-2-78-1.jnk" has 17 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 982 utf-check-982-2-79-0.jnk \ +{File "%TEMP%/utf-check-982-2-79-0.jnk" has 18 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 983 utf-check-983-2-79-1.jnk \ +{File "%TEMP%/utf-check-983-2-79-1.jnk" has 19 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 984 utf-check-984-2-80-0.jnk \ +{File "%TEMP%/utf-check-984-2-80-0.jnk" has 16388 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 985 utf-check-985-2-80-1.jnk \ +{File "%TEMP%/utf-check-985-2-80-1.jnk" has 16389 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 986 utf-check-986-2-81-0.jnk \ +{File "%TEMP%/utf-check-986-2-81-0.jnk" has 16390 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 987 utf-check-987-2-81-1.jnk \ +{File "%TEMP%/utf-check-987-2-81-1.jnk" has 16391 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 988 utf-check-988-2-82-0.jnk \ +{File "%TEMP%/utf-check-988-2-82-0.jnk" has 16390 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 989 utf-check-989-2-82-1.jnk \ +{File "%TEMP%/utf-check-989-2-82-1.jnk" has 16391 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 990 utf-check-990-2-83-0.jnk \ +{File "%TEMP%/utf-check-990-2-83-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 991 utf-check-991-2-83-1.jnk \ +{File "%TEMP%/utf-check-991-2-83-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 992 utf-check-992-2-84-0.jnk \ +{File "%TEMP%/utf-check-992-2-84-0.jnk" has 16394 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 993 utf-check-993-2-84-1.jnk \ +{File "%TEMP%/utf-check-993-2-84-1.jnk" has 16395 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 994 utf-check-994-2-85-0.jnk \ +{File "%TEMP%/utf-check-994-2-85-0.jnk" has 16396 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 995 utf-check-995-2-85-1.jnk \ +{File "%TEMP%/utf-check-995-2-85-1.jnk" has 16397 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 996 utf-check-996-2-86-0.jnk \ +{File "%TEMP%/utf-check-996-2-86-0.jnk" has 16396 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 997 utf-check-997-2-86-1.jnk \ +{File "%TEMP%/utf-check-997-2-86-1.jnk" has 16397 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 998 utf-check-998-2-87-0.jnk \ +{File "%TEMP%/utf-check-998-2-87-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 999 utf-check-999-2-87-1.jnk \ +{File "%TEMP%/utf-check-999-2-87-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1000 utf-check-1000-2-88-0.jnk \ +{File "%TEMP%/utf-check-1000-2-88-0.jnk" has 16390 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1001 utf-check-1001-2-88-1.jnk \ +{File "%TEMP%/utf-check-1001-2-88-1.jnk" has 16391 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1002 utf-check-1002-2-89-0.jnk \ +{File "%TEMP%/utf-check-1002-2-89-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1003 utf-check-1003-2-89-1.jnk \ +{File "%TEMP%/utf-check-1003-2-89-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1004 utf-check-1004-2-90-0.jnk \ +{File "%TEMP%/utf-check-1004-2-90-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1005 utf-check-1005-2-90-1.jnk \ +{File "%TEMP%/utf-check-1005-2-90-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1006 utf-check-1006-2-91-0.jnk \ +{File "%TEMP%/utf-check-1006-2-91-0.jnk" has 16394 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1007 utf-check-1007-2-91-1.jnk \ +{File "%TEMP%/utf-check-1007-2-91-1.jnk" has 16395 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1008 utf-check-1008-2-92-0.jnk \ +{File "%TEMP%/utf-check-1008-2-92-0.jnk" has 16396 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1009 utf-check-1009-2-92-1.jnk \ +{File "%TEMP%/utf-check-1009-2-92-1.jnk" has 16397 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1010 utf-check-1010-2-93-0.jnk \ +{File "%TEMP%/utf-check-1010-2-93-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1011 utf-check-1011-2-93-1.jnk \ +{File "%TEMP%/utf-check-1011-2-93-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1012 utf-check-1012-2-94-0.jnk \ +{File "%TEMP%/utf-check-1012-2-94-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1013 utf-check-1013-2-94-1.jnk \ +{File "%TEMP%/utf-check-1013-2-94-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1014 utf-check-1014-2-95-0.jnk \ +{File "%TEMP%/utf-check-1014-2-95-0.jnk" has 16400 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1015 utf-check-1015-2-95-1.jnk \ +{File "%TEMP%/utf-check-1015-2-95-1.jnk" has 16401 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1016 utf-check-1016-2-96-0.jnk \ +{File "%TEMP%/utf-check-1016-2-96-0.jnk" has 16390 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1017 utf-check-1017-2-96-1.jnk \ +{File "%TEMP%/utf-check-1017-2-96-1.jnk" has 16391 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1018 utf-check-1018-2-97-0.jnk \ +{File "%TEMP%/utf-check-1018-2-97-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1019 utf-check-1019-2-97-1.jnk \ +{File "%TEMP%/utf-check-1019-2-97-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1020 utf-check-1020-2-98-0.jnk \ +{File "%TEMP%/utf-check-1020-2-98-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1021 utf-check-1021-2-98-1.jnk \ +{File "%TEMP%/utf-check-1021-2-98-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1022 utf-check-1022-2-99-0.jnk \ +{File "%TEMP%/utf-check-1022-2-99-0.jnk" has 16394 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1023 utf-check-1023-2-99-1.jnk \ +{File "%TEMP%/utf-check-1023-2-99-1.jnk" has 16395 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1024 utf-check-1024-2-100-0.jnk \ +{File "%TEMP%/utf-check-1024-2-100-0.jnk" has 16396 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1025 utf-check-1025-2-100-1.jnk \ +{File "%TEMP%/utf-check-1025-2-100-1.jnk" has 16397 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1026 utf-check-1026-2-101-0.jnk \ +{File "%TEMP%/utf-check-1026-2-101-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1027 utf-check-1027-2-101-1.jnk \ +{File "%TEMP%/utf-check-1027-2-101-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1028 utf-check-1028-2-102-0.jnk \ +{File "%TEMP%/utf-check-1028-2-102-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1029 utf-check-1029-2-102-1.jnk \ +{File "%TEMP%/utf-check-1029-2-102-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1030 utf-check-1030-2-103-0.jnk \ +{File "%TEMP%/utf-check-1030-2-103-0.jnk" has 16400 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1031 utf-check-1031-2-103-1.jnk \ +{File "%TEMP%/utf-check-1031-2-103-1.jnk" has 16401 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1032 utf-check-1032-2-104-0.jnk \ +{File "%TEMP%/utf-check-1032-2-104-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1033 utf-check-1033-2-104-1.jnk \ +{File "%TEMP%/utf-check-1033-2-104-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1034 utf-check-1034-2-105-0.jnk \ +{File "%TEMP%/utf-check-1034-2-105-0.jnk" has 16394 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1035 utf-check-1035-2-105-1.jnk \ +{File "%TEMP%/utf-check-1035-2-105-1.jnk" has 16395 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1036 utf-check-1036-2-106-0.jnk \ +{File "%TEMP%/utf-check-1036-2-106-0.jnk" has 16394 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1037 utf-check-1037-2-106-1.jnk \ +{File "%TEMP%/utf-check-1037-2-106-1.jnk" has 16395 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1038 utf-check-1038-2-107-0.jnk \ +{File "%TEMP%/utf-check-1038-2-107-0.jnk" has 16396 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1039 utf-check-1039-2-107-1.jnk \ +{File "%TEMP%/utf-check-1039-2-107-1.jnk" has 16397 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1040 utf-check-1040-2-108-0.jnk \ +{File "%TEMP%/utf-check-1040-2-108-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1041 utf-check-1041-2-108-1.jnk \ +{File "%TEMP%/utf-check-1041-2-108-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1042 utf-check-1042-2-109-0.jnk \ +{File "%TEMP%/utf-check-1042-2-109-0.jnk" has 16400 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1043 utf-check-1043-2-109-1.jnk \ +{File "%TEMP%/utf-check-1043-2-109-1.jnk" has 16401 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1044 utf-check-1044-2-110-0.jnk \ +{File "%TEMP%/utf-check-1044-2-110-0.jnk" has 16400 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1045 utf-check-1045-2-110-1.jnk \ +{File "%TEMP%/utf-check-1045-2-110-1.jnk" has 16401 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1046 utf-check-1046-2-111-0.jnk \ +{File "%TEMP%/utf-check-1046-2-111-0.jnk" has 16402 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1047 utf-check-1047-2-111-1.jnk \ +{File "%TEMP%/utf-check-1047-2-111-1.jnk" has 16403 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1048 utf-check-1048-2-112-0.jnk \ +{File "%TEMP%/utf-check-1048-2-112-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1049 utf-check-1049-2-112-1.jnk \ +{File "%TEMP%/utf-check-1049-2-112-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1050 utf-check-1050-2-113-0.jnk \ +{File "%TEMP%/utf-check-1050-2-113-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1051 utf-check-1051-2-113-1.jnk \ +{File "%TEMP%/utf-check-1051-2-113-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1052 utf-check-1052-2-114-0.jnk \ +{File "%TEMP%/utf-check-1052-2-114-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1053 utf-check-1053-2-114-1.jnk \ +{File "%TEMP%/utf-check-1053-2-114-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1054 utf-check-1054-2-115-0.jnk \ +{File "%TEMP%/utf-check-1054-2-115-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1055 utf-check-1055-2-115-1.jnk \ +{File "%TEMP%/utf-check-1055-2-115-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1056 utf-check-1056-2-116-0.jnk \ +{File "%TEMP%/utf-check-1056-2-116-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1057 utf-check-1057-2-116-1.jnk \ +{File "%TEMP%/utf-check-1057-2-116-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1058 utf-check-1058-2-117-0.jnk \ +{File "%TEMP%/utf-check-1058-2-117-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1059 utf-check-1059-2-117-1.jnk \ +{File "%TEMP%/utf-check-1059-2-117-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1060 utf-check-1060-2-118-0.jnk \ +{File "%TEMP%/utf-check-1060-2-118-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1061 utf-check-1061-2-118-1.jnk \ +{File "%TEMP%/utf-check-1061-2-118-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1062 utf-check-1062-2-119-0.jnk \ +{File "%TEMP%/utf-check-1062-2-119-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1063 utf-check-1063-2-119-1.jnk \ +{File "%TEMP%/utf-check-1063-2-119-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1064 utf-check-1064-2-120-0.jnk \ +{File "%TEMP%/utf-check-1064-2-120-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1065 utf-check-1065-2-120-1.jnk \ +{File "%TEMP%/utf-check-1065-2-120-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1066 utf-check-1066-2-121-0.jnk \ +{File "%TEMP%/utf-check-1066-2-121-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1067 utf-check-1067-2-121-1.jnk \ +{File "%TEMP%/utf-check-1067-2-121-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1068 utf-check-1068-2-122-0.jnk \ +{File "%TEMP%/utf-check-1068-2-122-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1069 utf-check-1069-2-122-1.jnk \ +{File "%TEMP%/utf-check-1069-2-122-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1070 utf-check-1070-2-123-0.jnk \ +{File "%TEMP%/utf-check-1070-2-123-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1071 utf-check-1071-2-123-1.jnk \ +{File "%TEMP%/utf-check-1071-2-123-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1072 utf-check-1072-2-124-0.jnk \ +{File "%TEMP%/utf-check-1072-2-124-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1073 utf-check-1073-2-124-1.jnk \ +{File "%TEMP%/utf-check-1073-2-124-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1074 utf-check-1074-2-125-0.jnk \ +{File "%TEMP%/utf-check-1074-2-125-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1075 utf-check-1075-2-125-1.jnk \ +{File "%TEMP%/utf-check-1075-2-125-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1076 utf-check-1076-2-126-0.jnk \ +{File "%TEMP%/utf-check-1076-2-126-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1077 utf-check-1077-2-126-1.jnk \ +{File "%TEMP%/utf-check-1077-2-126-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1078 utf-check-1078-2-127-0.jnk \ +{File "%TEMP%/utf-check-1078-2-127-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1079 utf-check-1079-2-127-1.jnk \ +{File "%TEMP%/utf-check-1079-2-127-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1080 utf-check-1080-2-128-0.jnk \ +{File "%TEMP%/utf-check-1080-2-128-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1081 utf-check-1081-2-128-1.jnk \ +{File "%TEMP%/utf-check-1081-2-128-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1082 utf-check-1082-2-129-0.jnk \ +{File "%TEMP%/utf-check-1082-2-129-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1083 utf-check-1083-2-129-1.jnk \ +{File "%TEMP%/utf-check-1083-2-129-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1084 utf-check-1084-2-130-0.jnk \ +{File "%TEMP%/utf-check-1084-2-130-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1085 utf-check-1085-2-130-1.jnk \ +{File "%TEMP%/utf-check-1085-2-130-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1086 utf-check-1086-2-131-0.jnk \ +{File "%TEMP%/utf-check-1086-2-131-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1087 utf-check-1087-2-131-1.jnk \ +{File "%TEMP%/utf-check-1087-2-131-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1088 utf-check-1088-2-132-0.jnk \ +{File "%TEMP%/utf-check-1088-2-132-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1089 utf-check-1089-2-132-1.jnk \ +{File "%TEMP%/utf-check-1089-2-132-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1090 utf-check-1090-2-133-0.jnk \ +{File "%TEMP%/utf-check-1090-2-133-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1091 utf-check-1091-2-133-1.jnk \ +{File "%TEMP%/utf-check-1091-2-133-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1092 utf-check-1092-2-134-0.jnk \ +{File "%TEMP%/utf-check-1092-2-134-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1093 utf-check-1093-2-134-1.jnk \ +{File "%TEMP%/utf-check-1093-2-134-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1094 utf-check-1094-2-135-0.jnk \ +{File "%TEMP%/utf-check-1094-2-135-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1095 utf-check-1095-2-135-1.jnk \ +{File "%TEMP%/utf-check-1095-2-135-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1096 utf-check-1096-2-136-0.jnk \ +{File "%TEMP%/utf-check-1096-2-136-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1097 utf-check-1097-2-136-1.jnk \ +{File "%TEMP%/utf-check-1097-2-136-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1098 utf-check-1098-2-137-0.jnk \ +{File "%TEMP%/utf-check-1098-2-137-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1099 utf-check-1099-2-137-1.jnk \ +{File "%TEMP%/utf-check-1099-2-137-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1100 utf-check-1100-2-138-0.jnk \ +{File "%TEMP%/utf-check-1100-2-138-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1101 utf-check-1101-2-138-1.jnk \ +{File "%TEMP%/utf-check-1101-2-138-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1102 utf-check-1102-2-139-0.jnk \ +{File "%TEMP%/utf-check-1102-2-139-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1103 utf-check-1103-2-139-1.jnk \ +{File "%TEMP%/utf-check-1103-2-139-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1104 utf-check-1104-2-140-0.jnk \ +{File "%TEMP%/utf-check-1104-2-140-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1105 utf-check-1105-2-140-1.jnk \ +{File "%TEMP%/utf-check-1105-2-140-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1106 utf-check-1106-2-141-0.jnk \ +{File "%TEMP%/utf-check-1106-2-141-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1107 utf-check-1107-2-141-1.jnk \ +{File "%TEMP%/utf-check-1107-2-141-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1108 utf-check-1108-2-142-0.jnk \ +{File "%TEMP%/utf-check-1108-2-142-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1109 utf-check-1109-2-142-1.jnk \ +{File "%TEMP%/utf-check-1109-2-142-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1110 utf-check-1110-2-143-0.jnk \ +{File "%TEMP%/utf-check-1110-2-143-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1111 utf-check-1111-2-143-1.jnk \ +{File "%TEMP%/utf-check-1111-2-143-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1112 utf-check-1112-2-144-0.jnk \ +{File "%TEMP%/utf-check-1112-2-144-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1113 utf-check-1113-2-144-1.jnk \ +{File "%TEMP%/utf-check-1113-2-144-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1114 utf-check-1114-2-145-0.jnk \ +{File "%TEMP%/utf-check-1114-2-145-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1115 utf-check-1115-2-145-1.jnk \ +{File "%TEMP%/utf-check-1115-2-145-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1116 utf-check-1116-2-146-0.jnk \ +{File "%TEMP%/utf-check-1116-2-146-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1117 utf-check-1117-2-146-1.jnk \ +{File "%TEMP%/utf-check-1117-2-146-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1118 utf-check-1118-2-147-0.jnk \ +{File "%TEMP%/utf-check-1118-2-147-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1119 utf-check-1119-2-147-1.jnk \ +{File "%TEMP%/utf-check-1119-2-147-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1120 utf-check-1120-2-148-0.jnk \ +{File "%TEMP%/utf-check-1120-2-148-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1121 utf-check-1121-2-148-1.jnk \ +{File "%TEMP%/utf-check-1121-2-148-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1122 utf-check-1122-2-149-0.jnk \ +{File "%TEMP%/utf-check-1122-2-149-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1123 utf-check-1123-2-149-1.jnk \ +{File "%TEMP%/utf-check-1123-2-149-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1124 utf-check-1124-2-150-0.jnk \ +{File "%TEMP%/utf-check-1124-2-150-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1125 utf-check-1125-2-150-1.jnk \ +{File "%TEMP%/utf-check-1125-2-150-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1126 utf-check-1126-2-151-0.jnk \ +{File "%TEMP%/utf-check-1126-2-151-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1127 utf-check-1127-2-151-1.jnk \ +{File "%TEMP%/utf-check-1127-2-151-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1128 utf-check-1128-2-152-0.jnk \ +{File "%TEMP%/utf-check-1128-2-152-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1129 utf-check-1129-2-152-1.jnk \ +{File "%TEMP%/utf-check-1129-2-152-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1130 utf-check-1130-2-153-0.jnk \ +{File "%TEMP%/utf-check-1130-2-153-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1131 utf-check-1131-2-153-1.jnk \ +{File "%TEMP%/utf-check-1131-2-153-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1132 utf-check-1132-2-154-0.jnk \ +{File "%TEMP%/utf-check-1132-2-154-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1133 utf-check-1133-2-154-1.jnk \ +{File "%TEMP%/utf-check-1133-2-154-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1134 utf-check-1134-2-155-0.jnk \ +{File "%TEMP%/utf-check-1134-2-155-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1135 utf-check-1135-2-155-1.jnk \ +{File "%TEMP%/utf-check-1135-2-155-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1136 utf-check-1136-2-156-0.jnk \ +{File "%TEMP%/utf-check-1136-2-156-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: yes +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1137 utf-check-1137-2-156-1.jnk \ +{File "%TEMP%/utf-check-1137-2-156-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1138 utf-check-1138-2-157-0.jnk \ +{File "%TEMP%/utf-check-1138-2-157-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1139 utf-check-1139-2-157-1.jnk \ +{File "%TEMP%/utf-check-1139-2-157-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1140 utf-check-1140-2-158-0.jnk \ +{File "%TEMP%/utf-check-1140-2-158-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1141 utf-check-1141-2-158-1.jnk \ +{File "%TEMP%/utf-check-1141-2-158-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1142 utf-check-1142-2-159-0.jnk \ +{File "%TEMP%/utf-check-1142-2-159-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: no +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1143 utf-check-1143-2-159-1.jnk \ +{File "%TEMP%/utf-check-1143-2-159-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1144 utf-check-1144-2-160-0.jnk \ +{File "%TEMP%/utf-check-1144-2-160-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: yes Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no @@ -5839,32 +17035,32 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 446 utf-check-446-1-16-0.jnk \ -{File "%TEMP%/utf-check-446-1-16-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no +utf-check 1145 utf-check-1145-2-160-1.jnk \ +{File "%TEMP%/utf-check-1145-2-160-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 447 utf-check-447-1-16-1.jnk \ -{File "%TEMP%/utf-check-447-1-16-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no +utf-check 1146 utf-check-1146-2-161-0.jnk \ +{File "%TEMP%/utf-check-1146-2-161-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no +Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no @@ -5871,111 +17067,31 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 448 utf-check-448-1-17-0.jnk \ -{File "%TEMP%/utf-check-448-1-17-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no +utf-check 1147 utf-check-1147-2-161-1.jnk \ +{File "%TEMP%/utf-check-1147-2-161-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 449 utf-check-449-1-17-1.jnk \ -{File "%TEMP%/utf-check-449-1-17-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 450 utf-check-450-1-18-0.jnk \ -{File "%TEMP%/utf-check-450-1-18-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 451 utf-check-451-1-18-1.jnk \ -{File "%TEMP%/utf-check-451-1-18-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 452 utf-check-452-1-19-0.jnk \ -{File "%TEMP%/utf-check-452-1-19-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 453 utf-check-453-1-19-1.jnk \ -{File "%TEMP%/utf-check-453-1-19-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 454 utf-check-454-1-20-0.jnk \ -{File "%TEMP%/utf-check-454-1-20-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1148 utf-check-1148-2-162-0.jnk \ +{File "%TEMP%/utf-check-1148-2-162-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: yes Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no @@ -5983,127 +17099,31 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 455 utf-check-455-1-20-1.jnk \ -{File "%TEMP%/utf-check-455-1-20-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no +utf-check 1149 utf-check-1149-2-162-1.jnk \ +{File "%TEMP%/utf-check-1149-2-162-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes Has flag LOOK_CR: yes Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 456 utf-check-456-1-21-0.jnk \ -{File "%TEMP%/utf-check-456-1-21-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 457 utf-check-457-1-21-1.jnk \ -{File "%TEMP%/utf-check-457-1-21-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 458 utf-check-458-1-22-0.jnk \ -{File "%TEMP%/utf-check-458-1-22-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 459 utf-check-459-1-22-1.jnk \ -{File "%TEMP%/utf-check-459-1-22-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 460 utf-check-460-1-23-0.jnk \ -{File "%TEMP%/utf-check-460-1-23-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 461 utf-check-461-1-23-1.jnk \ -{File "%TEMP%/utf-check-461-1-23-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 462 utf-check-462-1-24-0.jnk \ -{File "%TEMP%/utf-check-462-1-24-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1150 utf-check-1150-2-163-0.jnk \ +{File "%TEMP%/utf-check-1150-2-163-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes @@ -6111,239 +17131,31 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 463 utf-check-463-1-24-1.jnk \ -{File "%TEMP%/utf-check-463-1-24-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no +utf-check 1151 utf-check-1151-2-163-1.jnk \ +{File "%TEMP%/utf-check-1151-2-163-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 464 utf-check-464-1-25-0.jnk \ -{File "%TEMP%/utf-check-464-1-25-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 465 utf-check-465-1-25-1.jnk \ -{File "%TEMP%/utf-check-465-1-25-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 466 utf-check-466-1-26-0.jnk \ -{File "%TEMP%/utf-check-466-1-26-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 467 utf-check-467-1-26-1.jnk \ -{File "%TEMP%/utf-check-467-1-26-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 468 utf-check-468-1-27-0.jnk \ -{File "%TEMP%/utf-check-468-1-27-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 469 utf-check-469-1-27-1.jnk \ -{File "%TEMP%/utf-check-469-1-27-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 470 utf-check-470-1-28-0.jnk \ -{File "%TEMP%/utf-check-470-1-28-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 471 utf-check-471-1-28-1.jnk \ -{File "%TEMP%/utf-check-471-1-28-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 472 utf-check-472-1-29-0.jnk \ -{File "%TEMP%/utf-check-472-1-29-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 473 utf-check-473-1-29-1.jnk \ -{File "%TEMP%/utf-check-473-1-29-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 474 utf-check-474-1-30-0.jnk \ -{File "%TEMP%/utf-check-474-1-30-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 475 utf-check-475-1-30-1.jnk \ -{File "%TEMP%/utf-check-475-1-30-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 476 utf-check-476-1-31-0.jnk \ -{File "%TEMP%/utf-check-476-1-31-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 477 utf-check-477-1-31-1.jnk \ -{File "%TEMP%/utf-check-477-1-31-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1152 utf-check-1152-2-164-0.jnk \ +{File "%TEMP%/utf-check-1152-2-164-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: yes Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no @@ -6351,31 +17163,31 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 478 utf-check-478-1-32-0.jnk \ -{File "%TEMP%/utf-check-478-1-32-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no +utf-check 1153 utf-check-1153-2-164-1.jnk \ +{File "%TEMP%/utf-check-1153-2-164-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no +Has flag LOOK_CR: yes +Has flag LOOK_LONE_CR: yes +Has flag LOOK_LF: yes +Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 479 utf-check-479-1-32-1.jnk \ -{File "%TEMP%/utf-check-479-1-32-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no +utf-check 1154 utf-check-1154-2-165-0.jnk \ +{File "%TEMP%/utf-check-1154-2-165-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no @@ -6383,112 +17195,32 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 480 utf-check-480-1-33-0.jnk \ -{File "%TEMP%/utf-check-480-1-33-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no +utf-check 1155 utf-check-1155-2-165-1.jnk \ +{File "%TEMP%/utf-check-1155-2-165-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 481 utf-check-481-1-33-1.jnk \ -{File "%TEMP%/utf-check-481-1-33-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 482 utf-check-482-1-34-0.jnk \ -{File "%TEMP%/utf-check-482-1-34-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 483 utf-check-483-1-34-1.jnk \ -{File "%TEMP%/utf-check-483-1-34-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 484 utf-check-484-1-35-0.jnk \ -{File "%TEMP%/utf-check-484-1-35-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 485 utf-check-485-1-35-1.jnk \ -{File "%TEMP%/utf-check-485-1-35-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 486 utf-check-486-1-36-0.jnk \ -{File "%TEMP%/utf-check-486-1-36-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1156 utf-check-1156-2-166-0.jnk \ +{File "%TEMP%/utf-check-1156-2-166-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no Has flag LOOK_CR: yes Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no @@ -6495,128 +17227,32 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 487 utf-check-487-1-36-1.jnk \ -{File "%TEMP%/utf-check-487-1-36-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no +utf-check 1157 utf-check-1157-2-166-1.jnk \ +{File "%TEMP%/utf-check-1157-2-166-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: yes Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 488 utf-check-488-1-37-0.jnk \ -{File "%TEMP%/utf-check-488-1-37-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 489 utf-check-489-1-37-1.jnk \ -{File "%TEMP%/utf-check-489-1-37-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 490 utf-check-490-1-38-0.jnk \ -{File "%TEMP%/utf-check-490-1-38-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 491 utf-check-491-1-38-1.jnk \ -{File "%TEMP%/utf-check-491-1-38-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 492 utf-check-492-1-39-0.jnk \ -{File "%TEMP%/utf-check-492-1-39-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 493 utf-check-493-1-39-1.jnk \ -{File "%TEMP%/utf-check-493-1-39-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 494 utf-check-494-1-40-0.jnk \ -{File "%TEMP%/utf-check-494-1-40-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1158 utf-check-1158-2-167-0.jnk \ +{File "%TEMP%/utf-check-1158-2-167-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no @@ -6623,2367 +17259,31 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 495 utf-check-495-1-40-1.jnk \ -{File "%TEMP%/utf-check-495-1-40-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no +utf-check 1159 utf-check-1159-2-167-1.jnk \ +{File "%TEMP%/utf-check-1159-2-167-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 496 utf-check-496-1-41-0.jnk \ -{File "%TEMP%/utf-check-496-1-41-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 497 utf-check-497-1-41-1.jnk \ -{File "%TEMP%/utf-check-497-1-41-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 498 utf-check-498-1-42-0.jnk \ -{File "%TEMP%/utf-check-498-1-42-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 499 utf-check-499-1-42-1.jnk \ -{File "%TEMP%/utf-check-499-1-42-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 500 utf-check-500-1-43-0.jnk \ -{File "%TEMP%/utf-check-500-1-43-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 501 utf-check-501-1-43-1.jnk \ -{File "%TEMP%/utf-check-501-1-43-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 502 utf-check-502-1-44-0.jnk \ -{File "%TEMP%/utf-check-502-1-44-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 503 utf-check-503-1-44-1.jnk \ -{File "%TEMP%/utf-check-503-1-44-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 504 utf-check-504-1-45-0.jnk \ -{File "%TEMP%/utf-check-504-1-45-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 505 utf-check-505-1-45-1.jnk \ -{File "%TEMP%/utf-check-505-1-45-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 506 utf-check-506-1-46-0.jnk \ -{File "%TEMP%/utf-check-506-1-46-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 507 utf-check-507-1-46-1.jnk \ -{File "%TEMP%/utf-check-507-1-46-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 508 utf-check-508-1-47-0.jnk \ -{File "%TEMP%/utf-check-508-1-47-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 509 utf-check-509-1-47-1.jnk \ -{File "%TEMP%/utf-check-509-1-47-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 510 utf-check-510-1-48-0.jnk \ -{File "%TEMP%/utf-check-510-1-48-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 511 utf-check-511-1-48-1.jnk \ -{File "%TEMP%/utf-check-511-1-48-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 512 utf-check-512-1-49-0.jnk \ -{File "%TEMP%/utf-check-512-1-49-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 513 utf-check-513-1-49-1.jnk \ -{File "%TEMP%/utf-check-513-1-49-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 514 utf-check-514-1-50-0.jnk \ -{File "%TEMP%/utf-check-514-1-50-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 515 utf-check-515-1-50-1.jnk \ -{File "%TEMP%/utf-check-515-1-50-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 516 utf-check-516-1-51-0.jnk \ -{File "%TEMP%/utf-check-516-1-51-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 517 utf-check-517-1-51-1.jnk \ -{File "%TEMP%/utf-check-517-1-51-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 518 utf-check-518-1-52-0.jnk \ -{File "%TEMP%/utf-check-518-1-52-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 519 utf-check-519-1-52-1.jnk \ -{File "%TEMP%/utf-check-519-1-52-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 520 utf-check-520-1-53-0.jnk \ -{File "%TEMP%/utf-check-520-1-53-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 521 utf-check-521-1-53-1.jnk \ -{File "%TEMP%/utf-check-521-1-53-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 522 utf-check-522-1-54-0.jnk \ -{File "%TEMP%/utf-check-522-1-54-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 523 utf-check-523-1-54-1.jnk \ -{File "%TEMP%/utf-check-523-1-54-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 524 utf-check-524-1-55-0.jnk \ -{File "%TEMP%/utf-check-524-1-55-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 525 utf-check-525-1-55-1.jnk \ -{File "%TEMP%/utf-check-525-1-55-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 526 utf-check-526-1-56-0.jnk \ -{File "%TEMP%/utf-check-526-1-56-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 527 utf-check-527-1-56-1.jnk \ -{File "%TEMP%/utf-check-527-1-56-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 528 utf-check-528-1-57-0.jnk \ -{File "%TEMP%/utf-check-528-1-57-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 529 utf-check-529-1-57-1.jnk \ -{File "%TEMP%/utf-check-529-1-57-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 530 utf-check-530-1-58-0.jnk \ -{File "%TEMP%/utf-check-530-1-58-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 531 utf-check-531-1-58-1.jnk \ -{File "%TEMP%/utf-check-531-1-58-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 532 utf-check-532-1-59-0.jnk \ -{File "%TEMP%/utf-check-532-1-59-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 533 utf-check-533-1-59-1.jnk \ -{File "%TEMP%/utf-check-533-1-59-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 534 utf-check-534-1-60-0.jnk \ -{File "%TEMP%/utf-check-534-1-60-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 535 utf-check-535-1-60-1.jnk \ -{File "%TEMP%/utf-check-535-1-60-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 536 utf-check-536-1-61-0.jnk \ -{File "%TEMP%/utf-check-536-1-61-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 537 utf-check-537-1-61-1.jnk \ -{File "%TEMP%/utf-check-537-1-61-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 538 utf-check-538-1-62-0.jnk \ -{File "%TEMP%/utf-check-538-1-62-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 539 utf-check-539-1-62-1.jnk \ -{File "%TEMP%/utf-check-539-1-62-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 540 utf-check-540-1-63-0.jnk \ -{File "%TEMP%/utf-check-540-1-63-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 541 utf-check-541-1-63-1.jnk \ -{File "%TEMP%/utf-check-541-1-63-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 542 utf-check-542-1-64-0.jnk \ -{File "%TEMP%/utf-check-542-1-64-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 543 utf-check-543-1-64-1.jnk \ -{File "%TEMP%/utf-check-543-1-64-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 544 utf-check-544-1-65-0.jnk \ -{File "%TEMP%/utf-check-544-1-65-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 545 utf-check-545-1-65-1.jnk \ -{File "%TEMP%/utf-check-545-1-65-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 546 utf-check-546-1-66-0.jnk \ -{File "%TEMP%/utf-check-546-1-66-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 547 utf-check-547-1-66-1.jnk \ -{File "%TEMP%/utf-check-547-1-66-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 548 utf-check-548-1-67-0.jnk \ -{File "%TEMP%/utf-check-548-1-67-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 549 utf-check-549-1-67-1.jnk \ -{File "%TEMP%/utf-check-549-1-67-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 550 utf-check-550-1-68-0.jnk \ -{File "%TEMP%/utf-check-550-1-68-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 551 utf-check-551-1-68-1.jnk \ -{File "%TEMP%/utf-check-551-1-68-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 552 utf-check-552-1-69-0.jnk \ -{File "%TEMP%/utf-check-552-1-69-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 553 utf-check-553-1-69-1.jnk \ -{File "%TEMP%/utf-check-553-1-69-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 554 utf-check-554-1-70-0.jnk \ -{File "%TEMP%/utf-check-554-1-70-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 555 utf-check-555-1-70-1.jnk \ -{File "%TEMP%/utf-check-555-1-70-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 556 utf-check-556-1-71-0.jnk \ -{File "%TEMP%/utf-check-556-1-71-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 557 utf-check-557-1-71-1.jnk \ -{File "%TEMP%/utf-check-557-1-71-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 558 utf-check-558-1-72-0.jnk \ -{File "%TEMP%/utf-check-558-1-72-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 559 utf-check-559-1-72-1.jnk \ -{File "%TEMP%/utf-check-559-1-72-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 560 utf-check-560-1-73-0.jnk \ -{File "%TEMP%/utf-check-560-1-73-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 561 utf-check-561-1-73-1.jnk \ -{File "%TEMP%/utf-check-561-1-73-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 562 utf-check-562-1-74-0.jnk \ -{File "%TEMP%/utf-check-562-1-74-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 563 utf-check-563-1-74-1.jnk \ -{File "%TEMP%/utf-check-563-1-74-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 564 utf-check-564-1-75-0.jnk \ -{File "%TEMP%/utf-check-564-1-75-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 565 utf-check-565-1-75-1.jnk \ -{File "%TEMP%/utf-check-565-1-75-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 566 utf-check-566-1-76-0.jnk \ -{File "%TEMP%/utf-check-566-1-76-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 567 utf-check-567-1-76-1.jnk \ -{File "%TEMP%/utf-check-567-1-76-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 568 utf-check-568-1-77-0.jnk \ -{File "%TEMP%/utf-check-568-1-77-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 569 utf-check-569-1-77-1.jnk \ -{File "%TEMP%/utf-check-569-1-77-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 570 utf-check-570-1-78-0.jnk \ -{File "%TEMP%/utf-check-570-1-78-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 571 utf-check-571-1-78-1.jnk \ -{File "%TEMP%/utf-check-571-1-78-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 572 utf-check-572-1-79-0.jnk \ -{File "%TEMP%/utf-check-572-1-79-0.jnk" has 11 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 573 utf-check-573-1-79-1.jnk \ -{File "%TEMP%/utf-check-573-1-79-1.jnk" has 12 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 574 utf-check-574-1-80-0.jnk \ -{File "%TEMP%/utf-check-574-1-80-0.jnk" has 8196 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 575 utf-check-575-1-80-1.jnk \ -{File "%TEMP%/utf-check-575-1-80-1.jnk" has 8197 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 576 utf-check-576-1-81-0.jnk \ -{File "%TEMP%/utf-check-576-1-81-0.jnk" has 8197 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 577 utf-check-577-1-81-1.jnk \ -{File "%TEMP%/utf-check-577-1-81-1.jnk" has 8198 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 578 utf-check-578-1-82-0.jnk \ -{File "%TEMP%/utf-check-578-1-82-0.jnk" has 8197 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 579 utf-check-579-1-82-1.jnk \ -{File "%TEMP%/utf-check-579-1-82-1.jnk" has 8198 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 580 utf-check-580-1-83-0.jnk \ -{File "%TEMP%/utf-check-580-1-83-0.jnk" has 8198 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 581 utf-check-581-1-83-1.jnk \ -{File "%TEMP%/utf-check-581-1-83-1.jnk" has 8199 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 582 utf-check-582-1-84-0.jnk \ -{File "%TEMP%/utf-check-582-1-84-0.jnk" has 8199 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 583 utf-check-583-1-84-1.jnk \ -{File "%TEMP%/utf-check-583-1-84-1.jnk" has 8200 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 584 utf-check-584-1-85-0.jnk \ -{File "%TEMP%/utf-check-584-1-85-0.jnk" has 8200 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 585 utf-check-585-1-85-1.jnk \ -{File "%TEMP%/utf-check-585-1-85-1.jnk" has 8201 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 586 utf-check-586-1-86-0.jnk \ -{File "%TEMP%/utf-check-586-1-86-0.jnk" has 8200 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 587 utf-check-587-1-86-1.jnk \ -{File "%TEMP%/utf-check-587-1-86-1.jnk" has 8201 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 588 utf-check-588-1-87-0.jnk \ -{File "%TEMP%/utf-check-588-1-87-0.jnk" has 8201 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 589 utf-check-589-1-87-1.jnk \ -{File "%TEMP%/utf-check-589-1-87-1.jnk" has 8202 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 590 utf-check-590-1-88-0.jnk \ -{File "%TEMP%/utf-check-590-1-88-0.jnk" has 8197 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 591 utf-check-591-1-88-1.jnk \ -{File "%TEMP%/utf-check-591-1-88-1.jnk" has 8198 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 592 utf-check-592-1-89-0.jnk \ -{File "%TEMP%/utf-check-592-1-89-0.jnk" has 8198 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 593 utf-check-593-1-89-1.jnk \ -{File "%TEMP%/utf-check-593-1-89-1.jnk" has 8199 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 594 utf-check-594-1-90-0.jnk \ -{File "%TEMP%/utf-check-594-1-90-0.jnk" has 8198 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 595 utf-check-595-1-90-1.jnk \ -{File "%TEMP%/utf-check-595-1-90-1.jnk" has 8199 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 596 utf-check-596-1-91-0.jnk \ -{File "%TEMP%/utf-check-596-1-91-0.jnk" has 8199 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 597 utf-check-597-1-91-1.jnk \ -{File "%TEMP%/utf-check-597-1-91-1.jnk" has 8200 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 598 utf-check-598-1-92-0.jnk \ -{File "%TEMP%/utf-check-598-1-92-0.jnk" has 8200 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 599 utf-check-599-1-92-1.jnk \ -{File "%TEMP%/utf-check-599-1-92-1.jnk" has 8201 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 600 utf-check-600-1-93-0.jnk \ -{File "%TEMP%/utf-check-600-1-93-0.jnk" has 8201 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 601 utf-check-601-1-93-1.jnk \ -{File "%TEMP%/utf-check-601-1-93-1.jnk" has 8202 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 602 utf-check-602-1-94-0.jnk \ -{File "%TEMP%/utf-check-602-1-94-0.jnk" has 8201 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 603 utf-check-603-1-94-1.jnk \ -{File "%TEMP%/utf-check-603-1-94-1.jnk" has 8202 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 604 utf-check-604-1-95-0.jnk \ -{File "%TEMP%/utf-check-604-1-95-0.jnk" has 8202 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 605 utf-check-605-1-95-1.jnk \ -{File "%TEMP%/utf-check-605-1-95-1.jnk" has 8203 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 606 utf-check-606-1-96-0.jnk \ -{File "%TEMP%/utf-check-606-1-96-0.jnk" has 8197 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 607 utf-check-607-1-96-1.jnk \ -{File "%TEMP%/utf-check-607-1-96-1.jnk" has 8198 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 608 utf-check-608-1-97-0.jnk \ -{File "%TEMP%/utf-check-608-1-97-0.jnk" has 8198 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 609 utf-check-609-1-97-1.jnk \ -{File "%TEMP%/utf-check-609-1-97-1.jnk" has 8199 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 610 utf-check-610-1-98-0.jnk \ -{File "%TEMP%/utf-check-610-1-98-0.jnk" has 8198 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 611 utf-check-611-1-98-1.jnk \ -{File "%TEMP%/utf-check-611-1-98-1.jnk" has 8199 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 612 utf-check-612-1-99-0.jnk \ -{File "%TEMP%/utf-check-612-1-99-0.jnk" has 8199 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 613 utf-check-613-1-99-1.jnk \ -{File "%TEMP%/utf-check-613-1-99-1.jnk" has 8200 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 614 utf-check-614-1-100-0.jnk \ -{File "%TEMP%/utf-check-614-1-100-0.jnk" has 8200 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 615 utf-check-615-1-100-1.jnk \ -{File "%TEMP%/utf-check-615-1-100-1.jnk" has 8201 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 616 utf-check-616-1-101-0.jnk \ -{File "%TEMP%/utf-check-616-1-101-0.jnk" has 8201 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 617 utf-check-617-1-101-1.jnk \ -{File "%TEMP%/utf-check-617-1-101-1.jnk" has 8202 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 618 utf-check-618-1-102-0.jnk \ -{File "%TEMP%/utf-check-618-1-102-0.jnk" has 8201 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 619 utf-check-619-1-102-1.jnk \ -{File "%TEMP%/utf-check-619-1-102-1.jnk" has 8202 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 620 utf-check-620-1-103-0.jnk \ -{File "%TEMP%/utf-check-620-1-103-0.jnk" has 8202 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 621 utf-check-621-1-103-1.jnk \ -{File "%TEMP%/utf-check-621-1-103-1.jnk" has 8203 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 622 utf-check-622-1-104-0.jnk \ -{File "%TEMP%/utf-check-622-1-104-0.jnk" has 8198 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 623 utf-check-623-1-104-1.jnk \ -{File "%TEMP%/utf-check-623-1-104-1.jnk" has 8199 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 624 utf-check-624-1-105-0.jnk \ -{File "%TEMP%/utf-check-624-1-105-0.jnk" has 8199 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 625 utf-check-625-1-105-1.jnk \ -{File "%TEMP%/utf-check-625-1-105-1.jnk" has 8200 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 626 utf-check-626-1-106-0.jnk \ -{File "%TEMP%/utf-check-626-1-106-0.jnk" has 8199 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 627 utf-check-627-1-106-1.jnk \ -{File "%TEMP%/utf-check-627-1-106-1.jnk" has 8200 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 628 utf-check-628-1-107-0.jnk \ -{File "%TEMP%/utf-check-628-1-107-0.jnk" has 8200 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 629 utf-check-629-1-107-1.jnk \ -{File "%TEMP%/utf-check-629-1-107-1.jnk" has 8201 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 630 utf-check-630-1-108-0.jnk \ -{File "%TEMP%/utf-check-630-1-108-0.jnk" has 8201 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 631 utf-check-631-1-108-1.jnk \ -{File "%TEMP%/utf-check-631-1-108-1.jnk" has 8202 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 632 utf-check-632-1-109-0.jnk \ -{File "%TEMP%/utf-check-632-1-109-0.jnk" has 8202 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 633 utf-check-633-1-109-1.jnk \ -{File "%TEMP%/utf-check-633-1-109-1.jnk" has 8203 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 634 utf-check-634-1-110-0.jnk \ -{File "%TEMP%/utf-check-634-1-110-0.jnk" has 8202 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 635 utf-check-635-1-110-1.jnk \ -{File "%TEMP%/utf-check-635-1-110-1.jnk" has 8203 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 636 utf-check-636-1-111-0.jnk \ -{File "%TEMP%/utf-check-636-1-111-0.jnk" has 8203 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 637 utf-check-637-1-111-1.jnk \ -{File "%TEMP%/utf-check-637-1-111-1.jnk" has 8204 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 638 utf-check-638-1-112-0.jnk \ -{File "%TEMP%/utf-check-638-1-112-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 639 utf-check-639-1-112-1.jnk \ -{File "%TEMP%/utf-check-639-1-112-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 640 utf-check-640-1-113-0.jnk \ -{File "%TEMP%/utf-check-640-1-113-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 641 utf-check-641-1-113-1.jnk \ -{File "%TEMP%/utf-check-641-1-113-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 642 utf-check-642-1-114-0.jnk \ -{File "%TEMP%/utf-check-642-1-114-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1160 utf-check-1160-2-168-0.jnk \ +{File "%TEMP%/utf-check-1160-2-168-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: yes Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: no @@ -8991,31 +17291,31 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 643 utf-check-643-1-114-1.jnk \ -{File "%TEMP%/utf-check-643-1-114-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no +utf-check 1161 utf-check-1161-2-168-1.jnk \ +{File "%TEMP%/utf-check-1161-2-168-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no +Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes +Has flag LOOK_LONE_LF: yes +Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 644 utf-check-644-1-115-0.jnk \ -{File "%TEMP%/utf-check-644-1-115-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no +utf-check 1162 utf-check-1162-2-169-0.jnk \ +{File "%TEMP%/utf-check-1162-2-169-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no @@ -9023,1372 +17323,28 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 645 utf-check-645-1-115-1.jnk \ -{File "%TEMP%/utf-check-645-1-115-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 646 utf-check-646-1-116-0.jnk \ -{File "%TEMP%/utf-check-646-1-116-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 647 utf-check-647-1-116-1.jnk \ -{File "%TEMP%/utf-check-647-1-116-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 648 utf-check-648-1-117-0.jnk \ -{File "%TEMP%/utf-check-648-1-117-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 649 utf-check-649-1-117-1.jnk \ -{File "%TEMP%/utf-check-649-1-117-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 650 utf-check-650-1-118-0.jnk \ -{File "%TEMP%/utf-check-650-1-118-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 651 utf-check-651-1-118-1.jnk \ -{File "%TEMP%/utf-check-651-1-118-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 652 utf-check-652-1-119-0.jnk \ -{File "%TEMP%/utf-check-652-1-119-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 653 utf-check-653-1-119-1.jnk \ -{File "%TEMP%/utf-check-653-1-119-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 654 utf-check-654-1-120-0.jnk \ -{File "%TEMP%/utf-check-654-1-120-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 655 utf-check-655-1-120-1.jnk \ -{File "%TEMP%/utf-check-655-1-120-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 656 utf-check-656-1-121-0.jnk \ -{File "%TEMP%/utf-check-656-1-121-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 657 utf-check-657-1-121-1.jnk \ -{File "%TEMP%/utf-check-657-1-121-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 658 utf-check-658-1-122-0.jnk \ -{File "%TEMP%/utf-check-658-1-122-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 659 utf-check-659-1-122-1.jnk \ -{File "%TEMP%/utf-check-659-1-122-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 660 utf-check-660-1-123-0.jnk \ -{File "%TEMP%/utf-check-660-1-123-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 661 utf-check-661-1-123-1.jnk \ -{File "%TEMP%/utf-check-661-1-123-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 662 utf-check-662-1-124-0.jnk \ -{File "%TEMP%/utf-check-662-1-124-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 663 utf-check-663-1-124-1.jnk \ -{File "%TEMP%/utf-check-663-1-124-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 664 utf-check-664-1-125-0.jnk \ -{File "%TEMP%/utf-check-664-1-125-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 665 utf-check-665-1-125-1.jnk \ -{File "%TEMP%/utf-check-665-1-125-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 666 utf-check-666-1-126-0.jnk \ -{File "%TEMP%/utf-check-666-1-126-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 667 utf-check-667-1-126-1.jnk \ -{File "%TEMP%/utf-check-667-1-126-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 668 utf-check-668-1-127-0.jnk \ -{File "%TEMP%/utf-check-668-1-127-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 669 utf-check-669-1-127-1.jnk \ -{File "%TEMP%/utf-check-669-1-127-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 670 utf-check-670-1-128-0.jnk \ -{File "%TEMP%/utf-check-670-1-128-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 671 utf-check-671-1-128-1.jnk \ -{File "%TEMP%/utf-check-671-1-128-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 672 utf-check-672-1-129-0.jnk \ -{File "%TEMP%/utf-check-672-1-129-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 673 utf-check-673-1-129-1.jnk \ -{File "%TEMP%/utf-check-673-1-129-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 674 utf-check-674-1-130-0.jnk \ -{File "%TEMP%/utf-check-674-1-130-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 675 utf-check-675-1-130-1.jnk \ -{File "%TEMP%/utf-check-675-1-130-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 676 utf-check-676-1-131-0.jnk \ -{File "%TEMP%/utf-check-676-1-131-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 677 utf-check-677-1-131-1.jnk \ -{File "%TEMP%/utf-check-677-1-131-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 678 utf-check-678-1-132-0.jnk \ -{File "%TEMP%/utf-check-678-1-132-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 679 utf-check-679-1-132-1.jnk \ -{File "%TEMP%/utf-check-679-1-132-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 680 utf-check-680-1-133-0.jnk \ -{File "%TEMP%/utf-check-680-1-133-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 681 utf-check-681-1-133-1.jnk \ -{File "%TEMP%/utf-check-681-1-133-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 682 utf-check-682-1-134-0.jnk \ -{File "%TEMP%/utf-check-682-1-134-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 683 utf-check-683-1-134-1.jnk \ -{File "%TEMP%/utf-check-683-1-134-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 684 utf-check-684-1-135-0.jnk \ -{File "%TEMP%/utf-check-684-1-135-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 685 utf-check-685-1-135-1.jnk \ -{File "%TEMP%/utf-check-685-1-135-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 686 utf-check-686-1-136-0.jnk \ -{File "%TEMP%/utf-check-686-1-136-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 687 utf-check-687-1-136-1.jnk \ -{File "%TEMP%/utf-check-687-1-136-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 688 utf-check-688-1-137-0.jnk \ -{File "%TEMP%/utf-check-688-1-137-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 689 utf-check-689-1-137-1.jnk \ -{File "%TEMP%/utf-check-689-1-137-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 690 utf-check-690-1-138-0.jnk \ -{File "%TEMP%/utf-check-690-1-138-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 691 utf-check-691-1-138-1.jnk \ -{File "%TEMP%/utf-check-691-1-138-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 692 utf-check-692-1-139-0.jnk \ -{File "%TEMP%/utf-check-692-1-139-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 693 utf-check-693-1-139-1.jnk \ -{File "%TEMP%/utf-check-693-1-139-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 694 utf-check-694-1-140-0.jnk \ -{File "%TEMP%/utf-check-694-1-140-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 695 utf-check-695-1-140-1.jnk \ -{File "%TEMP%/utf-check-695-1-140-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 696 utf-check-696-1-141-0.jnk \ -{File "%TEMP%/utf-check-696-1-141-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 697 utf-check-697-1-141-1.jnk \ -{File "%TEMP%/utf-check-697-1-141-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 698 utf-check-698-1-142-0.jnk \ -{File "%TEMP%/utf-check-698-1-142-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 699 utf-check-699-1-142-1.jnk \ -{File "%TEMP%/utf-check-699-1-142-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 700 utf-check-700-1-143-0.jnk \ -{File "%TEMP%/utf-check-700-1-143-0.jnk" has 5 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 701 utf-check-701-1-143-1.jnk \ -{File "%TEMP%/utf-check-701-1-143-1.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 702 utf-check-702-1-144-0.jnk \ -{File "%TEMP%/utf-check-702-1-144-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 703 utf-check-703-1-144-1.jnk \ -{File "%TEMP%/utf-check-703-1-144-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 704 utf-check-704-1-145-0.jnk \ -{File "%TEMP%/utf-check-704-1-145-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 705 utf-check-705-1-145-1.jnk \ -{File "%TEMP%/utf-check-705-1-145-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 706 utf-check-706-1-146-0.jnk \ -{File "%TEMP%/utf-check-706-1-146-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 707 utf-check-707-1-146-1.jnk \ -{File "%TEMP%/utf-check-707-1-146-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 708 utf-check-708-1-147-0.jnk \ -{File "%TEMP%/utf-check-708-1-147-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 709 utf-check-709-1-147-1.jnk \ -{File "%TEMP%/utf-check-709-1-147-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 710 utf-check-710-1-148-0.jnk \ -{File "%TEMP%/utf-check-710-1-148-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 711 utf-check-711-1-148-1.jnk \ -{File "%TEMP%/utf-check-711-1-148-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 712 utf-check-712-1-149-0.jnk \ -{File "%TEMP%/utf-check-712-1-149-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 713 utf-check-713-1-149-1.jnk \ -{File "%TEMP%/utf-check-713-1-149-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 714 utf-check-714-1-150-0.jnk \ -{File "%TEMP%/utf-check-714-1-150-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 715 utf-check-715-1-150-1.jnk \ -{File "%TEMP%/utf-check-715-1-150-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 716 utf-check-716-1-151-0.jnk \ -{File "%TEMP%/utf-check-716-1-151-0.jnk" has 7 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 717 utf-check-717-1-151-1.jnk \ -{File "%TEMP%/utf-check-717-1-151-1.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 718 utf-check-718-1-152-0.jnk \ -{File "%TEMP%/utf-check-718-1-152-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 719 utf-check-719-1-152-1.jnk \ -{File "%TEMP%/utf-check-719-1-152-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 720 utf-check-720-1-153-0.jnk \ -{File "%TEMP%/utf-check-720-1-153-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 721 utf-check-721-1-153-1.jnk \ -{File "%TEMP%/utf-check-721-1-153-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 722 utf-check-722-1-154-0.jnk \ -{File "%TEMP%/utf-check-722-1-154-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 723 utf-check-723-1-154-1.jnk \ -{File "%TEMP%/utf-check-723-1-154-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 724 utf-check-724-1-155-0.jnk \ -{File "%TEMP%/utf-check-724-1-155-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 725 utf-check-725-1-155-1.jnk \ -{File "%TEMP%/utf-check-725-1-155-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 726 utf-check-726-1-156-0.jnk \ -{File "%TEMP%/utf-check-726-1-156-0.jnk" has 9 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 727 utf-check-727-1-156-1.jnk \ -{File "%TEMP%/utf-check-727-1-156-1.jnk" has 10 bytes. -Starts with UTF-8 BOM: yes -Starts with UTF-16 BOM: no -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 728 utf-check-728-2-0-0.jnk \ -{File "%TEMP%/utf-check-728-2-0-0.jnk" has 2 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 729 utf-check-729-2-0-1.jnk \ -{File "%TEMP%/utf-check-729-2-0-1.jnk" has 3 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 730 utf-check-730-2-1-0.jnk \ -{File "%TEMP%/utf-check-730-2-1-0.jnk" has 4 bytes. +utf-check 1163 utf-check-1163-2-169-1.jnk \ +{File "%TEMP%/utf-check-1163-2-169-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1164 utf-check-1164-2-170-0.jnk \ +{File "%TEMP%/utf-check-1164-2-170-0.jnk" has 10 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: yes @@ -10399,12 +17355,12 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 731 utf-check-731-2-1-1.jnk \ -{File "%TEMP%/utf-check-731-2-1-1.jnk" has 5 bytes. +utf-check 1165 utf-check-1165-2-170-1.jnk \ +{File "%TEMP%/utf-check-1165-2-170-1.jnk" has 11 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: yes @@ -10411,16 +17367,16 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 732 utf-check-732-2-2-0.jnk \ -{File "%TEMP%/utf-check-732-2-2-0.jnk" has 4 bytes. +utf-check 1166 utf-check-1166-2-171-0.jnk \ +{File "%TEMP%/utf-check-1166-2-171-0.jnk" has 10 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: no @@ -10431,12 +17387,12 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 733 utf-check-733-2-2-1.jnk \ -{File "%TEMP%/utf-check-733-2-2-1.jnk" has 5 bytes. +utf-check 1167 utf-check-1167-2-171-1.jnk \ +{File "%TEMP%/utf-check-1167-2-171-1.jnk" has 11 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no @@ -10443,16 +17399,16 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 734 utf-check-734-2-3-0.jnk \ -{File "%TEMP%/utf-check-734-2-3-0.jnk" has 6 bytes. +utf-check 1168 utf-check-1168-2-172-0.jnk \ +{File "%TEMP%/utf-check-1168-2-172-0.jnk" has 12 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: yes @@ -10463,12 +17419,12 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 735 utf-check-735-2-3-1.jnk \ -{File "%TEMP%/utf-check-735-2-3-1.jnk" has 7 bytes. +utf-check 1169 utf-check-1169-2-172-1.jnk \ +{File "%TEMP%/utf-check-1169-2-172-1.jnk" has 13 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: yes @@ -10475,1424 +17431,16 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 736 utf-check-736-2-4-0.jnk \ -{File "%TEMP%/utf-check-736-2-4-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 737 utf-check-737-2-4-1.jnk \ -{File "%TEMP%/utf-check-737-2-4-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 738 utf-check-738-2-5-0.jnk \ -{File "%TEMP%/utf-check-738-2-5-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 739 utf-check-739-2-5-1.jnk \ -{File "%TEMP%/utf-check-739-2-5-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 740 utf-check-740-2-6-0.jnk \ -{File "%TEMP%/utf-check-740-2-6-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 741 utf-check-741-2-6-1.jnk \ -{File "%TEMP%/utf-check-741-2-6-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 742 utf-check-742-2-7-0.jnk \ -{File "%TEMP%/utf-check-742-2-7-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 743 utf-check-743-2-7-1.jnk \ -{File "%TEMP%/utf-check-743-2-7-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 744 utf-check-744-2-8-0.jnk \ -{File "%TEMP%/utf-check-744-2-8-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 745 utf-check-745-2-8-1.jnk \ -{File "%TEMP%/utf-check-745-2-8-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 746 utf-check-746-2-9-0.jnk \ -{File "%TEMP%/utf-check-746-2-9-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 747 utf-check-747-2-9-1.jnk \ -{File "%TEMP%/utf-check-747-2-9-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 748 utf-check-748-2-10-0.jnk \ -{File "%TEMP%/utf-check-748-2-10-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 749 utf-check-749-2-10-1.jnk \ -{File "%TEMP%/utf-check-749-2-10-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 750 utf-check-750-2-11-0.jnk \ -{File "%TEMP%/utf-check-750-2-11-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 751 utf-check-751-2-11-1.jnk \ -{File "%TEMP%/utf-check-751-2-11-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 752 utf-check-752-2-12-0.jnk \ -{File "%TEMP%/utf-check-752-2-12-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 753 utf-check-753-2-12-1.jnk \ -{File "%TEMP%/utf-check-753-2-12-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 754 utf-check-754-2-13-0.jnk \ -{File "%TEMP%/utf-check-754-2-13-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 755 utf-check-755-2-13-1.jnk \ -{File "%TEMP%/utf-check-755-2-13-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 756 utf-check-756-2-14-0.jnk \ -{File "%TEMP%/utf-check-756-2-14-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 757 utf-check-757-2-14-1.jnk \ -{File "%TEMP%/utf-check-757-2-14-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 758 utf-check-758-2-15-0.jnk \ -{File "%TEMP%/utf-check-758-2-15-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 759 utf-check-759-2-15-1.jnk \ -{File "%TEMP%/utf-check-759-2-15-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 760 utf-check-760-2-16-0.jnk \ -{File "%TEMP%/utf-check-760-2-16-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 761 utf-check-761-2-16-1.jnk \ -{File "%TEMP%/utf-check-761-2-16-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 762 utf-check-762-2-17-0.jnk \ -{File "%TEMP%/utf-check-762-2-17-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 763 utf-check-763-2-17-1.jnk \ -{File "%TEMP%/utf-check-763-2-17-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 764 utf-check-764-2-18-0.jnk \ -{File "%TEMP%/utf-check-764-2-18-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 765 utf-check-765-2-18-1.jnk \ -{File "%TEMP%/utf-check-765-2-18-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 766 utf-check-766-2-19-0.jnk \ -{File "%TEMP%/utf-check-766-2-19-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 767 utf-check-767-2-19-1.jnk \ -{File "%TEMP%/utf-check-767-2-19-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 768 utf-check-768-2-20-0.jnk \ -{File "%TEMP%/utf-check-768-2-20-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 769 utf-check-769-2-20-1.jnk \ -{File "%TEMP%/utf-check-769-2-20-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 770 utf-check-770-2-21-0.jnk \ -{File "%TEMP%/utf-check-770-2-21-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 771 utf-check-771-2-21-1.jnk \ -{File "%TEMP%/utf-check-771-2-21-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 772 utf-check-772-2-22-0.jnk \ -{File "%TEMP%/utf-check-772-2-22-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 773 utf-check-773-2-22-1.jnk \ -{File "%TEMP%/utf-check-773-2-22-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 774 utf-check-774-2-23-0.jnk \ -{File "%TEMP%/utf-check-774-2-23-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 775 utf-check-775-2-23-1.jnk \ -{File "%TEMP%/utf-check-775-2-23-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 776 utf-check-776-2-24-0.jnk \ -{File "%TEMP%/utf-check-776-2-24-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 777 utf-check-777-2-24-1.jnk \ -{File "%TEMP%/utf-check-777-2-24-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 778 utf-check-778-2-25-0.jnk \ -{File "%TEMP%/utf-check-778-2-25-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 779 utf-check-779-2-25-1.jnk \ -{File "%TEMP%/utf-check-779-2-25-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 780 utf-check-780-2-26-0.jnk \ -{File "%TEMP%/utf-check-780-2-26-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 781 utf-check-781-2-26-1.jnk \ -{File "%TEMP%/utf-check-781-2-26-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 782 utf-check-782-2-27-0.jnk \ -{File "%TEMP%/utf-check-782-2-27-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 783 utf-check-783-2-27-1.jnk \ -{File "%TEMP%/utf-check-783-2-27-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 784 utf-check-784-2-28-0.jnk \ -{File "%TEMP%/utf-check-784-2-28-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 785 utf-check-785-2-28-1.jnk \ -{File "%TEMP%/utf-check-785-2-28-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 786 utf-check-786-2-29-0.jnk \ -{File "%TEMP%/utf-check-786-2-29-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 787 utf-check-787-2-29-1.jnk \ -{File "%TEMP%/utf-check-787-2-29-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 788 utf-check-788-2-30-0.jnk \ -{File "%TEMP%/utf-check-788-2-30-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 789 utf-check-789-2-30-1.jnk \ -{File "%TEMP%/utf-check-789-2-30-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 790 utf-check-790-2-31-0.jnk \ -{File "%TEMP%/utf-check-790-2-31-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 791 utf-check-791-2-31-1.jnk \ -{File "%TEMP%/utf-check-791-2-31-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 792 utf-check-792-2-32-0.jnk \ -{File "%TEMP%/utf-check-792-2-32-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 793 utf-check-793-2-32-1.jnk \ -{File "%TEMP%/utf-check-793-2-32-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 794 utf-check-794-2-33-0.jnk \ -{File "%TEMP%/utf-check-794-2-33-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 795 utf-check-795-2-33-1.jnk \ -{File "%TEMP%/utf-check-795-2-33-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 796 utf-check-796-2-34-0.jnk \ -{File "%TEMP%/utf-check-796-2-34-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 797 utf-check-797-2-34-1.jnk \ -{File "%TEMP%/utf-check-797-2-34-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 798 utf-check-798-2-35-0.jnk \ -{File "%TEMP%/utf-check-798-2-35-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 799 utf-check-799-2-35-1.jnk \ -{File "%TEMP%/utf-check-799-2-35-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 800 utf-check-800-2-36-0.jnk \ -{File "%TEMP%/utf-check-800-2-36-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 801 utf-check-801-2-36-1.jnk \ -{File "%TEMP%/utf-check-801-2-36-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 802 utf-check-802-2-37-0.jnk \ -{File "%TEMP%/utf-check-802-2-37-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 803 utf-check-803-2-37-1.jnk \ -{File "%TEMP%/utf-check-803-2-37-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 804 utf-check-804-2-38-0.jnk \ -{File "%TEMP%/utf-check-804-2-38-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 805 utf-check-805-2-38-1.jnk \ -{File "%TEMP%/utf-check-805-2-38-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 806 utf-check-806-2-39-0.jnk \ -{File "%TEMP%/utf-check-806-2-39-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 807 utf-check-807-2-39-1.jnk \ -{File "%TEMP%/utf-check-807-2-39-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 808 utf-check-808-2-40-0.jnk \ -{File "%TEMP%/utf-check-808-2-40-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 809 utf-check-809-2-40-1.jnk \ -{File "%TEMP%/utf-check-809-2-40-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 810 utf-check-810-2-41-0.jnk \ -{File "%TEMP%/utf-check-810-2-41-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 811 utf-check-811-2-41-1.jnk \ -{File "%TEMP%/utf-check-811-2-41-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 812 utf-check-812-2-42-0.jnk \ -{File "%TEMP%/utf-check-812-2-42-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 813 utf-check-813-2-42-1.jnk \ -{File "%TEMP%/utf-check-813-2-42-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 814 utf-check-814-2-43-0.jnk \ -{File "%TEMP%/utf-check-814-2-43-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 815 utf-check-815-2-43-1.jnk \ -{File "%TEMP%/utf-check-815-2-43-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 816 utf-check-816-2-44-0.jnk \ -{File "%TEMP%/utf-check-816-2-44-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 817 utf-check-817-2-44-1.jnk \ -{File "%TEMP%/utf-check-817-2-44-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 818 utf-check-818-2-45-0.jnk \ -{File "%TEMP%/utf-check-818-2-45-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 819 utf-check-819-2-45-1.jnk \ -{File "%TEMP%/utf-check-819-2-45-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 820 utf-check-820-2-46-0.jnk \ -{File "%TEMP%/utf-check-820-2-46-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 821 utf-check-821-2-46-1.jnk \ -{File "%TEMP%/utf-check-821-2-46-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 822 utf-check-822-2-47-0.jnk \ -{File "%TEMP%/utf-check-822-2-47-0.jnk" has 16 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 823 utf-check-823-2-47-1.jnk \ -{File "%TEMP%/utf-check-823-2-47-1.jnk" has 17 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 824 utf-check-824-2-48-0.jnk \ -{File "%TEMP%/utf-check-824-2-48-0.jnk" has 6 bytes. +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1170 utf-check-1170-2-173-0.jnk \ +{File "%TEMP%/utf-check-1170-2-173-0.jnk" has 12 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-16: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no @@ -11903,128 +17451,32 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 825 utf-check-825-2-48-1.jnk \ -{File "%TEMP%/utf-check-825-2-48-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 826 utf-check-826-2-49-0.jnk \ -{File "%TEMP%/utf-check-826-2-49-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 827 utf-check-827-2-49-1.jnk \ -{File "%TEMP%/utf-check-827-2-49-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 828 utf-check-828-2-50-0.jnk \ -{File "%TEMP%/utf-check-828-2-50-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 829 utf-check-829-2-50-1.jnk \ -{File "%TEMP%/utf-check-829-2-50-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 830 utf-check-830-2-51-0.jnk \ -{File "%TEMP%/utf-check-830-2-51-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 831 utf-check-831-2-51-1.jnk \ -{File "%TEMP%/utf-check-831-2-51-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 832 utf-check-832-2-52-0.jnk \ -{File "%TEMP%/utf-check-832-2-52-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes +utf-check 1171 utf-check-1171-2-173-1.jnk \ +{File "%TEMP%/utf-check-1171-2-173-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1172 utf-check-1172-2-174-0.jnk \ +{File "%TEMP%/utf-check-1172-2-174-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no Has flag LOOK_CR: yes Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no @@ -12031,12 +17483,12 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 833 utf-check-833-2-52-1.jnk \ -{File "%TEMP%/utf-check-833-2-52-1.jnk" has 9 bytes. +utf-check 1173 utf-check-1173-2-174-1.jnk \ +{File "%TEMP%/utf-check-1173-2-174-1.jnk" has 13 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: yes @@ -12043,116 +17495,20 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 834 utf-check-834-2-53-0.jnk \ -{File "%TEMP%/utf-check-834-2-53-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 835 utf-check-835-2-53-1.jnk \ -{File "%TEMP%/utf-check-835-2-53-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 836 utf-check-836-2-54-0.jnk \ -{File "%TEMP%/utf-check-836-2-54-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 837 utf-check-837-2-54-1.jnk \ -{File "%TEMP%/utf-check-837-2-54-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 838 utf-check-838-2-55-0.jnk \ -{File "%TEMP%/utf-check-838-2-55-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 839 utf-check-839-2-55-1.jnk \ -{File "%TEMP%/utf-check-839-2-55-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 840 utf-check-840-2-56-0.jnk \ -{File "%TEMP%/utf-check-840-2-56-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1174 utf-check-1174-2-175-0.jnk \ +{File "%TEMP%/utf-check-1174-2-175-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-16: yes +Has flag LOOK_NUL: no Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no @@ -12159,12 +17515,12 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 841 utf-check-841-2-56-1.jnk \ -{File "%TEMP%/utf-check-841-2-56-1.jnk" has 9 bytes. +utf-check 1175 utf-check-1175-2-175-1.jnk \ +{File "%TEMP%/utf-check-1175-2-175-1.jnk" has 13 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no @@ -12171,1840 +17527,16 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 842 utf-check-842-2-57-0.jnk \ -{File "%TEMP%/utf-check-842-2-57-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 843 utf-check-843-2-57-1.jnk \ -{File "%TEMP%/utf-check-843-2-57-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 844 utf-check-844-2-58-0.jnk \ -{File "%TEMP%/utf-check-844-2-58-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 845 utf-check-845-2-58-1.jnk \ -{File "%TEMP%/utf-check-845-2-58-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 846 utf-check-846-2-59-0.jnk \ -{File "%TEMP%/utf-check-846-2-59-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 847 utf-check-847-2-59-1.jnk \ -{File "%TEMP%/utf-check-847-2-59-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 848 utf-check-848-2-60-0.jnk \ -{File "%TEMP%/utf-check-848-2-60-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 849 utf-check-849-2-60-1.jnk \ -{File "%TEMP%/utf-check-849-2-60-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 850 utf-check-850-2-61-0.jnk \ -{File "%TEMP%/utf-check-850-2-61-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 851 utf-check-851-2-61-1.jnk \ -{File "%TEMP%/utf-check-851-2-61-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 852 utf-check-852-2-62-0.jnk \ -{File "%TEMP%/utf-check-852-2-62-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 853 utf-check-853-2-62-1.jnk \ -{File "%TEMP%/utf-check-853-2-62-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 854 utf-check-854-2-63-0.jnk \ -{File "%TEMP%/utf-check-854-2-63-0.jnk" has 16 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 855 utf-check-855-2-63-1.jnk \ -{File "%TEMP%/utf-check-855-2-63-1.jnk" has 17 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 856 utf-check-856-2-64-0.jnk \ -{File "%TEMP%/utf-check-856-2-64-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 857 utf-check-857-2-64-1.jnk \ -{File "%TEMP%/utf-check-857-2-64-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 858 utf-check-858-2-65-0.jnk \ -{File "%TEMP%/utf-check-858-2-65-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 859 utf-check-859-2-65-1.jnk \ -{File "%TEMP%/utf-check-859-2-65-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 860 utf-check-860-2-66-0.jnk \ -{File "%TEMP%/utf-check-860-2-66-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 861 utf-check-861-2-66-1.jnk \ -{File "%TEMP%/utf-check-861-2-66-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 862 utf-check-862-2-67-0.jnk \ -{File "%TEMP%/utf-check-862-2-67-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 863 utf-check-863-2-67-1.jnk \ -{File "%TEMP%/utf-check-863-2-67-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 864 utf-check-864-2-68-0.jnk \ -{File "%TEMP%/utf-check-864-2-68-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 865 utf-check-865-2-68-1.jnk \ -{File "%TEMP%/utf-check-865-2-68-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 866 utf-check-866-2-69-0.jnk \ -{File "%TEMP%/utf-check-866-2-69-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 867 utf-check-867-2-69-1.jnk \ -{File "%TEMP%/utf-check-867-2-69-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 868 utf-check-868-2-70-0.jnk \ -{File "%TEMP%/utf-check-868-2-70-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 869 utf-check-869-2-70-1.jnk \ -{File "%TEMP%/utf-check-869-2-70-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 870 utf-check-870-2-71-0.jnk \ -{File "%TEMP%/utf-check-870-2-71-0.jnk" has 16 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 871 utf-check-871-2-71-1.jnk \ -{File "%TEMP%/utf-check-871-2-71-1.jnk" has 17 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 872 utf-check-872-2-72-0.jnk \ -{File "%TEMP%/utf-check-872-2-72-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 873 utf-check-873-2-72-1.jnk \ -{File "%TEMP%/utf-check-873-2-72-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 874 utf-check-874-2-73-0.jnk \ -{File "%TEMP%/utf-check-874-2-73-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 875 utf-check-875-2-73-1.jnk \ -{File "%TEMP%/utf-check-875-2-73-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 876 utf-check-876-2-74-0.jnk \ -{File "%TEMP%/utf-check-876-2-74-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 877 utf-check-877-2-74-1.jnk \ -{File "%TEMP%/utf-check-877-2-74-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 878 utf-check-878-2-75-0.jnk \ -{File "%TEMP%/utf-check-878-2-75-0.jnk" has 16 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 879 utf-check-879-2-75-1.jnk \ -{File "%TEMP%/utf-check-879-2-75-1.jnk" has 17 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 880 utf-check-880-2-76-0.jnk \ -{File "%TEMP%/utf-check-880-2-76-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 881 utf-check-881-2-76-1.jnk \ -{File "%TEMP%/utf-check-881-2-76-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 882 utf-check-882-2-77-0.jnk \ -{File "%TEMP%/utf-check-882-2-77-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 883 utf-check-883-2-77-1.jnk \ -{File "%TEMP%/utf-check-883-2-77-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 884 utf-check-884-2-78-0.jnk \ -{File "%TEMP%/utf-check-884-2-78-0.jnk" has 16 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 885 utf-check-885-2-78-1.jnk \ -{File "%TEMP%/utf-check-885-2-78-1.jnk" has 17 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 886 utf-check-886-2-79-0.jnk \ -{File "%TEMP%/utf-check-886-2-79-0.jnk" has 18 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 887 utf-check-887-2-79-1.jnk \ -{File "%TEMP%/utf-check-887-2-79-1.jnk" has 19 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 888 utf-check-888-2-80-0.jnk \ -{File "%TEMP%/utf-check-888-2-80-0.jnk" has 16388 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 889 utf-check-889-2-80-1.jnk \ -{File "%TEMP%/utf-check-889-2-80-1.jnk" has 16389 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 890 utf-check-890-2-81-0.jnk \ -{File "%TEMP%/utf-check-890-2-81-0.jnk" has 16390 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 891 utf-check-891-2-81-1.jnk \ -{File "%TEMP%/utf-check-891-2-81-1.jnk" has 16391 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 892 utf-check-892-2-82-0.jnk \ -{File "%TEMP%/utf-check-892-2-82-0.jnk" has 16390 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 893 utf-check-893-2-82-1.jnk \ -{File "%TEMP%/utf-check-893-2-82-1.jnk" has 16391 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 894 utf-check-894-2-83-0.jnk \ -{File "%TEMP%/utf-check-894-2-83-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 895 utf-check-895-2-83-1.jnk \ -{File "%TEMP%/utf-check-895-2-83-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 896 utf-check-896-2-84-0.jnk \ -{File "%TEMP%/utf-check-896-2-84-0.jnk" has 16394 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 897 utf-check-897-2-84-1.jnk \ -{File "%TEMP%/utf-check-897-2-84-1.jnk" has 16395 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 898 utf-check-898-2-85-0.jnk \ -{File "%TEMP%/utf-check-898-2-85-0.jnk" has 16396 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 899 utf-check-899-2-85-1.jnk \ -{File "%TEMP%/utf-check-899-2-85-1.jnk" has 16397 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 900 utf-check-900-2-86-0.jnk \ -{File "%TEMP%/utf-check-900-2-86-0.jnk" has 16396 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 901 utf-check-901-2-86-1.jnk \ -{File "%TEMP%/utf-check-901-2-86-1.jnk" has 16397 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 902 utf-check-902-2-87-0.jnk \ -{File "%TEMP%/utf-check-902-2-87-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 903 utf-check-903-2-87-1.jnk \ -{File "%TEMP%/utf-check-903-2-87-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 904 utf-check-904-2-88-0.jnk \ -{File "%TEMP%/utf-check-904-2-88-0.jnk" has 16390 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 905 utf-check-905-2-88-1.jnk \ -{File "%TEMP%/utf-check-905-2-88-1.jnk" has 16391 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 906 utf-check-906-2-89-0.jnk \ -{File "%TEMP%/utf-check-906-2-89-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 907 utf-check-907-2-89-1.jnk \ -{File "%TEMP%/utf-check-907-2-89-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 908 utf-check-908-2-90-0.jnk \ -{File "%TEMP%/utf-check-908-2-90-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 909 utf-check-909-2-90-1.jnk \ -{File "%TEMP%/utf-check-909-2-90-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 910 utf-check-910-2-91-0.jnk \ -{File "%TEMP%/utf-check-910-2-91-0.jnk" has 16394 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 911 utf-check-911-2-91-1.jnk \ -{File "%TEMP%/utf-check-911-2-91-1.jnk" has 16395 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 912 utf-check-912-2-92-0.jnk \ -{File "%TEMP%/utf-check-912-2-92-0.jnk" has 16396 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 913 utf-check-913-2-92-1.jnk \ -{File "%TEMP%/utf-check-913-2-92-1.jnk" has 16397 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 914 utf-check-914-2-93-0.jnk \ -{File "%TEMP%/utf-check-914-2-93-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 915 utf-check-915-2-93-1.jnk \ -{File "%TEMP%/utf-check-915-2-93-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 916 utf-check-916-2-94-0.jnk \ -{File "%TEMP%/utf-check-916-2-94-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 917 utf-check-917-2-94-1.jnk \ -{File "%TEMP%/utf-check-917-2-94-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 918 utf-check-918-2-95-0.jnk \ -{File "%TEMP%/utf-check-918-2-95-0.jnk" has 16400 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 919 utf-check-919-2-95-1.jnk \ -{File "%TEMP%/utf-check-919-2-95-1.jnk" has 16401 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 920 utf-check-920-2-96-0.jnk \ -{File "%TEMP%/utf-check-920-2-96-0.jnk" has 16390 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 921 utf-check-921-2-96-1.jnk \ -{File "%TEMP%/utf-check-921-2-96-1.jnk" has 16391 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 922 utf-check-922-2-97-0.jnk \ -{File "%TEMP%/utf-check-922-2-97-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 923 utf-check-923-2-97-1.jnk \ -{File "%TEMP%/utf-check-923-2-97-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 924 utf-check-924-2-98-0.jnk \ -{File "%TEMP%/utf-check-924-2-98-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 925 utf-check-925-2-98-1.jnk \ -{File "%TEMP%/utf-check-925-2-98-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 926 utf-check-926-2-99-0.jnk \ -{File "%TEMP%/utf-check-926-2-99-0.jnk" has 16394 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 927 utf-check-927-2-99-1.jnk \ -{File "%TEMP%/utf-check-927-2-99-1.jnk" has 16395 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 928 utf-check-928-2-100-0.jnk \ -{File "%TEMP%/utf-check-928-2-100-0.jnk" has 16396 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 929 utf-check-929-2-100-1.jnk \ -{File "%TEMP%/utf-check-929-2-100-1.jnk" has 16397 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 930 utf-check-930-2-101-0.jnk \ -{File "%TEMP%/utf-check-930-2-101-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 931 utf-check-931-2-101-1.jnk \ -{File "%TEMP%/utf-check-931-2-101-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 932 utf-check-932-2-102-0.jnk \ -{File "%TEMP%/utf-check-932-2-102-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 933 utf-check-933-2-102-1.jnk \ -{File "%TEMP%/utf-check-933-2-102-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 934 utf-check-934-2-103-0.jnk \ -{File "%TEMP%/utf-check-934-2-103-0.jnk" has 16400 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 935 utf-check-935-2-103-1.jnk \ -{File "%TEMP%/utf-check-935-2-103-1.jnk" has 16401 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 936 utf-check-936-2-104-0.jnk \ -{File "%TEMP%/utf-check-936-2-104-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 937 utf-check-937-2-104-1.jnk \ -{File "%TEMP%/utf-check-937-2-104-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 938 utf-check-938-2-105-0.jnk \ -{File "%TEMP%/utf-check-938-2-105-0.jnk" has 16394 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 939 utf-check-939-2-105-1.jnk \ -{File "%TEMP%/utf-check-939-2-105-1.jnk" has 16395 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 940 utf-check-940-2-106-0.jnk \ -{File "%TEMP%/utf-check-940-2-106-0.jnk" has 16394 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 941 utf-check-941-2-106-1.jnk \ -{File "%TEMP%/utf-check-941-2-106-1.jnk" has 16395 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 942 utf-check-942-2-107-0.jnk \ -{File "%TEMP%/utf-check-942-2-107-0.jnk" has 16396 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 943 utf-check-943-2-107-1.jnk \ -{File "%TEMP%/utf-check-943-2-107-1.jnk" has 16397 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 944 utf-check-944-2-108-0.jnk \ -{File "%TEMP%/utf-check-944-2-108-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 945 utf-check-945-2-108-1.jnk \ -{File "%TEMP%/utf-check-945-2-108-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 946 utf-check-946-2-109-0.jnk \ -{File "%TEMP%/utf-check-946-2-109-0.jnk" has 16400 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 947 utf-check-947-2-109-1.jnk \ -{File "%TEMP%/utf-check-947-2-109-1.jnk" has 16401 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 948 utf-check-948-2-110-0.jnk \ -{File "%TEMP%/utf-check-948-2-110-0.jnk" has 16400 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 949 utf-check-949-2-110-1.jnk \ -{File "%TEMP%/utf-check-949-2-110-1.jnk" has 16401 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 950 utf-check-950-2-111-0.jnk \ -{File "%TEMP%/utf-check-950-2-111-0.jnk" has 16402 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 951 utf-check-951-2-111-1.jnk \ -{File "%TEMP%/utf-check-951-2-111-1.jnk" has 16403 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 952 utf-check-952-2-112-0.jnk \ -{File "%TEMP%/utf-check-952-2-112-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 953 utf-check-953-2-112-1.jnk \ -{File "%TEMP%/utf-check-953-2-112-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 954 utf-check-954-2-113-0.jnk \ -{File "%TEMP%/utf-check-954-2-113-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 955 utf-check-955-2-113-1.jnk \ -{File "%TEMP%/utf-check-955-2-113-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 956 utf-check-956-2-114-0.jnk \ -{File "%TEMP%/utf-check-956-2-114-0.jnk" has 6 bytes. +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1176 utf-check-1176-2-176-0.jnk \ +{File "%TEMP%/utf-check-1176-2-176-0.jnk" has 14 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: yes @@ -14015,12 +17547,12 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 957 utf-check-957-2-114-1.jnk \ -{File "%TEMP%/utf-check-957-2-114-1.jnk" has 7 bytes. +utf-check 1177 utf-check-1177-2-176-1.jnk \ +{File "%TEMP%/utf-check-1177-2-176-1.jnk" has 15 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: yes @@ -14027,208 +17559,16 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 958 utf-check-958-2-115-0.jnk \ -{File "%TEMP%/utf-check-958-2-115-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 959 utf-check-959-2-115-1.jnk \ -{File "%TEMP%/utf-check-959-2-115-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 960 utf-check-960-2-116-0.jnk \ -{File "%TEMP%/utf-check-960-2-116-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 961 utf-check-961-2-116-1.jnk \ -{File "%TEMP%/utf-check-961-2-116-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 962 utf-check-962-2-117-0.jnk \ -{File "%TEMP%/utf-check-962-2-117-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 963 utf-check-963-2-117-1.jnk \ -{File "%TEMP%/utf-check-963-2-117-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 964 utf-check-964-2-118-0.jnk \ -{File "%TEMP%/utf-check-964-2-118-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 965 utf-check-965-2-118-1.jnk \ -{File "%TEMP%/utf-check-965-2-118-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 966 utf-check-966-2-119-0.jnk \ -{File "%TEMP%/utf-check-966-2-119-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 967 utf-check-967-2-119-1.jnk \ -{File "%TEMP%/utf-check-967-2-119-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 968 utf-check-968-2-120-0.jnk \ -{File "%TEMP%/utf-check-968-2-120-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 969 utf-check-969-2-120-1.jnk \ -{File "%TEMP%/utf-check-969-2-120-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 970 utf-check-970-2-121-0.jnk \ -{File "%TEMP%/utf-check-970-2-121-0.jnk" has 8 bytes. +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1178 utf-check-1178-2-177-0.jnk \ +{File "%TEMP%/utf-check-1178-2-177-0.jnk" has 12 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-16: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no @@ -14239,236 +17579,12 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 971 utf-check-971-2-121-1.jnk \ -{File "%TEMP%/utf-check-971-2-121-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 972 utf-check-972-2-122-0.jnk \ -{File "%TEMP%/utf-check-972-2-122-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 973 utf-check-973-2-122-1.jnk \ -{File "%TEMP%/utf-check-973-2-122-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 974 utf-check-974-2-123-0.jnk \ -{File "%TEMP%/utf-check-974-2-123-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 975 utf-check-975-2-123-1.jnk \ -{File "%TEMP%/utf-check-975-2-123-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 976 utf-check-976-2-124-0.jnk \ -{File "%TEMP%/utf-check-976-2-124-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 977 utf-check-977-2-124-1.jnk \ -{File "%TEMP%/utf-check-977-2-124-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 978 utf-check-978-2-125-0.jnk \ -{File "%TEMP%/utf-check-978-2-125-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 979 utf-check-979-2-125-1.jnk \ -{File "%TEMP%/utf-check-979-2-125-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 980 utf-check-980-2-126-0.jnk \ -{File "%TEMP%/utf-check-980-2-126-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 981 utf-check-981-2-126-1.jnk \ -{File "%TEMP%/utf-check-981-2-126-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 982 utf-check-982-2-127-0.jnk \ -{File "%TEMP%/utf-check-982-2-127-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 983 utf-check-983-2-127-1.jnk \ -{File "%TEMP%/utf-check-983-2-127-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 984 utf-check-984-2-128-0.jnk \ -{File "%TEMP%/utf-check-984-2-128-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 985 utf-check-985-2-128-1.jnk \ -{File "%TEMP%/utf-check-985-2-128-1.jnk" has 7 bytes. +utf-check 1179 utf-check-1179-2-177-1.jnk \ +{File "%TEMP%/utf-check-1179-2-177-1.jnk" has 13 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no @@ -14475,176 +17591,16 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 986 utf-check-986-2-129-0.jnk \ -{File "%TEMP%/utf-check-986-2-129-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 987 utf-check-987-2-129-1.jnk \ -{File "%TEMP%/utf-check-987-2-129-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 988 utf-check-988-2-130-0.jnk \ -{File "%TEMP%/utf-check-988-2-130-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 989 utf-check-989-2-130-1.jnk \ -{File "%TEMP%/utf-check-989-2-130-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 990 utf-check-990-2-131-0.jnk \ -{File "%TEMP%/utf-check-990-2-131-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 991 utf-check-991-2-131-1.jnk \ -{File "%TEMP%/utf-check-991-2-131-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 992 utf-check-992-2-132-0.jnk \ -{File "%TEMP%/utf-check-992-2-132-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 993 utf-check-993-2-132-1.jnk \ -{File "%TEMP%/utf-check-993-2-132-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 994 utf-check-994-2-133-0.jnk \ -{File "%TEMP%/utf-check-994-2-133-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 995 utf-check-995-2-133-1.jnk \ -{File "%TEMP%/utf-check-995-2-133-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 996 utf-check-996-2-134-0.jnk \ -{File "%TEMP%/utf-check-996-2-134-0.jnk" has 6 bytes. +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1180 utf-check-1180-2-178-0.jnk \ +{File "%TEMP%/utf-check-1180-2-178-0.jnk" has 12 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: yes @@ -14655,12 +17611,12 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 997 utf-check-997-2-134-1.jnk \ -{File "%TEMP%/utf-check-997-2-134-1.jnk" has 7 bytes. +utf-check 1181 utf-check-1181-2-178-1.jnk \ +{File "%TEMP%/utf-check-1181-2-178-1.jnk" has 13 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: yes @@ -14667,16 +17623,16 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 998 utf-check-998-2-135-0.jnk \ -{File "%TEMP%/utf-check-998-2-135-0.jnk" has 6 bytes. +utf-check 1182 utf-check-1182-2-179-0.jnk \ +{File "%TEMP%/utf-check-1182-2-179-0.jnk" has 12 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: no @@ -14687,12 +17643,12 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 999 utf-check-999-2-135-1.jnk \ -{File "%TEMP%/utf-check-999-2-135-1.jnk" has 7 bytes. +utf-check 1183 utf-check-1183-2-179-1.jnk \ +{File "%TEMP%/utf-check-1183-2-179-1.jnk" has 13 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no @@ -14699,16 +17655,16 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 1000 utf-check-1000-2-136-0.jnk \ -{File "%TEMP%/utf-check-1000-2-136-0.jnk" has 8 bytes. +utf-check 1184 utf-check-1184-2-180-0.jnk \ +{File "%TEMP%/utf-check-1184-2-180-0.jnk" has 14 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: yes @@ -14719,12 +17675,12 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 1001 utf-check-1001-2-136-1.jnk \ -{File "%TEMP%/utf-check-1001-2-136-1.jnk" has 9 bytes. +utf-check 1185 utf-check-1185-2-180-1.jnk \ +{File "%TEMP%/utf-check-1185-2-180-1.jnk" has 15 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: yes @@ -14731,656 +17687,16 @@ Has flag LOOK_LONE_CR: yes Has flag LOOK_LF: yes Has flag LOOK_LONE_LF: yes Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1002 utf-check-1002-2-137-0.jnk \ -{File "%TEMP%/utf-check-1002-2-137-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1003 utf-check-1003-2-137-1.jnk \ -{File "%TEMP%/utf-check-1003-2-137-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1004 utf-check-1004-2-138-0.jnk \ -{File "%TEMP%/utf-check-1004-2-138-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1005 utf-check-1005-2-138-1.jnk \ -{File "%TEMP%/utf-check-1005-2-138-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1006 utf-check-1006-2-139-0.jnk \ -{File "%TEMP%/utf-check-1006-2-139-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1007 utf-check-1007-2-139-1.jnk \ -{File "%TEMP%/utf-check-1007-2-139-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1008 utf-check-1008-2-140-0.jnk \ -{File "%TEMP%/utf-check-1008-2-140-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1009 utf-check-1009-2-140-1.jnk \ -{File "%TEMP%/utf-check-1009-2-140-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1010 utf-check-1010-2-141-0.jnk \ -{File "%TEMP%/utf-check-1010-2-141-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1011 utf-check-1011-2-141-1.jnk \ -{File "%TEMP%/utf-check-1011-2-141-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1012 utf-check-1012-2-142-0.jnk \ -{File "%TEMP%/utf-check-1012-2-142-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1013 utf-check-1013-2-142-1.jnk \ -{File "%TEMP%/utf-check-1013-2-142-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1014 utf-check-1014-2-143-0.jnk \ -{File "%TEMP%/utf-check-1014-2-143-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1015 utf-check-1015-2-143-1.jnk \ -{File "%TEMP%/utf-check-1015-2-143-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1016 utf-check-1016-2-144-0.jnk \ -{File "%TEMP%/utf-check-1016-2-144-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1017 utf-check-1017-2-144-1.jnk \ -{File "%TEMP%/utf-check-1017-2-144-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1018 utf-check-1018-2-145-0.jnk \ -{File "%TEMP%/utf-check-1018-2-145-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1019 utf-check-1019-2-145-1.jnk \ -{File "%TEMP%/utf-check-1019-2-145-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1020 utf-check-1020-2-146-0.jnk \ -{File "%TEMP%/utf-check-1020-2-146-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1021 utf-check-1021-2-146-1.jnk \ -{File "%TEMP%/utf-check-1021-2-146-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1022 utf-check-1022-2-147-0.jnk \ -{File "%TEMP%/utf-check-1022-2-147-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1023 utf-check-1023-2-147-1.jnk \ -{File "%TEMP%/utf-check-1023-2-147-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1024 utf-check-1024-2-148-0.jnk \ -{File "%TEMP%/utf-check-1024-2-148-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1025 utf-check-1025-2-148-1.jnk \ -{File "%TEMP%/utf-check-1025-2-148-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1026 utf-check-1026-2-149-0.jnk \ -{File "%TEMP%/utf-check-1026-2-149-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1027 utf-check-1027-2-149-1.jnk \ -{File "%TEMP%/utf-check-1027-2-149-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1028 utf-check-1028-2-150-0.jnk \ -{File "%TEMP%/utf-check-1028-2-150-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1029 utf-check-1029-2-150-1.jnk \ -{File "%TEMP%/utf-check-1029-2-150-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1030 utf-check-1030-2-151-0.jnk \ -{File "%TEMP%/utf-check-1030-2-151-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1031 utf-check-1031-2-151-1.jnk \ -{File "%TEMP%/utf-check-1031-2-151-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1032 utf-check-1032-2-152-0.jnk \ -{File "%TEMP%/utf-check-1032-2-152-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1033 utf-check-1033-2-152-1.jnk \ -{File "%TEMP%/utf-check-1033-2-152-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1034 utf-check-1034-2-153-0.jnk \ -{File "%TEMP%/utf-check-1034-2-153-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1035 utf-check-1035-2-153-1.jnk \ -{File "%TEMP%/utf-check-1035-2-153-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1036 utf-check-1036-2-154-0.jnk \ -{File "%TEMP%/utf-check-1036-2-154-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1037 utf-check-1037-2-154-1.jnk \ -{File "%TEMP%/utf-check-1037-2-154-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1038 utf-check-1038-2-155-0.jnk \ -{File "%TEMP%/utf-check-1038-2-155-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1039 utf-check-1039-2-155-1.jnk \ -{File "%TEMP%/utf-check-1039-2-155-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1040 utf-check-1040-2-156-0.jnk \ -{File "%TEMP%/utf-check-1040-2-156-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-16: yes -Has flag LOOK_NUL: no -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: yes -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1041 utf-check-1041-2-156-1.jnk \ -{File "%TEMP%/utf-check-1041-2-156-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: yes -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: yes -Has flag LOOK_LONE_CR: yes -Has flag LOOK_LF: yes -Has flag LOOK_LONE_LF: yes -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1042 utf-check-1042-3-0-0.jnk \ -{File "%TEMP%/utf-check-1042-3-0-0.jnk" has 2 bytes. +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1186 utf-check-1186-3-0-0.jnk \ +{File "%TEMP%/utf-check-1186-3-0-0.jnk" has 2 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: reversed Looks like UTF-16: yes Has flag LOOK_NUL: no Has flag LOOK_CR: no @@ -15391,12 +17707,12 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 1043 utf-check-1043-3-0-1.jnk \ -{File "%TEMP%/utf-check-1043-3-0-1.jnk" has 3 bytes. +utf-check 1187 utf-check-1187-3-0-1.jnk \ +{File "%TEMP%/utf-check-1187-3-0-1.jnk" has 3 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: reversed Looks like UTF-8: yes Has flag LOOK_NUL: no Has flag LOOK_CR: no @@ -15403,2544 +17719,3552 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1044 utf-check-1044-3-1-0.jnk \ -{File "%TEMP%/utf-check-1044-3-1-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1045 utf-check-1045-3-1-1.jnk \ -{File "%TEMP%/utf-check-1045-3-1-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1046 utf-check-1046-3-2-0.jnk \ -{File "%TEMP%/utf-check-1046-3-2-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1047 utf-check-1047-3-2-1.jnk \ -{File "%TEMP%/utf-check-1047-3-2-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1048 utf-check-1048-3-3-0.jnk \ -{File "%TEMP%/utf-check-1048-3-3-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1049 utf-check-1049-3-3-1.jnk \ -{File "%TEMP%/utf-check-1049-3-3-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1050 utf-check-1050-3-4-0.jnk \ -{File "%TEMP%/utf-check-1050-3-4-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1051 utf-check-1051-3-4-1.jnk \ -{File "%TEMP%/utf-check-1051-3-4-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1052 utf-check-1052-3-5-0.jnk \ -{File "%TEMP%/utf-check-1052-3-5-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1053 utf-check-1053-3-5-1.jnk \ -{File "%TEMP%/utf-check-1053-3-5-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1054 utf-check-1054-3-6-0.jnk \ -{File "%TEMP%/utf-check-1054-3-6-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1055 utf-check-1055-3-6-1.jnk \ -{File "%TEMP%/utf-check-1055-3-6-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1056 utf-check-1056-3-7-0.jnk \ -{File "%TEMP%/utf-check-1056-3-7-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1057 utf-check-1057-3-7-1.jnk \ -{File "%TEMP%/utf-check-1057-3-7-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1058 utf-check-1058-3-8-0.jnk \ -{File "%TEMP%/utf-check-1058-3-8-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1059 utf-check-1059-3-8-1.jnk \ -{File "%TEMP%/utf-check-1059-3-8-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1060 utf-check-1060-3-9-0.jnk \ -{File "%TEMP%/utf-check-1060-3-9-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1061 utf-check-1061-3-9-1.jnk \ -{File "%TEMP%/utf-check-1061-3-9-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1062 utf-check-1062-3-10-0.jnk \ -{File "%TEMP%/utf-check-1062-3-10-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1063 utf-check-1063-3-10-1.jnk \ -{File "%TEMP%/utf-check-1063-3-10-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1064 utf-check-1064-3-11-0.jnk \ -{File "%TEMP%/utf-check-1064-3-11-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1065 utf-check-1065-3-11-1.jnk \ -{File "%TEMP%/utf-check-1065-3-11-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1066 utf-check-1066-3-12-0.jnk \ -{File "%TEMP%/utf-check-1066-3-12-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1067 utf-check-1067-3-12-1.jnk \ -{File "%TEMP%/utf-check-1067-3-12-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1068 utf-check-1068-3-13-0.jnk \ -{File "%TEMP%/utf-check-1068-3-13-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1069 utf-check-1069-3-13-1.jnk \ -{File "%TEMP%/utf-check-1069-3-13-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1070 utf-check-1070-3-14-0.jnk \ -{File "%TEMP%/utf-check-1070-3-14-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1071 utf-check-1071-3-14-1.jnk \ -{File "%TEMP%/utf-check-1071-3-14-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1072 utf-check-1072-3-15-0.jnk \ -{File "%TEMP%/utf-check-1072-3-15-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1073 utf-check-1073-3-15-1.jnk \ -{File "%TEMP%/utf-check-1073-3-15-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1074 utf-check-1074-3-16-0.jnk \ -{File "%TEMP%/utf-check-1074-3-16-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1075 utf-check-1075-3-16-1.jnk \ -{File "%TEMP%/utf-check-1075-3-16-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1076 utf-check-1076-3-17-0.jnk \ -{File "%TEMP%/utf-check-1076-3-17-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1077 utf-check-1077-3-17-1.jnk \ -{File "%TEMP%/utf-check-1077-3-17-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1078 utf-check-1078-3-18-0.jnk \ -{File "%TEMP%/utf-check-1078-3-18-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1079 utf-check-1079-3-18-1.jnk \ -{File "%TEMP%/utf-check-1079-3-18-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1080 utf-check-1080-3-19-0.jnk \ -{File "%TEMP%/utf-check-1080-3-19-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1081 utf-check-1081-3-19-1.jnk \ -{File "%TEMP%/utf-check-1081-3-19-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1082 utf-check-1082-3-20-0.jnk \ -{File "%TEMP%/utf-check-1082-3-20-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1083 utf-check-1083-3-20-1.jnk \ -{File "%TEMP%/utf-check-1083-3-20-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1084 utf-check-1084-3-21-0.jnk \ -{File "%TEMP%/utf-check-1084-3-21-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1085 utf-check-1085-3-21-1.jnk \ -{File "%TEMP%/utf-check-1085-3-21-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1086 utf-check-1086-3-22-0.jnk \ -{File "%TEMP%/utf-check-1086-3-22-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1087 utf-check-1087-3-22-1.jnk \ -{File "%TEMP%/utf-check-1087-3-22-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1088 utf-check-1088-3-23-0.jnk \ -{File "%TEMP%/utf-check-1088-3-23-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1089 utf-check-1089-3-23-1.jnk \ -{File "%TEMP%/utf-check-1089-3-23-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1090 utf-check-1090-3-24-0.jnk \ -{File "%TEMP%/utf-check-1090-3-24-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1091 utf-check-1091-3-24-1.jnk \ -{File "%TEMP%/utf-check-1091-3-24-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1092 utf-check-1092-3-25-0.jnk \ -{File "%TEMP%/utf-check-1092-3-25-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1093 utf-check-1093-3-25-1.jnk \ -{File "%TEMP%/utf-check-1093-3-25-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1094 utf-check-1094-3-26-0.jnk \ -{File "%TEMP%/utf-check-1094-3-26-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1095 utf-check-1095-3-26-1.jnk \ -{File "%TEMP%/utf-check-1095-3-26-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1096 utf-check-1096-3-27-0.jnk \ -{File "%TEMP%/utf-check-1096-3-27-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1097 utf-check-1097-3-27-1.jnk \ -{File "%TEMP%/utf-check-1097-3-27-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1098 utf-check-1098-3-28-0.jnk \ -{File "%TEMP%/utf-check-1098-3-28-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1099 utf-check-1099-3-28-1.jnk \ -{File "%TEMP%/utf-check-1099-3-28-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1100 utf-check-1100-3-29-0.jnk \ -{File "%TEMP%/utf-check-1100-3-29-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1101 utf-check-1101-3-29-1.jnk \ -{File "%TEMP%/utf-check-1101-3-29-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1102 utf-check-1102-3-30-0.jnk \ -{File "%TEMP%/utf-check-1102-3-30-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1103 utf-check-1103-3-30-1.jnk \ -{File "%TEMP%/utf-check-1103-3-30-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1104 utf-check-1104-3-31-0.jnk \ -{File "%TEMP%/utf-check-1104-3-31-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1105 utf-check-1105-3-31-1.jnk \ -{File "%TEMP%/utf-check-1105-3-31-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1106 utf-check-1106-3-32-0.jnk \ -{File "%TEMP%/utf-check-1106-3-32-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1107 utf-check-1107-3-32-1.jnk \ -{File "%TEMP%/utf-check-1107-3-32-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1108 utf-check-1108-3-33-0.jnk \ -{File "%TEMP%/utf-check-1108-3-33-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1109 utf-check-1109-3-33-1.jnk \ -{File "%TEMP%/utf-check-1109-3-33-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1110 utf-check-1110-3-34-0.jnk \ -{File "%TEMP%/utf-check-1110-3-34-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1111 utf-check-1111-3-34-1.jnk \ -{File "%TEMP%/utf-check-1111-3-34-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1112 utf-check-1112-3-35-0.jnk \ -{File "%TEMP%/utf-check-1112-3-35-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1113 utf-check-1113-3-35-1.jnk \ -{File "%TEMP%/utf-check-1113-3-35-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1114 utf-check-1114-3-36-0.jnk \ -{File "%TEMP%/utf-check-1114-3-36-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1115 utf-check-1115-3-36-1.jnk \ -{File "%TEMP%/utf-check-1115-3-36-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1116 utf-check-1116-3-37-0.jnk \ -{File "%TEMP%/utf-check-1116-3-37-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1117 utf-check-1117-3-37-1.jnk \ -{File "%TEMP%/utf-check-1117-3-37-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1118 utf-check-1118-3-38-0.jnk \ -{File "%TEMP%/utf-check-1118-3-38-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1119 utf-check-1119-3-38-1.jnk \ -{File "%TEMP%/utf-check-1119-3-38-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1120 utf-check-1120-3-39-0.jnk \ -{File "%TEMP%/utf-check-1120-3-39-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1121 utf-check-1121-3-39-1.jnk \ -{File "%TEMP%/utf-check-1121-3-39-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1122 utf-check-1122-3-40-0.jnk \ -{File "%TEMP%/utf-check-1122-3-40-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1123 utf-check-1123-3-40-1.jnk \ -{File "%TEMP%/utf-check-1123-3-40-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1124 utf-check-1124-3-41-0.jnk \ -{File "%TEMP%/utf-check-1124-3-41-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1125 utf-check-1125-3-41-1.jnk \ -{File "%TEMP%/utf-check-1125-3-41-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1126 utf-check-1126-3-42-0.jnk \ -{File "%TEMP%/utf-check-1126-3-42-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1127 utf-check-1127-3-42-1.jnk \ -{File "%TEMP%/utf-check-1127-3-42-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1128 utf-check-1128-3-43-0.jnk \ -{File "%TEMP%/utf-check-1128-3-43-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1129 utf-check-1129-3-43-1.jnk \ -{File "%TEMP%/utf-check-1129-3-43-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1130 utf-check-1130-3-44-0.jnk \ -{File "%TEMP%/utf-check-1130-3-44-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1131 utf-check-1131-3-44-1.jnk \ -{File "%TEMP%/utf-check-1131-3-44-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1132 utf-check-1132-3-45-0.jnk \ -{File "%TEMP%/utf-check-1132-3-45-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1133 utf-check-1133-3-45-1.jnk \ -{File "%TEMP%/utf-check-1133-3-45-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1134 utf-check-1134-3-46-0.jnk \ -{File "%TEMP%/utf-check-1134-3-46-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1135 utf-check-1135-3-46-1.jnk \ -{File "%TEMP%/utf-check-1135-3-46-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1136 utf-check-1136-3-47-0.jnk \ -{File "%TEMP%/utf-check-1136-3-47-0.jnk" has 16 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1137 utf-check-1137-3-47-1.jnk \ -{File "%TEMP%/utf-check-1137-3-47-1.jnk" has 17 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1138 utf-check-1138-3-48-0.jnk \ -{File "%TEMP%/utf-check-1138-3-48-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1139 utf-check-1139-3-48-1.jnk \ -{File "%TEMP%/utf-check-1139-3-48-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1140 utf-check-1140-3-49-0.jnk \ -{File "%TEMP%/utf-check-1140-3-49-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1141 utf-check-1141-3-49-1.jnk \ -{File "%TEMP%/utf-check-1141-3-49-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1142 utf-check-1142-3-50-0.jnk \ -{File "%TEMP%/utf-check-1142-3-50-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1143 utf-check-1143-3-50-1.jnk \ -{File "%TEMP%/utf-check-1143-3-50-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1144 utf-check-1144-3-51-0.jnk \ -{File "%TEMP%/utf-check-1144-3-51-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1145 utf-check-1145-3-51-1.jnk \ -{File "%TEMP%/utf-check-1145-3-51-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1146 utf-check-1146-3-52-0.jnk \ -{File "%TEMP%/utf-check-1146-3-52-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1147 utf-check-1147-3-52-1.jnk \ -{File "%TEMP%/utf-check-1147-3-52-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1148 utf-check-1148-3-53-0.jnk \ -{File "%TEMP%/utf-check-1148-3-53-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1149 utf-check-1149-3-53-1.jnk \ -{File "%TEMP%/utf-check-1149-3-53-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1150 utf-check-1150-3-54-0.jnk \ -{File "%TEMP%/utf-check-1150-3-54-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1151 utf-check-1151-3-54-1.jnk \ -{File "%TEMP%/utf-check-1151-3-54-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1152 utf-check-1152-3-55-0.jnk \ -{File "%TEMP%/utf-check-1152-3-55-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1153 utf-check-1153-3-55-1.jnk \ -{File "%TEMP%/utf-check-1153-3-55-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1154 utf-check-1154-3-56-0.jnk \ -{File "%TEMP%/utf-check-1154-3-56-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1155 utf-check-1155-3-56-1.jnk \ -{File "%TEMP%/utf-check-1155-3-56-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1156 utf-check-1156-3-57-0.jnk \ -{File "%TEMP%/utf-check-1156-3-57-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1157 utf-check-1157-3-57-1.jnk \ -{File "%TEMP%/utf-check-1157-3-57-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1158 utf-check-1158-3-58-0.jnk \ -{File "%TEMP%/utf-check-1158-3-58-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1159 utf-check-1159-3-58-1.jnk \ -{File "%TEMP%/utf-check-1159-3-58-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1160 utf-check-1160-3-59-0.jnk \ -{File "%TEMP%/utf-check-1160-3-59-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1161 utf-check-1161-3-59-1.jnk \ -{File "%TEMP%/utf-check-1161-3-59-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1162 utf-check-1162-3-60-0.jnk \ -{File "%TEMP%/utf-check-1162-3-60-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1163 utf-check-1163-3-60-1.jnk \ -{File "%TEMP%/utf-check-1163-3-60-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1164 utf-check-1164-3-61-0.jnk \ -{File "%TEMP%/utf-check-1164-3-61-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1165 utf-check-1165-3-61-1.jnk \ -{File "%TEMP%/utf-check-1165-3-61-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1166 utf-check-1166-3-62-0.jnk \ -{File "%TEMP%/utf-check-1166-3-62-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1167 utf-check-1167-3-62-1.jnk \ -{File "%TEMP%/utf-check-1167-3-62-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1168 utf-check-1168-3-63-0.jnk \ -{File "%TEMP%/utf-check-1168-3-63-0.jnk" has 16 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1169 utf-check-1169-3-63-1.jnk \ -{File "%TEMP%/utf-check-1169-3-63-1.jnk" has 17 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1170 utf-check-1170-3-64-0.jnk \ -{File "%TEMP%/utf-check-1170-3-64-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1171 utf-check-1171-3-64-1.jnk \ -{File "%TEMP%/utf-check-1171-3-64-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1172 utf-check-1172-3-65-0.jnk \ -{File "%TEMP%/utf-check-1172-3-65-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1173 utf-check-1173-3-65-1.jnk \ -{File "%TEMP%/utf-check-1173-3-65-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1174 utf-check-1174-3-66-0.jnk \ -{File "%TEMP%/utf-check-1174-3-66-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1175 utf-check-1175-3-66-1.jnk \ -{File "%TEMP%/utf-check-1175-3-66-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1176 utf-check-1176-3-67-0.jnk \ -{File "%TEMP%/utf-check-1176-3-67-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1177 utf-check-1177-3-67-1.jnk \ -{File "%TEMP%/utf-check-1177-3-67-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1178 utf-check-1178-3-68-0.jnk \ -{File "%TEMP%/utf-check-1178-3-68-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1179 utf-check-1179-3-68-1.jnk \ -{File "%TEMP%/utf-check-1179-3-68-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1180 utf-check-1180-3-69-0.jnk \ -{File "%TEMP%/utf-check-1180-3-69-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1181 utf-check-1181-3-69-1.jnk \ -{File "%TEMP%/utf-check-1181-3-69-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1182 utf-check-1182-3-70-0.jnk \ -{File "%TEMP%/utf-check-1182-3-70-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1183 utf-check-1183-3-70-1.jnk \ -{File "%TEMP%/utf-check-1183-3-70-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1184 utf-check-1184-3-71-0.jnk \ -{File "%TEMP%/utf-check-1184-3-71-0.jnk" has 16 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1185 utf-check-1185-3-71-1.jnk \ -{File "%TEMP%/utf-check-1185-3-71-1.jnk" has 17 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1186 utf-check-1186-3-72-0.jnk \ -{File "%TEMP%/utf-check-1186-3-72-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1187 utf-check-1187-3-72-1.jnk \ -{File "%TEMP%/utf-check-1187-3-72-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1188 utf-check-1188-3-73-0.jnk \ -{File "%TEMP%/utf-check-1188-3-73-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1189 utf-check-1189-3-73-1.jnk \ -{File "%TEMP%/utf-check-1189-3-73-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1190 utf-check-1190-3-74-0.jnk \ -{File "%TEMP%/utf-check-1190-3-74-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1191 utf-check-1191-3-74-1.jnk \ -{File "%TEMP%/utf-check-1191-3-74-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1192 utf-check-1192-3-75-0.jnk \ -{File "%TEMP%/utf-check-1192-3-75-0.jnk" has 16 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1193 utf-check-1193-3-75-1.jnk \ -{File "%TEMP%/utf-check-1193-3-75-1.jnk" has 17 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1194 utf-check-1194-3-76-0.jnk \ -{File "%TEMP%/utf-check-1194-3-76-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1195 utf-check-1195-3-76-1.jnk \ -{File "%TEMP%/utf-check-1195-3-76-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1196 utf-check-1196-3-77-0.jnk \ -{File "%TEMP%/utf-check-1196-3-77-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1197 utf-check-1197-3-77-1.jnk \ -{File "%TEMP%/utf-check-1197-3-77-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1198 utf-check-1198-3-78-0.jnk \ -{File "%TEMP%/utf-check-1198-3-78-0.jnk" has 16 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1199 utf-check-1199-3-78-1.jnk \ -{File "%TEMP%/utf-check-1199-3-78-1.jnk" has 17 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1200 utf-check-1200-3-79-0.jnk \ -{File "%TEMP%/utf-check-1200-3-79-0.jnk" has 18 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1201 utf-check-1201-3-79-1.jnk \ -{File "%TEMP%/utf-check-1201-3-79-1.jnk" has 19 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1202 utf-check-1202-3-80-0.jnk \ -{File "%TEMP%/utf-check-1202-3-80-0.jnk" has 16388 bytes. +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1188 utf-check-1188-3-1-0.jnk \ +{File "%TEMP%/utf-check-1188-3-1-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1189 utf-check-1189-3-1-1.jnk \ +{File "%TEMP%/utf-check-1189-3-1-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1190 utf-check-1190-3-2-0.jnk \ +{File "%TEMP%/utf-check-1190-3-2-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1191 utf-check-1191-3-2-1.jnk \ +{File "%TEMP%/utf-check-1191-3-2-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1192 utf-check-1192-3-3-0.jnk \ +{File "%TEMP%/utf-check-1192-3-3-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1193 utf-check-1193-3-3-1.jnk \ +{File "%TEMP%/utf-check-1193-3-3-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1194 utf-check-1194-3-4-0.jnk \ +{File "%TEMP%/utf-check-1194-3-4-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1195 utf-check-1195-3-4-1.jnk \ +{File "%TEMP%/utf-check-1195-3-4-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1196 utf-check-1196-3-5-0.jnk \ +{File "%TEMP%/utf-check-1196-3-5-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1197 utf-check-1197-3-5-1.jnk \ +{File "%TEMP%/utf-check-1197-3-5-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1198 utf-check-1198-3-6-0.jnk \ +{File "%TEMP%/utf-check-1198-3-6-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1199 utf-check-1199-3-6-1.jnk \ +{File "%TEMP%/utf-check-1199-3-6-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1200 utf-check-1200-3-7-0.jnk \ +{File "%TEMP%/utf-check-1200-3-7-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1201 utf-check-1201-3-7-1.jnk \ +{File "%TEMP%/utf-check-1201-3-7-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1202 utf-check-1202-3-8-0.jnk \ +{File "%TEMP%/utf-check-1202-3-8-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1203 utf-check-1203-3-8-1.jnk \ +{File "%TEMP%/utf-check-1203-3-8-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1204 utf-check-1204-3-9-0.jnk \ +{File "%TEMP%/utf-check-1204-3-9-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1205 utf-check-1205-3-9-1.jnk \ +{File "%TEMP%/utf-check-1205-3-9-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1206 utf-check-1206-3-10-0.jnk \ +{File "%TEMP%/utf-check-1206-3-10-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1207 utf-check-1207-3-10-1.jnk \ +{File "%TEMP%/utf-check-1207-3-10-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1208 utf-check-1208-3-11-0.jnk \ +{File "%TEMP%/utf-check-1208-3-11-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1209 utf-check-1209-3-11-1.jnk \ +{File "%TEMP%/utf-check-1209-3-11-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1210 utf-check-1210-3-12-0.jnk \ +{File "%TEMP%/utf-check-1210-3-12-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1211 utf-check-1211-3-12-1.jnk \ +{File "%TEMP%/utf-check-1211-3-12-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1212 utf-check-1212-3-13-0.jnk \ +{File "%TEMP%/utf-check-1212-3-13-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1213 utf-check-1213-3-13-1.jnk \ +{File "%TEMP%/utf-check-1213-3-13-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1214 utf-check-1214-3-14-0.jnk \ +{File "%TEMP%/utf-check-1214-3-14-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1215 utf-check-1215-3-14-1.jnk \ +{File "%TEMP%/utf-check-1215-3-14-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1216 utf-check-1216-3-15-0.jnk \ +{File "%TEMP%/utf-check-1216-3-15-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1217 utf-check-1217-3-15-1.jnk \ +{File "%TEMP%/utf-check-1217-3-15-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1218 utf-check-1218-3-16-0.jnk \ +{File "%TEMP%/utf-check-1218-3-16-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1219 utf-check-1219-3-16-1.jnk \ +{File "%TEMP%/utf-check-1219-3-16-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1220 utf-check-1220-3-17-0.jnk \ +{File "%TEMP%/utf-check-1220-3-17-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1221 utf-check-1221-3-17-1.jnk \ +{File "%TEMP%/utf-check-1221-3-17-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1222 utf-check-1222-3-18-0.jnk \ +{File "%TEMP%/utf-check-1222-3-18-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1223 utf-check-1223-3-18-1.jnk \ +{File "%TEMP%/utf-check-1223-3-18-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1224 utf-check-1224-3-19-0.jnk \ +{File "%TEMP%/utf-check-1224-3-19-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1225 utf-check-1225-3-19-1.jnk \ +{File "%TEMP%/utf-check-1225-3-19-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1226 utf-check-1226-3-20-0.jnk \ +{File "%TEMP%/utf-check-1226-3-20-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1227 utf-check-1227-3-20-1.jnk \ +{File "%TEMP%/utf-check-1227-3-20-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1228 utf-check-1228-3-21-0.jnk \ +{File "%TEMP%/utf-check-1228-3-21-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1229 utf-check-1229-3-21-1.jnk \ +{File "%TEMP%/utf-check-1229-3-21-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1230 utf-check-1230-3-22-0.jnk \ +{File "%TEMP%/utf-check-1230-3-22-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1231 utf-check-1231-3-22-1.jnk \ +{File "%TEMP%/utf-check-1231-3-22-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1232 utf-check-1232-3-23-0.jnk \ +{File "%TEMP%/utf-check-1232-3-23-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1233 utf-check-1233-3-23-1.jnk \ +{File "%TEMP%/utf-check-1233-3-23-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1234 utf-check-1234-3-24-0.jnk \ +{File "%TEMP%/utf-check-1234-3-24-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1235 utf-check-1235-3-24-1.jnk \ +{File "%TEMP%/utf-check-1235-3-24-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1236 utf-check-1236-3-25-0.jnk \ +{File "%TEMP%/utf-check-1236-3-25-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1237 utf-check-1237-3-25-1.jnk \ +{File "%TEMP%/utf-check-1237-3-25-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1238 utf-check-1238-3-26-0.jnk \ +{File "%TEMP%/utf-check-1238-3-26-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1239 utf-check-1239-3-26-1.jnk \ +{File "%TEMP%/utf-check-1239-3-26-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1240 utf-check-1240-3-27-0.jnk \ +{File "%TEMP%/utf-check-1240-3-27-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1241 utf-check-1241-3-27-1.jnk \ +{File "%TEMP%/utf-check-1241-3-27-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1242 utf-check-1242-3-28-0.jnk \ +{File "%TEMP%/utf-check-1242-3-28-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1243 utf-check-1243-3-28-1.jnk \ +{File "%TEMP%/utf-check-1243-3-28-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1244 utf-check-1244-3-29-0.jnk \ +{File "%TEMP%/utf-check-1244-3-29-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1245 utf-check-1245-3-29-1.jnk \ +{File "%TEMP%/utf-check-1245-3-29-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1246 utf-check-1246-3-30-0.jnk \ +{File "%TEMP%/utf-check-1246-3-30-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1247 utf-check-1247-3-30-1.jnk \ +{File "%TEMP%/utf-check-1247-3-30-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1248 utf-check-1248-3-31-0.jnk \ +{File "%TEMP%/utf-check-1248-3-31-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1249 utf-check-1249-3-31-1.jnk \ +{File "%TEMP%/utf-check-1249-3-31-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1250 utf-check-1250-3-32-0.jnk \ +{File "%TEMP%/utf-check-1250-3-32-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1251 utf-check-1251-3-32-1.jnk \ +{File "%TEMP%/utf-check-1251-3-32-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1252 utf-check-1252-3-33-0.jnk \ +{File "%TEMP%/utf-check-1252-3-33-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1253 utf-check-1253-3-33-1.jnk \ +{File "%TEMP%/utf-check-1253-3-33-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1254 utf-check-1254-3-34-0.jnk \ +{File "%TEMP%/utf-check-1254-3-34-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1255 utf-check-1255-3-34-1.jnk \ +{File "%TEMP%/utf-check-1255-3-34-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1256 utf-check-1256-3-35-0.jnk \ +{File "%TEMP%/utf-check-1256-3-35-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1257 utf-check-1257-3-35-1.jnk \ +{File "%TEMP%/utf-check-1257-3-35-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1258 utf-check-1258-3-36-0.jnk \ +{File "%TEMP%/utf-check-1258-3-36-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1259 utf-check-1259-3-36-1.jnk \ +{File "%TEMP%/utf-check-1259-3-36-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1260 utf-check-1260-3-37-0.jnk \ +{File "%TEMP%/utf-check-1260-3-37-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1261 utf-check-1261-3-37-1.jnk \ +{File "%TEMP%/utf-check-1261-3-37-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1262 utf-check-1262-3-38-0.jnk \ +{File "%TEMP%/utf-check-1262-3-38-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1263 utf-check-1263-3-38-1.jnk \ +{File "%TEMP%/utf-check-1263-3-38-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1264 utf-check-1264-3-39-0.jnk \ +{File "%TEMP%/utf-check-1264-3-39-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1265 utf-check-1265-3-39-1.jnk \ +{File "%TEMP%/utf-check-1265-3-39-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1266 utf-check-1266-3-40-0.jnk \ +{File "%TEMP%/utf-check-1266-3-40-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1267 utf-check-1267-3-40-1.jnk \ +{File "%TEMP%/utf-check-1267-3-40-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1268 utf-check-1268-3-41-0.jnk \ +{File "%TEMP%/utf-check-1268-3-41-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1269 utf-check-1269-3-41-1.jnk \ +{File "%TEMP%/utf-check-1269-3-41-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1270 utf-check-1270-3-42-0.jnk \ +{File "%TEMP%/utf-check-1270-3-42-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1271 utf-check-1271-3-42-1.jnk \ +{File "%TEMP%/utf-check-1271-3-42-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1272 utf-check-1272-3-43-0.jnk \ +{File "%TEMP%/utf-check-1272-3-43-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1273 utf-check-1273-3-43-1.jnk \ +{File "%TEMP%/utf-check-1273-3-43-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1274 utf-check-1274-3-44-0.jnk \ +{File "%TEMP%/utf-check-1274-3-44-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1275 utf-check-1275-3-44-1.jnk \ +{File "%TEMP%/utf-check-1275-3-44-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1276 utf-check-1276-3-45-0.jnk \ +{File "%TEMP%/utf-check-1276-3-45-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1277 utf-check-1277-3-45-1.jnk \ +{File "%TEMP%/utf-check-1277-3-45-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1278 utf-check-1278-3-46-0.jnk \ +{File "%TEMP%/utf-check-1278-3-46-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1279 utf-check-1279-3-46-1.jnk \ +{File "%TEMP%/utf-check-1279-3-46-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1280 utf-check-1280-3-47-0.jnk \ +{File "%TEMP%/utf-check-1280-3-47-0.jnk" has 16 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1281 utf-check-1281-3-47-1.jnk \ +{File "%TEMP%/utf-check-1281-3-47-1.jnk" has 17 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1282 utf-check-1282-3-48-0.jnk \ +{File "%TEMP%/utf-check-1282-3-48-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1283 utf-check-1283-3-48-1.jnk \ +{File "%TEMP%/utf-check-1283-3-48-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1284 utf-check-1284-3-49-0.jnk \ +{File "%TEMP%/utf-check-1284-3-49-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1285 utf-check-1285-3-49-1.jnk \ +{File "%TEMP%/utf-check-1285-3-49-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1286 utf-check-1286-3-50-0.jnk \ +{File "%TEMP%/utf-check-1286-3-50-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1287 utf-check-1287-3-50-1.jnk \ +{File "%TEMP%/utf-check-1287-3-50-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1288 utf-check-1288-3-51-0.jnk \ +{File "%TEMP%/utf-check-1288-3-51-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1289 utf-check-1289-3-51-1.jnk \ +{File "%TEMP%/utf-check-1289-3-51-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1290 utf-check-1290-3-52-0.jnk \ +{File "%TEMP%/utf-check-1290-3-52-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1291 utf-check-1291-3-52-1.jnk \ +{File "%TEMP%/utf-check-1291-3-52-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1292 utf-check-1292-3-53-0.jnk \ +{File "%TEMP%/utf-check-1292-3-53-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1293 utf-check-1293-3-53-1.jnk \ +{File "%TEMP%/utf-check-1293-3-53-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1294 utf-check-1294-3-54-0.jnk \ +{File "%TEMP%/utf-check-1294-3-54-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1295 utf-check-1295-3-54-1.jnk \ +{File "%TEMP%/utf-check-1295-3-54-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1296 utf-check-1296-3-55-0.jnk \ +{File "%TEMP%/utf-check-1296-3-55-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1297 utf-check-1297-3-55-1.jnk \ +{File "%TEMP%/utf-check-1297-3-55-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1298 utf-check-1298-3-56-0.jnk \ +{File "%TEMP%/utf-check-1298-3-56-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1299 utf-check-1299-3-56-1.jnk \ +{File "%TEMP%/utf-check-1299-3-56-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1300 utf-check-1300-3-57-0.jnk \ +{File "%TEMP%/utf-check-1300-3-57-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1301 utf-check-1301-3-57-1.jnk \ +{File "%TEMP%/utf-check-1301-3-57-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1302 utf-check-1302-3-58-0.jnk \ +{File "%TEMP%/utf-check-1302-3-58-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1303 utf-check-1303-3-58-1.jnk \ +{File "%TEMP%/utf-check-1303-3-58-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1304 utf-check-1304-3-59-0.jnk \ +{File "%TEMP%/utf-check-1304-3-59-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1305 utf-check-1305-3-59-1.jnk \ +{File "%TEMP%/utf-check-1305-3-59-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1306 utf-check-1306-3-60-0.jnk \ +{File "%TEMP%/utf-check-1306-3-60-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1307 utf-check-1307-3-60-1.jnk \ +{File "%TEMP%/utf-check-1307-3-60-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1308 utf-check-1308-3-61-0.jnk \ +{File "%TEMP%/utf-check-1308-3-61-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1309 utf-check-1309-3-61-1.jnk \ +{File "%TEMP%/utf-check-1309-3-61-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1310 utf-check-1310-3-62-0.jnk \ +{File "%TEMP%/utf-check-1310-3-62-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1311 utf-check-1311-3-62-1.jnk \ +{File "%TEMP%/utf-check-1311-3-62-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1312 utf-check-1312-3-63-0.jnk \ +{File "%TEMP%/utf-check-1312-3-63-0.jnk" has 16 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1313 utf-check-1313-3-63-1.jnk \ +{File "%TEMP%/utf-check-1313-3-63-1.jnk" has 17 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1314 utf-check-1314-3-64-0.jnk \ +{File "%TEMP%/utf-check-1314-3-64-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1315 utf-check-1315-3-64-1.jnk \ +{File "%TEMP%/utf-check-1315-3-64-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1316 utf-check-1316-3-65-0.jnk \ +{File "%TEMP%/utf-check-1316-3-65-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1317 utf-check-1317-3-65-1.jnk \ +{File "%TEMP%/utf-check-1317-3-65-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1318 utf-check-1318-3-66-0.jnk \ +{File "%TEMP%/utf-check-1318-3-66-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1319 utf-check-1319-3-66-1.jnk \ +{File "%TEMP%/utf-check-1319-3-66-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1320 utf-check-1320-3-67-0.jnk \ +{File "%TEMP%/utf-check-1320-3-67-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1321 utf-check-1321-3-67-1.jnk \ +{File "%TEMP%/utf-check-1321-3-67-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1322 utf-check-1322-3-68-0.jnk \ +{File "%TEMP%/utf-check-1322-3-68-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1323 utf-check-1323-3-68-1.jnk \ +{File "%TEMP%/utf-check-1323-3-68-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1324 utf-check-1324-3-69-0.jnk \ +{File "%TEMP%/utf-check-1324-3-69-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1325 utf-check-1325-3-69-1.jnk \ +{File "%TEMP%/utf-check-1325-3-69-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1326 utf-check-1326-3-70-0.jnk \ +{File "%TEMP%/utf-check-1326-3-70-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1327 utf-check-1327-3-70-1.jnk \ +{File "%TEMP%/utf-check-1327-3-70-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1328 utf-check-1328-3-71-0.jnk \ +{File "%TEMP%/utf-check-1328-3-71-0.jnk" has 16 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1329 utf-check-1329-3-71-1.jnk \ +{File "%TEMP%/utf-check-1329-3-71-1.jnk" has 17 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1330 utf-check-1330-3-72-0.jnk \ +{File "%TEMP%/utf-check-1330-3-72-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1331 utf-check-1331-3-72-1.jnk \ +{File "%TEMP%/utf-check-1331-3-72-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1332 utf-check-1332-3-73-0.jnk \ +{File "%TEMP%/utf-check-1332-3-73-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1333 utf-check-1333-3-73-1.jnk \ +{File "%TEMP%/utf-check-1333-3-73-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1334 utf-check-1334-3-74-0.jnk \ +{File "%TEMP%/utf-check-1334-3-74-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1335 utf-check-1335-3-74-1.jnk \ +{File "%TEMP%/utf-check-1335-3-74-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1336 utf-check-1336-3-75-0.jnk \ +{File "%TEMP%/utf-check-1336-3-75-0.jnk" has 16 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1337 utf-check-1337-3-75-1.jnk \ +{File "%TEMP%/utf-check-1337-3-75-1.jnk" has 17 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1338 utf-check-1338-3-76-0.jnk \ +{File "%TEMP%/utf-check-1338-3-76-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1339 utf-check-1339-3-76-1.jnk \ +{File "%TEMP%/utf-check-1339-3-76-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1340 utf-check-1340-3-77-0.jnk \ +{File "%TEMP%/utf-check-1340-3-77-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1341 utf-check-1341-3-77-1.jnk \ +{File "%TEMP%/utf-check-1341-3-77-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1342 utf-check-1342-3-78-0.jnk \ +{File "%TEMP%/utf-check-1342-3-78-0.jnk" has 16 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1343 utf-check-1343-3-78-1.jnk \ +{File "%TEMP%/utf-check-1343-3-78-1.jnk" has 17 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1344 utf-check-1344-3-79-0.jnk \ +{File "%TEMP%/utf-check-1344-3-79-0.jnk" has 18 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1345 utf-check-1345-3-79-1.jnk \ +{File "%TEMP%/utf-check-1345-3-79-1.jnk" has 19 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1346 utf-check-1346-3-80-0.jnk \ +{File "%TEMP%/utf-check-1346-3-80-0.jnk" has 16388 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1347 utf-check-1347-3-80-1.jnk \ +{File "%TEMP%/utf-check-1347-3-80-1.jnk" has 16389 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1348 utf-check-1348-3-81-0.jnk \ +{File "%TEMP%/utf-check-1348-3-81-0.jnk" has 16390 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1349 utf-check-1349-3-81-1.jnk \ +{File "%TEMP%/utf-check-1349-3-81-1.jnk" has 16391 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1350 utf-check-1350-3-82-0.jnk \ +{File "%TEMP%/utf-check-1350-3-82-0.jnk" has 16390 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1351 utf-check-1351-3-82-1.jnk \ +{File "%TEMP%/utf-check-1351-3-82-1.jnk" has 16391 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1352 utf-check-1352-3-83-0.jnk \ +{File "%TEMP%/utf-check-1352-3-83-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1353 utf-check-1353-3-83-1.jnk \ +{File "%TEMP%/utf-check-1353-3-83-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1354 utf-check-1354-3-84-0.jnk \ +{File "%TEMP%/utf-check-1354-3-84-0.jnk" has 16394 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1355 utf-check-1355-3-84-1.jnk \ +{File "%TEMP%/utf-check-1355-3-84-1.jnk" has 16395 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1356 utf-check-1356-3-85-0.jnk \ +{File "%TEMP%/utf-check-1356-3-85-0.jnk" has 16396 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1357 utf-check-1357-3-85-1.jnk \ +{File "%TEMP%/utf-check-1357-3-85-1.jnk" has 16397 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1358 utf-check-1358-3-86-0.jnk \ +{File "%TEMP%/utf-check-1358-3-86-0.jnk" has 16396 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1359 utf-check-1359-3-86-1.jnk \ +{File "%TEMP%/utf-check-1359-3-86-1.jnk" has 16397 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1360 utf-check-1360-3-87-0.jnk \ +{File "%TEMP%/utf-check-1360-3-87-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1361 utf-check-1361-3-87-1.jnk \ +{File "%TEMP%/utf-check-1361-3-87-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1362 utf-check-1362-3-88-0.jnk \ +{File "%TEMP%/utf-check-1362-3-88-0.jnk" has 16390 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1363 utf-check-1363-3-88-1.jnk \ +{File "%TEMP%/utf-check-1363-3-88-1.jnk" has 16391 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1364 utf-check-1364-3-89-0.jnk \ +{File "%TEMP%/utf-check-1364-3-89-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1365 utf-check-1365-3-89-1.jnk \ +{File "%TEMP%/utf-check-1365-3-89-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1366 utf-check-1366-3-90-0.jnk \ +{File "%TEMP%/utf-check-1366-3-90-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1367 utf-check-1367-3-90-1.jnk \ +{File "%TEMP%/utf-check-1367-3-90-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1368 utf-check-1368-3-91-0.jnk \ +{File "%TEMP%/utf-check-1368-3-91-0.jnk" has 16394 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1369 utf-check-1369-3-91-1.jnk \ +{File "%TEMP%/utf-check-1369-3-91-1.jnk" has 16395 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1370 utf-check-1370-3-92-0.jnk \ +{File "%TEMP%/utf-check-1370-3-92-0.jnk" has 16396 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1371 utf-check-1371-3-92-1.jnk \ +{File "%TEMP%/utf-check-1371-3-92-1.jnk" has 16397 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1372 utf-check-1372-3-93-0.jnk \ +{File "%TEMP%/utf-check-1372-3-93-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1373 utf-check-1373-3-93-1.jnk \ +{File "%TEMP%/utf-check-1373-3-93-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1374 utf-check-1374-3-94-0.jnk \ +{File "%TEMP%/utf-check-1374-3-94-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1375 utf-check-1375-3-94-1.jnk \ +{File "%TEMP%/utf-check-1375-3-94-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1376 utf-check-1376-3-95-0.jnk \ +{File "%TEMP%/utf-check-1376-3-95-0.jnk" has 16400 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1377 utf-check-1377-3-95-1.jnk \ +{File "%TEMP%/utf-check-1377-3-95-1.jnk" has 16401 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1378 utf-check-1378-3-96-0.jnk \ +{File "%TEMP%/utf-check-1378-3-96-0.jnk" has 16390 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1379 utf-check-1379-3-96-1.jnk \ +{File "%TEMP%/utf-check-1379-3-96-1.jnk" has 16391 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1380 utf-check-1380-3-97-0.jnk \ +{File "%TEMP%/utf-check-1380-3-97-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1381 utf-check-1381-3-97-1.jnk \ +{File "%TEMP%/utf-check-1381-3-97-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1382 utf-check-1382-3-98-0.jnk \ +{File "%TEMP%/utf-check-1382-3-98-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1383 utf-check-1383-3-98-1.jnk \ +{File "%TEMP%/utf-check-1383-3-98-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1384 utf-check-1384-3-99-0.jnk \ +{File "%TEMP%/utf-check-1384-3-99-0.jnk" has 16394 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1385 utf-check-1385-3-99-1.jnk \ +{File "%TEMP%/utf-check-1385-3-99-1.jnk" has 16395 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1386 utf-check-1386-3-100-0.jnk \ +{File "%TEMP%/utf-check-1386-3-100-0.jnk" has 16396 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1387 utf-check-1387-3-100-1.jnk \ +{File "%TEMP%/utf-check-1387-3-100-1.jnk" has 16397 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1388 utf-check-1388-3-101-0.jnk \ +{File "%TEMP%/utf-check-1388-3-101-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1389 utf-check-1389-3-101-1.jnk \ +{File "%TEMP%/utf-check-1389-3-101-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1390 utf-check-1390-3-102-0.jnk \ +{File "%TEMP%/utf-check-1390-3-102-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1391 utf-check-1391-3-102-1.jnk \ +{File "%TEMP%/utf-check-1391-3-102-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1392 utf-check-1392-3-103-0.jnk \ +{File "%TEMP%/utf-check-1392-3-103-0.jnk" has 16400 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1393 utf-check-1393-3-103-1.jnk \ +{File "%TEMP%/utf-check-1393-3-103-1.jnk" has 16401 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1394 utf-check-1394-3-104-0.jnk \ +{File "%TEMP%/utf-check-1394-3-104-0.jnk" has 16392 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1395 utf-check-1395-3-104-1.jnk \ +{File "%TEMP%/utf-check-1395-3-104-1.jnk" has 16393 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1396 utf-check-1396-3-105-0.jnk \ +{File "%TEMP%/utf-check-1396-3-105-0.jnk" has 16394 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1397 utf-check-1397-3-105-1.jnk \ +{File "%TEMP%/utf-check-1397-3-105-1.jnk" has 16395 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1398 utf-check-1398-3-106-0.jnk \ +{File "%TEMP%/utf-check-1398-3-106-0.jnk" has 16394 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1399 utf-check-1399-3-106-1.jnk \ +{File "%TEMP%/utf-check-1399-3-106-1.jnk" has 16395 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1400 utf-check-1400-3-107-0.jnk \ +{File "%TEMP%/utf-check-1400-3-107-0.jnk" has 16396 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1401 utf-check-1401-3-107-1.jnk \ +{File "%TEMP%/utf-check-1401-3-107-1.jnk" has 16397 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1402 utf-check-1402-3-108-0.jnk \ +{File "%TEMP%/utf-check-1402-3-108-0.jnk" has 16398 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1403 utf-check-1403-3-108-1.jnk \ +{File "%TEMP%/utf-check-1403-3-108-1.jnk" has 16399 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1404 utf-check-1404-3-109-0.jnk \ +{File "%TEMP%/utf-check-1404-3-109-0.jnk" has 16400 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1405 utf-check-1405-3-109-1.jnk \ +{File "%TEMP%/utf-check-1405-3-109-1.jnk" has 16401 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1406 utf-check-1406-3-110-0.jnk \ +{File "%TEMP%/utf-check-1406-3-110-0.jnk" has 16400 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1407 utf-check-1407-3-110-1.jnk \ +{File "%TEMP%/utf-check-1407-3-110-1.jnk" has 16401 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1408 utf-check-1408-3-111-0.jnk \ +{File "%TEMP%/utf-check-1408-3-111-0.jnk" has 16402 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: yes +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1409 utf-check-1409-3-111-1.jnk \ +{File "%TEMP%/utf-check-1409-3-111-1.jnk" has 16403 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: no Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no @@ -17947,1024 +21271,2192 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1410 utf-check-1410-3-112-0.jnk \ +{File "%TEMP%/utf-check-1410-3-112-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1411 utf-check-1411-3-112-1.jnk \ +{File "%TEMP%/utf-check-1411-3-112-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1412 utf-check-1412-3-113-0.jnk \ +{File "%TEMP%/utf-check-1412-3-113-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1413 utf-check-1413-3-113-1.jnk \ +{File "%TEMP%/utf-check-1413-3-113-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1414 utf-check-1414-3-114-0.jnk \ +{File "%TEMP%/utf-check-1414-3-114-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1415 utf-check-1415-3-114-1.jnk \ +{File "%TEMP%/utf-check-1415-3-114-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1416 utf-check-1416-3-115-0.jnk \ +{File "%TEMP%/utf-check-1416-3-115-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1417 utf-check-1417-3-115-1.jnk \ +{File "%TEMP%/utf-check-1417-3-115-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1418 utf-check-1418-3-116-0.jnk \ +{File "%TEMP%/utf-check-1418-3-116-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1419 utf-check-1419-3-116-1.jnk \ +{File "%TEMP%/utf-check-1419-3-116-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1420 utf-check-1420-3-117-0.jnk \ +{File "%TEMP%/utf-check-1420-3-117-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1421 utf-check-1421-3-117-1.jnk \ +{File "%TEMP%/utf-check-1421-3-117-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1422 utf-check-1422-3-118-0.jnk \ +{File "%TEMP%/utf-check-1422-3-118-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1423 utf-check-1423-3-118-1.jnk \ +{File "%TEMP%/utf-check-1423-3-118-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1424 utf-check-1424-3-119-0.jnk \ +{File "%TEMP%/utf-check-1424-3-119-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1425 utf-check-1425-3-119-1.jnk \ +{File "%TEMP%/utf-check-1425-3-119-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1426 utf-check-1426-3-120-0.jnk \ +{File "%TEMP%/utf-check-1426-3-120-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1427 utf-check-1427-3-120-1.jnk \ +{File "%TEMP%/utf-check-1427-3-120-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1428 utf-check-1428-3-121-0.jnk \ +{File "%TEMP%/utf-check-1428-3-121-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1429 utf-check-1429-3-121-1.jnk \ +{File "%TEMP%/utf-check-1429-3-121-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1430 utf-check-1430-3-122-0.jnk \ +{File "%TEMP%/utf-check-1430-3-122-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1431 utf-check-1431-3-122-1.jnk \ +{File "%TEMP%/utf-check-1431-3-122-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1432 utf-check-1432-3-123-0.jnk \ +{File "%TEMP%/utf-check-1432-3-123-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1433 utf-check-1433-3-123-1.jnk \ +{File "%TEMP%/utf-check-1433-3-123-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1434 utf-check-1434-3-124-0.jnk \ +{File "%TEMP%/utf-check-1434-3-124-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1435 utf-check-1435-3-124-1.jnk \ +{File "%TEMP%/utf-check-1435-3-124-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1436 utf-check-1436-3-125-0.jnk \ +{File "%TEMP%/utf-check-1436-3-125-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1437 utf-check-1437-3-125-1.jnk \ +{File "%TEMP%/utf-check-1437-3-125-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1438 utf-check-1438-3-126-0.jnk \ +{File "%TEMP%/utf-check-1438-3-126-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1439 utf-check-1439-3-126-1.jnk \ +{File "%TEMP%/utf-check-1439-3-126-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1440 utf-check-1440-3-127-0.jnk \ +{File "%TEMP%/utf-check-1440-3-127-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1441 utf-check-1441-3-127-1.jnk \ +{File "%TEMP%/utf-check-1441-3-127-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1442 utf-check-1442-3-128-0.jnk \ +{File "%TEMP%/utf-check-1442-3-128-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1443 utf-check-1443-3-128-1.jnk \ +{File "%TEMP%/utf-check-1443-3-128-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1444 utf-check-1444-3-129-0.jnk \ +{File "%TEMP%/utf-check-1444-3-129-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1445 utf-check-1445-3-129-1.jnk \ +{File "%TEMP%/utf-check-1445-3-129-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1446 utf-check-1446-3-130-0.jnk \ +{File "%TEMP%/utf-check-1446-3-130-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1447 utf-check-1447-3-130-1.jnk \ +{File "%TEMP%/utf-check-1447-3-130-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1448 utf-check-1448-3-131-0.jnk \ +{File "%TEMP%/utf-check-1448-3-131-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1449 utf-check-1449-3-131-1.jnk \ +{File "%TEMP%/utf-check-1449-3-131-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1450 utf-check-1450-3-132-0.jnk \ +{File "%TEMP%/utf-check-1450-3-132-0.jnk" has 4 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1451 utf-check-1451-3-132-1.jnk \ +{File "%TEMP%/utf-check-1451-3-132-1.jnk" has 5 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1452 utf-check-1452-3-133-0.jnk \ +{File "%TEMP%/utf-check-1452-3-133-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1453 utf-check-1453-3-133-1.jnk \ +{File "%TEMP%/utf-check-1453-3-133-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1454 utf-check-1454-3-134-0.jnk \ +{File "%TEMP%/utf-check-1454-3-134-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1455 utf-check-1455-3-134-1.jnk \ +{File "%TEMP%/utf-check-1455-3-134-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1456 utf-check-1456-3-135-0.jnk \ +{File "%TEMP%/utf-check-1456-3-135-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1457 utf-check-1457-3-135-1.jnk \ +{File "%TEMP%/utf-check-1457-3-135-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1458 utf-check-1458-3-136-0.jnk \ +{File "%TEMP%/utf-check-1458-3-136-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1459 utf-check-1459-3-136-1.jnk \ +{File "%TEMP%/utf-check-1459-3-136-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1460 utf-check-1460-3-137-0.jnk \ +{File "%TEMP%/utf-check-1460-3-137-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1461 utf-check-1461-3-137-1.jnk \ +{File "%TEMP%/utf-check-1461-3-137-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1462 utf-check-1462-3-138-0.jnk \ +{File "%TEMP%/utf-check-1462-3-138-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1463 utf-check-1463-3-138-1.jnk \ +{File "%TEMP%/utf-check-1463-3-138-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1464 utf-check-1464-3-139-0.jnk \ +{File "%TEMP%/utf-check-1464-3-139-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1465 utf-check-1465-3-139-1.jnk \ +{File "%TEMP%/utf-check-1465-3-139-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1466 utf-check-1466-3-140-0.jnk \ +{File "%TEMP%/utf-check-1466-3-140-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1467 utf-check-1467-3-140-1.jnk \ +{File "%TEMP%/utf-check-1467-3-140-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1468 utf-check-1468-3-141-0.jnk \ +{File "%TEMP%/utf-check-1468-3-141-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1469 utf-check-1469-3-141-1.jnk \ +{File "%TEMP%/utf-check-1469-3-141-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1470 utf-check-1470-3-142-0.jnk \ +{File "%TEMP%/utf-check-1470-3-142-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1471 utf-check-1471-3-142-1.jnk \ +{File "%TEMP%/utf-check-1471-3-142-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1472 utf-check-1472-3-143-0.jnk \ +{File "%TEMP%/utf-check-1472-3-143-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1473 utf-check-1473-3-143-1.jnk \ +{File "%TEMP%/utf-check-1473-3-143-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1474 utf-check-1474-3-144-0.jnk \ +{File "%TEMP%/utf-check-1474-3-144-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1475 utf-check-1475-3-144-1.jnk \ +{File "%TEMP%/utf-check-1475-3-144-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1476 utf-check-1476-3-145-0.jnk \ +{File "%TEMP%/utf-check-1476-3-145-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1477 utf-check-1477-3-145-1.jnk \ +{File "%TEMP%/utf-check-1477-3-145-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1478 utf-check-1478-3-146-0.jnk \ +{File "%TEMP%/utf-check-1478-3-146-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1479 utf-check-1479-3-146-1.jnk \ +{File "%TEMP%/utf-check-1479-3-146-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1480 utf-check-1480-3-147-0.jnk \ +{File "%TEMP%/utf-check-1480-3-147-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1481 utf-check-1481-3-147-1.jnk \ +{File "%TEMP%/utf-check-1481-3-147-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1482 utf-check-1482-3-148-0.jnk \ +{File "%TEMP%/utf-check-1482-3-148-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1483 utf-check-1483-3-148-1.jnk \ +{File "%TEMP%/utf-check-1483-3-148-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1484 utf-check-1484-3-149-0.jnk \ +{File "%TEMP%/utf-check-1484-3-149-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1485 utf-check-1485-3-149-1.jnk \ +{File "%TEMP%/utf-check-1485-3-149-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1486 utf-check-1486-3-150-0.jnk \ +{File "%TEMP%/utf-check-1486-3-150-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1487 utf-check-1487-3-150-1.jnk \ +{File "%TEMP%/utf-check-1487-3-150-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1488 utf-check-1488-3-151-0.jnk \ +{File "%TEMP%/utf-check-1488-3-151-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1489 utf-check-1489-3-151-1.jnk \ +{File "%TEMP%/utf-check-1489-3-151-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1490 utf-check-1490-3-152-0.jnk \ +{File "%TEMP%/utf-check-1490-3-152-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1491 utf-check-1491-3-152-1.jnk \ +{File "%TEMP%/utf-check-1491-3-152-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1492 utf-check-1492-3-153-0.jnk \ +{File "%TEMP%/utf-check-1492-3-153-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1493 utf-check-1493-3-153-1.jnk \ +{File "%TEMP%/utf-check-1493-3-153-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1494 utf-check-1494-3-154-0.jnk \ +{File "%TEMP%/utf-check-1494-3-154-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1495 utf-check-1495-3-154-1.jnk \ +{File "%TEMP%/utf-check-1495-3-154-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1496 utf-check-1496-3-155-0.jnk \ +{File "%TEMP%/utf-check-1496-3-155-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1497 utf-check-1497-3-155-1.jnk \ +{File "%TEMP%/utf-check-1497-3-155-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1498 utf-check-1498-3-156-0.jnk \ +{File "%TEMP%/utf-check-1498-3-156-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1499 utf-check-1499-3-156-1.jnk \ +{File "%TEMP%/utf-check-1499-3-156-1.jnk" has 15 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1500 utf-check-1500-3-157-0.jnk \ +{File "%TEMP%/utf-check-1500-3-157-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1501 utf-check-1501-3-157-1.jnk \ +{File "%TEMP%/utf-check-1501-3-157-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1502 utf-check-1502-3-158-0.jnk \ +{File "%TEMP%/utf-check-1502-3-158-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1503 utf-check-1503-3-158-1.jnk \ +{File "%TEMP%/utf-check-1503-3-158-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1504 utf-check-1504-3-159-0.jnk \ +{File "%TEMP%/utf-check-1504-3-159-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1505 utf-check-1505-3-159-1.jnk \ +{File "%TEMP%/utf-check-1505-3-159-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1506 utf-check-1506-3-160-0.jnk \ +{File "%TEMP%/utf-check-1506-3-160-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1507 utf-check-1507-3-160-1.jnk \ +{File "%TEMP%/utf-check-1507-3-160-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1508 utf-check-1508-3-161-0.jnk \ +{File "%TEMP%/utf-check-1508-3-161-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1509 utf-check-1509-3-161-1.jnk \ +{File "%TEMP%/utf-check-1509-3-161-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1510 utf-check-1510-3-162-0.jnk \ +{File "%TEMP%/utf-check-1510-3-162-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1511 utf-check-1511-3-162-1.jnk \ +{File "%TEMP%/utf-check-1511-3-162-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1512 utf-check-1512-3-163-0.jnk \ +{File "%TEMP%/utf-check-1512-3-163-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1513 utf-check-1513-3-163-1.jnk \ +{File "%TEMP%/utf-check-1513-3-163-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1514 utf-check-1514-3-164-0.jnk \ +{File "%TEMP%/utf-check-1514-3-164-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1515 utf-check-1515-3-164-1.jnk \ +{File "%TEMP%/utf-check-1515-3-164-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1516 utf-check-1516-3-165-0.jnk \ +{File "%TEMP%/utf-check-1516-3-165-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1517 utf-check-1517-3-165-1.jnk \ +{File "%TEMP%/utf-check-1517-3-165-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1518 utf-check-1518-3-166-0.jnk \ +{File "%TEMP%/utf-check-1518-3-166-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1519 utf-check-1519-3-166-1.jnk \ +{File "%TEMP%/utf-check-1519-3-166-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1520 utf-check-1520-3-167-0.jnk \ +{File "%TEMP%/utf-check-1520-3-167-0.jnk" has 8 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1521 utf-check-1521-3-167-1.jnk \ +{File "%TEMP%/utf-check-1521-3-167-1.jnk" has 9 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1522 utf-check-1522-3-168-0.jnk \ +{File "%TEMP%/utf-check-1522-3-168-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1523 utf-check-1523-3-168-1.jnk \ +{File "%TEMP%/utf-check-1523-3-168-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1524 utf-check-1524-3-169-0.jnk \ +{File "%TEMP%/utf-check-1524-3-169-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1525 utf-check-1525-3-169-1.jnk \ +{File "%TEMP%/utf-check-1525-3-169-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1526 utf-check-1526-3-170-0.jnk \ +{File "%TEMP%/utf-check-1526-3-170-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1527 utf-check-1527-3-170-1.jnk \ +{File "%TEMP%/utf-check-1527-3-170-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1528 utf-check-1528-3-171-0.jnk \ +{File "%TEMP%/utf-check-1528-3-171-0.jnk" has 10 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1529 utf-check-1529-3-171-1.jnk \ +{File "%TEMP%/utf-check-1529-3-171-1.jnk" has 11 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1530 utf-check-1530-3-172-0.jnk \ +{File "%TEMP%/utf-check-1530-3-172-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1531 utf-check-1531-3-172-1.jnk \ +{File "%TEMP%/utf-check-1531-3-172-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1532 utf-check-1532-3-173-0.jnk \ +{File "%TEMP%/utf-check-1532-3-173-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1533 utf-check-1533-3-173-1.jnk \ +{File "%TEMP%/utf-check-1533-3-173-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1534 utf-check-1534-3-174-0.jnk \ +{File "%TEMP%/utf-check-1534-3-174-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1535 utf-check-1535-3-174-1.jnk \ +{File "%TEMP%/utf-check-1535-3-174-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1536 utf-check-1536-3-175-0.jnk \ +{File "%TEMP%/utf-check-1536-3-175-0.jnk" has 12 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1537 utf-check-1537-3-175-1.jnk \ +{File "%TEMP%/utf-check-1537-3-175-1.jnk" has 13 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1538 utf-check-1538-3-176-0.jnk \ +{File "%TEMP%/utf-check-1538-3-176-0.jnk" has 14 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 1203 utf-check-1203-3-80-1.jnk \ -{File "%TEMP%/utf-check-1203-3-80-1.jnk" has 16389 bytes. +utf-check 1539 utf-check-1539-3-176-1.jnk \ +{File "%TEMP%/utf-check-1539-3-176-1.jnk" has 15 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: no Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 1204 utf-check-1204-3-81-0.jnk \ -{File "%TEMP%/utf-check-1204-3-81-0.jnk" has 16390 bytes. +utf-check 1540 utf-check-1540-3-177-0.jnk \ +{File "%TEMP%/utf-check-1540-3-177-0.jnk" has 12 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: no Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -utf-check 1205 utf-check-1205-3-81-1.jnk \ -{File "%TEMP%/utf-check-1205-3-81-1.jnk" has 16391 bytes. +utf-check 1541 utf-check-1541-3-177-1.jnk \ +{File "%TEMP%/utf-check-1541-3-177-1.jnk" has 13 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: no Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1206 utf-check-1206-3-82-0.jnk \ -{File "%TEMP%/utf-check-1206-3-82-0.jnk" has 16390 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1207 utf-check-1207-3-82-1.jnk \ -{File "%TEMP%/utf-check-1207-3-82-1.jnk" has 16391 bytes. +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1542 utf-check-1542-3-178-0.jnk \ +{File "%TEMP%/utf-check-1542-3-178-0.jnk" has 12 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: no Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1208 utf-check-1208-3-83-0.jnk \ -{File "%TEMP%/utf-check-1208-3-83-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1209 utf-check-1209-3-83-1.jnk \ -{File "%TEMP%/utf-check-1209-3-83-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1210 utf-check-1210-3-84-0.jnk \ -{File "%TEMP%/utf-check-1210-3-84-0.jnk" has 16394 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1211 utf-check-1211-3-84-1.jnk \ -{File "%TEMP%/utf-check-1211-3-84-1.jnk" has 16395 bytes. +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1543 utf-check-1543-3-178-1.jnk \ +{File "%TEMP%/utf-check-1543-3-178-1.jnk" has 13 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: no Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1212 utf-check-1212-3-85-0.jnk \ -{File "%TEMP%/utf-check-1212-3-85-0.jnk" has 16396 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1213 utf-check-1213-3-85-1.jnk \ -{File "%TEMP%/utf-check-1213-3-85-1.jnk" has 16397 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1214 utf-check-1214-3-86-0.jnk \ -{File "%TEMP%/utf-check-1214-3-86-0.jnk" has 16396 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1215 utf-check-1215-3-86-1.jnk \ -{File "%TEMP%/utf-check-1215-3-86-1.jnk" has 16397 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1216 utf-check-1216-3-87-0.jnk \ -{File "%TEMP%/utf-check-1216-3-87-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1217 utf-check-1217-3-87-1.jnk \ -{File "%TEMP%/utf-check-1217-3-87-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1218 utf-check-1218-3-88-0.jnk \ -{File "%TEMP%/utf-check-1218-3-88-0.jnk" has 16390 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1219 utf-check-1219-3-88-1.jnk \ -{File "%TEMP%/utf-check-1219-3-88-1.jnk" has 16391 bytes. +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1544 utf-check-1544-3-179-0.jnk \ +{File "%TEMP%/utf-check-1544-3-179-0.jnk" has 12 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: no Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1220 utf-check-1220-3-89-0.jnk \ -{File "%TEMP%/utf-check-1220-3-89-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1221 utf-check-1221-3-89-1.jnk \ -{File "%TEMP%/utf-check-1221-3-89-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1222 utf-check-1222-3-90-0.jnk \ -{File "%TEMP%/utf-check-1222-3-90-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1223 utf-check-1223-3-90-1.jnk \ -{File "%TEMP%/utf-check-1223-3-90-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1224 utf-check-1224-3-91-0.jnk \ -{File "%TEMP%/utf-check-1224-3-91-0.jnk" has 16394 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1225 utf-check-1225-3-91-1.jnk \ -{File "%TEMP%/utf-check-1225-3-91-1.jnk" has 16395 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1226 utf-check-1226-3-92-0.jnk \ -{File "%TEMP%/utf-check-1226-3-92-0.jnk" has 16396 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1227 utf-check-1227-3-92-1.jnk \ -{File "%TEMP%/utf-check-1227-3-92-1.jnk" has 16397 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1228 utf-check-1228-3-93-0.jnk \ -{File "%TEMP%/utf-check-1228-3-93-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1229 utf-check-1229-3-93-1.jnk \ -{File "%TEMP%/utf-check-1229-3-93-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1230 utf-check-1230-3-94-0.jnk \ -{File "%TEMP%/utf-check-1230-3-94-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1231 utf-check-1231-3-94-1.jnk \ -{File "%TEMP%/utf-check-1231-3-94-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1232 utf-check-1232-3-95-0.jnk \ -{File "%TEMP%/utf-check-1232-3-95-0.jnk" has 16400 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1233 utf-check-1233-3-95-1.jnk \ -{File "%TEMP%/utf-check-1233-3-95-1.jnk" has 16401 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1234 utf-check-1234-3-96-0.jnk \ -{File "%TEMP%/utf-check-1234-3-96-0.jnk" has 16390 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1235 utf-check-1235-3-96-1.jnk \ -{File "%TEMP%/utf-check-1235-3-96-1.jnk" has 16391 bytes. +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1545 utf-check-1545-3-179-1.jnk \ +{File "%TEMP%/utf-check-1545-3-179-1.jnk" has 13 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: no Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1236 utf-check-1236-3-97-0.jnk \ -{File "%TEMP%/utf-check-1236-3-97-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1237 utf-check-1237-3-97-1.jnk \ -{File "%TEMP%/utf-check-1237-3-97-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1238 utf-check-1238-3-98-0.jnk \ -{File "%TEMP%/utf-check-1238-3-98-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1239 utf-check-1239-3-98-1.jnk \ -{File "%TEMP%/utf-check-1239-3-98-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1240 utf-check-1240-3-99-0.jnk \ -{File "%TEMP%/utf-check-1240-3-99-0.jnk" has 16394 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1241 utf-check-1241-3-99-1.jnk \ -{File "%TEMP%/utf-check-1241-3-99-1.jnk" has 16395 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1242 utf-check-1242-3-100-0.jnk \ -{File "%TEMP%/utf-check-1242-3-100-0.jnk" has 16396 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1243 utf-check-1243-3-100-1.jnk \ -{File "%TEMP%/utf-check-1243-3-100-1.jnk" has 16397 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1244 utf-check-1244-3-101-0.jnk \ -{File "%TEMP%/utf-check-1244-3-101-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1245 utf-check-1245-3-101-1.jnk \ -{File "%TEMP%/utf-check-1245-3-101-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1246 utf-check-1246-3-102-0.jnk \ -{File "%TEMP%/utf-check-1246-3-102-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1247 utf-check-1247-3-102-1.jnk \ -{File "%TEMP%/utf-check-1247-3-102-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1248 utf-check-1248-3-103-0.jnk \ -{File "%TEMP%/utf-check-1248-3-103-0.jnk" has 16400 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1249 utf-check-1249-3-103-1.jnk \ -{File "%TEMP%/utf-check-1249-3-103-1.jnk" has 16401 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1250 utf-check-1250-3-104-0.jnk \ -{File "%TEMP%/utf-check-1250-3-104-0.jnk" has 16392 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1251 utf-check-1251-3-104-1.jnk \ -{File "%TEMP%/utf-check-1251-3-104-1.jnk" has 16393 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1252 utf-check-1252-3-105-0.jnk \ -{File "%TEMP%/utf-check-1252-3-105-0.jnk" has 16394 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1253 utf-check-1253-3-105-1.jnk \ -{File "%TEMP%/utf-check-1253-3-105-1.jnk" has 16395 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1254 utf-check-1254-3-106-0.jnk \ -{File "%TEMP%/utf-check-1254-3-106-0.jnk" has 16394 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1255 utf-check-1255-3-106-1.jnk \ -{File "%TEMP%/utf-check-1255-3-106-1.jnk" has 16395 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1256 utf-check-1256-3-107-0.jnk \ -{File "%TEMP%/utf-check-1256-3-107-0.jnk" has 16396 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1257 utf-check-1257-3-107-1.jnk \ -{File "%TEMP%/utf-check-1257-3-107-1.jnk" has 16397 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1258 utf-check-1258-3-108-0.jnk \ -{File "%TEMP%/utf-check-1258-3-108-0.jnk" has 16398 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1259 utf-check-1259-3-108-1.jnk \ -{File "%TEMP%/utf-check-1259-3-108-1.jnk" has 16399 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1260 utf-check-1260-3-109-0.jnk \ -{File "%TEMP%/utf-check-1260-3-109-0.jnk" has 16400 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1261 utf-check-1261-3-109-1.jnk \ -{File "%TEMP%/utf-check-1261-3-109-1.jnk" has 16401 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1262 utf-check-1262-3-110-0.jnk \ -{File "%TEMP%/utf-check-1262-3-110-0.jnk" has 16400 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1263 utf-check-1263-3-110-1.jnk \ -{File "%TEMP%/utf-check-1263-3-110-1.jnk" has 16401 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1264 utf-check-1264-3-111-0.jnk \ -{File "%TEMP%/utf-check-1264-3-111-0.jnk" has 16402 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1265 utf-check-1265-3-111-1.jnk \ -{File "%TEMP%/utf-check-1265-3-111-1.jnk" has 16403 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: yes -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1266 utf-check-1266-3-112-0.jnk \ -{File "%TEMP%/utf-check-1266-3-112-0.jnk" has 6 bytes. +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1546 utf-check-1546-3-180-0.jnk \ +{File "%TEMP%/utf-check-1546-3-180-0.jnk" has 14 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: no Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no @@ -18971,208 +23463,16 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1267 utf-check-1267-3-112-1.jnk \ -{File "%TEMP%/utf-check-1267-3-112-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1268 utf-check-1268-3-113-0.jnk \ -{File "%TEMP%/utf-check-1268-3-113-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1269 utf-check-1269-3-113-1.jnk \ -{File "%TEMP%/utf-check-1269-3-113-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1270 utf-check-1270-3-114-0.jnk \ -{File "%TEMP%/utf-check-1270-3-114-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1271 utf-check-1271-3-114-1.jnk \ -{File "%TEMP%/utf-check-1271-3-114-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1272 utf-check-1272-3-115-0.jnk \ -{File "%TEMP%/utf-check-1272-3-115-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1273 utf-check-1273-3-115-1.jnk \ -{File "%TEMP%/utf-check-1273-3-115-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1274 utf-check-1274-3-116-0.jnk \ -{File "%TEMP%/utf-check-1274-3-116-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1275 utf-check-1275-3-116-1.jnk \ -{File "%TEMP%/utf-check-1275-3-116-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1276 utf-check-1276-3-117-0.jnk \ -{File "%TEMP%/utf-check-1276-3-117-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1277 utf-check-1277-3-117-1.jnk \ -{File "%TEMP%/utf-check-1277-3-117-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1278 utf-check-1278-3-118-0.jnk \ -{File "%TEMP%/utf-check-1278-3-118-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1279 utf-check-1279-3-118-1.jnk \ -{File "%TEMP%/utf-check-1279-3-118-1.jnk" has 9 bytes. +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1547 utf-check-1547-3-180-1.jnk \ +{File "%TEMP%/utf-check-1547-3-180-1.jnk" has 15 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: no Looks like UTF-8: no Has flag LOOK_NUL: yes Has flag LOOK_CR: no @@ -19179,1228 +23479,12 @@ Has flag LOOK_LONE_CR: no Has flag LOOK_LF: no Has flag LOOK_LONE_LF: no Has flag LOOK_CRLF: no Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1280 utf-check-1280-3-119-0.jnk \ -{File "%TEMP%/utf-check-1280-3-119-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1281 utf-check-1281-3-119-1.jnk \ -{File "%TEMP%/utf-check-1281-3-119-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1282 utf-check-1282-3-120-0.jnk \ -{File "%TEMP%/utf-check-1282-3-120-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1283 utf-check-1283-3-120-1.jnk \ -{File "%TEMP%/utf-check-1283-3-120-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1284 utf-check-1284-3-121-0.jnk \ -{File "%TEMP%/utf-check-1284-3-121-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1285 utf-check-1285-3-121-1.jnk \ -{File "%TEMP%/utf-check-1285-3-121-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1286 utf-check-1286-3-122-0.jnk \ -{File "%TEMP%/utf-check-1286-3-122-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1287 utf-check-1287-3-122-1.jnk \ -{File "%TEMP%/utf-check-1287-3-122-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1288 utf-check-1288-3-123-0.jnk \ -{File "%TEMP%/utf-check-1288-3-123-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1289 utf-check-1289-3-123-1.jnk \ -{File "%TEMP%/utf-check-1289-3-123-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1290 utf-check-1290-3-124-0.jnk \ -{File "%TEMP%/utf-check-1290-3-124-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1291 utf-check-1291-3-124-1.jnk \ -{File "%TEMP%/utf-check-1291-3-124-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1292 utf-check-1292-3-125-0.jnk \ -{File "%TEMP%/utf-check-1292-3-125-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1293 utf-check-1293-3-125-1.jnk \ -{File "%TEMP%/utf-check-1293-3-125-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1294 utf-check-1294-3-126-0.jnk \ -{File "%TEMP%/utf-check-1294-3-126-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1295 utf-check-1295-3-126-1.jnk \ -{File "%TEMP%/utf-check-1295-3-126-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1296 utf-check-1296-3-127-0.jnk \ -{File "%TEMP%/utf-check-1296-3-127-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1297 utf-check-1297-3-127-1.jnk \ -{File "%TEMP%/utf-check-1297-3-127-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1298 utf-check-1298-3-128-0.jnk \ -{File "%TEMP%/utf-check-1298-3-128-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1299 utf-check-1299-3-128-1.jnk \ -{File "%TEMP%/utf-check-1299-3-128-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1300 utf-check-1300-3-129-0.jnk \ -{File "%TEMP%/utf-check-1300-3-129-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1301 utf-check-1301-3-129-1.jnk \ -{File "%TEMP%/utf-check-1301-3-129-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1302 utf-check-1302-3-130-0.jnk \ -{File "%TEMP%/utf-check-1302-3-130-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1303 utf-check-1303-3-130-1.jnk \ -{File "%TEMP%/utf-check-1303-3-130-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1304 utf-check-1304-3-131-0.jnk \ -{File "%TEMP%/utf-check-1304-3-131-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1305 utf-check-1305-3-131-1.jnk \ -{File "%TEMP%/utf-check-1305-3-131-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1306 utf-check-1306-3-132-0.jnk \ -{File "%TEMP%/utf-check-1306-3-132-0.jnk" has 4 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1307 utf-check-1307-3-132-1.jnk \ -{File "%TEMP%/utf-check-1307-3-132-1.jnk" has 5 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1308 utf-check-1308-3-133-0.jnk \ -{File "%TEMP%/utf-check-1308-3-133-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1309 utf-check-1309-3-133-1.jnk \ -{File "%TEMP%/utf-check-1309-3-133-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1310 utf-check-1310-3-134-0.jnk \ -{File "%TEMP%/utf-check-1310-3-134-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1311 utf-check-1311-3-134-1.jnk \ -{File "%TEMP%/utf-check-1311-3-134-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1312 utf-check-1312-3-135-0.jnk \ -{File "%TEMP%/utf-check-1312-3-135-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1313 utf-check-1313-3-135-1.jnk \ -{File "%TEMP%/utf-check-1313-3-135-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1314 utf-check-1314-3-136-0.jnk \ -{File "%TEMP%/utf-check-1314-3-136-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1315 utf-check-1315-3-136-1.jnk \ -{File "%TEMP%/utf-check-1315-3-136-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1316 utf-check-1316-3-137-0.jnk \ -{File "%TEMP%/utf-check-1316-3-137-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1317 utf-check-1317-3-137-1.jnk \ -{File "%TEMP%/utf-check-1317-3-137-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1318 utf-check-1318-3-138-0.jnk \ -{File "%TEMP%/utf-check-1318-3-138-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1319 utf-check-1319-3-138-1.jnk \ -{File "%TEMP%/utf-check-1319-3-138-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1320 utf-check-1320-3-139-0.jnk \ -{File "%TEMP%/utf-check-1320-3-139-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1321 utf-check-1321-3-139-1.jnk \ -{File "%TEMP%/utf-check-1321-3-139-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1322 utf-check-1322-3-140-0.jnk \ -{File "%TEMP%/utf-check-1322-3-140-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1323 utf-check-1323-3-140-1.jnk \ -{File "%TEMP%/utf-check-1323-3-140-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1324 utf-check-1324-3-141-0.jnk \ -{File "%TEMP%/utf-check-1324-3-141-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1325 utf-check-1325-3-141-1.jnk \ -{File "%TEMP%/utf-check-1325-3-141-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1326 utf-check-1326-3-142-0.jnk \ -{File "%TEMP%/utf-check-1326-3-142-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1327 utf-check-1327-3-142-1.jnk \ -{File "%TEMP%/utf-check-1327-3-142-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1328 utf-check-1328-3-143-0.jnk \ -{File "%TEMP%/utf-check-1328-3-143-0.jnk" has 6 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1329 utf-check-1329-3-143-1.jnk \ -{File "%TEMP%/utf-check-1329-3-143-1.jnk" has 7 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1330 utf-check-1330-3-144-0.jnk \ -{File "%TEMP%/utf-check-1330-3-144-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1331 utf-check-1331-3-144-1.jnk \ -{File "%TEMP%/utf-check-1331-3-144-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1332 utf-check-1332-3-145-0.jnk \ -{File "%TEMP%/utf-check-1332-3-145-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1333 utf-check-1333-3-145-1.jnk \ -{File "%TEMP%/utf-check-1333-3-145-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1334 utf-check-1334-3-146-0.jnk \ -{File "%TEMP%/utf-check-1334-3-146-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1335 utf-check-1335-3-146-1.jnk \ -{File "%TEMP%/utf-check-1335-3-146-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1336 utf-check-1336-3-147-0.jnk \ -{File "%TEMP%/utf-check-1336-3-147-0.jnk" has 8 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1337 utf-check-1337-3-147-1.jnk \ -{File "%TEMP%/utf-check-1337-3-147-1.jnk" has 9 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1338 utf-check-1338-3-148-0.jnk \ -{File "%TEMP%/utf-check-1338-3-148-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1339 utf-check-1339-3-148-1.jnk \ -{File "%TEMP%/utf-check-1339-3-148-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1340 utf-check-1340-3-149-0.jnk \ -{File "%TEMP%/utf-check-1340-3-149-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1341 utf-check-1341-3-149-1.jnk \ -{File "%TEMP%/utf-check-1341-3-149-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1342 utf-check-1342-3-150-0.jnk \ -{File "%TEMP%/utf-check-1342-3-150-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1343 utf-check-1343-3-150-1.jnk \ -{File "%TEMP%/utf-check-1343-3-150-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1344 utf-check-1344-3-151-0.jnk \ -{File "%TEMP%/utf-check-1344-3-151-0.jnk" has 10 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1345 utf-check-1345-3-151-1.jnk \ -{File "%TEMP%/utf-check-1345-3-151-1.jnk" has 11 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1346 utf-check-1346-3-152-0.jnk \ -{File "%TEMP%/utf-check-1346-3-152-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1347 utf-check-1347-3-152-1.jnk \ -{File "%TEMP%/utf-check-1347-3-152-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1348 utf-check-1348-3-153-0.jnk \ -{File "%TEMP%/utf-check-1348-3-153-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1349 utf-check-1349-3-153-1.jnk \ -{File "%TEMP%/utf-check-1349-3-153-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1350 utf-check-1350-3-154-0.jnk \ -{File "%TEMP%/utf-check-1350-3-154-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1351 utf-check-1351-3-154-1.jnk \ -{File "%TEMP%/utf-check-1351-3-154-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1352 utf-check-1352-3-155-0.jnk \ -{File "%TEMP%/utf-check-1352-3-155-0.jnk" has 12 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1353 utf-check-1353-3-155-1.jnk \ -{File "%TEMP%/utf-check-1353-3-155-1.jnk" has 13 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1354 utf-check-1354-3-156-0.jnk \ -{File "%TEMP%/utf-check-1354-3-156-0.jnk" has 14 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no -Has flag LOOK_ODD: no -Has flag LOOK_SHORT: no} - -utf-check 1355 utf-check-1355-3-156-1.jnk \ -{File "%TEMP%/utf-check-1355-3-156-1.jnk" has 15 bytes. -Starts with UTF-8 BOM: no -Starts with UTF-16 BOM: no -Looks like UTF-8: no -Has flag LOOK_NUL: yes -Has flag LOOK_CR: no -Has flag LOOK_LONE_CR: no -Has flag LOOK_LF: no -Has flag LOOK_LONE_LF: no -Has flag LOOK_CRLF: no -Has flag LOOK_LONG: no -Has flag LOOK_INVALID: no +Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} ############################ END GENERATED SECTION ############################ deleteTestFiles $tempPath 100 ADDED test/utf16be.txt Index: test/utf16be.txt ================================================================== --- /dev/null +++ test/utf16be.txt @@ -0,0 +1,23 @@ +This file contains utf-16be text. +The purpose for including this file in the Fossil +repository is to provide the ability to test Fossil's +handling of UTF-16 using its own repository. + +Browsing to this file in the web interface should display the file as +text on the screen. + +When there are changes to this file those changes should show +up in the "diff" output and be properly displayed on the +screen. + +Test procedures: + + 1. Verify that this file is correctly display using the /artifact + webpage. + + 2. Verify that this file is correctly displayed by the /doc webpage. + + 3. Verify that changes to are correctly displayed by the /fdiff webpage. + + 4. Verify that the "fossil diff" command correctly displays changes + in this file. Do the same with the --tk option. ADDED test/utf16le.txt Index: test/utf16le.txt ================================================================== --- /dev/null +++ test/utf16le.txt @@ -0,0 +1,23 @@ +This file contains utf-16le text. +The purpose for including this file in the Fossil +repository is to provide the ability to test Fossil's +handling of UTF-16 using its own repository. + +Browsing to this file in the web interface should display the file as +text on the screen. + +When there are changes to this file those changes should show +up in the "diff" output and be properly displayed on the +screen. + +Test procedures: + + 1. Verify that this file is correctly display using the /artifact + webpage. + + 2. Verify that this file is correctly displayed by the /doc webpage. + + 3. Verify that changes to are correctly displayed by the /fdiff webpage. + + 4. Verify that the "fossil diff" command correctly displays changes + in this file. Do the same with the --tk option. Index: test/valgrind-www.tcl ================================================================== --- test/valgrind-www.tcl +++ test/valgrind-www.tcl @@ -1,10 +1,10 @@ #!/usr/bin/tclsh # -# Run this script in an open Fossil checkout at the top-level with a +# Run this script in an open Fossil checkout at the top-level with a # fresh build of Fossil itself. This script will run fossil on hundreds -# of different web-pages looking for memory allocation problems using +# of different web-pages looking for memory allocation problems using # valgrind. Valgrind output appears on stderr. Suggested test scenario: # # make # tclsh valgrind-www.tcl 2>&1 | tee valgrind-out.txt # @@ -12,11 +12,13 @@ # proc run_query {url} { set fd [open q.txt w] puts $fd "GET $url HTTP/1.0\r\n\r" close $fd - return [exec valgrind ./fossil test-http @ stderr] + set msg {} + catch {exec valgrind ./fossil test-http @ stderr} msg + return $msg } set todo {} foreach url { /home /timeline Index: tools/fossil_chat.tcl ================================================================== --- tools/fossil_chat.tcl +++ tools/fossil_chat.tcl @@ -84,11 +84,11 @@ catch {close $SOCKET} if {[catch { if {$::PROXYHOST ne {}} { set SOCKET [socket $::PROXYHOST $::PROXYPORT] puts $SOCKET "CONNECT $::SERVERHOST:$::SERVERPORT HTTP/1.1" - puts $SOCKET "Host: $::SERVERHOST:$::SERVERPORT" + puts $SOCKET "Host: $::SERVERHOST:$::SERVERPORT" puts $SOCKET "" } else { set SOCKET [socket $::SERVERHOST $::SERVERPORT] } fconfigure $SOCKET -translation binary -blocking 0 @@ -142,11 +142,11 @@ # proc delete_files {} { global FILES .mb.files delete 3 end array unset FILES - .mb.files entryconfigure 1 -state disabled + .mb.files entryconfigure 1 -state disabled } # Prompt the user to select a file from the disk. Then send that # file to all chat participants. # @@ -188,11 +188,11 @@ if {![info exists prior] || $filename!=$prior} { .mb.files add command -label "Save \"$filename\"" \ -command [list save_file $filename] } set FILES($filename) $data - .mb.files entryconfigure 1 -state active + .mb.files entryconfigure 1 -state active set time [clock format [clock seconds] -format {%H:%M} -gmt 1] .msg.t insert end "\[$time $from\] " meta "File: \"$filename\"\n" norm .msg.t see end } @@ -232,11 +232,11 @@ } elseif {$cmd=="file"} { if {[info commands handle_file]=="handle_file"} { handle_file [lindex $line 1] [lindex $line 2] [lindex $line 3] } } -} +} # Handle a broken socket connection # proc disconnect {} { global SOCKET Index: win/Makefile.PellesCGMake ================================================================== --- win/Makefile.PellesCGMake +++ win/Makefile.PellesCGMake @@ -30,13 +30,13 @@ # and # PellesC 6.00.4 # gmake 3.80 # zlib sources 1.2.5 # Windows 7 Home Premium -# +# -# +# PellesCDir=c:\Programme\PellesC # Select between 32/64 bit code, default is 32 bit #TARGETVERSION=64 @@ -75,25 +75,25 @@ RC=$(PellesCDir)\bin\porc.exe RCFLAGS=$(INCLUDE) -D__POCC__=1 -D_M_X$(TARGETVERSION) # define the special utilities files, needed to generate # the automatically generated source files -UTILS=translate.exe mkindex.exe makeheaders.exe +UTILS=translate.exe mkindex.exe makeheaders.exe mkbuiltin.exe UTILS_OBJ=$(UTILS:.exe=.obj) UTILS_SRC=$(foreach uf,$(UTILS),$(SRCDIR)$(uf:.exe=.c)) -# define the sqlite files, which need special flags on compile +# define the SQLite files, which need special flags on compile SQLITESRC=sqlite3.c ORIGSQLITESRC=$(foreach sf,$(SQLITESRC),$(SRCDIR)$(sf)) SQLITEOBJ=$(foreach sf,$(SQLITESRC),$(sf:.c=.obj)) -SQLITEDEFINES=-DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_WIN32_NO_ANSI +SQLITEDEFINES=-DNDEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_WIN32_NO_ANSI -# define the sqlite shell files, which need special flags on compile +# define the SQLite shell files, which need special flags on compile SQLITESHELLSRC=shell.c ORIGSQLITESHELLSRC=$(foreach sf,$(SQLITESHELLSRC),$(SRCDIR)$(sf)) SQLITESHELLOBJ=$(foreach sf,$(SQLITESHELLSRC),$(sf:.c=.obj)) -SQLITESHELLDEFINES=-Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -Dsqlite3_strglob=strglob +SQLITESHELLDEFINES=-Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=fossil_open -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen # define the th scripting files, which need special flags on compile THSRC=th.c th_lang.c ORIGTHSRC=$(foreach sf,$(THSRC),$(SRCDIR)$(sf)) THOBJ=$(foreach sf,$(THSRC),$(sf:.c=.obj)) @@ -114,11 +114,11 @@ # main target file is the application APPLICATION=fossil.exe # define the standard make target .PHONY: default -default: page_index.h headers $(APPLICATION) +default: page_index.h builtin_data.h headers $(APPLICATION) # symbolic target to generate the source generate utils .PHONY: utils utils: $(UTILS) @@ -140,16 +140,19 @@ # generate the index source, containing all web references,.. page_index.h: $(TRANSLATEDSRC) mkindex.exe mkindex.exe $(TRANSLATEDSRC) >$@ +builtin_data.h: $(EXTRA_FILES) mkbuiltin.exe + mkbuiltin.exe --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ + # extracting version info from manifest VERSION.h: version.exe ..\manifest.uuid ..\manifest ..\VERSION - version.exe ..\manifest.uuid ..\manifest ..\VERSION > $@ + version.exe ..\manifest.uuid ..\manifest ..\VERSION >$@ # generate the simplified headers -headers: makeheaders.exe page_index.h VERSION.h ../src/sqlite3.h ../src/th.h VERSION.h +headers: makeheaders.exe page_index.h builtin_data.h VERSION.h ../src/sqlite3.h ../src/th.h VERSION.h makeheaders.exe $(foreach ts,$(TRANSLATEDSRC),$(ts):$(ts:_.c=.h)) ../src/sqlite3.h ../src/th.h VERSION.h echo Done >$@ # compile C sources with relevant options Index: win/Makefile.dmc ================================================================== --- win/Makefile.dmc +++ win/Makefile.dmc @@ -24,33 +24,36 @@ CFLAGS = -o BCC = $(DMDIR)\bin\dmc $(CFLAGS) TCC = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL) LIBS = $(DMDIR)\extra\lib\ zlib wsock32 advapi32 -SQLITE_OPTIONS = -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_STAT3 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_WIN32_NO_ANSI +SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS3_PARENTHESIS -SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c regexp_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c +SHELL_OPTIONS = -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=fossil_open -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen -OBJ = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winhttp$O $(OBJDIR)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O +SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c foci_.c fusefs_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c sitemap_.c skins_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c + +OBJ = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\fusefs$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O RC=$(DMDIR)\bin\rcc RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ APPNAME = $(OBJDIR)\fossil$(E) all: $(APPNAME) -$(APPNAME) : translate$E mkindex$E headers $(OBJ) $(OBJDIR)\link - cd $(OBJDIR) +$(APPNAME) : translate$E mkindex$E codecheck1$E headers $(OBJ) $(OBJDIR)\link + cd $(OBJDIR) + codecheck1$E $(SRC) $(DMDIR)\bin\link @link $(OBJDIR)\fossil.res: $B\win\fossil.rc $(RC) $(RCFLAGS) -o$@ $** $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res - +echo add allrepo attach bag bisect blob branch browse captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo glob graph gzip http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path pivot popen pqueue printf rebuild regexp report rss schema search setup sha1 shun skins sqlcmd stash stat style sync tag tar th_main timeline tkt tktsetup undo unicode update url user utf8 util verify vfile wiki wikiformat winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@ + +echo add allrepo attach bag bisect blob branch browse builtin bundle cache captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo foci fusefs glob graph gzip http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path pivot popen pqueue printf publish purge rebuild regexp report rss schema search setup sha1 shun sitemap skins sqlcmd stash stat statrep style sync tag tar th_main timeline tkt tktsetup undo unicode update url user utf8 util verify vfile wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@ +echo fossil >> $@ +echo fossil >> $@ +echo $(LIBS) >> $@ +echo. >> $@ +echo fossil >> $@ @@ -62,18 +65,24 @@ $(BCC) -o$@ $** mkindex$E: $(SRCDIR)\mkindex.c $(BCC) -o$@ $** -version$E: $B\src\mkversion.c +mkbuiltin$E: $(SRCDIR)\mkbuiltin.c + $(BCC) -o$@ $** + +mkversion$E: $(SRCDIR)\mkversion.c + $(BCC) -o$@ $** + +codecheck1$E: $(SRCDIR)\codecheck1.c $(BCC) -o$@ $** $(OBJDIR)\shell$O : $(SRCDIR)\shell.c - $(TCC) -o$@ -c -Dmain=sqlite3_shell $(SQLITE_OPTIONS) $** + $(TCC) -o$@ -c $(SHELL_OPTIONS) $(SQLITE_OPTIONS) $(SHELL_CFLAGS) $** $(OBJDIR)\sqlite3$O : $(SRCDIR)\sqlite3.c - $(TCC) -o$@ -c $(SQLITE_OPTIONS) $** + $(TCC) -o$@ -c $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) $** $(OBJDIR)\th$O : $(SRCDIR)\th.c $(TCC) -o$@ -c $** $(OBJDIR)\th_lang$O : $(SRCDIR)\th_lang.c @@ -80,22 +89,25 @@ $(TCC) -o$@ -c $** $(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h cp $@ $@ -VERSION.h : version$E $B\manifest.uuid $B\manifest $B\VERSION +VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION + +$** > $@ + +page_index.h: mkindex$E $(SRC) +$** > $@ -page_index.h: mkindex$E $(SRC) - +$** > $@ +builtin_data.h: mkbuiltin$E $(EXTRA_FILES) + mkbuiltin$E --prefix $(SRCDIR)/ $(EXTRA_FILES) > $@ clean: -del $(OBJDIR)\*.obj -del *.obj *_.c *.h *.map realclean: - -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E + -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E codecheck1$E mkbuiltin$E $(OBJDIR)\json$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_config$O : $(SRCDIR)\json_detail.h @@ -158,10 +170,28 @@ $(OBJDIR)\browse$O : browse_.c browse.h $(TCC) -o$@ -c browse_.c browse_.c : $(SRCDIR)\browse.c +translate$E $** > $@ + +$(OBJDIR)\builtin$O : builtin_.c builtin.h + $(TCC) -o$@ -c builtin_.c + +builtin_.c : $(SRCDIR)\builtin.c + +translate$E $** > $@ + +$(OBJDIR)\bundle$O : bundle_.c bundle.h + $(TCC) -o$@ -c bundle_.c + +bundle_.c : $(SRCDIR)\bundle.c + +translate$E $** > $@ + +$(OBJDIR)\cache$O : cache_.c cache.h + $(TCC) -o$@ -c cache_.c + +cache_.c : $(SRCDIR)\cache.c + +translate$E $** > $@ $(OBJDIR)\captcha$O : captcha_.c captcha.h $(TCC) -o$@ -c captcha_.c captcha_.c : $(SRCDIR)\captcha.c @@ -284,10 +314,22 @@ $(OBJDIR)\finfo$O : finfo_.c finfo.h $(TCC) -o$@ -c finfo_.c finfo_.c : $(SRCDIR)\finfo.c +translate$E $** > $@ + +$(OBJDIR)\foci$O : foci_.c foci.h + $(TCC) -o$@ -c foci_.c + +foci_.c : $(SRCDIR)\foci.c + +translate$E $** > $@ + +$(OBJDIR)\fusefs$O : fusefs_.c fusefs.h + $(TCC) -o$@ -c fusefs_.c + +fusefs_.c : $(SRCDIR)\fusefs.c + +translate$E $** > $@ $(OBJDIR)\glob$O : glob_.c glob.h $(TCC) -o$@ -c glob_.c glob_.c : $(SRCDIR)\glob.c @@ -434,10 +476,16 @@ $(OBJDIR)\leaf$O : leaf_.c leaf.h $(TCC) -o$@ -c leaf_.c leaf_.c : $(SRCDIR)\leaf.c +translate$E $** > $@ + +$(OBJDIR)\loadctrl$O : loadctrl_.c loadctrl.h + $(TCC) -o$@ -c loadctrl_.c + +loadctrl_.c : $(SRCDIR)\loadctrl.c + +translate$E $** > $@ $(OBJDIR)\login$O : login_.c login.h $(TCC) -o$@ -c login_.c login_.c : $(SRCDIR)\login.c @@ -530,10 +578,22 @@ $(OBJDIR)\printf$O : printf_.c printf.h $(TCC) -o$@ -c printf_.c printf_.c : $(SRCDIR)\printf.c +translate$E $** > $@ + +$(OBJDIR)\publish$O : publish_.c publish.h + $(TCC) -o$@ -c publish_.c + +publish_.c : $(SRCDIR)\publish.c + +translate$E $** > $@ + +$(OBJDIR)\purge$O : purge_.c purge.h + $(TCC) -o$@ -c purge_.c + +purge_.c : $(SRCDIR)\purge.c + +translate$E $** > $@ $(OBJDIR)\rebuild$O : rebuild_.c rebuild.h $(TCC) -o$@ -c rebuild_.c rebuild_.c : $(SRCDIR)\rebuild.c @@ -584,10 +644,16 @@ $(OBJDIR)\shun$O : shun_.c shun.h $(TCC) -o$@ -c shun_.c shun_.c : $(SRCDIR)\shun.c +translate$E $** > $@ + +$(OBJDIR)\sitemap$O : sitemap_.c sitemap.h + $(TCC) -o$@ -c sitemap_.c + +sitemap_.c : $(SRCDIR)\sitemap.c + +translate$E $** > $@ $(OBJDIR)\skins$O : skins_.c skins.h $(TCC) -o$@ -c skins_.c skins_.c : $(SRCDIR)\skins.c @@ -608,10 +674,16 @@ $(OBJDIR)\stat$O : stat_.c stat.h $(TCC) -o$@ -c stat_.c stat_.c : $(SRCDIR)\stat.c +translate$E $** > $@ + +$(OBJDIR)\statrep$O : statrep_.c statrep.h + $(TCC) -o$@ -c statrep_.c + +statrep_.c : $(SRCDIR)\statrep.c + +translate$E $** > $@ $(OBJDIR)\style$O : style_.c style.h $(TCC) -o$@ -c style_.c style_.c : $(SRCDIR)\style.c @@ -722,10 +794,16 @@ $(OBJDIR)\wikiformat$O : wikiformat_.c wikiformat.h $(TCC) -o$@ -c wikiformat_.c wikiformat_.c : $(SRCDIR)\wikiformat.c +translate$E $** > $@ + +$(OBJDIR)\winfile$O : winfile_.c winfile.h + $(TCC) -o$@ -c winfile_.c + +winfile_.c : $(SRCDIR)\winfile.c + +translate$E $** > $@ $(OBJDIR)\winhttp$O : winhttp_.c winhttp.h $(TCC) -o$@ -c winhttp_.c winhttp_.c : $(SRCDIR)\winhttp.c @@ -753,8 +831,8 @@ $(TCC) -o$@ -c zip_.c zip_.c : $(SRCDIR)\zip.c +translate$E $** > $@ -headers: makeheaders$E page_index.h VERSION.h - +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h regexp_.c:regexp.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h wysiwyg_.c:wysiwyg.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h +headers: makeheaders$E page_index.h builtin_data.h VERSION.h + +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h foci_.c:foci.h fusefs_.c:fusefs.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h wysiwyg_.c:wysiwyg.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h @copy /Y nul: headers Index: win/Makefile.mingw ================================================================== --- win/Makefile.mingw +++ win/Makefile.mingw @@ -47,10 +47,23 @@ # FOSSIL_ENABLE_JSON = 1 #### Enable HTTPS support via OpenSSL (links to libssl and libcrypto) # # FOSSIL_ENABLE_SSL = 1 + +#### Automatically build OpenSSL when building Fossil (causes rebuild +# issues when building incrementally). +# +# FOSSIL_BUILD_SSL = 1 + +#### Enable TH1 scripts in embedded documentation files +# +# FOSSIL_ENABLE_TH1_DOCS = 1 + +#### Enable hooks for commands and web pages via TH1 +# +# FOSSIL_ENABLE_TH1_HOOKS = 1 #### Enable scripting support via Tcl/Tk # # FOSSIL_ENABLE_TCL = 1 @@ -60,17 +73,26 @@ #### Load Tcl using the private stubs mechanism # # FOSSIL_ENABLE_TCL_PRIVATE_STUBS = 1 +#### Use 'system' SQLite +# +# USE_SYSTEM_SQLITE = 1 + +#### Use the miniz compression library +# +# FOSSIL_ENABLE_MINIZ = 1 + #### Use the Tcl source directory instead of the install directory? # This is useful when Tcl has been compiled statically with MinGW. # FOSSIL_TCL_SOURCE = 1 #### Check if the workaround for the MinGW command line handling needs to -# be enabled by default. +# be enabled by default. This check may be somewhat fragile due to the +# use of "findstring". # ifndef MINGW_IS_32BIT_ONLY ifeq (,$(findstring w64-mingw32,$(PREFIX))) MINGW_IS_32BIT_ONLY = 1 endif @@ -78,18 +100,59 @@ #### The directories where the zlib include and library files are located. # ZINCDIR = $(SRCDIR)/../compat/zlib ZLIBDIR = $(SRCDIR)/../compat/zlib + +#### Make an attempt to detect if Fossil is being built for the x64 processor +# architecture. This check may be somewhat fragile due to "findstring". +# +ifndef X64 +ifneq (,$(findstring x86_64-w64-mingw32,$(PREFIX))) +X64 = 1 +endif +endif + +#### Determine if the optimized assembly routines provided with zlib should be +# used, taking into account whether zlib is actually enabled and the target +# processor architecture. +# +ifndef X64 +SSLCONFIG = mingw +ifndef FOSSIL_ENABLE_MINIZ +ZLIBCONFIG = LOC="-DASMV -DASMINF" OBJA="inffas86.o match.o" +LIBTARGETS = $(ZLIBDIR)/inffas86.o $(ZLIBDIR)/match.o +else +ZLIBCONFIG = +LIBTARGETS = +endif +else +SSLCONFIG = mingw64 +ZLIBCONFIG = +LIBTARGETS = +endif + +#### Disable creation of the OpenSSL shared libraries. Also, disable support +# for both SSLv2 and SSLv3 (i.e. thereby forcing the use of TLS). +# +SSLCONFIG += no-ssl2 no-ssl3 no-shared + +#### When using zlib, make sure that OpenSSL is configured to use the zlib +# that Fossil knows about (i.e. the one within the source tree). +# +ifndef FOSSIL_ENABLE_MINIZ +SSLCONFIG += --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib +endif #### The directories where the OpenSSL include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "openssl-1.x" sub-directory of the # Fossil source code directory and the target OpenSSL source directory. # -OPENSSLINCDIR = $(SRCDIR)/../compat/openssl-1.0.1e/include -OPENSSLLIBDIR = $(SRCDIR)/../compat/openssl-1.0.1e +OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.0.2a +OPENSSLINCDIR = $(OPENSSLDIR)/include +OPENSSLLIBDIR = $(OPENSSLDIR) #### Either the directory where the Tcl library is installed or the Tcl # source code directory resides (depending on the value of the macro # FOSSIL_TCL_SOURCE). If this points to the Tcl install directory, # this directory must have "include" and "lib" sub-directories. If @@ -130,11 +193,17 @@ # will run on the target platform. This is usually the same # as BCC, unless you are cross-compiling. This C compiler builds # the finished binary for fossil. The BCC compiler above is used # for building intermediate code-generator tools. # -TCC = $(PREFIX)gcc -Os -Wall -L$(ZLIBDIR) -I$(ZINCDIR) +TCC = $(PREFIX)gcc -Os -Wall + +#### When not using the miniz compression library, zlib is required. +# +ifndef FOSSIL_ENABLE_MINIZ +TCC += -L$(ZLIBDIR) -I$(ZINCDIR) +endif #### Add the necessary command line options to build with debugging # symbols, if enabled. # ifdef FOSSIL_ENABLE_SYMBOLS @@ -142,11 +211,15 @@ endif #### Compile resources for use in building executables that will run # on the target platform. # -RCC = $(PREFIX)windres -I$(SRCDIR) -I$(ZINCDIR) +RCC = $(PREFIX)windres -I$(SRCDIR) + +ifndef FOSSIL_ENABLE_MINIZ +RCC += -I$(ZINCDIR) +endif # With HTTPS support ifdef FOSSIL_ENABLE_SSL TCC += -L$(OPENSSLLIBDIR) -I$(OPENSSLINCDIR) RCC += -I$(OPENSSLINCDIR) @@ -160,22 +233,40 @@ else TCC += -L$(TCLLIBDIR) -I$(TCLINCDIR) RCC += -I$(TCLINCDIR) endif endif + +# With miniz (i.e. instead of zlib) +ifdef FOSSIL_ENABLE_MINIZ +TCC += -DFOSSIL_ENABLE_MINIZ=1 +RCC += -DFOSSIL_ENABLE_MINIZ=1 +endif # With MinGW command line handling workaround ifdef MINGW_IS_32BIT_ONLY -TCC += -DBROKEN_MINGW_CMDLINE=1 -D_USE_32BIT_TIME_T -RCC += -DBROKEN_MINGW_CMDLINE=1 -D_USE_32BIT_TIME_T +TCC += -DBROKEN_MINGW_CMDLINE=1 +RCC += -DBROKEN_MINGW_CMDLINE=1 endif # With HTTPS support ifdef FOSSIL_ENABLE_SSL TCC += -DFOSSIL_ENABLE_SSL=1 RCC += -DFOSSIL_ENABLE_SSL=1 endif + +# With TH1 embedded docs support +ifdef FOSSIL_ENABLE_TH1_DOCS +TCC += -DFOSSIL_ENABLE_TH1_DOCS=1 +RCC += -DFOSSIL_ENABLE_TH1_DOCS=1 +endif + +# With TH1 hook support +ifdef FOSSIL_ENABLE_TH1_HOOKS +TCC += -DFOSSIL_ENABLE_TH1_HOOKS=1 +RCC += -DFOSSIL_ENABLE_TH1_HOOKS=1 +endif # With Tcl support ifdef FOSSIL_ENABLE_TCL TCC += -DFOSSIL_ENABLE_TCL=1 RCC += -DFOSSIL_ENABLE_TCL=1 @@ -202,30 +293,45 @@ #### We add the -static option here so that we can build a static # executable that will run in a chroot jail. # LIB = -static -# MinGW: If available, use the Unicode capable runtime startup code. +#### MinGW: If available, use the Unicode capable runtime startup code. +# ifndef MINGW_IS_32BIT_ONLY LIB += -municode endif -# OpenSSL: Add the necessary libraries required, if enabled. +#### SQLite: If enabled, use the system SQLite library. +# +ifdef USE_SYSTEM_SQLITE +LIB += -lsqlite3 +endif + +#### OpenSSL: Add the necessary libraries required, if enabled. +# ifdef FOSSIL_ENABLE_SSL LIB += -lssl -lcrypto -lgdi32 endif -# Tcl: Add the necessary libraries required, if enabled. +#### Tcl: Add the necessary libraries required, if enabled. +# ifdef FOSSIL_ENABLE_TCL LIB += $(LIBTCL) endif #### Extra arguments for linking the finished binary. Fossil needs # to link against the Z-Lib compression library. There are no # other mandatory dependencies. # -LIB += -lmingwex -lz +LIB += -lmingwex + +#### When not using the miniz compression library, zlib is required. +# +ifndef FOSSIL_ENABLE_MINIZ +LIB += -lz +endif #### These libraries MUST appear in the same order as they do for Tcl # or linking with it will not work (exact reason unknown). # ifdef FOSSIL_ENABLE_TCL @@ -243,11 +349,15 @@ # TCLSH = tclsh #### Nullsoft installer MakeNSIS location # -MAKENSIS = "$(ProgramFiles)\NSIS\MakeNSIS.exe" +MAKENSIS = "$(PROGRAMFILES)\NSIS\MakeNSIS.exe" + +#### Inno Setup executable location +# +INNOSETUP = "$(PROGRAMFILES)\Inno Setup 5\ISCC.exe" #### Include a configuration file that can override any one of these settings. # -include config.w32 @@ -263,10 +373,13 @@ $(SRCDIR)/bag.c \ $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ + $(SRCDIR)/builtin.c \ + $(SRCDIR)/bundle.c \ + $(SRCDIR)/cache.c \ $(SRCDIR)/captcha.c \ $(SRCDIR)/cgi.c \ $(SRCDIR)/checkin.c \ $(SRCDIR)/checkout.c \ $(SRCDIR)/clearsign.c \ @@ -284,10 +397,12 @@ $(SRCDIR)/encode.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/file.c \ $(SRCDIR)/finfo.c \ + $(SRCDIR)/foci.c \ + $(SRCDIR)/fusefs.c \ $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/gzip.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ @@ -309,10 +424,11 @@ $(SRCDIR)/json_tag.c \ $(SRCDIR)/json_timeline.c \ $(SRCDIR)/json_user.c \ $(SRCDIR)/json_wiki.c \ $(SRCDIR)/leaf.c \ + $(SRCDIR)/loadctrl.c \ $(SRCDIR)/login.c \ $(SRCDIR)/lookslike.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/markdown.c \ @@ -325,23 +441,27 @@ $(SRCDIR)/path.c \ $(SRCDIR)/pivot.c \ $(SRCDIR)/popen.c \ $(SRCDIR)/pqueue.c \ $(SRCDIR)/printf.c \ + $(SRCDIR)/publish.c \ + $(SRCDIR)/purge.c \ $(SRCDIR)/rebuild.c \ $(SRCDIR)/regexp.c \ $(SRCDIR)/report.c \ $(SRCDIR)/rss.c \ $(SRCDIR)/schema.c \ $(SRCDIR)/search.c \ $(SRCDIR)/setup.c \ $(SRCDIR)/sha1.c \ $(SRCDIR)/shun.c \ + $(SRCDIR)/sitemap.c \ $(SRCDIR)/skins.c \ $(SRCDIR)/sqlcmd.c \ $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ + $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ $(SRCDIR)/th_main.c \ @@ -357,25 +477,80 @@ $(SRCDIR)/util.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ + $(SRCDIR)/winfile.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/wysiwyg.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/xfersetup.c \ $(SRCDIR)/zip.c +EXTRA_FILES = \ + $(SRCDIR)/../skins/aht/details.txt \ + $(SRCDIR)/../skins/black_and_white/css.txt \ + $(SRCDIR)/../skins/black_and_white/details.txt \ + $(SRCDIR)/../skins/black_and_white/footer.txt \ + $(SRCDIR)/../skins/black_and_white/header.txt \ + $(SRCDIR)/../skins/blitz/css.txt \ + $(SRCDIR)/../skins/blitz/details.txt \ + $(SRCDIR)/../skins/blitz/footer.txt \ + $(SRCDIR)/../skins/blitz/header.txt \ + $(SRCDIR)/../skins/blitz/ticket.txt \ + $(SRCDIR)/../skins/blitz_no_logo/css.txt \ + $(SRCDIR)/../skins/blitz_no_logo/details.txt \ + $(SRCDIR)/../skins/blitz_no_logo/footer.txt \ + $(SRCDIR)/../skins/blitz_no_logo/header.txt \ + $(SRCDIR)/../skins/blitz_no_logo/ticket.txt \ + $(SRCDIR)/../skins/default/css.txt \ + $(SRCDIR)/../skins/default/details.txt \ + $(SRCDIR)/../skins/default/footer.txt \ + $(SRCDIR)/../skins/default/header.txt \ + $(SRCDIR)/../skins/eagle/css.txt \ + $(SRCDIR)/../skins/eagle/details.txt \ + $(SRCDIR)/../skins/eagle/footer.txt \ + $(SRCDIR)/../skins/eagle/header.txt \ + $(SRCDIR)/../skins/enhanced1/css.txt \ + $(SRCDIR)/../skins/enhanced1/details.txt \ + $(SRCDIR)/../skins/enhanced1/footer.txt \ + $(SRCDIR)/../skins/enhanced1/header.txt \ + $(SRCDIR)/../skins/khaki/css.txt \ + $(SRCDIR)/../skins/khaki/details.txt \ + $(SRCDIR)/../skins/khaki/footer.txt \ + $(SRCDIR)/../skins/khaki/header.txt \ + $(SRCDIR)/../skins/original/css.txt \ + $(SRCDIR)/../skins/original/details.txt \ + $(SRCDIR)/../skins/original/footer.txt \ + $(SRCDIR)/../skins/original/header.txt \ + $(SRCDIR)/../skins/plain_gray/css.txt \ + $(SRCDIR)/../skins/plain_gray/details.txt \ + $(SRCDIR)/../skins/plain_gray/footer.txt \ + $(SRCDIR)/../skins/plain_gray/header.txt \ + $(SRCDIR)/../skins/rounded1/css.txt \ + $(SRCDIR)/../skins/rounded1/details.txt \ + $(SRCDIR)/../skins/rounded1/footer.txt \ + $(SRCDIR)/../skins/rounded1/header.txt \ + $(SRCDIR)/../skins/xekri/css.txt \ + $(SRCDIR)/../skins/xekri/details.txt \ + $(SRCDIR)/../skins/xekri/footer.txt \ + $(SRCDIR)/../skins/xekri/header.txt \ + $(SRCDIR)/diff.tcl \ + $(SRCDIR)/markdown.md + TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/bag_.c \ $(OBJDIR)/bisect_.c \ $(OBJDIR)/blob_.c \ $(OBJDIR)/branch_.c \ $(OBJDIR)/browse_.c \ + $(OBJDIR)/builtin_.c \ + $(OBJDIR)/bundle_.c \ + $(OBJDIR)/cache_.c \ $(OBJDIR)/captcha_.c \ $(OBJDIR)/cgi_.c \ $(OBJDIR)/checkin_.c \ $(OBJDIR)/checkout_.c \ $(OBJDIR)/clearsign_.c \ @@ -393,10 +568,12 @@ $(OBJDIR)/encode_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/finfo_.c \ + $(OBJDIR)/foci_.c \ + $(OBJDIR)/fusefs_.c \ $(OBJDIR)/glob_.c \ $(OBJDIR)/graph_.c \ $(OBJDIR)/gzip_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ @@ -418,10 +595,11 @@ $(OBJDIR)/json_tag_.c \ $(OBJDIR)/json_timeline_.c \ $(OBJDIR)/json_user_.c \ $(OBJDIR)/json_wiki_.c \ $(OBJDIR)/leaf_.c \ + $(OBJDIR)/loadctrl_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/lookslike_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/markdown_.c \ @@ -434,23 +612,27 @@ $(OBJDIR)/path_.c \ $(OBJDIR)/pivot_.c \ $(OBJDIR)/popen_.c \ $(OBJDIR)/pqueue_.c \ $(OBJDIR)/printf_.c \ + $(OBJDIR)/publish_.c \ + $(OBJDIR)/purge_.c \ $(OBJDIR)/rebuild_.c \ $(OBJDIR)/regexp_.c \ $(OBJDIR)/report_.c \ $(OBJDIR)/rss_.c \ $(OBJDIR)/schema_.c \ $(OBJDIR)/search_.c \ $(OBJDIR)/setup_.c \ $(OBJDIR)/sha1_.c \ $(OBJDIR)/shun_.c \ + $(OBJDIR)/sitemap_.c \ $(OBJDIR)/skins_.c \ $(OBJDIR)/sqlcmd_.c \ $(OBJDIR)/stash_.c \ $(OBJDIR)/stat_.c \ + $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ $(OBJDIR)/th_main_.c \ @@ -466,10 +648,11 @@ $(OBJDIR)/util_.c \ $(OBJDIR)/verify_.c \ $(OBJDIR)/vfile_.c \ $(OBJDIR)/wiki_.c \ $(OBJDIR)/wikiformat_.c \ + $(OBJDIR)/winfile_.c \ $(OBJDIR)/winhttp_.c \ $(OBJDIR)/wysiwyg_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/zip_.c @@ -481,10 +664,13 @@ $(OBJDIR)/bag.o \ $(OBJDIR)/bisect.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ + $(OBJDIR)/builtin.o \ + $(OBJDIR)/bundle.o \ + $(OBJDIR)/cache.o \ $(OBJDIR)/captcha.o \ $(OBJDIR)/cgi.o \ $(OBJDIR)/checkin.o \ $(OBJDIR)/checkout.o \ $(OBJDIR)/clearsign.o \ @@ -502,10 +688,12 @@ $(OBJDIR)/encode.o \ $(OBJDIR)/event.o \ $(OBJDIR)/export.o \ $(OBJDIR)/file.o \ $(OBJDIR)/finfo.o \ + $(OBJDIR)/foci.o \ + $(OBJDIR)/fusefs.o \ $(OBJDIR)/glob.o \ $(OBJDIR)/graph.o \ $(OBJDIR)/gzip.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ @@ -527,10 +715,11 @@ $(OBJDIR)/json_tag.o \ $(OBJDIR)/json_timeline.o \ $(OBJDIR)/json_user.o \ $(OBJDIR)/json_wiki.o \ $(OBJDIR)/leaf.o \ + $(OBJDIR)/loadctrl.o \ $(OBJDIR)/login.o \ $(OBJDIR)/lookslike.o \ $(OBJDIR)/main.o \ $(OBJDIR)/manifest.o \ $(OBJDIR)/markdown.o \ @@ -543,23 +732,27 @@ $(OBJDIR)/path.o \ $(OBJDIR)/pivot.o \ $(OBJDIR)/popen.o \ $(OBJDIR)/pqueue.o \ $(OBJDIR)/printf.o \ + $(OBJDIR)/publish.o \ + $(OBJDIR)/purge.o \ $(OBJDIR)/rebuild.o \ $(OBJDIR)/regexp.o \ $(OBJDIR)/report.o \ $(OBJDIR)/rss.o \ $(OBJDIR)/schema.o \ $(OBJDIR)/search.o \ $(OBJDIR)/setup.o \ $(OBJDIR)/sha1.o \ $(OBJDIR)/shun.o \ + $(OBJDIR)/sitemap.o \ $(OBJDIR)/skins.o \ $(OBJDIR)/sqlcmd.o \ $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ + $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ $(OBJDIR)/th_main.o \ @@ -575,41 +768,51 @@ $(OBJDIR)/util.o \ $(OBJDIR)/verify.o \ $(OBJDIR)/vfile.o \ $(OBJDIR)/wiki.o \ $(OBJDIR)/wikiformat.o \ + $(OBJDIR)/winfile.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/wysiwyg.o \ $(OBJDIR)/xfer.o \ $(OBJDIR)/xfersetup.o \ $(OBJDIR)/zip.o -APPNAME = fossil.exe +APPNAME = fossil.exe +APPTARGETS = #### If the USE_WINDOWS variable exists, it is assumed that we are building # inside of a Windows-style shell; otherwise, it is assumed that we are # building inside of a Unix-style shell. Note that the "move" command is # broken when attempting to use it from the Windows shell via MinGW make # because the SHELL variable is only used for certain commands that are # recognized internally by make. # ifdef USE_WINDOWS -TRANSLATE = $(subst /,\,$(OBJDIR)/translate) -MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders) -MKINDEX = $(subst /,\,$(OBJDIR)/mkindex) -VERSION = $(subst /,\,$(OBJDIR)/version) +TRANSLATE = $(subst /,\,$(OBJDIR)/translate.exe) +MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders.exe) +MKINDEX = $(subst /,\,$(OBJDIR)/mkindex.exe) +MKBUILTIN = $(subst /,\,$(OBJDIR)/mkbuiltin.exe) +MKVERSION = $(subst /,\,$(OBJDIR)/mkversion.exe) +CODECHECK1 = $(subst /,\,$(OBJDIR)/codecheck1.exe) +CAT = type CP = copy +GREP = find MV = copy RM = del /Q MKDIR = -mkdir RMDIR = rmdir /S /Q else -TRANSLATE = $(OBJDIR)/translate -MAKEHEADERS = $(OBJDIR)/makeheaders -MKINDEX = $(OBJDIR)/mkindex -VERSION = $(OBJDIR)/version +TRANSLATE = $(OBJDIR)/translate.exe +MAKEHEADERS = $(OBJDIR)/makeheaders.exe +MKINDEX = $(OBJDIR)/mkindex.exe +MKBUILTIN = $(OBJDIR)/mkbuiltin.exe +MKVERSION = $(OBJDIR)/mkversion.exe +CODECHECK1 = $(OBJDIR)/codecheck1.exe +CAT = cat CP = cp +GREP = grep MV = mv RM = rm -f MKDIR = -mkdir -p RMDIR = rm -rf endif @@ -616,15 +819,19 @@ all: $(OBJDIR) $(APPNAME) $(OBJDIR)/fossil.o: $(SRCDIR)/../win/fossil.rc $(OBJDIR)/VERSION.h ifdef USE_WINDOWS + $(CAT) $(subst /,\,$(SRCDIR)\miniz.c) | $(GREP) "define MZ_VERSION" > $(subst /,\,$(OBJDIR)\minizver.h) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.rc) $(subst /,\,$(OBJDIR)) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.ico) $(subst /,\,$(OBJDIR)) + $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.exe.manifest) $(subst /,\,$(OBJDIR)) else + $(CAT) $(SRCDIR)/miniz.c | $(GREP) "define MZ_VERSION" > $(OBJDIR)/minizver.h $(CP) $(SRCDIR)/../win/fossil.rc $(OBJDIR) $(CP) $(SRCDIR)/../win/fossil.ico $(OBJDIR) + $(CP) $(SRCDIR)/../win/fossil.exe.manifest $(OBJDIR) endif $(RCC) $(OBJDIR)/fossil.rc -o $(OBJDIR)/fossil.o install: $(OBJDIR) $(APPNAME) ifdef USE_WINDOWS @@ -640,45 +847,82 @@ $(MKDIR) $(subst /,\,$(OBJDIR)) else $(MKDIR) $(OBJDIR) endif -$(OBJDIR)/translate: $(SRCDIR)/translate.c - $(BCC) -o $(OBJDIR)/translate $(SRCDIR)/translate.c - -$(OBJDIR)/makeheaders: $(SRCDIR)/makeheaders.c - $(BCC) -o $(OBJDIR)/makeheaders $(SRCDIR)/makeheaders.c - -$(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c - $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c - -$(VERSION): $(SRCDIR)/mkversion.c - $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c +$(TRANSLATE): $(SRCDIR)/translate.c + $(BCC) -o $@ $(SRCDIR)/translate.c + +$(MAKEHEADERS): $(SRCDIR)/makeheaders.c + $(BCC) -o $@ $(SRCDIR)/makeheaders.c + +$(MKINDEX): $(SRCDIR)/mkindex.c + $(BCC) -o $@ $(SRCDIR)/mkindex.c + +$(MKBUILTIN): $(SRCDIR)/mkbuiltin.c + $(BCC) -o $@ $(SRCDIR)/mkbuiltin.c + +$(MKVERSION): $(SRCDIR)/mkversion.c + $(BCC) -o $@ $(SRCDIR)/mkversion.c + +$(CODECHECK1): $(SRCDIR)/codecheck1.c + $(BCC) -o $@ $(SRCDIR)/codecheck1.c # WARNING. DANGER. Running the test suite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) -$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION) - $(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h - -EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(OBJDIR)/cson_amalgamation.o - -ifdef FOSSIL_ENABLE_TCL -EXTRAOBJ += $(OBJDIR)/th_tcl.o -endif +$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION) + $(MKVERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$@ + +# The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set +# to 1. If it is set to 1, then there is no need to build or link +# the sqlite3.o object. Instead, the system SQLite will be linked +# using -lsqlite3. +SQLITE3_OBJ.1 = +SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o +SQLITE3_OBJ. = $(SQLITE3_OBJ.0) + +# The FOSSIL_ENABLE_MINIZ variable may be undefined, set to 0, or +# set to 1. If it is set to 1, the miniz library included in the +# source tree should be used; otherwise, it should not. +MINIZ_OBJ.0 = +MINIZ_OBJ.1 = $(OBJDIR)/miniz.o +MINIZ_OBJ. = $(MINIZ_OBJ.0) + + +EXTRAOBJ = \ + $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \ + $(MINIZ_OBJ.$(FOSSIL_ENABLE_MINIZ)) \ + $(OBJDIR)/shell.o \ + $(OBJDIR)/th.o \ + $(OBJDIR)/th_lang.o \ + $(OBJDIR)/th_tcl.o \ + $(OBJDIR)/cson_amalgamation.o + zlib: - $(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc libz.a + $(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) $(ZLIBCONFIG) -f win32/Makefile.gcc libz.a clean-zlib: $(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc clean -openssl: zlib - cd $(OPENSSLLIBDIR);./Configure --cross-compile-prefix=$(PREFIX) --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib mingw +$(ZLIBDIR)/inffas86.o: + $(TCC) -c -o $@ -DASMINF -I$(ZLIBDIR) -O3 $(ZLIBDIR)/contrib/inflate86/inffas86.c + +$(ZLIBDIR)/match.o: + $(TCC) -c -o $@ -DASMV $(ZLIBDIR)/contrib/asm686/match.S + + +ifndef FOSSIL_ENABLE_MINIZ +LIBTARGETS += zlib +endif + +openssl: $(LIBTARGETS) + cd $(OPENSSLLIBDIR);./Configure --cross-compile-prefix=$(PREFIX) $(SSLCONFIG) $(MAKE) -C $(OPENSSLLIBDIR) build_libs clean-openssl: $(MAKE) -C $(OPENSSLLIBDIR) clean @@ -687,12 +931,19 @@ $(MAKE) -C $(TCLSRCDIR)/win $(TCLTARGET) clean-tcl: $(MAKE) -C $(TCLSRCDIR)/win distclean -$(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(OBJDIR)/fossil.o zlib - $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/fossil.o +APPTARGETS += $(LIBTARGETS) + +ifdef FOSSIL_BUILD_SSL +APPTARGETS += openssl +endif + +$(APPNAME): $(APPTARGETS) $(OBJDIR)/headers $(CODECHECK1) $(OBJ) $(EXTRAOBJ) $(OBJDIR)/fossil.o + $(CODECHECK1) $(TRANS_SRC) + $(TCC) -o $@ $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/fossil.o # This rule prevents make from using its default rules to try build # an executable named "manifest" out of the file named "manifest.c" # $(SRCDIR)/../manifest: @@ -706,24 +957,33 @@ $(RM) $(APPNAME) $(RMDIR) $(OBJDIR) endif setup: $(OBJDIR) $(APPNAME) - $(MAKENSIS) ./fossil.nsi + $(MAKENSIS) ./setup/fossil.nsi -$(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex +innosetup: $(OBJDIR) $(APPNAME) + $(INNOSETUP) ./setup/fossil.iss -DAppVersion=$(shell $(CAT) ./VERSION) + +$(OBJDIR)/page_index.h: $(TRANS_SRC) $(MKINDEX) $(MKINDEX) $(TRANS_SRC) >$@ -$(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h +$(OBJDIR)/builtin_data.h: $(MKBUILTIN) $(EXTRA_FILES) + $(MKBUILTIN) --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ + +$(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \ $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \ $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \ $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \ $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \ $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \ $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \ $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \ + $(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \ + $(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \ + $(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \ $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \ $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \ $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \ $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \ $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \ @@ -741,10 +1001,12 @@ $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ + $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ + $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ $(OBJDIR)/http_.c:$(OBJDIR)/http.h \ $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ @@ -766,10 +1028,11 @@ $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h \ $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h \ $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h \ $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h \ $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h \ + $(OBJDIR)/loadctrl_.c:$(OBJDIR)/loadctrl.h \ $(OBJDIR)/login_.c:$(OBJDIR)/login.h \ $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \ $(OBJDIR)/main_.c:$(OBJDIR)/main.h \ $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \ $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \ @@ -782,23 +1045,27 @@ $(OBJDIR)/path_.c:$(OBJDIR)/path.h \ $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h \ $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h \ $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h \ $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h \ + $(OBJDIR)/publish_.c:$(OBJDIR)/publish.h \ + $(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \ $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \ $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \ $(OBJDIR)/report_.c:$(OBJDIR)/report.h \ $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \ $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \ $(OBJDIR)/search_.c:$(OBJDIR)/search.h \ $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \ $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \ $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \ + $(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \ $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \ $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h \ $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \ $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \ + $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ @@ -814,10 +1081,11 @@ $(OBJDIR)/util_.c:$(OBJDIR)/util.h \ $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h \ $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h \ $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h \ $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h \ + $(OBJDIR)/winfile_.c:$(OBJDIR)/winfile.h \ $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h \ $(OBJDIR)/wysiwyg_.c:$(OBJDIR)/wysiwyg.h \ $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h \ $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h \ $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h \ @@ -828,882 +1096,999 @@ $(OBJDIR)/headers: Makefile Makefile: -$(OBJDIR)/add_.c: $(SRCDIR)/add.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/add.c >$(OBJDIR)/add_.c +$(OBJDIR)/add_.c: $(SRCDIR)/add.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/add.c >$@ -$(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h +$(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c $(OBJDIR)/add.h: $(OBJDIR)/headers -$(OBJDIR)/allrepo_.c: $(SRCDIR)/allrepo.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/allrepo.c >$(OBJDIR)/allrepo_.c +$(OBJDIR)/allrepo_.c: $(SRCDIR)/allrepo.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/allrepo.c >$@ -$(OBJDIR)/allrepo.o: $(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h +$(OBJDIR)/allrepo.o: $(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/allrepo.o -c $(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h: $(OBJDIR)/headers -$(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/attach.c >$(OBJDIR)/attach_.c +$(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/attach.c >$@ -$(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h +$(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c $(OBJDIR)/attach.h: $(OBJDIR)/headers -$(OBJDIR)/bag_.c: $(SRCDIR)/bag.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/bag.c >$(OBJDIR)/bag_.c +$(OBJDIR)/bag_.c: $(SRCDIR)/bag.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/bag.c >$@ -$(OBJDIR)/bag.o: $(OBJDIR)/bag_.c $(OBJDIR)/bag.h $(SRCDIR)/config.h +$(OBJDIR)/bag.o: $(OBJDIR)/bag_.c $(OBJDIR)/bag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/bag.o -c $(OBJDIR)/bag_.c $(OBJDIR)/bag.h: $(OBJDIR)/headers -$(OBJDIR)/bisect_.c: $(SRCDIR)/bisect.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/bisect.c >$(OBJDIR)/bisect_.c +$(OBJDIR)/bisect_.c: $(SRCDIR)/bisect.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/bisect.c >$@ -$(OBJDIR)/bisect.o: $(OBJDIR)/bisect_.c $(OBJDIR)/bisect.h $(SRCDIR)/config.h +$(OBJDIR)/bisect.o: $(OBJDIR)/bisect_.c $(OBJDIR)/bisect.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/bisect.o -c $(OBJDIR)/bisect_.c $(OBJDIR)/bisect.h: $(OBJDIR)/headers -$(OBJDIR)/blob_.c: $(SRCDIR)/blob.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/blob.c >$(OBJDIR)/blob_.c +$(OBJDIR)/blob_.c: $(SRCDIR)/blob.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/blob.c >$@ -$(OBJDIR)/blob.o: $(OBJDIR)/blob_.c $(OBJDIR)/blob.h $(SRCDIR)/config.h +$(OBJDIR)/blob.o: $(OBJDIR)/blob_.c $(OBJDIR)/blob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/blob.o -c $(OBJDIR)/blob_.c $(OBJDIR)/blob.h: $(OBJDIR)/headers -$(OBJDIR)/branch_.c: $(SRCDIR)/branch.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/branch.c >$(OBJDIR)/branch_.c +$(OBJDIR)/branch_.c: $(SRCDIR)/branch.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/branch.c >$@ -$(OBJDIR)/branch.o: $(OBJDIR)/branch_.c $(OBJDIR)/branch.h $(SRCDIR)/config.h +$(OBJDIR)/branch.o: $(OBJDIR)/branch_.c $(OBJDIR)/branch.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/branch.o -c $(OBJDIR)/branch_.c $(OBJDIR)/branch.h: $(OBJDIR)/headers -$(OBJDIR)/browse_.c: $(SRCDIR)/browse.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/browse.c >$(OBJDIR)/browse_.c +$(OBJDIR)/browse_.c: $(SRCDIR)/browse.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/browse.c >$@ -$(OBJDIR)/browse.o: $(OBJDIR)/browse_.c $(OBJDIR)/browse.h $(SRCDIR)/config.h +$(OBJDIR)/browse.o: $(OBJDIR)/browse_.c $(OBJDIR)/browse.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/browse.o -c $(OBJDIR)/browse_.c $(OBJDIR)/browse.h: $(OBJDIR)/headers -$(OBJDIR)/captcha_.c: $(SRCDIR)/captcha.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/captcha.c >$(OBJDIR)/captcha_.c +$(OBJDIR)/builtin_.c: $(SRCDIR)/builtin.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/builtin.c >$@ -$(OBJDIR)/captcha.o: $(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h +$(OBJDIR)/builtin.o: $(OBJDIR)/builtin_.c $(OBJDIR)/builtin.h $(OBJDIR)/builtin_data.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/builtin.o -c $(OBJDIR)/builtin_.c + +$(OBJDIR)/builtin.h: $(OBJDIR)/headers + +$(OBJDIR)/bundle_.c: $(SRCDIR)/bundle.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/bundle.c >$@ + +$(OBJDIR)/bundle.o: $(OBJDIR)/bundle_.c $(OBJDIR)/bundle.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/bundle.o -c $(OBJDIR)/bundle_.c + +$(OBJDIR)/bundle.h: $(OBJDIR)/headers + +$(OBJDIR)/cache_.c: $(SRCDIR)/cache.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/cache.c >$@ + +$(OBJDIR)/cache.o: $(OBJDIR)/cache_.c $(OBJDIR)/cache.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/cache.o -c $(OBJDIR)/cache_.c + +$(OBJDIR)/cache.h: $(OBJDIR)/headers + +$(OBJDIR)/captcha_.c: $(SRCDIR)/captcha.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/captcha.c >$@ + +$(OBJDIR)/captcha.o: $(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/captcha.o -c $(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h: $(OBJDIR)/headers -$(OBJDIR)/cgi_.c: $(SRCDIR)/cgi.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/cgi.c >$(OBJDIR)/cgi_.c +$(OBJDIR)/cgi_.c: $(SRCDIR)/cgi.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/cgi.c >$@ -$(OBJDIR)/cgi.o: $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h $(SRCDIR)/config.h +$(OBJDIR)/cgi.o: $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/cgi.o -c $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h: $(OBJDIR)/headers -$(OBJDIR)/checkin_.c: $(SRCDIR)/checkin.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/checkin.c >$(OBJDIR)/checkin_.c +$(OBJDIR)/checkin_.c: $(SRCDIR)/checkin.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/checkin.c >$@ -$(OBJDIR)/checkin.o: $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h $(SRCDIR)/config.h +$(OBJDIR)/checkin.o: $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/checkin.o -c $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h: $(OBJDIR)/headers -$(OBJDIR)/checkout_.c: $(SRCDIR)/checkout.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/checkout.c >$(OBJDIR)/checkout_.c +$(OBJDIR)/checkout_.c: $(SRCDIR)/checkout.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/checkout.c >$@ -$(OBJDIR)/checkout.o: $(OBJDIR)/checkout_.c $(OBJDIR)/checkout.h $(SRCDIR)/config.h +$(OBJDIR)/checkout.o: $(OBJDIR)/checkout_.c $(OBJDIR)/checkout.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/checkout.o -c $(OBJDIR)/checkout_.c $(OBJDIR)/checkout.h: $(OBJDIR)/headers -$(OBJDIR)/clearsign_.c: $(SRCDIR)/clearsign.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/clearsign.c >$(OBJDIR)/clearsign_.c +$(OBJDIR)/clearsign_.c: $(SRCDIR)/clearsign.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/clearsign.c >$@ -$(OBJDIR)/clearsign.o: $(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.h $(SRCDIR)/config.h +$(OBJDIR)/clearsign.o: $(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/clearsign.o -c $(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.h: $(OBJDIR)/headers -$(OBJDIR)/clone_.c: $(SRCDIR)/clone.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/clone.c >$(OBJDIR)/clone_.c +$(OBJDIR)/clone_.c: $(SRCDIR)/clone.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/clone.c >$@ -$(OBJDIR)/clone.o: $(OBJDIR)/clone_.c $(OBJDIR)/clone.h $(SRCDIR)/config.h +$(OBJDIR)/clone.o: $(OBJDIR)/clone_.c $(OBJDIR)/clone.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/clone.o -c $(OBJDIR)/clone_.c $(OBJDIR)/clone.h: $(OBJDIR)/headers -$(OBJDIR)/comformat_.c: $(SRCDIR)/comformat.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/comformat.c >$(OBJDIR)/comformat_.c +$(OBJDIR)/comformat_.c: $(SRCDIR)/comformat.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/comformat.c >$@ -$(OBJDIR)/comformat.o: $(OBJDIR)/comformat_.c $(OBJDIR)/comformat.h $(SRCDIR)/config.h +$(OBJDIR)/comformat.o: $(OBJDIR)/comformat_.c $(OBJDIR)/comformat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/comformat.o -c $(OBJDIR)/comformat_.c $(OBJDIR)/comformat.h: $(OBJDIR)/headers -$(OBJDIR)/configure_.c: $(SRCDIR)/configure.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/configure.c >$(OBJDIR)/configure_.c +$(OBJDIR)/configure_.c: $(SRCDIR)/configure.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/configure.c >$@ -$(OBJDIR)/configure.o: $(OBJDIR)/configure_.c $(OBJDIR)/configure.h $(SRCDIR)/config.h +$(OBJDIR)/configure.o: $(OBJDIR)/configure_.c $(OBJDIR)/configure.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/configure.o -c $(OBJDIR)/configure_.c $(OBJDIR)/configure.h: $(OBJDIR)/headers -$(OBJDIR)/content_.c: $(SRCDIR)/content.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/content.c >$(OBJDIR)/content_.c +$(OBJDIR)/content_.c: $(SRCDIR)/content.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/content.c >$@ -$(OBJDIR)/content.o: $(OBJDIR)/content_.c $(OBJDIR)/content.h $(SRCDIR)/config.h +$(OBJDIR)/content.o: $(OBJDIR)/content_.c $(OBJDIR)/content.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/content.o -c $(OBJDIR)/content_.c $(OBJDIR)/content.h: $(OBJDIR)/headers -$(OBJDIR)/db_.c: $(SRCDIR)/db.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/db.c >$(OBJDIR)/db_.c +$(OBJDIR)/db_.c: $(SRCDIR)/db.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/db.c >$@ -$(OBJDIR)/db.o: $(OBJDIR)/db_.c $(OBJDIR)/db.h $(SRCDIR)/config.h +$(OBJDIR)/db.o: $(OBJDIR)/db_.c $(OBJDIR)/db.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/db.o -c $(OBJDIR)/db_.c $(OBJDIR)/db.h: $(OBJDIR)/headers -$(OBJDIR)/delta_.c: $(SRCDIR)/delta.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/delta.c >$(OBJDIR)/delta_.c +$(OBJDIR)/delta_.c: $(SRCDIR)/delta.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/delta.c >$@ -$(OBJDIR)/delta.o: $(OBJDIR)/delta_.c $(OBJDIR)/delta.h $(SRCDIR)/config.h +$(OBJDIR)/delta.o: $(OBJDIR)/delta_.c $(OBJDIR)/delta.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/delta.o -c $(OBJDIR)/delta_.c $(OBJDIR)/delta.h: $(OBJDIR)/headers -$(OBJDIR)/deltacmd_.c: $(SRCDIR)/deltacmd.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/deltacmd.c >$(OBJDIR)/deltacmd_.c +$(OBJDIR)/deltacmd_.c: $(SRCDIR)/deltacmd.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/deltacmd.c >$@ -$(OBJDIR)/deltacmd.o: $(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.h $(SRCDIR)/config.h +$(OBJDIR)/deltacmd.o: $(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/deltacmd.o -c $(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.h: $(OBJDIR)/headers -$(OBJDIR)/descendants_.c: $(SRCDIR)/descendants.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/descendants.c >$(OBJDIR)/descendants_.c +$(OBJDIR)/descendants_.c: $(SRCDIR)/descendants.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/descendants.c >$@ -$(OBJDIR)/descendants.o: $(OBJDIR)/descendants_.c $(OBJDIR)/descendants.h $(SRCDIR)/config.h +$(OBJDIR)/descendants.o: $(OBJDIR)/descendants_.c $(OBJDIR)/descendants.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/descendants.o -c $(OBJDIR)/descendants_.c $(OBJDIR)/descendants.h: $(OBJDIR)/headers -$(OBJDIR)/diff_.c: $(SRCDIR)/diff.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/diff.c >$(OBJDIR)/diff_.c +$(OBJDIR)/diff_.c: $(SRCDIR)/diff.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/diff.c >$@ -$(OBJDIR)/diff.o: $(OBJDIR)/diff_.c $(OBJDIR)/diff.h $(SRCDIR)/config.h +$(OBJDIR)/diff.o: $(OBJDIR)/diff_.c $(OBJDIR)/diff.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/diff.o -c $(OBJDIR)/diff_.c $(OBJDIR)/diff.h: $(OBJDIR)/headers -$(OBJDIR)/diffcmd_.c: $(SRCDIR)/diffcmd.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/diffcmd.c >$(OBJDIR)/diffcmd_.c +$(OBJDIR)/diffcmd_.c: $(SRCDIR)/diffcmd.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/diffcmd.c >$@ -$(OBJDIR)/diffcmd.o: $(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.h $(SRCDIR)/config.h +$(OBJDIR)/diffcmd.o: $(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/diffcmd.o -c $(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.h: $(OBJDIR)/headers -$(OBJDIR)/doc_.c: $(SRCDIR)/doc.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/doc.c >$(OBJDIR)/doc_.c +$(OBJDIR)/doc_.c: $(SRCDIR)/doc.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/doc.c >$@ -$(OBJDIR)/doc.o: $(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h +$(OBJDIR)/doc.o: $(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c $(OBJDIR)/doc.h: $(OBJDIR)/headers -$(OBJDIR)/encode_.c: $(SRCDIR)/encode.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/encode.c >$(OBJDIR)/encode_.c +$(OBJDIR)/encode_.c: $(SRCDIR)/encode.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/encode.c >$@ -$(OBJDIR)/encode.o: $(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h +$(OBJDIR)/encode.o: $(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c $(OBJDIR)/encode.h: $(OBJDIR)/headers -$(OBJDIR)/event_.c: $(SRCDIR)/event.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/event.c >$(OBJDIR)/event_.c +$(OBJDIR)/event_.c: $(SRCDIR)/event.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/event.c >$@ -$(OBJDIR)/event.o: $(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h +$(OBJDIR)/event.o: $(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/event.o -c $(OBJDIR)/event_.c $(OBJDIR)/event.h: $(OBJDIR)/headers -$(OBJDIR)/export_.c: $(SRCDIR)/export.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/export.c >$(OBJDIR)/export_.c +$(OBJDIR)/export_.c: $(SRCDIR)/export.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/export.c >$@ -$(OBJDIR)/export.o: $(OBJDIR)/export_.c $(OBJDIR)/export.h $(SRCDIR)/config.h +$(OBJDIR)/export.o: $(OBJDIR)/export_.c $(OBJDIR)/export.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/export.o -c $(OBJDIR)/export_.c $(OBJDIR)/export.h: $(OBJDIR)/headers -$(OBJDIR)/file_.c: $(SRCDIR)/file.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/file.c >$(OBJDIR)/file_.c +$(OBJDIR)/file_.c: $(SRCDIR)/file.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/file.c >$@ -$(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h +$(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c $(OBJDIR)/file.h: $(OBJDIR)/headers -$(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/finfo.c >$(OBJDIR)/finfo_.c +$(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/finfo.c >$@ -$(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h +$(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/finfo.o -c $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h: $(OBJDIR)/headers -$(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/glob.c >$(OBJDIR)/glob_.c +$(OBJDIR)/foci_.c: $(SRCDIR)/foci.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/foci.c >$@ -$(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h +$(OBJDIR)/foci.o: $(OBJDIR)/foci_.c $(OBJDIR)/foci.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/foci.o -c $(OBJDIR)/foci_.c + +$(OBJDIR)/foci.h: $(OBJDIR)/headers + +$(OBJDIR)/fusefs_.c: $(SRCDIR)/fusefs.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/fusefs.c >$@ + +$(OBJDIR)/fusefs.o: $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/fusefs.o -c $(OBJDIR)/fusefs_.c + +$(OBJDIR)/fusefs.h: $(OBJDIR)/headers + +$(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/glob.c >$@ + +$(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/glob.o -c $(OBJDIR)/glob_.c $(OBJDIR)/glob.h: $(OBJDIR)/headers -$(OBJDIR)/graph_.c: $(SRCDIR)/graph.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/graph.c >$(OBJDIR)/graph_.c +$(OBJDIR)/graph_.c: $(SRCDIR)/graph.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/graph.c >$@ -$(OBJDIR)/graph.o: $(OBJDIR)/graph_.c $(OBJDIR)/graph.h $(SRCDIR)/config.h +$(OBJDIR)/graph.o: $(OBJDIR)/graph_.c $(OBJDIR)/graph.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/graph.o -c $(OBJDIR)/graph_.c $(OBJDIR)/graph.h: $(OBJDIR)/headers -$(OBJDIR)/gzip_.c: $(SRCDIR)/gzip.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/gzip.c >$(OBJDIR)/gzip_.c +$(OBJDIR)/gzip_.c: $(SRCDIR)/gzip.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/gzip.c >$@ -$(OBJDIR)/gzip.o: $(OBJDIR)/gzip_.c $(OBJDIR)/gzip.h $(SRCDIR)/config.h +$(OBJDIR)/gzip.o: $(OBJDIR)/gzip_.c $(OBJDIR)/gzip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/gzip.o -c $(OBJDIR)/gzip_.c $(OBJDIR)/gzip.h: $(OBJDIR)/headers -$(OBJDIR)/http_.c: $(SRCDIR)/http.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/http.c >$(OBJDIR)/http_.c +$(OBJDIR)/http_.c: $(SRCDIR)/http.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/http.c >$@ -$(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h +$(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http.o -c $(OBJDIR)/http_.c $(OBJDIR)/http.h: $(OBJDIR)/headers -$(OBJDIR)/http_socket_.c: $(SRCDIR)/http_socket.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/http_socket.c >$(OBJDIR)/http_socket_.c +$(OBJDIR)/http_socket_.c: $(SRCDIR)/http_socket.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/http_socket.c >$@ -$(OBJDIR)/http_socket.o: $(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.h $(SRCDIR)/config.h +$(OBJDIR)/http_socket.o: $(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_socket.o -c $(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.h: $(OBJDIR)/headers -$(OBJDIR)/http_ssl_.c: $(SRCDIR)/http_ssl.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/http_ssl.c >$(OBJDIR)/http_ssl_.c +$(OBJDIR)/http_ssl_.c: $(SRCDIR)/http_ssl.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/http_ssl.c >$@ -$(OBJDIR)/http_ssl.o: $(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.h $(SRCDIR)/config.h +$(OBJDIR)/http_ssl.o: $(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_ssl.o -c $(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.h: $(OBJDIR)/headers -$(OBJDIR)/http_transport_.c: $(SRCDIR)/http_transport.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/http_transport.c >$(OBJDIR)/http_transport_.c +$(OBJDIR)/http_transport_.c: $(SRCDIR)/http_transport.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/http_transport.c >$@ -$(OBJDIR)/http_transport.o: $(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.h $(SRCDIR)/config.h +$(OBJDIR)/http_transport.o: $(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_transport.o -c $(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.h: $(OBJDIR)/headers -$(OBJDIR)/import_.c: $(SRCDIR)/import.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/import.c >$(OBJDIR)/import_.c +$(OBJDIR)/import_.c: $(SRCDIR)/import.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/import.c >$@ -$(OBJDIR)/import.o: $(OBJDIR)/import_.c $(OBJDIR)/import.h $(SRCDIR)/config.h +$(OBJDIR)/import.o: $(OBJDIR)/import_.c $(OBJDIR)/import.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/import.o -c $(OBJDIR)/import_.c $(OBJDIR)/import.h: $(OBJDIR)/headers -$(OBJDIR)/info_.c: $(SRCDIR)/info.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/info.c >$(OBJDIR)/info_.c +$(OBJDIR)/info_.c: $(SRCDIR)/info.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/info.c >$@ -$(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h +$(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c $(OBJDIR)/info.h: $(OBJDIR)/headers -$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json.c >$(OBJDIR)/json_.c +$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json.c >$@ -$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h +$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json.o -c $(OBJDIR)/json_.c $(OBJDIR)/json.h: $(OBJDIR)/headers -$(OBJDIR)/json_artifact_.c: $(SRCDIR)/json_artifact.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_artifact.c >$(OBJDIR)/json_artifact_.c +$(OBJDIR)/json_artifact_.c: $(SRCDIR)/json_artifact.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_artifact.c >$@ -$(OBJDIR)/json_artifact.o: $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h $(SRCDIR)/config.h +$(OBJDIR)/json_artifact.o: $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_artifact.o -c $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h: $(OBJDIR)/headers -$(OBJDIR)/json_branch_.c: $(SRCDIR)/json_branch.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_branch.c >$(OBJDIR)/json_branch_.c +$(OBJDIR)/json_branch_.c: $(SRCDIR)/json_branch.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_branch.c >$@ -$(OBJDIR)/json_branch.o: $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h $(SRCDIR)/config.h +$(OBJDIR)/json_branch.o: $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_branch.o -c $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h: $(OBJDIR)/headers -$(OBJDIR)/json_config_.c: $(SRCDIR)/json_config.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_config.c >$(OBJDIR)/json_config_.c +$(OBJDIR)/json_config_.c: $(SRCDIR)/json_config.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_config.c >$@ -$(OBJDIR)/json_config.o: $(OBJDIR)/json_config_.c $(OBJDIR)/json_config.h $(SRCDIR)/config.h +$(OBJDIR)/json_config.o: $(OBJDIR)/json_config_.c $(OBJDIR)/json_config.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_config.o -c $(OBJDIR)/json_config_.c $(OBJDIR)/json_config.h: $(OBJDIR)/headers -$(OBJDIR)/json_diff_.c: $(SRCDIR)/json_diff.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_diff.c >$(OBJDIR)/json_diff_.c +$(OBJDIR)/json_diff_.c: $(SRCDIR)/json_diff.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_diff.c >$@ -$(OBJDIR)/json_diff.o: $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h $(SRCDIR)/config.h +$(OBJDIR)/json_diff.o: $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_diff.o -c $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h: $(OBJDIR)/headers -$(OBJDIR)/json_dir_.c: $(SRCDIR)/json_dir.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_dir.c >$(OBJDIR)/json_dir_.c +$(OBJDIR)/json_dir_.c: $(SRCDIR)/json_dir.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_dir.c >$@ -$(OBJDIR)/json_dir.o: $(OBJDIR)/json_dir_.c $(OBJDIR)/json_dir.h $(SRCDIR)/config.h +$(OBJDIR)/json_dir.o: $(OBJDIR)/json_dir_.c $(OBJDIR)/json_dir.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_dir.o -c $(OBJDIR)/json_dir_.c $(OBJDIR)/json_dir.h: $(OBJDIR)/headers -$(OBJDIR)/json_finfo_.c: $(SRCDIR)/json_finfo.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_finfo.c >$(OBJDIR)/json_finfo_.c +$(OBJDIR)/json_finfo_.c: $(SRCDIR)/json_finfo.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_finfo.c >$@ -$(OBJDIR)/json_finfo.o: $(OBJDIR)/json_finfo_.c $(OBJDIR)/json_finfo.h $(SRCDIR)/config.h +$(OBJDIR)/json_finfo.o: $(OBJDIR)/json_finfo_.c $(OBJDIR)/json_finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_finfo.o -c $(OBJDIR)/json_finfo_.c $(OBJDIR)/json_finfo.h: $(OBJDIR)/headers -$(OBJDIR)/json_login_.c: $(SRCDIR)/json_login.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_login.c >$(OBJDIR)/json_login_.c +$(OBJDIR)/json_login_.c: $(SRCDIR)/json_login.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_login.c >$@ -$(OBJDIR)/json_login.o: $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h $(SRCDIR)/config.h +$(OBJDIR)/json_login.o: $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_login.o -c $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h: $(OBJDIR)/headers -$(OBJDIR)/json_query_.c: $(SRCDIR)/json_query.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_query.c >$(OBJDIR)/json_query_.c +$(OBJDIR)/json_query_.c: $(SRCDIR)/json_query.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_query.c >$@ -$(OBJDIR)/json_query.o: $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h $(SRCDIR)/config.h +$(OBJDIR)/json_query.o: $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_query.o -c $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h: $(OBJDIR)/headers -$(OBJDIR)/json_report_.c: $(SRCDIR)/json_report.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_report.c >$(OBJDIR)/json_report_.c +$(OBJDIR)/json_report_.c: $(SRCDIR)/json_report.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_report.c >$@ -$(OBJDIR)/json_report.o: $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h $(SRCDIR)/config.h +$(OBJDIR)/json_report.o: $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_report.o -c $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h: $(OBJDIR)/headers -$(OBJDIR)/json_status_.c: $(SRCDIR)/json_status.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_status.c >$(OBJDIR)/json_status_.c +$(OBJDIR)/json_status_.c: $(SRCDIR)/json_status.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_status.c >$@ -$(OBJDIR)/json_status.o: $(OBJDIR)/json_status_.c $(OBJDIR)/json_status.h $(SRCDIR)/config.h +$(OBJDIR)/json_status.o: $(OBJDIR)/json_status_.c $(OBJDIR)/json_status.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_status.o -c $(OBJDIR)/json_status_.c $(OBJDIR)/json_status.h: $(OBJDIR)/headers -$(OBJDIR)/json_tag_.c: $(SRCDIR)/json_tag.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_tag.c >$(OBJDIR)/json_tag_.c +$(OBJDIR)/json_tag_.c: $(SRCDIR)/json_tag.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_tag.c >$@ -$(OBJDIR)/json_tag.o: $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h $(SRCDIR)/config.h +$(OBJDIR)/json_tag.o: $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_tag.o -c $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h: $(OBJDIR)/headers -$(OBJDIR)/json_timeline_.c: $(SRCDIR)/json_timeline.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_timeline.c >$(OBJDIR)/json_timeline_.c +$(OBJDIR)/json_timeline_.c: $(SRCDIR)/json_timeline.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_timeline.c >$@ -$(OBJDIR)/json_timeline.o: $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h $(SRCDIR)/config.h +$(OBJDIR)/json_timeline.o: $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_timeline.o -c $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h: $(OBJDIR)/headers -$(OBJDIR)/json_user_.c: $(SRCDIR)/json_user.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_user.c >$(OBJDIR)/json_user_.c +$(OBJDIR)/json_user_.c: $(SRCDIR)/json_user.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_user.c >$@ -$(OBJDIR)/json_user.o: $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h $(SRCDIR)/config.h +$(OBJDIR)/json_user.o: $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_user.o -c $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h: $(OBJDIR)/headers -$(OBJDIR)/json_wiki_.c: $(SRCDIR)/json_wiki.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_wiki.c >$(OBJDIR)/json_wiki_.c +$(OBJDIR)/json_wiki_.c: $(SRCDIR)/json_wiki.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_wiki.c >$@ -$(OBJDIR)/json_wiki.o: $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h $(SRCDIR)/config.h +$(OBJDIR)/json_wiki.o: $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_wiki.o -c $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h: $(OBJDIR)/headers -$(OBJDIR)/leaf_.c: $(SRCDIR)/leaf.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c +$(OBJDIR)/leaf_.c: $(SRCDIR)/leaf.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/leaf.c >$@ -$(OBJDIR)/leaf.o: $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h $(SRCDIR)/config.h +$(OBJDIR)/leaf.o: $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h: $(OBJDIR)/headers -$(OBJDIR)/login_.c: $(SRCDIR)/login.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/login.c >$(OBJDIR)/login_.c +$(OBJDIR)/loadctrl_.c: $(SRCDIR)/loadctrl.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/loadctrl.c >$@ -$(OBJDIR)/login.o: $(OBJDIR)/login_.c $(OBJDIR)/login.h $(SRCDIR)/config.h +$(OBJDIR)/loadctrl.o: $(OBJDIR)/loadctrl_.c $(OBJDIR)/loadctrl.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/loadctrl.o -c $(OBJDIR)/loadctrl_.c + +$(OBJDIR)/loadctrl.h: $(OBJDIR)/headers + +$(OBJDIR)/login_.c: $(SRCDIR)/login.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/login.c >$@ + +$(OBJDIR)/login.o: $(OBJDIR)/login_.c $(OBJDIR)/login.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/login.o -c $(OBJDIR)/login_.c $(OBJDIR)/login.h: $(OBJDIR)/headers -$(OBJDIR)/lookslike_.c: $(SRCDIR)/lookslike.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/lookslike.c >$(OBJDIR)/lookslike_.c +$(OBJDIR)/lookslike_.c: $(SRCDIR)/lookslike.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/lookslike.c >$@ -$(OBJDIR)/lookslike.o: $(OBJDIR)/lookslike_.c $(OBJDIR)/lookslike.h $(SRCDIR)/config.h +$(OBJDIR)/lookslike.o: $(OBJDIR)/lookslike_.c $(OBJDIR)/lookslike.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/lookslike.o -c $(OBJDIR)/lookslike_.c $(OBJDIR)/lookslike.h: $(OBJDIR)/headers -$(OBJDIR)/main_.c: $(SRCDIR)/main.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/main.c >$(OBJDIR)/main_.c +$(OBJDIR)/main_.c: $(SRCDIR)/main.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/main.c >$@ $(OBJDIR)/main.o: $(OBJDIR)/main_.c $(OBJDIR)/main.h $(OBJDIR)/page_index.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/main.o -c $(OBJDIR)/main_.c $(OBJDIR)/main.h: $(OBJDIR)/headers -$(OBJDIR)/manifest_.c: $(SRCDIR)/manifest.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/manifest.c >$(OBJDIR)/manifest_.c +$(OBJDIR)/manifest_.c: $(SRCDIR)/manifest.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/manifest.c >$@ -$(OBJDIR)/manifest.o: $(OBJDIR)/manifest_.c $(OBJDIR)/manifest.h $(SRCDIR)/config.h +$(OBJDIR)/manifest.o: $(OBJDIR)/manifest_.c $(OBJDIR)/manifest.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/manifest.o -c $(OBJDIR)/manifest_.c $(OBJDIR)/manifest.h: $(OBJDIR)/headers -$(OBJDIR)/markdown_.c: $(SRCDIR)/markdown.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/markdown.c >$(OBJDIR)/markdown_.c +$(OBJDIR)/markdown_.c: $(SRCDIR)/markdown.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/markdown.c >$@ -$(OBJDIR)/markdown.o: $(OBJDIR)/markdown_.c $(OBJDIR)/markdown.h $(SRCDIR)/config.h +$(OBJDIR)/markdown.o: $(OBJDIR)/markdown_.c $(OBJDIR)/markdown.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/markdown.o -c $(OBJDIR)/markdown_.c $(OBJDIR)/markdown.h: $(OBJDIR)/headers -$(OBJDIR)/markdown_html_.c: $(SRCDIR)/markdown_html.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/markdown_html.c >$(OBJDIR)/markdown_html_.c +$(OBJDIR)/markdown_html_.c: $(SRCDIR)/markdown_html.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/markdown_html.c >$@ -$(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h +$(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers -$(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/md5.c >$(OBJDIR)/md5_.c +$(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/md5.c >$@ -$(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h +$(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/md5.o -c $(OBJDIR)/md5_.c $(OBJDIR)/md5.h: $(OBJDIR)/headers -$(OBJDIR)/merge_.c: $(SRCDIR)/merge.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/merge.c >$(OBJDIR)/merge_.c +$(OBJDIR)/merge_.c: $(SRCDIR)/merge.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/merge.c >$@ -$(OBJDIR)/merge.o: $(OBJDIR)/merge_.c $(OBJDIR)/merge.h $(SRCDIR)/config.h +$(OBJDIR)/merge.o: $(OBJDIR)/merge_.c $(OBJDIR)/merge.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/merge.o -c $(OBJDIR)/merge_.c $(OBJDIR)/merge.h: $(OBJDIR)/headers -$(OBJDIR)/merge3_.c: $(SRCDIR)/merge3.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/merge3.c >$(OBJDIR)/merge3_.c +$(OBJDIR)/merge3_.c: $(SRCDIR)/merge3.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/merge3.c >$@ -$(OBJDIR)/merge3.o: $(OBJDIR)/merge3_.c $(OBJDIR)/merge3.h $(SRCDIR)/config.h +$(OBJDIR)/merge3.o: $(OBJDIR)/merge3_.c $(OBJDIR)/merge3.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/merge3.o -c $(OBJDIR)/merge3_.c $(OBJDIR)/merge3.h: $(OBJDIR)/headers -$(OBJDIR)/moderate_.c: $(SRCDIR)/moderate.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/moderate.c >$(OBJDIR)/moderate_.c +$(OBJDIR)/moderate_.c: $(SRCDIR)/moderate.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/moderate.c >$@ -$(OBJDIR)/moderate.o: $(OBJDIR)/moderate_.c $(OBJDIR)/moderate.h $(SRCDIR)/config.h +$(OBJDIR)/moderate.o: $(OBJDIR)/moderate_.c $(OBJDIR)/moderate.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/moderate.o -c $(OBJDIR)/moderate_.c $(OBJDIR)/moderate.h: $(OBJDIR)/headers -$(OBJDIR)/name_.c: $(SRCDIR)/name.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/name.c >$(OBJDIR)/name_.c +$(OBJDIR)/name_.c: $(SRCDIR)/name.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/name.c >$@ -$(OBJDIR)/name.o: $(OBJDIR)/name_.c $(OBJDIR)/name.h $(SRCDIR)/config.h +$(OBJDIR)/name.o: $(OBJDIR)/name_.c $(OBJDIR)/name.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/name.o -c $(OBJDIR)/name_.c $(OBJDIR)/name.h: $(OBJDIR)/headers -$(OBJDIR)/path_.c: $(SRCDIR)/path.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/path.c >$(OBJDIR)/path_.c +$(OBJDIR)/path_.c: $(SRCDIR)/path.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/path.c >$@ -$(OBJDIR)/path.o: $(OBJDIR)/path_.c $(OBJDIR)/path.h $(SRCDIR)/config.h +$(OBJDIR)/path.o: $(OBJDIR)/path_.c $(OBJDIR)/path.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/path.o -c $(OBJDIR)/path_.c $(OBJDIR)/path.h: $(OBJDIR)/headers -$(OBJDIR)/pivot_.c: $(SRCDIR)/pivot.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/pivot.c >$(OBJDIR)/pivot_.c +$(OBJDIR)/pivot_.c: $(SRCDIR)/pivot.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/pivot.c >$@ -$(OBJDIR)/pivot.o: $(OBJDIR)/pivot_.c $(OBJDIR)/pivot.h $(SRCDIR)/config.h +$(OBJDIR)/pivot.o: $(OBJDIR)/pivot_.c $(OBJDIR)/pivot.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/pivot.o -c $(OBJDIR)/pivot_.c $(OBJDIR)/pivot.h: $(OBJDIR)/headers -$(OBJDIR)/popen_.c: $(SRCDIR)/popen.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/popen.c >$(OBJDIR)/popen_.c +$(OBJDIR)/popen_.c: $(SRCDIR)/popen.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/popen.c >$@ -$(OBJDIR)/popen.o: $(OBJDIR)/popen_.c $(OBJDIR)/popen.h $(SRCDIR)/config.h +$(OBJDIR)/popen.o: $(OBJDIR)/popen_.c $(OBJDIR)/popen.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/popen.o -c $(OBJDIR)/popen_.c $(OBJDIR)/popen.h: $(OBJDIR)/headers -$(OBJDIR)/pqueue_.c: $(SRCDIR)/pqueue.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/pqueue.c >$(OBJDIR)/pqueue_.c +$(OBJDIR)/pqueue_.c: $(SRCDIR)/pqueue.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/pqueue.c >$@ -$(OBJDIR)/pqueue.o: $(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.h $(SRCDIR)/config.h +$(OBJDIR)/pqueue.o: $(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/pqueue.o -c $(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.h: $(OBJDIR)/headers -$(OBJDIR)/printf_.c: $(SRCDIR)/printf.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/printf.c >$(OBJDIR)/printf_.c +$(OBJDIR)/printf_.c: $(SRCDIR)/printf.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/printf.c >$@ -$(OBJDIR)/printf.o: $(OBJDIR)/printf_.c $(OBJDIR)/printf.h $(SRCDIR)/config.h +$(OBJDIR)/printf.o: $(OBJDIR)/printf_.c $(OBJDIR)/printf.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/printf.o -c $(OBJDIR)/printf_.c $(OBJDIR)/printf.h: $(OBJDIR)/headers -$(OBJDIR)/rebuild_.c: $(SRCDIR)/rebuild.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/rebuild.c >$(OBJDIR)/rebuild_.c +$(OBJDIR)/publish_.c: $(SRCDIR)/publish.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/publish.c >$@ -$(OBJDIR)/rebuild.o: $(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.h $(SRCDIR)/config.h +$(OBJDIR)/publish.o: $(OBJDIR)/publish_.c $(OBJDIR)/publish.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/publish.o -c $(OBJDIR)/publish_.c + +$(OBJDIR)/publish.h: $(OBJDIR)/headers + +$(OBJDIR)/purge_.c: $(SRCDIR)/purge.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/purge.c >$@ + +$(OBJDIR)/purge.o: $(OBJDIR)/purge_.c $(OBJDIR)/purge.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/purge.o -c $(OBJDIR)/purge_.c + +$(OBJDIR)/purge.h: $(OBJDIR)/headers + +$(OBJDIR)/rebuild_.c: $(SRCDIR)/rebuild.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/rebuild.c >$@ + +$(OBJDIR)/rebuild.o: $(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/rebuild.o -c $(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.h: $(OBJDIR)/headers -$(OBJDIR)/regexp_.c: $(SRCDIR)/regexp.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/regexp.c >$(OBJDIR)/regexp_.c +$(OBJDIR)/regexp_.c: $(SRCDIR)/regexp.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/regexp.c >$@ -$(OBJDIR)/regexp.o: $(OBJDIR)/regexp_.c $(OBJDIR)/regexp.h $(SRCDIR)/config.h +$(OBJDIR)/regexp.o: $(OBJDIR)/regexp_.c $(OBJDIR)/regexp.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/regexp.o -c $(OBJDIR)/regexp_.c $(OBJDIR)/regexp.h: $(OBJDIR)/headers -$(OBJDIR)/report_.c: $(SRCDIR)/report.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/report.c >$(OBJDIR)/report_.c +$(OBJDIR)/report_.c: $(SRCDIR)/report.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/report.c >$@ -$(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h +$(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c $(OBJDIR)/report.h: $(OBJDIR)/headers -$(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/rss.c >$(OBJDIR)/rss_.c +$(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/rss.c >$@ -$(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h +$(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/rss.o -c $(OBJDIR)/rss_.c $(OBJDIR)/rss.h: $(OBJDIR)/headers -$(OBJDIR)/schema_.c: $(SRCDIR)/schema.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/schema.c >$(OBJDIR)/schema_.c +$(OBJDIR)/schema_.c: $(SRCDIR)/schema.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/schema.c >$@ -$(OBJDIR)/schema.o: $(OBJDIR)/schema_.c $(OBJDIR)/schema.h $(SRCDIR)/config.h +$(OBJDIR)/schema.o: $(OBJDIR)/schema_.c $(OBJDIR)/schema.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/schema.o -c $(OBJDIR)/schema_.c $(OBJDIR)/schema.h: $(OBJDIR)/headers -$(OBJDIR)/search_.c: $(SRCDIR)/search.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/search.c >$(OBJDIR)/search_.c +$(OBJDIR)/search_.c: $(SRCDIR)/search.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/search.c >$@ -$(OBJDIR)/search.o: $(OBJDIR)/search_.c $(OBJDIR)/search.h $(SRCDIR)/config.h +$(OBJDIR)/search.o: $(OBJDIR)/search_.c $(OBJDIR)/search.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/search.o -c $(OBJDIR)/search_.c $(OBJDIR)/search.h: $(OBJDIR)/headers -$(OBJDIR)/setup_.c: $(SRCDIR)/setup.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/setup.c >$(OBJDIR)/setup_.c +$(OBJDIR)/setup_.c: $(SRCDIR)/setup.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/setup.c >$@ -$(OBJDIR)/setup.o: $(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h +$(OBJDIR)/setup.o: $(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/setup.o -c $(OBJDIR)/setup_.c $(OBJDIR)/setup.h: $(OBJDIR)/headers -$(OBJDIR)/sha1_.c: $(SRCDIR)/sha1.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/sha1.c >$(OBJDIR)/sha1_.c +$(OBJDIR)/sha1_.c: $(SRCDIR)/sha1.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/sha1.c >$@ -$(OBJDIR)/sha1.o: $(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h +$(OBJDIR)/sha1.o: $(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sha1.o -c $(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h: $(OBJDIR)/headers -$(OBJDIR)/shun_.c: $(SRCDIR)/shun.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/shun.c >$(OBJDIR)/shun_.c +$(OBJDIR)/shun_.c: $(SRCDIR)/shun.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/shun.c >$@ -$(OBJDIR)/shun.o: $(OBJDIR)/shun_.c $(OBJDIR)/shun.h $(SRCDIR)/config.h +$(OBJDIR)/shun.o: $(OBJDIR)/shun_.c $(OBJDIR)/shun.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/shun.o -c $(OBJDIR)/shun_.c $(OBJDIR)/shun.h: $(OBJDIR)/headers -$(OBJDIR)/skins_.c: $(SRCDIR)/skins.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/skins.c >$(OBJDIR)/skins_.c +$(OBJDIR)/sitemap_.c: $(SRCDIR)/sitemap.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/sitemap.c >$@ -$(OBJDIR)/skins.o: $(OBJDIR)/skins_.c $(OBJDIR)/skins.h $(SRCDIR)/config.h +$(OBJDIR)/sitemap.o: $(OBJDIR)/sitemap_.c $(OBJDIR)/sitemap.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/sitemap.o -c $(OBJDIR)/sitemap_.c + +$(OBJDIR)/sitemap.h: $(OBJDIR)/headers + +$(OBJDIR)/skins_.c: $(SRCDIR)/skins.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/skins.c >$@ + +$(OBJDIR)/skins.o: $(OBJDIR)/skins_.c $(OBJDIR)/skins.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/skins.o -c $(OBJDIR)/skins_.c $(OBJDIR)/skins.h: $(OBJDIR)/headers -$(OBJDIR)/sqlcmd_.c: $(SRCDIR)/sqlcmd.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/sqlcmd.c >$(OBJDIR)/sqlcmd_.c +$(OBJDIR)/sqlcmd_.c: $(SRCDIR)/sqlcmd.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/sqlcmd.c >$@ -$(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h +$(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sqlcmd.o -c $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h: $(OBJDIR)/headers -$(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/stash.c >$(OBJDIR)/stash_.c +$(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/stash.c >$@ -$(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h +$(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/stash.o -c $(OBJDIR)/stash_.c $(OBJDIR)/stash.h: $(OBJDIR)/headers -$(OBJDIR)/stat_.c: $(SRCDIR)/stat.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/stat.c >$(OBJDIR)/stat_.c +$(OBJDIR)/stat_.c: $(SRCDIR)/stat.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/stat.c >$@ -$(OBJDIR)/stat.o: $(OBJDIR)/stat_.c $(OBJDIR)/stat.h $(SRCDIR)/config.h +$(OBJDIR)/stat.o: $(OBJDIR)/stat_.c $(OBJDIR)/stat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/stat.o -c $(OBJDIR)/stat_.c $(OBJDIR)/stat.h: $(OBJDIR)/headers -$(OBJDIR)/style_.c: $(SRCDIR)/style.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/style.c >$(OBJDIR)/style_.c +$(OBJDIR)/statrep_.c: $(SRCDIR)/statrep.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/statrep.c >$@ -$(OBJDIR)/style.o: $(OBJDIR)/style_.c $(OBJDIR)/style.h $(SRCDIR)/config.h +$(OBJDIR)/statrep.o: $(OBJDIR)/statrep_.c $(OBJDIR)/statrep.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/statrep.o -c $(OBJDIR)/statrep_.c + +$(OBJDIR)/statrep.h: $(OBJDIR)/headers + +$(OBJDIR)/style_.c: $(SRCDIR)/style.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/style.c >$@ + +$(OBJDIR)/style.o: $(OBJDIR)/style_.c $(OBJDIR)/style.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/style.o -c $(OBJDIR)/style_.c $(OBJDIR)/style.h: $(OBJDIR)/headers -$(OBJDIR)/sync_.c: $(SRCDIR)/sync.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/sync.c >$(OBJDIR)/sync_.c +$(OBJDIR)/sync_.c: $(SRCDIR)/sync.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/sync.c >$@ -$(OBJDIR)/sync.o: $(OBJDIR)/sync_.c $(OBJDIR)/sync.h $(SRCDIR)/config.h +$(OBJDIR)/sync.o: $(OBJDIR)/sync_.c $(OBJDIR)/sync.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sync.o -c $(OBJDIR)/sync_.c $(OBJDIR)/sync.h: $(OBJDIR)/headers -$(OBJDIR)/tag_.c: $(SRCDIR)/tag.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/tag.c >$(OBJDIR)/tag_.c +$(OBJDIR)/tag_.c: $(SRCDIR)/tag.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/tag.c >$@ -$(OBJDIR)/tag.o: $(OBJDIR)/tag_.c $(OBJDIR)/tag.h $(SRCDIR)/config.h +$(OBJDIR)/tag.o: $(OBJDIR)/tag_.c $(OBJDIR)/tag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tag.o -c $(OBJDIR)/tag_.c $(OBJDIR)/tag.h: $(OBJDIR)/headers -$(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/tar.c >$(OBJDIR)/tar_.c +$(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/tar.c >$@ -$(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h +$(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c $(OBJDIR)/tar.h: $(OBJDIR)/headers -$(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/th_main.c >$(OBJDIR)/th_main_.c +$(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/th_main.c >$@ -$(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h +$(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/th_main.o -c $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h: $(OBJDIR)/headers -$(OBJDIR)/timeline_.c: $(SRCDIR)/timeline.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/timeline.c >$(OBJDIR)/timeline_.c +$(OBJDIR)/timeline_.c: $(SRCDIR)/timeline.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/timeline.c >$@ -$(OBJDIR)/timeline.o: $(OBJDIR)/timeline_.c $(OBJDIR)/timeline.h $(SRCDIR)/config.h +$(OBJDIR)/timeline.o: $(OBJDIR)/timeline_.c $(OBJDIR)/timeline.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/timeline.o -c $(OBJDIR)/timeline_.c $(OBJDIR)/timeline.h: $(OBJDIR)/headers -$(OBJDIR)/tkt_.c: $(SRCDIR)/tkt.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/tkt.c >$(OBJDIR)/tkt_.c +$(OBJDIR)/tkt_.c: $(SRCDIR)/tkt.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/tkt.c >$@ -$(OBJDIR)/tkt.o: $(OBJDIR)/tkt_.c $(OBJDIR)/tkt.h $(SRCDIR)/config.h +$(OBJDIR)/tkt.o: $(OBJDIR)/tkt_.c $(OBJDIR)/tkt.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tkt.o -c $(OBJDIR)/tkt_.c $(OBJDIR)/tkt.h: $(OBJDIR)/headers -$(OBJDIR)/tktsetup_.c: $(SRCDIR)/tktsetup.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/tktsetup.c >$(OBJDIR)/tktsetup_.c +$(OBJDIR)/tktsetup_.c: $(SRCDIR)/tktsetup.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/tktsetup.c >$@ -$(OBJDIR)/tktsetup.o: $(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.h $(SRCDIR)/config.h +$(OBJDIR)/tktsetup.o: $(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tktsetup.o -c $(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.h: $(OBJDIR)/headers -$(OBJDIR)/undo_.c: $(SRCDIR)/undo.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/undo.c >$(OBJDIR)/undo_.c +$(OBJDIR)/undo_.c: $(SRCDIR)/undo.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/undo.c >$@ -$(OBJDIR)/undo.o: $(OBJDIR)/undo_.c $(OBJDIR)/undo.h $(SRCDIR)/config.h +$(OBJDIR)/undo.o: $(OBJDIR)/undo_.c $(OBJDIR)/undo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/undo.o -c $(OBJDIR)/undo_.c $(OBJDIR)/undo.h: $(OBJDIR)/headers -$(OBJDIR)/unicode_.c: $(SRCDIR)/unicode.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/unicode.c >$(OBJDIR)/unicode_.c +$(OBJDIR)/unicode_.c: $(SRCDIR)/unicode.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/unicode.c >$@ -$(OBJDIR)/unicode.o: $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h $(SRCDIR)/config.h +$(OBJDIR)/unicode.o: $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/unicode.o -c $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h: $(OBJDIR)/headers -$(OBJDIR)/update_.c: $(SRCDIR)/update.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/update.c >$(OBJDIR)/update_.c +$(OBJDIR)/update_.c: $(SRCDIR)/update.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/update.c >$@ -$(OBJDIR)/update.o: $(OBJDIR)/update_.c $(OBJDIR)/update.h $(SRCDIR)/config.h +$(OBJDIR)/update.o: $(OBJDIR)/update_.c $(OBJDIR)/update.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/update.o -c $(OBJDIR)/update_.c $(OBJDIR)/update.h: $(OBJDIR)/headers -$(OBJDIR)/url_.c: $(SRCDIR)/url.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/url.c >$(OBJDIR)/url_.c +$(OBJDIR)/url_.c: $(SRCDIR)/url.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/url.c >$@ -$(OBJDIR)/url.o: $(OBJDIR)/url_.c $(OBJDIR)/url.h $(SRCDIR)/config.h +$(OBJDIR)/url.o: $(OBJDIR)/url_.c $(OBJDIR)/url.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/url.o -c $(OBJDIR)/url_.c $(OBJDIR)/url.h: $(OBJDIR)/headers -$(OBJDIR)/user_.c: $(SRCDIR)/user.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/user.c >$(OBJDIR)/user_.c +$(OBJDIR)/user_.c: $(SRCDIR)/user.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/user.c >$@ -$(OBJDIR)/user.o: $(OBJDIR)/user_.c $(OBJDIR)/user.h $(SRCDIR)/config.h +$(OBJDIR)/user.o: $(OBJDIR)/user_.c $(OBJDIR)/user.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/user.o -c $(OBJDIR)/user_.c $(OBJDIR)/user.h: $(OBJDIR)/headers -$(OBJDIR)/utf8_.c: $(SRCDIR)/utf8.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/utf8.c >$(OBJDIR)/utf8_.c +$(OBJDIR)/utf8_.c: $(SRCDIR)/utf8.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/utf8.c >$@ -$(OBJDIR)/utf8.o: $(OBJDIR)/utf8_.c $(OBJDIR)/utf8.h $(SRCDIR)/config.h +$(OBJDIR)/utf8.o: $(OBJDIR)/utf8_.c $(OBJDIR)/utf8.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/utf8.o -c $(OBJDIR)/utf8_.c $(OBJDIR)/utf8.h: $(OBJDIR)/headers -$(OBJDIR)/util_.c: $(SRCDIR)/util.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/util.c >$(OBJDIR)/util_.c +$(OBJDIR)/util_.c: $(SRCDIR)/util.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/util.c >$@ -$(OBJDIR)/util.o: $(OBJDIR)/util_.c $(OBJDIR)/util.h $(SRCDIR)/config.h +$(OBJDIR)/util.o: $(OBJDIR)/util_.c $(OBJDIR)/util.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/util.o -c $(OBJDIR)/util_.c $(OBJDIR)/util.h: $(OBJDIR)/headers -$(OBJDIR)/verify_.c: $(SRCDIR)/verify.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/verify.c >$(OBJDIR)/verify_.c +$(OBJDIR)/verify_.c: $(SRCDIR)/verify.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/verify.c >$@ -$(OBJDIR)/verify.o: $(OBJDIR)/verify_.c $(OBJDIR)/verify.h $(SRCDIR)/config.h +$(OBJDIR)/verify.o: $(OBJDIR)/verify_.c $(OBJDIR)/verify.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/verify.o -c $(OBJDIR)/verify_.c $(OBJDIR)/verify.h: $(OBJDIR)/headers -$(OBJDIR)/vfile_.c: $(SRCDIR)/vfile.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/vfile.c >$(OBJDIR)/vfile_.c +$(OBJDIR)/vfile_.c: $(SRCDIR)/vfile.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/vfile.c >$@ -$(OBJDIR)/vfile.o: $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h $(SRCDIR)/config.h +$(OBJDIR)/vfile.o: $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/vfile.o -c $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h: $(OBJDIR)/headers -$(OBJDIR)/wiki_.c: $(SRCDIR)/wiki.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/wiki.c >$(OBJDIR)/wiki_.c +$(OBJDIR)/wiki_.c: $(SRCDIR)/wiki.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/wiki.c >$@ -$(OBJDIR)/wiki.o: $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h $(SRCDIR)/config.h +$(OBJDIR)/wiki.o: $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/wiki.o -c $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h: $(OBJDIR)/headers -$(OBJDIR)/wikiformat_.c: $(SRCDIR)/wikiformat.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/wikiformat.c >$(OBJDIR)/wikiformat_.c +$(OBJDIR)/wikiformat_.c: $(SRCDIR)/wikiformat.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/wikiformat.c >$@ -$(OBJDIR)/wikiformat.o: $(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.h $(SRCDIR)/config.h +$(OBJDIR)/wikiformat.o: $(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/wikiformat.o -c $(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.h: $(OBJDIR)/headers -$(OBJDIR)/winhttp_.c: $(SRCDIR)/winhttp.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/winhttp.c >$(OBJDIR)/winhttp_.c +$(OBJDIR)/winfile_.c: $(SRCDIR)/winfile.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/winfile.c >$@ + +$(OBJDIR)/winfile.o: $(OBJDIR)/winfile_.c $(OBJDIR)/winfile.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/winfile.o -c $(OBJDIR)/winfile_.c + +$(OBJDIR)/winfile.h: $(OBJDIR)/headers + +$(OBJDIR)/winhttp_.c: $(SRCDIR)/winhttp.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/winhttp.c >$@ -$(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h +$(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/winhttp.o -c $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h: $(OBJDIR)/headers -$(OBJDIR)/wysiwyg_.c: $(SRCDIR)/wysiwyg.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/wysiwyg.c >$(OBJDIR)/wysiwyg_.c +$(OBJDIR)/wysiwyg_.c: $(SRCDIR)/wysiwyg.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/wysiwyg.c >$@ -$(OBJDIR)/wysiwyg.o: $(OBJDIR)/wysiwyg_.c $(OBJDIR)/wysiwyg.h $(SRCDIR)/config.h +$(OBJDIR)/wysiwyg.o: $(OBJDIR)/wysiwyg_.c $(OBJDIR)/wysiwyg.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/wysiwyg.o -c $(OBJDIR)/wysiwyg_.c $(OBJDIR)/wysiwyg.h: $(OBJDIR)/headers -$(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/xfer.c >$(OBJDIR)/xfer_.c +$(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/xfer.c >$@ -$(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h +$(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h: $(OBJDIR)/headers -$(OBJDIR)/xfersetup_.c: $(SRCDIR)/xfersetup.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/xfersetup.c >$(OBJDIR)/xfersetup_.c +$(OBJDIR)/xfersetup_.c: $(SRCDIR)/xfersetup.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/xfersetup.c >$@ -$(OBJDIR)/xfersetup.o: $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h +$(OBJDIR)/xfersetup.o: $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfersetup.o -c $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h: $(OBJDIR)/headers -$(OBJDIR)/zip_.c: $(SRCDIR)/zip.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/zip.c >$(OBJDIR)/zip_.c +$(OBJDIR)/zip_.c: $(SRCDIR)/zip.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/zip.c >$@ -$(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h +$(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c $(OBJDIR)/zip.h: $(OBJDIR)/headers -$(OBJDIR)/sqlite3.o: $(SRCDIR)/sqlite3.c - $(XTCC) -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_STAT3 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_WIN32_NO_ANSI -c $(SRCDIR)/sqlite3.c -o $(OBJDIR)/sqlite3.o +SQLITE_OPTIONS = -DNDEBUG=1 \ + -DSQLITE_OMIT_LOAD_EXTENSION=1 \ + -DSQLITE_ENABLE_LOCKING_STYLE=0 \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_DEFAULT_FILE_FORMAT=4 \ + -DSQLITE_OMIT_DEPRECATED \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -DSQLITE_ENABLE_FTS4 \ + -DSQLITE_ENABLE_FTS3_PARENTHESIS \ + -DSQLITE_WIN32_NO_ANSI \ + -D_HAVE__MINGW_H \ + -DSQLITE_USE_MALLOC_H \ + -DSQLITE_USE_MSIZE + +SHELL_OPTIONS = -Dmain=sqlite3_shell \ + -DSQLITE_OMIT_LOAD_EXTENSION=1 \ + -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \ + -DSQLITE_SHELL_DBNAME_PROC=fossil_open \ + -Daccess=file_access \ + -Dsystem=fossil_system \ + -Dgetenv=fossil_getenv \ + -Dfopen=fossil_fopen + +MINIZ_OPTIONS = -DMINIZ_NO_STDIO \ + -DMINIZ_NO_TIME \ + -DMINIZ_NO_ARCHIVE_APIS + +$(OBJDIR)/sqlite3.o: $(SRCDIR)/sqlite3.c $(SRCDIR)/../win/Makefile.mingw + $(XTCC) $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) -c $(SRCDIR)/sqlite3.c -o $@ $(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c - $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o + $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $@ $(OBJDIR)/json.o $(OBJDIR)/json_artifact.o $(OBJDIR)/json_branch.o $(OBJDIR)/json_config.o $(OBJDIR)/json_diff.o $(OBJDIR)/json_dir.o $(OBJDIR)/jsos_finfo.o $(OBJDIR)/json_login.o $(OBJDIR)/json_query.o $(OBJDIR)/json_report.o $(OBJDIR)/json_status.o $(OBJDIR)/json_tag.o $(OBJDIR)/json_timeline.o $(OBJDIR)/json_user.o $(OBJDIR)/json_wiki.o : $(SRCDIR)/json_detail.h -$(OBJDIR)/shell.o: $(SRCDIR)/shell.c $(SRCDIR)/sqlite3.h - $(XTCC) -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -c $(SRCDIR)/shell.c -o $(OBJDIR)/shell.o +$(OBJDIR)/shell.o: $(SRCDIR)/shell.c $(SRCDIR)/sqlite3.h $(SRCDIR)/../win/Makefile.mingw + $(XTCC) $(SHELL_OPTIONS) $(SHELL_CFLAGS) -c $(SRCDIR)/shell.c -o $@ $(OBJDIR)/th.o: $(SRCDIR)/th.c - $(XTCC) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o + $(XTCC) -c $(SRCDIR)/th.c -o $@ $(OBJDIR)/th_lang.o: $(SRCDIR)/th_lang.c - $(XTCC) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o + $(XTCC) -c $(SRCDIR)/th_lang.c -o $@ -ifdef FOSSIL_ENABLE_TCL $(OBJDIR)/th_tcl.o: $(SRCDIR)/th_tcl.c - $(XTCC) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o -endif + $(XTCC) -c $(SRCDIR)/th_tcl.c -o $@ + +$(OBJDIR)/miniz.o: $(SRCDIR)/miniz.c + $(XTCC) $(MINIZ_OPTIONS) -c $(SRCDIR)/miniz.c -o $@ + Index: win/Makefile.mingw.mistachkin ================================================================== --- win/Makefile.mingw.mistachkin +++ win/Makefile.mingw.mistachkin @@ -47,10 +47,23 @@ FOSSIL_ENABLE_JSON = 1 #### Enable HTTPS support via OpenSSL (links to libssl and libcrypto) # FOSSIL_ENABLE_SSL = 1 + +#### Automatically build OpenSSL when building Fossil (causes rebuild +# issues when building incrementally). +# +# FOSSIL_BUILD_SSL = 1 + +#### Enable TH1 scripts in embedded documentation files +# +FOSSIL_ENABLE_TH1_DOCS = 1 + +#### Enable hooks for commands and web pages via TH1 +# +FOSSIL_ENABLE_TH1_HOOKS = 1 #### Enable scripting support via Tcl/Tk # FOSSIL_ENABLE_TCL = 1 @@ -60,17 +73,26 @@ #### Load Tcl using the private stubs mechanism # FOSSIL_ENABLE_TCL_PRIVATE_STUBS = 1 +#### Use 'system' SQLite +# +# USE_SYSTEM_SQLITE = 1 + +#### Use the miniz compression library +# +# FOSSIL_ENABLE_MINIZ = 1 + #### Use the Tcl source directory instead of the install directory? # This is useful when Tcl has been compiled statically with MinGW. # FOSSIL_TCL_SOURCE = 1 #### Check if the workaround for the MinGW command line handling needs to -# be enabled by default. +# be enabled by default. This check may be somewhat fragile due to the +# use of "findstring". # ifndef MINGW_IS_32BIT_ONLY ifeq (,$(findstring w64-mingw32,$(PREFIX))) MINGW_IS_32BIT_ONLY = 1 endif @@ -78,18 +100,59 @@ #### The directories where the zlib include and library files are located. # ZINCDIR = $(SRCDIR)/../compat/zlib ZLIBDIR = $(SRCDIR)/../compat/zlib + +#### Make an attempt to detect if Fossil is being built for the x64 processor +# architecture. This check may be somewhat fragile due to "findstring". +# +ifndef X64 +ifneq (,$(findstring x86_64-w64-mingw32,$(PREFIX))) +X64 = 1 +endif +endif + +#### Determine if the optimized assembly routines provided with zlib should be +# used, taking into account whether zlib is actually enabled and the target +# processor architecture. +# +ifndef X64 +SSLCONFIG = mingw +ifndef FOSSIL_ENABLE_MINIZ +ZLIBCONFIG = LOC="-DASMV -DASMINF" OBJA="inffas86.o match.o" +LIBTARGETS = $(ZLIBDIR)/inffas86.o $(ZLIBDIR)/match.o +else +ZLIBCONFIG = +LIBTARGETS = +endif +else +SSLCONFIG = mingw64 +ZLIBCONFIG = +LIBTARGETS = +endif + +#### Disable creation of the OpenSSL shared libraries. Also, disable support +# for both SSLv2 and SSLv3 (i.e. thereby forcing the use of TLS). +# +SSLCONFIG += no-ssl2 no-ssl3 no-shared + +#### When using zlib, make sure that OpenSSL is configured to use the zlib +# that Fossil knows about (i.e. the one within the source tree). +# +ifndef FOSSIL_ENABLE_MINIZ +SSLCONFIG += --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib +endif #### The directories where the OpenSSL include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "openssl-1.x" sub-directory of the # Fossil source code directory and the target OpenSSL source directory. # -OPENSSLINCDIR = $(SRCDIR)/../compat/openssl-1.0.1e/include -OPENSSLLIBDIR = $(SRCDIR)/../compat/openssl-1.0.1e +OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.0.2a +OPENSSLINCDIR = $(OPENSSLDIR)/include +OPENSSLLIBDIR = $(OPENSSLDIR) #### Either the directory where the Tcl library is installed or the Tcl # source code directory resides (depending on the value of the macro # FOSSIL_TCL_SOURCE). If this points to the Tcl install directory, # this directory must have "include" and "lib" sub-directories. If @@ -130,11 +193,17 @@ # will run on the target platform. This is usually the same # as BCC, unless you are cross-compiling. This C compiler builds # the finished binary for fossil. The BCC compiler above is used # for building intermediate code-generator tools. # -TCC = $(PREFIX)gcc -Os -Wall -L$(ZLIBDIR) -I$(ZINCDIR) +TCC = $(PREFIX)gcc -Os -Wall + +#### When not using the miniz compression library, zlib is required. +# +ifndef FOSSIL_ENABLE_MINIZ +TCC += -L$(ZLIBDIR) -I$(ZINCDIR) +endif #### Add the necessary command line options to build with debugging # symbols, if enabled. # ifdef FOSSIL_ENABLE_SYMBOLS @@ -142,11 +211,15 @@ endif #### Compile resources for use in building executables that will run # on the target platform. # -RCC = $(PREFIX)windres -I$(SRCDIR) -I$(ZINCDIR) +RCC = $(PREFIX)windres -I$(SRCDIR) + +ifndef FOSSIL_ENABLE_MINIZ +RCC += -I$(ZINCDIR) +endif # With HTTPS support ifdef FOSSIL_ENABLE_SSL TCC += -L$(OPENSSLLIBDIR) -I$(OPENSSLINCDIR) RCC += -I$(OPENSSLINCDIR) @@ -160,22 +233,40 @@ else TCC += -L$(TCLLIBDIR) -I$(TCLINCDIR) RCC += -I$(TCLINCDIR) endif endif + +# With miniz (i.e. instead of zlib) +ifdef FOSSIL_ENABLE_MINIZ +TCC += -DFOSSIL_ENABLE_MINIZ=1 +RCC += -DFOSSIL_ENABLE_MINIZ=1 +endif # With MinGW command line handling workaround ifdef MINGW_IS_32BIT_ONLY -TCC += -DBROKEN_MINGW_CMDLINE=1 -D_USE_32BIT_TIME_T -RCC += -DBROKEN_MINGW_CMDLINE=1 -D_USE_32BIT_TIME_T +TCC += -DBROKEN_MINGW_CMDLINE=1 +RCC += -DBROKEN_MINGW_CMDLINE=1 endif # With HTTPS support ifdef FOSSIL_ENABLE_SSL TCC += -DFOSSIL_ENABLE_SSL=1 RCC += -DFOSSIL_ENABLE_SSL=1 endif + +# With TH1 embedded docs support +ifdef FOSSIL_ENABLE_TH1_DOCS +TCC += -DFOSSIL_ENABLE_TH1_DOCS=1 +RCC += -DFOSSIL_ENABLE_TH1_DOCS=1 +endif + +# With TH1 hook support +ifdef FOSSIL_ENABLE_TH1_HOOKS +TCC += -DFOSSIL_ENABLE_TH1_HOOKS=1 +RCC += -DFOSSIL_ENABLE_TH1_HOOKS=1 +endif # With Tcl support ifdef FOSSIL_ENABLE_TCL TCC += -DFOSSIL_ENABLE_TCL=1 RCC += -DFOSSIL_ENABLE_TCL=1 @@ -202,30 +293,45 @@ #### We add the -static option here so that we can build a static # executable that will run in a chroot jail. # LIB = -static -# MinGW: If available, use the Unicode capable runtime startup code. +#### MinGW: If available, use the Unicode capable runtime startup code. +# ifndef MINGW_IS_32BIT_ONLY LIB += -municode endif -# OpenSSL: Add the necessary libraries required, if enabled. +#### SQLite: If enabled, use the system SQLite library. +# +ifdef USE_SYSTEM_SQLITE +LIB += -lsqlite3 +endif + +#### OpenSSL: Add the necessary libraries required, if enabled. +# ifdef FOSSIL_ENABLE_SSL LIB += -lssl -lcrypto -lgdi32 endif -# Tcl: Add the necessary libraries required, if enabled. +#### Tcl: Add the necessary libraries required, if enabled. +# ifdef FOSSIL_ENABLE_TCL LIB += $(LIBTCL) endif #### Extra arguments for linking the finished binary. Fossil needs # to link against the Z-Lib compression library. There are no # other mandatory dependencies. # -LIB += -lmingwex -lz +LIB += -lmingwex + +#### When not using the miniz compression library, zlib is required. +# +ifndef FOSSIL_ENABLE_MINIZ +LIB += -lz +endif #### These libraries MUST appear in the same order as they do for Tcl # or linking with it will not work (exact reason unknown). # ifdef FOSSIL_ENABLE_TCL @@ -243,11 +349,15 @@ # TCLSH = tclsh #### Nullsoft installer MakeNSIS location # -MAKENSIS = "$(ProgramFiles)\NSIS\MakeNSIS.exe" +MAKENSIS = "$(PROGRAMFILES)\NSIS\MakeNSIS.exe" + +#### Inno Setup executable location +# +INNOSETUP = "$(PROGRAMFILES)\Inno Setup 5\ISCC.exe" #### Include a configuration file that can override any one of these settings. # -include config.w32 @@ -263,10 +373,13 @@ $(SRCDIR)/bag.c \ $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ + $(SRCDIR)/builtin.c \ + $(SRCDIR)/bundle.c \ + $(SRCDIR)/cache.c \ $(SRCDIR)/captcha.c \ $(SRCDIR)/cgi.c \ $(SRCDIR)/checkin.c \ $(SRCDIR)/checkout.c \ $(SRCDIR)/clearsign.c \ @@ -284,10 +397,12 @@ $(SRCDIR)/encode.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/file.c \ $(SRCDIR)/finfo.c \ + $(SRCDIR)/foci.c \ + $(SRCDIR)/fusefs.c \ $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/gzip.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ @@ -309,10 +424,11 @@ $(SRCDIR)/json_tag.c \ $(SRCDIR)/json_timeline.c \ $(SRCDIR)/json_user.c \ $(SRCDIR)/json_wiki.c \ $(SRCDIR)/leaf.c \ + $(SRCDIR)/loadctrl.c \ $(SRCDIR)/login.c \ $(SRCDIR)/lookslike.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/markdown.c \ @@ -325,23 +441,27 @@ $(SRCDIR)/path.c \ $(SRCDIR)/pivot.c \ $(SRCDIR)/popen.c \ $(SRCDIR)/pqueue.c \ $(SRCDIR)/printf.c \ + $(SRCDIR)/publish.c \ + $(SRCDIR)/purge.c \ $(SRCDIR)/rebuild.c \ $(SRCDIR)/regexp.c \ $(SRCDIR)/report.c \ $(SRCDIR)/rss.c \ $(SRCDIR)/schema.c \ $(SRCDIR)/search.c \ $(SRCDIR)/setup.c \ $(SRCDIR)/sha1.c \ $(SRCDIR)/shun.c \ + $(SRCDIR)/sitemap.c \ $(SRCDIR)/skins.c \ $(SRCDIR)/sqlcmd.c \ $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ + $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ $(SRCDIR)/th_main.c \ @@ -357,25 +477,80 @@ $(SRCDIR)/util.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ + $(SRCDIR)/winfile.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/wysiwyg.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/xfersetup.c \ $(SRCDIR)/zip.c +EXTRA_FILES = \ + $(SRCDIR)/../skins/aht/details.txt \ + $(SRCDIR)/../skins/black_and_white/css.txt \ + $(SRCDIR)/../skins/black_and_white/details.txt \ + $(SRCDIR)/../skins/black_and_white/footer.txt \ + $(SRCDIR)/../skins/black_and_white/header.txt \ + $(SRCDIR)/../skins/blitz/css.txt \ + $(SRCDIR)/../skins/blitz/details.txt \ + $(SRCDIR)/../skins/blitz/footer.txt \ + $(SRCDIR)/../skins/blitz/header.txt \ + $(SRCDIR)/../skins/blitz/ticket.txt \ + $(SRCDIR)/../skins/blitz_no_logo/css.txt \ + $(SRCDIR)/../skins/blitz_no_logo/details.txt \ + $(SRCDIR)/../skins/blitz_no_logo/footer.txt \ + $(SRCDIR)/../skins/blitz_no_logo/header.txt \ + $(SRCDIR)/../skins/blitz_no_logo/ticket.txt \ + $(SRCDIR)/../skins/default/css.txt \ + $(SRCDIR)/../skins/default/details.txt \ + $(SRCDIR)/../skins/default/footer.txt \ + $(SRCDIR)/../skins/default/header.txt \ + $(SRCDIR)/../skins/eagle/css.txt \ + $(SRCDIR)/../skins/eagle/details.txt \ + $(SRCDIR)/../skins/eagle/footer.txt \ + $(SRCDIR)/../skins/eagle/header.txt \ + $(SRCDIR)/../skins/enhanced1/css.txt \ + $(SRCDIR)/../skins/enhanced1/details.txt \ + $(SRCDIR)/../skins/enhanced1/footer.txt \ + $(SRCDIR)/../skins/enhanced1/header.txt \ + $(SRCDIR)/../skins/khaki/css.txt \ + $(SRCDIR)/../skins/khaki/details.txt \ + $(SRCDIR)/../skins/khaki/footer.txt \ + $(SRCDIR)/../skins/khaki/header.txt \ + $(SRCDIR)/../skins/original/css.txt \ + $(SRCDIR)/../skins/original/details.txt \ + $(SRCDIR)/../skins/original/footer.txt \ + $(SRCDIR)/../skins/original/header.txt \ + $(SRCDIR)/../skins/plain_gray/css.txt \ + $(SRCDIR)/../skins/plain_gray/details.txt \ + $(SRCDIR)/../skins/plain_gray/footer.txt \ + $(SRCDIR)/../skins/plain_gray/header.txt \ + $(SRCDIR)/../skins/rounded1/css.txt \ + $(SRCDIR)/../skins/rounded1/details.txt \ + $(SRCDIR)/../skins/rounded1/footer.txt \ + $(SRCDIR)/../skins/rounded1/header.txt \ + $(SRCDIR)/../skins/xekri/css.txt \ + $(SRCDIR)/../skins/xekri/details.txt \ + $(SRCDIR)/../skins/xekri/footer.txt \ + $(SRCDIR)/../skins/xekri/header.txt \ + $(SRCDIR)/diff.tcl \ + $(SRCDIR)/markdown.md + TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/bag_.c \ $(OBJDIR)/bisect_.c \ $(OBJDIR)/blob_.c \ $(OBJDIR)/branch_.c \ $(OBJDIR)/browse_.c \ + $(OBJDIR)/builtin_.c \ + $(OBJDIR)/bundle_.c \ + $(OBJDIR)/cache_.c \ $(OBJDIR)/captcha_.c \ $(OBJDIR)/cgi_.c \ $(OBJDIR)/checkin_.c \ $(OBJDIR)/checkout_.c \ $(OBJDIR)/clearsign_.c \ @@ -393,10 +568,12 @@ $(OBJDIR)/encode_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/finfo_.c \ + $(OBJDIR)/foci_.c \ + $(OBJDIR)/fusefs_.c \ $(OBJDIR)/glob_.c \ $(OBJDIR)/graph_.c \ $(OBJDIR)/gzip_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ @@ -418,10 +595,11 @@ $(OBJDIR)/json_tag_.c \ $(OBJDIR)/json_timeline_.c \ $(OBJDIR)/json_user_.c \ $(OBJDIR)/json_wiki_.c \ $(OBJDIR)/leaf_.c \ + $(OBJDIR)/loadctrl_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/lookslike_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/markdown_.c \ @@ -434,23 +612,27 @@ $(OBJDIR)/path_.c \ $(OBJDIR)/pivot_.c \ $(OBJDIR)/popen_.c \ $(OBJDIR)/pqueue_.c \ $(OBJDIR)/printf_.c \ + $(OBJDIR)/publish_.c \ + $(OBJDIR)/purge_.c \ $(OBJDIR)/rebuild_.c \ $(OBJDIR)/regexp_.c \ $(OBJDIR)/report_.c \ $(OBJDIR)/rss_.c \ $(OBJDIR)/schema_.c \ $(OBJDIR)/search_.c \ $(OBJDIR)/setup_.c \ $(OBJDIR)/sha1_.c \ $(OBJDIR)/shun_.c \ + $(OBJDIR)/sitemap_.c \ $(OBJDIR)/skins_.c \ $(OBJDIR)/sqlcmd_.c \ $(OBJDIR)/stash_.c \ $(OBJDIR)/stat_.c \ + $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ $(OBJDIR)/th_main_.c \ @@ -466,10 +648,11 @@ $(OBJDIR)/util_.c \ $(OBJDIR)/verify_.c \ $(OBJDIR)/vfile_.c \ $(OBJDIR)/wiki_.c \ $(OBJDIR)/wikiformat_.c \ + $(OBJDIR)/winfile_.c \ $(OBJDIR)/winhttp_.c \ $(OBJDIR)/wysiwyg_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/zip_.c @@ -481,10 +664,13 @@ $(OBJDIR)/bag.o \ $(OBJDIR)/bisect.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ + $(OBJDIR)/builtin.o \ + $(OBJDIR)/bundle.o \ + $(OBJDIR)/cache.o \ $(OBJDIR)/captcha.o \ $(OBJDIR)/cgi.o \ $(OBJDIR)/checkin.o \ $(OBJDIR)/checkout.o \ $(OBJDIR)/clearsign.o \ @@ -502,10 +688,12 @@ $(OBJDIR)/encode.o \ $(OBJDIR)/event.o \ $(OBJDIR)/export.o \ $(OBJDIR)/file.o \ $(OBJDIR)/finfo.o \ + $(OBJDIR)/foci.o \ + $(OBJDIR)/fusefs.o \ $(OBJDIR)/glob.o \ $(OBJDIR)/graph.o \ $(OBJDIR)/gzip.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ @@ -527,10 +715,11 @@ $(OBJDIR)/json_tag.o \ $(OBJDIR)/json_timeline.o \ $(OBJDIR)/json_user.o \ $(OBJDIR)/json_wiki.o \ $(OBJDIR)/leaf.o \ + $(OBJDIR)/loadctrl.o \ $(OBJDIR)/login.o \ $(OBJDIR)/lookslike.o \ $(OBJDIR)/main.o \ $(OBJDIR)/manifest.o \ $(OBJDIR)/markdown.o \ @@ -543,23 +732,27 @@ $(OBJDIR)/path.o \ $(OBJDIR)/pivot.o \ $(OBJDIR)/popen.o \ $(OBJDIR)/pqueue.o \ $(OBJDIR)/printf.o \ + $(OBJDIR)/publish.o \ + $(OBJDIR)/purge.o \ $(OBJDIR)/rebuild.o \ $(OBJDIR)/regexp.o \ $(OBJDIR)/report.o \ $(OBJDIR)/rss.o \ $(OBJDIR)/schema.o \ $(OBJDIR)/search.o \ $(OBJDIR)/setup.o \ $(OBJDIR)/sha1.o \ $(OBJDIR)/shun.o \ + $(OBJDIR)/sitemap.o \ $(OBJDIR)/skins.o \ $(OBJDIR)/sqlcmd.o \ $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ + $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ $(OBJDIR)/th_main.o \ @@ -575,41 +768,51 @@ $(OBJDIR)/util.o \ $(OBJDIR)/verify.o \ $(OBJDIR)/vfile.o \ $(OBJDIR)/wiki.o \ $(OBJDIR)/wikiformat.o \ + $(OBJDIR)/winfile.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/wysiwyg.o \ $(OBJDIR)/xfer.o \ $(OBJDIR)/xfersetup.o \ $(OBJDIR)/zip.o -APPNAME = fossil.exe +APPNAME = fossil.exe +APPTARGETS = #### If the USE_WINDOWS variable exists, it is assumed that we are building # inside of a Windows-style shell; otherwise, it is assumed that we are # building inside of a Unix-style shell. Note that the "move" command is # broken when attempting to use it from the Windows shell via MinGW make # because the SHELL variable is only used for certain commands that are # recognized internally by make. # ifdef USE_WINDOWS -TRANSLATE = $(subst /,\,$(OBJDIR)/translate) -MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders) -MKINDEX = $(subst /,\,$(OBJDIR)/mkindex) -VERSION = $(subst /,\,$(OBJDIR)/version) +TRANSLATE = $(subst /,\,$(OBJDIR)/translate.exe) +MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders.exe) +MKINDEX = $(subst /,\,$(OBJDIR)/mkindex.exe) +MKBUILTIN = $(subst /,\,$(OBJDIR)/mkbuiltin.exe) +MKVERSION = $(subst /,\,$(OBJDIR)/mkversion.exe) +CODECHECK1 = $(subst /,\,$(OBJDIR)/codecheck1.exe) +CAT = type CP = copy +GREP = find MV = copy RM = del /Q MKDIR = -mkdir RMDIR = rmdir /S /Q else -TRANSLATE = $(OBJDIR)/translate -MAKEHEADERS = $(OBJDIR)/makeheaders -MKINDEX = $(OBJDIR)/mkindex -VERSION = $(OBJDIR)/version +TRANSLATE = $(OBJDIR)/translate.exe +MAKEHEADERS = $(OBJDIR)/makeheaders.exe +MKINDEX = $(OBJDIR)/mkindex.exe +MKBUILTIN = $(OBJDIR)/mkbuiltin.exe +MKVERSION = $(OBJDIR)/mkversion.exe +CODECHECK1 = $(OBJDIR)/codecheck1.exe +CAT = cat CP = cp +GREP = grep MV = mv RM = rm -f MKDIR = -mkdir -p RMDIR = rm -rf endif @@ -616,15 +819,19 @@ all: $(OBJDIR) $(APPNAME) $(OBJDIR)/fossil.o: $(SRCDIR)/../win/fossil.rc $(OBJDIR)/VERSION.h ifdef USE_WINDOWS + $(CAT) $(subst /,\,$(SRCDIR)\miniz.c) | $(GREP) "define MZ_VERSION" > $(subst /,\,$(OBJDIR)\minizver.h) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.rc) $(subst /,\,$(OBJDIR)) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.ico) $(subst /,\,$(OBJDIR)) + $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.exe.manifest) $(subst /,\,$(OBJDIR)) else + $(CAT) $(SRCDIR)/miniz.c | $(GREP) "define MZ_VERSION" > $(OBJDIR)/minizver.h $(CP) $(SRCDIR)/../win/fossil.rc $(OBJDIR) $(CP) $(SRCDIR)/../win/fossil.ico $(OBJDIR) + $(CP) $(SRCDIR)/../win/fossil.exe.manifest $(OBJDIR) endif $(RCC) $(OBJDIR)/fossil.rc -o $(OBJDIR)/fossil.o install: $(OBJDIR) $(APPNAME) ifdef USE_WINDOWS @@ -640,45 +847,82 @@ $(MKDIR) $(subst /,\,$(OBJDIR)) else $(MKDIR) $(OBJDIR) endif -$(OBJDIR)/translate: $(SRCDIR)/translate.c - $(BCC) -o $(OBJDIR)/translate $(SRCDIR)/translate.c - -$(OBJDIR)/makeheaders: $(SRCDIR)/makeheaders.c - $(BCC) -o $(OBJDIR)/makeheaders $(SRCDIR)/makeheaders.c - -$(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c - $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c - -$(VERSION): $(SRCDIR)/mkversion.c - $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c +$(TRANSLATE): $(SRCDIR)/translate.c + $(BCC) -o $@ $(SRCDIR)/translate.c + +$(MAKEHEADERS): $(SRCDIR)/makeheaders.c + $(BCC) -o $@ $(SRCDIR)/makeheaders.c + +$(MKINDEX): $(SRCDIR)/mkindex.c + $(BCC) -o $@ $(SRCDIR)/mkindex.c + +$(MKBUILTIN): $(SRCDIR)/mkbuiltin.c + $(BCC) -o $@ $(SRCDIR)/mkbuiltin.c + +$(MKVERSION): $(SRCDIR)/mkversion.c + $(BCC) -o $@ $(SRCDIR)/mkversion.c + +$(CODECHECK1): $(SRCDIR)/codecheck1.c + $(BCC) -o $@ $(SRCDIR)/codecheck1.c # WARNING. DANGER. Running the test suite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) -$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION) - $(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h - -EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(OBJDIR)/cson_amalgamation.o - -ifdef FOSSIL_ENABLE_TCL -EXTRAOBJ += $(OBJDIR)/th_tcl.o -endif +$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION) + $(MKVERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$@ + +# The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set +# to 1. If it is set to 1, then there is no need to build or link +# the sqlite3.o object. Instead, the system SQLite will be linked +# using -lsqlite3. +SQLITE3_OBJ.1 = +SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o +SQLITE3_OBJ. = $(SQLITE3_OBJ.0) + +# The FOSSIL_ENABLE_MINIZ variable may be undefined, set to 0, or +# set to 1. If it is set to 1, the miniz library included in the +# source tree should be used; otherwise, it should not. +MINIZ_OBJ.0 = +MINIZ_OBJ.1 = $(OBJDIR)/miniz.o +MINIZ_OBJ. = $(MINIZ_OBJ.0) + + +EXTRAOBJ = \ + $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \ + $(MINIZ_OBJ.$(FOSSIL_ENABLE_MINIZ)) \ + $(OBJDIR)/shell.o \ + $(OBJDIR)/th.o \ + $(OBJDIR)/th_lang.o \ + $(OBJDIR)/th_tcl.o \ + $(OBJDIR)/cson_amalgamation.o + zlib: - $(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc libz.a + $(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) $(ZLIBCONFIG) -f win32/Makefile.gcc libz.a clean-zlib: $(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc clean -openssl: zlib - cd $(OPENSSLLIBDIR);./Configure --cross-compile-prefix=$(PREFIX) --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib mingw +$(ZLIBDIR)/inffas86.o: + $(TCC) -c -o $@ -DASMINF -I$(ZLIBDIR) -O3 $(ZLIBDIR)/contrib/inflate86/inffas86.c + +$(ZLIBDIR)/match.o: + $(TCC) -c -o $@ -DASMV $(ZLIBDIR)/contrib/asm686/match.S + + +ifndef FOSSIL_ENABLE_MINIZ +LIBTARGETS += zlib +endif + +openssl: $(LIBTARGETS) + cd $(OPENSSLLIBDIR);./Configure --cross-compile-prefix=$(PREFIX) $(SSLCONFIG) $(MAKE) -C $(OPENSSLLIBDIR) build_libs clean-openssl: $(MAKE) -C $(OPENSSLLIBDIR) clean @@ -687,12 +931,19 @@ $(MAKE) -C $(TCLSRCDIR)/win $(TCLTARGET) clean-tcl: $(MAKE) -C $(TCLSRCDIR)/win distclean -$(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(OBJDIR)/fossil.o zlib - $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/fossil.o +APPTARGETS += $(LIBTARGETS) + +ifdef FOSSIL_BUILD_SSL +APPTARGETS += openssl +endif + +$(APPNAME): $(APPTARGETS) $(OBJDIR)/headers $(CODECHECK1) $(OBJ) $(EXTRAOBJ) $(OBJDIR)/fossil.o + $(CODECHECK1) $(TRANS_SRC) + $(TCC) -o $@ $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/fossil.o # This rule prevents make from using its default rules to try build # an executable named "manifest" out of the file named "manifest.c" # $(SRCDIR)/../manifest: @@ -706,24 +957,33 @@ $(RM) $(APPNAME) $(RMDIR) $(OBJDIR) endif setup: $(OBJDIR) $(APPNAME) - $(MAKENSIS) ./fossil.nsi + $(MAKENSIS) ./setup/fossil.nsi -$(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex +innosetup: $(OBJDIR) $(APPNAME) + $(INNOSETUP) ./setup/fossil.iss -DAppVersion=$(shell $(CAT) ./VERSION) + +$(OBJDIR)/page_index.h: $(TRANS_SRC) $(MKINDEX) $(MKINDEX) $(TRANS_SRC) >$@ -$(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h +$(OBJDIR)/builtin_data.h: $(MKBUILTIN) $(EXTRA_FILES) + $(MKBUILTIN) --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ + +$(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \ $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \ $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \ $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \ $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \ $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \ $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \ $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \ + $(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \ + $(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \ + $(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \ $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \ $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \ $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \ $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \ $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \ @@ -741,10 +1001,12 @@ $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ + $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ + $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ $(OBJDIR)/http_.c:$(OBJDIR)/http.h \ $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ @@ -766,10 +1028,11 @@ $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h \ $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h \ $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h \ $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h \ $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h \ + $(OBJDIR)/loadctrl_.c:$(OBJDIR)/loadctrl.h \ $(OBJDIR)/login_.c:$(OBJDIR)/login.h \ $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \ $(OBJDIR)/main_.c:$(OBJDIR)/main.h \ $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \ $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \ @@ -782,23 +1045,27 @@ $(OBJDIR)/path_.c:$(OBJDIR)/path.h \ $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h \ $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h \ $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h \ $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h \ + $(OBJDIR)/publish_.c:$(OBJDIR)/publish.h \ + $(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \ $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \ $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \ $(OBJDIR)/report_.c:$(OBJDIR)/report.h \ $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \ $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \ $(OBJDIR)/search_.c:$(OBJDIR)/search.h \ $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \ $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \ $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \ + $(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \ $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \ $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h \ $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \ $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \ + $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ @@ -814,10 +1081,11 @@ $(OBJDIR)/util_.c:$(OBJDIR)/util.h \ $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h \ $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h \ $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h \ $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h \ + $(OBJDIR)/winfile_.c:$(OBJDIR)/winfile.h \ $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h \ $(OBJDIR)/wysiwyg_.c:$(OBJDIR)/wysiwyg.h \ $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h \ $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h \ $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h \ @@ -828,882 +1096,999 @@ $(OBJDIR)/headers: Makefile Makefile: -$(OBJDIR)/add_.c: $(SRCDIR)/add.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/add.c >$(OBJDIR)/add_.c +$(OBJDIR)/add_.c: $(SRCDIR)/add.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/add.c >$@ -$(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h +$(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c $(OBJDIR)/add.h: $(OBJDIR)/headers -$(OBJDIR)/allrepo_.c: $(SRCDIR)/allrepo.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/allrepo.c >$(OBJDIR)/allrepo_.c +$(OBJDIR)/allrepo_.c: $(SRCDIR)/allrepo.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/allrepo.c >$@ -$(OBJDIR)/allrepo.o: $(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h +$(OBJDIR)/allrepo.o: $(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/allrepo.o -c $(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h: $(OBJDIR)/headers -$(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/attach.c >$(OBJDIR)/attach_.c +$(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/attach.c >$@ -$(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h +$(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c $(OBJDIR)/attach.h: $(OBJDIR)/headers -$(OBJDIR)/bag_.c: $(SRCDIR)/bag.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/bag.c >$(OBJDIR)/bag_.c +$(OBJDIR)/bag_.c: $(SRCDIR)/bag.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/bag.c >$@ -$(OBJDIR)/bag.o: $(OBJDIR)/bag_.c $(OBJDIR)/bag.h $(SRCDIR)/config.h +$(OBJDIR)/bag.o: $(OBJDIR)/bag_.c $(OBJDIR)/bag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/bag.o -c $(OBJDIR)/bag_.c $(OBJDIR)/bag.h: $(OBJDIR)/headers -$(OBJDIR)/bisect_.c: $(SRCDIR)/bisect.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/bisect.c >$(OBJDIR)/bisect_.c +$(OBJDIR)/bisect_.c: $(SRCDIR)/bisect.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/bisect.c >$@ -$(OBJDIR)/bisect.o: $(OBJDIR)/bisect_.c $(OBJDIR)/bisect.h $(SRCDIR)/config.h +$(OBJDIR)/bisect.o: $(OBJDIR)/bisect_.c $(OBJDIR)/bisect.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/bisect.o -c $(OBJDIR)/bisect_.c $(OBJDIR)/bisect.h: $(OBJDIR)/headers -$(OBJDIR)/blob_.c: $(SRCDIR)/blob.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/blob.c >$(OBJDIR)/blob_.c +$(OBJDIR)/blob_.c: $(SRCDIR)/blob.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/blob.c >$@ -$(OBJDIR)/blob.o: $(OBJDIR)/blob_.c $(OBJDIR)/blob.h $(SRCDIR)/config.h +$(OBJDIR)/blob.o: $(OBJDIR)/blob_.c $(OBJDIR)/blob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/blob.o -c $(OBJDIR)/blob_.c $(OBJDIR)/blob.h: $(OBJDIR)/headers -$(OBJDIR)/branch_.c: $(SRCDIR)/branch.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/branch.c >$(OBJDIR)/branch_.c +$(OBJDIR)/branch_.c: $(SRCDIR)/branch.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/branch.c >$@ -$(OBJDIR)/branch.o: $(OBJDIR)/branch_.c $(OBJDIR)/branch.h $(SRCDIR)/config.h +$(OBJDIR)/branch.o: $(OBJDIR)/branch_.c $(OBJDIR)/branch.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/branch.o -c $(OBJDIR)/branch_.c $(OBJDIR)/branch.h: $(OBJDIR)/headers -$(OBJDIR)/browse_.c: $(SRCDIR)/browse.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/browse.c >$(OBJDIR)/browse_.c +$(OBJDIR)/browse_.c: $(SRCDIR)/browse.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/browse.c >$@ -$(OBJDIR)/browse.o: $(OBJDIR)/browse_.c $(OBJDIR)/browse.h $(SRCDIR)/config.h +$(OBJDIR)/browse.o: $(OBJDIR)/browse_.c $(OBJDIR)/browse.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/browse.o -c $(OBJDIR)/browse_.c $(OBJDIR)/browse.h: $(OBJDIR)/headers -$(OBJDIR)/captcha_.c: $(SRCDIR)/captcha.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/captcha.c >$(OBJDIR)/captcha_.c +$(OBJDIR)/builtin_.c: $(SRCDIR)/builtin.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/builtin.c >$@ -$(OBJDIR)/captcha.o: $(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h +$(OBJDIR)/builtin.o: $(OBJDIR)/builtin_.c $(OBJDIR)/builtin.h $(OBJDIR)/builtin_data.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/builtin.o -c $(OBJDIR)/builtin_.c + +$(OBJDIR)/builtin.h: $(OBJDIR)/headers + +$(OBJDIR)/bundle_.c: $(SRCDIR)/bundle.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/bundle.c >$@ + +$(OBJDIR)/bundle.o: $(OBJDIR)/bundle_.c $(OBJDIR)/bundle.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/bundle.o -c $(OBJDIR)/bundle_.c + +$(OBJDIR)/bundle.h: $(OBJDIR)/headers + +$(OBJDIR)/cache_.c: $(SRCDIR)/cache.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/cache.c >$@ + +$(OBJDIR)/cache.o: $(OBJDIR)/cache_.c $(OBJDIR)/cache.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/cache.o -c $(OBJDIR)/cache_.c + +$(OBJDIR)/cache.h: $(OBJDIR)/headers + +$(OBJDIR)/captcha_.c: $(SRCDIR)/captcha.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/captcha.c >$@ + +$(OBJDIR)/captcha.o: $(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/captcha.o -c $(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h: $(OBJDIR)/headers -$(OBJDIR)/cgi_.c: $(SRCDIR)/cgi.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/cgi.c >$(OBJDIR)/cgi_.c +$(OBJDIR)/cgi_.c: $(SRCDIR)/cgi.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/cgi.c >$@ -$(OBJDIR)/cgi.o: $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h $(SRCDIR)/config.h +$(OBJDIR)/cgi.o: $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/cgi.o -c $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h: $(OBJDIR)/headers -$(OBJDIR)/checkin_.c: $(SRCDIR)/checkin.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/checkin.c >$(OBJDIR)/checkin_.c +$(OBJDIR)/checkin_.c: $(SRCDIR)/checkin.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/checkin.c >$@ -$(OBJDIR)/checkin.o: $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h $(SRCDIR)/config.h +$(OBJDIR)/checkin.o: $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/checkin.o -c $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h: $(OBJDIR)/headers -$(OBJDIR)/checkout_.c: $(SRCDIR)/checkout.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/checkout.c >$(OBJDIR)/checkout_.c +$(OBJDIR)/checkout_.c: $(SRCDIR)/checkout.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/checkout.c >$@ -$(OBJDIR)/checkout.o: $(OBJDIR)/checkout_.c $(OBJDIR)/checkout.h $(SRCDIR)/config.h +$(OBJDIR)/checkout.o: $(OBJDIR)/checkout_.c $(OBJDIR)/checkout.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/checkout.o -c $(OBJDIR)/checkout_.c $(OBJDIR)/checkout.h: $(OBJDIR)/headers -$(OBJDIR)/clearsign_.c: $(SRCDIR)/clearsign.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/clearsign.c >$(OBJDIR)/clearsign_.c +$(OBJDIR)/clearsign_.c: $(SRCDIR)/clearsign.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/clearsign.c >$@ -$(OBJDIR)/clearsign.o: $(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.h $(SRCDIR)/config.h +$(OBJDIR)/clearsign.o: $(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/clearsign.o -c $(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.h: $(OBJDIR)/headers -$(OBJDIR)/clone_.c: $(SRCDIR)/clone.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/clone.c >$(OBJDIR)/clone_.c +$(OBJDIR)/clone_.c: $(SRCDIR)/clone.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/clone.c >$@ -$(OBJDIR)/clone.o: $(OBJDIR)/clone_.c $(OBJDIR)/clone.h $(SRCDIR)/config.h +$(OBJDIR)/clone.o: $(OBJDIR)/clone_.c $(OBJDIR)/clone.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/clone.o -c $(OBJDIR)/clone_.c $(OBJDIR)/clone.h: $(OBJDIR)/headers -$(OBJDIR)/comformat_.c: $(SRCDIR)/comformat.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/comformat.c >$(OBJDIR)/comformat_.c +$(OBJDIR)/comformat_.c: $(SRCDIR)/comformat.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/comformat.c >$@ -$(OBJDIR)/comformat.o: $(OBJDIR)/comformat_.c $(OBJDIR)/comformat.h $(SRCDIR)/config.h +$(OBJDIR)/comformat.o: $(OBJDIR)/comformat_.c $(OBJDIR)/comformat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/comformat.o -c $(OBJDIR)/comformat_.c $(OBJDIR)/comformat.h: $(OBJDIR)/headers -$(OBJDIR)/configure_.c: $(SRCDIR)/configure.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/configure.c >$(OBJDIR)/configure_.c +$(OBJDIR)/configure_.c: $(SRCDIR)/configure.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/configure.c >$@ -$(OBJDIR)/configure.o: $(OBJDIR)/configure_.c $(OBJDIR)/configure.h $(SRCDIR)/config.h +$(OBJDIR)/configure.o: $(OBJDIR)/configure_.c $(OBJDIR)/configure.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/configure.o -c $(OBJDIR)/configure_.c $(OBJDIR)/configure.h: $(OBJDIR)/headers -$(OBJDIR)/content_.c: $(SRCDIR)/content.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/content.c >$(OBJDIR)/content_.c +$(OBJDIR)/content_.c: $(SRCDIR)/content.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/content.c >$@ -$(OBJDIR)/content.o: $(OBJDIR)/content_.c $(OBJDIR)/content.h $(SRCDIR)/config.h +$(OBJDIR)/content.o: $(OBJDIR)/content_.c $(OBJDIR)/content.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/content.o -c $(OBJDIR)/content_.c $(OBJDIR)/content.h: $(OBJDIR)/headers -$(OBJDIR)/db_.c: $(SRCDIR)/db.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/db.c >$(OBJDIR)/db_.c +$(OBJDIR)/db_.c: $(SRCDIR)/db.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/db.c >$@ -$(OBJDIR)/db.o: $(OBJDIR)/db_.c $(OBJDIR)/db.h $(SRCDIR)/config.h +$(OBJDIR)/db.o: $(OBJDIR)/db_.c $(OBJDIR)/db.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/db.o -c $(OBJDIR)/db_.c $(OBJDIR)/db.h: $(OBJDIR)/headers -$(OBJDIR)/delta_.c: $(SRCDIR)/delta.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/delta.c >$(OBJDIR)/delta_.c +$(OBJDIR)/delta_.c: $(SRCDIR)/delta.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/delta.c >$@ -$(OBJDIR)/delta.o: $(OBJDIR)/delta_.c $(OBJDIR)/delta.h $(SRCDIR)/config.h +$(OBJDIR)/delta.o: $(OBJDIR)/delta_.c $(OBJDIR)/delta.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/delta.o -c $(OBJDIR)/delta_.c $(OBJDIR)/delta.h: $(OBJDIR)/headers -$(OBJDIR)/deltacmd_.c: $(SRCDIR)/deltacmd.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/deltacmd.c >$(OBJDIR)/deltacmd_.c +$(OBJDIR)/deltacmd_.c: $(SRCDIR)/deltacmd.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/deltacmd.c >$@ -$(OBJDIR)/deltacmd.o: $(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.h $(SRCDIR)/config.h +$(OBJDIR)/deltacmd.o: $(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/deltacmd.o -c $(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.h: $(OBJDIR)/headers -$(OBJDIR)/descendants_.c: $(SRCDIR)/descendants.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/descendants.c >$(OBJDIR)/descendants_.c +$(OBJDIR)/descendants_.c: $(SRCDIR)/descendants.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/descendants.c >$@ -$(OBJDIR)/descendants.o: $(OBJDIR)/descendants_.c $(OBJDIR)/descendants.h $(SRCDIR)/config.h +$(OBJDIR)/descendants.o: $(OBJDIR)/descendants_.c $(OBJDIR)/descendants.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/descendants.o -c $(OBJDIR)/descendants_.c $(OBJDIR)/descendants.h: $(OBJDIR)/headers -$(OBJDIR)/diff_.c: $(SRCDIR)/diff.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/diff.c >$(OBJDIR)/diff_.c +$(OBJDIR)/diff_.c: $(SRCDIR)/diff.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/diff.c >$@ -$(OBJDIR)/diff.o: $(OBJDIR)/diff_.c $(OBJDIR)/diff.h $(SRCDIR)/config.h +$(OBJDIR)/diff.o: $(OBJDIR)/diff_.c $(OBJDIR)/diff.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/diff.o -c $(OBJDIR)/diff_.c $(OBJDIR)/diff.h: $(OBJDIR)/headers -$(OBJDIR)/diffcmd_.c: $(SRCDIR)/diffcmd.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/diffcmd.c >$(OBJDIR)/diffcmd_.c +$(OBJDIR)/diffcmd_.c: $(SRCDIR)/diffcmd.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/diffcmd.c >$@ -$(OBJDIR)/diffcmd.o: $(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.h $(SRCDIR)/config.h +$(OBJDIR)/diffcmd.o: $(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/diffcmd.o -c $(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.h: $(OBJDIR)/headers -$(OBJDIR)/doc_.c: $(SRCDIR)/doc.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/doc.c >$(OBJDIR)/doc_.c +$(OBJDIR)/doc_.c: $(SRCDIR)/doc.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/doc.c >$@ -$(OBJDIR)/doc.o: $(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h +$(OBJDIR)/doc.o: $(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c $(OBJDIR)/doc.h: $(OBJDIR)/headers -$(OBJDIR)/encode_.c: $(SRCDIR)/encode.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/encode.c >$(OBJDIR)/encode_.c +$(OBJDIR)/encode_.c: $(SRCDIR)/encode.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/encode.c >$@ -$(OBJDIR)/encode.o: $(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h +$(OBJDIR)/encode.o: $(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c $(OBJDIR)/encode.h: $(OBJDIR)/headers -$(OBJDIR)/event_.c: $(SRCDIR)/event.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/event.c >$(OBJDIR)/event_.c +$(OBJDIR)/event_.c: $(SRCDIR)/event.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/event.c >$@ -$(OBJDIR)/event.o: $(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h +$(OBJDIR)/event.o: $(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/event.o -c $(OBJDIR)/event_.c $(OBJDIR)/event.h: $(OBJDIR)/headers -$(OBJDIR)/export_.c: $(SRCDIR)/export.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/export.c >$(OBJDIR)/export_.c +$(OBJDIR)/export_.c: $(SRCDIR)/export.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/export.c >$@ -$(OBJDIR)/export.o: $(OBJDIR)/export_.c $(OBJDIR)/export.h $(SRCDIR)/config.h +$(OBJDIR)/export.o: $(OBJDIR)/export_.c $(OBJDIR)/export.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/export.o -c $(OBJDIR)/export_.c $(OBJDIR)/export.h: $(OBJDIR)/headers -$(OBJDIR)/file_.c: $(SRCDIR)/file.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/file.c >$(OBJDIR)/file_.c +$(OBJDIR)/file_.c: $(SRCDIR)/file.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/file.c >$@ -$(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h +$(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c $(OBJDIR)/file.h: $(OBJDIR)/headers -$(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/finfo.c >$(OBJDIR)/finfo_.c +$(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/finfo.c >$@ -$(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h +$(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/finfo.o -c $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h: $(OBJDIR)/headers -$(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/glob.c >$(OBJDIR)/glob_.c +$(OBJDIR)/foci_.c: $(SRCDIR)/foci.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/foci.c >$@ -$(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h +$(OBJDIR)/foci.o: $(OBJDIR)/foci_.c $(OBJDIR)/foci.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/foci.o -c $(OBJDIR)/foci_.c + +$(OBJDIR)/foci.h: $(OBJDIR)/headers + +$(OBJDIR)/fusefs_.c: $(SRCDIR)/fusefs.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/fusefs.c >$@ + +$(OBJDIR)/fusefs.o: $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/fusefs.o -c $(OBJDIR)/fusefs_.c + +$(OBJDIR)/fusefs.h: $(OBJDIR)/headers + +$(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/glob.c >$@ + +$(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/glob.o -c $(OBJDIR)/glob_.c $(OBJDIR)/glob.h: $(OBJDIR)/headers -$(OBJDIR)/graph_.c: $(SRCDIR)/graph.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/graph.c >$(OBJDIR)/graph_.c +$(OBJDIR)/graph_.c: $(SRCDIR)/graph.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/graph.c >$@ -$(OBJDIR)/graph.o: $(OBJDIR)/graph_.c $(OBJDIR)/graph.h $(SRCDIR)/config.h +$(OBJDIR)/graph.o: $(OBJDIR)/graph_.c $(OBJDIR)/graph.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/graph.o -c $(OBJDIR)/graph_.c $(OBJDIR)/graph.h: $(OBJDIR)/headers -$(OBJDIR)/gzip_.c: $(SRCDIR)/gzip.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/gzip.c >$(OBJDIR)/gzip_.c +$(OBJDIR)/gzip_.c: $(SRCDIR)/gzip.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/gzip.c >$@ -$(OBJDIR)/gzip.o: $(OBJDIR)/gzip_.c $(OBJDIR)/gzip.h $(SRCDIR)/config.h +$(OBJDIR)/gzip.o: $(OBJDIR)/gzip_.c $(OBJDIR)/gzip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/gzip.o -c $(OBJDIR)/gzip_.c $(OBJDIR)/gzip.h: $(OBJDIR)/headers -$(OBJDIR)/http_.c: $(SRCDIR)/http.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/http.c >$(OBJDIR)/http_.c +$(OBJDIR)/http_.c: $(SRCDIR)/http.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/http.c >$@ -$(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h +$(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http.o -c $(OBJDIR)/http_.c $(OBJDIR)/http.h: $(OBJDIR)/headers -$(OBJDIR)/http_socket_.c: $(SRCDIR)/http_socket.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/http_socket.c >$(OBJDIR)/http_socket_.c +$(OBJDIR)/http_socket_.c: $(SRCDIR)/http_socket.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/http_socket.c >$@ -$(OBJDIR)/http_socket.o: $(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.h $(SRCDIR)/config.h +$(OBJDIR)/http_socket.o: $(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_socket.o -c $(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.h: $(OBJDIR)/headers -$(OBJDIR)/http_ssl_.c: $(SRCDIR)/http_ssl.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/http_ssl.c >$(OBJDIR)/http_ssl_.c +$(OBJDIR)/http_ssl_.c: $(SRCDIR)/http_ssl.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/http_ssl.c >$@ -$(OBJDIR)/http_ssl.o: $(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.h $(SRCDIR)/config.h +$(OBJDIR)/http_ssl.o: $(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_ssl.o -c $(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.h: $(OBJDIR)/headers -$(OBJDIR)/http_transport_.c: $(SRCDIR)/http_transport.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/http_transport.c >$(OBJDIR)/http_transport_.c +$(OBJDIR)/http_transport_.c: $(SRCDIR)/http_transport.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/http_transport.c >$@ -$(OBJDIR)/http_transport.o: $(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.h $(SRCDIR)/config.h +$(OBJDIR)/http_transport.o: $(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_transport.o -c $(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.h: $(OBJDIR)/headers -$(OBJDIR)/import_.c: $(SRCDIR)/import.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/import.c >$(OBJDIR)/import_.c +$(OBJDIR)/import_.c: $(SRCDIR)/import.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/import.c >$@ -$(OBJDIR)/import.o: $(OBJDIR)/import_.c $(OBJDIR)/import.h $(SRCDIR)/config.h +$(OBJDIR)/import.o: $(OBJDIR)/import_.c $(OBJDIR)/import.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/import.o -c $(OBJDIR)/import_.c $(OBJDIR)/import.h: $(OBJDIR)/headers -$(OBJDIR)/info_.c: $(SRCDIR)/info.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/info.c >$(OBJDIR)/info_.c +$(OBJDIR)/info_.c: $(SRCDIR)/info.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/info.c >$@ -$(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h +$(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c $(OBJDIR)/info.h: $(OBJDIR)/headers -$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json.c >$(OBJDIR)/json_.c +$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json.c >$@ -$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h +$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json.o -c $(OBJDIR)/json_.c $(OBJDIR)/json.h: $(OBJDIR)/headers -$(OBJDIR)/json_artifact_.c: $(SRCDIR)/json_artifact.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_artifact.c >$(OBJDIR)/json_artifact_.c +$(OBJDIR)/json_artifact_.c: $(SRCDIR)/json_artifact.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_artifact.c >$@ -$(OBJDIR)/json_artifact.o: $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h $(SRCDIR)/config.h +$(OBJDIR)/json_artifact.o: $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_artifact.o -c $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h: $(OBJDIR)/headers -$(OBJDIR)/json_branch_.c: $(SRCDIR)/json_branch.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_branch.c >$(OBJDIR)/json_branch_.c +$(OBJDIR)/json_branch_.c: $(SRCDIR)/json_branch.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_branch.c >$@ -$(OBJDIR)/json_branch.o: $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h $(SRCDIR)/config.h +$(OBJDIR)/json_branch.o: $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_branch.o -c $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h: $(OBJDIR)/headers -$(OBJDIR)/json_config_.c: $(SRCDIR)/json_config.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_config.c >$(OBJDIR)/json_config_.c +$(OBJDIR)/json_config_.c: $(SRCDIR)/json_config.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_config.c >$@ -$(OBJDIR)/json_config.o: $(OBJDIR)/json_config_.c $(OBJDIR)/json_config.h $(SRCDIR)/config.h +$(OBJDIR)/json_config.o: $(OBJDIR)/json_config_.c $(OBJDIR)/json_config.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_config.o -c $(OBJDIR)/json_config_.c $(OBJDIR)/json_config.h: $(OBJDIR)/headers -$(OBJDIR)/json_diff_.c: $(SRCDIR)/json_diff.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_diff.c >$(OBJDIR)/json_diff_.c +$(OBJDIR)/json_diff_.c: $(SRCDIR)/json_diff.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_diff.c >$@ -$(OBJDIR)/json_diff.o: $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h $(SRCDIR)/config.h +$(OBJDIR)/json_diff.o: $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_diff.o -c $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h: $(OBJDIR)/headers -$(OBJDIR)/json_dir_.c: $(SRCDIR)/json_dir.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_dir.c >$(OBJDIR)/json_dir_.c +$(OBJDIR)/json_dir_.c: $(SRCDIR)/json_dir.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_dir.c >$@ -$(OBJDIR)/json_dir.o: $(OBJDIR)/json_dir_.c $(OBJDIR)/json_dir.h $(SRCDIR)/config.h +$(OBJDIR)/json_dir.o: $(OBJDIR)/json_dir_.c $(OBJDIR)/json_dir.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_dir.o -c $(OBJDIR)/json_dir_.c $(OBJDIR)/json_dir.h: $(OBJDIR)/headers -$(OBJDIR)/json_finfo_.c: $(SRCDIR)/json_finfo.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_finfo.c >$(OBJDIR)/json_finfo_.c +$(OBJDIR)/json_finfo_.c: $(SRCDIR)/json_finfo.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_finfo.c >$@ -$(OBJDIR)/json_finfo.o: $(OBJDIR)/json_finfo_.c $(OBJDIR)/json_finfo.h $(SRCDIR)/config.h +$(OBJDIR)/json_finfo.o: $(OBJDIR)/json_finfo_.c $(OBJDIR)/json_finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_finfo.o -c $(OBJDIR)/json_finfo_.c $(OBJDIR)/json_finfo.h: $(OBJDIR)/headers -$(OBJDIR)/json_login_.c: $(SRCDIR)/json_login.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_login.c >$(OBJDIR)/json_login_.c +$(OBJDIR)/json_login_.c: $(SRCDIR)/json_login.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_login.c >$@ -$(OBJDIR)/json_login.o: $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h $(SRCDIR)/config.h +$(OBJDIR)/json_login.o: $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_login.o -c $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h: $(OBJDIR)/headers -$(OBJDIR)/json_query_.c: $(SRCDIR)/json_query.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_query.c >$(OBJDIR)/json_query_.c +$(OBJDIR)/json_query_.c: $(SRCDIR)/json_query.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_query.c >$@ -$(OBJDIR)/json_query.o: $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h $(SRCDIR)/config.h +$(OBJDIR)/json_query.o: $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_query.o -c $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h: $(OBJDIR)/headers -$(OBJDIR)/json_report_.c: $(SRCDIR)/json_report.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_report.c >$(OBJDIR)/json_report_.c +$(OBJDIR)/json_report_.c: $(SRCDIR)/json_report.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_report.c >$@ -$(OBJDIR)/json_report.o: $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h $(SRCDIR)/config.h +$(OBJDIR)/json_report.o: $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_report.o -c $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h: $(OBJDIR)/headers -$(OBJDIR)/json_status_.c: $(SRCDIR)/json_status.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_status.c >$(OBJDIR)/json_status_.c +$(OBJDIR)/json_status_.c: $(SRCDIR)/json_status.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_status.c >$@ -$(OBJDIR)/json_status.o: $(OBJDIR)/json_status_.c $(OBJDIR)/json_status.h $(SRCDIR)/config.h +$(OBJDIR)/json_status.o: $(OBJDIR)/json_status_.c $(OBJDIR)/json_status.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_status.o -c $(OBJDIR)/json_status_.c $(OBJDIR)/json_status.h: $(OBJDIR)/headers -$(OBJDIR)/json_tag_.c: $(SRCDIR)/json_tag.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_tag.c >$(OBJDIR)/json_tag_.c +$(OBJDIR)/json_tag_.c: $(SRCDIR)/json_tag.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_tag.c >$@ -$(OBJDIR)/json_tag.o: $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h $(SRCDIR)/config.h +$(OBJDIR)/json_tag.o: $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_tag.o -c $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h: $(OBJDIR)/headers -$(OBJDIR)/json_timeline_.c: $(SRCDIR)/json_timeline.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_timeline.c >$(OBJDIR)/json_timeline_.c +$(OBJDIR)/json_timeline_.c: $(SRCDIR)/json_timeline.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_timeline.c >$@ -$(OBJDIR)/json_timeline.o: $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h $(SRCDIR)/config.h +$(OBJDIR)/json_timeline.o: $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_timeline.o -c $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h: $(OBJDIR)/headers -$(OBJDIR)/json_user_.c: $(SRCDIR)/json_user.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_user.c >$(OBJDIR)/json_user_.c +$(OBJDIR)/json_user_.c: $(SRCDIR)/json_user.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_user.c >$@ -$(OBJDIR)/json_user.o: $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h $(SRCDIR)/config.h +$(OBJDIR)/json_user.o: $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_user.o -c $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h: $(OBJDIR)/headers -$(OBJDIR)/json_wiki_.c: $(SRCDIR)/json_wiki.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_wiki.c >$(OBJDIR)/json_wiki_.c +$(OBJDIR)/json_wiki_.c: $(SRCDIR)/json_wiki.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/json_wiki.c >$@ -$(OBJDIR)/json_wiki.o: $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h $(SRCDIR)/config.h +$(OBJDIR)/json_wiki.o: $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_wiki.o -c $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h: $(OBJDIR)/headers -$(OBJDIR)/leaf_.c: $(SRCDIR)/leaf.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c +$(OBJDIR)/leaf_.c: $(SRCDIR)/leaf.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/leaf.c >$@ -$(OBJDIR)/leaf.o: $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h $(SRCDIR)/config.h +$(OBJDIR)/leaf.o: $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h: $(OBJDIR)/headers -$(OBJDIR)/login_.c: $(SRCDIR)/login.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/login.c >$(OBJDIR)/login_.c +$(OBJDIR)/loadctrl_.c: $(SRCDIR)/loadctrl.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/loadctrl.c >$@ -$(OBJDIR)/login.o: $(OBJDIR)/login_.c $(OBJDIR)/login.h $(SRCDIR)/config.h +$(OBJDIR)/loadctrl.o: $(OBJDIR)/loadctrl_.c $(OBJDIR)/loadctrl.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/loadctrl.o -c $(OBJDIR)/loadctrl_.c + +$(OBJDIR)/loadctrl.h: $(OBJDIR)/headers + +$(OBJDIR)/login_.c: $(SRCDIR)/login.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/login.c >$@ + +$(OBJDIR)/login.o: $(OBJDIR)/login_.c $(OBJDIR)/login.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/login.o -c $(OBJDIR)/login_.c $(OBJDIR)/login.h: $(OBJDIR)/headers -$(OBJDIR)/lookslike_.c: $(SRCDIR)/lookslike.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/lookslike.c >$(OBJDIR)/lookslike_.c +$(OBJDIR)/lookslike_.c: $(SRCDIR)/lookslike.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/lookslike.c >$@ -$(OBJDIR)/lookslike.o: $(OBJDIR)/lookslike_.c $(OBJDIR)/lookslike.h $(SRCDIR)/config.h +$(OBJDIR)/lookslike.o: $(OBJDIR)/lookslike_.c $(OBJDIR)/lookslike.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/lookslike.o -c $(OBJDIR)/lookslike_.c $(OBJDIR)/lookslike.h: $(OBJDIR)/headers -$(OBJDIR)/main_.c: $(SRCDIR)/main.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/main.c >$(OBJDIR)/main_.c +$(OBJDIR)/main_.c: $(SRCDIR)/main.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/main.c >$@ $(OBJDIR)/main.o: $(OBJDIR)/main_.c $(OBJDIR)/main.h $(OBJDIR)/page_index.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/main.o -c $(OBJDIR)/main_.c $(OBJDIR)/main.h: $(OBJDIR)/headers -$(OBJDIR)/manifest_.c: $(SRCDIR)/manifest.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/manifest.c >$(OBJDIR)/manifest_.c +$(OBJDIR)/manifest_.c: $(SRCDIR)/manifest.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/manifest.c >$@ -$(OBJDIR)/manifest.o: $(OBJDIR)/manifest_.c $(OBJDIR)/manifest.h $(SRCDIR)/config.h +$(OBJDIR)/manifest.o: $(OBJDIR)/manifest_.c $(OBJDIR)/manifest.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/manifest.o -c $(OBJDIR)/manifest_.c $(OBJDIR)/manifest.h: $(OBJDIR)/headers -$(OBJDIR)/markdown_.c: $(SRCDIR)/markdown.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/markdown.c >$(OBJDIR)/markdown_.c +$(OBJDIR)/markdown_.c: $(SRCDIR)/markdown.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/markdown.c >$@ -$(OBJDIR)/markdown.o: $(OBJDIR)/markdown_.c $(OBJDIR)/markdown.h $(SRCDIR)/config.h +$(OBJDIR)/markdown.o: $(OBJDIR)/markdown_.c $(OBJDIR)/markdown.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/markdown.o -c $(OBJDIR)/markdown_.c $(OBJDIR)/markdown.h: $(OBJDIR)/headers -$(OBJDIR)/markdown_html_.c: $(SRCDIR)/markdown_html.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/markdown_html.c >$(OBJDIR)/markdown_html_.c +$(OBJDIR)/markdown_html_.c: $(SRCDIR)/markdown_html.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/markdown_html.c >$@ -$(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h +$(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers -$(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/md5.c >$(OBJDIR)/md5_.c +$(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/md5.c >$@ -$(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h +$(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/md5.o -c $(OBJDIR)/md5_.c $(OBJDIR)/md5.h: $(OBJDIR)/headers -$(OBJDIR)/merge_.c: $(SRCDIR)/merge.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/merge.c >$(OBJDIR)/merge_.c +$(OBJDIR)/merge_.c: $(SRCDIR)/merge.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/merge.c >$@ -$(OBJDIR)/merge.o: $(OBJDIR)/merge_.c $(OBJDIR)/merge.h $(SRCDIR)/config.h +$(OBJDIR)/merge.o: $(OBJDIR)/merge_.c $(OBJDIR)/merge.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/merge.o -c $(OBJDIR)/merge_.c $(OBJDIR)/merge.h: $(OBJDIR)/headers -$(OBJDIR)/merge3_.c: $(SRCDIR)/merge3.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/merge3.c >$(OBJDIR)/merge3_.c +$(OBJDIR)/merge3_.c: $(SRCDIR)/merge3.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/merge3.c >$@ -$(OBJDIR)/merge3.o: $(OBJDIR)/merge3_.c $(OBJDIR)/merge3.h $(SRCDIR)/config.h +$(OBJDIR)/merge3.o: $(OBJDIR)/merge3_.c $(OBJDIR)/merge3.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/merge3.o -c $(OBJDIR)/merge3_.c $(OBJDIR)/merge3.h: $(OBJDIR)/headers -$(OBJDIR)/moderate_.c: $(SRCDIR)/moderate.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/moderate.c >$(OBJDIR)/moderate_.c +$(OBJDIR)/moderate_.c: $(SRCDIR)/moderate.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/moderate.c >$@ -$(OBJDIR)/moderate.o: $(OBJDIR)/moderate_.c $(OBJDIR)/moderate.h $(SRCDIR)/config.h +$(OBJDIR)/moderate.o: $(OBJDIR)/moderate_.c $(OBJDIR)/moderate.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/moderate.o -c $(OBJDIR)/moderate_.c $(OBJDIR)/moderate.h: $(OBJDIR)/headers -$(OBJDIR)/name_.c: $(SRCDIR)/name.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/name.c >$(OBJDIR)/name_.c +$(OBJDIR)/name_.c: $(SRCDIR)/name.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/name.c >$@ -$(OBJDIR)/name.o: $(OBJDIR)/name_.c $(OBJDIR)/name.h $(SRCDIR)/config.h +$(OBJDIR)/name.o: $(OBJDIR)/name_.c $(OBJDIR)/name.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/name.o -c $(OBJDIR)/name_.c $(OBJDIR)/name.h: $(OBJDIR)/headers -$(OBJDIR)/path_.c: $(SRCDIR)/path.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/path.c >$(OBJDIR)/path_.c +$(OBJDIR)/path_.c: $(SRCDIR)/path.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/path.c >$@ -$(OBJDIR)/path.o: $(OBJDIR)/path_.c $(OBJDIR)/path.h $(SRCDIR)/config.h +$(OBJDIR)/path.o: $(OBJDIR)/path_.c $(OBJDIR)/path.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/path.o -c $(OBJDIR)/path_.c $(OBJDIR)/path.h: $(OBJDIR)/headers -$(OBJDIR)/pivot_.c: $(SRCDIR)/pivot.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/pivot.c >$(OBJDIR)/pivot_.c +$(OBJDIR)/pivot_.c: $(SRCDIR)/pivot.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/pivot.c >$@ -$(OBJDIR)/pivot.o: $(OBJDIR)/pivot_.c $(OBJDIR)/pivot.h $(SRCDIR)/config.h +$(OBJDIR)/pivot.o: $(OBJDIR)/pivot_.c $(OBJDIR)/pivot.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/pivot.o -c $(OBJDIR)/pivot_.c $(OBJDIR)/pivot.h: $(OBJDIR)/headers -$(OBJDIR)/popen_.c: $(SRCDIR)/popen.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/popen.c >$(OBJDIR)/popen_.c +$(OBJDIR)/popen_.c: $(SRCDIR)/popen.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/popen.c >$@ -$(OBJDIR)/popen.o: $(OBJDIR)/popen_.c $(OBJDIR)/popen.h $(SRCDIR)/config.h +$(OBJDIR)/popen.o: $(OBJDIR)/popen_.c $(OBJDIR)/popen.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/popen.o -c $(OBJDIR)/popen_.c $(OBJDIR)/popen.h: $(OBJDIR)/headers -$(OBJDIR)/pqueue_.c: $(SRCDIR)/pqueue.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/pqueue.c >$(OBJDIR)/pqueue_.c +$(OBJDIR)/pqueue_.c: $(SRCDIR)/pqueue.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/pqueue.c >$@ -$(OBJDIR)/pqueue.o: $(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.h $(SRCDIR)/config.h +$(OBJDIR)/pqueue.o: $(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/pqueue.o -c $(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.h: $(OBJDIR)/headers -$(OBJDIR)/printf_.c: $(SRCDIR)/printf.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/printf.c >$(OBJDIR)/printf_.c +$(OBJDIR)/printf_.c: $(SRCDIR)/printf.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/printf.c >$@ -$(OBJDIR)/printf.o: $(OBJDIR)/printf_.c $(OBJDIR)/printf.h $(SRCDIR)/config.h +$(OBJDIR)/printf.o: $(OBJDIR)/printf_.c $(OBJDIR)/printf.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/printf.o -c $(OBJDIR)/printf_.c $(OBJDIR)/printf.h: $(OBJDIR)/headers -$(OBJDIR)/rebuild_.c: $(SRCDIR)/rebuild.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/rebuild.c >$(OBJDIR)/rebuild_.c +$(OBJDIR)/publish_.c: $(SRCDIR)/publish.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/publish.c >$@ -$(OBJDIR)/rebuild.o: $(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.h $(SRCDIR)/config.h +$(OBJDIR)/publish.o: $(OBJDIR)/publish_.c $(OBJDIR)/publish.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/publish.o -c $(OBJDIR)/publish_.c + +$(OBJDIR)/publish.h: $(OBJDIR)/headers + +$(OBJDIR)/purge_.c: $(SRCDIR)/purge.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/purge.c >$@ + +$(OBJDIR)/purge.o: $(OBJDIR)/purge_.c $(OBJDIR)/purge.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/purge.o -c $(OBJDIR)/purge_.c + +$(OBJDIR)/purge.h: $(OBJDIR)/headers + +$(OBJDIR)/rebuild_.c: $(SRCDIR)/rebuild.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/rebuild.c >$@ + +$(OBJDIR)/rebuild.o: $(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/rebuild.o -c $(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.h: $(OBJDIR)/headers -$(OBJDIR)/regexp_.c: $(SRCDIR)/regexp.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/regexp.c >$(OBJDIR)/regexp_.c +$(OBJDIR)/regexp_.c: $(SRCDIR)/regexp.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/regexp.c >$@ -$(OBJDIR)/regexp.o: $(OBJDIR)/regexp_.c $(OBJDIR)/regexp.h $(SRCDIR)/config.h +$(OBJDIR)/regexp.o: $(OBJDIR)/regexp_.c $(OBJDIR)/regexp.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/regexp.o -c $(OBJDIR)/regexp_.c $(OBJDIR)/regexp.h: $(OBJDIR)/headers -$(OBJDIR)/report_.c: $(SRCDIR)/report.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/report.c >$(OBJDIR)/report_.c +$(OBJDIR)/report_.c: $(SRCDIR)/report.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/report.c >$@ -$(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h +$(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c $(OBJDIR)/report.h: $(OBJDIR)/headers -$(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/rss.c >$(OBJDIR)/rss_.c +$(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/rss.c >$@ -$(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h +$(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/rss.o -c $(OBJDIR)/rss_.c $(OBJDIR)/rss.h: $(OBJDIR)/headers -$(OBJDIR)/schema_.c: $(SRCDIR)/schema.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/schema.c >$(OBJDIR)/schema_.c +$(OBJDIR)/schema_.c: $(SRCDIR)/schema.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/schema.c >$@ -$(OBJDIR)/schema.o: $(OBJDIR)/schema_.c $(OBJDIR)/schema.h $(SRCDIR)/config.h +$(OBJDIR)/schema.o: $(OBJDIR)/schema_.c $(OBJDIR)/schema.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/schema.o -c $(OBJDIR)/schema_.c $(OBJDIR)/schema.h: $(OBJDIR)/headers -$(OBJDIR)/search_.c: $(SRCDIR)/search.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/search.c >$(OBJDIR)/search_.c +$(OBJDIR)/search_.c: $(SRCDIR)/search.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/search.c >$@ -$(OBJDIR)/search.o: $(OBJDIR)/search_.c $(OBJDIR)/search.h $(SRCDIR)/config.h +$(OBJDIR)/search.o: $(OBJDIR)/search_.c $(OBJDIR)/search.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/search.o -c $(OBJDIR)/search_.c $(OBJDIR)/search.h: $(OBJDIR)/headers -$(OBJDIR)/setup_.c: $(SRCDIR)/setup.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/setup.c >$(OBJDIR)/setup_.c +$(OBJDIR)/setup_.c: $(SRCDIR)/setup.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/setup.c >$@ -$(OBJDIR)/setup.o: $(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h +$(OBJDIR)/setup.o: $(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/setup.o -c $(OBJDIR)/setup_.c $(OBJDIR)/setup.h: $(OBJDIR)/headers -$(OBJDIR)/sha1_.c: $(SRCDIR)/sha1.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/sha1.c >$(OBJDIR)/sha1_.c +$(OBJDIR)/sha1_.c: $(SRCDIR)/sha1.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/sha1.c >$@ -$(OBJDIR)/sha1.o: $(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h +$(OBJDIR)/sha1.o: $(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sha1.o -c $(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h: $(OBJDIR)/headers -$(OBJDIR)/shun_.c: $(SRCDIR)/shun.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/shun.c >$(OBJDIR)/shun_.c +$(OBJDIR)/shun_.c: $(SRCDIR)/shun.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/shun.c >$@ -$(OBJDIR)/shun.o: $(OBJDIR)/shun_.c $(OBJDIR)/shun.h $(SRCDIR)/config.h +$(OBJDIR)/shun.o: $(OBJDIR)/shun_.c $(OBJDIR)/shun.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/shun.o -c $(OBJDIR)/shun_.c $(OBJDIR)/shun.h: $(OBJDIR)/headers -$(OBJDIR)/skins_.c: $(SRCDIR)/skins.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/skins.c >$(OBJDIR)/skins_.c +$(OBJDIR)/sitemap_.c: $(SRCDIR)/sitemap.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/sitemap.c >$@ -$(OBJDIR)/skins.o: $(OBJDIR)/skins_.c $(OBJDIR)/skins.h $(SRCDIR)/config.h +$(OBJDIR)/sitemap.o: $(OBJDIR)/sitemap_.c $(OBJDIR)/sitemap.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/sitemap.o -c $(OBJDIR)/sitemap_.c + +$(OBJDIR)/sitemap.h: $(OBJDIR)/headers + +$(OBJDIR)/skins_.c: $(SRCDIR)/skins.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/skins.c >$@ + +$(OBJDIR)/skins.o: $(OBJDIR)/skins_.c $(OBJDIR)/skins.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/skins.o -c $(OBJDIR)/skins_.c $(OBJDIR)/skins.h: $(OBJDIR)/headers -$(OBJDIR)/sqlcmd_.c: $(SRCDIR)/sqlcmd.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/sqlcmd.c >$(OBJDIR)/sqlcmd_.c +$(OBJDIR)/sqlcmd_.c: $(SRCDIR)/sqlcmd.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/sqlcmd.c >$@ -$(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h +$(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sqlcmd.o -c $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h: $(OBJDIR)/headers -$(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/stash.c >$(OBJDIR)/stash_.c +$(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/stash.c >$@ -$(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h +$(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/stash.o -c $(OBJDIR)/stash_.c $(OBJDIR)/stash.h: $(OBJDIR)/headers -$(OBJDIR)/stat_.c: $(SRCDIR)/stat.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/stat.c >$(OBJDIR)/stat_.c +$(OBJDIR)/stat_.c: $(SRCDIR)/stat.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/stat.c >$@ -$(OBJDIR)/stat.o: $(OBJDIR)/stat_.c $(OBJDIR)/stat.h $(SRCDIR)/config.h +$(OBJDIR)/stat.o: $(OBJDIR)/stat_.c $(OBJDIR)/stat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/stat.o -c $(OBJDIR)/stat_.c $(OBJDIR)/stat.h: $(OBJDIR)/headers -$(OBJDIR)/style_.c: $(SRCDIR)/style.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/style.c >$(OBJDIR)/style_.c +$(OBJDIR)/statrep_.c: $(SRCDIR)/statrep.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/statrep.c >$@ -$(OBJDIR)/style.o: $(OBJDIR)/style_.c $(OBJDIR)/style.h $(SRCDIR)/config.h +$(OBJDIR)/statrep.o: $(OBJDIR)/statrep_.c $(OBJDIR)/statrep.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/statrep.o -c $(OBJDIR)/statrep_.c + +$(OBJDIR)/statrep.h: $(OBJDIR)/headers + +$(OBJDIR)/style_.c: $(SRCDIR)/style.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/style.c >$@ + +$(OBJDIR)/style.o: $(OBJDIR)/style_.c $(OBJDIR)/style.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/style.o -c $(OBJDIR)/style_.c $(OBJDIR)/style.h: $(OBJDIR)/headers -$(OBJDIR)/sync_.c: $(SRCDIR)/sync.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/sync.c >$(OBJDIR)/sync_.c +$(OBJDIR)/sync_.c: $(SRCDIR)/sync.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/sync.c >$@ -$(OBJDIR)/sync.o: $(OBJDIR)/sync_.c $(OBJDIR)/sync.h $(SRCDIR)/config.h +$(OBJDIR)/sync.o: $(OBJDIR)/sync_.c $(OBJDIR)/sync.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sync.o -c $(OBJDIR)/sync_.c $(OBJDIR)/sync.h: $(OBJDIR)/headers -$(OBJDIR)/tag_.c: $(SRCDIR)/tag.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/tag.c >$(OBJDIR)/tag_.c +$(OBJDIR)/tag_.c: $(SRCDIR)/tag.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/tag.c >$@ -$(OBJDIR)/tag.o: $(OBJDIR)/tag_.c $(OBJDIR)/tag.h $(SRCDIR)/config.h +$(OBJDIR)/tag.o: $(OBJDIR)/tag_.c $(OBJDIR)/tag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tag.o -c $(OBJDIR)/tag_.c $(OBJDIR)/tag.h: $(OBJDIR)/headers -$(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/tar.c >$(OBJDIR)/tar_.c +$(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/tar.c >$@ -$(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h +$(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c $(OBJDIR)/tar.h: $(OBJDIR)/headers -$(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/th_main.c >$(OBJDIR)/th_main_.c +$(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/th_main.c >$@ -$(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h +$(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/th_main.o -c $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h: $(OBJDIR)/headers -$(OBJDIR)/timeline_.c: $(SRCDIR)/timeline.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/timeline.c >$(OBJDIR)/timeline_.c +$(OBJDIR)/timeline_.c: $(SRCDIR)/timeline.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/timeline.c >$@ -$(OBJDIR)/timeline.o: $(OBJDIR)/timeline_.c $(OBJDIR)/timeline.h $(SRCDIR)/config.h +$(OBJDIR)/timeline.o: $(OBJDIR)/timeline_.c $(OBJDIR)/timeline.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/timeline.o -c $(OBJDIR)/timeline_.c $(OBJDIR)/timeline.h: $(OBJDIR)/headers -$(OBJDIR)/tkt_.c: $(SRCDIR)/tkt.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/tkt.c >$(OBJDIR)/tkt_.c +$(OBJDIR)/tkt_.c: $(SRCDIR)/tkt.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/tkt.c >$@ -$(OBJDIR)/tkt.o: $(OBJDIR)/tkt_.c $(OBJDIR)/tkt.h $(SRCDIR)/config.h +$(OBJDIR)/tkt.o: $(OBJDIR)/tkt_.c $(OBJDIR)/tkt.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tkt.o -c $(OBJDIR)/tkt_.c $(OBJDIR)/tkt.h: $(OBJDIR)/headers -$(OBJDIR)/tktsetup_.c: $(SRCDIR)/tktsetup.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/tktsetup.c >$(OBJDIR)/tktsetup_.c +$(OBJDIR)/tktsetup_.c: $(SRCDIR)/tktsetup.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/tktsetup.c >$@ -$(OBJDIR)/tktsetup.o: $(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.h $(SRCDIR)/config.h +$(OBJDIR)/tktsetup.o: $(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tktsetup.o -c $(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.h: $(OBJDIR)/headers -$(OBJDIR)/undo_.c: $(SRCDIR)/undo.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/undo.c >$(OBJDIR)/undo_.c +$(OBJDIR)/undo_.c: $(SRCDIR)/undo.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/undo.c >$@ -$(OBJDIR)/undo.o: $(OBJDIR)/undo_.c $(OBJDIR)/undo.h $(SRCDIR)/config.h +$(OBJDIR)/undo.o: $(OBJDIR)/undo_.c $(OBJDIR)/undo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/undo.o -c $(OBJDIR)/undo_.c $(OBJDIR)/undo.h: $(OBJDIR)/headers -$(OBJDIR)/unicode_.c: $(SRCDIR)/unicode.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/unicode.c >$(OBJDIR)/unicode_.c +$(OBJDIR)/unicode_.c: $(SRCDIR)/unicode.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/unicode.c >$@ -$(OBJDIR)/unicode.o: $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h $(SRCDIR)/config.h +$(OBJDIR)/unicode.o: $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/unicode.o -c $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h: $(OBJDIR)/headers -$(OBJDIR)/update_.c: $(SRCDIR)/update.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/update.c >$(OBJDIR)/update_.c +$(OBJDIR)/update_.c: $(SRCDIR)/update.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/update.c >$@ -$(OBJDIR)/update.o: $(OBJDIR)/update_.c $(OBJDIR)/update.h $(SRCDIR)/config.h +$(OBJDIR)/update.o: $(OBJDIR)/update_.c $(OBJDIR)/update.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/update.o -c $(OBJDIR)/update_.c $(OBJDIR)/update.h: $(OBJDIR)/headers -$(OBJDIR)/url_.c: $(SRCDIR)/url.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/url.c >$(OBJDIR)/url_.c +$(OBJDIR)/url_.c: $(SRCDIR)/url.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/url.c >$@ -$(OBJDIR)/url.o: $(OBJDIR)/url_.c $(OBJDIR)/url.h $(SRCDIR)/config.h +$(OBJDIR)/url.o: $(OBJDIR)/url_.c $(OBJDIR)/url.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/url.o -c $(OBJDIR)/url_.c $(OBJDIR)/url.h: $(OBJDIR)/headers -$(OBJDIR)/user_.c: $(SRCDIR)/user.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/user.c >$(OBJDIR)/user_.c +$(OBJDIR)/user_.c: $(SRCDIR)/user.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/user.c >$@ -$(OBJDIR)/user.o: $(OBJDIR)/user_.c $(OBJDIR)/user.h $(SRCDIR)/config.h +$(OBJDIR)/user.o: $(OBJDIR)/user_.c $(OBJDIR)/user.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/user.o -c $(OBJDIR)/user_.c $(OBJDIR)/user.h: $(OBJDIR)/headers -$(OBJDIR)/utf8_.c: $(SRCDIR)/utf8.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/utf8.c >$(OBJDIR)/utf8_.c +$(OBJDIR)/utf8_.c: $(SRCDIR)/utf8.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/utf8.c >$@ -$(OBJDIR)/utf8.o: $(OBJDIR)/utf8_.c $(OBJDIR)/utf8.h $(SRCDIR)/config.h +$(OBJDIR)/utf8.o: $(OBJDIR)/utf8_.c $(OBJDIR)/utf8.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/utf8.o -c $(OBJDIR)/utf8_.c $(OBJDIR)/utf8.h: $(OBJDIR)/headers -$(OBJDIR)/util_.c: $(SRCDIR)/util.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/util.c >$(OBJDIR)/util_.c +$(OBJDIR)/util_.c: $(SRCDIR)/util.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/util.c >$@ -$(OBJDIR)/util.o: $(OBJDIR)/util_.c $(OBJDIR)/util.h $(SRCDIR)/config.h +$(OBJDIR)/util.o: $(OBJDIR)/util_.c $(OBJDIR)/util.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/util.o -c $(OBJDIR)/util_.c $(OBJDIR)/util.h: $(OBJDIR)/headers -$(OBJDIR)/verify_.c: $(SRCDIR)/verify.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/verify.c >$(OBJDIR)/verify_.c +$(OBJDIR)/verify_.c: $(SRCDIR)/verify.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/verify.c >$@ -$(OBJDIR)/verify.o: $(OBJDIR)/verify_.c $(OBJDIR)/verify.h $(SRCDIR)/config.h +$(OBJDIR)/verify.o: $(OBJDIR)/verify_.c $(OBJDIR)/verify.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/verify.o -c $(OBJDIR)/verify_.c $(OBJDIR)/verify.h: $(OBJDIR)/headers -$(OBJDIR)/vfile_.c: $(SRCDIR)/vfile.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/vfile.c >$(OBJDIR)/vfile_.c +$(OBJDIR)/vfile_.c: $(SRCDIR)/vfile.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/vfile.c >$@ -$(OBJDIR)/vfile.o: $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h $(SRCDIR)/config.h +$(OBJDIR)/vfile.o: $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/vfile.o -c $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h: $(OBJDIR)/headers -$(OBJDIR)/wiki_.c: $(SRCDIR)/wiki.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/wiki.c >$(OBJDIR)/wiki_.c +$(OBJDIR)/wiki_.c: $(SRCDIR)/wiki.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/wiki.c >$@ -$(OBJDIR)/wiki.o: $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h $(SRCDIR)/config.h +$(OBJDIR)/wiki.o: $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/wiki.o -c $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h: $(OBJDIR)/headers -$(OBJDIR)/wikiformat_.c: $(SRCDIR)/wikiformat.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/wikiformat.c >$(OBJDIR)/wikiformat_.c +$(OBJDIR)/wikiformat_.c: $(SRCDIR)/wikiformat.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/wikiformat.c >$@ -$(OBJDIR)/wikiformat.o: $(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.h $(SRCDIR)/config.h +$(OBJDIR)/wikiformat.o: $(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/wikiformat.o -c $(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.h: $(OBJDIR)/headers -$(OBJDIR)/winhttp_.c: $(SRCDIR)/winhttp.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/winhttp.c >$(OBJDIR)/winhttp_.c +$(OBJDIR)/winfile_.c: $(SRCDIR)/winfile.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/winfile.c >$@ + +$(OBJDIR)/winfile.o: $(OBJDIR)/winfile_.c $(OBJDIR)/winfile.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/winfile.o -c $(OBJDIR)/winfile_.c + +$(OBJDIR)/winfile.h: $(OBJDIR)/headers + +$(OBJDIR)/winhttp_.c: $(SRCDIR)/winhttp.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/winhttp.c >$@ -$(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h +$(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/winhttp.o -c $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h: $(OBJDIR)/headers -$(OBJDIR)/wysiwyg_.c: $(SRCDIR)/wysiwyg.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/wysiwyg.c >$(OBJDIR)/wysiwyg_.c +$(OBJDIR)/wysiwyg_.c: $(SRCDIR)/wysiwyg.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/wysiwyg.c >$@ -$(OBJDIR)/wysiwyg.o: $(OBJDIR)/wysiwyg_.c $(OBJDIR)/wysiwyg.h $(SRCDIR)/config.h +$(OBJDIR)/wysiwyg.o: $(OBJDIR)/wysiwyg_.c $(OBJDIR)/wysiwyg.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/wysiwyg.o -c $(OBJDIR)/wysiwyg_.c $(OBJDIR)/wysiwyg.h: $(OBJDIR)/headers -$(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/xfer.c >$(OBJDIR)/xfer_.c +$(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/xfer.c >$@ -$(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h +$(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h: $(OBJDIR)/headers -$(OBJDIR)/xfersetup_.c: $(SRCDIR)/xfersetup.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/xfersetup.c >$(OBJDIR)/xfersetup_.c +$(OBJDIR)/xfersetup_.c: $(SRCDIR)/xfersetup.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/xfersetup.c >$@ -$(OBJDIR)/xfersetup.o: $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h +$(OBJDIR)/xfersetup.o: $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfersetup.o -c $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h: $(OBJDIR)/headers -$(OBJDIR)/zip_.c: $(SRCDIR)/zip.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/zip.c >$(OBJDIR)/zip_.c +$(OBJDIR)/zip_.c: $(SRCDIR)/zip.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/zip.c >$@ -$(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h +$(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c $(OBJDIR)/zip.h: $(OBJDIR)/headers -$(OBJDIR)/sqlite3.o: $(SRCDIR)/sqlite3.c - $(XTCC) -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_STAT3 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_WIN32_NO_ANSI -c $(SRCDIR)/sqlite3.c -o $(OBJDIR)/sqlite3.o +SQLITE_OPTIONS = -DNDEBUG=1 \ + -DSQLITE_OMIT_LOAD_EXTENSION=1 \ + -DSQLITE_ENABLE_LOCKING_STYLE=0 \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_DEFAULT_FILE_FORMAT=4 \ + -DSQLITE_OMIT_DEPRECATED \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -DSQLITE_ENABLE_FTS4 \ + -DSQLITE_ENABLE_FTS3_PARENTHESIS \ + -DSQLITE_WIN32_NO_ANSI \ + -D_HAVE__MINGW_H \ + -DSQLITE_USE_MALLOC_H \ + -DSQLITE_USE_MSIZE + +SHELL_OPTIONS = -Dmain=sqlite3_shell \ + -DSQLITE_OMIT_LOAD_EXTENSION=1 \ + -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \ + -DSQLITE_SHELL_DBNAME_PROC=fossil_open \ + -Daccess=file_access \ + -Dsystem=fossil_system \ + -Dgetenv=fossil_getenv \ + -Dfopen=fossil_fopen + +MINIZ_OPTIONS = -DMINIZ_NO_STDIO \ + -DMINIZ_NO_TIME \ + -DMINIZ_NO_ARCHIVE_APIS + +$(OBJDIR)/sqlite3.o: $(SRCDIR)/sqlite3.c $(SRCDIR)/../win/Makefile.mingw.mistachkin + $(XTCC) $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) -c $(SRCDIR)/sqlite3.c -o $@ $(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c - $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o + $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $@ $(OBJDIR)/json.o $(OBJDIR)/json_artifact.o $(OBJDIR)/json_branch.o $(OBJDIR)/json_config.o $(OBJDIR)/json_diff.o $(OBJDIR)/json_dir.o $(OBJDIR)/jsos_finfo.o $(OBJDIR)/json_login.o $(OBJDIR)/json_query.o $(OBJDIR)/json_report.o $(OBJDIR)/json_status.o $(OBJDIR)/json_tag.o $(OBJDIR)/json_timeline.o $(OBJDIR)/json_user.o $(OBJDIR)/json_wiki.o : $(SRCDIR)/json_detail.h -$(OBJDIR)/shell.o: $(SRCDIR)/shell.c $(SRCDIR)/sqlite3.h - $(XTCC) -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -c $(SRCDIR)/shell.c -o $(OBJDIR)/shell.o +$(OBJDIR)/shell.o: $(SRCDIR)/shell.c $(SRCDIR)/sqlite3.h $(SRCDIR)/../win/Makefile.mingw.mistachkin + $(XTCC) $(SHELL_OPTIONS) $(SHELL_CFLAGS) -c $(SRCDIR)/shell.c -o $@ $(OBJDIR)/th.o: $(SRCDIR)/th.c - $(XTCC) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o + $(XTCC) -c $(SRCDIR)/th.c -o $@ $(OBJDIR)/th_lang.o: $(SRCDIR)/th_lang.c - $(XTCC) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o + $(XTCC) -c $(SRCDIR)/th_lang.c -o $@ -ifdef FOSSIL_ENABLE_TCL $(OBJDIR)/th_tcl.o: $(SRCDIR)/th_tcl.c - $(XTCC) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o -endif + $(XTCC) -c $(SRCDIR)/th_tcl.c -o $@ + +$(OBJDIR)/miniz.o: $(SRCDIR)/miniz.c + $(XTCC) $(MINIZ_OPTIONS) -c $(SRCDIR)/miniz.c -o $@ + Index: win/Makefile.msc ================================================================== --- win/Makefile.msc +++ win/Makefile.msc @@ -1,79 +1,226 @@ # ############################################################################## # WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") ############################################################################## +# +# This Makefile will only function correctly if used from a sub-directory +# that is a direct child of the top-level directory for this project. +# +!if !exist("..\.fossil-settings") +!error "Please change the current directory to the one containing this file." +!endif + # # This file is automatically generated. Instead of editing this # file, edit "makemake.tcl" then run "tclsh makemake.tcl" # to regenerate this file. # -B = .. -SRCDIR = $B\src -OBJDIR = . -OX = . -O = .obj -E = .exe +B = .. +SRCDIR = $B\src +OBJDIR = . +OX = . +O = .obj +E = .exe +P = .pdb + +# Perl is only necessary if OpenSSL support is enabled and it must +# be built from source code. The PERLDIR variable should point to +# the directory containing the main Perl binary (i.e. "perl.exe"). +PERLDIR = C:\Perl\bin +PERL = perl.exe + +# Uncomment to enable debug symbols +# DEBUG = 1 + +# Uncomment to support Windows XP with Visual Studio 201x +# FOSSIL_ENABLE_WINXP = 1 # Uncomment to enable JSON API # FOSSIL_ENABLE_JSON = 1 + +# Uncomment to enable miniz usage +# FOSSIL_ENABLE_MINIZ = 1 # Uncomment to enable SSL support # FOSSIL_ENABLE_SSL = 1 + +# Uncomment to build SSL libraries +# FOSSIL_BUILD_SSL = 1 + +# Uncomment to enable TH1 scripts in embedded documentation files +# FOSSIL_ENABLE_TH1_DOCS = 1 + +# Uncomment to enable TH1 hooks +# FOSSIL_ENABLE_TH1_HOOKS = 1 + +# Uncomment to enable Tcl support +# FOSSIL_ENABLE_TCL = 1 !ifdef FOSSIL_ENABLE_SSL -SSLINCDIR = $(B)\compat\openssl-1.0.1e\include -SSLLIBDIR = $(B)\compat\openssl-1.0.1e\out32 +SSLDIR = $(B)\compat\openssl-1.0.2a +SSLINCDIR = $(SSLDIR)\inc32 +SSLLIBDIR = $(SSLDIR)\out32 +SSLLFLAGS = /nologo /opt:ref /debug SSLLIB = ssleay32.lib libeay32.lib user32.lib gdi32.lib +!if "$(PLATFORM)"=="amd64" || "$(PLATFORM)"=="x64" +!message Using 'x64' platform for OpenSSL... +# BUGBUG (OpenSSL): Apparently, using "no-ssl*" here breaks the build. +# SSLCONFIG = VC-WIN64A no-asm no-ssl2 no-ssl3 no-shared +SSLCONFIG = VC-WIN64A no-asm no-shared +SSLSETUP = ms\do_win64a.bat +SSLNMAKE = ms\nt.mak all +SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 +!elseif "$(PLATFORM)"=="ia64" +!message Using 'ia64' platform for OpenSSL... +# BUGBUG (OpenSSL): Apparently, using "no-ssl*" here breaks the build. +# SSLCONFIG = VC-WIN64I no-asm no-ssl2 no-ssl3 no-shared +SSLCONFIG = VC-WIN64I no-asm no-shared +SSLSETUP = ms\do_win64i.bat +SSLNMAKE = ms\nt.mak all +SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 +!else +!message Assuming 'x86' platform for OpenSSL... +# BUGBUG (OpenSSL): Apparently, using "no-ssl*" here breaks the build. +# SSLCONFIG = VC-WIN32 no-asm no-ssl2 no-ssl3 no-shared +SSLCONFIG = VC-WIN32 no-asm no-shared +SSLSETUP = ms\do_ms.bat +SSLNMAKE = ms\nt.mak all +SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 +!endif +!endif + +!ifdef FOSSIL_ENABLE_TCL +TCLDIR = $(B)\compat\tcl-8.6 +TCLSRCDIR = $(TCLDIR) +TCLINCDIR = $(TCLSRCDIR)\generic !endif # zlib options -ZINCDIR = $(B)\compat\zlib -ZLIBDIR = $(B)\compat\zlib -ZLIB = zlib.lib - -INCL = -I. -I$(SRCDIR) -I$B\win\include -I$(ZINCDIR) - -!ifdef FOSSIL_ENABLE_SSL -INCL = $(INCL) -I$(SSLINCDIR) -!endif - -CFLAGS = -nologo -MT -O2 -BCC = $(CC) $(CFLAGS) -TCC = $(CC) -c $(CFLAGS) $(MSCDEF) $(INCL) -RCC = rc -D_WIN32 -D_MSC_VER $(MSCDEF) $(INCL) -LIBS = $(ZLIB) ws2_32.lib advapi32.lib -LIBDIR = -LIBPATH:$(ZLIBDIR) - -!ifdef FOSSIL_ENABLE_JSON -TCC = $(TCC) -DFOSSIL_ENABLE_JSON=1 -RCC = $(RCC) -DFOSSIL_ENABLE_JSON=1 -!endif - -!ifdef FOSSIL_ENABLE_SSL -TCC = $(TCC) -DFOSSIL_ENABLE_SSL=1 -RCC = $(RCC) -DFOSSIL_ENABLE_SSL=1 -LIBS = $(LIBS) $(SSLLIB) -LIBDIR = $(LIBDIR) -LIBPATH:$(SSLLIBDIR) -!endif - -SQLITE_OPTIONS = /DSQLITE_OMIT_LOAD_EXTENSION=1 \ - /DSQLITE_THREADSAFE=0 \ - /DSQLITE_DEFAULT_FILE_FORMAT=4 \ - /DSQLITE_ENABLE_STAT3 \ - /Dlocaltime=fossil_localtime \ - /DSQLITE_ENABLE_LOCKING_STYLE=0 \ - /DSQLITE_WIN32_NO_ANSI +ZINCDIR = $(B)\compat\zlib +ZLIBDIR = $(B)\compat\zlib +ZLIB = zlib.lib + +INCL = /I. /I$(SRCDIR) /I$B\win\include + +!ifndef FOSSIL_ENABLE_MINIZ +INCL = $(INCL) /I$(ZINCDIR) +!endif + +!ifdef FOSSIL_ENABLE_SSL +INCL = $(INCL) /I$(SSLINCDIR) +!endif + +!ifdef FOSSIL_ENABLE_TCL +INCL = $(INCL) /I$(TCLINCDIR) +!endif + +CFLAGS = /nologo +LDFLAGS = /NODEFAULTLIB:msvcrt /MANIFEST:NO + +!ifdef FOSSIL_ENABLE_WINXP +XPCFLAGS = $(XPCFLAGS) /D_USING_V110_SDK71_=1 +CFLAGS = $(CFLAGS) $(XPCFLAGS) +!if "$(PLATFORM)"=="amd64" || "$(PLATFORM)"=="x64" +XPLDFLAGS = $(XPLDFLAGS) /SUBSYSTEM:CONSOLE,5.02 +!else +XPLDFLAGS = $(XPLDFLAGS) /SUBSYSTEM:CONSOLE,5.01 +!endif +LDFLAGS = $(LDFLAGS) $(XPLDFLAGS) +!endif + +!ifdef DEBUG +CFLAGS = $(CFLAGS) /Zi /MTd /Od +LDFLAGS = $(LDFLAGS) /DEBUG +!else +CFLAGS = $(CFLAGS) /MT /O2 +!endif + +BCC = $(CC) $(CFLAGS) +TCC = $(CC) /c $(CFLAGS) $(MSCDEF) $(INCL) +RCC = rc /D_WIN32 /D_MSC_VER $(MSCDEF) $(INCL) +LIBS = ws2_32.lib advapi32.lib +LIBDIR = + +!ifndef FOSSIL_ENABLE_MINIZ +LIBS = $(LIBS) $(ZLIB) +LIBDIR = $(LIBDIR) /LIBPATH:$(ZLIBDIR) +!endif + +!ifdef FOSSIL_ENABLE_MINIZ +TCC = $(TCC) /DFOSSIL_ENABLE_MINIZ=1 +RCC = $(RCC) /DFOSSIL_ENABLE_MINIZ=1 +!endif + +!ifdef FOSSIL_ENABLE_JSON +TCC = $(TCC) /DFOSSIL_ENABLE_JSON=1 +RCC = $(RCC) /DFOSSIL_ENABLE_JSON=1 +!endif + +!ifdef FOSSIL_ENABLE_SSL +TCC = $(TCC) /DFOSSIL_ENABLE_SSL=1 +RCC = $(RCC) /DFOSSIL_ENABLE_SSL=1 +LIBS = $(LIBS) $(SSLLIB) +LIBDIR = $(LIBDIR) /LIBPATH:$(SSLLIBDIR) +!endif + +!ifdef FOSSIL_ENABLE_TH1_DOCS +TCC = $(TCC) /DFOSSIL_ENABLE_TH1_DOCS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TH1_DOCS=1 +!endif + +!ifdef FOSSIL_ENABLE_TH1_HOOKS +TCC = $(TCC) /DFOSSIL_ENABLE_TH1_HOOKS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TH1_HOOKS=1 +!endif + +!ifdef FOSSIL_ENABLE_TCL +TCC = $(TCC) /DFOSSIL_ENABLE_TCL=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TCL=1 +TCC = $(TCC) /DFOSSIL_ENABLE_TCL_STUBS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TCL_STUBS=1 +TCC = $(TCC) /DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1 +TCC = $(TCC) /DUSE_TCL_STUBS=1 +RCC = $(RCC) /DUSE_TCL_STUBS=1 +!endif + +SQLITE_OPTIONS = /DNDEBUG=1 \ + /DSQLITE_OMIT_LOAD_EXTENSION=1 \ + /DSQLITE_ENABLE_LOCKING_STYLE=0 \ + /DSQLITE_THREADSAFE=0 \ + /DSQLITE_DEFAULT_FILE_FORMAT=4 \ + /DSQLITE_OMIT_DEPRECATED \ + /DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + /DSQLITE_ENABLE_FTS4 \ + /DSQLITE_ENABLE_FTS3_PARENTHESIS \ + /DSQLITE_WIN32_NO_ANSI + +SHELL_OPTIONS = /Dmain=sqlite3_shell \ + /DSQLITE_OMIT_LOAD_EXTENSION=1 \ + /DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \ + /DSQLITE_SHELL_DBNAME_PROC=fossil_open \ + /Daccess=file_access \ + /Dsystem=fossil_system \ + /Dgetenv=fossil_getenv \ + /Dfopen=fossil_fopen + +MINIZ_OPTIONS = /DMINIZ_NO_STDIO \ + /DMINIZ_NO_TIME \ + /DMINIZ_NO_ARCHIVE_APIS SRC = add_.c \ allrepo_.c \ attach_.c \ bag_.c \ bisect_.c \ blob_.c \ branch_.c \ browse_.c \ + builtin_.c \ + bundle_.c \ + cache_.c \ captcha_.c \ cgi_.c \ checkin_.c \ checkout_.c \ clearsign_.c \ @@ -91,10 +238,12 @@ encode_.c \ event_.c \ export_.c \ file_.c \ finfo_.c \ + foci_.c \ + fusefs_.c \ glob_.c \ graph_.c \ gzip_.c \ http_.c \ http_socket_.c \ @@ -116,10 +265,11 @@ json_tag_.c \ json_timeline_.c \ json_user_.c \ json_wiki_.c \ leaf_.c \ + loadctrl_.c \ login_.c \ lookslike_.c \ main_.c \ manifest_.c \ markdown_.c \ @@ -132,23 +282,27 @@ path_.c \ pivot_.c \ popen_.c \ pqueue_.c \ printf_.c \ + publish_.c \ + purge_.c \ rebuild_.c \ regexp_.c \ report_.c \ rss_.c \ schema_.c \ search_.c \ setup_.c \ sha1_.c \ shun_.c \ + sitemap_.c \ skins_.c \ sqlcmd_.c \ stash_.c \ stat_.c \ + statrep_.c \ style_.c \ sync_.c \ tag_.c \ tar_.c \ th_main_.c \ @@ -164,24 +318,78 @@ util_.c \ verify_.c \ vfile_.c \ wiki_.c \ wikiformat_.c \ + winfile_.c \ winhttp_.c \ wysiwyg_.c \ xfer_.c \ xfersetup_.c \ zip_.c + +EXTRA_FILES = $(SRCDIR)\../skins/aht/details.txt \ + $(SRCDIR)\../skins/black_and_white/css.txt \ + $(SRCDIR)\../skins/black_and_white/details.txt \ + $(SRCDIR)\../skins/black_and_white/footer.txt \ + $(SRCDIR)\../skins/black_and_white/header.txt \ + $(SRCDIR)\../skins/blitz/css.txt \ + $(SRCDIR)\../skins/blitz/details.txt \ + $(SRCDIR)\../skins/blitz/footer.txt \ + $(SRCDIR)\../skins/blitz/header.txt \ + $(SRCDIR)\../skins/blitz/ticket.txt \ + $(SRCDIR)\../skins/blitz_no_logo/css.txt \ + $(SRCDIR)\../skins/blitz_no_logo/details.txt \ + $(SRCDIR)\../skins/blitz_no_logo/footer.txt \ + $(SRCDIR)\../skins/blitz_no_logo/header.txt \ + $(SRCDIR)\../skins/blitz_no_logo/ticket.txt \ + $(SRCDIR)\../skins/default/css.txt \ + $(SRCDIR)\../skins/default/details.txt \ + $(SRCDIR)\../skins/default/footer.txt \ + $(SRCDIR)\../skins/default/header.txt \ + $(SRCDIR)\../skins/eagle/css.txt \ + $(SRCDIR)\../skins/eagle/details.txt \ + $(SRCDIR)\../skins/eagle/footer.txt \ + $(SRCDIR)\../skins/eagle/header.txt \ + $(SRCDIR)\../skins/enhanced1/css.txt \ + $(SRCDIR)\../skins/enhanced1/details.txt \ + $(SRCDIR)\../skins/enhanced1/footer.txt \ + $(SRCDIR)\../skins/enhanced1/header.txt \ + $(SRCDIR)\../skins/khaki/css.txt \ + $(SRCDIR)\../skins/khaki/details.txt \ + $(SRCDIR)\../skins/khaki/footer.txt \ + $(SRCDIR)\../skins/khaki/header.txt \ + $(SRCDIR)\../skins/original/css.txt \ + $(SRCDIR)\../skins/original/details.txt \ + $(SRCDIR)\../skins/original/footer.txt \ + $(SRCDIR)\../skins/original/header.txt \ + $(SRCDIR)\../skins/plain_gray/css.txt \ + $(SRCDIR)\../skins/plain_gray/details.txt \ + $(SRCDIR)\../skins/plain_gray/footer.txt \ + $(SRCDIR)\../skins/plain_gray/header.txt \ + $(SRCDIR)\../skins/rounded1/css.txt \ + $(SRCDIR)\../skins/rounded1/details.txt \ + $(SRCDIR)\../skins/rounded1/footer.txt \ + $(SRCDIR)\../skins/rounded1/header.txt \ + $(SRCDIR)\../skins/xekri/css.txt \ + $(SRCDIR)\../skins/xekri/details.txt \ + $(SRCDIR)\../skins/xekri/footer.txt \ + $(SRCDIR)\../skins/xekri/header.txt \ + $(SRCDIR)\diff.tcl \ + $(SRCDIR)\markdown.md OBJ = $(OX)\add$O \ $(OX)\allrepo$O \ $(OX)\attach$O \ $(OX)\bag$O \ $(OX)\bisect$O \ $(OX)\blob$O \ $(OX)\branch$O \ $(OX)\browse$O \ + $(OX)\builtin$O \ + $(OX)\bundle$O \ + $(OX)\cache$O \ $(OX)\captcha$O \ $(OX)\cgi$O \ $(OX)\checkin$O \ $(OX)\checkout$O \ $(OX)\clearsign$O \ @@ -200,10 +408,12 @@ $(OX)\encode$O \ $(OX)\event$O \ $(OX)\export$O \ $(OX)\file$O \ $(OX)\finfo$O \ + $(OX)\foci$O \ + $(OX)\fusefs$O \ $(OX)\glob$O \ $(OX)\graph$O \ $(OX)\gzip$O \ $(OX)\http$O \ $(OX)\http_socket$O \ @@ -225,10 +435,11 @@ $(OX)\json_tag$O \ $(OX)\json_timeline$O \ $(OX)\json_user$O \ $(OX)\json_wiki$O \ $(OX)\leaf$O \ + $(OX)\loadctrl$O \ $(OX)\login$O \ $(OX)\lookslike$O \ $(OX)\main$O \ $(OX)\manifest$O \ $(OX)\markdown$O \ @@ -241,10 +452,12 @@ $(OX)\path$O \ $(OX)\pivot$O \ $(OX)\popen$O \ $(OX)\pqueue$O \ $(OX)\printf$O \ + $(OX)\publish$O \ + $(OX)\purge$O \ $(OX)\rebuild$O \ $(OX)\regexp$O \ $(OX)\report$O \ $(OX)\rss$O \ $(OX)\schema$O \ @@ -251,22 +464,25 @@ $(OX)\search$O \ $(OX)\setup$O \ $(OX)\sha1$O \ $(OX)\shell$O \ $(OX)\shun$O \ + $(OX)\sitemap$O \ $(OX)\skins$O \ $(OX)\sqlcmd$O \ $(OX)\sqlite3$O \ $(OX)\stash$O \ $(OX)\stat$O \ + $(OX)\statrep$O \ $(OX)\style$O \ $(OX)\sync$O \ $(OX)\tag$O \ $(OX)\tar$O \ $(OX)\th$O \ $(OX)\th_lang$O \ $(OX)\th_main$O \ + $(OX)\th_tcl$O \ $(OX)\timeline$O \ $(OX)\tkt$O \ $(OX)\tktsetup$O \ $(OX)\undo$O \ $(OX)\unicode$O \ @@ -277,28 +493,65 @@ $(OX)\util$O \ $(OX)\verify$O \ $(OX)\vfile$O \ $(OX)\wiki$O \ $(OX)\wikiformat$O \ + $(OX)\winfile$O \ $(OX)\winhttp$O \ $(OX)\wysiwyg$O \ $(OX)\xfer$O \ $(OX)\xfersetup$O \ $(OX)\zip$O \ +!ifdef FOSSIL_ENABLE_MINIZ + $(OX)\miniz$O \ +!endif $(OX)\fossil.res -APPNAME = $(OX)\fossil$(E) + +APPNAME = $(OX)\fossil$(E) +PDBNAME = $(OX)\fossil$(P) +APPTARGETS = all: $(OX) $(APPNAME) zlib: @echo Building zlib from "$(ZLIBDIR)"... - @pushd "$(ZLIBDIR)" && nmake /f win32\Makefile.msc $(ZLIB) && popd +!ifdef FOSSIL_ENABLE_WINXP + @pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc $(ZLIB) "CC=cl $(XPCFLAGS)" "LD=link $(XPLDFLAGS)" && popd +!else + @pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc $(ZLIB) && popd +!endif -$(APPNAME) : translate$E mkindex$E headers $(OBJ) $(OX)\linkopts zlib - cd $(OX) - link /NODEFAULTLIB:msvcrt -OUT:$@ $(LIBDIR) Wsetargv.obj fossil.res @linkopts +!ifdef FOSSIL_ENABLE_SSL +openssl: + @echo Building OpenSSL from "$(SSLDIR)"... +!if "$(PERLDIR)" != "" + @set PATH=$(PERLDIR);$(PATH) +!endif + @pushd "$(SSLDIR)" && $(PERL) Configure $(SSLCONFIG) && popd + @pushd "$(SSLDIR)" && call $(SSLSETUP) && popd +!ifdef FOSSIL_ENABLE_WINXP + @pushd "$(SSLDIR)" && $(MAKE) /f $(SSLNMAKE) "CC=cl $(SSLCFLAGS) $(XPCFLAGS)" "LFLAGS=$(SSLLFLAGS) $(XPLDFLAGS)" && popd +!else + @pushd "$(SSLDIR)" && $(MAKE) /f $(SSLNMAKE) "CC=cl $(SSLCFLAGS)" && popd +!endif +!endif + +!ifndef FOSSIL_ENABLE_MINIZ +APPTARGETS = $(APPTARGETS) zlib +!endif + +!ifdef FOSSIL_ENABLE_SSL +!ifdef FOSSIL_BUILD_SSL +APPTARGETS = $(APPTARGETS) openssl +!endif +!endif + +$(APPNAME) : $(APPTARGETS) translate$E mkindex$E codecheck1$E headers $(OBJ) $(OX)\linkopts + cd $(OX) + codecheck1$E $(SRC) + link $(LDFLAGS) /OUT:$@ $(LIBDIR) Wsetargv.obj fossil.res @linkopts $(OX)\linkopts: $B\win\Makefile.msc echo $(OX)\add.obj > $@ echo $(OX)\allrepo.obj >> $@ echo $(OX)\attach.obj >> $@ @@ -305,10 +558,13 @@ echo $(OX)\bag.obj >> $@ echo $(OX)\bisect.obj >> $@ echo $(OX)\blob.obj >> $@ echo $(OX)\branch.obj >> $@ echo $(OX)\browse.obj >> $@ + echo $(OX)\builtin.obj >> $@ + echo $(OX)\bundle.obj >> $@ + echo $(OX)\cache.obj >> $@ echo $(OX)\captcha.obj >> $@ echo $(OX)\cgi.obj >> $@ echo $(OX)\checkin.obj >> $@ echo $(OX)\checkout.obj >> $@ echo $(OX)\clearsign.obj >> $@ @@ -327,10 +583,12 @@ echo $(OX)\encode.obj >> $@ echo $(OX)\event.obj >> $@ echo $(OX)\export.obj >> $@ echo $(OX)\file.obj >> $@ echo $(OX)\finfo.obj >> $@ + echo $(OX)\foci.obj >> $@ + echo $(OX)\fusefs.obj >> $@ echo $(OX)\glob.obj >> $@ echo $(OX)\graph.obj >> $@ echo $(OX)\gzip.obj >> $@ echo $(OX)\http.obj >> $@ echo $(OX)\http_socket.obj >> $@ @@ -352,10 +610,11 @@ echo $(OX)\json_tag.obj >> $@ echo $(OX)\json_timeline.obj >> $@ echo $(OX)\json_user.obj >> $@ echo $(OX)\json_wiki.obj >> $@ echo $(OX)\leaf.obj >> $@ + echo $(OX)\loadctrl.obj >> $@ echo $(OX)\login.obj >> $@ echo $(OX)\lookslike.obj >> $@ echo $(OX)\main.obj >> $@ echo $(OX)\manifest.obj >> $@ echo $(OX)\markdown.obj >> $@ @@ -368,10 +627,12 @@ echo $(OX)\path.obj >> $@ echo $(OX)\pivot.obj >> $@ echo $(OX)\popen.obj >> $@ echo $(OX)\pqueue.obj >> $@ echo $(OX)\printf.obj >> $@ + echo $(OX)\publish.obj >> $@ + echo $(OX)\purge.obj >> $@ echo $(OX)\rebuild.obj >> $@ echo $(OX)\regexp.obj >> $@ echo $(OX)\report.obj >> $@ echo $(OX)\rss.obj >> $@ echo $(OX)\schema.obj >> $@ @@ -378,22 +639,25 @@ echo $(OX)\search.obj >> $@ echo $(OX)\setup.obj >> $@ echo $(OX)\sha1.obj >> $@ echo $(OX)\shell.obj >> $@ echo $(OX)\shun.obj >> $@ + echo $(OX)\sitemap.obj >> $@ echo $(OX)\skins.obj >> $@ echo $(OX)\sqlcmd.obj >> $@ echo $(OX)\sqlite3.obj >> $@ echo $(OX)\stash.obj >> $@ echo $(OX)\stat.obj >> $@ + echo $(OX)\statrep.obj >> $@ echo $(OX)\style.obj >> $@ echo $(OX)\sync.obj >> $@ echo $(OX)\tag.obj >> $@ echo $(OX)\tar.obj >> $@ echo $(OX)\th.obj >> $@ echo $(OX)\th_lang.obj >> $@ echo $(OX)\th_main.obj >> $@ + echo $(OX)\th_tcl.obj >> $@ echo $(OX)\timeline.obj >> $@ echo $(OX)\tkt.obj >> $@ echo $(OX)\tktsetup.obj >> $@ echo $(OX)\undo.obj >> $@ echo $(OX)\unicode.obj >> $@ @@ -404,19 +668,20 @@ echo $(OX)\util.obj >> $@ echo $(OX)\verify.obj >> $@ echo $(OX)\vfile.obj >> $@ echo $(OX)\wiki.obj >> $@ echo $(OX)\wikiformat.obj >> $@ + echo $(OX)\winfile.obj >> $@ echo $(OX)\winhttp.obj >> $@ echo $(OX)\wysiwyg.obj >> $@ echo $(OX)\xfer.obj >> $@ echo $(OX)\xfersetup.obj >> $@ echo $(OX)\zip.obj >> $@ +!ifdef FOSSIL_ENABLE_MINIZ + echo $(OX)\miniz.obj >> $@ +!endif echo $(LIBS) >> $@ - - - $(OX): @-mkdir $@ translate$E: $(SRCDIR)\translate.c @@ -426,50 +691,75 @@ $(BCC) $** mkindex$E: $(SRCDIR)\mkindex.c $(BCC) $** -mkversion$E: $B\src\mkversion.c +mkbuiltin$E: $(SRCDIR)\mkbuiltin.c + $(BCC) $** + +mkversion$E: $(SRCDIR)\mkversion.c + $(BCC) $** + +codecheck1$E: $(SRCDIR)\codecheck1.c $(BCC) $** -$(OX)\shell$O : $(SRCDIR)\shell.c - $(TCC) /Fo$@ /Dmain=sqlite3_shell $(SQLITE_OPTIONS) -c $(SRCDIR)\shell.c +$(OX)\shell$O : $(SRCDIR)\shell.c $B\win\Makefile.msc + $(TCC) /Fo$@ $(SHELL_OPTIONS) $(SQLITE_OPTIONS) $(SHELL_CFLAGS) -c $(SRCDIR)\shell.c -$(OX)\sqlite3$O : $(SRCDIR)\sqlite3.c - $(TCC) /Fo$@ -c $(SQLITE_OPTIONS) $** +$(OX)\sqlite3$O : $(SRCDIR)\sqlite3.c $B\win\Makefile.msc + $(TCC) /Fo$@ -c $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) $(SRCDIR)\sqlite3.c $(OX)\th$O : $(SRCDIR)\th.c $(TCC) /Fo$@ -c $** $(OX)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) /Fo$@ -c $** +$(OX)\th_tcl$O : $(SRCDIR)\th_tcl.c + $(TCC) /Fo$@ -c $** + +$(OX)\miniz$O : $(SRCDIR)\miniz.c + $(TCC) /Fo$@ -c $(MINIZ_OPTIONS) $(SRCDIR)\miniz.c + VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION $** > $@ $(OX)\cson_amalgamation$O : $(SRCDIR)\cson_amalgamation.c - $(TCC) /Fo$@ -c $** + $(TCC) /Fo$@ /c $** -page_index.h: mkindex$E $(SRC) +page_index.h: mkindex$E $(SRC) $** > $@ +builtin_data.h: mkbuiltin$E $(EXTRA_FILES) + mkbuiltin$E --prefix $(SRCDIR)/ $(EXTRA_FILES) > $@ + clean: - -del $(OX)\*.obj - -del *.obj - -del *_.c - -del *.h - -del *.map - -del *.manifest - -del headers - -del linkopts - -del *.res + del $(OX)\*.obj 2>NUL + del *.obj 2>NUL + del *_.c 2>NUL + del *.h 2>NUL + del *.ilk 2>NUL + del *.map 2>NUL + del *.res 2>NUL + del headers 2>NUL + del linkopts 2>NUL + del vc*.pdb 2>NUL realclean: clean - -del $(APPNAME) - -del translate$E - -del mkindex$E - -del makeheaders$E - -del mkversion$E + del $(APPNAME) 2>NUL + del $(PDBNAME) 2>NUL + del translate$E 2>NUL + del translate$P 2>NUL + del mkindex$E 2>NUL + del mkindex$P 2>NUL + del makeheaders$E 2>NUL + del makeheaders$P 2>NUL + del mkversion$E 2>NUL + del mkversion$P 2>NUL + del codecheck1$E 2>NUL + del codecheck1$P 2>NUL + del mkbuiltin$E 2>NUL + del mkbuiltin$P 2>NUL $(OBJDIR)\json$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_config$O : $(SRCDIR)\json_detail.h @@ -482,11 +772,10 @@ $(OBJDIR)\json_status$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h - $(OX)\add$O : add_.c add.h $(TCC) /Fo$@ -c add_.c add_.c : $(SRCDIR)\add.c @@ -531,10 +820,28 @@ $(OX)\browse$O : browse_.c browse.h $(TCC) /Fo$@ -c browse_.c browse_.c : $(SRCDIR)\browse.c translate$E $** > $@ + +$(OX)\builtin$O : builtin_.c builtin.h + $(TCC) /Fo$@ -c builtin_.c + +builtin_.c : $(SRCDIR)\builtin.c + translate$E $** > $@ + +$(OX)\bundle$O : bundle_.c bundle.h + $(TCC) /Fo$@ -c bundle_.c + +bundle_.c : $(SRCDIR)\bundle.c + translate$E $** > $@ + +$(OX)\cache$O : cache_.c cache.h + $(TCC) /Fo$@ -c cache_.c + +cache_.c : $(SRCDIR)\cache.c + translate$E $** > $@ $(OX)\captcha$O : captcha_.c captcha.h $(TCC) /Fo$@ -c captcha_.c captcha_.c : $(SRCDIR)\captcha.c @@ -657,10 +964,22 @@ $(OX)\finfo$O : finfo_.c finfo.h $(TCC) /Fo$@ -c finfo_.c finfo_.c : $(SRCDIR)\finfo.c translate$E $** > $@ + +$(OX)\foci$O : foci_.c foci.h + $(TCC) /Fo$@ -c foci_.c + +foci_.c : $(SRCDIR)\foci.c + translate$E $** > $@ + +$(OX)\fusefs$O : fusefs_.c fusefs.h + $(TCC) /Fo$@ -c fusefs_.c + +fusefs_.c : $(SRCDIR)\fusefs.c + translate$E $** > $@ $(OX)\glob$O : glob_.c glob.h $(TCC) /Fo$@ -c glob_.c glob_.c : $(SRCDIR)\glob.c @@ -807,10 +1126,16 @@ $(OX)\leaf$O : leaf_.c leaf.h $(TCC) /Fo$@ -c leaf_.c leaf_.c : $(SRCDIR)\leaf.c translate$E $** > $@ + +$(OX)\loadctrl$O : loadctrl_.c loadctrl.h + $(TCC) /Fo$@ -c loadctrl_.c + +loadctrl_.c : $(SRCDIR)\loadctrl.c + translate$E $** > $@ $(OX)\login$O : login_.c login.h $(TCC) /Fo$@ -c login_.c login_.c : $(SRCDIR)\login.c @@ -903,10 +1228,22 @@ $(OX)\printf$O : printf_.c printf.h $(TCC) /Fo$@ -c printf_.c printf_.c : $(SRCDIR)\printf.c translate$E $** > $@ + +$(OX)\publish$O : publish_.c publish.h + $(TCC) /Fo$@ -c publish_.c + +publish_.c : $(SRCDIR)\publish.c + translate$E $** > $@ + +$(OX)\purge$O : purge_.c purge.h + $(TCC) /Fo$@ -c purge_.c + +purge_.c : $(SRCDIR)\purge.c + translate$E $** > $@ $(OX)\rebuild$O : rebuild_.c rebuild.h $(TCC) /Fo$@ -c rebuild_.c rebuild_.c : $(SRCDIR)\rebuild.c @@ -957,10 +1294,16 @@ $(OX)\shun$O : shun_.c shun.h $(TCC) /Fo$@ -c shun_.c shun_.c : $(SRCDIR)\shun.c translate$E $** > $@ + +$(OX)\sitemap$O : sitemap_.c sitemap.h + $(TCC) /Fo$@ -c sitemap_.c + +sitemap_.c : $(SRCDIR)\sitemap.c + translate$E $** > $@ $(OX)\skins$O : skins_.c skins.h $(TCC) /Fo$@ -c skins_.c skins_.c : $(SRCDIR)\skins.c @@ -981,10 +1324,16 @@ $(OX)\stat$O : stat_.c stat.h $(TCC) /Fo$@ -c stat_.c stat_.c : $(SRCDIR)\stat.c translate$E $** > $@ + +$(OX)\statrep$O : statrep_.c statrep.h + $(TCC) /Fo$@ -c statrep_.c + +statrep_.c : $(SRCDIR)\statrep.c + translate$E $** > $@ $(OX)\style$O : style_.c style.h $(TCC) /Fo$@ -c style_.c style_.c : $(SRCDIR)\style.c @@ -1095,10 +1444,16 @@ $(OX)\wikiformat$O : wikiformat_.c wikiformat.h $(TCC) /Fo$@ -c wikiformat_.c wikiformat_.c : $(SRCDIR)\wikiformat.c translate$E $** > $@ + +$(OX)\winfile$O : winfile_.c winfile.h + $(TCC) /Fo$@ -c winfile_.c + +winfile_.c : $(SRCDIR)\winfile.c + translate$E $** > $@ $(OX)\winhttp$O : winhttp_.c winhttp.h $(TCC) /Fo$@ -c winhttp_.c winhttp_.c : $(SRCDIR)\winhttp.c @@ -1127,20 +1482,24 @@ zip_.c : $(SRCDIR)\zip.c translate$E $** > $@ fossil.res : $B\win\fossil.rc - $(RCC) -fo $@ $** -headers: makeheaders$E page_index.h VERSION.h + $(RCC) /fo $@ $** + +headers: makeheaders$E page_index.h builtin_data.h VERSION.h makeheaders$E add_.c:add.h \ allrepo_.c:allrepo.h \ attach_.c:attach.h \ bag_.c:bag.h \ bisect_.c:bisect.h \ blob_.c:blob.h \ branch_.c:branch.h \ browse_.c:browse.h \ + builtin_.c:builtin.h \ + bundle_.c:bundle.h \ + cache_.c:cache.h \ captcha_.c:captcha.h \ cgi_.c:cgi.h \ checkin_.c:checkin.h \ checkout_.c:checkout.h \ clearsign_.c:clearsign.h \ @@ -1158,10 +1517,12 @@ encode_.c:encode.h \ event_.c:event.h \ export_.c:export.h \ file_.c:file.h \ finfo_.c:finfo.h \ + foci_.c:foci.h \ + fusefs_.c:fusefs.h \ glob_.c:glob.h \ graph_.c:graph.h \ gzip_.c:gzip.h \ http_.c:http.h \ http_socket_.c:http_socket.h \ @@ -1183,10 +1544,11 @@ json_tag_.c:json_tag.h \ json_timeline_.c:json_timeline.h \ json_user_.c:json_user.h \ json_wiki_.c:json_wiki.h \ leaf_.c:leaf.h \ + loadctrl_.c:loadctrl.h \ login_.c:login.h \ lookslike_.c:lookslike.h \ main_.c:main.h \ manifest_.c:manifest.h \ markdown_.c:markdown.h \ @@ -1199,23 +1561,27 @@ path_.c:path.h \ pivot_.c:pivot.h \ popen_.c:popen.h \ pqueue_.c:pqueue.h \ printf_.c:printf.h \ + publish_.c:publish.h \ + purge_.c:purge.h \ rebuild_.c:rebuild.h \ regexp_.c:regexp.h \ report_.c:report.h \ rss_.c:rss.h \ schema_.c:schema.h \ search_.c:search.h \ setup_.c:setup.h \ sha1_.c:sha1.h \ shun_.c:shun.h \ + sitemap_.c:sitemap.h \ skins_.c:skins.h \ sqlcmd_.c:sqlcmd.h \ stash_.c:stash.h \ stat_.c:stat.h \ + statrep_.c:statrep.h \ style_.c:style.h \ sync_.c:sync.h \ tag_.c:tag.h \ tar_.c:tar.h \ th_main_.c:th_main.h \ @@ -1231,10 +1597,11 @@ util_.c:util.h \ verify_.c:verify.h \ vfile_.c:vfile.h \ wiki_.c:wiki.h \ wikiformat_.c:wikiformat.h \ + winfile_.c:winfile.h \ winhttp_.c:winhttp.h \ wysiwyg_.c:wysiwyg.h \ xfer_.c:xfer.h \ xfersetup_.c:xfersetup.h \ zip_.c:zip.h \ ADDED win/buildmsvc.bat Index: win/buildmsvc.bat ================================================================== --- /dev/null +++ win/buildmsvc.bat @@ -0,0 +1,288 @@ +@ECHO OFF + +:: +:: buildmsvc.bat -- +:: +:: This batch file attempts to build Fossil using the latest version +:: Microsoft Visual Studio installed on this machine. +:: +:: + +SETLOCAL + +REM SET __ECHO=ECHO +REM SET __ECHO2=ECHO +IF NOT DEFINED _AECHO (SET _AECHO=REM) +IF NOT DEFINED _CECHO (SET _CECHO=REM) +IF NOT DEFINED _VECHO (SET _VECHO=REM) + +REM +REM NOTE: Setup local environment variables that point to the root directory +REM of the Fossil source checkout and to the directory containing this +REM build tool. +REM +SET ROOT=%~dp0\.. +SET ROOT=%ROOT:\\=\% + +%_VECHO% Root = '%ROOT%' + +SET TOOLS=%~dp0 +SET TOOLS=%TOOLS:~0,-1% + +%_VECHO% Tools = '%TOOLS%' + +REM +REM Visual C++ ???? +REM +IF DEFINED VCINSTALLDIR IF EXIST "%VCINSTALLDIR%" ( + %_AECHO% Build environment appears to be setup. + GOTO skip_setupVisualStudio +) + +REM +REM Visual Studio ???? +REM +IF DEFINED VSVARS32 IF EXIST "%VSVARS32%" ( + %_AECHO% Build environment batch file manually overridden to "%VSVARS32%"... + GOTO skip_detectVisualStudio +) + +REM +REM Visual Studio 2013 +REM +IF NOT DEFINED VS120COMNTOOLS GOTO skip_detectVisualStudio2013 +SET VSVARS32=%VS120COMNTOOLS%\vsvars32.bat +IF EXIST "%VSVARS32%" ( + %_AECHO% Using Visual Studio 2013... + GOTO skip_detectVisualStudio +) +:skip_detectVisualStudio2013 + +REM +REM Visual Studio 2012 +REM +IF NOT DEFINED VS110COMNTOOLS GOTO skip_detectVisualStudio2012 +SET VSVARS32=%VS110COMNTOOLS%\vsvars32.bat +IF EXIST "%VSVARS32%" ( + %_AECHO% Using Visual Studio 2012... + GOTO skip_detectVisualStudio +) +:skip_detectVisualStudio2012 + +REM +REM Visual Studio 2010 +REM +IF NOT DEFINED VS100COMNTOOLS GOTO skip_detectVisualStudio2010 +SET VSVARS32=%VS100COMNTOOLS%\vsvars32.bat +IF EXIST "%VSVARS32%" ( + %_AECHO% Using Visual Studio 2010... + GOTO skip_detectVisualStudio +) +:skip_detectVisualStudio2010 + +REM +REM Visual Studio 2008 +REM +IF NOT DEFINED VS90COMNTOOLS GOTO skip_detectVisualStudio2008 +SET VSVARS32=%VS90COMNTOOLS%\vsvars32.bat +IF EXIST "%VSVARS32%" ( + %_AECHO% Using Visual Studio 2008... + GOTO skip_detectVisualStudio +) +:skip_detectVisualStudio2008 + +REM +REM Visual Studio 2005 +REM +IF NOT DEFINED VS80COMNTOOLS GOTO skip_detectVisualStudio2005 +SET VSVARS32=%VS80COMNTOOLS%\vsvars32.bat +IF EXIST "%VSVARS32%" ( + %_AECHO% Using Visual Studio 2005... + GOTO skip_detectVisualStudio +) +:skip_detectVisualStudio2005 + +REM +REM Visual Studio 2003 +REM +IF NOT DEFINED VS71COMNTOOLS GOTO skip_detectVisualStudio2003 +SET VSVARS32=%VS71COMNTOOLS%\vsvars32.bat +IF EXIST "%VSVARS32%" ( + %_AECHO% Using Visual Studio 2003... + GOTO skip_detectVisualStudio +) +:skip_detectVisualStudio2003 + +REM +REM Visual Studio 2002 +REM +IF NOT DEFINED VS70COMNTOOLS GOTO skip_detectVisualStudio2002 +SET VSVARS32=%VS70COMNTOOLS%\vsvars32.bat +IF EXIST "%VSVARS32%" ( + %_AECHO% Using Visual Studio 2002... + GOTO skip_detectVisualStudio +) +:skip_detectVisualStudio2002 + +REM +REM NOTE: If we get to this point, no Visual Studio build environment batch +REM files were found. +REM +ECHO No Visual Studio build environment batch files were found. +GOTO errors + +REM +REM NOTE: At this point, the appropriate Visual Studio version should be +REM selected. +REM +:skip_detectVisualStudio + +REM +REM NOTE: Remove any double-backslash sequences that may be present in the +REM selected Visual Studio common tools path. This is not strictly +REM necessary; however, it makes reading the output easier. +REM +SET VSVARS32=%VSVARS32:\\=\% + +%_VECHO% VsVars32 = '%VSVARS32%' + +REM +REM NOTE: After this point, a clean ERRORLEVEL is required; therefore, make +REM sure it is reset now. +REM +CALL :fn_ResetErrorLevel + +REM +REM NOTE: Attempt to call the selected batch file to setup the environment +REM variables for building with MSVC. +REM +%__ECHO3% CALL "%VSVARS32%" + +IF ERRORLEVEL 1 ( + ECHO Visual Studio build environment batch file "%VSVARS32%" failed. + GOTO errors +) + +REM +REM NOTE: After this point, the environment should already be setup for +REM building with MSVC. +REM +:skip_setupVisualStudio + +%_VECHO% VcInstallDir = '%VCINSTALLDIR%' + +REM +REM NOTE: Attempt to create the build output directory, if necessary. +REM +IF NOT EXIST "%ROOT%\msvcbld" ( + %__ECHO% MKDIR "%ROOT%\msvcbld" + + IF ERRORLEVEL 1 ( + ECHO Could not make directory "%ROOT%\msvcbld". + GOTO errors + ) +) + +REM +REM NOTE: Attempt to change to the created build output directory so that +REM the generated files will be placed there. +REM +%__ECHO2% PUSHD "%ROOT%\msvcbld" + +IF ERRORLEVEL 1 ( + ECHO Could not change to directory "%ROOT%\msvcbld". + GOTO errors +) + +REM +REM NOTE: If requested, setup the build environment to refer to the Windows +REM SDK v7.1A, which is required if the binaries are being built with +REM Visual Studio 201x and need to work on Windows XP. +REM +IF DEFINED USE_V110SDK71A ( + %_AECHO% Forcing use of the Windows SDK v7.1A... + CALL :fn_UseV110Sdk71A +) + +%_VECHO% Path = '%PATH%' +%_VECHO% Include = '%INCLUDE%' +%_VECHO% Lib = '%LIB%' +%_VECHO% NmakeArgs = '%NMAKE_ARGS%' + +REM +REM NOTE: Attempt to execute NMAKE for the Fossil MSVC makefile, passing +REM anything extra from our command line along (e.g. extra options). +REM +%__ECHO% nmake /f "%TOOLS%\Makefile.msc" %NMAKE_ARGS% %* + +IF ERRORLEVEL 1 ( + GOTO errors +) + +REM +REM NOTE: Attempt to restore the previously saved directory. +REM +%__ECHO2% POPD + +IF ERRORLEVEL 1 ( + ECHO Could not restore directory. + GOTO errors +) + +GOTO no_errors + +:fn_UseV110Sdk71A + IF "%PROCESSOR_ARCHITECTURE%" == "x86" GOTO set_v110Sdk71A_x86 + SET PFILES_SDK71A=%ProgramFiles(x86)% + GOTO set_v110Sdk71A_done + :set_v110Sdk71A_x86 + SET PFILES_SDK71A=%ProgramFiles% + :set_v110Sdk71A_done + SET PATH=%PFILES_SDK71A%\Microsoft SDKs\Windows\7.1A\Bin;%PATH% + SET INCLUDE=%PFILES_SDK71A%\Microsoft SDKs\Windows\7.1A\Include;%INCLUDE% + IF "%PLATFORM%" == "x64" ( + SET LIB=%PFILES_SDK71A%\Microsoft SDKs\Windows\7.1A\Lib\x64;%LIB% + ) ELSE ( + SET LIB=%PFILES_SDK71A%\Microsoft SDKs\Windows\7.1A\Lib;%LIB% + ) + CALL :fn_UnsetVariable PFILES_SDK71A + SET NMAKE_ARGS=%NMAKE_ARGS% FOSSIL_ENABLE_WINXP=1 + GOTO :EOF + +:fn_UnsetVariable + IF NOT "%1" == "" ( + SET %1= + CALL :fn_ResetErrorLevel + ) + GOTO :EOF + +:fn_ResetErrorLevel + VERIFY > NUL + GOTO :EOF + +:fn_SetErrorLevel + VERIFY MAYBE 2> NUL + GOTO :EOF + +:usage + ECHO. + ECHO Usage: %~nx0 [...] + ECHO. + GOTO errors + +:errors + CALL :fn_SetErrorLevel + ENDLOCAL + ECHO. + ECHO Build failure, errors were encountered. + GOTO end_of_file + +:no_errors + CALL :fn_ResetErrorLevel + ENDLOCAL + ECHO. + ECHO Build success, no errors were encountered. + GOTO end_of_file + +:end_of_file +%__ECHO% EXIT /B %ERRORLEVEL% ADDED win/fossil.exe.manifest Index: win/fossil.exe.manifest ================================================================== --- /dev/null +++ win/fossil.exe.manifest @@ -0,0 +1,43 @@ + + + + + Simple, high-reliability, distributed software configuration management system. + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + Index: win/fossil.rc ================================================================== --- win/fossil.rc +++ win/fossil.rc @@ -30,11 +30,21 @@ #include "VERSION.h" #define _RC_COMPILE_ #include "config.h" #include "sqlite3.h" + +#if defined(FOSSIL_ENABLE_MINIZ) +#if defined(__MINGW32__) +#include "minizver.h" +#else +#define MINIZ_HEADER_FILE_ONLY +#include "miniz.c" +#endif /* defined(__MINGW32__) */ +#else #include "zlib.h" +#endif /* defined(FOSSIL_ENABLE_MINIZ) */ #if defined(FOSSIL_ENABLE_SSL) #include "openssl/opensslv.h" #endif /* defined(FOSSIL_ENABLE_SSL) */ @@ -83,28 +93,42 @@ BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Fossil Development Team\0" - VALUE "FileDescription", "Simple, high-reliability, distributed software configuration management system.\0" + VALUE "FileDescription", "Fossil is a simple, high-reliability, distributed software configuration management system.\0" VALUE "ProductName", "Fossil\0" VALUE "ProductVersion", "Fossil " RELEASE_VERSION " " MANIFEST_VERSION " " MANIFEST_DATE " UTC\0" VALUE "FileVersion", "Fossil " RELEASE_VERSION " " MANIFEST_VERSION " " MANIFEST_DATE " UTC\0" VALUE "InternalName", "fossil\0" VALUE "LegalCopyright", "Copyright © " MANIFEST_YEAR " by D. Richard Hipp. All rights reserved.\0" VALUE "OriginalFilename", "fossil.exe\0" VALUE "CompilerName", COMPILER_NAME "\0" VALUE "SQLiteVersion", "SQLite " SQLITE_VERSION " " SQLITE_SOURCE_ID "\0" +#if defined(FOSSIL_ENABLE_MINIZ) + VALUE "MinizVersion", "miniz " MZ_VERSION "\0" +#else VALUE "ZlibVersion", "zlib " ZLIB_VERSION "\0" +#endif /* defined(FOSSIL_ENABLE_MINIZ) */ #if defined(BROKEN_MINGW_CMDLINE) VALUE "CommandLineIsUnicode", "No\0" #else VALUE "CommandLineIsUnicode", "Yes\0" #endif /* defined(BROKEN_MINGW_CMDLINE) */ #if defined(FOSSIL_ENABLE_SSL) VALUE "SslEnabled", "Yes, " OPENSSL_VERSION_TEXT "\0" #endif /* defined(FOSSIL_ENABLE_SSL) */ +#if defined(FOSSIL_ENABLE_TH1_DOCS) + VALUE "Th1Docs", "Yes\0" +#else + VALUE "Th1Docs", "No\0" +#endif /* defined(FOSSIL_ENABLE_TH1_DOCS) */ +#if defined(FOSSIL_ENABLE_TH1_HOOKS) + VALUE "Th1Hooks", "Yes\0" +#else + VALUE "Th1Hooks", "No\0" +#endif /* defined(FOSSIL_ENABLE_TH1_HOOKS) */ #if defined(FOSSIL_ENABLE_TCL) VALUE "TclEnabled", "Yes, Tcl " TCL_PATCH_LEVEL "\0" #if defined(USE_TCL_STUBS) VALUE "UseTclStubsEnabled", "Yes\0" #else @@ -130,5 +154,19 @@ BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 0x4b0 END END + +/* + * This embedded manifest is needed for Windows 8.1. + */ + +#ifndef RT_MANIFEST +#define RT_MANIFEST 24 +#endif /* RT_MANIFEST */ + +#ifndef CREATEPROCESS_MANIFEST_RESOURCE_ID +#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 +#endif /* CREATEPROCESS_MANIFEST_RESOURCE_ID */ + +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "fossil.exe.manifest" Index: win/include/dirent.h ================================================================== --- win/include/dirent.h +++ win/include/dirent.h @@ -20,84 +20,24 @@ * IN NO EVENT SHALL TONI RONKKO BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * - * - * Version 1.13, Dec 12 2012, Toni Ronkko - * Use traditional 8+3 file name if the name cannot be represented in the - * default ANSI code page. Now compiles again with MSVC 6.0. Thanks to - * Konstantin Khomoutov for testing. - * - * Version 1.12.1, Oct 1 2012, Toni Ronkko - * Bug fix: renamed wide-character DIR structure _wDIR to _WDIR (with - * capital W) in order to maintain compatibility with MingW. - * - * Version 1.12, Sep 30 2012, Toni Ronkko - * Define PATH_MAX and NAME_MAX. Added wide-character variants _wDIR, - * _wdirent, _wopendir(), _wreaddir(), _wclosedir() and _wrewinddir(). - * Thanks to Edgar Buerkle and Jan Nijtmans for ideas and code. - * - * Do not include windows.h. This allows dirent.h to be integrated more - * easily into programs using winsock. Thanks to Fernando Azaldegui. - * - * Version 1.11, Mar 15, 2011, Toni Ronkko - * Defined FILE_ATTRIBUTE_DEVICE for MSVC 6.0. - * - * Version 1.10, Aug 11, 2010, Toni Ronkko - * Added d_type and d_namlen fields to dirent structure. The former is - * especially useful for determining whether directory entry represents a - * file or a directory. For more information, see - * http://www.delorie.com/gnu/docs/glibc/libc_270.html - * - * Improved conformance to the standards. For example, errno is now set - * properly on failure and assert() is never used. Thanks to Peter Brockam - * for suggestions. - * - * Fixed a bug in rewinddir(): when using relative directory names, change - * of working directory no longer causes rewinddir() to fail. - * - * Version 1.9, Dec 15, 2009, John Cunningham - * Added rewinddir member function - * - * Version 1.8, Jan 18, 2008, Toni Ronkko - * Using FindFirstFileA and WIN32_FIND_DATAA to avoid converting string - * between multi-byte and unicode representations. This makes the - * code simpler and also allows the code to be compiled under MingW. Thanks - * to Azriel Fasten for the suggestion. - * - * Mar 4, 2007, Toni Ronkko - * Bug fix: due to the strncpy_s() function this file only compiled in - * Visual Studio 2005. Using the new string functions only when the - * compiler version allows. - * - * Nov 2, 2006, Toni Ronkko - * Major update: removed support for Watcom C, MS-DOS and Turbo C to - * simplify the file, updated the code to compile cleanly on Visual - * Studio 2005 with both unicode and multi-byte character strings, - * removed rewinddir() as it had a bug. - * - * Aug 20, 2006, Toni Ronkko - * Removed all remarks about MSVC 1.0, which is antiqued now. Simplified - * comments by removing SGML tags. - * - * May 14 2002, Toni Ronkko - * Embedded the function definitions directly to the header so that no - * source modules need to be included in the Visual Studio project. Removed - * all the dependencies to other projects so that this header file can be - * used independently. - * - * May 28 1998, Toni Ronkko - * First version. - *****************************************************************************/ + * $Id: dirent.h,v 1.20 2014/03/19 17:52:23 tronkko Exp $ + */ #ifndef DIRENT_H #define DIRENT_H -#if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && defined(_M_IX86) +/* + * Define architecture flags so we don't need to include windows.h. + * Avoiding windows.h makes it simpler to use windows sockets in conjunction + * with dirent.h. + */ +#if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_IX86) # define _X86_ #endif -#if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && defined(_M_AMD64) +#if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_AMD64) #define _AMD64_ #endif #include #include @@ -189,10 +129,11 @@ #define DT_DIR S_IFDIR #define DT_FIFO S_IFIFO #define DT_SOCK S_IFSOCK #define DT_CHR S_IFCHR #define DT_BLK S_IFBLK +#define DT_LNK S_IFLNK /* Macros for converting between st_mode and d_type */ #define IFTODT(mode) ((mode) & S_IFMT) #define DTTOIF(type) (type) @@ -212,11 +153,11 @@ /* Return the exact length of d_namlen without zero terminator */ #define _D_EXACT_NAMLEN(p) ((p)->d_namlen) /* Return number of bytes needed to store d_namlen */ -#define _D_ALLOC_NAMLEN(p) (PATH_MAX + 1) +#define _D_ALLOC_NAMLEN(p) (PATH_MAX) #ifdef __cplusplus extern "C" { #endif @@ -226,11 +167,11 @@ struct _wdirent { long d_ino; /* Always zero */ unsigned short d_reclen; /* Structure size */ size_t d_namlen; /* Length of name without \0 */ int d_type; /* File type */ - wchar_t d_name[PATH_MAX + 1]; /* File name */ + wchar_t d_name[PATH_MAX]; /* File name */ }; typedef struct _wdirent _wdirent; struct _WDIR { struct _wdirent ent; /* Current directory entry */ @@ -260,11 +201,11 @@ struct dirent { long d_ino; /* Always zero */ unsigned short d_reclen; /* Structure size */ size_t d_namlen; /* Length of name without \0 */ int d_type; /* File type */ - char d_name[PATH_MAX + 1]; /* File name */ + char d_name[PATH_MAX]; /* File name */ }; typedef struct dirent dirent; struct DIR { struct dirent ent; @@ -422,11 +363,11 @@ * Copy file name as wide-character string. If the file name is too * long to fit in to the destination buffer, then truncate file name * to PATH_MAX characters and zero-terminate the buffer. */ n = 0; - while (n < PATH_MAX && datap->cFileName[n] != 0) { + while (n + 1 < PATH_MAX && datap->cFileName[n] != 0) { entp->d_name[n] = datap->cFileName[n]; n++; } dirp->ent.d_name[n] = 0; @@ -591,16 +532,15 @@ } /* Allocate memory for DIR structure */ dirp = (DIR*) malloc (sizeof (struct DIR)); if (dirp) { - wchar_t wname[PATH_MAX + 1]; + wchar_t wname[PATH_MAX]; size_t n; /* Convert directory name to wide-character string */ - error = dirent_mbstowcs_s( - &n, wname, PATH_MAX + 1, dirname, PATH_MAX); + error = dirent_mbstowcs_s (&n, wname, PATH_MAX, dirname, PATH_MAX); if (!error) { /* Open directory stream using wide-character name */ dirp->wdirp = _wopendir (wname); if (dirp->wdirp) { @@ -661,11 +601,11 @@ size_t n; int error; /* Attempt to convert file name to multi-byte string */ error = dirent_wcstombs_s( - &n, dirp->ent.d_name, MAX_PATH + 1, datap->cFileName, MAX_PATH); + &n, dirp->ent.d_name, PATH_MAX, datap->cFileName, PATH_MAX); /* * If the file name cannot be represented by a multi-byte string, * then attempt to use old 8+3 file name. This allows traditional * Unix-code to access some file names despite of unicode @@ -675,13 +615,12 @@ * name unless the file system provides one. At least * VirtualBox shared folders fail to do this. */ if (error && datap->cAlternateFileName[0] != '\0') { error = dirent_wcstombs_s( - &n, dirp->ent.d_name, MAX_PATH + 1, datap->cAlternateFileName, - sizeof (datap->cAlternateFileName) / - sizeof (datap->cAlternateFileName[0])); + &n, dirp->ent.d_name, PATH_MAX, + datap->cAlternateFileName, PATH_MAX); } if (!error) { DWORD attr; @@ -786,16 +725,19 @@ #else /* Older Visual Studio or non-Microsoft compiler */ size_t n; - /* Convert to wide-character string */ - n = mbstowcs (wcstr, mbstr, count); - if (n < sizeInWords) { + /* Convert to wide-character string (or count characters) */ + n = mbstowcs (wcstr, mbstr, sizeInWords); + if (!wcstr || n < count) { /* Zero-terminate output buffer */ - if (wcstr) { + if (wcstr && sizeInWords) { + if (n >= sizeInWords) { + n = sizeInWords - 1; + } wcstr[n] = 0; } /* Length of resuting multi-byte string WITH zero terminator */ if (pReturnValue) { @@ -820,11 +762,11 @@ /* Convert wide-character string to multi-byte string */ static int dirent_wcstombs_s( size_t *pReturnValue, char *mbstr, - size_t sizeInBytes, + size_t sizeInBytes, /* max size of mbstr */ const wchar_t *wcstr, size_t count) { int error; @@ -836,16 +778,19 @@ #else /* Older Visual Studio or non-Microsoft compiler */ size_t n; - /* Convert to multi-byte string */ - n = wcstombs (mbstr, wcstr, count); - if (n < sizeInBytes) { + /* Convert to multi-byte string (or count the number of bytes needed) */ + n = wcstombs (mbstr, wcstr, sizeInBytes); + if (!mbstr || n < count) { /* Zero-terminate output buffer */ - if (mbstr) { + if (mbstr && sizeInBytes) { + if (n >= sizeInBytes) { + n = sizeInBytes - 1; + } mbstr[n] = '\0'; } /* Lenght of resulting multi-bytes string WITH zero-terminator */ if (pReturnValue) { @@ -870,11 +815,11 @@ /* Set errno variable */ static void dirent_set_errno( int error) { -#if defined(_MSC_VER) && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 /* Microsoft Visual Studio 2005 and later */ _set_errno (error); #else Index: win/include/unistd.h ================================================================== --- win/include/unistd.h +++ win/include/unistd.h @@ -1,11 +1,11 @@ #ifndef _UNISTD_H #define _UNISTD_H 1 -/* This file intended to serve as a drop-in replacement for +/* This file intended to serve as a drop-in replacement for * unistd.h on Windows - * Please add functionality as neeeded + * Please add functionality as neeeded */ #include #include #define srandom srand Index: www/adding_code.wiki ================================================================== --- www/adding_code.wiki +++ www/adding_code.wiki @@ -10,11 +10,11 @@ Fossil is written in C-89. There are specific [./style.wiki | style guidelines] that are required for any new code that will be accepted into the Fossil core. But, of course, if you are writing an extension just for yourself, you can use any programming style you want. -The source code for Fossil is not feed directly into the C compiler, however. +The source code for Fossil is not sent directly into the C compiler. There are three separate code [./makefile.wiki#preprocessing|preprocessors] that run over the code first. 1. The mkindex preprocessor scans all regular source files looking for special comments that contain "help" text and which identify routines @@ -150,11 +150,11 @@ "commandname_cmd", as is done in the example. You could also use "printf()" instead of "fossil_print()" to generate the output text, if desired. But "fossil_print()" is recommended as it has extra logic to insert \r characters at the right times on -windows systems. +Windows systems. Once you have the command running, you can then start adding code to make it do useful things. There are lots of utility functions in Fossil for parsing command-line options and for opening and accessing and manipulating the repository and Index: www/antibot.wiki ================================================================== --- www/antibot.wiki +++ www/antibot.wiki @@ -59,11 +59,11 @@
          • Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
          • Wget/1.12 (openbsd4.9)
          The first two UserAgent strings above identify Firefox 19 and -Internet Explorer 8.0, both running on windows NT. The third +Internet Explorer 8.0, both running on Windows NT. The third example is the spider used by Google to index the internet. The fourth example is the "wget" utility running on OpenBSD. Thus the first two UserAgent strings above identify the requestor as human whereas the second two identify the requestor as a spider. Note that the UserAgent string is completely under the control Index: www/branching.wiki ================================================================== --- www/branching.wiki +++ www/branching.wiki @@ -19,19 +19,19 @@ The arrows in figure 1 show the evolution of a project. The initial check-in is 1. Check-in 2 is derived from 1. In other words, check-in 2 was created by making edits to check-in 1 and then committing those edits. We say that 2 is a child of 1 -and that 1 is a parent of 2. +and that 1 is a parent of 2. Check-in 3 is derived from check-in 2, making 3 a child of 2. We say that 3 is a descendant of both 1 and 2 and that 1 -and 2 are both ancestors of 3. +and 2 are both ancestors of 3.

          DAGs

          -The graph of check-ins is a +The graph of check-ins is a [http://en.wikipedia.org/wiki/Directed_acyclic_graph | directed acyclic graph] commonly shortened to DAG. Check-in 1 is the root of the DAG since it has no ancestors. Check-in 4 is a leaf of the DAG since it has no descendants. (We will give a more precise definition later of "leaf.") @@ -47,29 +47,29 @@ The graph in figure 2 has two leaves: check-ins 3 and 4. Check-in 2 has two children, check-ins 3 and 4. We call this state a fork. -Fossil tries to prevent forks. Suppose two programmers named Alice and -Bob are each editing check-in 2 separately. Alice finishes her edits -first and commits her changes, resulting in check-in 3. Later, when Bob -attempts to commit his changes, fossil verifies that check-in 2 is still -a leaf. Fossil sees that check-in 3 has occurred and aborts Bob's commit -attempt with a message "would fork." This allows Bob to do a "fossil -update" which pulls in Alice's changes, merging them into his own -changes. After merging, Bob commits check-in 4 as a child of check-in 3. -The result is a linear graph as shown in figure 1. This is how CVS -works. This is also how fossil works in [concepts.wiki#workflow | -"autosync"] mode. +Fossil tries to prevent forks. Suppose two programmers named Alice and +Bob are each editing check-in 2 separately. Alice finishes her edits +first and commits her changes, resulting in check-in 3. Later, when Bob +attempts to commit his changes, fossil verifies that check-in 2 is still +a leaf. Fossil sees that check-in 3 has occurred and aborts Bob's commit +attempt with a message "would fork." This allows Bob to do a "fossil +update" which pulls in Alice's changes, merging them into his own +changes. After merging, Bob commits check-in 4 as a child of check-in 3. +The result is a linear graph as shown in figure 1. This is how CVS +works. This is also how fossil works in [./concepts.wiki#workflow | +"autosync"] mode. But perhaps Bob is off-network when he does his commit, so he has no way of knowing that Alice has already committed her changes. Or, it could be that Bob has turned off "autosync" mode in Fossil. Or, maybe Bob just doesn't want to merge in Alice's changes before he has -saved his own, so he forces the commit to occur using the "--force" option -to the fossil commit command. For any of these reasons, two commits against -check-in 2 have occurred and now the DAG has two leaves. +saved his own, so he forces the commit to occur using the "--allow-fork" +option to the fossil commit command. For any of these reasons, +two commits against check-in 2 have occurred and now the DAG has two leaves. So which version of the project is the "latest" in the sense of having the most features and the most bug fixes? When there is more than one leaf in the graph, you don't really know. So we like to have graphs with a single leaf. @@ -86,11 +86,11 @@ Check-in 5 is a child of check-in 3 because it was created by editing check-in 3. But check-in 5 also inherits the changes from check-in 4 by virtue of the merge. So we say that check-in 5 is a merge child -of check-in 4 and that it is a direct child of check-in 3. +of check-in 4 and that it is a direct child of check-in 3. The graph is now back to a single leaf (check-in 5). We have already seen that if fossil is in autosync mode then Bob would have been warned about the potential fork the first time he tried to commit check-in 4. If Bob had updated his local check-out to merge in @@ -112,11 +112,11 @@ instead of as a separate manual step. We will not take sides in that debate. We will simply point out that fossil enables you to do it either way.

          Forking Versus Branching

          -Having more than one leaf in the check-in DAG is called a "fork." This +Having more than one leaf in the check-in DAG is called a "fork." This is usually undesirable and either avoided entirely, as in figure 1, or else quickly resolved as shown in figure 3. But sometimes, one does want to have multiple leaves. For example, a project might have one leaf that is the latest version of the project under development and another leaf that is the latest version that has been @@ -131,19 +131,19 @@
          Figure 4 The hypothetical scenario of figure 4 is this: The project starts and -progresses to a point where (at check-in 2) +progresses to a point where (at check-in 2) it is ready to enter testing for its first release. In a real project, of course, there might be hundreds or thousands of check-ins before a project reaches this point, but for simplicity of presentation we will say that the project is ready after check-in 2. The project then splits into two branches that are used by separate teams. The testing team, using the blue branch, finds and fixes a few bugs. This is shown by check-ins 6 and 9. Meanwhile the development -team, working on the top uncolored branch, +team, working on the top uncolored branch, is busy adding features for the second release. Of course, the development team would like to take advantage of the bug fixes implemented by the testing team. So periodically, the changes in the test branch are merged into the dev branch. This is shown by the dashed merge arrows between check-ins 6 and 7 and between @@ -174,27 +174,26 @@ A tag is a name that is attached to a check-in. A property is a name/value pair. Internally, fossil implements tags as properties with a NULL value. So, tags and properties really are much the same thing, and henceforth we will use the word "tag" -to mean either a tag or a property. +to mean either a tag or a property. -A tag can be a one-time tag, a propagating tag or a cancellation tag. +A tag can be a one-time tag, a propagating tag or a cancellation tag. A one-time tag only applies to the check-in to which it is attached. A propagating tag applies to the check-in to which it is attached and also to all direct descendants of that check-in. A direct descendant -is a descendant through direct children. Tags propagation does not +is a descendant through direct children. Tag propagation does not cross merges. Tag propagation also stops as soon as it encounters another check-in with the same tag. A cancellation tag is attached to a single check-in in order to either override a one-time tag that was previously placed on that same check-in, or to block tag propagation from an ancestor. -Every repository is created with a single empty check-in that has two -propagating tags. In figure 5, that initial empty check-in is check-in 1. -The branch tag tells (by its value) -what branch the check-in is a member of. +The initial check-in of every repository has two propagating tags. In +figure 5, that initial check-in is check-in 1. The branch tag +tells (by its value) what branch the check-in is a member of. The default branch is called "trunk." All tags that begin with "sym-" are symbolic name tags. When a symbolic name tag is attached to a check-in, that allows you to refer to that check-in by its symbolic name rather than by its 40-character SHA1 hash name. When a symbolic name tag propagates (as does the sym-trunk tag) then referring to that @@ -212,12 +211,12 @@ Check-in 4 also has a sym-test tag, which gives the symbolic name "test" to check-ins 4, 6, and 9. Because tags do not propagate across merges, check-ins 7, 8, and 10 do not inherit the sym-test tag and are hence not known by the name "test." -To prevent the sym-trunk tag from propagating from check-in 1 -into check-ins 4, 6, and 9, there is a cancellation tag for +To prevent the sym-trunk tag from propagating from check-in 1 +into check-ins 4, 6, and 9, there is a cancellation tag for sym-trunk on check-in 4. The net effect is that check-ins on the trunk go by the symbolic name of "trunk" and check-ins on the test branch go by the symbolic name "test." The bgcolor=blue tag on check-in 4 causes the background color @@ -225,11 +224,11 @@ Figure 5 also shows two one-time tags on check-in 9. (The diagram does not make a graphical distinction between one-time and propagating tags.) The sym-release-1.0 tag means that check-in 9 can be referred to using the more meaningful name "release-1.0." The closed tag means -that check-in 9 is a "closed leaf." A closed leaf is a leaf that should +that check-in 9 is a "closed leaf." A closed leaf is a leaf that should never have direct children.

          Review Of Terminology

          Index: www/build.wiki ================================================================== --- www/build.wiki +++ www/build.wiki @@ -17,12 +17,12 @@

          Building and installing is very simple. Three steps:

          1. Download and unpack a source tarball or ZIP.
          2. ./configure; make -
          3. Move or copy the resulting "fossil" executable to someplace - on your $PATH. +
          4. Move the resulting "fossil" or "fossil.exe" executable to someplace on +your $PATH.


          1.0 Obtaining The Source Code

          @@ -33,11 +33,11 @@ released versions of fossil are available from the downloads page. To obtain a development version of fossil, follow these steps:

            -
          1. Point your web browser at +

          2. Point your web browser to http://www.fossil-scm.org/.

          3. Click on the Timeline @@ -48,21 +48,41 @@ link.

          4. Finally, click on one of the "Zip Archive" or "Tarball" links, according to your preference. These link will build a ZIP archive or a gzip-compressed tarball of the -complete source code and download it to your browser. +complete source code and download it to your computer.

          + +

          Aside: Is it really safe to use an unreleased development version of +the Fossil source code?

          + +Yes! Any check-in on the +[/timeline?t=trunk | trunk branch] of the Fossil +[http://fossil-scm.org/fossil/timeline | Fossil self-hosting repository] +will work fine. (Dodgy code is always on a branch.) In the unlikely +event that you pick a version with a serious bug, it still won't +clobber your files. Fossil uses several +[./selfcheck.wiki | self-checks] prior to committing any +repository change that prevent loss-of-work due to bugs. + +The Fossil [./selfhost.wiki | self-hosting repositories], especially +the one at [http://www.fossil-scm.org/fossil], usually run a version +of trunk that is less than a week or two old. Look at the bottom +left-hand corner of this screen (to the right of "This page was +generated in...") to see exactly which version of Fossil is +rendering this page. It is always safe to use whatever version +of the Fossil code you find running on the main Fossil website.

          2.0 Compiling

          1. Unpack the ZIP or tarball you downloaded then cd into the directory created.

          2. -
          3. (Optional, unix only) +
          4. (Optional, Unix only) Run ./configure to construct a makefile.
            1. If you do not have the OpenSSL library installed on your system, then @@ -80,33 +100,57 @@

            2. Run "make" to build the "fossil" or "fossil.exe" executable. The details depend on your platform and compiler.

              1. Unix → the configure-generated Makefile should work on -all unix and unix-like systems. Simply type "make". +all Unix and Unix-like systems. Simply type "make".

              2. Unix without running "configure" → if you prefer to avoid running configure, you can also use: make -f Makefile.classic. You may want to make minor edits to Makefile.classic to configure the build for your system. -

              3. MinGW/MinGW-w64 → Use the mingw makefile: +

              4. MinGW3.x (not 4.0)/MinGW-w64 → Use the mingw makefile: "make -f win/Makefile.mingw". On a Windows box you will need either Cygwin or Msys as build environment. On Cygwin, Linux or Darwin you may want to make minor edits to win/Makefile.mingw to configure the cross-compile environment. -

              5. VC++ → Use the msc makefile. First +Hint: don't use MinGW-4.0, it will compile but fossil won't work correctly, see +https://www.fossil-scm.org/index.html/tktview/18cff45a4e210430e24c. + +

              6. MSVC → Use the MSVC makefile. First change to the "win/" subdirectory ("cd win") then run -"nmake /f Makefile.msc". +"nmake /f Makefile.msc".

                Alternatively, the batch +file "win\buildmsvc.bat" may be used and it will attempt to +detect and use the latest installed version of MSVC.

                To enable +the optional OpenSSL support, +first download the official +source code for OpenSSL and extract it to an appropriately named +"openssl-X.Y.ZA" subdirectory within the local +[/tree?ci=trunk&name=compat | compat] directory (e.g. +"compat/openssl-1.0.2a"), then make sure that some recent +Perl binaries are installed locally, +and finally run one of the following commands: +

                +nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin
                +
                +
                +buildmsvc.bat FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin
                +
                + +
              7. Cygwin → The same as other Unix-like systems. It is +recommended to configure using: "configure --disable-internal-sqlite", +making sure you have the "libsqlite3-devel" , "zlib-devel" and +"openssl-devel" packages installed first.

            3.0 Installing

            1. -

              The finished binary is named "fossil" (or "fossil.exe" on windows). +

              The finished binary is named "fossil" (or "fossil.exe" on Windows). Put this binary in a directory that is somewhere on your PATH environment variable. It does not matter where.

            2. @@ -134,9 +178,11 @@
            3. To build on older Macs (circa 2002, MacOS 10.2) edit the Makefile generated by configure to add the following lines:

                 TCC += -DSQLITE_WITHOUT_ZONEMALLOC
              +  TCC += -D_BSD_SOURCE
                 TCC += -DWITHOUT_ICONV
                 TCC += -Dsocketlen_t=int
              -  
              + TCC += -DSQLITE_MAX_MMAP_SIZE=0 +
        Index: www/changes.wiki ================================================================== --- www/changes.wiki +++ www/changes.wiki @@ -1,7 +1,263 @@ Change Log +

        Changes for Version 1.33 (not released yet)

        + * Add [/help?cmd=import|fossil import --svn], for importing a subversion + repository into fossil which was exported using "svnadmin dump". + +

        Changes for Version 1.32 (2015-03-14)

        + * When creating a new repository using [/help?cmd=init|fossil init], ensure + that the new repository is fully compatible with historical versions of + Fossil by having a valid manifest as RID 1. + * Anti-aliased rendering of arrowheads on timeline graphs. + * Added vi/less-style key bindings to the --tk diff GUI. + * Documentation updates to fix spellings and changes all "checkins" to + "check-ins". + * Add the --repolist option to server commands such as + [/help?cmd=server|fossil server] or [/help?cmd=http|fossil http]. + * Added the "Xekri" skin. + * Enhance the "ln=" query parameter on artifact displays to accept multiple + ranges, separate by spaces (or "+" when URL-encoded). + * Added [/help?cmd=forget|fossil forget] as an alias for + [/help?cmd=rm|fossil rm]. + +

        Changes For Version 1.31 (2015-02-23)

        + * Change the auxiliary schema by adding columns MLINK.ISAUX and MLINK.PMID + columns to the schema, to support better drawing of file change graphs. + A [/help?cmd=rebuild|fossil rebuild] is recommended but is not required. + so that the new graph drawing logic can work effectively. + * Added [/search|search] over Check-in comments, Documents, Tickets and + Wiki. Disabled by default. The search can be either a full-scan or it + can use an index that is kept up-to-date automatically. The new + /srchsetup web-page and the [/help?cmd=fts-config|fts-config] command + were added to help configure the search capability. Expect further + enhancements to the search capabilities in subsequent releases. + * Added form elements to some submenus (in particular the /timeline) + for easier operation. + * Added the --ifneeded option to [/help?cmd=rebuild|fossil rebuild]. + * Added "override skins" using the "skin:" line of the CGI script or + using the --skin LABEL option on the + [/help?cmd=server|server], + [/help?cmd=ui|ui], or + [/help?cmd=http|http] commands. + * Embedded html documents that begin with + <doc class="fossil-doc"> are displayed with standard + headers and footers added. + * Allow <div style='...'> markup in [/wiki_rules|wiki]. + * Renamed "Events" to "Technical Notes", while updating the technote + display and control pages. Add support for technotes as plain text + or as Markdown. + * Added the [/md_rules] pages containing summary instructions on the + Markdown format. + * Added the --repolist and --nojail options to the various server commands + (ex: [/help?cmd=server|fossil server]). + * Added the [/help?cmd=all|fossil all add] subcommand to "fossil all". + * Improvements to the /login page. Some hyperlinks to pages that require + "anonymous" privileges are displayed even if the current user is "nobody" + but automatically redirect to /login. + * The [/help?cmd=/doc|/doc] web-page will now try to deliver the file + "404.md" from the top-level directory (if such a file exists) in + place of its built-in 404 text. + * Download of Tarballs and ZIP Archives by user "nobody" is now enabled + by default in new repositories. + * Enhancements to the table sorting controls. More display tables + are now sortable. + * Add IPv6 support to [/help?cmd=sync|fossil sync] and + [/help?cmd=clone|fossil clone] + * Add more skins such as "San Francisco Modern" and "Eagle". + * During shutdown, check to see if the check-out database (".fslckout") + contains a lot of free space, and if it does, VACUUM it. + * Added the [/mimetype_list] page. + * Added the [/hash-collisions] page. + * Allow the user of Common Table Expressions in the SQL that defaults + ticket reports. + * Break out the components (css, footer, and header) for the + various built-in skins into separate files in the source tree. + +

        Changes For Version 1.30 (2015-01-19)

        + * Added the [/help?cmd=bundle|fossil bundle] command. + * Added the [/help?cmd=purge|fossil purge] command. + * Added the [/help?cmd=publish|fossil publish] command. + * Added the [/help?cmd=unpublished|fossil unpublished] command. + * Enhance the [/tree] webpage to show the age of each file with the option + to sort by age. + * Enhance the [/brlist] webpage to show additional information about each branch + and to be sortable by clicking on column headers. + * Add support for Docker. Just install docker and type + "sudo docker run -d -p 8080:8080 nijtmans/fossil" to get it running. + * Add the [/help/fusefs|fossil fusefs DIRECTORY] command that mounts a + Fuse Filesystem at the given DIRECTORY and populates it with read-only + copies of all historical check-ins. This only works on systems that + support FuseFS. + * Add the administrative log that records all configuration. + * Added the [/sitemap] webpage. + * Added the [/bloblist] web page. + * Let [/help?cmd=new|fossil new] no longer create an initial empty commit + by default. The first commit after checking out an empty repository will + become the initial commit. + * Added the [/help?cmd=all|fossil all dbstat] and + [/help?cmd=all|fossil all info] commands. + * Update SQLite to version 3.8.8. + * Added the --verily option to the [/help?cmd=clean|fossil clean] command. + * Add the "autosync-tries" setting to control the number of autosync attempts + before returning an error. + * Added a compile-time option (--with-miniz) to build using miniz instead + of zlib. Disabled by default. + * Support customization of commands and webpages, including the ability to + add new ones, via the "TH1 hooks" feature. Disabled by default. Enabled + via a compile-time option. + * Add the [checkout], [render], [styleHeader], [styleFooter], + [trace], [getParameter], [setParameter], [artifact], and + [globalState] commands to TH1, primarily for use by TH1 hooks. + * Automatically adjust the width of command-line timeline output according to the + detected width of the terminal. + * Prompt the user to optionally fix invalid UTF-8 at check-in. + * Added a line-number toggle option to the [/help?cmd=/info|/info] + and [/help?cmd=/artifact|/artifact] pages. + * Most commands now issue errors rather than silently ignoring unrecognized + command-line options. + * Use full 40-character SHA1 hashes (instead of abbreviations) in most + internal URLs. + * The "ssh:" sync method on Windows now uses "plink.exe" instead of "ssh" as + the secure-shell client program. + * Prevent a partial clone when the connection is lost. + * Make the distinction between 301 and 302 redirects. + * Allow commits against a closed check-in as long as the commit goes onto + a different branch. + * Improved cache control in the web interface reduces unnecessary requests + for common resources like the page logo and CSS. + * Fix a rare and long-standing sync protocol bug + that would silently prevent the sync from running to completion. Before + this bug-fix it was sometimes necessary to do "fossil sync --verily" to + get two repositories in sync. + * Add the [/finfo?name=src/foci.c|files_of_checkin] virtual table - useful + for ad hoc queries in the [/help?cmd=sqlite3|fossil sql] interface, + and also used internally. + * Added the "$secureurl" TH1 variable for use in headers and footers. + * (Internal:) Add the ability to include resources as separate files in the + source tree that are converted into constant byte arrays in the compiled + binary. Use this feature to store the Tk script that implements the --tk + diff option in a separate file for easier editing. + * (Internal:) Implement a system of compile-time checks to help ensure + the correctness of printf-style formatting strings. + * Fix CVE-2014-3566, also known as the POODLE SSL 3.0 vulnerability. + * Numerous documentation fixes and improvements. + * Other obscure and minor bug fixes - see the timeline for details. + +

        Changes For Version 1.29 (2014-06-12)

        + * Add the ability to display content, diffs and annotations for UTF16 + text files in the web interface. + * Add the "SaveAs..." and "Invert" buttons + to the graphical diff display that results + from using the --tk option with the [/help/diff | fossil diff] command. + * The [/reports] page now requires Read ("o") permissions. The "byweek" + report now properly propagates the selected year through the event type + filter links. + * The [/help/info | info command] now shows leaf status of the checkout. + * Add support for tunneling https through a http proxy (Ticket [e854101c4f]). + * Add option --empty to the "[/help?cmd=open | fossil open]" command. + * Enhanced [/help?cmd=/fileage|the fileage page] to support a glob parameter. + * Add -w|--ignore-all-space and -Z|--ignore-trailing-space options to + [/help?cmd=annotate|fossil annotate], [/help?cmd=blame|fossil blame], + [/help?cmd=diff|fossil (g)diff], [/help?cmd=stash|fossil stash diff]. + * Add --strip-trailing-cr option to [/help?cmd=diff|fossil (g)diff] and + [/help?cmd=stash|fossil stash diff]. + * Add button "Ignore Whitespace" to /annotate, /blame, /ci, /fdiff + and /vdiff UI pages. + * Enhance [/reports?view=byweekday|/reports] with a "byweekday" view. + * Enhance the [/help?cmd=cat|fossil cat] command so that it works outside + of a checkout when using the -R command-line option. + * Use full-length SHA1 hashes, not abbreviations, in most hyperlinks. + * Correctly render the <title> markup on wiki pages in the + [/help?cmd=/artifact|/artifact] webpage. + * Enhance the [/help?cmd=whatis|fossil whatis] command to report on attachments + and cluster artifacts. Added the [/help?cmd=test-whatis-all] command for + testing purposes. + * Add support for HTTP Basic Authentication on [/help?cmd=clone|clone] and + [/help?cmd=sync|sync]. + * Fix the [/help?cmd=stash|stash] so that it remembers added files and re-adds + them when the stash is applied. + * Fix the server so that it avoids writing to the database (and thus avoids + database locking issues) on a + [/help?cmd=pull|pull] or [/help?cmd=clone|clone]. + * Add support for [./server.wiki#loadmgmt|server load management] using both + a cache of expensive pages (the [/help?cmd=cache|fossil cache] command) + and by rejecting expensive page requests when the server load average is too + high. + * Add the [/help?cmd=praise|fossil praise] command as an alias for + [/help?cmd=blame|fossil blame] for subversion compatibility. + * Enhance the [/help?cmd=test-diff|fossil test-diff] command with -y or --tk + options so that it shows both filenames above their respective columns in + the side-by-side diff output. + * Issue a warning if a [/help?cmd=add|fossil add] command tries to add a file + that matches the ignore-glob. + * Add option -W|--width to "[/help?cmd=stash|fossil stash ls]" + and "[/help?cmd=leaves|fossil leaves]" commands. + * Enhance support for running as the root user. Now works on Haiku. + * Added the -empty option to [/help?cmd=new|fossil new], which + causes it to not create an initial empty commit. The first commit after + checking out a repo created this way will become the initial commit. + * Enhance sync operations by committing each round-trip to minimize number + of retransmits when autosync fails. Include option for + [/help?cmd=update| fossil update] and [/help?cmd=merge| fossil merge] to + continue even if missing content. + * Minor portability fixes for platforms where the char type is unsigned + by default. + +

        Changes For Version 1.28 (2014-01-27)

        + * Enhance [/help?cmd=/reports | /reports] to support event type filtering. + * When cloning a repository, the user name passed via the URL (if any) + is now used as the default local admin user's name. + * Enhance the SSH transport mechanism so that it runs a single instance of + the "fossil" executable on the remote side, obviating the need for a shell + on the remote side. Some users may need to add the "?fossil=/path/to/fossil" + query parameter to "ssh:" URIs if their fossil binary is not in a standard + place. + * Add the "[/help?cmd=blame | fossil blame]" command that works just like + "fossil annotate" but uses a different output format that includes the + user who made each changes and omits line numbers. + * Add the "Tarball and ZIP-archive Prefix" configuration parameter under + Admin/Configuration. + * Fix CGI processing so that it works on web servers that do not + supply REQUEST_URI. + * Add options --dirsonly, --emptydirs, and --allckouts to the + "[/help?cmd=clean | fossil clean]" command. + * Ten-fold performance improvement in large "fossil blame" or + "fossil annotate" commands. + * Add option -W|--width and --offset to "[/help?cmd=timeline | fossil timeline]" + and "[/help?cmd=finfo | fossil finfo]" commands. + * Option -n|--limit of "[/help?cmd=timeline | fossil timeline]" now + specifies the number of entries, just like all other commands which + have the -n|--limit option. The various timeline-related functions + now output "--- ?? limit (??) reached ---" at the end whenever + appropriate. Use "-n 0" if no limit is desired. + * Fix handling of password embedded in Fossil URL. + * New --once option to [/help?cmd=clone | fossil clone] command + which does not store the URL or password when cloning. + * Modify [/help?cmd=ui | fossil ui] to respect "default user" in an open + repository. + * Fossil now hides check-ins that have the "hidden" tag in timeline webpages. + * Enhance /ci_edit page to add the "hidden" tag to check-ins. + * Advanced possibilities for commit and ticket change notifications over + http using TH1 scripting. + * Add --sha1sum and --integrate options + to the "[/help?cmd=commit | fossil commit]" command. + * Add the "clean" and "extra" subcommands to the + "[/help?cmd=all | fossil all]" command + * Add the --whatif option to "[/help?cmd=clean|fossil clean]" that works the + same as "--dry-run", + so that the name does not collide with the --dry-run option of "fossil all". + * Provide a configuration option to show dates on the web timeline + as "YYMMMDD HH:MM" + * Add an option to the "stats" webpage that allows an administrator to see + the current repository schema. + * Enhancements to the "[/help?cmd=/vdiff|/vdiff]" webpage for more difference + display options. + * Added the "[/tree?ci=trunk&expand | /tree]" webpage as an alternative + to "/dir" and make it the default way of showing file lists. + * Send gzipped HTTP responses to clients that support it. +

        Changes For Version 1.27 (2013-09-11)

        * Enhance the [/help?cmd=changes | fossil changes], [/help?cmd=clean | fossil clean], [/help?cmd=extras | fossil extras], [/help?cmd=ls | fossil ls] and [/help?cmd=status | fossil status] commands to restrict operation to files and directories named on the command-line. @@ -10,11 +266,11 @@ * Renamed /stats_report page to [/reports]. Graph width is now relative, not absolute. * Added yw=YYYY-WW (year-week) filter to timeline to limit the results to a specific year and calendar week number, e.g. [/timeline?yw=2013-01]. * Updates to SQLite to prevent opening a repository file using file descriptors - 1 or 2 on unix. This fixes a bug under which an assertion failure could + 1 or 2 on Unix. This fixes a bug under which an assertion failure could overwrite part of a repository database file, corrupting it. * Added support for unlimited line lengths in side-by-side diffs. * New --close option to [/help?cmd=commit | fossil commit], which immediately closes the branch being committed. * Added chart option to [/help?cmd=bisect | fossil bisect]. @@ -33,11 +289,11 @@ [/help?cmd=server | fossil server] commands can take an IP address in addition to the port number, causing Fossil to bind to just that one IP address. * After prompting for a password, also ask if that password should be remembered. * Performance improvements to the diff engine. - * Fix the side-by-side diff engine to work better with multi-byte unicode text. + * Fix the side-by-side diff engine to work better with multi-byte Unicode text. * Color-coding in the web-based annotation (blame) display. Fix the annotation engine so that it is no longer confused by time-warps. * The markdown formatter is now available by default and can be used for tickets, wiki, and embedded documentation. * Add subcommands "fossil bisect log" and "fossil bisect status" to the @@ -55,11 +311,11 @@ See [http://cygwin.com/cygwin-ug-net/using-specialnames.html#pathnames-casesensitive] * Enhancements to /timeline.rss, adding more flags for filtering results, including the ability to subscribe to changes made to individual tickets. For example: [/timeline.rss?y=t&tkt=12fceeec82]. - * Improved handling of the differences between case-sensitive and + * Improved handling of the differences between case-sensitive and case-insensitive filesystems. * JSON API: added the 'status' command to report local checkout status. * Fixes to the --args support and documented this feature in the help. * Added [/stats_report] page. * Added ym=YYYY-MM filter to the [/timeline?ym=2013-06]. @@ -68,20 +324,19 @@ * Added [/hash-color-test] web page. * Cherry-pick merges are recorded internally (though no yet displayed on the timeline graph.) * Bring in the latest versions of SQLite, zlib, and autosetup from upstream. -

        Changes For Version 1.25 (2013-02-16)

        * Enhancements to ticket processing. There are now two tables: TICKET and TICKETCHNG. There is one row in TICKETCHNG for each ticket artifact. Fields from ticket artifacts go into either or both of TICKET and - TICKETCHNG, whichever contain matching column names. Default ticket + TICKETCHNG, whichever contain matching column names. Default ticket edit and viewing scripts are updated to use TICKETCHNG. The TH1 scripting language is enhanced to support this, including the new "query" command for doing SQL queries against the repository database. - All changes should be backwards compatible. + All changes should be backwards compatible. * Add the ability to moderate ticket and wiki changes. Unmoderated changes do not sync and may be deleted by the moderator if found to contain spam or other objectionable content. * Add javascript so that clicking on a node of the timeline graph selects that node. Then clicking on a second node shows a diff between the @@ -93,17 +348,17 @@ sorts by the indicated column. * Add the "fossil cat" command which is basically an alias for "fossil finfo -p". * Hyperlinks with the class "button" are rendered as submenu buttons on embedded documentation. - * The check-in comment editor on windows now defaults to NotePad.exe. - * Correctly deal with BOMs in check-in comments. Also attempt to convert + * The check-in comment editor on Windows now defaults to NotePad.exe. + * Correctly deal with BOMs in check-in comments. Also attempt to convert check-in comments to UTF8 from other encodings. * Allow the deletion of multiple stash entries using multiple arguments to the "fossil stash rm" command. * Enhance the "fossil server DIRECTORY" command to serve static content - files contained in DIRECTORY. For security, only files with a + files contained in DIRECTORY. For security, only files with a recognized suffix (such as *.html, *.jpg, *.txt, etc) will be delivered as static content, and *.fossil files are not on the list of recognized suffixes. There are additional restrictions on the names of the files. * Allow the "fossil ui" command to specify a directory as long as the the --notfound option is used. @@ -126,13 +381,13 @@ * Add options to "fossil commit" to override the various sanity checks. Options added: --allow-empty, --allow-fork, --allow-older, and --allow-conflict. * Optionally require a CAPTCHA (controlled by a setting on the Admin/Access webpage) when a user who is not logged in tries to - edit wiki, or a ticket, or an attachment. + edit wiki, or a ticket, or an attachment. * Improvements to the "ssh://" sync protocol, to help it move past - noisey motd comments. + noisy motd comments. * Add the uf=FILE-SHA1-HASH query parameter to the timeline, causing the timeline to show only check-ins that contain the specific file identified by FILE-SHA1-HASH. ("uf" stands for "uses file".) * Enhance the file change annotator so that it follows the file across name changes. @@ -147,11 +402,11 @@ * Disallow invalid UTF8 characters (such as characters in the surrogate pair range) in filenames. * Judge the UserAgent strings issued by the NetSurf webbrowser to be coming from a human, not from a bot. * Add the zlib sources to the Fossil source tree (under compat/zlib) and - use those sources when compiling on (windows) systems that do not have + use those sources when compiling on (Windows) systems that do not have a zlib library installed by default. * Prompt the user with the option to convert non-UTF8 files into UTF8 when committing. * Allow the characters *[]? in filenames. * Allow the --context option on diff commands to have a value of 0. @@ -167,25 +422,25 @@ * Allow style= attribute to occur in HTML markup on wiki pages. * Added the --tk option to the "fossi diff" and "fossil stash diff" commands, causing color-coded diff output to be displayed in a Tcl/Tk GUI window. This option only works if Tcl/Tk is installed on the host. - * On windows, make the "gdiff" command default to use WinDiff.exe. + * On Windows, make the "gdiff" command default to use WinDiff.exe. * Update the "fossil stash" command so that it always prompts for a comment if the -m option is omitted. * Enhance the timeline webpages so that a=, b=, c=, d=, p=, and dp= - query parameters (and others) can all accept any valid checkin name + query parameters (and others) can all accept any valid check-in name (such as branch names or labels) instead of just SHA1 hashes. * Added the "fossil stash show" command. * Added the "fileage" webpage with links to this page from the check-in information page and from the file browser. * Added --age and -t options to the "fossil ls" command. * Added the --setmtime option to "fossil update". When used, the mtime - of all mananged files is set to the time when the most recent version of + of all managed files is set to the time when the most recent version of the file was checked in. * Changed the "vdiff" webpage to show the complete text of files that - were added or removed (the equivelent of using the -N or --newfile + were added or removed (the equivalent of using the -N or --newfile options with the "fossil diff" command-line.) * Added the --temp option to "fossil clean" and "fossil extra", causing those commands to only look at temporary files generated by Fossil, such as merge-conflict reports or aborted check-in messages. * Enhance the raw page download so that it can guess the mimetype of @@ -231,24 +486,24 @@ * Merge the latest SQLite changes from upstream. * Lots of minor bug fixes.

        Changes For Version 1.23 (2012-08-08)

        * The default checkout database name is now ".fslckout" instead of - "_FOSSIL_" on unix. Both names continue to work. + "_FOSSIL_" on Unix. Both names continue to work. * Added the "fossil all changes" command * Added the --ckout option to the "fossil all list" command * Added the "public-pages" glob pattern that can be configured to allow anonymous users to see embedded documentation on sites where source code should not be accessible to anonymous users. * Allow multiple --tag options on the same "fossil commit" command. * Change the meaning of the --bgcolor option for "fossil commit" to only - change the color for that one commit. The new --branchcolor option + change the color for that one commit. The new --branchcolor option is available to set a persistent background color. * Add the branch= query parameter to the vdiff page and the --branch option to the "fossil diff" command. * Check-in names of the form "root:BRANCH" now refer to the origin of - the branch. Hence to see all changes in a branch, use + the branch. Hence to see all changes in a branch, use "fossil diff --from root:BRANCH --to BRANCH". The --branch option on the diff command is an alias for the same. * Add the ability to configure ad-units to be displayed between the menu bar and the content. * Add the ability to set a background image as part of server configuration. @@ -255,18 +510,18 @@ * Allow partial commits of cherrypick merges. * Updates against an uncommitted merge are now a warning, not a fatal error. * Prompt the user to continue if a check-in comment is unedited. * Fixes to case sensitivity settings with the /dir webpage. * Repositories now try to remember the locations of all checkouts and - web-access URLs and display this information with the + web-access URLs and display this information with the "fossil info $REPO" command. * Improved defense against spiders: The src= attribute of <a> elements is set using javascript after the page loads. * Enhanced formatting of the user list page. * If a file named in "fossil add" is missing, that is now a warning instead of a fatal error. - * Fix side-by-side diff so that it displays correctly with + * Fix side-by-side diff so that it displays correctly with multi-byte UTF8 characters. * Performance improvements in the diff logic. * Other performance tweaks and documentation updates.

        Changes For Version 1.22 (2012-03-17)

        @@ -275,11 +530,11 @@ * Promote "allow-symlinks" to a versionable setting * Harden the CGI processing logic against DOS attacks * Add the ability to run TH1 scripts after sync requests * Store the repository name in _FOSSIL_ as it is type in the "open" command, possibly as a relative pathname. - * Make ".fslckout" the alternative name for the "_FOSSIL_" file. + * Make ".fslckout" the alternative name for the "_FOSSIL_" file. * Change the "ssh:" transfer method to allow all access regardless of user permission. * Improvements to the timeline messages associated with tag changes. (Requires a "[/help/rebuild | fossil rebuild]" to take effect.) * Various additions and fixes for the JSON API. @@ -289,27 +544,27 @@ * Update to use SQLite version 3.7.11. * Various minor bug fixes.

        Changes For Version 1.21 (2011-12-13)

        * Added side-by-side diffs in the command-line interface - * Automatically enable hyperlinks if the UserAgent string in the + * Automatically enable hyperlinks if the UserAgent string in the HTTP header suggests that the requestor is a human and not a bot. * Show only commonly used commands with "fossil help". Use "fossil help --all" to see the complete list now. - * Improvements to the "stash" command: (1) Stash all files, not just - those below the working directory. (2) Add the --detail option to - "list". (3) Confirm before "drop --all". (4) Add the "help" + * Improvements to the "stash" command: (1) Stash all files, not just + those below the working directory. (2) Add the --detail option to + "list". (3) Confirm before "drop --all". (4) Add the "help" subcommand. * Add an Admin/Access setting to change the number of octets of the IP address that are saved in login cookies - allowing this setting to be changed to zero * Promote the "test-md5sum" command to "md5sum". * Added the "whatis" command. * Stop showing the server-code in status outputs - it is no longer used for anything. - * Added a compile-time option (--with-tcl) to build in the full - TCL interpreter to augment TH1. + * Added a compile-time option (--with-tcl) to build in full Tcl scripting + support via integration with TH1. * Merged the JSON branch into trunk. Disabled by default. Enabled by a compile-time option. Probably it will be enabled by default in some future release. * Update to use SQLite version 3.7.9 plus the alignment fix for Sparc. align @@ -318,26 +573,26 @@ * Added side-by-side diffs in HTML interface. [0bde74ea1e] * Added support for symlinks. (Controlled by "allow-symlinks" setting, off by default). [e4f1c1fe95] * Fixed CLI annotate to show the proper file version in case there are multiple equal versions in history. [e161670939] - * Timeline now shows tag changes (requires rebuild).[87540ed6e6] + * Timeline now shows tag changes (requires rebuild).[87540ed6e6] * Fixed annotate to show "more relevant" versions of lines in some cases. [e161670939] * New command: ticket history. [98a855c508] * Disabled SSLv2 in HTTPS client.[ea1d369d23] * Fixed constant prompting regarding previously-saved SSL certificates. [636804745b] * Other SSL improvements. * Added -R REPOFILE support to several more CLI commands. [e080560378] * Generated tarballs now have constant timestamps, so they are - always identical for any given checkin. [e080560378] + always identical for any given check-in. [e080560378] * A number of minor HTML-related tweaks and fixes. * Added --args FILENAME global CLI argument to import arbitrary CLI arguments from a file (e.g. long file lists). [e080560378] * Fixed significant memory leak in annotation of files with long - histories.[9929bab702] + histories.[9929bab702] * Added warnings when a merge operation overwrites local copies (UNDO is available, but previously this condition normally went silently unnoticed). [39f979b08c] * Improved performance when adding many files. [a369dc7721] * Improve merges which contain many file renames. [0b93b0f958] @@ -350,18 +605,17 @@ * Now works on MSVC with repos >2GB. [6092935ff2] * A number of code cleanups to resolve warnings from various compilers. * Update the built-in SQLite to version 3.7.9 beta.

        Changes For Version 1.19 (2011-09-02)

        - * Added a ./configure script based on autosetup. * Added the "[/help/winsrv | fossil winsrv]" command - for creating a Fossil service on windows systems. + for creating a Fossil service on Windows systems. * Added "versionable settings" where settings that affect the local tree can be stored in versioned files in the .fossil-settings directory. - * Background colors for branches are choosen automatically if no + * Background colors for branches are chosen automatically if no color is specified by the user. * The status, changes and extras commands now show pathnames relative to the current working directory, unless overridden by command line options or the "relative-paths" setting.
        WARNING: This @@ -372,19 +626,18 @@ * Added support for client-side SSL certificates with "ssl-identity" setting and --ssl-identity option. * Added "ssl-ca-location" setting to specify trusted root SSL certificates. * Added the --case-sensitive BOOLEAN command-line option to many commands. - Default to true for unix and false for windows. + Default to true for Unix and false for Windows. * Added the "Color-Test" submenu button on the branch list web page. * Compatibility improvements to the git-export feature. * Performance improvements on SHA1 checksums * Update to the latest SQLite version 3.7.8 alpha. * Fix the tarball generator to work with very log pathnames

        Changes For Version 1.18 (2011-07-14)

        - * Added this Change Log * Added sequential version numbering * Added a optional configure script - the Makefile still works for most systems. * Improvements to the "annotate" algorithm: only search primary Index: www/checkin_names.wiki ================================================================== --- www/checkin_names.wiki +++ www/checkin_names.wiki @@ -43,11 +43,11 @@ Fossil provides a variety of ways to specify a check-in. This document describes the various methods.

        Canonical Check-in Name

        -The canonical name of a checkin is the SHA1 hash of its +The canonical name of a check-in is the SHA1 hash of its [./fileformat.wiki#manifest | manifest] expressed as a 40-character lowercase hexadecimal number. For example:
         fossil info e5a734a19a9826973e1d073b49dc2a16aa2308f9
        @@ -115,10 +115,31 @@
         
         The "tag:deed2" name will refer to the most recent check-in 
         tagged with "deed2" not to the
         check-in whose canonical name begins with "deed2".
         
        +

        Whole Branches

        + +Usually when a branch name is specified, it means the latest check-in on +that branch. But for some commands (ex: [/help/purge|purge]) a branch name +on the argument means the earliest connected check-in on the branch. This +seems confusing when being explained here, but it works out to be intuitive +in practice. + +For example, the command "fossil purge XYZ" means to purge the check-in XYZ +and all of its descendants. But when XYZ is in the form of a branch name, one +generally wants to purge the entire branch, not just the last check-in on the +branch. And so for this reason, commands like purge will interpret a branch +name to be the first check-in of the branch rather than the last. If there +are two or more branches with the same name, then these commands will select +the first check-in of the branch that has the most recent check-in. What +happens is that Fossil searches for the most recent check-in with the given +tag, just as it always does. But if that tag is a branch name, it then walks +back down the branch looking for the first check-in of that branch. + +Again, this behavior only occurs on a few commands where it make sense. +

        Timestamps

        A timestamp in one of the formats shown below means the most recent check-in that occurs no later than the timestamp given: @@ -138,13 +159,13 @@ in Universal Coordinated Time (UTC). This tends to work the best for distributed projects where participants are scattered around the globe. But there is an option on the Admin/Timeline page of the web-interface to switch to local time. The "Z" suffix on an timestamp check-in name is meaningless if Fossil is in the default mode of using UTC for -everything, but if Fossil has been switched to localtime mode, then the +everything, but if Fossil has been switched to local time mode, then the "Z" suffix means to interpret that particular timestamp using -UTC instead localtime. +UTC instead of local time. For an example of how timestamps are useful, consider the homepage for the Fossil website itself:
        Index: www/concepts.wiki ================================================================== --- www/concepts.wiki +++ www/concepts.wiki @@ -79,11 +79,11 @@

        2.1 Identification Of Artifacts

        A particular version of a particular file is called an "artifact". Each artifact has a universally unique name which is the -SHA1 hash of the content +SHA1 hash of the content of that file expressed as 40 characters of lower-case hexadecimal. Such a hash is referred to as the Artifact Identifier or Artifact ID for the artifact. The SHA1 algorithm is created with the purpose of providing a highly forgery-resistant identifier for a file. Given any file it is simple to find the artifact ID for that file. But given a @@ -124,11 +124,12 @@ a software project.

        2.2 Manifests

        Associated with every check-in is a special file called the -"manifest". The manifest is a listing of all other files in +[./fileformat.wiki#manifest| "manifest"]. The manifest is a +listing of all other files in that source tree. The manifest contains the (complete) artifact ID of the file and the name of the file as it appears on disk, and thus serves as a mapping from artifact ID to disk name. The artifact ID of the manifest is the identifier for the entire check-in. When you look at a "timeline" of changes in fossil, the ID associated @@ -177,11 +178,11 @@

      3.0 Fossil - The Program

      Fossil is software. The implementation of fossil is in the form -of a single executable named "fossil" (or "fossil.exe" on windows). +of a single executable named "fossil" (or "fossil.exe" on Windows). To install fossil on your system, all you have to do is obtain a copy of this one executable file (either by downloading a pre-compiled version or [./build.wiki | compiling it yourself]) and then @@ -196,11 +197,15 @@ is identified by your VISUAL environment variable. Fossil will also use GPG to clearsign your manifests if you happen to have it installed, but fossil will skip that step if GPG missing from your system. You can optionally set up fossil to use external "diff" programs, though fossil has an excellent built-in "diff" algorithm that works -fine for most people. +fine for most people. If you happen to have Tcl/Tk installed on your +system, Fossil will use it to generate a graphical "diff" display when +you use the --tk option to the "diff" command, but this too is entirely +optional. + To uninstall fossil, simply delete the executable. To upgrade an older version of fossil to a newer version, just replace the old executable with the new one. You might need to @@ -368,11 +373,11 @@ local repository.
    • Once changes are in your local repository, use -use the update command to merge them to your local source tree. +the update command to merge them to your local source tree. If you merge in some changes and find that the changes do not work out or are not to your liking, you can back out the changes using the undo command.
    • @@ -390,75 +395,32 @@

      5.0 Setting Up A Fossil Server

      With other configuration management software, setting up a server is a lot of work and normally takes time, patience, and a lot of system knowledge. Fossil is designed to avoid this frustration. Setting up -a server with fossil is ridiculously easy. You have three options:

      +a server with fossil is ridiculously easy. You have four options:

        -
      1. Setting up a stand-alone server - -From within your source tree just use the server command and -fossil will start listening for incoming requests on TCP port 8080. -You can point your web browser at -http://localhost:8080/ and begin exploring. Or your coworkers -can do pushes or pulls against your server. Use the --port -option to the server command to specify a different TCP port. If -you do not have a local source tree, use the -R command-line -option to specify the repository file. - -A stand-alone server is a great way to set of transient connections -between coworkers for doing quick pushes or pulls. But you can also -set up a permanent stand-alone server if you prefer. Just make -arrangements for fossil to be launched with appropriate arguments -after every reboot. - -If you just want a server to browse the built-in fossil website -locally, use the ui command in place of server. The -ui command starts up a local server too, but it also takes -the additional step of automatically launching your webbrowser and -pointing at the new server. -
      2. - -
      3. Setting up a CGI server - -If you have a web-server running on your machine already, you can -set up fossil to be run from CGI. Simply create an executable script -that looks something like this: - -
        -#!/usr/local/bin/fossil
        -repository: /home/me/bigproject.fossil
        -
        - -Edit this script to use whatever pathnames are appropriate for -your project. Then point your web browser at the script and off you -go. The [./selfhost.wiki | self-hosting fossil repositories] are -all set up this way.
      4. - -
      5. Setting up an inetd server - -If you have inetd or xinetd running on your system, you can set -those services up to launch fossil to deal with inbound TCP/IP connections -on whatever port you want. Set up inetd or xinetd to launch fossil -like this: - -
        -/usr/local/bin/fossil http /home/me/bigproject.fossil
        -
        - -As before, change the filenames to whatever is appropriate for -your system. You can have fossil run as any user that has write -permission on the repository and on the directory that contains the -repository. But it is safer to run fossil as root. When fossil -sees that it is running as root, it automatically puts itself into -a chroot jail and -drops all privileges prior to reading any information from the client. -Since fossil is a stand-alone program, you do not need to put anything -in the chroot jail with fossil in order for it to do its job. -
      6. -
      +
    • Stand-alone server. +Simply run the [/help?cmd=server|fossil server] or +[/help?cmd=ui|fossil ui] command from the command-line. + +

    • CGI. +Install a 2-line CGI script on a CGI-enabled web-server like Apache. + +

    • SCGI. +Start an SCGI server using the +[/help?cmd=server| fossil server --scgi] command for handling +SCGI requests from web-servers like Nginx. + +

    • Inetd or Stunnel. +Configure programs like inetd, xinetd, or stunnel to hand off HTTP requests +directly to the [/help?cmd=http|fossil http] command. + + +See the [./server.wiki | How To Configure A Fossil Server] document +for details.

      6.0 Review Of Key Concepts

      • The fossil program is a self-contained stand-alone executable. Index: www/contribute.wiki ================================================================== --- www/contribute.wiki +++ www/contribute.wiki @@ -15,11 +15,11 @@ and other lawyer-rich organizations require this as a precondition to using Fossil. If you do not wish to submit a Contributor Agreement, we would still welcome your suggestions and example code, but we will not use your code -directly - we will be forced to reimplement your changes from scratch which +directly - we will be forced to re-implement your changes from scratch which might take longer.

        2.0 Submitting Patches

        Suggested changes or bug fixes can be submitted by creating a patch @@ -51,11 +51,11 @@ Contributors are asked to make all non-trivial changes on a branch. The Fossil Architect (Richard Hipp) will merge changes onto the trunk.

        Contributors are required to following the -[./checkin.wiki | pre-checkin checklist] prior to every checkin to +[./checkin.wiki | pre-checkin checklist] prior to every check-in to the Fossil self-hosting repository. This checklist is short and succinct and should only require a few seconds to follow. Contributors should print out a copy of the pre-checkin checklist and keep it on a notecard beside their workstations, for quick reference. Index: www/copyright-release.html ================================================================== --- www/copyright-release.html +++ www/copyright-release.html @@ -3,11 +3,11 @@
    • This agreement applies to your contribution of material to the Fossil Software Configuration Management System ("Fossil") that is -mananged by Hipp, Wyrick & Company, Inc. ("Hwaci") and +managed by Hipp, Wyrick & Company, Inc. ("Hwaci") and sets out the intellectual property rights you grant to Hwaci in the contributed material. The terms "contribution" and "contributed material" mean any source code, object code, patch, tool, sample, graphic, specification, manual, documentation, or any other material posted, submitted, or uploaded by @@ -66,11 +66,11 @@ grant the rights set out in this agreement.

    • Your contribution does not, to the best of your knowledge and belief, violate any third party's copyrights, trademarks, patents, or other intellectual property rights.
    • You are authorized to sign this agreement on behalf of your - company (if appliable). + company (if applicable).

      By filling in the following information and signing your name, you agree to be bound by all of the terms ADDED www/customskin.md Index: www/customskin.md ================================================================== --- /dev/null +++ www/customskin.md @@ -0,0 +1,236 @@ +Theming +======= + +Every HTML page generated by Fossil has the following basic structure: + + +

      + + + +
      Header
      +Fossil-Generated Content
      Footer
      + +The header and footer control the "look" of Fossil pages. Those +two sections can be customized separately for each repository to +develop a new theme. + +The header will normally look something like this: + + + ... + + ... top banner and menu bar ... +
      + +And the footer will look something like this: + +
      + ... bottom material ... + + + +The <head> element in the header will normally reference the +/style.css CSS file that Fossil stores internally. (The $stylesheet_url +TH1 variable, described below, is useful for accomplishing this.) + +The middle "content" section comprised the bulk of most pages and +contains the actual Fossil-generated data +that the user is interested in seeing. The text of this content +section is not normally configurable. The content text can be styled +using CSS, but it otherwise fixed. Hence it is the header and footer +and the CSS that determine the look of a repository. +We call the bundle of built-in CSS, header, and footer a "skin". + +Built-in Skins +-------------- + +Fossil comes with several built-in skins. The sources to these built-ins can +be found in the Fossil source tree under the skins/ folder. The skins/ +folder contains a separate subfolder for each built-in skin, with each +subfolders holding four files, "css.txt", "details.txt", +"footer.txt", and "header.txt", +that describe the CSS, rendering options, +footer, and header for that skin, respectively. + +The skin of a repository can be changed to any of the built-in skins using +the web interface by going to the /setup_skin web page (requires Admin +privileges) and clicking the appropriate button. Or, the --skin command +line option can be used for the +[fossil ui](../../../help?cmd=ui) or +[fossil server](../../../help?cmd=server) commands to force that particular +instance of Fossil to use the specified built-in skin. + +Sharing Skins +------------- + +The skin of a repository is not part of the versioned state and does not +"push" or "pull" like checked-in files. The skin is local to the +repository. However, skins can be shared between repositories using +the [fossil config](../../../help?cmd=configuration) command. +The "fossil config push skin" command will send the local skin to a remote +repository and the "fossil config pull skin" command will import a skin +from a remote repository. The "fossil config export skin FILENAME" +will export the skin for a repository into a file FILENAME. This file +can then be imported into a different repository using the +"fossil config import FILENAME" command. Unlike "push" and "pull", +the "export" and "import" commands are able to move skins between +repositories for different projects. So, for example, if you have a +group of related repositories, you can develop a skin for one of them, +then get a consistent look across all the repositories by exporting +the skin from the first repository and importing into all the others. + +The file generated by "fossil config export" could be checked into +one of your repositories and versioned, if desired. This will not +automatically change the skin when looking backwards in time, but it +will provide an historical record of what the skin used to be and +allow the historical look of the repositories to be recreated if +necessary. + +When cloning a repository, the skin of new repository is initialized to +the skin of the repository from which it was cloned. + +Header And Footer Processing +---------------------------- + +The header.txt and footer.txt files of a scan are merely the HTML text +of the header and footer. Except, before being prepended and appended to +the content, the header and footer text are run through a +[TH1 interpreter](./th1.md) that might adjust the text as follows: + + * All text within <th1>...</th1> is elided from the + output and that text is instead run as a TH1 script. That TH1 + script has the opportunity to insert new text in place of itself, + or to inhibit or enable the output of subsequent text. + + * Text for the form "$NAME" or "$<NAME>" is replace with + the value of the TH1 variable NAME. + +For example, the following is the first few lines of a typical +header file: + + + + + $<project_name>: $<title> + + + + +After variables are substituted by TH1, the final header text +delivered to the web browser might look something like this: + + + + + Fossil: Timeline + + + + +The same TH1 interpreter is used for both the header and the footer +and for all scripts contained within them both. Hence, any global +TH1 variables that are set by the header are available to the footer. + +TH1 Variables +------------- + +Before expanding the TH1 within the header and footer, Fossil first +initializes a number of TH1 variables to values that depend on +respository settings and the specific page being generated. + + * **project_name** - The project_name variable is filled with the + name of the project as configured under the Admin/Configuration + menu. + + * **title** - The title variable holds the title of the page being + generated. + + The title variable is special in that it is deleted after + the header script runs and before the footer script. This is + necessary to avoid a conflict with a variable by the same name used + in my ticket-screen scripts. + + * **baseurl** - The root of the URL namespace for this server. + + * **secureurl** - The same as $baseurl except that if the scheme is + "http:" it is changed to "https:" + + * **home** - The $baseurl without the scheme and hostname. For example, + if the $baseurl is "http://projectX.com/cgi-bin/fossil" then the + $home will be just "/cgi-bin/fossil". + + * **index_page** - The landing page URI as + specified by the Admin/Configuration setup page. + + * **current_page** - The name of the page currently being processed, + without the leading "/" and without query parameters. + Examples: "timeline", "doc/trunk/README.txt", "wiki". + + * **csrf_token** - A token used to prevent cross-site request forgery. + + * **release_version** - The release version of Fossil. Ex: "1.31" + + * **manifest_version** - A prefix on the SHA1 check-in hash of the + specific version of fossil that is running. Ex: "\[47bb6432a1\]" + + * **manifest_date** - The date of the source-code check-in for the + version of fossil that is running. + + * **compiler_name** - The name and version of the compiler used to + build the fossil executable. + + * **login** - This variable only exists if the user has logged in. + The value is the username of the user. + + * **stylesheet_url** - A URL for the internal style-sheet maintained + by Fossil. + + * **log\_image\_url** - A URL for the logo image for this project, as + configured on the Admin/Logo page. + + * **background\_image\_url** - A URL for a background image for this + project, as configured on the Admin/Logo page. + +All of the above are variables in the sense that either the header or the +footer is free to change or erase them. But they should probably be treated +as constants. New predefined values are likely to be added in future +releases of Fossil. + +Suggested Skin Customization Procedure +-------------------------------------- + +Developers are free, of course, to develop new skins using any method they +want, but the following is a technique that has worked well in the past and +can serve as a starting point for future work: + + 1. Select a built-in skin that is closest to the desired look. Make + copies of the css, footer, and header into files name "css.txt", + "details.txt", + "footer.txt", and "header.txt" in some temporary directory. + + If the Fossil source code is available, then these three files can + be copied directly out of one of the subdirectories under skins. If + sources are not easily at hand, then a copy/paste out of the + CSS, footer, and header editing screens under the Admin menu will + work just as well. The important point is that the three files + be named exactly "css.txt", "footer.txt", and "header.txt" and that + they all be in the same directory. + + 2. Run the [fossil ui](../../../help?cmd=ui) command with an extra + option "--skin SKINDIR" where SKINDIR is the name of the directory + in which the three txt files were stored in step 1. This will bring + up the Fossil website using the tree files in SKINDIR. + + 3. Edit the four txt files in SKINDIR. After making each small change, + press Reload on the web browser to see the effect of that change. + Iterate until the desired look is achieved. + + 4. Copy/paste the resulting css.txt, details.txt, + header.txt, and footer.txt files + into the CSS, details, header, and footer configuration screens + under the Admin/Skins menu. Index: www/delta_format.wiki ================================================================== --- www/delta_format.wiki +++ www/delta_format.wiki @@ -66,11 +66,11 @@

      1.3 Segment-List

      The segment-list of a delta describes how to create the target from the original by a combination of inserting literal byte-sequences and -copying ranges of bytes from the original. This is there the +copying ranges of bytes from the original. This is where the compression takes place, by encoding the large common parts of original and target in small copy instructions.

      The target is constructed from beginning to end, with the data generated by each instruction appended after the data of all previous @@ -155,11 +155,11 @@   5x@Kt, Copy 380 @ 1336   6:pieces Literal 6 'pieces'   79@Qt, Copy 457 @ 1720   F: Example: eskilLiteral 15 ' Example: eskil'   ~E@Y0, Copy 4046 @ 2176 -Trailer2zMM3E Ckecksum -1101438770 +Trailer2zMM3E Checksum -1101438770

      The unified diff behind the above delta is

      - + @@ -594,11 +596,11 @@ - + Index: www/fiveminutes.wiki ================================================================== --- www/fiveminutes.wiki +++ www/fiveminutes.wiki @@ -5,18 +5,18 @@


      Up and running in 5 minutes as a single user

      This short document explains the main basic Fossil commands for a single -user, ie. with no additional users, with no need to synchronize with some remote +user, i.e. with no additional users, with no need to synchronize with some remote repository, and no need for branching/forking.

      Create a new repository

      fossil new c:\test.repo

      -

      This will create the new SQLite binary file that holds the repository, ie. +

      This will create the new SQLite binary file that holds the repository, i.e. files, tickets, wiki, etc. It can be located anywhere, although it's considered -best practise to keep it outside the work directory where you will work on files +best practice to keep it outside the work directory where you will work on files after they've been checked out of the repository.

      Open the repository

      cd c:\temp\test.fossil

      fossil open c:\test.repo

      @@ -27,28 +27,28 @@

      Add new files

      fossil add .

      To tell Fossil to add new files to the repository. The files aren't actually added until you run "commit". When using ".", it tells Fossil -to add all the files in the current directory recursively, ie. including all +to add all the files in the current directory recursively, i.e. including all the files in all the subdirectories.

      Note: To tell Fossil to ignore some extensions:

      fossil settings ignore-glob "*.o,*.obj,*.exe" --global

      -

      Remove files that haven't been commited yet

      +

      Remove files that haven't been committed yet

      fossil delete myfile.c

      This will simply remove the item from the list of files that were previously added through "fossil add".

      Check current status

      fossil changes

      -

      This shows the list of changes that have been done and will be commited the +

      This shows the list of changes that have been done and will be committed the next time you run "fossil commit". It's a useful command to run before running "fossil commit" just to check that things are OK before proceeding.

      Commit changes

      -

      To actually apply the pending changes to the repository, eg. new files marked +

      To actually apply the pending changes to the repository, e.g. new files marked for addition, checked-out files that have been edited and must be checked-in, etc.

      fossil commit -m "Added stuff"

      @@ -59,11 +59,11 @@

      If you wish to compare the last revision of a file and its checked out version in your work directory:

      fossil gdiff myfile.c

      If you wish to compare two different revisions of a file in the repository:

      fossil finfo myfile: Note the first hash, which is the UUID of the commit -when the file was commited

      +when the file was committed

      fossil gdiff --from UUID#1 --to UUID#2 myfile.c

      Cancel changes and go back to previous revision

      fossil revert myfile.c

      Fossil does not prompt when reverting a file. It simply reminds the user about the "undo" command, just in case the revert was a mistake.

      Index: www/foss-cklist.wiki ================================================================== --- www/foss-cklist.wiki +++ www/foss-cklist.wiki @@ -5,11 +5,11 @@ blog post http://spot.livejournal.com/308370.html (see also [1] and [2]). Tom's original post assigned point scores to the various elements and -by adding together the individual points, the reader is suppose to be able +by adding together the individual points, the reader is supposed to be able to judge the likelihood that the project will fail. The point scores, and the items on the list, clearly reflect Tom's biases and are not necessarily those of the larger open-source community. Nevertheless, the policy of the Fossil shall be to strive for a perfect score.

      Index: www/fossil-v-git.wiki ================================================================== --- www/fossil-v-git.wiki +++ www/fossil-v-git.wiki @@ -3,31 +3,34 @@

      1.0 Don't Stress!

      If you start out using one DVCS and later decide you like the other better, it is [./inout.wiki | easy to change]. -But it also helps to be informed about the differences between +But it also helps to be informed about the differences between [http://git-scm.com | Git] and Fossil. See the table below for a high-level summary and the text that follows for more details. -Keep in mind that you are reading this on a Fossil website, +Keep in mind that you are reading this on a Fossil website, so the information here might be biased in favor of Fossil. Ask around with people who have used both Fossil and Git for other opinions.

      2.0 Executive Summary:

      @@ -213,11 +213,11 @@
       
      • Pure text files generate a pure text delta.
      • Binary files generate a delta that may contain some binary data.
      • -
      • The delta encoding does not attempt to compress the content +
      • The delta encoding does not attempt to compress the content. It was considered to be much more sensible to do compression using a separate general-purpose compression library, like zlib.
      Index: www/embeddeddoc.wiki ================================================================== --- www/embeddeddoc.wiki +++ www/embeddeddoc.wiki @@ -1,7 +1,7 @@ -Managing Project Documentation -

      Managing Project Documentation

      +Project Documentation +

      Project Documentation

      Fossil provides a built-in wiki that can be used to store the documentation for a project. This is sufficient for many projects. If your project is well-served by wiki documentation, then you @@ -63,26 +63,40 @@ Finally, the <filename> element of the URL is the pathname of the documentation file relative to the root of the source tree. The mimetype (and thus the rendering) of documentation files is -determined by the file suffix. Fossil currently understands 197 -different file suffixes, including all the popular ones such as -".css", ".gif", ".htm", ".html", ".jpg", ".jpeg", ".png", and ".txt". +determined by the file suffix. Fossil currently understands +[/mimetype_list|many different file suffixes], +including all the popular ones such as ".css", ".gif", ".htm", +".html", ".jpg", ".jpeg", ".png", and ".txt". Documentation files whose names end in ".wiki" use the [/wiki_rules | same markup as wiki pages] - a safe subset of HTML together with some wiki rules for paragraph breaks, lists, and hyperlinks. Documentation files ending in ".md" or ".markdown" use the -Markdown markup langauge. +[/md_rules | Markdown markup langauge]. Documentation files ending in ".txt" are plain text. Wiki, markdown, and plain text documentation files are rendered with the standard fossil header and footer added. -All other mimetypes (including ".html" files) -are delivered directly to the requesting +Most other mimetypes are delivered directly to the requesting web browser without interpretation, additions, or changes. + +Files with the mimetype "text/html" (the .html or .htm suffix) are +usually rendered directly to the browser without interpretation. +However, if the file begins with a <div> element like this: + + <div class='fossil-doc' data-title='Title Text'> + +Then the standard Fossil header and footer are added to the document +prior to being displayed. The "class='fossil-doc'" attribute is +required for this to occur. The "data-title='...'" attribute is +optional, but if it is present the text will become the title displayed +in the Fossil header. An example of this can be seen in the text +of the [/artifact/84b4b3d041d93a?txt=1 | Index Of Fossil Documentation] +document.

      Examples

      This file that you are currently reading is an example of embedded documentation. The name of this file in the fossil Index: www/event.wiki ================================================================== --- www/event.wiki +++ www/event.wiki @@ -1,75 +1,75 @@ -Events +Technical Notes -

      What Is An "Event"?

      +

      What Is A "Technote"?

      -In Fossil, and "event" is a special kind of [./wikitheory.wiki | wiki page] +In Fossil, a "technical note" or "technote" (formerly called an "event") +is a special kind of [./wikitheory.wiki | wiki page] that is associated with a point in time rather than having a page name. -Each event causes a single entry to appear on the [/timeline | Timeline Page]. -Clicking on the hyperlink of the timeline entry cause a jump to the wiki -content for the event. The wiki content, the timeline entry text, the -time of the event, and the timeline background color can all be edited. - -As with check-ins, wiki, and tickets, all events automatically synchronize -to other repositories. Hence, events can be viewed, created, and edited -off-line. And the complete edit history for events is maintained +Each technote causes a single entry to appear on the +[/timeline?y=e | Timeline Page]. +Clicking on the timeline link will display the text of the technote. +The wiki content, the timeline entry text, the +time of the technote, and the timeline background color can all be edited. + +As with check-ins, wiki, and tickets, all technotes automatically synchronize +to other repositories. Hence, technotes can be viewed, created, and edited +off-line. And the complete edit history for technotes is maintained for auditing purposes. -Possible uses for events include: +Possible uses for technotes include: * Milestones. Project milestones, such as releases or beta-test - cycles, can be recorded as events. The timeline entry for the event + cycles, can be recorded as technotes. The timeline entry for the technote can be something simple like "Version 1.2.3" perhaps with a bright color background to draw attention to the entry and the wiki content can contain release notes, for example. * Blog Entries. Blog entries from developers describing the current state of a project, or rational for various design decisions, or - roadmaps for future development, can be entered as events. + roadmaps for future development, can be entered as technotes. * Process Checkpoints. For projects that have a formal process, - events can be used to record the completion or the initiation of - various process steps. For example, an event can be used to record + technotes can be used to record the completion or the initiation of + various process steps. For example, a technote can be used to record the successful completion of a long-running test, perhaps with performance results and details of where the test was run and who ran it recorded in the wiki content. * News Articles. Significant occurrences in the lifecycle of - a project can be recorded as news articles using events. Perhaps the + a project can be recorded as news articles using technotes. Perhaps the domain name of the canonical website for a project changes, or new server hardware is obtained. Such happenings are appropriate for reporting as news. * Announcements. Changes to the composition of the development team or acquisition of new project sponsors can be communicated as - announcements which can be implemented as events. + announcements which can be implemented as technotes. -No project is required to use events. But events can help many projects +No project is required to use technotes. But technotes can help many projects stay better organized and provide a better historical record of the development progress. -

      Viewing Events

      +

      Viewing Technotes

      -Because events are considered a special kind of wiki, -users must have permission to read wiki in order read events. +Because technotes are considered a special kind of wiki, +users must have permission to read wiki in order read technotes. Enable the "j" permission under the /Setup/Users menu in order to give specific users or user classes the ability to view wiki -and events. - -Events show up on the timeline. Click on the hyperlink beside the -event title to see the details of the event. - -

      Creating And Editing Events

      - -There is a hyperlink under the /Wiki menu that can be used to create -new events. And there is a submenu hyperlink on event displays for -editing existing events. +and technotes. + +Technotes show up on the timeline. Click on the hyperlink beside the +technote title to see the complete text. + +

      Creating And Editing Technotes

      + +There is a hyperlink under the /wikihelp menu that can be used to create +new technotes. And there is a submenu hyperlink on technote displays for +editing existing technotes. Users must have check-in privileges (permission "i") in order to -create or edit events. In addition, users must have create-wiki -privilege (permission "f") to create new events and edit-wiki -privilege (permission "k") in order to edit existing events. - -If the first non-whitespace text of the event wiki content is -<title>...</title> then that markup is omitted from -the body of the wiki pages and is instead displayed as the page -title. +create or edit technotes. In addition, users must have create-wiki +privilege (permission "f") to create new technotes and edit-wiki +privilege (permission "k") in order to edit existing technotes. + +Technote content may be formatted as [/wiki_rules | Fossil wiki], +[/md_rules | Markdown], or a plain text. Index: www/faq.tcl ================================================================== --- www/faq.tcl +++ www/faq.tcl @@ -55,12 +55,12 @@ If you already have a fork in your check-in tree and you want to convert that fork to a branch, you can do this from the web interface. First locate the check-in that you want to be the initial check-in of your branch on the timeline and click on its link so that you are on the ci page. Then find the "edit" - link (near the "Commands:" label) and click on that. On the - "Edit Check-in" page, check the box beside "Branching:" and fill in + link (near the "Commands:" label) and click on that. On the + "Edit Check-in" page, check the box beside "Branching:" and fill in the name of your new branch to the right and press the "Apply Changes" button. } faq { @@ -83,31 +83,31 @@ The CHECK-IN in the previous line can be any [./checkin_names.wiki | valid check-in name format]. You can also add (and remove) tags from a check-in using the - [./webui.wiki | web interface]. First locate the check-in that you - what to tag on the tmline, then click on the link to go the detailed + [./webui.wiki | web interface]. First locate the check-in that you + what to tag on the timeline, then click on the link to go the detailed information page for that check-in. Then find the "edit" link (near the "Commands:" label) and click on that. There are controls on the edit page that allow new tags to be added and existing tags to be removed. -} +} faq { How do I create a private branch that won't get pushed back to the main repository. } { - Use the --private command-line option on the + Use the --private command-line option on the commit command. The result will be a check-in which exists on - your local repository only and is never pushed to other repositories. - All descendents of a private check-in are also private. - + your local repository only and is never pushed to other repositories. + All descendants of a private check-in are also private. + Unless you specify something different using the --branch and/or --bgcolor options, the new private check-in will be put on a branch named "private" with an orange background color. - + You can merge from the trunk into your private branch in order to keep your private branch in sync with the latest changes on the trunk. Once you have everything in your private branch the way you want it, you can then merge your private branch back into the trunk and push. Only the final merge operation will appear in other repositories. It will seem Index: www/faq.wiki ================================================================== --- www/faq.wiki +++ www/faq.wiki @@ -87,11 +87,11 @@ The CHECK-IN in the previous line can be any [./checkin_names.wiki | valid check-in name format]. You can also add (and remove) tags from a check-in using the [./webui.wiki | web interface]. First locate the check-in that you -what to tag on the tmline, then click on the link to go the detailed +what to tag on the timeline, then click on the link to go the detailed information page for that check-in. Then find the "edit" link (near the "Commands:" label) and click on that. There are controls on the edit page that allow new tags to be added and existing tags to be removed. @@ -100,11 +100,11 @@ main repository.

      Use the --private command-line option on the commit command. The result will be a check-in which exists on your local repository only and is never pushed to other repositories. -All descendents of a private check-in are also private. +All descendants of a private check-in are also private. Unless you specify something different using the --branch and/or --bgcolor options, the new private check-in will be put on a branch named "private" with an orange background color. Index: www/fileformat.wiki ================================================================== --- www/fileformat.wiki +++ www/fileformat.wiki @@ -28,12 +28,12 @@ The local state is not composed of artifacts and is not intended to be enduring. This document is concerned with global state only. Local state is only mentioned here in order to distinguish it from global state. Each artifact in the repository is named by its SHA1 hash. -No prefixes or meta information is added to a artifact before -its hash is computed. The name of a artifact in the repository +No prefixes or meta information is added to an artifact before +its hash is computed. The name of an artifact in the repository is exactly the same SHA1 hash that is computed by sha1sum on the file as it exists in your source tree.

      Some artifacts have a particular format which gives them special meaning to fossil. Fossil recognizes: @@ -43,17 +43,17 @@
    • [#cluster | Clusters]
    • [#ctrl | Control Artifacts]
    • [#wikichng | Wiki Pages]
    • [#tktchng | Ticket Changes]
    • [#attachment | Attachments]
    • -
    • [#event | Events]
    • +
    • [#event | TechNotes]
    • These seven artifact types are described in the following sections. In the current implementation (as of 2009-01-25) the artifacts that -make up a fossil repository are stored in in as delta- and zlib-compressed +make up a fossil repository are stored as delta- and zlib-compressed blobs in an SQLite database. This is an implementation detail and might change in a future release. For the purpose of this article "file format" means the format of the artifacts, not how the artifacts are stored on disk. It is the artifact format that is intended to be enduring. The specifics of how artifacts are stored on @@ -113,11 +113,11 @@ A manifest may optionally have a single B-card. The B-card specifies another manifest that serves as the "baseline" for this manifest. A manifest that has a B-card is called a delta-manifest and a manifest that omits the B-card is a baseline-manifest. The other manifest identified by the argument of the B-card must be a baseline-manifest. -A baseline-manifest records the complete contents of a checkin. +A baseline-manifest records the complete contents of a check-in. A delta-manifest records only changes from its baseline. A manifest must have exactly one C-card. The sole argument to the C-card is a check-in comment that describes the check-in that the manifest defines. The check-in comment is text. The following @@ -138,31 +138,31 @@ YYYY-MM-DDTHH:MM:SS
      YYYY-MM-DDTHH:MM:SS.SSS
      A manifest has zero or more F-cards. Each F-card identifies a file -that is part of the check-in. There are one, two, three, or four arguments. -The first argument -is the pathname of the file in the check-in relative to the root -of the project file hierarchy. No ".." or "." directories are allowed -within the filename. Space characters are escaped as in C-card -comment text. Backslash characters and newlines are not allowed -within filenames. The directory separator character is a forward -slash (ASCII 0x2F). The second argument to the F-card is the -full 40-character lower-case hexadecimal SHA1 hash of the content -artifact. The second argument is required for baseline manifests -but is optional for delta manifests. When the second argument to the -F-card is omitted, it means that the file has been deleted relative -to the baseline. The optional 3rd argument defines any special access -permissions associated with the file. The only special code currently -defined is "x" which means that the file is executable. All files are -always readable and writable. This can be expressed by "w" permission -if desired but is optional. The file format might be extended with -new permission letters in the future. -The optional 4th argument is the name of the same file as it existed in -the parent check-in. If the name of the file is unchanged from its -parent, then the 4th argument is omitted. +that is part of the check-in. There are one, two, three, or four +arguments. The first argument is the pathname of the file in the +check-in relative to the root of the project file hierarchy. No ".." +or "." directories are allowed within the filename. Space characters +are escaped as in C-card comment text. Backslash characters and +newlines are not allowed within filenames. The directory separator +character is a forward slash (ASCII 0x2F). The second argument to the +F-card is the full 40-character lower-case hexadecimal SHA1 hash of +the content artifact. The second argument is required for baseline +manifests but is optional for delta manifests. When the second +argument to the F-card is omitted, it means that the file has been +deleted relative to the baseline (files removed in baseline manifests +versions are not added as F-cards). The optional 3rd argument +defines any special access permissions associated with the file. This +can be defined as "x" to mean that the file is executable or "l" +(small letter ell) to mean a symlink. All files are always readable +and writable. This can be expressed by "w" permission if desired but +is optional. The file format might be extended with new permission +letters in the future. The optional 4th argument is the name of the +same file as it existed in the parent check-in. If the name of the +file is unchanged from its parent, then the 4th argument is omitted. A manifest has zero or one N-cards. The N-card specifies the mimetype for the text in the comment of the C-card. If the N-card is omitted, a default mimetype is used. @@ -184,11 +184,11 @@ ancestor, the Q-card is used to identify a single check-in or a small range of check-ins which were cherry-picked for inclusion in or exclusion from the current manifest. The first argument of the Q-card is the artifact ID of another manifest (the "target") which has had its changes included or excluded in the current manifest. -The target is preceeded by "+" or "-" to show inclusion or +The target is preceded by "+" or "-" to show inclusion or exclusion, respectively. The optional second argument to the Q-card is another manifest artifact ID which is the "baseline" for the cherry-pick. If omitted, the baseline is the primary parent of the target. The changes included or excluded consist of all changes moving from @@ -238,11 +238,11 @@ [/artifact/28987096ac | here].

      2.0 Clusters

      -A cluster is a artifact that declares the existence of other artifacts. +A cluster is an artifact that declares the existence of other artifacts. Clusters are used during repository synchronization to help reduce network traffic. As such, clusters are an optimization and may be removed from a repository without loss or damage to the underlying project code. @@ -313,15 +313,15 @@ to which the tag is to be applied. The first value is the tag name. The first character of the tag is either "+", "-", or "*". The "+" means the tag should be added to the artifact. The "-" means the tag should be removed. The "*" character means the tag should be added to the artifact -and all direct descendants (but not descendents through a merge) down +and all direct descendants (but not descendants through a merge) down to but not including the first descendant that contains a -more recent "-" or "+" tag with the same name. +more recent "-", "*", or "+" tag with the same name. The optional third argument is the value of the tag. A tag -without a value is a boolean. +without a value is a Boolean. When two or more tags with the same name are applied to the same artifact, the tag with the latest (most recent) date is used. @@ -359,14 +359,14 @@ The D card is the date and time when the wiki page was edited. The P card specifies the parent wiki pages, if any. The L card gives the name of the wiki page. The optional N card specifies the mimetype of the wiki text. If the N card is omitted, the -mimetype is assumed to be text/x-fossil. +mimetype is assumed to be text/x-fossil-wiki. The U card specifies the login of the user who made this edit to the wiki page. The Z card is -the usual checksum over the either artifact and is required. +the usual checksum over the entire artifact and is required. The W card is used to specify the text of the wiki page. The argument to the W card is an integer which is the number of bytes of text in the wiki page. That text follows the newline character that terminates the W card. The wiki text is always followed by one @@ -423,11 +423,12 @@

      6.0 Attachments

      An attachment artifact associates some other artifact that is the -attachment (the source artifact) with a ticket or wiki page or event to which +attachment (the source artifact) with a ticket or wiki page or +technical note to which the attachment is connected (the target artifact). The following cards are allowed on an attachment artifact:
      A filename target ?source?
      @@ -437,12 +438,12 @@ U user-name
      Z checksum
      The A card specifies a filename for the attachment in its first argument. -The second argument to the A card is the name -of the wiki page or ticket or event to which the attachment is connected. The +The second argument to the A card is the name of the wiki page or +ticket or technical note to which the attachment is connected. The third argument is either missing or else it is the 40-character artifact ID of the attachment itself. A missing third argument means that the attachment should be deleted. The C card is an optional comment describing what the attachment is about. @@ -453,80 +454,81 @@ There may be zero or one N cards. The N card specifies the mimetype of the comment text provided in the C card. If the N card is omitted, the C card mimetype is taken to be text/plain. -A single U card gives the name of the user to added the attachment. +A single U card gives the name of the user who added the attachment. If an attachment is added anonymously, then the U card may be omitted. The Z card is the usual checksum over the rest of the attachment artifact. The Z card is required. -

      7.0 Events

      +

      7.0 Technical Notes

      -An event artifact associates a timeline comment and a page of text -(similar to a wiki page) with a point in time. Events can be used +A technical note or "technote" artifact (formerly known as an "event" artifact) +associates a timeline comment and a page of text +(similar to a wiki page) with a point in time. Technotes can be used to record project milestones, release notes, blog entries, process checkpoints, or news articles. -The following cards are allowed on an event artifact: +The following cards are allowed on an technote artifact:
      C comment
      D time-and-date-stamp
      -E event-time event-id
      +E technote-time technote-id
      N mimetype
      P parent-artifact-id+
      T +tag-name * ?value?
      U user-name
      W size \n text \n
      Z checksum
      The C card contains text that is displayed on the timeline for the -event. The C card is optional, but there can only be one. +technote. The C card is optional, but there can only be one. A single D card is required to give the date and time when the -event artifact was created. This is different from the time at which -the event occurs. +technote artifact was created. This is different from the time at which +the technote appears on the timeline. -A single E card gives the time of the event (the point on the timeline -where the event is displayed) and a unique identifier for the event. -When there are multiple artifacts with the same event-id, the one with -the most recent D card is the only one used. The event-id must be a +A single E card gives the time of the technote (the point on the timeline +where the technote is displayed) and a unique identifier for the technote. +When there are multiple artifacts with the same technote-id, the one with +the most recent D card is the only one used. The technote-id must be a 40-character lower-case hexadecimal string. -The optional N card specifies the mimetype of the text of the event +The optional N card specifies the mimetype of the text of the technote that is contained in the W card. If the N card is omitted, then the W card text mimetype is assumed to be text/x-fossil, which is the Fossil wiki format. -The optional P card specifies a prior event with the same event-id from -which the current event is an edit. The P card is a hint to the system -that it might be space efficient to store one event as a delta of the -other. +The optional P card specifies a prior technote with the same technote-id +from which the current technote is an edit. The P card is a hint to the +system that it might be space efficient to store one technote as a delta of +the other. -An event might contain one or more T-cards used to set +A technote might contain one or more T-cards used to set [./branching.wiki#tags | tags or properties] -on the event. The format of the T-card is the same as +on the technote. The format of the T-card is the same as described in [#ctrl | Control Artifacts] section above, except that the second argument is the single character "*" instead of an artifact ID and the name is always prefaced by "+". The * in place of the artifact ID indicates that the tag or property applies to the current artifact. It is not possible to encode the current artifact ID as part of an artifact, since the act of inserting the artifact ID would change the artifact ID, hence a * is used to represent "self". The "+" on the name means that tags can only be add and they can only be non-propagating -tags. A an event, T cards are normally used to set the background +tags. In a technote, T cards are normally used to set the background display color for timelines. -The optional U card gives name of the user who entered the event. +The optional U card gives name of the user who entered the technote. A single W card provides wiki text for the document associated with the -event. The format of the W card is exactly the same as for a +technote. The format of the W card is exactly the same as for a [#wikichng | wiki artifact]. The Z card is the required checksum over the rest of the artifact. @@ -550,11 +552,11 @@
      Cluster Control Wiki Ticket AttachmentEventTechnote
      A filename target ?source?    1 1 1
      E event-time event-idE technote-time technote-id          
      - + - + + + +
      GITFOSSIL
      File versioning onlyVersioning, Tickets, Wiki, and Blog/News
      Versioning, Tickets, Wiki, and Technotes
      ShardingReplicating
      Developer branchesFeature branches
      ComplexIntuitive
      Separate web toolsIntegrated Web interface
      Lots of little toolsSingle executable
      Pile-of-files repositorySingle file repository
      Pile-of-files repositorySingle-file relational database
      One check-out per repositoryMany check-outs per repository
      Uses "rebase"Immutable
      GPLBSD

      3.0 Discussion

      @@ -36,11 +39,11 @@ Git provides file versioning services only, whereas Fossil adds an integrated [./wikitheory.wiki | wiki], [./bugtheory.wiki | ticketing & bug tracking], [./embeddeddoc.wiki | embedded documentation], and -[./event.wiki | News/Blog features]. +[./event.wiki | Technical notes]. These additional capabilities are available for Git as 3rd-party user-installed add-ons, but with Fossil they are integrated into the design. One way to describe Fossil is that it is "[https://github.com/ | github]-in-a-box". @@ -47,21 +50,21 @@

      3.2 Sharding versus Replicating

      Git makes it easy for each repository in a project to hold a subset of the branches for that project. In fact, it is entirely possible and not uncommon for no repository in the project to hold all the different code -versions for a project. Instead the information is distributed. +versions for a project. Instead the information is distributed. Individual developers have one or more private branches. A hierarchy of integrators merge changes from individual developers into collaborative branches, until all the changes are merged together at the top-level master branch. And all of this can be accomplished without having to have all the code in any one repository. Developers or groups of developers can share -only those branches that they want to share and keep other branchs of the -project private. This is analogous to sharding an a distributed database. +only those branches that they want to share and keep other branches of the +project private. This is analogous to sharding a distributed database. Fossil allows private branches, but its default mode is to share everything. -And so in a Fossil project, all respositories tend to contain all of the +And so in a Fossil project, all repositories tend to contain all of the content at all times. This is analogous to replication in a distributed database. The Git model works best for large projects, like the Linux kernel for which Git was designed. @@ -73,35 +76,35 @@ works in his or her own branch and then merges changes up the hierarchy until they reach the master branch. Fossil is designed for smaller and non-hierarchical teams where all developers are operating directly on the master branch, or at most -a small number of well defined branches. +a small number of well-defined branches. The [./concepts.wiki#workflow | autosync] mode of Fossil makes it easy for multiple developers to work on a single branch and maintain linear development on that branch and avoid needless forking and merging.

      3.3 Branches

      -Git (and especially GitHub) encourages a workflow where each developer +Git (and especially GitHub) encourages a workflow where each developer has his or her own branch or branches. Developers then send "pull requests" to have their changes be merged into "official" branches by integrators. For example, the Linux kernel team has a hierarchy of integrators with Linus Torvalds at the root. Individual developers each have their own private branches of the source tree into which they make their own changes. They then encourage first-tier integrators to pull those changes. The first-tier integrators merge together changes from multiple contributors then try to get second-tier integrators to pull their branches. The -changes merge up the hierarchy until (hopefully) they are pulled into +changes merge up the hierarchy until (hopefully) they are pulled into "Linus's branch", at which time they become part of the "official" Linux. In Git, each branch is "owned" by the person who creates it and works on it. The owner might pull changes from others, but the owner is always in control of the branch. Branches are developer-centric. -Fossil, on the other hand, encourages a workflow where branches are +Fossil, on the other hand, encourages a workflow where branches are associated with features or releases, not individual developers. All developers share all branches in common, and two or more developers can and often do intersperse commits onto the same branch. Branches do not belong to individuals. All branches are read/write accessible to all developers at all times. There is no need @@ -112,12 +115,12 @@ So to a first approximation, branches in Git are developer-centric whereas branches in Fossil are feature-centric. The Git approach scales much better for large projects like the Linux kernel with thousands of contributors who in many cases don't even know -each others names. The integrators serve a gatekeeper role to help keep -undesirable code out of the official Linux source tree. On the other hand, +each other's names. The integrators serve a gatekeeper role to help keep +undesirable code out of the official Linux source tree. On the other hand, not many projects are as big or as loosely organized as the Linux kernel. Most projects have a small team of developers who all know each other well and trust each other, and who enjoy working together collaboratively without the overhead and hierarchy of integrators. @@ -141,17 +144,20 @@ to think about version control to some extent. But one wants to minimize the thinking about version control. Git requires the developer to maintain a more complex mental model than most other DVCSes. Git takes longer to learn. And you have to spend -more time thinking about what you are doing with Git. +more time thinking about what you are doing with Git. Fossil strives for simplicity. Fossil wants to be easy to learn and to -require little thinking about how to operating it. +require little thinking about how to operating it. [./quotes.wiki | Reports from the field] indicate that Fossil is mostly successful at this effort. +Fossil will never get you into anything like the +"disconnected head state" which has frustrated so many Git users. +

      3.5 Web Interface

      Git has a web interface, but it requires a fair amount of setup and an external web server. Fossil comes with a fully functional [./webui.wiki | built-in web-server] @@ -185,43 +191,82 @@ between repositories and working checkouts. A power-loss or system crash in the middle of Git operation can damage or corrupt the Git repository. A Fossil repository consists of a single disk file. A single Fossil repository can serve multiple simultaneous working checkouts. -A Fossil repository is an SQLite database, so it highly resistant +A Fossil repository is an SQLite database, so it is highly resistant to damage from a power-loss or system crash - incomplete transactions are simply rolled back after the system reboots. -

      3.8 Audit Trail

      +

      3.8 Check-outs Per Repository

      + +In Git, a check-out and a repository are joined in a fundamental way +so that only a single version of the project history, or a single branch, +can be open at once. If you have a project with multiple branches and +you want to have two or more branches open at the same time (perhaps to +do performance comparisons, or maybe to run simultaneous builds using +different compile-time options) then in Git you actually have to create +a new clone of the repository for each open checkout. + +In Fossil, the repository and the check-out are distinct entities and +so a single repository can support multiple simultaneous checkouts. +This feature is extensively used by the Fossil developers +themselves. Perhaps we are biased, but we not understand how anyone +can work efficiently with just one check-out per repository. + +

      3.9 Audit Trail

      Git features the "rebase" command which can be used to change the sequence of check-ins in the repository. Rebase can be used to "clean up" a complex sequence of check-ins to make their intent easier for others to understand. This is important if you view the history of a project -as part of the documentation for the project. +as part of the documentation for the project. Fossil takes an opposing view. Fossil views history as sacrosanct and -stubornly refuses to change it. +stubbornly refuses to change it. Fossil allows mistakes to be corrected (for example, check-in comments can be revised, and check-ins can be moved onto new branches even after -the check-in has occurred) but the correction is an addition to the respository +the check-in has occurred) but the correction is an addition to the repository and the original actions are preserved and displayed alongside the corrections, thus preserving an historically accurate audit trail. -This is analogous to an accountant marking through an incorrect -entry in a ledger and writing in a correction beside it, rather than -erasing and incorrect entry. +This is analogous to an accounting practice of marking through an incorrect +entry in a ledger and writing a correction beside it. + To put it another way, Git remembers what you should have done whereas Fossil remembers what you actually did. The lack of a "rebase" command and the inability to rewrite history is considered a feature of Fossil, not an omission or bug. -

      3.9 License

      +

      3.10 License

      -Both Git and Fossil are open-source. Git is under +Both Git and Fossil are open-source. Git is under [http://www.gnu.org/licenses/gpl.html | GPL] whereas Fossil is -under the +under the [http://en.wikipedia.org/wiki/BSD_licenses | two-clause BSD license]. -The difference should not be of a concern to most users. However, -some corporate lawyers have objections to using GPL products and -are more comfortable with a BSD-style license. +The different licenses parallel, to some extent, the different philosophies +of Git and Fossil. +There are exceptions on both sides, but to a first approximation, Git +works better for GPL projects and Fossil works better for BSD projects. + +The GPL is designed to provide a very contributor-friendly environment. +No legal paperwork is needed to contribute to a GPL project because +the GPL is cleverly designed so that the act of contributing +to the project (or even reading the code for the project) constitutes +an acceptance of the licensing terms. GPL encourages a bazaar-style +development model, with lots of anonymous programmers contributing +drive-by patches. The theory is that with many eyeballs, all bugs +are shallow. Surprisingly, this has actually been demonstrated to +work in many well-known projects. + +The BSD-style licenses are more user-friendly. BSD-style licenses +place fewer restrictions on the users of the software at the expense +of making it more difficult to contribute changes or enhancements. +To protect against IP claims, +every contributor to a BSD-style project must sign legal documents in +which they agree to release their contributions under the same license. +(Some BSD-licensed projects omit this formality, but do so at their peril.) +A BSD-style license encourages a more cathedral-style approach to development. +There is a small team of developers. Drive-by patches and anonymous +contributors are discouraged and/or prohibited. Contributors are expected +to be experts and be available to support their changes for the long-term. Index: www/hacker-howto.wiki ================================================================== --- www/hacker-howto.wiki +++ www/hacker-howto.wiki @@ -2,10 +2,11 @@ The following links are of interest to programmers who want to modify or enhance Fossil. Ordinary users can safely ignore this information. * [./build.wiki | How To Compile And Install Fossil] + * [./customskin.md | Theming Fossil] * [./makefile.wiki | The Fossil Build Process] * [./tech_overview.wiki | A Technical Overview of Fossil] * [./adding_code.wiki | Adding Features To Fossil] * [./contribute.wiki|Contributing Code Or Enhancements To The Fossil Project] * [./style.wiki | Coding Style Guidelines] Index: www/hints.wiki ================================================================== --- www/hints.wiki +++ www/hints.wiki @@ -9,11 +9,12 @@ window is run as a separate Tcl/Tk process, so you will need to have Tcl/Tk installed on your machine for this to work. Visit [http://www.activestate.com/activetcl] to for a quick download of Tcl/Tk if you do not already have it on your system.) - 3. The "[/help?cmd=clean | fossil clean -f]" command makes a great + 3. The "[/help/clean | fossil clean -f]" or + "[/help/clean | fossil clean --verily]" command is a great alternative to "make clean". 4. Use "[/help?cmd=all | fossil all changes]" to look for any uncommitted edits in any of your Fossil projects. Use "[/help?cmd=all | fossil all pull]" on your laptop @@ -20,18 +21,15 @@ prior to going off network (for example, on a long plane ride) to make sure you have all the latest content locally. Then run "[/help/all|fossil all push]" when you get back online to upload your changes. - 5. Sub-menu options on Timelines lets you select either 20 or 200 - records. But you can manual edit the "n=" query parameter in the - URL to get any number of records you desire. To see a complete - timeline graph, set n to some ridiculously large value like 10000000. + 5. To see an entire timeline, type "all" into the "Max:" entry box. 6. You can manually add a "c=CHECKIN" query parameter to the timeline URL to get a snapshot of what was going on about the time of some - checkin. The "CHECKIN" can be + check-in. The "CHECKIN" can be [./checkin_names.wiki | any valid check-in or version name], including tags, branch names, and dates. For example, to see what was going on in the Fossil repository on 2008-01-01, visit [http://www.fossil-scm.org/fossil/timeline?c=2008-01-01]. @@ -47,15 +45,15 @@ 9. On web pages showing the content of a file (for example [http://www.fossil-scm.org/fossil/artifact/c7dd1de9f]) you can manually add a query parameter of the form "ln=FROM,TO" to the URL that will cause the range of lines indicated to be highlighted. This is useful in pointing out a few lines of code using a hyperlink - in a email or text message. Example: + in an email or text message. Example: [http://www.fossil-scm.org/fossil/artifact/c7dd1de9f?ln=28,30]. Adding the "ln" query parameter without any argument simply turns on line numbers. This feature only works right with files with a mimetype of text/plain, of course. 10. When editing documentation to be checked in as managed files, you can preview what the documentation will look like by using the special "ckout" branch name in the "doc" URL while running "fossil ui". See the [./embeddeddoc.wiki | embedded documentation] for details. Index: www/index.wiki ================================================================== --- www/index.wiki +++ www/index.wiki @@ -1,119 +1,97 @@ -Fossil - - -

      - -Simple, high-reliability, distributed software configuration management - -

      - - -

      Why Use Fossil?

      - - -
      - - - -
      +Home + +

      What Is Fossil?

      + +
      • [http://www.fossil-scm.org/download.html | Download]
      • [./quickstart.wiki | Quick Start]
      • [./build.wiki | Install]
      • [../COPYRIGHT-BSD2.txt | License] -
      • [/timeline | Recent changes]
      • [./faq.wiki | FAQ] +
      • [./changes.wiki | Change Log]
      • [./hacker-howto.wiki | Hacker How-To] -
      • [./changes.wiki | Change Log]
      • [./hints.wiki | Tip & Hints] -
      • [./permutedindex.wiki#pindex | Documentation Index] +
      • [./permutedindex.html | Documentation Index]
      • [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book]
      • Mailing list
          -
        • [http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users | sign-up] +
        • [http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/fossil-users | sign-up]
        • [http://www.mail-archive.com/fossil-users@lists.fossil-scm.org | archives] -
            +
        -
      -
      -
      - -There are plenty of open-source version control systems available on the -internet these days. What makes Fossil worthy of attention? - - 1. Bug Tracking And Wiki - +
    • + +

      Fossil is a simple, high-reliability, distributed software configuration +management system with these advanced features: + + 1. Integrated Bug Tracking, Wiki, and Technotes - In addition to doing [./concepts.wiki | distributed version control] like Git and Mercurial, - Fossil also supports [./bugtheory.wiki | distributed bug tracking], - [./wikitheory.wiki | distributed wiki], and a - [./event.wiki | distributed blog] mechanism all in a single - integrated package. - - 2. Web Interface - - Fossil has a built-in and easy-to-use [./webui.wiki | web interface] - that simplifies project tracking and promotes situational awareness. - Simply type "fossil ui" from within any check-out and Fossil - automatically opens your web browser in a page that gives detailed - [/timeline?n=100&y=ci | graphical history] and status information - on that project. - - This entire website (except the - [http://www.fossil-scm.org/download.html | download] page) + Fossil also supports [./bugtheory.wiki | bug tracking], + [./wikitheory.wiki | wiki], and [./event.wiki | technotes]. + + 2. Built-in Web Interface - + Fossil has a built-in and intuitive [./webui.wiki | web interface] + with a rich assortment of information pages + ([./webpage-ex.md|examples]) designed to promote situational awareness. + + This entire website¹ is just a running instance of Fossil. The pages you see here are all [./wikitheory.wiki | wiki] or [./embeddeddoc.wiki | embedded documentation]. When you clone Fossil from one of its [./selfhost.wiki | self-hosting repositories], you get more than just source code - you get this entire website. + (¹except the + [http://www.fossil-scm.org/download.html | download] page) + + 3. Self-Contained - + Fossil is a single self-contained stand-alone executable. + To install, simply download a + precompiled binary + for Linux, Mac, OpenBSD, or Windows and put it on your $PATH. + [./build.wiki | Easy-to-compile source code] is also available. + + 4. Simple Networking - + No custom protocols or TCP ports. + Fossil uses ordinary HTTP (or HTTPS or SSH) + for network communications, so it works fine from behind + restrictive firewalls, including [./quickstart.wiki#proxy|proxies]. + The protocol is + [./stats.wiki | bandwidth efficient] to the point that Fossil can be + used comfortably over dial-up. + + 5. CGI/SCGI Enabled - No server is required, but if you want to + set one up, Fossil supports four easy + [./server.wiki | server configurations]. - 3. Autosync - + 6. Autosync - Fossil supports [./concepts.wiki#workflow | "autosync" mode] which helps to keep projects moving forward by reducing the amount of needless [./branching.wiki | forking and merging] often associated with distributed projects. - 4. Self-Contained - - Fossil is a single stand-alone executable that contains everything - needed to do configuration management. - Installation is trivial: simply download a - precompiled binary - for Linux, Mac, or Windows and put it on your $PATH. - [./build.wiki | Easy-to-compile source code] is available for - users on other platforms. Fossil sources are also mostly self-contained, - requiring only the standard C library to build. - - 5. Simple Networking - - Fossil uses plain old HTTP (with - [./quickstart.wiki#proxy | proxy support]) - for all network communications, meaning that it works fine from behind - restrictive firewalls. The protocol is - [./stats.wiki | bandwidth efficient] to the point that Fossil can be - used comfortably over a dial-up internet connection. - - 6. CGI/SCGI Enabled - - No server is required to use fossil. But a - server does make collaboration easier. Fossil supports four different - yet simple [./server.wiki | server configurations]. - The most popular is a 2-line CGI script. This is the approach - used by the [./selfhost.wiki | self-hosting fossil repositories]. - 7. Robust & Reliable - Fossil stores content using an [./fileformat.wiki | enduring file format] in an SQLite database so that transactions are - atomic even if interrupted by a power loss or system crash. Furthermore, - automatic [./selfcheck.wiki | self-checks] verify that all aspects of - the repository are consistent prior to each commit. In over six years - of operation, no work has ever been lost after having been committed to - a Fossil repository. + atomic even if interrupted by a power loss or system crash. + Automatic [./selfcheck.wiki | self-checks] verify that all aspects of + the repository are consistent prior to each commit. + + 8. Free and Open-Source - Uses the [../COPYRIGHT-BSD2.txt|2-clause BSD license].


      Links For Fossil Users:

      + * "Fuel" is cross-platform GUI front-end for Fossil + written in Qt. [http://fuelscm.org/]. + Fuel is an independent project run by a different group of + developers. * [./reviews.wiki | Testimonials] from satisfied fossil users and [./quotes.wiki | Quotes] about Fossil and other DVCSes. * [./faq.wiki | FAQ] * The [./concepts.wiki | concepts] behind fossil * [./quickstart.wiki | Quick Start] guide to using fossil @@ -155,11 +133,11 @@ * [./fossil-v-git.wiki | Fossil versus Git]. * [./fiveminutes.wiki | Up and running in 5 minutes as a single user] (contributed by Gilles Ganault on 2013-01-08). * [./antibot.wiki | How Fossil defends against abuse by spiders and bots]. -

      Links For Fossil Developer:

      +

      Links For Fossil Developers:

      * [./contribute.wiki | Contributing] code or documentation to the Fossil project. * [./theory1.wiki | Thoughts On The Design Of Fossil]. * [./pop.wiki | Principles Of Operation] Index: www/inout.wiki ================================================================== --- www/inout.wiki +++ www/inout.wiki @@ -47,6 +47,17 @@ As with the "import" command, the --git option is not required since the git-fast-export file format is currently the only VCS interchange format that Fossil will generate. However, future versions of Fossil might add the ability to generate other VCS interchange formats, and so for compatibility, the use of the --git -option recommented. +option recommended. + +An anonymous user sends this comment: + +
      +The main Fossil branch is called "trunk", while the main git branch is +called "master". After you've exported your FOSSIL repo to git, you won't +see any files and gitk will complain about a missing "HEAD". You can +resolve this problem by merging "trunk" with "master" +(first verify using git status that you are on the "master" branch): +git merge trunk +
      Index: www/makefile.wiki ================================================================== --- www/makefile.wiki +++ www/makefile.wiki @@ -42,13 +42,12 @@ The sqlite3.c and sqlite3.h source files are byte-for-byte copies of a standard [http://www.sqlite.org/amalgamation.html | amalgamation]. The shell.c source file is code for the SQLite [http://www.sqlite.org/sqlite.html | command-line shell] that is used to help implement the [/help/sqlite3 | fossil sql] command. The -shell.c source file has been modified slightly from the standard -shell.c file in the SQLite release. Search for "Fossil Patch" in -the shell.c source file of Fossil to see the changes. +shell.c source file is also a byte-for-byte copy of the +shell.c file from the SQLite release. The TH1 script engine is implemented using files: 7. th.c 8. th.h @@ -58,29 +57,36 @@ The VERSION.h header file is generated from other information sources using a small program called: 9. mkversion.c + +The builtin_data.h header file contains the definitions of C-language +byte-array constants that contain various resources such as scripts and +images. The builtin_data.h header file is generate from the original +resource files using a small program called: + + 10 mkbuiltin.c The src/ subdirectory also contains documentation about the makeheaders preprocessor program: - 10. [../src/makeheaders.html | makeheaders.html] + 11. [../src/makeheaders.html | makeheaders.html] Click on the link to read this documentation. In addition there is a [http://www.tcl.tk/ | Tcl] script used to build the various makefiles: - 11. makemake.tcl + 12. makemake.tcl Running this Tcl script will automatically regenerate all makefiles. In order to add a new source file to the Fossil implementation, simply edit makemake.tcl to add the new filename, then rerun the script, and all of the makefiles for all targets will be rebuild. Finally, there is one of the makefiles generated by makemake.tcl: - 12. main.mk + 13. main.mk The main.mk makefile is invoked from the Makefile in the top-level directory. The main.mk is generated by makemake.tcl and should not be hand edited. Other makefiles generated by makemake.tcl are in other subdirectories (currently all in the win/ subdirectory). @@ -106,11 +112,11 @@ The VERSION.h header file is generated by a C program: src/mkversion.c. To run the VERSION.h generator, first compile the src/mkversion.c source file into a command-line program (named "mkversion.exe") -than run: +then run:
       mkversion.exe manifest.uuid manifest VERSION >VERSION.h
       
      @@ -117,10 +123,24 @@ The pathnames in the above command might need to be adjusted to get the directories right. The point is that the manifest.uuid, manifest, and VERSION files in the root of the source tree are the three arguments and the generated VERSION.h file appears on standard output. + +The builtin_data.h header file is generated by a C program: src/mkbuiltin.c. +The builtin_data.h file contains C-langauge byte-array definitions for +the content of resource files used by Fossil. To generate the +builtin_data.h file, first compile the mkbuiltin.c program, then run: + +
      +mkbuiltin.exe diff.tcl OtherFiles... >builtin_data.h
      +
      + +At the time of this writing, the "diff.tcl" script (a Tcl/Tk script used +to generate implement --tk option on the diff command) is the only resource +file processed using mkbuiltin.exe. However, new resources will likely be +added using this facility in future versions of Fossil.

      4.0 Preprocessing

      There are three preprocessors for the Fossil sources. The mkindex @@ -203,24 +223,23 @@ together in a final step. Some files require special C-preprocessor macro definitions. When compiling sqlite.c, the following macros are recommended: - * -Dlocaltime=fossil_localtime * -DSQLITE_OMIT_LOAD_EXTENSION=1 + * -DSQLITE_ENABLE_FTS4=1 * -DSQLITE_ENABLE_LOCKING_STYLE=0 * -DSQLITE_THREADSAFE=0 * -DSQLITE_DEFAULT_FILE_FORMAT=4 - * -DSQLITE_ENABLE_STAT3 - -The first and second symbol definitions above are required; the others -are merely recommended. The "localtime()" library function in SQLite must -be redefined to invoke fossil_localtime() instead. The fossil_localtime() -routine will invoke either gmtime() or localtime() depending on the -"Use UTC" setting for the fossil repository. Extension loading is omitted -as a security measure. Fossil is single-threaded so mutexing is disabled -in SQLite as a performance enhancement. + * -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 + +The first two symbol definitions above are required; the others are merely +recommended. Extension loading is omitted as a security measure. FTS4 is +needed for the search feature. Fossil is single-threaded so mutexing is +disabled in SQLite as a performance enhancement. The +SQLITE_ENABLE_EXPLAIN_COMMENTS option makes the output of "EXPLAIN" queries +in the "[/help?cmd=sqlite3|fossil sql]" command much more readable. When compiling the shell.c source file, these macros are required: * -Dmain=sqlite3_main * -DSQLITE_OMIT_LOAD_EXTENSION=1 Index: www/mkdownload.tcl ================================================================== --- www/mkdownload.tcl +++ www/mkdownload.tcl @@ -5,42 +5,41 @@ # # set out [open download.html w] fconfigure $out -encoding utf-8 -translation lf puts $out \ -{ - - -Fossil: Downloads - - - -
      - - -
      Fossil Downloads
      -
      -