From fc5fc812f63105746769d43216b99643f8cbf0f5 Mon Sep 17 00:00:00 2001 From: Stephen Lombardo Date: Wed, 16 Feb 2011 16:14:46 -0500 Subject: [PATCH] track 3.7.5 --- Makefile.in | 17 +- VERSION | 2 +- configure | 76 +- configure.ac | 18 - ext/fts2/fts2.c | 8 +- ext/fts2/fts2_porter.c | 1 - ext/fts2/fts2_tokenizer1.c | 6 +- ext/fts3/fts3.c | 1627 ++++++++++++++++++++------- ext/fts3/fts3Int.h | 103 +- ext/fts3/fts3_expr.c | 98 +- ext/fts3/fts3_porter.c | 2 +- ext/fts3/fts3_snippet.c | 587 ++++++++-- ext/fts3/fts3_tokenizer.c | 48 +- ext/fts3/fts3_write.c | 775 +++++++++---- ext/fts3/fts3speed.tcl | 122 ++ ext/rtree/rtree.c | 793 +++++++++---- ext/rtree/rtree1.test | 2 +- ext/rtree/rtree2.test | 4 +- ext/rtree/rtree3.test | 222 +++- ext/rtree/rtree4.test | 22 +- ext/rtree/rtree5.test | 2 +- ext/rtree/rtree6.test | 62 +- ext/rtree/rtree7.test | 2 +- ext/rtree/rtree8.test | 171 +++ ext/rtree/rtree9.test | 125 +++ ext/rtree/rtreeA.test | 220 ++++ ext/rtree/sqlite3rtree.h | 56 + ext/rtree/tkt3363.test | 2 +- main.mk | 19 +- manifest | 416 ++++--- manifest.uuid | 2 +- src/alter.c | 5 + src/analyze.c | 110 +- src/attach.c | 11 +- src/backup.c | 77 +- src/btree.c | 94 +- src/btree.h | 23 +- src/btreeInt.h | 17 +- src/build.c | 36 +- src/callback.c | 2 +- src/ctime.c | 3 + src/date.c | 24 +- src/expr.c | 161 ++- src/fkey.c | 2 +- src/func.c | 16 +- src/loadext.c | 40 + src/main.c | 277 +++-- src/malloc.c | 399 ++++--- src/mem1.c | 2 +- src/mem2.c | 1 + src/mem5.c | 2 +- src/memjournal.c | 3 +- src/mutex.h | 4 +- src/mutex_unix.c | 2 +- src/mutex_w32.c | 2 +- src/os.c | 6 + src/os_os2.c | 2 +- src/os_unix.c | 70 +- src/os_win.c | 138 ++- src/pager.c | 344 ++++-- src/pager.h | 3 +- src/pcache.c | 4 + src/pcache1.c | 433 +++++--- src/pragma.c | 65 +- src/prepare.c | 14 +- src/printf.c | 33 +- src/select.c | 210 +++- src/shell.c | 39 +- src/sqlite.h.in | 852 +++++++++----- src/sqlite3ext.h | 42 + src/sqliteInt.h | 76 +- src/status.c | 16 + src/tclsqlite.c | 41 +- src/test1.c | 440 +++++++- src/test3.c | 9 +- src/test_config.c | 12 + src/test_malloc.c | 13 +- src/test_multiplex.c | 973 ++++++++++++++++ src/test_mutex.c | 2 +- src/test_quota.c | 952 ++++++++++++++++ src/test_rtree.c | 294 +++++ src/test_superlock.c | 356 ++++++ src/test_vfs.c | 2 + src/update.c | 1 + src/util.c | 274 +++-- src/vacuum.c | 4 + src/vdbe.c | 242 ++-- src/vdbeInt.h | 135 +-- src/vdbeapi.c | 58 +- src/vdbeaux.c | 22 +- src/vdbeblob.c | 214 ++-- src/vdbemem.c | 99 +- src/vdbetrace.c | 124 ++- src/vtab.c | 4 +- src/wal.c | 117 +- src/wal.h | 9 +- src/where.c | 334 ++++-- test/all.test | 1 + test/alter2.test | 5 - test/alter3.test | 4 +- test/alter4.test | 65 -- test/analyze.test | 14 +- test/analyze2.test | 198 ++-- test/analyze3.test | 48 +- test/analyze4.test | 111 ++ test/attachmalloc.test | 9 +- test/auth.test | 6 +- test/autoindex1.test | 132 ++- test/backcompat.test | 100 +- test/backup.test | 2 +- test/cache.test | 83 +- test/capi2.test | 12 +- test/capi3d.test | 27 +- test/capi3e.test | 120 ++ test/conflict.test | 2 +- test/date.test | 2 +- test/dbstatus.test | 7 + test/e_createtable.test | 1930 ++++++++++++++++++++++++++++++++ test/e_delete.test | 470 ++++++++ test/e_droptrigger.test | 218 ++++ test/e_dropview.test | 192 ++++ test/e_expr.test | 754 ++++++++++++- test/e_fkey.test | 54 +- test/e_insert.test | 406 +++++++ test/e_reindex.test | 294 +++++ test/e_resolve.test | 135 +++ test/e_select.test | 2162 ++++++++++++++++++++++++++++++++++++ test/e_select2.test | 580 ++++++++++ test/e_update.test | 608 ++++++++++ test/e_vacuum.test | 301 +++++ test/enc4.test | 139 +++ test/eqp.test | 535 +++++++++ test/exclusive.test | 39 +- test/exclusive2.test | 36 +- test/expr.test | 2 +- test/fkey2.test | 12 +- test/fts3aa.test | 25 +- test/fts3ah.test | 37 +- test/fts3ao.test | 51 + test/fts3corrupt.test | 135 +++ test/fts3corrupt2.test | 110 ++ test/fts3cov.test | 79 +- test/fts3defer.test | 446 ++++++++ test/fts3defer2.test | 134 +++ test/fts3fault.test | 233 ++++ test/fts3malloc.test | 1 - test/fts3matchinfo.test | 384 +++++++ test/fts3query.test | 109 +- test/fts3rnd.test | 5 +- test/fts3shared.test | 72 ++ test/fts3snippet.test | 10 +- test/func3.test | 71 ++ test/in.test | 37 +- test/incrblob.test | 175 +-- test/incrblob3.test | 272 +++++ test/incrblobfault.test | 70 ++ test/index.test | 15 +- test/indexedby.test | 163 +-- test/ioerr.test | 7 +- test/jrnlmode3.test | 11 +- test/like.test | 208 ++-- test/lock.test | 2 +- test/lock6.test | 8 +- test/lock_common.tcl | 2 + test/lookaside.test | 32 +- test/malloc3.test | 2 +- test/malloc_common.tcl | 76 +- test/memsubsys1.test | 35 +- test/misc4.test | 2 +- test/misc7.test | 34 +- test/multiplex.test | 499 +++++++++ test/mutex1.test | 14 +- test/pager1.test | 207 ++-- test/pager2.test | 2 +- test/pagerfault.test | 41 +- test/pagerfault3.test | 63 ++ test/pcache.test | 8 + test/permutations.test | 57 +- test/pragma.test | 22 +- test/quota.test | 396 +++++++ test/releasetest.mk | 14 + test/releasetest.tcl | 332 ++++++ test/rtree.test | 31 +- test/savepoint.test | 25 +- test/schema4.test | 175 +++ test/select6.test | 1 + test/shared2.test | 11 + test/stat.test | 15 +- test/stmt.test | 1 + test/superlock.test | 249 +++++ test/tempdb.test | 2 +- test/tester.tcl | 170 ++- test/threadtest3.c | 124 +++ test/tkt-313723c356.test | 57 + test/tkt-38cb5df375.test | 321 ++++++ test/tkt-3998683a16.test | 46 + test/tkt-5d863f876e.test | 53 + test/tkt-78e04e52ea.test | 6 +- test/tkt-80ba201079.test | 191 ++++ test/tkt-8454a207b9.test | 67 ++ test/tkt-b351d95f9.test | 47 + test/tkt-f3e5abed55.test | 92 +- test/tkt1667.test | 2 +- test/tkt3442.test | 6 +- test/tkt3757.test | 4 +- test/trace2.test | 152 +++ test/triggerC.test | 12 + test/types.test | 2 +- test/vacuum.test | 24 + test/vacuum2.test | 41 + test/vtab1.test | 15 + test/wal2.test | 134 ++- test/wal3.test | 44 +- test/wal6.test | 90 ++ test/walfault.test | 97 ++ test/walmode.test | 20 +- test/walnoshm.test | 184 +++ test/walshared.test | 3 + test/where3.test | 157 ++- test/where7.test | 48 +- test/where9.test | 94 +- tool/lemon.c | 10 +- tool/mksqlite3c.tcl | 2 +- tool/mksqlite3h.tcl | 57 +- tool/shell1.test | 2 +- tool/showdb.c | 187 +++- tool/showjournal.c | 157 ++- 227 files changed, 27390 insertions(+), 4362 deletions(-) create mode 100644 ext/fts3/fts3speed.tcl create mode 100644 ext/rtree/rtree8.test create mode 100644 ext/rtree/rtree9.test create mode 100644 ext/rtree/rtreeA.test create mode 100644 ext/rtree/sqlite3rtree.h create mode 100644 src/test_multiplex.c create mode 100644 src/test_quota.c create mode 100644 src/test_rtree.c create mode 100644 src/test_superlock.c create mode 100644 test/analyze4.test create mode 100644 test/capi3e.test create mode 100644 test/e_createtable.test create mode 100644 test/e_delete.test create mode 100644 test/e_droptrigger.test create mode 100644 test/e_dropview.test create mode 100644 test/e_insert.test create mode 100644 test/e_reindex.test create mode 100644 test/e_resolve.test create mode 100644 test/e_select.test create mode 100644 test/e_select2.test create mode 100644 test/e_update.test create mode 100644 test/e_vacuum.test create mode 100644 test/enc4.test create mode 100644 test/eqp.test create mode 100644 test/fts3corrupt.test create mode 100644 test/fts3corrupt2.test create mode 100644 test/fts3defer.test create mode 100644 test/fts3defer2.test create mode 100644 test/fts3fault.test create mode 100644 test/fts3matchinfo.test create mode 100644 test/fts3shared.test create mode 100644 test/func3.test create mode 100644 test/incrblob3.test create mode 100644 test/incrblobfault.test create mode 100644 test/multiplex.test create mode 100644 test/pagerfault3.test create mode 100644 test/quota.test create mode 100644 test/releasetest.mk create mode 100644 test/releasetest.tcl create mode 100644 test/schema4.test create mode 100644 test/superlock.test create mode 100644 test/tkt-313723c356.test create mode 100644 test/tkt-38cb5df375.test create mode 100644 test/tkt-3998683a16.test create mode 100644 test/tkt-5d863f876e.test create mode 100644 test/tkt-80ba201079.test create mode 100644 test/tkt-8454a207b9.test create mode 100644 test/tkt-b351d95f9.test create mode 100644 test/trace2.test create mode 100644 test/wal6.test create mode 100644 test/walnoshm.test diff --git a/Makefile.in b/Makefile.in index 5d2c1d95..0e2837cd 100644 --- a/Makefile.in +++ b/Makefile.in @@ -26,7 +26,7 @@ BCC = @BUILD_CC@ @BUILD_CFLAGS@ # will run on the target platform. (BCC and TCC are usually the # same unless your are cross-compiling.) # -TCC = @CC@ @CPPFLAGS@ @CFLAGS@ -I. -I${TOP}/src +TCC = @CC@ @CPPFLAGS@ @CFLAGS@ -I. -I${TOP}/src -I${TOP}/ext/rtree # Define this for the autoconf-based build, so that the code knows it can # include the generated config.h @@ -59,10 +59,6 @@ LIBREADLINE = @TARGET_READLINE_LIBS@ # TCC += -DSQLITE_THREADSAFE=@SQLITE_THREADSAFE@ -# Do threads override each others locks by default (1), or do we test (-1) -# -TCC += -DSQLITE_THREAD_OVERRIDE_LOCK=@THREADSOVERRIDELOCKS@ - # Any target libraries which libsqlite must be linked against # TLIBS = @LIBS@ @@ -167,7 +163,8 @@ USE_AMALGAMATION = @USE_AMALGAMATION@ # LIBOBJS0 = alter.lo analyze.lo attach.lo auth.lo \ backup.lo bitvec.lo btmutex.lo btree.lo build.lo \ - callback.lo complete.lo ctime.lo date.lo delete.lo expr.lo fault.lo fkey.lo \ + callback.lo complete.lo ctime.lo date.lo delete.lo \ + expr.lo fault.lo fkey.lo \ fts3.lo fts3_expr.lo fts3_hash.lo fts3_icu.lo fts3_porter.lo \ fts3_snippet.lo fts3_tokenizer.lo fts3_tokenizer1.lo fts3_write.lo \ func.lo global.lo hash.lo \ @@ -181,7 +178,7 @@ LIBOBJS0 = alter.lo analyze.lo attach.lo auth.lo \ table.lo tokenize.lo trigger.lo \ update.lo util.lo vacuum.lo \ vdbe.lo vdbeapi.lo vdbeaux.lo vdbeblob.lo vdbemem.lo vdbetrace.lo \ - wal.lo walker.lo where.lo utf.o vtab.lo + wal.lo walker.lo where.lo utf.lo vtab.lo # Object files for the amalgamation. # @@ -363,12 +360,16 @@ TESTSRC = \ $(TOP)/src/test_intarray.c \ $(TOP)/src/test_journal.c \ $(TOP)/src/test_malloc.c \ + $(TOP)/src/test_multiplex.c \ $(TOP)/src/test_mutex.c \ $(TOP)/src/test_onefile.c \ $(TOP)/src/test_osinst.c \ $(TOP)/src/test_pcache.c \ + $(TOP)/src/test_quota.c \ + $(TOP)/src/test_rtree.c \ $(TOP)/src/test_schema.c \ $(TOP)/src/test_server.c \ + $(TOP)/src/test_superlock.c \ $(TOP)/src/test_stat.c \ $(TOP)/src/test_tclvar.c \ $(TOP)/src/test_thread.c \ @@ -460,6 +461,8 @@ EXTHDR += \ $(TOP)/ext/rtree/rtree.h EXTHDR += \ $(TOP)/ext/icu/sqliteicu.h +EXTHDR += \ + $(TOP)/ext/rtree/sqlite3rtree.h # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. diff --git a/VERSION b/VERSION index 0b2eb36f..aaf18d29 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.7.2 +3.7.5 diff --git a/configure b/configure index 3ac3077c..38343411 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.62 for sqlite 3.7.2. +# Generated by GNU Autoconf 2.62 for sqlite 3.7.5. # # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, # 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. @@ -743,8 +743,8 @@ SHELL=${CONFIG_SHELL-/bin/sh} # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.7.2' -PACKAGE_STRING='sqlite 3.7.2' +PACKAGE_VERSION='3.7.5' +PACKAGE_STRING='sqlite 3.7.5' PACKAGE_BUGREPORT='' # Factoring default headers for most tests. @@ -869,7 +869,6 @@ VERSION_NUMBER BUILD_CC SQLITE_THREADSAFE XTHREADCONNECT -THREADSOVERRIDELOCKS ALLOWRELEASE TEMP_STORE BUILD_EXEEXT @@ -912,7 +911,6 @@ enable_largefile with_hints enable_threadsafe enable_cross_thread_connections -enable_threads_override_locks enable_releasemode enable_tempstore enable_tcl @@ -1487,7 +1485,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures sqlite 3.7.2 to adapt to many kinds of systems. +\`configure' configures sqlite 3.7.5 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1552,7 +1550,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.7.2:";; + short | recursive ) echo "Configuration of sqlite 3.7.5:";; esac cat <<\_ACEOF @@ -1569,8 +1567,6 @@ Optional Features: --enable-threadsafe Support threadsafe operation --enable-cross-thread-connections Allow connection sharing across threads - --enable-threads-override-locks - Threads can override each others locks --enable-releasemode Support libtool link to release mode --enable-tempstore Use an in-ram database for temporary tables (never,no,yes,always) @@ -1670,7 +1666,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.7.2 +sqlite configure 3.7.5 generated by GNU Autoconf 2.62 Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, @@ -1684,7 +1680,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by sqlite $as_me 3.7.2, which was +It was created by sqlite $as_me 3.7.5, which was generated by GNU Autoconf 2.62. Invocation command line was $ $0 $@ @@ -3738,13 +3734,13 @@ if test "${lt_cv_nm_interface+set}" = set; then else lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext - (eval echo "\"\$as_me:3741: $ac_compile\"" >&5) + (eval echo "\"\$as_me:3737: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 - (eval echo "\"\$as_me:3744: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval echo "\"\$as_me:3740: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 - (eval echo "\"\$as_me:3747: output\"" >&5) + (eval echo "\"\$as_me:3743: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" @@ -4966,7 +4962,7 @@ ia64-*-hpux*) ;; *-*-irix6*) # Find out which ABI we are using. - echo '#line 4969 "configure"' > conftest.$ac_ext + echo '#line 4965 "configure"' > conftest.$ac_ext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -6835,11 +6831,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:6838: $lt_compile\"" >&5) + (eval echo "\"\$as_me:6834: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:6842: \$? = $ac_status" >&5 + echo "$as_me:6838: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -7174,11 +7170,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7177: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7173: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:7181: \$? = $ac_status" >&5 + echo "$as_me:7177: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -7279,11 +7275,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7282: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7278: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7286: \$? = $ac_status" >&5 + echo "$as_me:7282: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -7334,11 +7330,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7337: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7333: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7341: \$? = $ac_status" >&5 + echo "$as_me:7337: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -10147,7 +10143,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 10150 "configure" +#line 10146 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -10243,7 +10239,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 10246 "configure" +#line 10242 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -12580,32 +12576,6 @@ $as_echo "yes" >&6; } fi -########## -# Do we want to set threadsOverrideEachOthersLocks variable to be 1 (true) by -# default. Normally, a test at runtime is performed to determine the -# appropriate value of this variable. Use this option only if you're sure that -# threads can safely override each others locks in all runtime situations. -# -# Check whether --enable-threads-override-locks was given. -if test "${enable_threads_override_locks+set}" = set; then - enableval=$enable_threads_override_locks; -else - enable_threads_override_locks=no -fi - -{ $as_echo "$as_me:$LINENO: checking whether threads can override each others locks" >&5 -$as_echo_n "checking whether threads can override each others locks... " >&6; } -if test "$enable_threads_override_locks" = "no"; then - THREADSOVERRIDELOCKS='-1' - { $as_echo "$as_me:$LINENO: result: no" >&5 -$as_echo "no" >&6; } -else - THREADSOVERRIDELOCKS='1' - { $as_echo "$as_me:$LINENO: result: yes" >&5 -$as_echo "yes" >&6; } -fi - - ########## # Do we want to support release # @@ -13972,7 +13942,7 @@ exec 6>&1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by sqlite $as_me 3.7.2, which was +This file was extended by sqlite $as_me 3.7.5, which was generated by GNU Autoconf 2.62. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -14025,7 +13995,7 @@ Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_version="\\ -sqlite config.status 3.7.2 +sqlite config.status 3.7.5 configured by $0, generated by GNU Autoconf 2.62, with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\" diff --git a/configure.ac b/configure.ac index ea8c23f5..464ed1ef 100644 --- a/configure.ac +++ b/configure.ac @@ -268,24 +268,6 @@ else fi AC_SUBST(XTHREADCONNECT) -########## -# Do we want to set threadsOverrideEachOthersLocks variable to be 1 (true) by -# default. Normally, a test at runtime is performed to determine the -# appropriate value of this variable. Use this option only if you're sure that -# threads can safely override each others locks in all runtime situations. -# -AC_ARG_ENABLE(threads-override-locks, -AC_HELP_STRING([--enable-threads-override-locks],[Threads can override each others locks]),,enable_threads_override_locks=no) -AC_MSG_CHECKING([whether threads can override each others locks]) -if test "$enable_threads_override_locks" = "no"; then - THREADSOVERRIDELOCKS='-1' - AC_MSG_RESULT([no]) -else - THREADSOVERRIDELOCKS='1' - AC_MSG_RESULT([yes]) -fi -AC_SUBST(THREADSOVERRIDELOCKS) - ########## # Do we want to support release # diff --git a/ext/fts2/fts2.c b/ext/fts2/fts2.c index bc776aa8..74c2890d 100644 --- a/ext/fts2/fts2.c +++ b/ext/fts2/fts2.c @@ -306,8 +306,6 @@ #include #include #include -#include - #include "fts2.h" #include "fts2_hash.h" #include "fts2_tokenizer.h" @@ -345,13 +343,13 @@ SQLITE_EXTENSION_INIT1 */ /* TODO(shess) Is __isascii() a portable version of (c&0x80)==0? */ static int safe_isspace(char c){ - return (c&0x80)==0 ? isspace(c) : 0; + return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f'; } static int safe_tolower(char c){ - return (c&0x80)==0 ? tolower(c) : c; + return (c>='A' && c<='Z') ? (c - 'A' + 'a') : c; } static int safe_isalnum(char c){ - return (c&0x80)==0 ? isalnum(c) : 0; + return (c>='0' && c<='9') || (c>='A' && c<='Z') || (c>='a' && c<='z'); } typedef enum DocListType { diff --git a/ext/fts2/fts2_porter.c b/ext/fts2/fts2_porter.c index 97a95c87..16620b93 100644 --- a/ext/fts2/fts2_porter.c +++ b/ext/fts2/fts2_porter.c @@ -29,7 +29,6 @@ #include #include #include -#include #include "fts2_tokenizer.h" diff --git a/ext/fts2/fts2_tokenizer1.c b/ext/fts2/fts2_tokenizer1.c index f2ba49e5..7e133669 100644 --- a/ext/fts2/fts2_tokenizer1.c +++ b/ext/fts2/fts2_tokenizer1.c @@ -29,7 +29,6 @@ #include #include #include -#include #include "fts2_tokenizer.h" @@ -89,7 +88,8 @@ static int simpleCreate( /* Mark non-alphanumeric ASCII characters as delimiters */ int i; for(i=1; i<0x80; i++){ - t->delim[i] = !isalnum(i); + t->delim[i] = !((i>='0' && i<='9') || (i>='A' && i<='Z') || + (i>='a' && i<='z')); } } @@ -191,7 +191,7 @@ static int simpleNext( ** case-insensitivity. */ unsigned char ch = p[iStartOffset+i]; - c->pToken[i] = ch<0x80 ? tolower(ch) : ch; + c->pToken[i] = (ch>='A' && ch<='Z') ? (ch - 'A' + 'a') : ch; } *ppToken = c->pToken; *pnBytes = n; diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 16c95eda..072e12e2 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -441,16 +441,13 @@ static int fts3DisconnectMethod(sqlite3_vtab *pVtab){ int i; assert( p->nPendingData==0 ); + assert( p->pSegments==0 ); /* Free any prepared statements held */ for(i=0; iaStmt); i++){ sqlite3_finalize(p->aStmt[i]); } - for(i=0; inLeavesStmt; i++){ - sqlite3_finalize(p->aLeavesStmt[i]); - } - sqlite3_free(p->zSelectLeaves); - sqlite3_free(p->aLeavesStmt); + sqlite3_free(p->zSegmentsTbl); /* Invoke the tokenizer destructor to free the tokenizer. */ p->pTokenizer->pModule->xDestroy(p->pTokenizer); @@ -461,7 +458,7 @@ static int fts3DisconnectMethod(sqlite3_vtab *pVtab){ /* ** Construct one or more SQL statements from the format string given -** and then evaluate those statements. The success code is writting +** and then evaluate those statements. The success code is written ** into *pRc. ** ** If *pRc is initially non-zero then this routine is a no-op. @@ -513,33 +510,38 @@ static int fts3DestroyMethod(sqlite3_vtab *pVtab){ ** Invoke sqlite3_declare_vtab() to declare the schema for the FTS3 table ** passed as the first argument. This is done as part of the xConnect() ** and xCreate() methods. +** +** If *pRc is non-zero when this function is called, it is a no-op. +** Otherwise, if an error occurs, an SQLite error code is stored in *pRc +** before returning. */ -static int fts3DeclareVtab(Fts3Table *p){ - int i; /* Iterator variable */ - int rc; /* Return code */ - char *zSql; /* SQL statement passed to declare_vtab() */ - char *zCols; /* List of user defined columns */ +static void fts3DeclareVtab(int *pRc, Fts3Table *p){ + if( *pRc==SQLITE_OK ){ + int i; /* Iterator variable */ + int rc; /* Return code */ + char *zSql; /* SQL statement passed to declare_vtab() */ + char *zCols; /* List of user defined columns */ - /* Create a list of user columns for the virtual table */ - zCols = sqlite3_mprintf("%Q, ", p->azColumn[0]); - for(i=1; zCols && inColumn; i++){ - zCols = sqlite3_mprintf("%z%Q, ", zCols, p->azColumn[i]); + /* Create a list of user columns for the virtual table */ + zCols = sqlite3_mprintf("%Q, ", p->azColumn[0]); + for(i=1; zCols && inColumn; i++){ + zCols = sqlite3_mprintf("%z%Q, ", zCols, p->azColumn[i]); + } + + /* Create the whole "CREATE TABLE" statement to pass to SQLite */ + zSql = sqlite3_mprintf( + "CREATE TABLE x(%s %Q HIDDEN, docid HIDDEN)", zCols, p->zName + ); + if( !zCols || !zSql ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_declare_vtab(p->db, zSql); + } + + sqlite3_free(zSql); + sqlite3_free(zCols); + *pRc = rc; } - - /* Create the whole "CREATE TABLE" statement to pass to SQLite */ - zSql = sqlite3_mprintf( - "CREATE TABLE x(%s %Q HIDDEN, docid HIDDEN)", zCols, p->zName - ); - - if( !zCols || !zSql ){ - rc = SQLITE_NOMEM; - }else{ - rc = sqlite3_declare_vtab(p->db, zSql); - } - - sqlite3_free(zSql); - sqlite3_free(zCols); - return rc; } /* @@ -558,21 +560,19 @@ static int fts3CreateTables(Fts3Table *p){ sqlite3 *db = p->db; /* The database connection */ /* Create a list of user columns for the content table */ - if( p->bHasContent ){ - zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY"); - for(i=0; zContentCols && inColumn; i++){ - char *z = p->azColumn[i]; - zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z); - } - if( zContentCols==0 ) rc = SQLITE_NOMEM; - - /* Create the content table */ - fts3DbExec(&rc, db, - "CREATE TABLE %Q.'%q_content'(%s)", - p->zDb, p->zName, zContentCols - ); - sqlite3_free(zContentCols); + zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY"); + for(i=0; zContentCols && inColumn; i++){ + char *z = p->azColumn[i]; + zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z); } + if( zContentCols==0 ) rc = SQLITE_NOMEM; + + /* Create the content table */ + fts3DbExec(&rc, db, + "CREATE TABLE %Q.'%q_content'(%s)", + p->zDb, p->zName, zContentCols + ); + sqlite3_free(zContentCols); /* Create other tables */ fts3DbExec(&rc, db, "CREATE TABLE %Q.'%q_segments'(blockid INTEGER PRIMARY KEY, block BLOB);", @@ -595,6 +595,8 @@ static int fts3CreateTables(Fts3Table *p){ "CREATE TABLE %Q.'%q_docsize'(docid INTEGER PRIMARY KEY, size BLOB);", p->zDb, p->zName ); + } + if( p->bHasStat ){ fts3DbExec(&rc, db, "CREATE TABLE %Q.'%q_stat'(id INTEGER PRIMARY KEY, value BLOB);", p->zDb, p->zName @@ -604,39 +606,63 @@ static int fts3CreateTables(Fts3Table *p){ } /* -** An sqlite3_exec() callback for fts3TableExists. +** Store the current database page-size in bytes in p->nPgsz. +** +** If *pRc is non-zero when this function is called, it is a no-op. +** Otherwise, if an error occurs, an SQLite error code is stored in *pRc +** before returning. */ -static int fts3TableExistsCallback(void *pArg, int n, char **pp1, char **pp2){ - UNUSED_PARAMETER(n); - UNUSED_PARAMETER(pp1); - UNUSED_PARAMETER(pp2); - *(int*)pArg = 1; - return 1; +static void fts3DatabasePageSize(int *pRc, Fts3Table *p){ + if( *pRc==SQLITE_OK ){ + int rc; /* Return code */ + char *zSql; /* SQL text "PRAGMA %Q.page_size" */ + sqlite3_stmt *pStmt; /* Compiled "PRAGMA %Q.page_size" statement */ + + zSql = sqlite3_mprintf("PRAGMA %Q.page_size", p->zDb); + if( !zSql ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare(p->db, zSql, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_step(pStmt); + p->nPgsz = sqlite3_column_int(pStmt, 0); + rc = sqlite3_finalize(pStmt); + } + } + assert( p->nPgsz>0 || rc!=SQLITE_OK ); + sqlite3_free(zSql); + *pRc = rc; + } } /* -** Determine if a table currently exists in the database. +** "Special" FTS4 arguments are column specifications of the following form: +** +** = +** +** There may not be whitespace surrounding the "=" character. The +** term may be quoted, but the may not. */ -static void fts3TableExists( - int *pRc, /* Success code */ - sqlite3 *db, /* The database connection to test */ - const char *zDb, /* ATTACHed database within the connection */ - const char *zName, /* Name of the FTS3 table */ - const char *zSuffix, /* Shadow table extension */ - u8 *pResult /* Write results here */ +static int fts3IsSpecialColumn( + const char *z, + int *pnKey, + char **pzValue ){ - int rc = SQLITE_OK; - int res = 0; - char *zSql; - if( *pRc ) return; - zSql = sqlite3_mprintf( - "SELECT 1 FROM %Q.sqlite_master WHERE name='%q%s'", - zDb, zName, zSuffix - ); - rc = sqlite3_exec(db, zSql, fts3TableExistsCallback, &res, 0); - sqlite3_free(zSql); - *pResult = (u8)(res & 0xff); - if( rc!=SQLITE_ABORT ) *pRc = rc; + char *zValue; + const char *zCsr = z; + + while( *zCsr!='=' ){ + if( *zCsr=='\0' ) return 0; + zCsr++; + } + + *pnKey = (int)(zCsr-z); + zValue = sqlite3_mprintf("%s", &zCsr[1]); + if( zValue ){ + sqlite3Fts3Dequote(zValue); + } + *pzValue = zValue; + return 1; } /* @@ -660,8 +686,8 @@ static int fts3InitVtab( char **pzErr /* Write any error message here */ ){ Fts3Hash *pHash = (Fts3Hash *)pAux; - Fts3Table *p; /* Pointer to allocated vtab */ - int rc; /* Return code */ + Fts3Table *p = 0; /* Pointer to allocated vtab */ + int rc = SQLITE_OK; /* Return code */ int i; /* Iterator variable */ int nByte; /* Size of allocation used for *p */ int iCol; /* Column index */ @@ -670,35 +696,90 @@ static int fts3InitVtab( char *zCsr; /* Space for holding column names */ int nDb; /* Bytes required to hold database name */ int nName; /* Bytes required to hold table name */ - - const char *zTokenizer = 0; /* Name of tokenizer to use */ + int isFts4 = (argv[0][3]=='4'); /* True for FTS4, false for FTS3 */ + int bNoDocsize = 0; /* True to omit %_docsize table */ + const char **aCol; /* Array of column names */ sqlite3_tokenizer *pTokenizer = 0; /* Tokenizer for this table */ + assert( strlen(argv[0])==4 ); + assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4) + || (sqlite3_strnicmp(argv[0], "fts3", 4)==0 && !isFts4) + ); + nDb = (int)strlen(argv[1]) + 1; nName = (int)strlen(argv[2]) + 1; - for(i=3; i8 + && 0==sqlite3_strnicmp(z, "tokenize", 8) + && 0==sqlite3Fts3IsIdChar(z[8]) + ){ + rc = sqlite3Fts3InitTokenizer(pHash, &z[9], &pTokenizer, pzErr); } - if( z!=zTokenizer ){ + + /* Check if it is an FTS4 special argument. */ + else if( isFts4 && fts3IsSpecialColumn(z, &nKey, &zVal) ){ + if( !zVal ){ + rc = SQLITE_NOMEM; + goto fts3_init_out; + } + if( nKey==9 && 0==sqlite3_strnicmp(z, "matchinfo", 9) ){ + if( strlen(zVal)==4 && 0==sqlite3_strnicmp(zVal, "fts3", 4) ){ + bNoDocsize = 1; + }else{ + *pzErr = sqlite3_mprintf("unrecognized matchinfo: %s", zVal); + rc = SQLITE_ERROR; + } + }else{ + *pzErr = sqlite3_mprintf("unrecognized parameter: %s", z); + rc = SQLITE_ERROR; + } + sqlite3_free(zVal); + } + + /* Otherwise, the argument is a column name. */ + else { nString += (int)(strlen(z) + 1); + aCol[nCol++] = z; } } - nCol = argc - 3 - (zTokenizer!=0); - if( zTokenizer==0 ){ - rc = sqlite3Fts3InitTokenizer(pHash, 0, &pTokenizer, 0, pzErr); - if( rc!=SQLITE_OK ){ - return rc; - } - assert( pTokenizer ); - } + if( rc!=SQLITE_OK ) goto fts3_init_out; if( nCol==0 ){ + assert( nString==0 ); + aCol[0] = "content"; + nString = 8; nCol = 1; } + if( pTokenizer==0 ){ + rc = sqlite3Fts3InitTokenizer(pHash, "simple", &pTokenizer, pzErr); + if( rc!=SQLITE_OK ) goto fts3_init_out; + } + assert( pTokenizer ); + + /* Allocate and populate the Fts3Table structure. */ nByte = sizeof(Fts3Table) + /* Fts3Table */ nCol * sizeof(char *) + /* azColumn */ @@ -711,7 +792,6 @@ static int fts3InitVtab( goto fts3_init_out; } memset(p, 0, nByte); - p->db = db; p->nColumn = nCol; p->nPendingData = 0; @@ -719,11 +799,12 @@ static int fts3InitVtab( p->pTokenizer = pTokenizer; p->nNodeSize = 1000; p->nMaxPendingData = FTS3_MAX_PENDING_DATA; - zCsr = (char *)&p->azColumn[nCol]; - + p->bHasDocsize = (isFts4 && bNoDocsize==0); + p->bHasStat = isFts4; fts3HashInit(&p->pendingTerms, FTS3_HASH_STRING, 1); /* Fill in the zName and zDb fields of the vtab structure. */ + zCsr = (char *)&p->azColumn[nCol]; p->zName = zCsr; memcpy(zCsr, argv[2], nName); zCsr += nName; @@ -732,52 +813,45 @@ static int fts3InitVtab( zCsr += nDb; /* Fill in the azColumn array */ - iCol = 0; - for(i=3; iazColumn[iCol++] = zCsr; - zCsr += n+1; - assert( zCsr <= &((char *)p)[nByte] ); - } - } - if( iCol==0 ){ - assert( nCol==1 ); - p->azColumn[0] = "content"; + for(iCol=0; iColazColumn[iCol] = zCsr; + zCsr += n+1; + assert( zCsr <= &((char *)p)[nByte] ); } /* If this is an xCreate call, create the underlying tables in the ** database. TODO: For xConnect(), it could verify that said tables exist. */ if( isCreate ){ - p->bHasContent = 1; - p->bHasDocsize = argv[0][3]=='4'; rc = fts3CreateTables(p); - }else{ - rc = SQLITE_OK; - fts3TableExists(&rc, db, argv[1], argv[2], "_content", &p->bHasContent); - fts3TableExists(&rc, db, argv[1], argv[2], "_docsize", &p->bHasDocsize); } - if( rc!=SQLITE_OK ) goto fts3_init_out; - rc = fts3DeclareVtab(p); - if( rc!=SQLITE_OK ) goto fts3_init_out; + /* Figure out the page-size for the database. This is required in order to + ** estimate the cost of loading large doclists from the database (see + ** function sqlite3Fts3SegReaderCost() for details). + */ + fts3DatabasePageSize(&rc, p); - *ppVTab = &p->base; + /* Declare the table schema to SQLite. */ + fts3DeclareVtab(&rc, p); fts3_init_out: - assert( p || (pTokenizer && rc!=SQLITE_OK) ); + + sqlite3_free((void *)aCol); if( rc!=SQLITE_OK ){ if( p ){ fts3DisconnectMethod((sqlite3_vtab *)p); - }else{ + }else if( pTokenizer ){ pTokenizer->pModule->xDestroy(pTokenizer); } + }else{ + *ppVTab = &p->base; } return rc; } @@ -889,10 +963,12 @@ static int fts3OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ ** Close the cursor. For additional information see the documentation ** on the xClose method of the virtual table interface. */ -static int fulltextClose(sqlite3_vtab_cursor *pCursor){ +static int fts3CloseMethod(sqlite3_vtab_cursor *pCursor){ Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; + assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); sqlite3_finalize(pCsr->pStmt); sqlite3Fts3ExprFree(pCsr->pExpr); + sqlite3Fts3FreeDeferredTokens(pCsr); sqlite3_free(pCsr->aDoclist); sqlite3_free(pCsr->aMatchinfo); sqlite3_free(pCsr); @@ -931,50 +1007,137 @@ static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){ } /* -** Advance the cursor to the next row in the %_content table that -** matches the search criteria. For a MATCH search, this will be -** the next row that matches. For a full-table scan, this will be -** simply the next row in the %_content table. For a docid lookup, -** this routine simply sets the EOF flag. +** This function is used to process a single interior node when searching +** a b-tree for a term or term prefix. The node data is passed to this +** function via the zNode/nNode parameters. The term to search for is +** passed in zTerm/nTerm. ** -** Return SQLITE_OK if nothing goes wrong. SQLITE_OK is returned -** even if we reach end-of-file. The fts3EofMethod() will be called -** subsequently to determine whether or not an EOF was hit. +** If piFirst is not NULL, then this function sets *piFirst to the blockid +** of the child node that heads the sub-tree that may contain the term. +** +** If piLast is not NULL, then *piLast is set to the right-most child node +** that heads a sub-tree that may contain a term for which zTerm/nTerm is +** a prefix. +** +** If an OOM error occurs, SQLITE_NOMEM is returned. Otherwise, SQLITE_OK. */ -static int fts3NextMethod(sqlite3_vtab_cursor *pCursor){ +static int fts3ScanInteriorNode( + const char *zTerm, /* Term to select leaves for */ + int nTerm, /* Size of term zTerm in bytes */ + const char *zNode, /* Buffer containing segment interior node */ + int nNode, /* Size of buffer at zNode */ + sqlite3_int64 *piFirst, /* OUT: Selected child node */ + sqlite3_int64 *piLast /* OUT: Selected child node */ +){ int rc = SQLITE_OK; /* Return code */ - Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; + const char *zCsr = zNode; /* Cursor to iterate through node */ + const char *zEnd = &zCsr[nNode];/* End of interior node buffer */ + char *zBuffer = 0; /* Buffer to load terms into */ + int nAlloc = 0; /* Size of allocated buffer */ + int isFirstTerm = 1; /* True when processing first term on page */ + sqlite3_int64 iChild; /* Block id of child node to descend to */ - if( pCsr->aDoclist==0 ){ - if( SQLITE_ROW!=sqlite3_step(pCsr->pStmt) ){ - pCsr->isEof = 1; - rc = sqlite3_reset(pCsr->pStmt); - } - }else if( pCsr->pNextId>=&pCsr->aDoclist[pCsr->nDoclist] ){ - pCsr->isEof = 1; - }else{ - sqlite3_reset(pCsr->pStmt); - fts3GetDeltaVarint(&pCsr->pNextId, &pCsr->iPrevId); - pCsr->isRequireSeek = 1; - pCsr->isMatchinfoNeeded = 1; + /* Skip over the 'height' varint that occurs at the start of every + ** interior node. Then load the blockid of the left-child of the b-tree + ** node into variable iChild. + ** + ** Even if the data structure on disk is corrupted, this (reading two + ** varints from the buffer) does not risk an overread. If zNode is a + ** root node, then the buffer comes from a SELECT statement. SQLite does + ** not make this guarantee explicitly, but in practice there are always + ** either more than 20 bytes of allocated space following the nNode bytes of + ** contents, or two zero bytes. Or, if the node is read from the %_segments + ** table, then there are always 20 bytes of zeroed padding following the + ** nNode bytes of content (see sqlite3Fts3ReadBlock() for details). + */ + zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); + zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); + if( zCsr>zEnd ){ + return SQLITE_CORRUPT; } + + while( zCsrzEnd ){ + rc = SQLITE_CORRUPT; + goto finish_scan; + } + if( nPrefix+nSuffix>nAlloc ){ + char *zNew; + nAlloc = (nPrefix+nSuffix) * 2; + zNew = (char *)sqlite3_realloc(zBuffer, nAlloc); + if( !zNew ){ + rc = SQLITE_NOMEM; + goto finish_scan; + } + zBuffer = zNew; + } + memcpy(&zBuffer[nPrefix], zCsr, nSuffix); + nBuffer = nPrefix + nSuffix; + zCsr += nSuffix; + + /* Compare the term we are searching for with the term just loaded from + ** the interior node. If the specified term is greater than or equal + ** to the term from the interior node, then all terms on the sub-tree + ** headed by node iChild are smaller than zTerm. No need to search + ** iChild. + ** + ** If the interior node term is larger than the specified term, then + ** the tree headed by iChild may contain the specified term. + */ + cmp = memcmp(zTerm, zBuffer, (nBuffer>nTerm ? nTerm : nBuffer)); + if( piFirst && (cmp<0 || (cmp==0 && nBuffer>nTerm)) ){ + *piFirst = iChild; + piFirst = 0; + } + + if( piLast && cmp<0 ){ + *piLast = iChild; + piLast = 0; + } + + iChild++; + }; + + if( piFirst ) *piFirst = iChild; + if( piLast ) *piLast = iChild; + + finish_scan: + sqlite3_free(zBuffer); return rc; } /* -** The buffer pointed to by argument zNode (size nNode bytes) contains the -** root node of a b-tree segment. The segment is guaranteed to be at least -** one level high (i.e. the root node is not also a leaf). If successful, -** this function locates the leaf node of the segment that may contain the -** term specified by arguments zTerm and nTerm and writes its block number -** to *piLeaf. +** The buffer pointed to by argument zNode (size nNode bytes) contains an +** interior node of a b-tree segment. The zTerm buffer (size nTerm bytes) +** contains a term. This function searches the sub-tree headed by the zNode +** node for the range of leaf nodes that may contain the specified term +** or terms for which the specified term is a prefix. ** -** It is possible that the returned leaf node does not contain the specified -** term. However, if the segment does contain said term, it is stored on -** the identified leaf node. Because this function only inspects interior -** segment nodes (and never loads leaf nodes into memory), it is not possible -** to be sure. +** If piLeaf is not NULL, then *piLeaf is set to the blockid of the +** left-most leaf node in the tree that may contain the specified term. +** If piLeaf2 is not NULL, then *piLeaf2 is set to the blockid of the +** right-most leaf node that may contain a term for which the specified +** term is a prefix. +** +** It is possible that the range of returned leaf nodes does not contain +** the specified term or any terms for which it is a prefix. However, if the +** segment does contain any such terms, they are stored within the identified +** range. Because this function only inspects interior segment nodes (and +** never loads leaf nodes into memory), it is not possible to be sure. ** ** If an error occurs, an error code other than SQLITE_OK is returned. */ @@ -984,77 +1147,41 @@ static int fts3SelectLeaf( int nTerm, /* Size of term zTerm in bytes */ const char *zNode, /* Buffer containing segment interior node */ int nNode, /* Size of buffer at zNode */ - sqlite3_int64 *piLeaf /* Selected leaf node */ + sqlite3_int64 *piLeaf, /* Selected leaf node */ + sqlite3_int64 *piLeaf2 /* Selected leaf node */ ){ - int rc = SQLITE_OK; /* Return code */ - const char *zCsr = zNode; /* Cursor to iterate through node */ - const char *zEnd = &zCsr[nNode];/* End of interior node buffer */ - char *zBuffer = 0; /* Buffer to load terms into */ - int nAlloc = 0; /* Size of allocated buffer */ + int rc; /* Return code */ + int iHeight; /* Height of this node in tree */ - while( 1 ){ - int isFirstTerm = 1; /* True when processing first term on page */ - int iHeight; /* Height of this node in tree */ - sqlite3_int64 iChild; /* Block id of child node to descend to */ - int nBlock; /* Size of child node in bytes */ + assert( piLeaf || piLeaf2 ); - zCsr += sqlite3Fts3GetVarint32(zCsr, &iHeight); - zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); - - while( zCsr1 ){ + char *zBlob = 0; /* Blob read from %_segments table */ + int nBlob; /* Size of zBlob in bytes */ + + if( piLeaf && piLeaf2 && (*piLeaf!=*piLeaf2) ){ + rc = sqlite3Fts3ReadBlock(p, *piLeaf, &zBlob, &nBlob); + if( rc==SQLITE_OK ){ + rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, 0); } - isFirstTerm = 0; - zCsr += sqlite3Fts3GetVarint32(zCsr, &nSuffix); - if( nPrefix+nSuffix>nAlloc ){ - char *zNew; - nAlloc = (nPrefix+nSuffix) * 2; - zNew = (char *)sqlite3_realloc(zBuffer, nAlloc); - if( !zNew ){ - sqlite3_free(zBuffer); - return SQLITE_NOMEM; - } - zBuffer = zNew; - } - memcpy(&zBuffer[nPrefix], zCsr, nSuffix); - nBuffer = nPrefix + nSuffix; - zCsr += nSuffix; - - /* Compare the term we are searching for with the term just loaded from - ** the interior node. If the specified term is greater than or equal - ** to the term from the interior node, then all terms on the sub-tree - ** headed by node iChild are smaller than zTerm. No need to search - ** iChild. - ** - ** If the interior node term is larger than the specified term, then - ** the tree headed by iChild may contain the specified term. - */ - cmp = memcmp(zTerm, zBuffer, (nBuffer>nTerm ? nTerm : nBuffer)); - if( cmp<0 || (cmp==0 && nBuffer>nTerm) ) break; - iChild++; - }; - - /* If (iHeight==1), the children of this interior node are leaves. The - ** specified term may be present on leaf node iChild. - */ - if( iHeight==1 ){ - *piLeaf = iChild; - break; + sqlite3_free(zBlob); + piLeaf = 0; + zBlob = 0; } - /* Descend to interior node iChild. */ - rc = sqlite3Fts3ReadBlock(p, iChild, &zCsr, &nBlock); - if( rc!=SQLITE_OK ) break; - zEnd = &zCsr[nBlock]; + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3ReadBlock(p, piLeaf ? *piLeaf : *piLeaf2, &zBlob, &nBlob); + } + if( rc==SQLITE_OK ){ + rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, piLeaf2); + } + sqlite3_free(zBlob); } - sqlite3_free(zBuffer); + return rc; } @@ -1286,20 +1413,44 @@ static void fts3PoslistMerge( /* ** nToken==1 searches for adjacent positions. +** +** This function is used to merge two position lists into one. When it is +** called, *pp1 and *pp2 must both point to position lists. A position-list is +** the part of a doclist that follows each document id. For example, if a row +** contains: +** +** 'a b c'|'x y z'|'a b b a' +** +** Then the position list for this row for token 'b' would consist of: +** +** 0x02 0x01 0x02 0x03 0x03 0x00 +** +** When this function returns, both *pp1 and *pp2 are left pointing to the +** byte following the 0x00 terminator of their respective position lists. +** +** If isSaveLeft is 0, an entry is added to the output position list for +** each position in *pp2 for which there exists one or more positions in +** *pp1 so that (pos(*pp2)>pos(*pp1) && pos(*pp2)-pos(*pp1)<=nToken). i.e. +** when the *pp1 token appears before the *pp2 token, but not more than nToken +** slots before it. */ static int fts3PoslistPhraseMerge( - char **pp, /* Output buffer */ + char **pp, /* IN/OUT: Preallocated output buffer */ int nToken, /* Maximum difference in token positions */ int isSaveLeft, /* Save the left position */ - char **pp1, /* Left input list */ - char **pp2 /* Right input list */ + int isExact, /* If *pp1 is exactly nTokens before *pp2 */ + char **pp1, /* IN/OUT: Left input list */ + char **pp2 /* IN/OUT: Right input list */ ){ char *p = (pp ? *pp : 0); char *p1 = *pp1; char *p2 = *pp2; - int iCol1 = 0; int iCol2 = 0; + + /* Never set both isSaveLeft and isExact for the same invocation. */ + assert( isSaveLeft==0 || isExact==0 ); + assert( *p1!=0 && *p2!=0 ); if( *p1==POS_COLUMN ){ p1++; @@ -1328,7 +1479,9 @@ static int fts3PoslistPhraseMerge( fts3GetDeltaVarint(&p2, &iPos2); iPos2 -= 2; while( 1 ){ - if( iPos2>iPos1 && iPos2<=iPos1+nToken ){ + if( iPos2==iPos1+nToken + || (isExact==0 && iPos2>iPos1 && iPos2<=iPos1+nToken) + ){ sqlite3_int64 iSave; if( !pp ){ fts3PoslistCopy(0, &p2); @@ -1411,21 +1564,21 @@ static int fts3PoslistNearMerge( char *p2 = *pp2; if( !pp ){ - if( fts3PoslistPhraseMerge(0, nRight, 0, pp1, pp2) ) return 1; + if( fts3PoslistPhraseMerge(0, nRight, 0, 0, pp1, pp2) ) return 1; *pp1 = p1; *pp2 = p2; - return fts3PoslistPhraseMerge(0, nLeft, 0, pp2, pp1); + return fts3PoslistPhraseMerge(0, nLeft, 0, 0, pp2, pp1); }else{ char *pTmp1 = aTmp; char *pTmp2; char *aTmp2; int res = 1; - fts3PoslistPhraseMerge(&pTmp1, nRight, 0, pp1, pp2); + fts3PoslistPhraseMerge(&pTmp1, nRight, 0, 0, pp1, pp2); aTmp2 = pTmp2 = pTmp1; *pp1 = p1; *pp2 = p2; - fts3PoslistPhraseMerge(&pTmp2, nLeft, 1, pp2, pp1); + fts3PoslistPhraseMerge(&pTmp2, nLeft, 1, 0, pp2, pp1); if( pTmp1!=aTmp && pTmp2!=aTmp2 ){ fts3PoslistMerge(pp, &aTmp, &aTmp2); }else if( pTmp1!=aTmp ){ @@ -1471,7 +1624,8 @@ static int fts3DoclistMerge( char *a1, /* Buffer containing first doclist */ int n1, /* Size of buffer a1 */ char *a2, /* Buffer containing second doclist */ - int n2 /* Size of buffer a2 */ + int n2, /* Size of buffer a2 */ + int *pnDoc /* OUT: Number of docids in output */ ){ sqlite3_int64 i1 = 0; sqlite3_int64 i2 = 0; @@ -1482,6 +1636,7 @@ static int fts3DoclistMerge( char *p2 = a2; char *pEnd1 = &a1[n1]; char *pEnd2 = &a2[n2]; + int nDoc = 0; assert( mergetype==MERGE_OR || mergetype==MERGE_POS_OR || mergetype==MERGE_AND || mergetype==MERGE_NOT @@ -1525,6 +1680,7 @@ static int fts3DoclistMerge( fts3PutDeltaVarint(&p, &iPrev, i1); fts3GetDeltaVarint2(&p1, pEnd1, &i1); fts3GetDeltaVarint2(&p2, pEnd2, &i2); + nDoc++; }else if( i1aaOutput[i]; nOut = pTS->anOutput[i]; - pTS->aaOutput[0] = 0; + pTS->aaOutput[i] = 0; }else{ int nNew = nOut + pTS->anOutput[i]; char *aNew = sqlite3_malloc(nNew); @@ -1657,7 +1816,7 @@ static int fts3TermSelectMerge(TermSelect *pTS){ return SQLITE_NOMEM; } fts3DoclistMerge(mergetype, 0, 0, - aNew, &nNew, pTS->aaOutput[i], pTS->anOutput[i], aOut, nOut + aNew, &nNew, pTS->aaOutput[i], pTS->anOutput[i], aOut, nOut, 0 ); sqlite3_free(pTS->aaOutput[i]); sqlite3_free(aOut); @@ -1728,8 +1887,8 @@ static int fts3TermSelectCb( } return SQLITE_NOMEM; } - fts3DoclistMerge(mergetype, 0, 0, - aNew, &nNew, pTS->aaOutput[iOut], pTS->anOutput[iOut], aMerge, nMerge + fts3DoclistMerge(mergetype, 0, 0, aNew, &nNew, + pTS->aaOutput[iOut], pTS->anOutput[iOut], aMerge, nMerge, 0 ); if( iOut>0 ) sqlite3_free(aMerge); @@ -1747,6 +1906,161 @@ static int fts3TermSelectCb( return SQLITE_OK; } +static int fts3DeferredTermSelect( + Fts3DeferredToken *pToken, /* Phrase token */ + int isTermPos, /* True to include positions */ + int *pnOut, /* OUT: Size of list */ + char **ppOut /* OUT: Body of list */ +){ + char *aSource; + int nSource; + + aSource = sqlite3Fts3DeferredDoclist(pToken, &nSource); + if( !aSource ){ + *pnOut = 0; + *ppOut = 0; + }else if( isTermPos ){ + *ppOut = sqlite3_malloc(nSource); + if( !*ppOut ) return SQLITE_NOMEM; + memcpy(*ppOut, aSource, nSource); + *pnOut = nSource; + }else{ + sqlite3_int64 docid; + *pnOut = sqlite3Fts3GetVarint(aSource, &docid); + *ppOut = sqlite3_malloc(*pnOut); + if( !*ppOut ) return SQLITE_NOMEM; + sqlite3Fts3PutVarint(*ppOut, docid); + } + + return SQLITE_OK; +} + +/* +** An Fts3SegReaderArray is used to store an array of Fts3SegReader objects. +** Elements are added to the array using fts3SegReaderArrayAdd(). +*/ +struct Fts3SegReaderArray { + int nSegment; /* Number of valid entries in apSegment[] */ + int nAlloc; /* Allocated size of apSegment[] */ + int nCost; /* The cost of executing SegReaderIterate() */ + Fts3SegReader *apSegment[1]; /* Array of seg-reader objects */ +}; + + +/* +** Free an Fts3SegReaderArray object. Also free all seg-readers in the +** array (using sqlite3Fts3SegReaderFree()). +*/ +static void fts3SegReaderArrayFree(Fts3SegReaderArray *pArray){ + if( pArray ){ + int i; + for(i=0; inSegment; i++){ + sqlite3Fts3SegReaderFree(pArray->apSegment[i]); + } + sqlite3_free(pArray); + } +} + +static int fts3SegReaderArrayAdd( + Fts3SegReaderArray **ppArray, + Fts3SegReader *pNew +){ + Fts3SegReaderArray *pArray = *ppArray; + + if( !pArray || pArray->nAlloc==pArray->nSegment ){ + int nNew = (pArray ? pArray->nAlloc+16 : 16); + pArray = (Fts3SegReaderArray *)sqlite3_realloc(pArray, + sizeof(Fts3SegReaderArray) + (nNew-1) * sizeof(Fts3SegReader*) + ); + if( !pArray ){ + sqlite3Fts3SegReaderFree(pNew); + return SQLITE_NOMEM; + } + if( nNew==16 ){ + pArray->nSegment = 0; + pArray->nCost = 0; + } + pArray->nAlloc = nNew; + *ppArray = pArray; + } + + pArray->apSegment[pArray->nSegment++] = pNew; + return SQLITE_OK; +} + +static int fts3TermSegReaderArray( + Fts3Cursor *pCsr, /* Virtual table cursor handle */ + const char *zTerm, /* Term to query for */ + int nTerm, /* Size of zTerm in bytes */ + int isPrefix, /* True for a prefix search */ + Fts3SegReaderArray **ppArray /* OUT: Allocated seg-reader array */ +){ + Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; + int rc; /* Return code */ + Fts3SegReaderArray *pArray = 0; /* Array object to build */ + Fts3SegReader *pReader = 0; /* Seg-reader to add to pArray */ + sqlite3_stmt *pStmt = 0; /* SQL statement to scan %_segdir table */ + int iAge = 0; /* Used to assign ages to segments */ + + /* Allocate a seg-reader to scan the pending terms, if any. */ + rc = sqlite3Fts3SegReaderPending(p, zTerm, nTerm, isPrefix, &pReader); + if( rc==SQLITE_OK && pReader ) { + rc = fts3SegReaderArrayAdd(&pArray, pReader); + } + + /* Loop through the entire %_segdir table. For each segment, create a + ** Fts3SegReader to iterate through the subset of the segment leaves + ** that may contain a term that matches zTerm/nTerm. For non-prefix + ** searches, this is always a single leaf. For prefix searches, this + ** may be a contiguous block of leaves. + */ + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3AllSegdirs(p, &pStmt); + } + while( rc==SQLITE_OK && SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){ + Fts3SegReader *pNew = 0; + int nRoot = sqlite3_column_bytes(pStmt, 4); + char const *zRoot = sqlite3_column_blob(pStmt, 4); + if( sqlite3_column_int64(pStmt, 1)==0 ){ + /* The entire segment is stored on the root node (which must be a + ** leaf). Do not bother inspecting any data in this case, just + ** create a Fts3SegReader to scan the single leaf. + */ + rc = sqlite3Fts3SegReaderNew(iAge, 0, 0, 0, zRoot, nRoot, &pNew); + }else{ + sqlite3_int64 i1; /* First leaf that may contain zTerm */ + sqlite3_int64 i2; /* Final leaf that may contain zTerm */ + rc = fts3SelectLeaf(p, zTerm, nTerm, zRoot, nRoot, &i1, (isPrefix?&i2:0)); + if( isPrefix==0 ) i2 = i1; + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3SegReaderNew(iAge, i1, i2, 0, 0, 0, &pNew); + } + } + assert( (pNew==0)==(rc!=SQLITE_OK) ); + + /* If a new Fts3SegReader was allocated, add it to the array. */ + if( rc==SQLITE_OK ){ + rc = fts3SegReaderArrayAdd(&pArray, pNew); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts3SegReaderCost(pCsr, pNew, &pArray->nCost); + } + iAge++; + } + + if( rc==SQLITE_DONE ){ + rc = sqlite3_reset(pStmt); + }else{ + sqlite3_reset(pStmt); + } + if( rc!=SQLITE_OK ){ + fts3SegReaderArrayFree(pArray); + pArray = 0; + } + *ppArray = pArray; + return rc; +} + /* ** This function retreives the doclist for the specified term (or term ** prefix) from the database. @@ -1760,112 +2074,31 @@ static int fts3TermSelectCb( */ static int fts3TermSelect( Fts3Table *p, /* Virtual table handle */ + Fts3PhraseToken *pTok, /* Token to query for */ int iColumn, /* Column to query (or -ve for all columns) */ - const char *zTerm, /* Term to query for */ - int nTerm, /* Size of zTerm in bytes */ - int isPrefix, /* True for a prefix search */ int isReqPos, /* True to include position lists in output */ int *pnOut, /* OUT: Size of buffer at *ppOut */ char **ppOut /* OUT: Malloced result buffer */ ){ - int i; - TermSelect tsc; - Fts3SegFilter filter; /* Segment term filter configuration */ - Fts3SegReader **apSegment; /* Array of segments to read data from */ - int nSegment = 0; /* Size of apSegment array */ - int nAlloc = 16; /* Allocated size of segment array */ int rc; /* Return code */ - sqlite3_stmt *pStmt = 0; /* SQL statement to scan %_segdir table */ - int iAge = 0; /* Used to assign ages to segments */ - - apSegment = (Fts3SegReader **)sqlite3_malloc(sizeof(Fts3SegReader*)*nAlloc); - if( !apSegment ) return SQLITE_NOMEM; - rc = sqlite3Fts3SegReaderPending(p, zTerm, nTerm, isPrefix, &apSegment[0]); - if( rc!=SQLITE_OK ) goto finished; - if( apSegment[0] ){ - nSegment = 1; - } - - /* Loop through the entire %_segdir table. For each segment, create a - ** Fts3SegReader to iterate through the subset of the segment leaves - ** that may contain a term that matches zTerm/nTerm. For non-prefix - ** searches, this is always a single leaf. For prefix searches, this - ** may be a contiguous block of leaves. - ** - ** The code in this loop does not actually load any leaves into memory - ** (unless the root node happens to be a leaf). It simply examines the - ** b-tree structure to determine which leaves need to be inspected. - */ - rc = sqlite3Fts3AllSegdirs(p, &pStmt); - while( rc==SQLITE_OK && SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){ - Fts3SegReader *pNew = 0; - int nRoot = sqlite3_column_bytes(pStmt, 4); - char const *zRoot = sqlite3_column_blob(pStmt, 4); - if( sqlite3_column_int64(pStmt, 1)==0 ){ - /* The entire segment is stored on the root node (which must be a - ** leaf). Do not bother inspecting any data in this case, just - ** create a Fts3SegReader to scan the single leaf. - */ - rc = sqlite3Fts3SegReaderNew(p, iAge, 0, 0, 0, zRoot, nRoot, &pNew); - }else{ - int rc2; /* Return value of sqlite3Fts3ReadBlock() */ - sqlite3_int64 i1; /* Blockid of leaf that may contain zTerm */ - rc = fts3SelectLeaf(p, zTerm, nTerm, zRoot, nRoot, &i1); - if( rc==SQLITE_OK ){ - sqlite3_int64 i2 = sqlite3_column_int64(pStmt, 2); - rc = sqlite3Fts3SegReaderNew(p, iAge, i1, i2, 0, 0, 0, &pNew); - } - - /* The following call to ReadBlock() serves to reset the SQL statement - ** used to retrieve blocks of data from the %_segments table. If it is - ** not reset here, then it may remain classified as an active statement - ** by SQLite, which may lead to "DROP TABLE" or "DETACH" commands - ** failing. - */ - rc2 = sqlite3Fts3ReadBlock(p, 0, 0, 0); - if( rc==SQLITE_OK ){ - rc = rc2; - } - } - iAge++; - - /* If a new Fts3SegReader was allocated, add it to the apSegment array. */ - assert( pNew!=0 || rc!=SQLITE_OK ); - if( pNew ){ - if( nSegment==nAlloc ){ - Fts3SegReader **pArray; - nAlloc += 16; - pArray = (Fts3SegReader **)sqlite3_realloc( - apSegment, nAlloc*sizeof(Fts3SegReader *) - ); - if( !pArray ){ - sqlite3Fts3SegReaderFree(p, pNew); - rc = SQLITE_NOMEM; - goto finished; - } - apSegment = pArray; - } - apSegment[nSegment++] = pNew; - } - } - if( rc!=SQLITE_DONE ){ - assert( rc!=SQLITE_OK ); - goto finished; - } + Fts3SegReaderArray *pArray; /* Seg-reader array for this term */ + TermSelect tsc; /* Context object for fts3TermSelectCb() */ + Fts3SegFilter filter; /* Segment term filter configuration */ + pArray = pTok->pArray; memset(&tsc, 0, sizeof(TermSelect)); tsc.isReqPos = isReqPos; filter.flags = FTS3_SEGMENT_IGNORE_EMPTY - | (isPrefix ? FTS3_SEGMENT_PREFIX : 0) + | (pTok->isPrefix ? FTS3_SEGMENT_PREFIX : 0) | (isReqPos ? FTS3_SEGMENT_REQUIRE_POS : 0) | (iColumnnColumn ? FTS3_SEGMENT_COLUMN_FILTER : 0); filter.iCol = iColumn; - filter.zTerm = zTerm; - filter.nTerm = nTerm; + filter.zTerm = pTok->z; + filter.nTerm = pTok->n; - rc = sqlite3Fts3SegReaderIterate(p, apSegment, nSegment, &filter, - fts3TermSelectCb, (void *)&tsc + rc = sqlite3Fts3SegReaderIterate(p, pArray->apSegment, pArray->nSegment, + &filter, fts3TermSelectCb, (void *)&tsc ); if( rc==SQLITE_OK ){ rc = fts3TermSelectMerge(&tsc); @@ -1875,26 +2108,112 @@ static int fts3TermSelect( *ppOut = tsc.aaOutput[0]; *pnOut = tsc.anOutput[0]; }else{ + int i; for(i=0; ipArray = 0; return rc; } +/* +** This function counts the total number of docids in the doclist stored +** in buffer aList[], size nList bytes. +** +** If the isPoslist argument is true, then it is assumed that the doclist +** contains a position-list following each docid. Otherwise, it is assumed +** that the doclist is simply a list of docids stored as delta encoded +** varints. +*/ +static int fts3DoclistCountDocids(int isPoslist, char *aList, int nList){ + int nDoc = 0; /* Return value */ + if( aList ){ + char *aEnd = &aList[nList]; /* Pointer to one byte after EOF */ + char *p = aList; /* Cursor */ + if( !isPoslist ){ + /* The number of docids in the list is the same as the number of + ** varints. In FTS3 a varint consists of a single byte with the 0x80 + ** bit cleared and zero or more bytes with the 0x80 bit set. So to + ** count the varints in the buffer, just count the number of bytes + ** with the 0x80 bit clear. */ + while( ppLeft); + if( rc==SQLITE_OK ){ + rc = fts3DeferExpression(pCsr, pExpr->pRight); + } + if( pExpr->eType==FTSQUERY_PHRASE ){ + int iCol = pExpr->pPhrase->iColumn; + int i; + for(i=0; rc==SQLITE_OK && ipPhrase->nToken; i++){ + Fts3PhraseToken *pToken = &pExpr->pPhrase->aToken[i]; + if( pToken->pDeferred==0 ){ + rc = sqlite3Fts3DeferToken(pCsr, pToken, iCol); + } + } + } + } + return rc; +} + +/* +** This function removes the position information from a doclist. When +** called, buffer aList (size *pnList bytes) contains a doclist that includes +** position information. This function removes the position information so +** that aList contains only docids, and adjusts *pnList to reflect the new +** (possibly reduced) size of the doclist. +*/ +static void fts3DoclistStripPositions( + char *aList, /* IN/OUT: Buffer containing doclist */ + int *pnList /* IN/OUT: Size of doclist in bytes */ +){ + if( aList ){ + char *aEnd = &aList[*pnList]; /* Pointer to one byte after EOF */ + char *p = aList; /* Input cursor */ + char *pOut = aList; /* Output cursor */ + + while( piColumn; int isTermPos = (pPhrase->nToken>1 || isReqPos); + Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; + int isFirst = 1; + + int iPrevTok = 0; + int nDoc = 0; + + /* If this is an xFilter() evaluation, create a segment-reader for each + ** phrase token. Or, if this is an xNext() or snippet/offsets/matchinfo + ** evaluation, only create segment-readers if there are no Fts3DeferredToken + ** objects attached to the phrase-tokens. + */ + for(ii=0; iinToken; ii++){ + Fts3PhraseToken *pTok = &pPhrase->aToken[ii]; + if( pTok->pArray==0 ){ + if( (pCsr->eEvalmode==FTS3_EVAL_FILTER) + || (pCsr->eEvalmode==FTS3_EVAL_NEXT && pCsr->pDeferred==0) + || (pCsr->eEvalmode==FTS3_EVAL_MATCHINFO && pTok->bFulltext) + ){ + rc = fts3TermSegReaderArray( + pCsr, pTok->z, pTok->n, pTok->isPrefix, &pTok->pArray + ); + if( rc!=SQLITE_OK ) return rc; + } + } + } for(ii=0; iinToken; ii++){ - struct PhraseToken *pTok = &pPhrase->aToken[ii]; - char *z = pTok->z; /* Next token of the phrase */ - int n = pTok->n; /* Size of z in bytes */ - int isPrefix = pTok->isPrefix;/* True if token is a prefix */ - char *pList; /* Pointer to token doclist */ - int nList; /* Size of buffer at pList */ + Fts3PhraseToken *pTok; /* Token to find doclist for */ + int iTok = 0; /* The token being queried this iteration */ + char *pList = 0; /* Pointer to token doclist */ + int nList = 0; /* Size of buffer at pList */ - rc = fts3TermSelect(p, iCol, z, n, isPrefix, isTermPos, &nList, &pList); + /* Select a token to process. If this is an xFilter() call, then tokens + ** are processed in order from least to most costly. Otherwise, tokens + ** are processed in the order in which they occur in the phrase. + */ + if( pCsr->eEvalmode==FTS3_EVAL_MATCHINFO ){ + assert( isReqPos ); + iTok = ii; + pTok = &pPhrase->aToken[iTok]; + if( pTok->bFulltext==0 ) continue; + }else if( pCsr->eEvalmode==FTS3_EVAL_NEXT || isReqPos ){ + iTok = ii; + pTok = &pPhrase->aToken[iTok]; + }else{ + int nMinCost = 0x7FFFFFFF; + int jj; + + /* Find the remaining token with the lowest cost. */ + for(jj=0; jjnToken; jj++){ + Fts3SegReaderArray *pArray = pPhrase->aToken[jj].pArray; + if( pArray && pArray->nCostnCost; + } + } + pTok = &pPhrase->aToken[iTok]; + + /* This branch is taken if it is determined that loading the doclist + ** for the next token would require more IO than loading all documents + ** currently identified by doclist pOut/nOut. No further doclists will + ** be loaded from the full-text index for this phrase. + */ + if( nMinCost>nDoc && ii>0 ){ + rc = fts3DeferExpression(pCsr, pCsr->pExpr); + break; + } + } + + if( pCsr->eEvalmode==FTS3_EVAL_NEXT && pTok->pDeferred ){ + rc = fts3DeferredTermSelect(pTok->pDeferred, isTermPos, &nList, &pList); + }else{ + if( pTok->pArray ){ + rc = fts3TermSelect(p, pTok, iCol, isTermPos, &nList, &pList); + } + pTok->bFulltext = 1; + } + assert( rc!=SQLITE_OK || pCsr->eEvalmode || pTok->pArray==0 ); if( rc!=SQLITE_OK ) break; - if( ii==0 ){ + if( isFirst ){ pOut = pList; nOut = nList; - }else{ - /* Merge the new term list and the current output. If this is the - ** last term in the phrase, and positions are not required in the - ** output of this function, the positions can be dropped as part - ** of this merge. Either way, the result of this merge will be - ** smaller than nList bytes. The code in fts3DoclistMerge() is written - ** so that it is safe to use pList as the output as well as an input - ** in this case. - */ - int mergetype = MERGE_POS_PHRASE; - if( ii==pPhrase->nToken-1 && !isReqPos ){ - mergetype = MERGE_PHRASE; + if( pCsr->eEvalmode==FTS3_EVAL_FILTER && pPhrase->nToken>1 ){ + nDoc = fts3DoclistCountDocids(1, pOut, nOut); } - fts3DoclistMerge(mergetype, 0, 0, pList, &nOut, pOut, nOut, pList, nList); - sqlite3_free(pOut); - pOut = pList; + isFirst = 0; + iPrevTok = iTok; + }else{ + /* Merge the new term list and the current output. */ + char *aLeft, *aRight; + int nLeft, nRight; + int nDist; + int mt; + + /* If this is the final token of the phrase, and positions were not + ** requested by the caller, use MERGE_PHRASE instead of POS_PHRASE. + ** This drops the position information from the output list. + */ + mt = MERGE_POS_PHRASE; + if( ii==pPhrase->nToken-1 && !isReqPos ) mt = MERGE_PHRASE; + + assert( iPrevTok!=iTok ); + if( iPrevToknToken ){ + assert( pCsr->eEvalmode==FTS3_EVAL_FILTER && isReqPos==0 ); + fts3DoclistStripPositions(pOut, &nOut); + } *paOut = pOut; *pnOut = nOut; }else{ @@ -1950,6 +2364,14 @@ static int fts3PhraseSelect( return rc; } +/* +** This function merges two doclists according to the requirements of a +** NEAR operator. +** +** Both input doclists must include position information. The output doclist +** includes position information if the first argument to this function +** is MERGE_POS_NEAR, or does not if it is MERGE_NEAR. +*/ static int fts3NearMerge( int mergetype, /* MERGE_POS_NEAR or MERGE_NEAR */ int nNear, /* Parameter to NEAR operator */ @@ -1962,8 +2384,8 @@ static int fts3NearMerge( char **paOut, /* OUT: Results of merge (malloced) */ int *pnOut /* OUT: Sized of output buffer */ ){ - char *aOut; - int rc; + char *aOut; /* Buffer to write output doclist to */ + int rc; /* Return code */ assert( mergetype==MERGE_POS_NEAR || MERGE_NEAR ); @@ -1972,7 +2394,7 @@ static int fts3NearMerge( rc = SQLITE_NOMEM; }else{ rc = fts3DoclistMerge(mergetype, nNear+nTokenRight, nNear+nTokenLeft, - aOut, pnOut, aLeft, nLeft, aRight, nRight + aOut, pnOut, aLeft, nLeft, aRight, nRight, 0 ); if( rc!=SQLITE_OK ){ sqlite3_free(aOut); @@ -1984,8 +2406,23 @@ static int fts3NearMerge( return rc; } +/* +** This function is used as part of the processing for the snippet() and +** offsets() functions. +** +** Both pLeft and pRight are expression nodes of type FTSQUERY_PHRASE. Both +** have their respective doclists (including position information) loaded +** in Fts3Expr.aDoclist/nDoclist. This function removes all entries from +** each doclist that are not within nNear tokens of a corresponding entry +** in the other doclist. +*/ int sqlite3Fts3ExprNearTrim(Fts3Expr *pLeft, Fts3Expr *pRight, int nNear){ - int rc; + int rc; /* Return code */ + + assert( pLeft->eType==FTSQUERY_PHRASE ); + assert( pRight->eType==FTSQUERY_PHRASE ); + assert( pLeft->isLoaded && pRight->isLoaded ); + if( pLeft->aDoclist==0 || pRight->aDoclist==0 ){ sqlite3_free(pLeft->aDoclist); sqlite3_free(pRight->aDoclist); @@ -1993,8 +2430,8 @@ int sqlite3Fts3ExprNearTrim(Fts3Expr *pLeft, Fts3Expr *pRight, int nNear){ pLeft->aDoclist = 0; rc = SQLITE_OK; }else{ - char *aOut; - int nOut; + char *aOut; /* Buffer in which to assemble new doclist */ + int nOut; /* Size of buffer aOut in bytes */ rc = fts3NearMerge(MERGE_POS_NEAR, nNear, pLeft->pPhrase->nToken, pLeft->aDoclist, pLeft->nDoclist, @@ -2018,14 +2455,156 @@ int sqlite3Fts3ExprNearTrim(Fts3Expr *pLeft, Fts3Expr *pRight, int nNear){ return rc; } + /* -** Evaluate the full-text expression pExpr against fts3 table pTab. Store -** the resulting doclist in *paOut and *pnOut. This routine mallocs for -** the space needed to store the output. The caller is responsible for -** freeing the space when it has finished. +** Allocate an Fts3SegReaderArray for each token in the expression pExpr. +** The allocated objects are stored in the Fts3PhraseToken.pArray member +** variables of each token structure. */ -static int evalFts3Expr( - Fts3Table *p, /* Virtual table handle */ +static int fts3ExprAllocateSegReaders( + Fts3Cursor *pCsr, /* FTS3 table */ + Fts3Expr *pExpr, /* Expression to create seg-readers for */ + int *pnExpr /* OUT: Number of AND'd expressions */ +){ + int rc = SQLITE_OK; /* Return code */ + + assert( pCsr->eEvalmode==FTS3_EVAL_FILTER ); + if( pnExpr && pExpr->eType!=FTSQUERY_AND ){ + (*pnExpr)++; + pnExpr = 0; + } + + if( pExpr->eType==FTSQUERY_PHRASE ){ + Fts3Phrase *pPhrase = pExpr->pPhrase; + int ii; + + for(ii=0; rc==SQLITE_OK && iinToken; ii++){ + Fts3PhraseToken *pTok = &pPhrase->aToken[ii]; + if( pTok->pArray==0 ){ + rc = fts3TermSegReaderArray( + pCsr, pTok->z, pTok->n, pTok->isPrefix, &pTok->pArray + ); + } + } + }else{ + rc = fts3ExprAllocateSegReaders(pCsr, pExpr->pLeft, pnExpr); + if( rc==SQLITE_OK ){ + rc = fts3ExprAllocateSegReaders(pCsr, pExpr->pRight, pnExpr); + } + } + return rc; +} + +/* +** Free the Fts3SegReaderArray objects associated with each token in the +** expression pExpr. In other words, this function frees the resources +** allocated by fts3ExprAllocateSegReaders(). +*/ +static void fts3ExprFreeSegReaders(Fts3Expr *pExpr){ + if( pExpr ){ + Fts3Phrase *pPhrase = pExpr->pPhrase; + if( pPhrase ){ + int kk; + for(kk=0; kknToken; kk++){ + fts3SegReaderArrayFree(pPhrase->aToken[kk].pArray); + pPhrase->aToken[kk].pArray = 0; + } + } + fts3ExprFreeSegReaders(pExpr->pLeft); + fts3ExprFreeSegReaders(pExpr->pRight); + } +} + +/* +** Return the sum of the costs of all tokens in the expression pExpr. This +** function must be called after Fts3SegReaderArrays have been allocated +** for all tokens using fts3ExprAllocateSegReaders(). +*/ +static int fts3ExprCost(Fts3Expr *pExpr){ + int nCost; /* Return value */ + if( pExpr->eType==FTSQUERY_PHRASE ){ + Fts3Phrase *pPhrase = pExpr->pPhrase; + int ii; + nCost = 0; + for(ii=0; iinToken; ii++){ + Fts3SegReaderArray *pArray = pPhrase->aToken[ii].pArray; + if( pArray ){ + nCost += pPhrase->aToken[ii].pArray->nCost; + } + } + }else{ + nCost = fts3ExprCost(pExpr->pLeft) + fts3ExprCost(pExpr->pRight); + } + return nCost; +} + +/* +** The following is a helper function (and type) for fts3EvalExpr(). It +** must be called after Fts3SegReaders have been allocated for every token +** in the expression. See the context it is called from in fts3EvalExpr() +** for further explanation. +*/ +typedef struct ExprAndCost ExprAndCost; +struct ExprAndCost { + Fts3Expr *pExpr; + int nCost; +}; +static void fts3ExprAssignCosts( + Fts3Expr *pExpr, /* Expression to create seg-readers for */ + ExprAndCost **ppExprCost /* OUT: Write to *ppExprCost */ +){ + if( pExpr->eType==FTSQUERY_AND ){ + fts3ExprAssignCosts(pExpr->pLeft, ppExprCost); + fts3ExprAssignCosts(pExpr->pRight, ppExprCost); + }else{ + (*ppExprCost)->pExpr = pExpr; + (*ppExprCost)->nCost = fts3ExprCost(pExpr); + (*ppExprCost)++; + } +} + +/* +** Evaluate the full-text expression pExpr against FTS3 table pTab. Store +** the resulting doclist in *paOut and *pnOut. This routine mallocs for +** the space needed to store the output. The caller is responsible for +** freeing the space when it has finished. +** +** This function is called in two distinct contexts: +** +** * From within the virtual table xFilter() method. In this case, the +** output doclist contains entries for all rows in the table, based on +** data read from the full-text index. +** +** In this case, if the query expression contains one or more tokens that +** are very common, then the returned doclist may contain a superset of +** the documents that actually match the expression. +** +** * From within the virtual table xNext() method. This call is only made +** if the call from within xFilter() found that there were very common +** tokens in the query expression and did return a superset of the +** matching documents. In this case the returned doclist contains only +** entries that correspond to the current row of the table. Instead of +** reading the data for each token from the full-text index, the data is +** already available in-memory in the Fts3PhraseToken.pDeferred structures. +** See fts3EvalDeferred() for how it gets there. +** +** In the first case above, Fts3Cursor.doDeferred==0. In the second (if it is +** required) Fts3Cursor.doDeferred==1. +** +** If the SQLite invokes the snippet(), offsets() or matchinfo() function +** as part of a SELECT on an FTS3 table, this function is called on each +** individual phrase expression in the query. If there were very common tokens +** found in the xFilter() call, then this function is called once for phrase +** for each row visited, and the returned doclist contains entries for the +** current row only. Otherwise, if there were no very common tokens, then this +** function is called once only for each phrase in the query and the returned +** doclist contains entries for all rows of the table. +** +** Fts3Cursor.doDeferred==1 when this function is called on phrases as a +** result of a snippet(), offsets() or matchinfo() invocation. +*/ +static int fts3EvalExpr( + Fts3Cursor *p, /* Virtual table cursor handle */ Fts3Expr *pExpr, /* Parsed fts3 expression */ char **paOut, /* OUT: Pointer to malloc'd result buffer */ int *pnOut, /* OUT: Size of buffer at *paOut */ @@ -2038,33 +2617,102 @@ static int evalFts3Expr( *pnOut = 0; if( pExpr ){ - assert( pExpr->eType==FTSQUERY_PHRASE - || pExpr->eType==FTSQUERY_NEAR - || isReqPos==0 + assert( pExpr->eType==FTSQUERY_NEAR || pExpr->eType==FTSQUERY_OR + || pExpr->eType==FTSQUERY_AND || pExpr->eType==FTSQUERY_NOT + || pExpr->eType==FTSQUERY_PHRASE ); + assert( pExpr->eType==FTSQUERY_PHRASE || isReqPos==0 ); + if( pExpr->eType==FTSQUERY_PHRASE ){ - rc = fts3PhraseSelect(p, pExpr->pPhrase, + rc = fts3PhraseSelect(p, pExpr->pPhrase, isReqPos || (pExpr->pParent && pExpr->pParent->eType==FTSQUERY_NEAR), paOut, pnOut ); + fts3ExprFreeSegReaders(pExpr); + }else if( p->eEvalmode==FTS3_EVAL_FILTER && pExpr->eType==FTSQUERY_AND ){ + ExprAndCost *aExpr = 0; /* Array of AND'd expressions and costs */ + int nExpr = 0; /* Size of aExpr[] */ + char *aRet = 0; /* Doclist to return to caller */ + int nRet = 0; /* Length of aRet[] in bytes */ + int nDoc = 0x7FFFFFFF; + + assert( !isReqPos ); + + rc = fts3ExprAllocateSegReaders(p, pExpr, &nExpr); + if( rc==SQLITE_OK ){ + assert( nExpr>1 ); + aExpr = sqlite3_malloc(sizeof(ExprAndCost) * nExpr); + if( !aExpr ) rc = SQLITE_NOMEM; + } + if( rc==SQLITE_OK ){ + int ii; /* Used to iterate through expressions */ + + fts3ExprAssignCosts(pExpr, &aExpr); + aExpr -= nExpr; + for(ii=0; iipExpr && (pBest==0 || pCand->nCostnCost) ){ + pBest = pCand; + } + } + + if( pBest->nCost>nDoc ){ + rc = fts3DeferExpression(p, p->pExpr); + break; + }else{ + rc = fts3EvalExpr(p, pBest->pExpr, &aNew, &nNew, 0); + if( rc!=SQLITE_OK ) break; + pBest->pExpr = 0; + if( ii==0 ){ + aRet = aNew; + nRet = nNew; + nDoc = fts3DoclistCountDocids(0, aRet, nRet); + }else{ + fts3DoclistMerge( + MERGE_AND, 0, 0, aRet, &nRet, aRet, nRet, aNew, nNew, &nDoc + ); + sqlite3_free(aNew); + } + } + } + } + + if( rc==SQLITE_OK ){ + *paOut = aRet; + *pnOut = nRet; + }else{ + assert( *paOut==0 ); + sqlite3_free(aRet); + } + sqlite3_free(aExpr); + fts3ExprFreeSegReaders(pExpr); + }else{ char *aLeft; char *aRight; int nLeft; int nRight; - if( 0==(rc = evalFts3Expr(p, pExpr->pRight, &aRight, &nRight, isReqPos)) - && 0==(rc = evalFts3Expr(p, pExpr->pLeft, &aLeft, &nLeft, isReqPos)) + assert( pExpr->eType==FTSQUERY_NEAR + || pExpr->eType==FTSQUERY_OR + || pExpr->eType==FTSQUERY_NOT + || (pExpr->eType==FTSQUERY_AND && p->eEvalmode==FTS3_EVAL_NEXT) + ); + + if( 0==(rc = fts3EvalExpr(p, pExpr->pRight, &aRight, &nRight, isReqPos)) + && 0==(rc = fts3EvalExpr(p, pExpr->pLeft, &aLeft, &nLeft, isReqPos)) ){ - assert( pExpr->eType==FTSQUERY_NEAR || pExpr->eType==FTSQUERY_OR - || pExpr->eType==FTSQUERY_AND || pExpr->eType==FTSQUERY_NOT - ); switch( pExpr->eType ){ case FTSQUERY_NEAR: { Fts3Expr *pLeft; Fts3Expr *pRight; - int mergetype = isReqPos ? MERGE_POS_NEAR : MERGE_NEAR; - + int mergetype = MERGE_NEAR; if( pExpr->pParent && pExpr->pParent->eType==FTSQUERY_NEAR ){ mergetype = MERGE_POS_NEAR; } @@ -2093,7 +2741,7 @@ static int evalFts3Expr( */ char *aBuffer = sqlite3_malloc(nRight+nLeft+1); rc = fts3DoclistMerge(MERGE_OR, 0, 0, aBuffer, pnOut, - aLeft, nLeft, aRight, nRight + aLeft, nLeft, aRight, nRight, 0 ); *paOut = aBuffer; sqlite3_free(aLeft); @@ -2103,7 +2751,7 @@ static int evalFts3Expr( default: { assert( FTSQUERY_NOT==MERGE_NOT && FTSQUERY_AND==MERGE_AND ); fts3DoclistMerge(pExpr->eType, 0, 0, aLeft, pnOut, - aLeft, nLeft, aRight, nRight + aLeft, nLeft, aRight, nRight, 0 ); *paOut = aLeft; break; @@ -2114,6 +2762,89 @@ static int evalFts3Expr( } } + assert( rc==SQLITE_OK || *paOut==0 ); + return rc; +} + +/* +** This function is called from within xNext() for each row visited by +** an FTS3 query. If evaluating the FTS3 query expression within xFilter() +** was able to determine the exact set of matching rows, this function sets +** *pbRes to true and returns SQLITE_IO immediately. +** +** Otherwise, if evaluating the query expression within xFilter() returned a +** superset of the matching documents instead of an exact set (this happens +** when the query includes very common tokens and it is deemed too expensive to +** load their doclists from disk), this function tests if the current row +** really does match the FTS3 query. +** +** If an error occurs, an SQLite error code is returned. Otherwise, SQLITE_OK +** is returned and *pbRes is set to true if the current row matches the +** FTS3 query (and should be included in the results returned to SQLite), or +** false otherwise. +*/ +static int fts3EvalDeferred( + Fts3Cursor *pCsr, /* FTS3 cursor pointing at row to test */ + int *pbRes /* OUT: Set to true if row is a match */ +){ + int rc = SQLITE_OK; + if( pCsr->pDeferred==0 ){ + *pbRes = 1; + }else{ + rc = fts3CursorSeek(0, pCsr); + if( rc==SQLITE_OK ){ + sqlite3Fts3FreeDeferredDoclists(pCsr); + rc = sqlite3Fts3CacheDeferredDoclists(pCsr); + } + if( rc==SQLITE_OK ){ + char *a = 0; + int n = 0; + rc = fts3EvalExpr(pCsr, pCsr->pExpr, &a, &n, 0); + assert( n>=0 ); + *pbRes = (n>0); + sqlite3_free(a); + } + } + return rc; +} + +/* +** Advance the cursor to the next row in the %_content table that +** matches the search criteria. For a MATCH search, this will be +** the next row that matches. For a full-table scan, this will be +** simply the next row in the %_content table. For a docid lookup, +** this routine simply sets the EOF flag. +** +** Return SQLITE_OK if nothing goes wrong. SQLITE_OK is returned +** even if we reach end-of-file. The fts3EofMethod() will be called +** subsequently to determine whether or not an EOF was hit. +*/ +static int fts3NextMethod(sqlite3_vtab_cursor *pCursor){ + int res; + int rc = SQLITE_OK; /* Return code */ + Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; + + pCsr->eEvalmode = FTS3_EVAL_NEXT; + do { + if( pCsr->aDoclist==0 ){ + if( SQLITE_ROW!=sqlite3_step(pCsr->pStmt) ){ + pCsr->isEof = 1; + rc = sqlite3_reset(pCsr->pStmt); + break; + } + pCsr->iPrevId = sqlite3_column_int64(pCsr->pStmt, 0); + }else{ + if( pCsr->pNextId>=&pCsr->aDoclist[pCsr->nDoclist] ){ + pCsr->isEof = 1; + break; + } + sqlite3_reset(pCsr->pStmt); + fts3GetDeltaVarint(&pCsr->pNextId, &pCsr->iPrevId); + pCsr->isRequireSeek = 1; + pCsr->isMatchinfoNeeded = 1; + } + }while( SQLITE_OK==(rc = fts3EvalDeferred(pCsr, &res)) && res==0 ); + return rc; } @@ -2133,11 +2864,6 @@ static int evalFts3Expr( ** number idxNum-FTS3_FULLTEXT_SEARCH, 0 indexed. argv[0] is the right-hand ** side of the MATCH operator. */ -/* TODO(shess) Upgrade the cursor initialization and destruction to -** account for fts3FilterMethod() being called multiple times on the -** same cursor. The current solution is very fragile. Apply fix to -** fts3 as appropriate. -*/ static int fts3FilterMethod( sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ int idxNum, /* Strategy index */ @@ -2160,6 +2886,7 @@ static int fts3FilterMethod( assert( idxNum>=0 && idxNum<=(FTS3_FULLTEXT_SEARCH+p->nColumn) ); assert( nVal==0 || nVal==1 ); assert( (nVal==0)==(idxNum==FTS3_FULLSCAN_SEARCH) ); + assert( p->pSegments==0 ); /* In case the cursor has been used before, clear it now. */ sqlite3_finalize(pCsr->pStmt); @@ -2167,24 +2894,7 @@ static int fts3FilterMethod( sqlite3Fts3ExprFree(pCsr->pExpr); memset(&pCursor[1], 0, sizeof(Fts3Cursor)-sizeof(sqlite3_vtab_cursor)); - /* 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. - */ - zSql = sqlite3_mprintf(azSql[idxNum==FTS3_FULLSCAN_SEARCH], p->zDb, p->zName); - if( !zSql ){ - rc = SQLITE_NOMEM; - }else{ - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); - sqlite3_free(zSql); - } - if( rc!=SQLITE_OK ) return rc; - pCsr->eSearch = (i16)idxNum; - - if( idxNum==FTS3_DOCID_SEARCH ){ - rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]); - }else if( idxNum!=FTS3_FULLSCAN_SEARCH ){ + 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]); @@ -2203,11 +2913,33 @@ static int fts3FilterMethod( return rc; } - rc = evalFts3Expr(p, pCsr->pExpr, &pCsr->aDoclist, &pCsr->nDoclist, 0); + rc = sqlite3Fts3ReadLock(p); + if( rc!=SQLITE_OK ) return rc; + + rc = fts3EvalExpr(pCsr, pCsr->pExpr, &pCsr->aDoclist, &pCsr->nDoclist, 0); + sqlite3Fts3SegmentsClose(p); + if( rc!=SQLITE_OK ) return rc; pCsr->pNextId = pCsr->aDoclist; pCsr->iPrevId = 0; } + /* 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. + */ + zSql = sqlite3_mprintf(azSql[idxNum==FTS3_FULLSCAN_SEARCH], p->zDb, p->zName); + if( !zSql ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); + sqlite3_free(zSql); + } + if( rc==SQLITE_OK && idxNum==FTS3_DOCID_SEARCH ){ + rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]); + } + pCsr->eSearch = (i16)idxNum; + if( rc!=SQLITE_OK ) return rc; return fts3NextMethod(pCursor); } @@ -2231,6 +2963,11 @@ static int fts3RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ if( pCsr->aDoclist ){ *pRowid = pCsr->iPrevId; }else{ + /* This branch runs if the query is implemented using a full-table scan + ** (not using the full-text index). In this case grab the rowid from the + ** SELECT statement. + */ + assert( pCsr->isRequireSeek==0 ); *pRowid = sqlite3_column_int64(pCsr->pStmt, 0); } return SQLITE_OK; @@ -2293,7 +3030,9 @@ static int fts3UpdateMethod( ** hash-table to the database. */ static int fts3SyncMethod(sqlite3_vtab *pVtab){ - return sqlite3Fts3PendingTermsFlush((Fts3Table *)pVtab); + int rc = sqlite3Fts3PendingTermsFlush((Fts3Table *)pVtab); + sqlite3Fts3SegmentsClose((Fts3Table *)pVtab); + return rc; } /* @@ -2331,8 +3070,27 @@ static int fts3RollbackMethod(sqlite3_vtab *pVtab){ ** This is used by the matchinfo(), snippet() and offsets() auxillary ** functions. */ -int sqlite3Fts3ExprLoadDoclist(Fts3Table *pTab, Fts3Expr *pExpr){ - return evalFts3Expr(pTab, pExpr, &pExpr->aDoclist, &pExpr->nDoclist, 1); +int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *pCsr, Fts3Expr *pExpr){ + int rc; + assert( pExpr->eType==FTSQUERY_PHRASE && pExpr->pPhrase ); + assert( pCsr->eEvalmode==FTS3_EVAL_NEXT ); + rc = fts3EvalExpr(pCsr, pExpr, &pExpr->aDoclist, &pExpr->nDoclist, 1); + return rc; +} + +int sqlite3Fts3ExprLoadFtDoclist( + Fts3Cursor *pCsr, + Fts3Expr *pExpr, + char **paDoclist, + int *pnDoclist +){ + int rc; + assert( pCsr->eEvalmode==FTS3_EVAL_NEXT ); + assert( pExpr->eType==FTSQUERY_PHRASE && pExpr->pPhrase ); + pCsr->eEvalmode = FTS3_EVAL_MATCHINFO; + rc = fts3EvalExpr(pCsr, pExpr, paDoclist, pnDoclist, 1); + pCsr->eEvalmode = FTS3_EVAL_NEXT; + return rc; } /* @@ -2348,9 +3106,16 @@ char *sqlite3Fts3FindPositions( assert( pExpr->isLoaded ); if( pExpr->aDoclist ){ char *pEnd = &pExpr->aDoclist[pExpr->nDoclist]; - char *pCsr = pExpr->pCurrent; + char *pCsr; + if( pExpr->pCurrent==0 ){ + pExpr->pCurrent = pExpr->aDoclist; + pExpr->iCurrent = 0; + pExpr->pCurrent += sqlite3Fts3GetVarint(pExpr->pCurrent,&pExpr->iCurrent); + } + pCsr = pExpr->pCurrent; assert( pCsr ); + while( pCsriCurrent1 ){ + zArg = (const char *)sqlite3_value_text(apVal[1]); + } + sqlite3Fts3Matchinfo(pContext, pCsr, zArg); } } @@ -2581,21 +3344,25 @@ static int fts3RenameMethod( const char *zName /* New name of table */ ){ Fts3Table *p = (Fts3Table *)pVtab; - sqlite3 *db; /* Database connection */ + sqlite3 *db = p->db; /* Database connection */ int rc; /* Return Code */ - - db = p->db; - rc = SQLITE_OK; + + rc = sqlite3Fts3PendingTermsFlush(p); + if( rc!=SQLITE_OK ){ + return rc; + } + fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';", p->zDb, p->zName, zName ); - if( rc==SQLITE_ERROR ) rc = SQLITE_OK; if( p->bHasDocsize ){ fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_docsize' RENAME TO '%q_docsize';", p->zDb, p->zName, zName ); + } + if( p->bHasStat ){ fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_stat' RENAME TO '%q_stat';", p->zDb, p->zName, zName @@ -2620,7 +3387,7 @@ static const sqlite3_module fts3Module = { /* xDisconnect */ fts3DisconnectMethod, /* xDestroy */ fts3DestroyMethod, /* xOpen */ fts3OpenMethod, - /* xClose */ fulltextClose, + /* xClose */ fts3CloseMethod, /* xFilter */ fts3FilterMethod, /* xNext */ fts3NextMethod, /* xEof */ fts3EofMethod, @@ -2647,19 +3414,20 @@ static void hashDestroy(void *p){ } /* -** The fts3 built-in tokenizers - "simple" and "porter" - are implemented -** in files fts3_tokenizer1.c and fts3_porter.c respectively. The following -** two forward declarations are for functions declared in these files -** used to retrieve the respective implementations. +** The fts3 built-in tokenizers - "simple", "porter" and "icu"- are +** implemented in files fts3_tokenizer1.c, fts3_porter.c and fts3_icu.c +** respectively. The following three forward declarations are for functions +** declared in these files used to retrieve the respective implementations. ** ** Calling sqlite3Fts3SimpleTokenizerModule() sets the value pointed ** to by the argument to point to the "simple" tokenizer implementation. -** Function ...PorterTokenizerModule() sets *pModule to point to the -** porter tokenizer/stemmer implementation. +** And so on. */ void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule); void sqlite3Fts3PorterTokenizerModule(sqlite3_tokenizer_module const**ppModule); +#ifdef SQLITE_ENABLE_ICU void sqlite3Fts3IcuTokenizerModule(sqlite3_tokenizer_module const**ppModule); +#endif /* ** Initialise the fts3 extension. If this extension is built as part @@ -2715,7 +3483,8 @@ int sqlite3Fts3Init(sqlite3 *db){ && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer")) && SQLITE_OK==(rc = sqlite3_overload_function(db, "snippet", -1)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "offsets", 1)) - && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", -1)) + && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 1)) + && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 2)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "optimize", 1)) ){ rc = sqlite3_create_module_v2( diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h index c5be598e..08754432 100644 --- a/ext/fts3/fts3Int.h +++ b/ext/fts3/fts3Int.h @@ -77,8 +77,14 @@ ** Macros indicating that conditional expressions are always true or ** false. */ +#ifdef SQLITE_COVERAGE_TEST +# define ALWAYS(x) (1) +# define NEVER(X) (0) +#else # define ALWAYS(x) (x) # define NEVER(X) (x) +#endif + /* ** Internal types used by SQLite. */ @@ -96,8 +102,12 @@ typedef struct Fts3Table Fts3Table; typedef struct Fts3Cursor Fts3Cursor; typedef struct Fts3Expr Fts3Expr; typedef struct Fts3Phrase Fts3Phrase; -typedef struct Fts3SegReader Fts3SegReader; +typedef struct Fts3PhraseToken Fts3PhraseToken; + typedef struct Fts3SegFilter Fts3SegFilter; +typedef struct Fts3DeferredToken Fts3DeferredToken; +typedef struct Fts3SegReader Fts3SegReader; +typedef struct Fts3SegReaderArray Fts3SegReaderArray; /* ** A connection to a fulltext index is an instance of the following @@ -118,22 +128,14 @@ struct Fts3Table { /* Precompiled statements used by the implementation. Each of these ** statements is run and reset within a single virtual table API call. */ - sqlite3_stmt *aStmt[25]; - - /* Pointer to string containing the SQL: - ** - ** "SELECT block FROM %_segments WHERE blockid BETWEEN ? AND ? - ** ORDER BY blockid" - */ - char *zSelectLeaves; - int nLeavesStmt; /* Valid statements in aLeavesStmt */ - int nLeavesTotal; /* Total number of prepared leaves stmts */ - int nLeavesAlloc; /* Allocated size of aLeavesStmt */ - sqlite3_stmt **aLeavesStmt; /* Array of prepared zSelectLeaves stmts */ + sqlite3_stmt *aStmt[24]; int nNodeSize; /* Soft limit for node size */ - u8 bHasContent; /* True if %_content table exists */ + u8 bHasStat; /* True if %_stat table exists */ u8 bHasDocsize; /* True if %_docsize table exists */ + int nPgsz; /* Page size for host database */ + char *zSegmentsTbl; /* Name of %_segments table */ + sqlite3_blob *pSegments; /* Blob handle open on %_segments table */ /* The following hash table is used to buffer pending index updates during ** transactions. Variable nPendingData estimates the memory size of the @@ -160,14 +162,25 @@ struct Fts3Cursor { u8 isRequireSeek; /* True if must seek pStmt to %_content row */ sqlite3_stmt *pStmt; /* Prepared statement in use by the cursor */ Fts3Expr *pExpr; /* Parsed MATCH query string */ + int nPhrase; /* Number of matchable phrases in query */ + Fts3DeferredToken *pDeferred; /* Deferred search tokens, if any */ sqlite3_int64 iPrevId; /* Previous id read from aDoclist */ char *pNextId; /* Pointer into the body of aDoclist */ char *aDoclist; /* List of docids for full-text queries */ int nDoclist; /* Size of buffer at aDoclist */ + int eEvalmode; /* An FTS3_EVAL_XX constant */ + int nRowAvg; /* Average size of database rows, in pages */ + 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 */ }; +#define FTS3_EVAL_FILTER 0 +#define FTS3_EVAL_NEXT 1 +#define FTS3_EVAL_MATCHINFO 2 + /* ** The Fts3Cursor.eSearch member is always set to one of the following. ** Actualy, Fts3Cursor.eSearch can be greater than or equal to @@ -190,18 +203,30 @@ struct Fts3Cursor { /* ** A "phrase" is a sequence of one or more tokens that must match in ** sequence. A single token is the base case and the most common case. -** For a sequence of tokens contained in "...", nToken will be the number -** of tokens in the string. +** For a sequence of tokens contained in double-quotes (i.e. "one two three") +** nToken will be the number of tokens in the string. +** +** The nDocMatch and nMatch variables contain data that may be used by the +** matchinfo() function. They are populated when the full-text index is +** queried for hits on the phrase. If one or more tokens in the phrase +** are deferred, the nDocMatch and nMatch variables are populated based +** on the assumption that the */ +struct Fts3PhraseToken { + char *z; /* Text of the token */ + int n; /* Number of bytes in buffer z */ + int isPrefix; /* True if token ends with a "*" character */ + int bFulltext; /* True if full-text index was used */ + Fts3SegReaderArray *pArray; /* Segment-reader for this token */ + Fts3DeferredToken *pDeferred; /* Deferred token object for this token */ +}; + struct Fts3Phrase { + /* Variables populated by fts3_expr.c when parsing a MATCH expression */ int nToken; /* Number of tokens in the phrase */ int iColumn; /* Index of column this phrase must match */ int isNot; /* Phrase prefixed by unary not (-) operator */ - struct PhraseToken { - char *z; /* Text of the token */ - int n; /* Number of bytes in buffer pointed to by z */ - int isPrefix; /* True if token ends in with a "*" character */ - } aToken[1]; /* One entry for each token in the phrase */ + Fts3PhraseToken aToken[1]; /* One entry for each token in the phrase */ }; /* @@ -251,28 +276,34 @@ struct Fts3Expr { #define FTSQUERY_PHRASE 5 -/* fts3_init.c */ -int sqlite3Fts3DeleteVtab(int, sqlite3_vtab *); -int sqlite3Fts3InitVtab(int, sqlite3*, void*, int, const char*const*, - sqlite3_vtab **, char **); - /* fts3_write.c */ int sqlite3Fts3UpdateMethod(sqlite3_vtab*,int,sqlite3_value**,sqlite3_int64*); int sqlite3Fts3PendingTermsFlush(Fts3Table *); void sqlite3Fts3PendingTermsClear(Fts3Table *); int sqlite3Fts3Optimize(Fts3Table *); -int sqlite3Fts3SegReaderNew(Fts3Table *,int, sqlite3_int64, +int sqlite3Fts3SegReaderNew(int, sqlite3_int64, sqlite3_int64, sqlite3_int64, const char *, int, Fts3SegReader**); int sqlite3Fts3SegReaderPending(Fts3Table*,const char*,int,int,Fts3SegReader**); -void sqlite3Fts3SegReaderFree(Fts3Table *, Fts3SegReader *); +void sqlite3Fts3SegReaderFree(Fts3SegReader *); int sqlite3Fts3SegReaderIterate( Fts3Table *, Fts3SegReader **, int, Fts3SegFilter *, int (*)(Fts3Table *, void *, char *, int, char *, int), void * ); -int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char const**, int*); +int sqlite3Fts3SegReaderCost(Fts3Cursor *, Fts3SegReader *, int *); int sqlite3Fts3AllSegdirs(Fts3Table*, sqlite3_stmt **); -int sqlite3Fts3MatchinfoDocsizeLocal(Fts3Cursor*, u32*); -int sqlite3Fts3MatchinfoDocsizeGlobal(Fts3Cursor*, u32*); +int sqlite3Fts3ReadLock(Fts3Table *); +int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char **, int*); + +int sqlite3Fts3SelectDoctotal(Fts3Table *, sqlite3_stmt **); +int sqlite3Fts3SelectDocsize(Fts3Table *, sqlite3_int64, sqlite3_stmt **); + +void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *); +int sqlite3Fts3DeferToken(Fts3Cursor *, Fts3PhraseToken *, int); +int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *); +void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *); +char *sqlite3Fts3DeferredDoclist(Fts3DeferredToken *, int *); + +void sqlite3Fts3SegmentsClose(Fts3Table *); /* Flags allowed as part of the 4th argument to SegmentReaderIterate() */ #define FTS3_SEGMENT_REQUIRE_POS 0x00000001 @@ -296,22 +327,24 @@ int sqlite3Fts3VarintLen(sqlite3_uint64); void sqlite3Fts3Dequote(char *); char *sqlite3Fts3FindPositions(Fts3Expr *, sqlite3_int64, int); -int sqlite3Fts3ExprLoadDoclist(Fts3Table *, Fts3Expr *); +int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *, Fts3Expr *); +int sqlite3Fts3ExprLoadFtDoclist(Fts3Cursor *, Fts3Expr *, char **, int *); int sqlite3Fts3ExprNearTrim(Fts3Expr *, Fts3Expr *, int); /* fts3_tokenizer.c */ const char *sqlite3Fts3NextToken(const char *, int *); int sqlite3Fts3InitHashTable(sqlite3 *, Fts3Hash *, const char *); -int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, - const char *, sqlite3_tokenizer **, const char **, char ** +int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *, + sqlite3_tokenizer **, char ** ); +int sqlite3Fts3IsIdChar(char); /* fts3_snippet.c */ void sqlite3Fts3Offsets(sqlite3_context*, Fts3Cursor*); void sqlite3Fts3Snippet(sqlite3_context *, Fts3Cursor *, const char *, const char *, const char *, int, int ); -void sqlite3Fts3Matchinfo(sqlite3_context *, Fts3Cursor *); +void sqlite3Fts3Matchinfo(sqlite3_context *, Fts3Cursor *, const char *); /* fts3_expr.c */ int sqlite3Fts3ExprParse(sqlite3_tokenizer *, diff --git a/ext/fts3/fts3_expr.c b/ext/fts3/fts3_expr.c index 008ba814..43f6d84a 100644 --- a/ext/fts3/fts3_expr.c +++ b/ext/fts3/fts3_expr.c @@ -106,6 +106,18 @@ static int fts3isspace(char c){ return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f'; } +/* +** Allocate nByte bytes of memory using sqlite3_malloc(). If successful, +** zero the memory before returning a pointer to it. If unsuccessful, +** return NULL. +*/ +static void *fts3MallocZero(int nByte){ + void *pRet = sqlite3_malloc(nByte); + if( pRet ) memset(pRet, 0, nByte); + return pRet; +} + + /* ** Extract the next token from buffer z (length n) using the tokenizer ** and other information (column names etc.) in pParse. Create an Fts3Expr @@ -143,11 +155,10 @@ static int getNextToken( if( rc==SQLITE_OK ){ nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase) + nToken; - pRet = (Fts3Expr *)sqlite3_malloc(nByte); + pRet = (Fts3Expr *)fts3MallocZero(nByte); if( !pRet ){ rc = SQLITE_NOMEM; }else{ - memset(pRet, 0, nByte); pRet->eType = FTSQUERY_PHRASE; pRet->pPhrase = (Fts3Phrase *)&pRet[1]; pRet->pPhrase->nToken = 1; @@ -223,7 +234,7 @@ static int getNextString( rc = pModule->xNext(pCursor, &zToken, &nToken, &iBegin, &iEnd, &iPos); if( rc==SQLITE_OK ){ int nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase); - p = fts3ReallocOrFree(p, nByte+ii*sizeof(struct PhraseToken)); + p = fts3ReallocOrFree(p, nByte+ii*sizeof(Fts3PhraseToken)); zTemp = fts3ReallocOrFree(zTemp, nTemp + nToken); if( !p || !zTemp ){ goto no_mem; @@ -233,6 +244,7 @@ static int getNextString( p->pPhrase = (Fts3Phrase *)&p[1]; } p->pPhrase = (Fts3Phrase *)&p[1]; + memset(&p->pPhrase->aToken[ii], 0, sizeof(Fts3PhraseToken)); p->pPhrase->nToken = ii+1; p->pPhrase->aToken[ii].n = nToken; memcpy(&zTemp[nTemp], zToken, nToken); @@ -254,7 +266,7 @@ static int getNextString( char *zNew = NULL; int nNew = 0; int nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase); - nByte += (p?(p->pPhrase->nToken-1):0) * sizeof(struct PhraseToken); + nByte += (p?(p->pPhrase->nToken-1):0) * sizeof(Fts3PhraseToken); p = fts3ReallocOrFree(p, nByte + nTemp); if( !p ){ goto no_mem; @@ -372,11 +384,10 @@ static int getNextNode( if( fts3isspace(cNext) || cNext=='"' || cNext=='(' || cNext==')' || cNext==0 ){ - pRet = (Fts3Expr *)sqlite3_malloc(sizeof(Fts3Expr)); + pRet = (Fts3Expr *)fts3MallocZero(sizeof(Fts3Expr)); if( !pRet ){ return SQLITE_NOMEM; } - memset(pRet, 0, sizeof(Fts3Expr)); pRet->eType = pKey->eType; pRet->nNear = nNear; *ppExpr = pRet; @@ -394,7 +405,6 @@ static int getNextNode( if( sqlite3_fts3_enable_parentheses ){ if( *zInput=='(' ){ int nConsumed; - int rc; pParse->nNest++; rc = fts3ExprParse(pParse, &zInput[1], nInput-1, ppExpr, &nConsumed); if( rc==SQLITE_OK && !*ppExpr ){ @@ -552,13 +562,12 @@ static int fts3ExprParse( && p->eType==FTSQUERY_PHRASE && p->pPhrase->isNot ){ /* Create an implicit NOT operator. */ - Fts3Expr *pNot = sqlite3_malloc(sizeof(Fts3Expr)); + Fts3Expr *pNot = fts3MallocZero(sizeof(Fts3Expr)); if( !pNot ){ sqlite3Fts3ExprFree(p); rc = SQLITE_NOMEM; goto exprparse_out; } - memset(pNot, 0, sizeof(Fts3Expr)); pNot->eType = FTSQUERY_NOT; pNot->pRight = p; if( pNotBranch ){ @@ -586,13 +595,12 @@ static int fts3ExprParse( /* Insert an implicit AND operator. */ Fts3Expr *pAnd; assert( pRet && pPrev ); - pAnd = sqlite3_malloc(sizeof(Fts3Expr)); + pAnd = fts3MallocZero(sizeof(Fts3Expr)); if( !pAnd ){ sqlite3Fts3ExprFree(p); rc = SQLITE_NOMEM; goto exprparse_out; } - memset(pAnd, 0, sizeof(Fts3Expr)); pAnd->eType = FTSQUERY_AND; insertBinaryOperator(&pRet, pPrev, pAnd); pPrev = pAnd; @@ -777,47 +785,53 @@ static int queryTestTokenizer( } /* -** This function is part of the test interface for the query parser. It -** writes a text representation of the query expression pExpr into the -** buffer pointed to by argument zBuf. It is assumed that zBuf is large -** enough to store the required text representation. +** Return a pointer to a buffer containing a text representation of the +** expression passed as the first argument. The buffer is obtained from +** sqlite3_malloc(). It is the responsibility of the caller to use +** sqlite3_free() to release the memory. If an OOM condition is encountered, +** NULL is returned. +** +** If the second argument is not NULL, then its contents are prepended to +** the returned expression text and then freed using sqlite3_free(). */ -static void exprToString(Fts3Expr *pExpr, char *zBuf){ +static char *exprToString(Fts3Expr *pExpr, char *zBuf){ switch( pExpr->eType ){ case FTSQUERY_PHRASE: { Fts3Phrase *pPhrase = pExpr->pPhrase; int i; - zBuf += sprintf(zBuf, "PHRASE %d %d", pPhrase->iColumn, pPhrase->isNot); - for(i=0; inToken; i++){ - zBuf += sprintf(zBuf," %.*s",pPhrase->aToken[i].n,pPhrase->aToken[i].z); - zBuf += sprintf(zBuf,"%s", (pPhrase->aToken[i].isPrefix?"+":"")); + zBuf = sqlite3_mprintf( + "%zPHRASE %d %d", zBuf, pPhrase->iColumn, pPhrase->isNot); + for(i=0; zBuf && inToken; i++){ + zBuf = sqlite3_mprintf("%z %.*s%s", zBuf, + pPhrase->aToken[i].n, pPhrase->aToken[i].z, + (pPhrase->aToken[i].isPrefix?"+":"") + ); } - return; + return zBuf; } case FTSQUERY_NEAR: - zBuf += sprintf(zBuf, "NEAR/%d ", pExpr->nNear); + zBuf = sqlite3_mprintf("%zNEAR/%d ", zBuf, pExpr->nNear); break; case FTSQUERY_NOT: - zBuf += sprintf(zBuf, "NOT "); + zBuf = sqlite3_mprintf("%zNOT ", zBuf); break; case FTSQUERY_AND: - zBuf += sprintf(zBuf, "AND "); + zBuf = sqlite3_mprintf("%zAND ", zBuf); break; case FTSQUERY_OR: - zBuf += sprintf(zBuf, "OR "); + zBuf = sqlite3_mprintf("%zOR ", zBuf); break; } - zBuf += sprintf(zBuf, "{"); - exprToString(pExpr->pLeft, zBuf); - zBuf += strlen(zBuf); - zBuf += sprintf(zBuf, "} "); + if( zBuf ) zBuf = sqlite3_mprintf("%z{", zBuf); + if( zBuf ) zBuf = exprToString(pExpr->pLeft, zBuf); + if( zBuf ) zBuf = sqlite3_mprintf("%z} {", zBuf); - zBuf += sprintf(zBuf, "{"); - exprToString(pExpr->pRight, zBuf); - zBuf += strlen(zBuf); - zBuf += sprintf(zBuf, "}"); + if( zBuf ) zBuf = exprToString(pExpr->pRight, zBuf); + if( zBuf ) zBuf = sqlite3_mprintf("%z}", zBuf); + + return zBuf; } /* @@ -848,6 +862,7 @@ static void fts3ExprTest( int nCol; int ii; Fts3Expr *pExpr; + char *zBuf = 0; sqlite3 *db = sqlite3_context_db_handle(context); if( argc<3 ){ @@ -890,18 +905,17 @@ static void fts3ExprTest( rc = sqlite3Fts3ExprParse( pTokenizer, azCol, nCol, nCol, zExpr, nExpr, &pExpr ); - if( rc==SQLITE_NOMEM ){ - sqlite3_result_error_nomem(context); - goto exprtest_out; - }else if( rc==SQLITE_OK ){ - char zBuf[4096]; - exprToString(pExpr, zBuf); - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); - sqlite3Fts3ExprFree(pExpr); - }else{ + if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM ){ sqlite3_result_error(context, "Error parsing expression", -1); + }else if( rc==SQLITE_NOMEM || !(zBuf = exprToString(pExpr, 0)) ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + sqlite3_free(zBuf); } + sqlite3Fts3ExprFree(pExpr); + exprtest_out: if( pModule && pTokenizer ){ rc = pModule->xDestroy(pTokenizer); diff --git a/ext/fts3/fts3_porter.c b/ext/fts3/fts3_porter.c index 5963abc5..27f9cf39 100644 --- a/ext/fts3/fts3_porter.c +++ b/ext/fts3/fts3_porter.c @@ -343,7 +343,7 @@ static void porter_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ int i, j; char zReverse[28]; char *z, *z2; - if( nIn<3 || nIn>=sizeof(zReverse)-7 ){ + if( nIn<3 || nIn>=(int)sizeof(zReverse)-7 ){ /* The word is too big or too small for the porter stemmer. ** Fallback to the copy stemmer */ copy_stemmer(zIn, nIn, zOut, pnOut); diff --git a/ext/fts3/fts3_snippet.c b/ext/fts3/fts3_snippet.c index d67f7ac0..acf17868 100644 --- a/ext/fts3/fts3_snippet.c +++ b/ext/fts3/fts3_snippet.c @@ -17,6 +17,22 @@ #include #include +/* +** Characters that may appear in the second argument to matchinfo(). +*/ +#define FTS3_MATCHINFO_NPHRASE 'p' /* 1 value */ +#define FTS3_MATCHINFO_NCOL 'c' /* 1 value */ +#define FTS3_MATCHINFO_NDOC 'n' /* 1 value */ +#define FTS3_MATCHINFO_AVGLENGTH 'a' /* nCol values */ +#define FTS3_MATCHINFO_LENGTH 'l' /* nCol values */ +#define FTS3_MATCHINFO_LCS 's' /* nCol values */ +#define FTS3_MATCHINFO_HITS 'x' /* 3*nCol*nPhrase values */ + +/* +** The default value for the second argument to matchinfo(). +*/ +#define FTS3_MATCHINFO_DEFAULT "pcx" + /* ** Used as an fts3ExprIterate() context when loading phrase doclists to @@ -24,7 +40,7 @@ */ typedef struct LoadDoclistCtx LoadDoclistCtx; struct LoadDoclistCtx { - Fts3Table *pTab; /* FTS3 Table */ + Fts3Cursor *pCsr; /* FTS3 Cursor */ int nPhrase; /* Number of phrases seen so far */ int nToken; /* Number of tokens seen so far */ }; @@ -70,6 +86,8 @@ typedef struct MatchInfo MatchInfo; struct MatchInfo { Fts3Cursor *pCursor; /* FTS3 Cursor */ int nCol; /* Number of columns in table */ + int nPhrase; /* Number of matchable phrases in query */ + sqlite3_int64 nDoc; /* Number of docs in database */ u32 *aMatchinfo; /* Pre-allocated buffer */ }; @@ -208,7 +226,7 @@ static int fts3ExprNearTrim(Fts3Expr *pExpr){ ** for each phrase into Fts3Expr.aDoclist[]/nDoclist. See also ** fts3ExprLoadDoclists(). */ -static int fts3ExprLoadDoclistsCb1(Fts3Expr *pExpr, int iPhrase, void *ctx){ +static int fts3ExprLoadDoclistsCb(Fts3Expr *pExpr, int iPhrase, void *ctx){ int rc = SQLITE_OK; LoadDoclistCtx *p = (LoadDoclistCtx *)ctx; @@ -218,7 +236,7 @@ static int fts3ExprLoadDoclistsCb1(Fts3Expr *pExpr, int iPhrase, void *ctx){ p->nToken += pExpr->pPhrase->nToken; if( pExpr->isLoaded==0 ){ - rc = sqlite3Fts3ExprLoadDoclist(p->pTab, pExpr); + rc = sqlite3Fts3ExprLoadDoclist(p->pCsr, pExpr); pExpr->isLoaded = 1; if( rc==SQLITE_OK ){ rc = fts3ExprNearTrim(pExpr); @@ -228,22 +246,6 @@ static int fts3ExprLoadDoclistsCb1(Fts3Expr *pExpr, int iPhrase, void *ctx){ return rc; } -/* -** This is an fts3ExprIterate() callback used while loading the doclists -** for each phrase into Fts3Expr.aDoclist[]/nDoclist. See also -** fts3ExprLoadDoclists(). -*/ -static int fts3ExprLoadDoclistsCb2(Fts3Expr *pExpr, int iPhrase, void *ctx){ - UNUSED_PARAMETER(iPhrase); - UNUSED_PARAMETER(ctx); - if( pExpr->aDoclist ){ - pExpr->pCurrent = pExpr->aDoclist; - pExpr->iCurrent = 0; - pExpr->pCurrent += sqlite3Fts3GetVarint(pExpr->pCurrent, &pExpr->iCurrent); - } - return SQLITE_OK; -} - /* ** Load the doclists for each phrase in the query associated with FTS3 cursor ** pCsr. @@ -261,16 +263,25 @@ static int fts3ExprLoadDoclists( ){ int rc; /* Return Code */ LoadDoclistCtx sCtx = {0,0,0}; /* Context for fts3ExprIterate() */ - sCtx.pTab = (Fts3Table *)pCsr->base.pVtab; - rc = fts3ExprIterate(pCsr->pExpr, fts3ExprLoadDoclistsCb1, (void *)&sCtx); - if( rc==SQLITE_OK ){ - (void)fts3ExprIterate(pCsr->pExpr, fts3ExprLoadDoclistsCb2, 0); - } + sCtx.pCsr = pCsr; + rc = fts3ExprIterate(pCsr->pExpr, fts3ExprLoadDoclistsCb, (void *)&sCtx); if( pnPhrase ) *pnPhrase = sCtx.nPhrase; if( pnToken ) *pnToken = sCtx.nToken; return rc; } +static int fts3ExprPhraseCountCb(Fts3Expr *pExpr, int iPhrase, void *ctx){ + (*(int *)ctx)++; + UNUSED_PARAMETER(pExpr); + UNUSED_PARAMETER(iPhrase); + return SQLITE_OK; +} +static int fts3ExprPhraseCount(Fts3Expr *pExpr){ + int nPhrase = 0; + (void)fts3ExprIterate(pExpr, fts3ExprPhraseCountCb, (void *)&nPhrase); + return nPhrase; +} + /* ** Advance the position list iterator specified by the first two ** arguments so that it points to the first element with a value greater @@ -783,38 +794,87 @@ static void fts3LoadColumnlistCounts(char **pp, u32 *aOut, int isGlobal){ /* ** fts3ExprIterate() callback used to collect the "global" matchinfo stats -** for a single query. The "global" stats are those elements of the matchinfo -** array that are constant for all rows returned by the current query. +** for a single query. +** +** fts3ExprIterate() callback to load the 'global' elements of a +** FTS3_MATCHINFO_HITS matchinfo array. The global stats are those elements +** of the matchinfo array that are constant for all rows returned by the +** current query. +** +** Argument pCtx is actually a pointer to a struct of type MatchInfo. This +** function populates Matchinfo.aMatchinfo[] as follows: +** +** for(iCol=0; iColpCursor; + char *pIter; char *pEnd; - const int iStart = 2 + (iPhrase * p->nCol * 3) + 1; + char *pFree = 0; + u32 *aOut = &p->aMatchinfo[3*iPhrase*p->nCol]; assert( pExpr->isLoaded ); + assert( pExpr->eType==FTSQUERY_PHRASE ); - /* Fill in the global hit count matrix row for this phrase. */ - pCsr = pExpr->aDoclist; - pEnd = &pExpr->aDoclist[pExpr->nDoclist]; - while( pCsraMatchinfo[iStart], 1); + if( pCsr->pDeferred ){ + Fts3Phrase *pPhrase = pExpr->pPhrase; + int ii; + for(ii=0; iinToken; ii++){ + if( pPhrase->aToken[ii].bFulltext ) break; + } + if( iinToken ){ + int nFree = 0; + int rc = sqlite3Fts3ExprLoadFtDoclist(pCsr, pExpr, &pFree, &nFree); + if( rc!=SQLITE_OK ) return rc; + pIter = pFree; + pEnd = &pFree[nFree]; + }else{ + int iCol; /* Column index */ + for(iCol=0; iColnCol; iCol++){ + aOut[iCol*3 + 1] = (u32)p->nDoc; + aOut[iCol*3 + 2] = (u32)p->nDoc; + } + return SQLITE_OK; + } + }else{ + pIter = pExpr->aDoclist; + pEnd = &pExpr->aDoclist[pExpr->nDoclist]; } + /* Fill in the global hit count matrix row for this phrase. */ + while( pIteraDoclist ){ char *pCsr; - int iStart = 2 + (iPhrase * p->nCol * 3); + int iStart = iPhrase * p->nCol * 3; int i; for(i=0; inCol; i++) p->aMatchinfo[iStart+i*3] = 0; @@ -837,67 +897,400 @@ static int fts3ExprLocalMatchinfoCb( return SQLITE_OK; } +static int fts3MatchinfoCheck( + Fts3Table *pTab, + char cArg, + char **pzErr +){ + if( (cArg==FTS3_MATCHINFO_NPHRASE) + || (cArg==FTS3_MATCHINFO_NCOL) + || (cArg==FTS3_MATCHINFO_NDOC && pTab->bHasStat) + || (cArg==FTS3_MATCHINFO_AVGLENGTH && pTab->bHasStat) + || (cArg==FTS3_MATCHINFO_LENGTH && pTab->bHasDocsize) + || (cArg==FTS3_MATCHINFO_LCS) + || (cArg==FTS3_MATCHINFO_HITS) + ){ + return SQLITE_OK; + } + *pzErr = sqlite3_mprintf("unrecognized matchinfo request: %c", cArg); + return SQLITE_ERROR; +} + +static int fts3MatchinfoSize(MatchInfo *pInfo, char cArg){ + int nVal; /* Number of integers output by cArg */ + + switch( cArg ){ + case FTS3_MATCHINFO_NDOC: + case FTS3_MATCHINFO_NPHRASE: + case FTS3_MATCHINFO_NCOL: + nVal = 1; + break; + + case FTS3_MATCHINFO_AVGLENGTH: + case FTS3_MATCHINFO_LENGTH: + case FTS3_MATCHINFO_LCS: + nVal = pInfo->nCol; + break; + + default: + assert( cArg==FTS3_MATCHINFO_HITS ); + nVal = pInfo->nCol * pInfo->nPhrase * 3; + break; + } + + return nVal; +} + +static int fts3MatchinfoSelectDoctotal( + Fts3Table *pTab, + sqlite3_stmt **ppStmt, + sqlite3_int64 *pnDoc, + const char **paLen +){ + sqlite3_stmt *pStmt; + const char *a; + sqlite3_int64 nDoc; + + if( !*ppStmt ){ + int rc = sqlite3Fts3SelectDoctotal(pTab, ppStmt); + if( rc!=SQLITE_OK ) return rc; + } + pStmt = *ppStmt; + assert( sqlite3_data_count(pStmt)==1 ); + + a = sqlite3_column_blob(pStmt, 0); + a += sqlite3Fts3GetVarint(a, &nDoc); + *pnDoc = (u32)nDoc; + + if( paLen ) *paLen = a; + return SQLITE_OK; +} + +/* +** An instance of the following structure is used to store state while +** iterating through a multi-column position-list corresponding to the +** hits for a single phrase on a single row in order to calculate the +** values for a matchinfo() FTS3_MATCHINFO_LCS request. +*/ +typedef struct LcsIterator LcsIterator; +struct LcsIterator { + Fts3Expr *pExpr; /* Pointer to phrase expression */ + char *pRead; /* Cursor used to iterate through aDoclist */ + int iPosOffset; /* Tokens count up to end of this phrase */ + int iCol; /* Current column number */ + int iPos; /* Current position */ +}; + +/* +** If LcsIterator.iCol is set to the following value, the iterator has +** finished iterating through all offsets for all columns. +*/ +#define LCS_ITERATOR_FINISHED 0x7FFFFFFF; + +static int fts3MatchinfoLcsCb( + Fts3Expr *pExpr, /* Phrase expression node */ + int iPhrase, /* Phrase number (numbered from zero) */ + void *pCtx /* Pointer to MatchInfo structure */ +){ + LcsIterator *aIter = (LcsIterator *)pCtx; + aIter[iPhrase].pExpr = pExpr; + return SQLITE_OK; +} + +/* +** Advance the iterator passed as an argument to the next position. Return +** 1 if the iterator is at EOF or if it now points to the start of the +** position list for the next column. +*/ +static int fts3LcsIteratorAdvance(LcsIterator *pIter){ + char *pRead = pIter->pRead; + sqlite3_int64 iRead; + int rc = 0; + + pRead += sqlite3Fts3GetVarint(pRead, &iRead); + if( iRead==0 ){ + pIter->iCol = LCS_ITERATOR_FINISHED; + rc = 1; + }else{ + if( iRead==1 ){ + pRead += sqlite3Fts3GetVarint(pRead, &iRead); + pIter->iCol = (int)iRead; + pIter->iPos = pIter->iPosOffset; + pRead += sqlite3Fts3GetVarint(pRead, &iRead); + rc = 1; + } + pIter->iPos += (int)(iRead-2); + } + + pIter->pRead = pRead; + return rc; +} + +/* +** This function implements the FTS3_MATCHINFO_LCS matchinfo() flag. +** +** If the call is successful, the longest-common-substring lengths for each +** column are written into the first nCol elements of the pInfo->aMatchinfo[] +** array before returning. SQLITE_OK is returned in this case. +** +** Otherwise, if an error occurs, an SQLite error code is returned and the +** data written to the first nCol elements of pInfo->aMatchinfo[] is +** undefined. +*/ +static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){ + LcsIterator *aIter; + int i; + int iCol; + int nToken = 0; + + /* Allocate and populate the array of LcsIterator objects. The array + ** contains one element for each matchable phrase in the query. + **/ + aIter = sqlite3_malloc(sizeof(LcsIterator) * pCsr->nPhrase); + if( !aIter ) return SQLITE_NOMEM; + memset(aIter, 0, sizeof(LcsIterator) * pCsr->nPhrase); + (void)fts3ExprIterate(pCsr->pExpr, fts3MatchinfoLcsCb, (void*)aIter); + for(i=0; inPhrase; i++){ + LcsIterator *pIter = &aIter[i]; + nToken -= pIter->pExpr->pPhrase->nToken; + pIter->iPosOffset = nToken; + pIter->pRead = sqlite3Fts3FindPositions(pIter->pExpr, pCsr->iPrevId, -1); + if( pIter->pRead ){ + pIter->iPos = pIter->iPosOffset; + fts3LcsIteratorAdvance(&aIter[i]); + }else{ + pIter->iCol = LCS_ITERATOR_FINISHED; + } + } + + for(iCol=0; iColnCol; iCol++){ + int nLcs = 0; /* LCS value for this column */ + int nLive = 0; /* Number of iterators in aIter not at EOF */ + + /* Loop through the iterators in aIter[]. Set nLive to the number of + ** iterators that point to a position-list corresponding to column iCol. + */ + for(i=0; inPhrase; i++){ + assert( aIter[i].iCol>=iCol ); + if( aIter[i].iCol==iCol ) nLive++; + } + + /* The following loop runs until all iterators in aIter[] have finished + ** iterating through positions in column iCol. Exactly one of the + ** iterators is advanced each time the body of the loop is run. + */ + while( nLive>0 ){ + LcsIterator *pAdv = 0; /* The iterator to advance by one position */ + int nThisLcs = 0; /* LCS for the current iterator positions */ + + for(i=0; inPhrase; i++){ + LcsIterator *pIter = &aIter[i]; + if( iCol!=pIter->iCol ){ + /* This iterator is already at EOF for this column. */ + nThisLcs = 0; + }else{ + if( pAdv==0 || pIter->iPosiPos ){ + pAdv = pIter; + } + if( nThisLcs==0 || pIter->iPos==pIter[-1].iPos ){ + nThisLcs++; + }else{ + nThisLcs = 1; + } + if( nThisLcs>nLcs ) nLcs = nThisLcs; + } + } + if( fts3LcsIteratorAdvance(pAdv) ) nLive--; + } + + pInfo->aMatchinfo[iCol] = nLcs; + } + + sqlite3_free(aIter); + return SQLITE_OK; +} + +/* +** Populate the buffer pInfo->aMatchinfo[] with an array of integers to +** be returned by the matchinfo() function. Argument zArg contains the +** format string passed as the second argument to matchinfo (or the +** default value "pcx" if no second argument was specified). The format +** string has already been validated and the pInfo->aMatchinfo[] array +** is guaranteed to be large enough for the output. +** +** If bGlobal is true, then populate all fields of the matchinfo() output. +** If it is false, then assume that those fields that do not change between +** rows (i.e. FTS3_MATCHINFO_NPHRASE, NCOL, NDOC, AVGLENGTH and part of HITS) +** have already been populated. +** +** Return SQLITE_OK if successful, or an SQLite error code if an error +** occurs. If a value other than SQLITE_OK is returned, the state the +** pInfo->aMatchinfo[] buffer is left in is undefined. +*/ +static int fts3MatchinfoValues( + Fts3Cursor *pCsr, /* FTS3 cursor object */ + int bGlobal, /* True to grab the global stats */ + MatchInfo *pInfo, /* Matchinfo context object */ + const char *zArg /* Matchinfo format string */ +){ + int rc = SQLITE_OK; + int i; + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; + sqlite3_stmt *pSelect = 0; + + for(i=0; rc==SQLITE_OK && zArg[i]; i++){ + + switch( zArg[i] ){ + case FTS3_MATCHINFO_NPHRASE: + if( bGlobal ) pInfo->aMatchinfo[0] = pInfo->nPhrase; + break; + + case FTS3_MATCHINFO_NCOL: + if( bGlobal ) pInfo->aMatchinfo[0] = pInfo->nCol; + break; + + case FTS3_MATCHINFO_NDOC: + if( bGlobal ){ + sqlite3_int64 nDoc; + rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, 0); + pInfo->aMatchinfo[0] = (u32)nDoc; + } + break; + + case FTS3_MATCHINFO_AVGLENGTH: + if( bGlobal ){ + sqlite3_int64 nDoc; /* Number of rows in table */ + const char *a; /* Aggregate column length array */ + + rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, &a); + if( rc==SQLITE_OK ){ + int iCol; + for(iCol=0; iColnCol; iCol++){ + sqlite3_int64 nToken; + a += sqlite3Fts3GetVarint(a, &nToken); + pInfo->aMatchinfo[iCol] = (u32)(((u32)(nToken&0xffffffff)+nDoc/2)/nDoc); + } + } + } + break; + + case FTS3_MATCHINFO_LENGTH: { + sqlite3_stmt *pSelectDocsize = 0; + rc = sqlite3Fts3SelectDocsize(pTab, pCsr->iPrevId, &pSelectDocsize); + if( rc==SQLITE_OK ){ + int iCol; + const char *a = sqlite3_column_blob(pSelectDocsize, 0); + for(iCol=0; iColnCol; iCol++){ + sqlite3_int64 nToken; + a += sqlite3Fts3GetVarint(a, &nToken); + pInfo->aMatchinfo[iCol] = (u32)nToken; + } + } + sqlite3_reset(pSelectDocsize); + break; + } + + case FTS3_MATCHINFO_LCS: + rc = fts3ExprLoadDoclists(pCsr, 0, 0); + if( rc==SQLITE_OK ){ + rc = fts3MatchinfoLcs(pCsr, pInfo); + } + break; + + default: { + Fts3Expr *pExpr; + assert( zArg[i]==FTS3_MATCHINFO_HITS ); + pExpr = pCsr->pExpr; + rc = fts3ExprLoadDoclists(pCsr, 0, 0); + if( rc!=SQLITE_OK ) break; + if( bGlobal ){ + if( pCsr->pDeferred ){ + rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &pInfo->nDoc, 0); + if( rc!=SQLITE_OK ) break; + } + rc = fts3ExprIterate(pExpr, fts3ExprGlobalHitsCb,(void*)pInfo); + if( rc!=SQLITE_OK ) break; + } + (void)fts3ExprIterate(pExpr, fts3ExprLocalHitsCb,(void*)pInfo); + break; + } + } + + pInfo->aMatchinfo += fts3MatchinfoSize(pInfo, zArg[i]); + } + + sqlite3_reset(pSelect); + return rc; +} + + /* ** Populate pCsr->aMatchinfo[] with data for the current row. The ** 'matchinfo' data is an array of 32-bit unsigned integers (C type u32). */ -static int fts3GetMatchinfo(Fts3Cursor *pCsr){ +static int fts3GetMatchinfo( + Fts3Cursor *pCsr, /* FTS3 Cursor object */ + const char *zArg /* Second argument to matchinfo() function */ +){ MatchInfo sInfo; Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; int rc = SQLITE_OK; + int bGlobal = 0; /* Collect 'global' stats as well as local */ + memset(&sInfo, 0, sizeof(MatchInfo)); sInfo.pCursor = pCsr; sInfo.nCol = pTab->nColumn; + /* If there is cached matchinfo() data, but the format string for the + ** cache does not match the format string for this request, discard + ** the cached data. */ + if( pCsr->zMatchinfo && strcmp(pCsr->zMatchinfo, zArg) ){ + assert( pCsr->aMatchinfo ); + sqlite3_free(pCsr->aMatchinfo); + pCsr->zMatchinfo = 0; + pCsr->aMatchinfo = 0; + } + + /* If Fts3Cursor.aMatchinfo[] is NULL, then this is the first time the + ** matchinfo function has been called for this query. In this case + ** allocate the array used to accumulate the matchinfo data and + ** initialize those elements that are constant for every row. + */ if( pCsr->aMatchinfo==0 ){ - /* If Fts3Cursor.aMatchinfo[] is NULL, then this is the first time the - ** matchinfo function has been called for this query. In this case - ** allocate the array used to accumulate the matchinfo data and - ** initialize those elements that are constant for every row. - */ - int nPhrase; /* Number of phrases */ - int nMatchinfo; /* Number of u32 elements in match-info */ + int nMatchinfo = 0; /* Number of u32 elements in match-info */ + int nArg; /* Bytes in zArg */ + int i; /* Used to iterate through zArg */ - /* Load doclists for each phrase in the query. */ - rc = fts3ExprLoadDoclists(pCsr, &nPhrase, 0); - if( rc!=SQLITE_OK ){ - return rc; - } - nMatchinfo = 2 + 3*sInfo.nCol*nPhrase; - if( pTab->bHasDocsize ){ - nMatchinfo += 1 + 2*pTab->nColumn; + /* Determine the number of phrases in the query */ + pCsr->nPhrase = fts3ExprPhraseCount(pCsr->pExpr); + sInfo.nPhrase = pCsr->nPhrase; + + /* Determine the number of integers in the buffer returned by this call. */ + for(i=0; zArg[i]; i++){ + nMatchinfo += fts3MatchinfoSize(&sInfo, zArg[i]); } - sInfo.aMatchinfo = (u32 *)sqlite3_malloc(sizeof(u32)*nMatchinfo); - if( !sInfo.aMatchinfo ){ - return SQLITE_NOMEM; - } - memset(sInfo.aMatchinfo, 0, sizeof(u32)*nMatchinfo); + /* Allocate space for Fts3Cursor.aMatchinfo[] and Fts3Cursor.zMatchinfo. */ + nArg = (int)strlen(zArg); + pCsr->aMatchinfo = (u32 *)sqlite3_malloc(sizeof(u32)*nMatchinfo + nArg + 1); + if( !pCsr->aMatchinfo ) return SQLITE_NOMEM; - - /* First element of match-info is the number of phrases in the query */ - sInfo.aMatchinfo[0] = nPhrase; - sInfo.aMatchinfo[1] = sInfo.nCol; - (void)fts3ExprIterate(pCsr->pExpr, fts3ExprGlobalMatchinfoCb,(void*)&sInfo); - if( pTab->bHasDocsize ){ - int ofst = 2 + 3*sInfo.aMatchinfo[0]*sInfo.aMatchinfo[1]; - rc = sqlite3Fts3MatchinfoDocsizeGlobal(pCsr, &sInfo.aMatchinfo[ofst]); - } - pCsr->aMatchinfo = sInfo.aMatchinfo; + pCsr->zMatchinfo = (char *)&pCsr->aMatchinfo[nMatchinfo]; + pCsr->nMatchinfo = nMatchinfo; + memcpy(pCsr->zMatchinfo, zArg, nArg+1); + memset(pCsr->aMatchinfo, 0, sizeof(u32)*nMatchinfo); pCsr->isMatchinfoNeeded = 1; + bGlobal = 1; } sInfo.aMatchinfo = pCsr->aMatchinfo; - if( rc==SQLITE_OK && pCsr->isMatchinfoNeeded ){ - (void)fts3ExprIterate(pCsr->pExpr, fts3ExprLocalMatchinfoCb, (void*)&sInfo); - if( pTab->bHasDocsize ){ - int ofst = 2 + 3*sInfo.aMatchinfo[0]*sInfo.aMatchinfo[1]; - rc = sqlite3Fts3MatchinfoDocsizeLocal(pCsr, &sInfo.aMatchinfo[ofst]); - } + sInfo.nPhrase = pCsr->nPhrase; + if( pCsr->isMatchinfoNeeded ){ + rc = fts3MatchinfoValues(pCsr, bGlobal, &sInfo, zArg); pCsr->isMatchinfoNeeded = 0; } - return SQLITE_OK; + return rc; } /* @@ -958,7 +1351,7 @@ void sqlite3Fts3Snippet( ** columns of the FTS3 table. Otherwise, only column iCol is considered. */ for(iRead=0; iReadnColumn; iRead++){ - SnippetFragment sF; + SnippetFragment sF = {0, 0, 0, 0}; int iS; if( iCol>=0 && iRead!=iCol ) continue; @@ -992,6 +1385,7 @@ void sqlite3Fts3Snippet( } snippet_out: + sqlite3Fts3SegmentsClose(pTab); if( rc!=SQLITE_OK ){ sqlite3_result_error_code(pCtx, rc); sqlite3_free(res.z); @@ -1171,6 +1565,7 @@ void sqlite3Fts3Offsets( offsets_out: sqlite3_free(sCtx.aTerm); assert( rc!=SQLITE_DONE ); + sqlite3Fts3SegmentsClose(pTab); if( rc!=SQLITE_OK ){ sqlite3_result_error_code(pCtx, rc); sqlite3_free(res.z); @@ -1183,21 +1578,43 @@ void sqlite3Fts3Offsets( /* ** Implementation of matchinfo() function. */ -void sqlite3Fts3Matchinfo(sqlite3_context *pContext, Fts3Cursor *pCsr){ +void sqlite3Fts3Matchinfo( + sqlite3_context *pContext, /* Function call context */ + Fts3Cursor *pCsr, /* FTS3 table cursor */ + const char *zArg /* Second arg to matchinfo() function */ +){ + Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; int rc; + int i; + const char *zFormat; + + if( zArg ){ + for(i=0; zArg[i]; i++){ + char *zErr = 0; + if( fts3MatchinfoCheck(pTab, zArg[i], &zErr) ){ + sqlite3_result_error(pContext, zErr, -1); + sqlite3_free(zErr); + return; + } + } + zFormat = zArg; + }else{ + zFormat = FTS3_MATCHINFO_DEFAULT; + } + if( !pCsr->pExpr ){ sqlite3_result_blob(pContext, "", 0, SQLITE_STATIC); return; } - rc = fts3GetMatchinfo(pCsr); + + /* Retrieve matchinfo() data. */ + rc = fts3GetMatchinfo(pCsr, zFormat); + sqlite3Fts3SegmentsClose(pTab); + if( rc!=SQLITE_OK ){ sqlite3_result_error_code(pContext, rc); }else{ - Fts3Table *pTab = (Fts3Table*)pCsr->base.pVtab; - int n = sizeof(u32)*(2+pCsr->aMatchinfo[0]*pCsr->aMatchinfo[1]*3); - if( pTab->bHasDocsize ){ - n += sizeof(u32)*(1 + 2*pTab->nColumn); - } + int n = pCsr->nMatchinfo * sizeof(u32); sqlite3_result_blob(pContext, pCsr->aMatchinfo, n, SQLITE_TRANSIENT); } } diff --git a/ext/fts3/fts3_tokenizer.c b/ext/fts3/fts3_tokenizer.c index 54a91bfe..aa289334 100644 --- a/ext/fts3/fts3_tokenizer.c +++ b/ext/fts3/fts3_tokenizer.c @@ -97,7 +97,7 @@ static void scalarFunc( sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT); } -static int fts3IsIdChar(char c){ +int sqlite3Fts3IsIdChar(char c){ static const char isFtsIdChar[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */ @@ -135,9 +135,9 @@ const char *sqlite3Fts3NextToken(const char *zStr, int *pn){ break; default: - if( fts3IsIdChar(*z1) ){ + if( sqlite3Fts3IsIdChar(*z1) ){ z2 = &z1[1]; - while( fts3IsIdChar(*z2) ) z2++; + while( sqlite3Fts3IsIdChar(*z2) ) z2++; }else{ z1++; } @@ -150,9 +150,8 @@ const char *sqlite3Fts3NextToken(const char *zStr, int *pn){ int sqlite3Fts3InitTokenizer( Fts3Hash *pHash, /* Tokenizer hash table */ - const char *zArg, /* Possible tokenizer specification */ + const char *zArg, /* Tokenizer name */ sqlite3_tokenizer **ppTok, /* OUT: Tokenizer (if applicable) */ - const char **pzTokenizer, /* OUT: Set to zArg if is tokenizer */ char **pzErr /* OUT: Set to malloced error message */ ){ int rc; @@ -162,26 +161,15 @@ int sqlite3Fts3InitTokenizer( char *zEnd; /* Pointer to nul-term of zCopy */ sqlite3_tokenizer_module *m; - if( !z ){ - zCopy = sqlite3_mprintf("simple"); - }else{ - if( sqlite3_strnicmp(z, "tokenize", 8) || fts3IsIdChar(z[8])){ - return SQLITE_OK; - } - zCopy = sqlite3_mprintf("%s", &z[8]); - *pzTokenizer = zArg; - } - if( !zCopy ){ - return SQLITE_NOMEM; - } - + zCopy = sqlite3_mprintf("%s", zArg); + if( !zCopy ) return SQLITE_NOMEM; zEnd = &zCopy[strlen(zCopy)]; z = (char *)sqlite3Fts3NextToken(zCopy, &n); z[n] = '\0'; sqlite3Fts3Dequote(z); - m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, z, (int)strlen(z)+1); + m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash,z,(int)strlen(z)+1); if( !m ){ *pzErr = sqlite3_mprintf("unknown tokenizer: %s", z); rc = SQLITE_ERROR; @@ -477,15 +465,23 @@ int sqlite3Fts3InitHashTable( } #endif - if( SQLITE_OK!=rc - || SQLITE_OK!=(rc = sqlite3_create_function(db, zName, 1, any, p, scalarFunc, 0, 0)) - || SQLITE_OK!=(rc = sqlite3_create_function(db, zName, 2, any, p, scalarFunc, 0, 0)) + if( SQLITE_OK==rc ){ + rc = sqlite3_create_function(db, zName, 1, any, p, scalarFunc, 0, 0); + } + if( SQLITE_OK==rc ){ + rc = sqlite3_create_function(db, zName, 2, any, p, scalarFunc, 0, 0); + } #ifdef SQLITE_TEST - || SQLITE_OK!=(rc = sqlite3_create_function(db, zTest, 2, any, p, testFunc, 0, 0)) - || SQLITE_OK!=(rc = sqlite3_create_function(db, zTest, 3, any, p, testFunc, 0, 0)) - || SQLITE_OK!=(rc = sqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0)) + if( SQLITE_OK==rc ){ + rc = sqlite3_create_function(db, zTest, 2, any, p, testFunc, 0, 0); + } + if( SQLITE_OK==rc ){ + rc = sqlite3_create_function(db, zTest, 3, any, p, testFunc, 0, 0); + } + if( SQLITE_OK==rc ){ + rc = sqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0); + } #endif - ); #ifdef SQLITE_TEST sqlite3_free(zTest); diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index cc3c362c..61def999 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -24,6 +24,18 @@ #include #include +/* +** When full-text index nodes are loaded from disk, the buffer that they +** are loaded into has the following number of bytes of padding at the end +** of it. i.e. if a full-text index node is 900 bytes in size, then a buffer +** of 920 bytes is allocated for it. +** +** This means that if we have a pointer into a buffer containing node data, +** it is always safe to read up to two varints from it without risking an +** overread, even if the node data is corrupted. +*/ +#define FTS3_NODE_PADDING (FTS3_VARINT_MAX*2) + typedef struct PendingList PendingList; typedef struct SegmentNode SegmentNode; typedef struct SegmentWriter SegmentWriter; @@ -42,6 +54,17 @@ struct PendingList { sqlite3_int64 iLastPos; }; + +/* +** Each cursor has a (possibly empty) linked list of the following objects. +*/ +struct Fts3DeferredToken { + Fts3PhraseToken *pToken; /* Pointer to corresponding expr token */ + int iCol; /* Column token must occur in */ + Fts3DeferredToken *pNext; /* Next in list of deferred tokens */ + PendingList *pList; /* Doclist is assembled here */ +}; + /* ** An instance of this structure is used to iterate through the terms on ** a contiguous set of segment b-tree leaf nodes. Although the details of @@ -51,6 +74,7 @@ struct PendingList { ** ** sqlite3Fts3SegReaderNew() ** sqlite3Fts3SegReaderFree() +** sqlite3Fts3SegReaderCost() ** sqlite3Fts3SegReaderIterate() ** ** Methods used to manipulate Fts3SegReader structures: @@ -61,12 +85,14 @@ struct PendingList { */ struct Fts3SegReader { int iIdx; /* Index within level, or 0x7FFFFFFF for PT */ - sqlite3_int64 iStartBlock; - sqlite3_int64 iEndBlock; - sqlite3_stmt *pStmt; /* SQL Statement to access leaf nodes */ + + sqlite3_int64 iStartBlock; /* Rowid of first leaf block to traverse */ + sqlite3_int64 iLeafEndBlock; /* Rowid of final leaf block to traverse */ + sqlite3_int64 iEndBlock; /* Rowid of final block in segment (or 0) */ + sqlite3_int64 iCurrentBlock; /* Current leaf block (or 0) */ + char *aNode; /* Pointer to node data (or NULL) */ int nNode; /* Size of buffer at aNode (or 0) */ - int nTermAlloc; /* Allocated size of zTerm buffer */ Fts3HashElem **ppNextElem; /* Variables set by fts3SegReaderNext(). These may be read directly @@ -76,6 +102,7 @@ struct Fts3SegReader { */ int nTerm; /* Number of bytes in current term */ char *zTerm; /* Pointer to current term */ + int nTermAlloc; /* Allocated size of zTerm buffer */ char *aDoclist; /* Pointer to doclist of current entry */ int nDoclist; /* Size of doclist in current entry */ @@ -85,6 +112,7 @@ struct Fts3SegReader { }; #define fts3SegReaderIsPending(p) ((p)->ppNextElem!=0) +#define fts3SegReaderIsRootOnly(p) ((p)->aNode==(char *)&(p)[1]) /* ** An instance of this structure is used to create a segment b-tree in the @@ -153,12 +181,11 @@ struct SegmentNode { #define SQL_DELETE_SEGDIR_BY_LEVEL 16 #define SQL_DELETE_SEGMENTS_RANGE 17 #define SQL_CONTENT_INSERT 18 -#define SQL_GET_BLOCK 19 -#define SQL_DELETE_DOCSIZE 20 -#define SQL_REPLACE_DOCSIZE 21 -#define SQL_SELECT_DOCSIZE 22 -#define SQL_SELECT_DOCTOTAL 23 -#define SQL_REPLACE_DOCTOTAL 24 +#define SQL_DELETE_DOCSIZE 19 +#define SQL_REPLACE_DOCSIZE 20 +#define SQL_SELECT_DOCSIZE 21 +#define SQL_SELECT_DOCTOTAL 22 +#define SQL_REPLACE_DOCTOTAL 23 /* ** This function is used to obtain an SQLite prepared statement handle @@ -203,12 +230,11 @@ static int fts3SqlStmt( /* 16 */ "DELETE FROM %Q.'%q_segdir' WHERE level = ?", /* 17 */ "DELETE FROM %Q.'%q_segments' WHERE blockid BETWEEN ? AND ?", /* 18 */ "INSERT INTO %Q.'%q_content' VALUES(%z)", -/* 19 */ "SELECT block FROM %Q.'%q_segments' WHERE blockid = ?", -/* 20 */ "DELETE FROM %Q.'%q_docsize' WHERE docid = ?", -/* 21 */ "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", -/* 22 */ "SELECT size FROM %Q.'%q_docsize' WHERE docid=?", -/* 23 */ "SELECT value FROM %Q.'%q_stat' WHERE id=0", -/* 24 */ "REPLACE INTO %Q.'%q_stat' VALUES(0,?)", +/* 19 */ "DELETE FROM %Q.'%q_docsize' WHERE docid = ?", +/* 20 */ "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", +/* 21 */ "SELECT size FROM %Q.'%q_docsize' WHERE docid=?", +/* 22 */ "SELECT value FROM %Q.'%q_stat' WHERE id=0", +/* 23 */ "REPLACE INTO %Q.'%q_stat' VALUES(0,?)", }; int rc = SQLITE_OK; sqlite3_stmt *pStmt; @@ -257,6 +283,51 @@ static int fts3SqlStmt( return rc; } +static int fts3SelectDocsize( + Fts3Table *pTab, /* FTS3 table handle */ + int eStmt, /* Either SQL_SELECT_DOCSIZE or DOCTOTAL */ + sqlite3_int64 iDocid, /* Docid to bind for SQL_SELECT_DOCSIZE */ + sqlite3_stmt **ppStmt /* OUT: Statement handle */ +){ + sqlite3_stmt *pStmt = 0; /* Statement requested from fts3SqlStmt() */ + int rc; /* Return code */ + + assert( eStmt==SQL_SELECT_DOCSIZE || eStmt==SQL_SELECT_DOCTOTAL ); + + rc = fts3SqlStmt(pTab, eStmt, &pStmt, 0); + if( rc==SQLITE_OK ){ + if( eStmt==SQL_SELECT_DOCSIZE ){ + sqlite3_bind_int64(pStmt, 1, iDocid); + } + rc = sqlite3_step(pStmt); + if( rc!=SQLITE_ROW ){ + rc = sqlite3_reset(pStmt); + if( rc==SQLITE_OK ) rc = SQLITE_CORRUPT; + pStmt = 0; + }else{ + rc = SQLITE_OK; + } + } + + *ppStmt = pStmt; + return rc; +} + +int sqlite3Fts3SelectDoctotal( + Fts3Table *pTab, /* Fts3 table handle */ + sqlite3_stmt **ppStmt /* OUT: Statement handle */ +){ + return fts3SelectDocsize(pTab, SQL_SELECT_DOCTOTAL, 0, ppStmt); +} + +int sqlite3Fts3SelectDocsize( + Fts3Table *pTab, /* Fts3 table handle */ + sqlite3_int64 iDocid, /* Docid to read size data for */ + sqlite3_stmt **ppStmt /* OUT: Statement handle */ +){ + return fts3SelectDocsize(pTab, SQL_SELECT_DOCSIZE, iDocid, ppStmt); +} + /* ** Similar to fts3SqlStmt(). Except, after binding the parameters in ** array apVal[] to the SQL statement identified by eStmt, the statement @@ -284,42 +355,33 @@ static void fts3SqlExec( /* -** Read a single block from the %_segments table. If the specified block -** does not exist, return SQLITE_CORRUPT. If some other error (malloc, IO -** etc.) occurs, return the appropriate SQLite error code. +** This function ensures that the caller has obtained a shared-cache +** table-lock on the %_content table. This is required before reading +** data from the fts3 table. If this lock is not acquired first, then +** the caller may end up holding read-locks on the %_segments and %_segdir +** tables, but no read-lock on the %_content table. If this happens +** a second connection will be able to write to the fts3 table, but +** attempting to commit those writes might return SQLITE_LOCKED or +** SQLITE_LOCKED_SHAREDCACHE (because the commit attempts to obtain +** write-locks on the %_segments and %_segdir ** tables). ** -** Otherwise, if successful, set *pzBlock to point to a buffer containing -** the block read from the database, and *pnBlock to the size of the read -** block in bytes. -** -** WARNING: The returned buffer is only valid until the next call to -** sqlite3Fts3ReadBlock(). +** We try to avoid this because if FTS3 returns any error when committing +** a transaction, the whole transaction will be rolled back. And this is +** not what users expect when they get SQLITE_LOCKED_SHAREDCACHE. It can +** still happen if the user reads data directly from the %_segments or +** %_segdir tables instead of going through FTS3 though. */ -int sqlite3Fts3ReadBlock( - Fts3Table *p, - sqlite3_int64 iBlock, - char const **pzBlock, - int *pnBlock -){ - sqlite3_stmt *pStmt; - int rc = fts3SqlStmt(p, SQL_GET_BLOCK, &pStmt, 0); - if( rc!=SQLITE_OK ) return rc; - sqlite3_reset(pStmt); +int sqlite3Fts3ReadLock(Fts3Table *p){ + int rc; /* Return code */ + sqlite3_stmt *pStmt; /* Statement used to obtain lock */ - if( pzBlock ){ - sqlite3_bind_int64(pStmt, 1, iBlock); - rc = sqlite3_step(pStmt); - if( rc!=SQLITE_ROW ){ - return (rc==SQLITE_DONE ? SQLITE_CORRUPT : rc); - } - - *pnBlock = sqlite3_column_bytes(pStmt, 0); - *pzBlock = (char *)sqlite3_column_blob(pStmt, 0); - if( sqlite3_column_type(pStmt, 0)!=SQLITE_BLOB ){ - return SQLITE_CORRUPT; - } + rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_null(pStmt, 1); + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); } - return SQLITE_OK; + return rc; } /* @@ -460,10 +522,10 @@ static int fts3PendingListAppend( ** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code. */ static int fts3PendingTermsAdd( - Fts3Table *p, /* FTS table into which text will be inserted */ - const char *zText, /* Text of document to be inseted */ - int iCol, /* Column number into which text is inserted */ - u32 *pnWord /* OUT: Number of tokens inserted */ + Fts3Table *p, /* Table into which text will be inserted */ + const char *zText, /* Text of document to be inserted */ + int iCol, /* Column into which text is being inserted */ + u32 *pnWord /* OUT: Number of tokens inserted */ ){ int rc; int iStart; @@ -548,6 +610,9 @@ static int fts3PendingTermsDocid(Fts3Table *p, sqlite_int64 iDocid){ return SQLITE_OK; } +/* +** Discard the contents of the pending-terms hash table. +*/ void sqlite3Fts3PendingTermsClear(Fts3Table *p){ Fts3HashElem *pElem; for(pElem=fts3HashFirst(&p->pendingTerms); pElem; pElem=fts3HashNext(pElem)){ @@ -575,6 +640,7 @@ static int fts3InsertTerms(Fts3Table *p, sqlite3_value **apVal, u32 *aSz){ return rc; } } + aSz[p->nColumn] += sqlite3_value_bytes(apVal[i]); } return SQLITE_OK; } @@ -662,6 +728,8 @@ static int fts3DeleteAll(Fts3Table *p){ fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGDIR, 0); if( p->bHasDocsize ){ fts3SqlExec(&rc, p, SQL_DELETE_ALL_DOCSIZE, 0); + } + if( p->bHasStat ){ fts3SqlExec(&rc, p, SQL_DELETE_ALL_STAT, 0); } return rc; @@ -672,7 +740,7 @@ static int fts3DeleteAll(Fts3Table *p){ ** (an integer) of a row about to be deleted. Remove all terms from the ** full-text index. */ -static void fts3DeleteTerms( +static void fts3DeleteTerms( int *pRC, /* Result code */ Fts3Table *p, /* The FTS table to delete from */ sqlite3_value **apVal, /* apVal[] contains the docid to be deleted */ @@ -694,6 +762,7 @@ static void fts3DeleteTerms( *pRC = rc; return; } + aSz[p->nColumn] += sqlite3_column_bytes(pSelect, i); } } rc = sqlite3_reset(pSelect); @@ -756,12 +825,93 @@ static int fts3AllocateSegdirIdx(Fts3Table *p, int iLevel, int *piIdx){ return rc; } +/* +** The %_segments table is declared as follows: +** +** CREATE TABLE %_segments(blockid INTEGER PRIMARY KEY, block BLOB) +** +** This function reads data from a single row of the %_segments table. The +** specific row is identified by the iBlockid parameter. If paBlob is not +** NULL, then a buffer is allocated using sqlite3_malloc() and populated +** with the contents of the blob stored in the "block" column of the +** identified table row is. Whether or not paBlob is NULL, *pnBlob is set +** to the size of the blob in bytes before returning. +** +** If an error occurs, or the table does not contain the specified row, +** an SQLite error code is returned. Otherwise, SQLITE_OK is returned. If +** paBlob is non-NULL, then it is the responsibility of the caller to +** eventually free the returned buffer. +** +** This function may leave an open sqlite3_blob* handle in the +** Fts3Table.pSegments variable. This handle is reused by subsequent calls +** to this function. The handle may be closed by calling the +** sqlite3Fts3SegmentsClose() function. Reusing a blob handle is a handy +** performance improvement, but the blob handle should always be closed +** before control is returned to the user (to prevent a lock being held +** on the database file for longer than necessary). Thus, any virtual table +** method (xFilter etc.) that may directly or indirectly call this function +** must call sqlite3Fts3SegmentsClose() before returning. +*/ +int sqlite3Fts3ReadBlock( + Fts3Table *p, /* FTS3 table handle */ + sqlite3_int64 iBlockid, /* Access the row with blockid=$iBlockid */ + char **paBlob, /* OUT: Blob data in malloc'd buffer */ + int *pnBlob /* OUT: Size of blob data */ +){ + int rc; /* Return code */ + + /* pnBlob must be non-NULL. paBlob may be NULL or non-NULL. */ + assert( pnBlob); + + if( p->pSegments ){ + rc = sqlite3_blob_reopen(p->pSegments, iBlockid); + }else{ + if( 0==p->zSegmentsTbl ){ + p->zSegmentsTbl = sqlite3_mprintf("%s_segments", p->zName); + if( 0==p->zSegmentsTbl ) return SQLITE_NOMEM; + } + rc = sqlite3_blob_open( + p->db, p->zDb, p->zSegmentsTbl, "block", iBlockid, 0, &p->pSegments + ); + } + + if( rc==SQLITE_OK ){ + int nByte = sqlite3_blob_bytes(p->pSegments); + if( paBlob ){ + char *aByte = sqlite3_malloc(nByte + FTS3_NODE_PADDING); + if( !aByte ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_blob_read(p->pSegments, aByte, nByte, 0); + memset(&aByte[nByte], 0, FTS3_NODE_PADDING); + if( rc!=SQLITE_OK ){ + sqlite3_free(aByte); + aByte = 0; + } + } + *paBlob = aByte; + } + *pnBlob = nByte; + } + + return rc; +} + +/* +** Close the blob handle at p->pSegments, if it is open. See comments above +** the sqlite3Fts3ReadBlock() function for details. +*/ +void sqlite3Fts3SegmentsClose(Fts3Table *p){ + sqlite3_blob_close(p->pSegments); + p->pSegments = 0; +} + /* ** Move the iterator passed as the first argument to the next term in the ** segment. If successful, SQLITE_OK is returned. If there is no next term, ** SQLITE_DONE. Otherwise, an SQLite error code. */ -static int fts3SegReaderNext(Fts3SegReader *pReader){ +static int fts3SegReaderNext(Fts3Table *p, Fts3SegReader *pReader){ char *pNext; /* Cursor variable */ int nPrefix; /* Number of bytes in term prefix */ int nSuffix; /* Number of bytes in term suffix */ @@ -773,7 +923,8 @@ static int fts3SegReaderNext(Fts3SegReader *pReader){ } if( !pNext || pNext>=&pReader->aNode[pReader->nNode] ){ - int rc; + int rc; /* Return code from Fts3ReadBlock() */ + if( fts3SegReaderIsPending(pReader) ){ Fts3HashElem *pElem = *(pReader->ppNextElem); if( pElem==0 ){ @@ -789,22 +940,36 @@ static int fts3SegReaderNext(Fts3SegReader *pReader){ } return SQLITE_OK; } - if( !pReader->pStmt ){ - pReader->aNode = 0; + + if( !fts3SegReaderIsRootOnly(pReader) ){ + sqlite3_free(pReader->aNode); + } + pReader->aNode = 0; + + /* If iCurrentBlock>=iLeafEndBlock, this is an EOF condition. All leaf + ** blocks have already been traversed. */ + assert( pReader->iCurrentBlock<=pReader->iLeafEndBlock ); + if( pReader->iCurrentBlock>=pReader->iLeafEndBlock ){ return SQLITE_OK; } - rc = sqlite3_step(pReader->pStmt); - if( rc!=SQLITE_ROW ){ - pReader->aNode = 0; - return (rc==SQLITE_DONE ? SQLITE_OK : rc); - } - pReader->nNode = sqlite3_column_bytes(pReader->pStmt, 0); - pReader->aNode = (char *)sqlite3_column_blob(pReader->pStmt, 0); + + rc = sqlite3Fts3ReadBlock( + p, ++pReader->iCurrentBlock, &pReader->aNode, &pReader->nNode + ); + if( rc!=SQLITE_OK ) return rc; pNext = pReader->aNode; } + /* 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); + if( nPrefix<0 || nSuffix<=0 + || &pNext[nSuffix]>&pReader->aNode[pReader->nNode] + ){ + return SQLITE_CORRUPT; + } if( nPrefix+nSuffix>pReader->nTermAlloc ){ int nNew = (nPrefix+nSuffix)*2; @@ -819,9 +984,18 @@ static int fts3SegReaderNext(Fts3SegReader *pReader){ pReader->nTerm = nPrefix+nSuffix; pNext += nSuffix; pNext += sqlite3Fts3GetVarint32(pNext, &pReader->nDoclist); - assert( pNext<&pReader->aNode[pReader->nNode] ); 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 + ** of these statements is untrue, then the data structure is corrupt. + */ + if( &pReader->aDoclist[pReader->nDoclist]>&pReader->aNode[pReader->nNode] + || pReader->aDoclist[pReader->nDoclist-1] + ){ + return SQLITE_CORRUPT; + } return SQLITE_OK; } @@ -884,32 +1058,110 @@ static void fts3SegReaderNextDocid( } } +/* +** This function is called to estimate the amount of data that will be +** loaded from the disk If SegReaderIterate() is called on this seg-reader, +** in units of average document size. +** +** This can be used as follows: If the caller has a small doclist that +** contains references to N documents, and is considering merging it with +** a large doclist (size X "average documents"), it may opt not to load +** the large doclist if X>N. +*/ +int sqlite3Fts3SegReaderCost( + Fts3Cursor *pCsr, /* FTS3 cursor handle */ + Fts3SegReader *pReader, /* Segment-reader handle */ + int *pnCost /* IN/OUT: Number of bytes read */ +){ + Fts3Table *p = (Fts3Table*)pCsr->base.pVtab; + int rc = SQLITE_OK; /* Return code */ + int nCost = 0; /* Cost in bytes to return */ + int pgsz = p->nPgsz; /* Database page size */ + + /* If this seg-reader is reading the pending-terms table, or if all data + ** for the segment is stored on the root page of the b-tree, then the cost + ** is zero. In this case all required data is already in main memory. + */ + if( p->bHasStat + && !fts3SegReaderIsPending(pReader) + && !fts3SegReaderIsRootOnly(pReader) + ){ + int nBlob = 0; + sqlite3_int64 iBlock; + + if( pCsr->nRowAvg==0 ){ + /* The average document size, which is required to calculate the cost + ** of each doclist, has not yet been determined. Read the required + ** data from the %_stat table to calculate it. + ** + ** Entry 0 of the %_stat table is a blob containing (nCol+1) FTS3 + ** varints, where nCol is the number of columns in the FTS3 table. + ** The first varint is the number of documents currently stored in + ** the table. The following nCol varints contain the total amount of + ** data stored in all rows of each column of the table, from left + ** to right. + */ + sqlite3_stmt *pStmt; + sqlite3_int64 nDoc = 0; + sqlite3_int64 nByte = 0; + const char *a; + rc = sqlite3Fts3SelectDoctotal(p, &pStmt); + if( rc ) return rc; + a = sqlite3_column_blob(pStmt, 0); + if( a ){ + const char *pEnd = &a[sqlite3_column_bytes(pStmt, 0)]; + a += sqlite3Fts3GetVarint(a, &nDoc); + while( anRowAvg = (int)(((nByte / nDoc) + pgsz) / pgsz); + assert( pCsr->nRowAvg>0 ); + rc = sqlite3_reset(pStmt); + if( rc!=SQLITE_OK ) return rc; + } + + /* Assume that a blob flows over onto overflow pages if it is larger + ** than (pgsz-35) bytes in size (the file-format documentation + ** confirms this). + */ + for(iBlock=pReader->iStartBlock; iBlock<=pReader->iLeafEndBlock; iBlock++){ + rc = sqlite3Fts3ReadBlock(p, iBlock, 0, &nBlob); + if( rc!=SQLITE_OK ) break; + if( (nBlob+35)>pgsz ){ + int nOvfl = (nBlob + 34)/pgsz; + nCost += ((nOvfl + pCsr->nRowAvg - 1)/pCsr->nRowAvg); + } + } + } + + *pnCost += nCost; + return rc; +} + /* ** Free all allocations associated with the iterator passed as the ** second argument. */ -void sqlite3Fts3SegReaderFree(Fts3Table *p, Fts3SegReader *pReader){ - if( pReader ){ - if( pReader->pStmt ){ - /* Move the leaf-range SELECT statement to the aLeavesStmt[] array, - ** so that it can be reused when required by another query. - */ - assert( p->nLeavesStmtnLeavesTotal ); - sqlite3_reset(pReader->pStmt); - p->aLeavesStmt[p->nLeavesStmt++] = pReader->pStmt; +void sqlite3Fts3SegReaderFree(Fts3SegReader *pReader){ + if( pReader && !fts3SegReaderIsPending(pReader) ){ + sqlite3_free(pReader->zTerm); + if( !fts3SegReaderIsRootOnly(pReader) ){ + sqlite3_free(pReader->aNode); } - if( !fts3SegReaderIsPending(pReader) ){ - sqlite3_free(pReader->zTerm); - } - sqlite3_free(pReader); } + sqlite3_free(pReader); } /* ** Allocate a new SegReader object. */ int sqlite3Fts3SegReaderNew( - Fts3Table *p, /* Virtual table handle */ int iAge, /* Segment "age". */ sqlite3_int64 iStartLeaf, /* First leaf to traverse */ sqlite3_int64 iEndLeaf, /* Final leaf to traverse */ @@ -922,8 +1174,9 @@ int sqlite3Fts3SegReaderNew( Fts3SegReader *pReader; /* Newly allocated SegReader object */ int nExtra = 0; /* Bytes to allocate segment root node */ + assert( iStartLeaf<=iEndLeaf ); if( iStartLeaf==0 ){ - nExtra = nRoot; + nExtra = nRoot + FTS3_NODE_PADDING; } pReader = (Fts3SegReader *)sqlite3_malloc(sizeof(Fts3SegReader) + nExtra); @@ -931,8 +1184,9 @@ int sqlite3Fts3SegReaderNew( return SQLITE_NOMEM; } memset(pReader, 0, sizeof(Fts3SegReader)); - pReader->iStartBlock = iStartLeaf; pReader->iIdx = iAge; + pReader->iStartBlock = iStartLeaf; + pReader->iLeafEndBlock = iEndLeaf; pReader->iEndBlock = iEndBlock; if( nExtra ){ @@ -940,59 +1194,15 @@ int sqlite3Fts3SegReaderNew( pReader->aNode = (char *)&pReader[1]; pReader->nNode = nRoot; memcpy(pReader->aNode, zRoot, nRoot); + memset(&pReader->aNode[nRoot], 0, FTS3_NODE_PADDING); }else{ - /* If the text of the SQL statement to iterate through a contiguous - ** set of entries in the %_segments table has not yet been composed, - ** compose it now. - */ - if( !p->zSelectLeaves ){ - p->zSelectLeaves = sqlite3_mprintf( - "SELECT block FROM %Q.'%q_segments' WHERE blockid BETWEEN ? AND ? " - "ORDER BY blockid", p->zDb, p->zName - ); - if( !p->zSelectLeaves ){ - rc = SQLITE_NOMEM; - goto finished; - } - } - - /* If there are no free statements in the aLeavesStmt[] array, prepare - ** a new statement now. Otherwise, reuse a prepared statement from - ** aLeavesStmt[]. - */ - if( p->nLeavesStmt==0 ){ - if( p->nLeavesTotal==p->nLeavesAlloc ){ - int nNew = p->nLeavesAlloc + 16; - sqlite3_stmt **aNew = (sqlite3_stmt **)sqlite3_realloc( - p->aLeavesStmt, nNew*sizeof(sqlite3_stmt *) - ); - if( !aNew ){ - rc = SQLITE_NOMEM; - goto finished; - } - p->nLeavesAlloc = nNew; - p->aLeavesStmt = aNew; - } - rc = sqlite3_prepare_v2(p->db, p->zSelectLeaves, -1, &pReader->pStmt, 0); - if( rc!=SQLITE_OK ){ - goto finished; - } - p->nLeavesTotal++; - }else{ - pReader->pStmt = p->aLeavesStmt[--p->nLeavesStmt]; - } - - /* Bind the start and end leaf blockids to the prepared SQL statement. */ - sqlite3_bind_int64(pReader->pStmt, 1, iStartLeaf); - sqlite3_bind_int64(pReader->pStmt, 2, iEndLeaf); + pReader->iCurrentBlock = iStartLeaf-1; } - rc = fts3SegReaderNext(pReader); - finished: if( rc==SQLITE_OK ){ *ppReader = pReader; }else{ - sqlite3Fts3SegReaderFree(p, pReader); + sqlite3Fts3SegReaderFree(pReader); } return rc; } @@ -1083,7 +1293,6 @@ int sqlite3Fts3SegReaderPending( pReader->iIdx = 0x7FFFFFFF; pReader->ppNextElem = (Fts3HashElem **)&pReader[1]; memcpy(pReader->ppNextElem, aElem, nElem*sizeof(Fts3HashElem *)); - fts3SegReaderNext(pReader); } } @@ -1116,12 +1325,11 @@ int sqlite3Fts3SegReaderPending( ** code is returned. */ static int fts3SegReaderNew( - Fts3Table *p, /* Virtual table handle */ sqlite3_stmt *pStmt, /* See above */ int iAge, /* Segment "age". */ Fts3SegReader **ppReader /* OUT: Allocated Fts3SegReader */ ){ - return sqlite3Fts3SegReaderNew(p, iAge, + return sqlite3Fts3SegReaderNew(iAge, sqlite3_column_int64(pStmt, 1), sqlite3_column_int64(pStmt, 2), sqlite3_column_int64(pStmt, 3), @@ -1325,7 +1533,7 @@ static int fts3PrefixCompress( ** (according to memcmp) than the previous term. */ static int fts3NodeAddTerm( - Fts3Table *p, /* Virtual table handle */ + Fts3Table *p, /* Virtual table handle */ SegmentNode **ppTree, /* IN/OUT: SegmentNode handle */ int isCopyTerm, /* True if zTerm/nTerm is transient */ const char *zTerm, /* Pointer to buffer containing term */ @@ -1955,15 +2163,14 @@ int sqlite3Fts3SegReaderIterate( ** unnecessary merge/sort operations for the case where single segment ** b-tree leaf nodes contain more than one term. */ - if( pFilter->zTerm ){ + for(i=0; inTerm; const char *zTerm = pFilter->zTerm; - for(i=0; inAlloc ){ char *aNew; - nAlloc = nDoclist+nByte*2; + nAlloc = (nDoclist+nByte)*2; aNew = sqlite3_realloc(aBuffer, nAlloc); if( !aNew ){ rc = SQLITE_NOMEM; @@ -2072,7 +2279,7 @@ int sqlite3Fts3SegReaderIterate( } for(i=0; ibase.pVtab; - rc = fts3SqlStmt(p, SQL_SELECT_DOCSIZE, &pStmt, 0); - if( rc ){ - return rc; - } - sqlite3_bind_int64(pStmt, 1, pCur->iPrevId); - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - nBlob = sqlite3_column_bytes(pStmt, 0); - pBlob = (const char*)sqlite3_column_blob(pStmt, 0); - for(i=j=0; inColumn && jbase.pVtab; - rc = fts3SqlStmt(p, SQL_SELECT_DOCTOTAL, &pStmt, 0); - if( rc ){ - return rc; - } - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - nBlob = sqlite3_column_bytes(pStmt, 0); - pBlob = (const char*)sqlite3_column_blob(pStmt, 0); - j = sqlite3Fts3GetVarint(pBlob, &x); - a[0] = nDoc = (u32)(x & 0xffffffff); - for(i=0; inColumn && jiPrevDocid. The sizes are encoded as @@ -2387,16 +2525,26 @@ static void fts3InsertDocsize( } /* -** Update the 0 record of the %_stat table so that it holds a blob -** which contains the document count followed by the cumulative -** document sizes for all columns. +** Record 0 of the %_stat table contains a blob consisting of N varints, +** where N is the number of user defined columns in the fts3 table plus +** two. If nCol is the number of user defined columns, then values of the +** varints are set as follows: +** +** Varint 0: Total number of rows in the table. +** +** Varint 1..nCol: For each column, the total number of tokens stored in +** the column for all rows of the table. +** +** Varint 1+nCol: The total size, in bytes, of all text values in all +** columns of all rows of the table. +** */ static void fts3UpdateDocTotals( - int *pRC, /* The result code */ - Fts3Table *p, /* Table being updated */ - u32 *aSzIns, /* Size increases */ - u32 *aSzDel, /* Size decreases */ - int nChng /* Change in the number of documents */ + int *pRC, /* The result code */ + Fts3Table *p, /* Table being updated */ + u32 *aSzIns, /* Size increases */ + u32 *aSzDel, /* Size decreases */ + int nChng /* Change in the number of documents */ ){ char *pBlob; /* Storage for BLOB written into %_stat */ int nBlob; /* Size of BLOB written into %_stat */ @@ -2405,13 +2553,15 @@ static void fts3UpdateDocTotals( int i; /* Loop counter */ int rc; /* Result code from subfunctions */ + const int nStat = p->nColumn+2; + if( *pRC ) return; - a = sqlite3_malloc( (sizeof(u32)+10)*(p->nColumn+1) ); + a = sqlite3_malloc( (sizeof(u32)+10)*nStat ); if( a==0 ){ *pRC = SQLITE_NOMEM; return; } - pBlob = (char*)&a[p->nColumn+1]; + pBlob = (char*)&a[nStat]; rc = fts3SqlStmt(p, SQL_SELECT_DOCTOTAL, &pStmt, 0); if( rc ){ sqlite3_free(a); @@ -2419,11 +2569,11 @@ static void fts3UpdateDocTotals( return; } if( sqlite3_step(pStmt)==SQLITE_ROW ){ - fts3DecodeIntArray(p->nColumn+1, a, + fts3DecodeIntArray(nStat, a, sqlite3_column_blob(pStmt, 0), sqlite3_column_bytes(pStmt, 0)); }else{ - memset(a, 0, sizeof(u32)*(p->nColumn+1) ); + memset(a, 0, sizeof(u32)*(nStat) ); } sqlite3_reset(pStmt); if( nChng<0 && a[0]<(u32)(-nChng) ){ @@ -2431,7 +2581,7 @@ static void fts3UpdateDocTotals( }else{ a[0] += nChng; } - for(i=0; inColumn; i++){ + for(i=0; inColumn+1; i++){ u32 x = a[i+1]; if( x+aSzIns[i] < aSzDel[i] ){ x = 0; @@ -2440,7 +2590,7 @@ static void fts3UpdateDocTotals( } a[i+1] = x; } - fts3EncodeIntArray(p->nColumn+1, a, pBlob, &nBlob); + fts3EncodeIntArray(nStat, a, pBlob, &nBlob); rc = fts3SqlStmt(p, SQL_REPLACE_DOCTOTAL, &pStmt, 0); if( rc ){ sqlite3_free(a); @@ -2487,9 +2637,159 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){ rc = SQLITE_ERROR; } + sqlite3Fts3SegmentsClose(p); return rc; } +/* +** Return the deferred doclist associated with deferred token pDeferred. +** This function assumes that sqlite3Fts3CacheDeferredDoclists() has already +** been called to allocate and populate the doclist. +*/ +char *sqlite3Fts3DeferredDoclist(Fts3DeferredToken *pDeferred, int *pnByte){ + if( pDeferred->pList ){ + *pnByte = pDeferred->pList->nData; + return pDeferred->pList->aData; + } + *pnByte = 0; + return 0; +} + +/* +** Helper fucntion for FreeDeferredDoclists(). This function removes all +** references to deferred doclists from within the tree of Fts3Expr +** structures headed by +*/ +static void fts3DeferredDoclistClear(Fts3Expr *pExpr){ + if( pExpr ){ + fts3DeferredDoclistClear(pExpr->pLeft); + fts3DeferredDoclistClear(pExpr->pRight); + if( pExpr->isLoaded ){ + sqlite3_free(pExpr->aDoclist); + pExpr->isLoaded = 0; + pExpr->aDoclist = 0; + pExpr->nDoclist = 0; + pExpr->pCurrent = 0; + pExpr->iCurrent = 0; + } + } +} + +/* +** Delete all cached deferred doclists. Deferred doclists are cached +** (allocated) by the sqlite3Fts3CacheDeferredDoclists() function. +*/ +void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *pCsr){ + Fts3DeferredToken *pDef; + for(pDef=pCsr->pDeferred; pDef; pDef=pDef->pNext){ + sqlite3_free(pDef->pList); + pDef->pList = 0; + } + if( pCsr->pDeferred ){ + fts3DeferredDoclistClear(pCsr->pExpr); + } +} + +/* +** Free all entries in the pCsr->pDeffered list. Entries are added to +** this list using sqlite3Fts3DeferToken(). +*/ +void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *pCsr){ + Fts3DeferredToken *pDef; + Fts3DeferredToken *pNext; + for(pDef=pCsr->pDeferred; pDef; pDef=pNext){ + pNext = pDef->pNext; + sqlite3_free(pDef->pList); + sqlite3_free(pDef); + } + pCsr->pDeferred = 0; +} + +/* +** Generate deferred-doclists for all tokens in the pCsr->pDeferred list +** based on the row that pCsr currently points to. +** +** A deferred-doclist is like any other doclist with position information +** included, except that it only contains entries for a single row of the +** table, not for all rows. +*/ +int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *pCsr){ + int rc = SQLITE_OK; /* Return code */ + if( pCsr->pDeferred ){ + int i; /* Used to iterate through table columns */ + sqlite3_int64 iDocid; /* Docid of the row pCsr points to */ + Fts3DeferredToken *pDef; /* Used to iterate through deferred tokens */ + + Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; + sqlite3_tokenizer *pT = p->pTokenizer; + sqlite3_tokenizer_module const *pModule = pT->pModule; + + assert( pCsr->isRequireSeek==0 ); + iDocid = sqlite3_column_int64(pCsr->pStmt, 0); + + for(i=0; inColumn && rc==SQLITE_OK; i++){ + const char *zText = (const char *)sqlite3_column_text(pCsr->pStmt, i+1); + sqlite3_tokenizer_cursor *pTC = 0; + + rc = pModule->xOpen(pT, zText, -1, &pTC); + while( rc==SQLITE_OK ){ + char const *zToken; /* Buffer containing token */ + int nToken; /* Number of bytes in token */ + int iDum1, iDum2; /* Dummy variables */ + int iPos; /* Position of token in zText */ + + pTC->pTokenizer = pT; + rc = pModule->xNext(pTC, &zToken, &nToken, &iDum1, &iDum2, &iPos); + for(pDef=pCsr->pDeferred; pDef && rc==SQLITE_OK; pDef=pDef->pNext){ + Fts3PhraseToken *pPT = pDef->pToken; + if( (pDef->iCol>=p->nColumn || pDef->iCol==i) + && (pPT->n==nToken || (pPT->isPrefix && pPT->nz, pPT->n)) + ){ + fts3PendingListAppend(&pDef->pList, iDocid, i, iPos, &rc); + } + } + } + if( pTC ) pModule->xClose(pTC); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + } + + for(pDef=pCsr->pDeferred; pDef && rc==SQLITE_OK; pDef=pDef->pNext){ + if( pDef->pList ){ + rc = fts3PendingListAppendVarint(&pDef->pList, 0); + } + } + } + + return rc; +} + +/* +** Add an entry for token pToken to the pCsr->pDeferred list. +*/ +int sqlite3Fts3DeferToken( + Fts3Cursor *pCsr, /* Fts3 table cursor */ + Fts3PhraseToken *pToken, /* Token to defer */ + int iCol /* Column that token must appear in (or -1) */ +){ + Fts3DeferredToken *pDeferred; + pDeferred = sqlite3_malloc(sizeof(*pDeferred)); + if( !pDeferred ){ + return SQLITE_NOMEM; + } + memset(pDeferred, 0, sizeof(*pDeferred)); + pDeferred->pToken = pToken; + pDeferred->pNext = pCsr->pDeferred; + pDeferred->iCol = iCol; + pCsr->pDeferred = pDeferred; + + assert( pToken->pDeferred==0 ); + pToken->pDeferred = pDeferred; + + return SQLITE_OK; +} + + /* ** This function does the work for the xUpdate method of FTS3 virtual ** tables. @@ -2508,16 +2808,17 @@ int sqlite3Fts3UpdateMethod( u32 *aSzDel; /* Sizes of deleted documents */ int nChng = 0; /* Net change in number of documents */ + assert( p->pSegments==0 ); /* Allocate space to hold the change in document sizes */ - aSzIns = sqlite3_malloc( sizeof(aSzIns[0])*p->nColumn*2 ); + aSzIns = sqlite3_malloc( sizeof(aSzIns[0])*(p->nColumn+1)*2 ); if( aSzIns==0 ) return SQLITE_NOMEM; - aSzDel = &aSzIns[p->nColumn]; - memset(aSzIns, 0, sizeof(aSzIns[0])*p->nColumn*2); + aSzDel = &aSzIns[p->nColumn+1]; + memset(aSzIns, 0, sizeof(aSzIns[0])*(p->nColumn+1)*2); /* If this is a DELETE or UPDATE operation, remove the old record. */ if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ - int isEmpty; + int isEmpty = 0; rc = fts3IsEmpty(p, apVal, &isEmpty); if( rc==SQLITE_OK ){ if( isEmpty ){ @@ -2534,8 +2835,8 @@ int sqlite3Fts3UpdateMethod( fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, apVal); if( p->bHasDocsize ){ fts3SqlExec(&rc, p, SQL_DELETE_DOCSIZE, apVal); - nChng--; } + nChng--; } } }else if( sqlite3_value_type(apVal[p->nColumn+2])!=SQLITE_NULL ){ @@ -2553,16 +2854,17 @@ int sqlite3Fts3UpdateMethod( rc = fts3InsertTerms(p, apVal, aSzIns); } if( p->bHasDocsize ){ - nChng++; fts3InsertDocsize(&rc, p, aSzIns); } + nChng++; } - if( p->bHasDocsize ){ + if( p->bHasStat ){ fts3UpdateDocTotals(&rc, p, aSzIns, aSzDel, nChng); } sqlite3_free(aSzIns); + sqlite3Fts3SegmentsClose(p); return rc; } @@ -2586,6 +2888,7 @@ int sqlite3Fts3Optimize(Fts3Table *p){ sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0); } } + sqlite3Fts3SegmentsClose(p); return rc; } diff --git a/ext/fts3/fts3speed.tcl b/ext/fts3/fts3speed.tcl new file mode 100644 index 00000000..377cb196 --- /dev/null +++ b/ext/fts3/fts3speed.tcl @@ -0,0 +1,122 @@ + + +#-------------------------------------------------------------------------- +# This script contains several sub-programs used to test FTS3/FTS4 +# performance. It does not run the queries directly, but generates SQL +# scripts that can be run using the shell tool. +# +# The following cases are tested: +# +# 1. Inserting documents into an FTS3 table. +# 2. Optimizing an FTS3 table (i.e. "INSERT INTO t1 VALUES('optimize')"). +# 3. Deleting documents from an FTS3 table. +# 4. Querying FTS3 tables. +# + +# Number of tokens in vocabulary. And number of tokens in each document. +# +set VOCAB_SIZE 2000 +set DOC_SIZE 100 + +set NUM_INSERTS 100000 +set NUM_SELECTS 1000 + +# Force everything in this script to be deterministic. +# +expr {srand(0)} + +proc usage {} { + puts stderr "Usage: $::argv0 " + exit -1 +} + +proc sql {sql} { + puts $::fd $sql +} + + +# Return a list of $nWord randomly generated tokens each between 2 and 10 +# characters in length. +# +proc build_vocab {nWord} { + set ret [list] + set chars [list a b c d e f g h i j k l m n o p q r s t u v w x y z] + for {set i 0} {$i<$nWord} {incr i} { + set len [expr {int((rand()*9.0)+2)}] + set term "" + for {set j 0} {$j<$len} {incr j} { + append term [lindex $chars [expr {int(rand()*[llength $chars])}]] + } + lappend ret $term + } + set ret +} + +proc select_term {} { + set n [llength $::vocab] + set t [expr int(rand()*$n*3)] + if {$t>=2*$n} { set t [expr {($t-2*$n)/100}] } + if {$t>=$n} { set t [expr {($t-$n)/10}] } + lindex $::vocab $t +} + +proc select_doc {nTerm} { + set ret [list] + for {set i 0} {$i<$nTerm} {incr i} { + lappend ret [select_term] + } + set ret +} + +proc test_1 {nInsert} { + sql "PRAGMA synchronous = OFF;" + sql "DROP TABLE IF EXISTS t1;" + sql "CREATE VIRTUAL TABLE t1 USING fts4;" + for {set i 0} {$i < $nInsert} {incr i} { + set doc [select_doc $::DOC_SIZE] + sql "INSERT INTO t1 VALUES('$doc');" + } +} + +proc test_2 {} { + sql "INSERT INTO t1(t1) VALUES('optimize');" +} + +proc test_3 {nSelect} { + for {set i 0} {$i < $nSelect} {incr i} { + sql "SELECT count(*) FROM t1 WHERE t1 MATCH '[select_term]';" + } +} + +proc test_4 {nSelect} { + for {set i 0} {$i < $nSelect} {incr i} { + sql "SELECT count(*) FROM t1 WHERE t1 MATCH '[select_term] [select_term]';" + } +} + +if {[llength $argv]!=0} usage + +set ::vocab [build_vocab $::VOCAB_SIZE] + +set ::fd [open fts3speed_insert.sql w] +test_1 $NUM_INSERTS +close $::fd + +set ::fd [open fts3speed_select.sql w] +test_3 $NUM_SELECTS +close $::fd + +set ::fd [open fts3speed_select2.sql w] +test_4 $NUM_SELECTS +close $::fd + +set ::fd [open fts3speed_optimize.sql w] +test_2 +close $::fd + +puts "Success. Created files:" +puts " fts3speed_insert.sql" +puts " fts3speed_select.sql" +puts " fts3speed_select2.sql" +puts " fts3speed_optimize.sql" + diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index 0bc4dd7e..5665e6aa 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -13,6 +13,45 @@ ** algorithms packaged as an SQLite virtual table module. */ +/* +** Database Format of R-Tree Tables +** -------------------------------- +** +** The data structure for a single virtual r-tree table is stored in three +** native SQLite tables declared as follows. In each case, the '%' character +** in the table name is replaced with the user-supplied name of the r-tree +** table. +** +** CREATE TABLE %_node(nodeno INTEGER PRIMARY KEY, data BLOB) +** CREATE TABLE %_parent(nodeno INTEGER PRIMARY KEY, parentnode INTEGER) +** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno INTEGER) +** +** The data for each node of the r-tree structure is stored in the %_node +** table. For each node that is not the root node of the r-tree, there is +** an entry in the %_parent table associating the node with its parent. +** And for each row of data in the table, there is an entry in the %_rowid +** table that maps from the entries rowid to the id of the node that it +** is stored on. +** +** The root node of an r-tree always exists, even if the r-tree table is +** empty. The nodeno of the root node is always 1. All other nodes in the +** table must be the same size as the root node. The content of each node +** is formatted as follows: +** +** 1. If the node is the root node (node 1), then the first 2 bytes +** of the node contain the tree depth as a big-endian integer. +** For non-root nodes, the first 2 bytes are left unused. +** +** 2. The next 2 bytes contain the number of entries currently +** stored in the node. +** +** 3. The remainder of the node contains the node entries. Each entry +** consists of a single 8-byte integer followed by an even number +** of 4-byte coordinates. For leaf nodes the integer is the rowid +** of a record. For internal nodes it is the node number of a +** child page. +*/ + #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_RTREE) /* @@ -53,6 +92,9 @@ #define AssignCells splitNodeStartree #endif +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif #ifndef SQLITE_CORE #include "sqlite3ext.h" @@ -65,16 +107,25 @@ #include #ifndef SQLITE_AMALGAMATION +#include "sqlite3rtree.h" typedef sqlite3_int64 i64; typedef unsigned char u8; typedef unsigned int u32; #endif +/* The following macro is used to suppress compiler warnings. +*/ +#ifndef UNUSED_PARAMETER +# define UNUSED_PARAMETER(x) (void)(x) +#endif + typedef struct Rtree Rtree; typedef struct RtreeCursor RtreeCursor; typedef struct RtreeNode RtreeNode; typedef struct RtreeCell RtreeCell; typedef struct RtreeConstraint RtreeConstraint; +typedef struct RtreeMatchArg RtreeMatchArg; +typedef struct RtreeGeomCallback RtreeGeomCallback; typedef union RtreeCoord RtreeCoord; /* The rtree may have between 1 and RTREE_MAX_DIMENSIONS dimensions. */ @@ -144,6 +195,15 @@ struct Rtree { #define RTREE_REINSERT(p) RTREE_MINCELLS(p) #define RTREE_MAXCELLS 51 +/* +** The smallest possible node-size is (512-64)==448 bytes. And the largest +** supported cell size is 48 bytes (8 byte rowid + ten 4 byte coordinates). +** Therefore all non-root nodes must contain at least 3 entries. Since +** 2^40 is greater than 2^64, an r-tree structure always has a depth of +** 40 or less. +*/ +#define RTREE_MAX_DEPTH 40 + /* ** An rtree cursor object. */ @@ -176,35 +236,23 @@ union RtreeCoord { ** A search constraint. */ struct RtreeConstraint { - int iCoord; /* Index of constrained coordinate */ - int op; /* Constraining operation */ - double rValue; /* Constraint value. */ + int iCoord; /* Index of constrained coordinate */ + int op; /* Constraining operation */ + double rValue; /* Constraint value. */ + int (*xGeom)(sqlite3_rtree_geometry *, int, double *, int *); + sqlite3_rtree_geometry *pGeom; /* Constraint callback argument for a MATCH */ }; /* 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_EQ 0x41 +#define RTREE_LE 0x42 +#define RTREE_LT 0x43 +#define RTREE_GE 0x44 +#define RTREE_GT 0x45 +#define RTREE_MATCH 0x46 /* ** An rtree structure node. -** -** Data format (RtreeNode.zData): -** -** 1. If the node is the root node (node 1), then the first 2 bytes -** of the node contain the tree depth as a big-endian integer. -** For non-root nodes, the first 2 bytes are left unused. -** -** 2. The next 2 bytes contain the number of entries currently -** stored in the node. -** -** 3. The remainder of the node contains the node entries. Each entry -** consists of a single 8-byte integer followed by an even number -** of 4-byte coordinates. For leaf nodes the integer is the rowid -** of a record. For internal nodes it is the node number of a -** child page. */ struct RtreeNode { RtreeNode *pParent; /* Parent node */ @@ -224,6 +272,40 @@ struct RtreeCell { RtreeCoord aCoord[RTREE_MAX_DIMENSIONS*2]; }; + +/* +** Value for the first field of every RtreeMatchArg object. The MATCH +** operator tests that the first field of a blob operand matches this +** 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. +*/ +struct RtreeMatchArg { + u32 magic; /* Always RTREE_GEOMETRY_MAGIC */ + int (*xGeom)(sqlite3_rtree_geometry *, int, double *, int *); + void *pContext; + int nParam; + double 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, double *, int *); + void *pContext; +}; + #ifndef MAX # define MAX(x,y) ((x) < (y) ? (y) : (x)) #endif @@ -306,10 +388,8 @@ static void nodeReference(RtreeNode *p){ ** Clear the content of node p (set all bytes to 0x00). */ static void nodeZero(Rtree *pRtree, RtreeNode *p){ - if( p ){ - memset(&p->zData[2], 0, pRtree->iNodeSize-2); - p->isDirty = 1; - } + memset(&p->zData[2], 0, pRtree->iNodeSize-2); + p->isDirty = 1; } /* @@ -329,7 +409,6 @@ static int nodeHash(i64 iNode){ */ static RtreeNode *nodeHashLookup(Rtree *pRtree, i64 iNode){ RtreeNode *p; - assert( iNode!=0 ); for(p=pRtree->aHash[nodeHash(iNode)]; p && p->iNode!=iNode; p=p->pNext); return p; } @@ -338,13 +417,11 @@ static RtreeNode *nodeHashLookup(Rtree *pRtree, i64 iNode){ ** Add node pNode to the node hash table. */ static void nodeHashInsert(Rtree *pRtree, RtreeNode *pNode){ - if( pNode ){ - int iHash; - assert( pNode->pNext==0 ); - iHash = nodeHash(pNode->iNode); - pNode->pNext = pRtree->aHash[iHash]; - pRtree->aHash[iHash] = pNode; - } + int iHash; + assert( pNode->pNext==0 ); + iHash = nodeHash(pNode->iNode); + pNode->pNext = pRtree->aHash[iHash]; + pRtree->aHash[iHash] = pNode; } /* @@ -366,11 +443,11 @@ static void nodeHashDelete(Rtree *pRtree, RtreeNode *pNode){ ** assigned a node number when nodeWrite() is called to write the ** node contents out to the database. */ -static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent, int zero){ +static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){ RtreeNode *pNode; pNode = (RtreeNode *)sqlite3_malloc(sizeof(RtreeNode) + pRtree->iNodeSize); if( pNode ){ - memset(pNode, 0, sizeof(RtreeNode) + (zero?pRtree->iNodeSize:0)); + memset(pNode, 0, sizeof(RtreeNode) + pRtree->iNodeSize); pNode->zData = (u8 *)&pNode[1]; pNode->nRef = 1; pNode->pParent = pParent; @@ -391,6 +468,7 @@ nodeAcquire( RtreeNode **ppNode /* OUT: Acquired node */ ){ int rc; + int rc2 = SQLITE_OK; RtreeNode *pNode; /* Check if the requested node is already in the hash table. If so, @@ -407,40 +485,64 @@ nodeAcquire( return SQLITE_OK; } - pNode = (RtreeNode *)sqlite3_malloc(sizeof(RtreeNode) + pRtree->iNodeSize); - if( !pNode ){ - *ppNode = 0; - return SQLITE_NOMEM; - } - pNode->pParent = pParent; - pNode->zData = (u8 *)&pNode[1]; - pNode->nRef = 1; - pNode->iNode = iNode; - pNode->isDirty = 0; - pNode->pNext = 0; - sqlite3_bind_int64(pRtree->pReadNode, 1, iNode); rc = sqlite3_step(pRtree->pReadNode); if( rc==SQLITE_ROW ){ const u8 *zBlob = sqlite3_column_blob(pRtree->pReadNode, 0); - assert( sqlite3_column_bytes(pRtree->pReadNode, 0)==pRtree->iNodeSize ); - memcpy(pNode->zData, zBlob, pRtree->iNodeSize); - nodeReference(pParent); + if( pRtree->iNodeSize==sqlite3_column_bytes(pRtree->pReadNode, 0) ){ + pNode = (RtreeNode *)sqlite3_malloc(sizeof(RtreeNode)+pRtree->iNodeSize); + if( !pNode ){ + rc2 = SQLITE_NOMEM; + }else{ + pNode->pParent = pParent; + pNode->zData = (u8 *)&pNode[1]; + pNode->nRef = 1; + pNode->iNode = iNode; + pNode->isDirty = 0; + pNode->pNext = 0; + memcpy(pNode->zData, zBlob, pRtree->iNodeSize); + nodeReference(pParent); + } + } + } + rc = sqlite3_reset(pRtree->pReadNode); + if( rc==SQLITE_OK ) rc = rc2; + + /* If the root node was just loaded, set pRtree->iDepth to the height + ** of the r-tree structure. A height of zero means all data is stored on + ** the root node. A height of one means the children of the root node + ** are the leaves, and so on. If the depth as specified on the root node + ** is greater than RTREE_MAX_DEPTH, the r-tree structure must be corrupt. + */ + if( pNode && iNode==1 ){ + pRtree->iDepth = readInt16(pNode->zData); + if( pRtree->iDepth>RTREE_MAX_DEPTH ){ + rc = SQLITE_CORRUPT; + } + } + + /* If no error has occurred so far, check if the "number of entries" + ** field on the node is too large. If so, set the return code to + ** SQLITE_CORRUPT. + */ + if( pNode && rc==SQLITE_OK ){ + if( NCELL(pNode)>((pRtree->iNodeSize-4)/pRtree->nBytesPerCell) ){ + rc = SQLITE_CORRUPT; + } + } + + if( rc==SQLITE_OK ){ + if( pNode!=0 ){ + nodeHashInsert(pRtree, pNode); + }else{ + rc = SQLITE_CORRUPT; + } + *ppNode = pNode; }else{ sqlite3_free(pNode); - pNode = 0; + *ppNode = 0; } - *ppNode = pNode; - rc = sqlite3_reset(pRtree->pReadNode); - - if( rc==SQLITE_OK && iNode==1 ){ - pRtree->iDepth = readInt16(pNode->zData); - } - - assert( (rc==SQLITE_OK && pNode) || (pNode==0 && rc!=SQLITE_OK) ); - nodeHashInsert(pRtree, pNode); - return rc; } @@ -492,8 +594,7 @@ nodeInsertCell( nMaxCell = (pRtree->iNodeSize-4)/pRtree->nBytesPerCell; nCell = NCELL(pNode); - assert(nCell<=nMaxCell); - + assert( nCell<=nMaxCell ); if( nCellzData[2], nCell+1); @@ -713,6 +814,25 @@ static int rtreeOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ return rc; } + +/* +** Free the RtreeCursor.aConstraint[] array and its contents. +*/ +static void freeCursorConstraints(RtreeCursor *pCsr){ + if( pCsr->aConstraint ){ + int i; /* Used to iterate through constraint array */ + for(i=0; inConstraint; i++){ + sqlite3_rtree_geometry *pGeom = pCsr->aConstraint[i].pGeom; + if( pGeom ){ + if( pGeom->xDelUser ) pGeom->xDelUser(pGeom->pUser); + sqlite3_free(pGeom); + } + } + sqlite3_free(pCsr->aConstraint); + pCsr->aConstraint = 0; + } +} + /* ** Rtree virtual table module xClose method. */ @@ -720,7 +840,7 @@ static int rtreeClose(sqlite3_vtab_cursor *cur){ Rtree *pRtree = (Rtree *)(cur->pVtab); int rc; RtreeCursor *pCsr = (RtreeCursor *)cur; - sqlite3_free(pCsr->aConstraint); + freeCursorConstraints(pCsr); rc = nodeRelease(pRtree, pCsr->pNode); sqlite3_free(pCsr); return rc; @@ -737,16 +857,43 @@ static int rtreeEof(sqlite3_vtab_cursor *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; + double aCoord[RTREE_MAX_DIMENSIONS*2]; + int nCoord = pRtree->nDim*2; + + assert( pConstraint->op==RTREE_MATCH ); + assert( pConstraint->pGeom ); + + for(i=0; iaCoord[i]); + } + return pConstraint->xGeom(pConstraint->pGeom, nCoord, aCoord, pbRes); +} + /* ** Cursor pCursor currently points to a cell in a non-leaf page. -** Return true if the sub-tree headed by the cell is filtered +** 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){ +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 && iinConstraint; ii++){ @@ -755,31 +902,51 @@ static int testRtreeCell(Rtree *pRtree, RtreeCursor *pCursor){ double 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_GT || p->op==RTREE_EQ || p->op==RTREE_MATCH ); switch( p->op ){ - case RTREE_LE: case RTREE_LT: bRes = p->rValuerValue>cell_max; break; - case RTREE_EQ: + case RTREE_LE: case RTREE_LT: + bRes = p->rValuerValue>cell_max; + break; + + case RTREE_EQ: bRes = (p->rValue>cell_max || p->rValueop==RTREE_MATCH ); + rc = testRtreeGeom(pRtree, p, &cell, &bRes); + bRes = !bRes; + break; + } } } - return bRes; + *pbEof = bRes; + return rc; } /* -** Return true if the cell that cursor pCursor currently points to +** Test if the cell that cursor pCursor currently points to ** would be filtered (excluded) by the constraints in the -** pCursor->aConstraint[] array, or false otherwise. +** 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){ +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; iinConstraint; ii++){ @@ -787,7 +954,7 @@ static int testRtreeEntry(Rtree *pRtree, RtreeCursor *pCursor){ double 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_GT || p->op==RTREE_EQ || p->op==RTREE_MATCH ); switch( p->op ){ case RTREE_LE: res = (coord<=p->rValue); break; @@ -795,12 +962,24 @@ static int testRtreeEntry(Rtree *pRtree, RtreeCursor *pCursor){ 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 ) return 1; + if( !res ){ + *pbEof = 1; + return SQLITE_OK; + } } - return 0; + return SQLITE_OK; } /* @@ -827,19 +1006,18 @@ static int descendToCell( assert( iHeight>=0 ); if( iHeight==0 ){ - isEof = testRtreeEntry(pRtree, pCursor); + rc = testRtreeEntry(pRtree, pCursor, &isEof); }else{ - isEof = testRtreeCell(pRtree, pCursor); + rc = testRtreeCell(pRtree, pCursor, &isEof); } - if( isEof || iHeight==0 ){ - *pEof = isEof; - return SQLITE_OK; + 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 ){ - return rc; + goto descend_to_cell_out; } nodeRelease(pRtree, pCursor->pNode); @@ -849,7 +1027,7 @@ static int descendToCell( pCursor->iCell = ii; rc = descendToCell(pRtree, pCursor, iHeight-1, &isEof); if( rc!=SQLITE_OK ){ - return rc; + goto descend_to_cell_out; } } @@ -861,32 +1039,43 @@ static int descendToCell( pCursor->iCell = iSavedCell; } +descend_to_cell_out: *pEof = isEof; - return SQLITE_OK; + return rc; } /* ** 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. */ -static int nodeRowidIndex(Rtree *pRtree, RtreeNode *pNode, i64 iRowid){ +static int nodeRowidIndex( + Rtree *pRtree, + RtreeNode *pNode, + i64 iRowid, + int *piIndex +){ int ii; - for(ii=0; nodeGetRowid(pRtree, pNode, ii)!=iRowid; ii++){ - assert( ii<(NCELL(pNode)-1) ); + int nCell = NCELL(pNode); + for(ii=0; iipParent; if( pParent ){ - return nodeRowidIndex(pRtree, pParent, pNode->iNode); + return nodeRowidIndex(pRtree, pParent, pNode->iNode, piIndex); } - return -1; + *piIndex = -1; + return SQLITE_OK; } /* @@ -897,13 +1086,17 @@ static int rtreeNext(sqlite3_vtab_cursor *pVtabCursor){ 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 if( pCsr->pNode ){ + }else{ /* Move to the next entry that matches the configured constraints. */ int iHeight = 0; while( pCsr->pNode ){ @@ -917,7 +1110,10 @@ static int rtreeNext(sqlite3_vtab_cursor *pVtabCursor){ } } pCsr->pNode = pNode->pParent; - pCsr->iCell = nodeParentIndex(pRtree, pNode); + rc = nodeParentIndex(pRtree, pNode, &pCsr->iCell); + if( rc!=SQLITE_OK ){ + return rc; + } nodeReference(pCsr->pNode); nodeRelease(pRtree, pNode); iHeight++; @@ -985,6 +1181,51 @@ static int findLeafNode(Rtree *pRtree, i64 iRowid, RtreeNode **ppLeaf){ return rc; } +/* +** This function is called to configure the RtreeConstraint object passed +** 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; + + /* 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. */ + nBlob = sqlite3_value_bytes(pValue); + if( nBlob<(int)sizeof(RtreeMatchArg) + || ((nBlob-sizeof(RtreeMatchArg))%sizeof(double))!=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(double)) + ){ + sqlite3_free(pGeom); + return SQLITE_ERROR; + } + + pGeom->pContext = p->pContext; + pGeom->nParam = p->nParam; + pGeom->aParam = p->aParam; + + pCons->xGeom = p->xGeom; + pCons->pGeom = pGeom; + return SQLITE_OK; +} /* ** Rtree virtual table module xFilter method. @@ -1003,8 +1244,7 @@ static int rtreeFilter( rtreeReference(pRtree); - sqlite3_free(pCsr->aConstraint); - pCsr->aConstraint = 0; + freeCursorConstraints(pCsr); pCsr->iStrategy = idxNum; if( idxNum==1 ){ @@ -1013,8 +1253,9 @@ static int rtreeFilter( i64 iRowid = sqlite3_value_int64(argv[0]); rc = findLeafNode(pRtree, iRowid, &pLeaf); pCsr->pNode = pLeaf; - if( pLeaf && rc==SQLITE_OK ){ - pCsr->iCell = nodeRowidIndex(pRtree, pLeaf, iRowid); + if( pLeaf ){ + assert( rc==SQLITE_OK ); + rc = nodeRowidIndex(pRtree, pLeaf, iRowid, &pCsr->iCell); } }else{ /* Normal case - r-tree scan. Set up the RtreeCursor.aConstraint array @@ -1026,12 +1267,24 @@ static int rtreeFilter( if( !pCsr->aConstraint ){ rc = SQLITE_NOMEM; }else{ + memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*argc); assert( (idxStr==0 && argc==0) || strlen(idxStr)==argc*2 ); for(ii=0; iiaConstraint[ii]; p->op = idxStr[ii*2]; p->iCoord = idxStr[ii*2+1]-'a'; - p->rValue = sqlite3_value_double(argv[ii]); + 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; + } + }else{ + p->rValue = sqlite3_value_double(argv[ii]); + } } } } @@ -1091,6 +1344,7 @@ static int rtreeFilter( ** < 0x43 ('C') ** >= 0x44 ('D') ** > 0x45 ('E') +** MATCH 0x46 ('F') ** ---------------------- ** ** The second of each pair of bytes identifies the coordinate column @@ -1104,6 +1358,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ 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; iinConstraint; ii++){ @@ -1129,7 +1384,9 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ return SQLITE_OK; } - if( p->usable && p->iColumn>0 ){ + if( p->usable && (p->iColumn>0 || p->op==SQLITE_INDEX_CONSTRAINT_MATCH) ){ + int j, opmsk; + static const unsigned char compatible[] = { 0, 0, 1, 1, 2, 2 }; u8 op = 0; switch( p->op ){ case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break; @@ -1137,31 +1394,33 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ case SQLITE_INDEX_CONSTRAINT_LE: op = RTREE_LE; break; case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; break; case SQLITE_INDEX_CONSTRAINT_GE: op = RTREE_GE; break; + default: + assert( p->op==SQLITE_INDEX_CONSTRAINT_MATCH ); + op = RTREE_MATCH; + break; } - if( op ){ - /* Make sure this particular constraint has not been used before. - ** If it has been used before, ignore it. - ** - ** A <= or < can be used if there is a prior >= or >. - ** A >= or > can be used if there is a prior < or <=. - ** A <= or < is disqualified if there is a prior <=, <, or ==. - ** A >= or > is disqualified if there is a prior >=, >, or ==. - ** A == is disqualifed if there is any prior constraint. - */ - int j, opmsk; - static const unsigned char compatible[] = { 0, 0, 1, 1, 2, 2 }; - assert( compatible[RTREE_EQ & 7]==0 ); - assert( compatible[RTREE_LT & 7]==1 ); - assert( compatible[RTREE_LE & 7]==1 ); - assert( compatible[RTREE_GT & 7]==2 ); - assert( compatible[RTREE_GE & 7]==2 ); - cCol = p->iColumn - 1 + 'a'; - opmsk = compatible[op & 7]; - for(j=0; j= or >. + ** A >= or > can be used if there is a prior < or <=. + ** A <= or < is disqualified if there is a prior <=, <, or ==. + ** A >= or > is disqualified if there is a prior >=, >, or ==. + ** A == is disqualifed if there is any prior constraint. + */ + assert( compatible[RTREE_EQ & 7]==0 ); + assert( compatible[RTREE_LT & 7]==1 ); + assert( compatible[RTREE_LE & 7]==1 ); + assert( compatible[RTREE_GT & 7]==2 ); + assert( compatible[RTREE_GE & 7]==2 ); + cCol = p->iColumn - 1 + 'a'; + opmsk = compatible[op & 7]; + for(j=0; jnDim*2); jj+=2){ @@ -1362,22 +1627,31 @@ static int ChooseLeaf( ** the smallest area. */ for(iCell=0; iCelliDepth-1) ){ overlap = cellOverlapEnlargement(pRtree,&cell,pCell,aCell,nCell,iCell); } -#endif if( (iCell==0) || (overlappParent ){ - RtreeCell cell; RtreeNode *pParent = p->pParent; - int iCell = nodeParentIndex(pRtree, p); + RtreeCell cell; + int iCell; + + if( nodeParentIndex(pRtree, p, &iCell) ){ + return SQLITE_CORRUPT; + } nodeGetCell(pRtree, pParent, iCell, &cell); if( !cellContains(pRtree, &cell, pCell) ){ @@ -1419,6 +1697,7 @@ static void AdjustTree( p = pParent; } + return SQLITE_OK; } /* @@ -1947,14 +2226,14 @@ static int SplitNode( nCell++; if( pNode->iNode==1 ){ - pRight = nodeNew(pRtree, pNode, 1); - pLeft = nodeNew(pRtree, pNode, 1); + pRight = nodeNew(pRtree, pNode); + pLeft = nodeNew(pRtree, pNode); pRtree->iDepth++; pNode->isDirty = 1; writeInt16(pNode->zData, pRtree->iDepth); }else{ pLeft = pNode; - pRight = nodeNew(pRtree, pLeft->pParent, 1); + pRight = nodeNew(pRtree, pLeft->pParent); nodeReference(pLeft); } @@ -1971,8 +2250,12 @@ static int SplitNode( goto splitnode_out; } - /* Ensure both child nodes have node numbers assigned to them. */ - if( (0==pRight->iNode && SQLITE_OK!=(rc = nodeWrite(pRtree, pRight))) + /* Ensure both child nodes have node numbers assigned to them by calling + ** nodeWrite(). Node pRight always needs a node number, as it was created + ** by nodeNew() above. But node pLeft sometimes already has a node number. + ** In this case avoid the all to nodeWrite(). + */ + if( SQLITE_OK!=(rc = nodeWrite(pRtree, pRight)) || (0==pLeft->iNode && SQLITE_OK!=(rc = nodeWrite(pRtree, pLeft))) ){ goto splitnode_out; @@ -1988,9 +2271,15 @@ static int SplitNode( } }else{ RtreeNode *pParent = pLeft->pParent; - int iCell = nodeParentIndex(pRtree, pLeft); - nodeOverwriteCell(pRtree, pParent, &leftbbox, iCell); - AdjustTree(pRtree, pParent, &leftbbox); + int iCell; + rc = nodeParentIndex(pRtree, pLeft, &iCell); + if( rc==SQLITE_OK ){ + nodeOverwriteCell(pRtree, pParent, &leftbbox, iCell); + rc = AdjustTree(pRtree, pParent, &leftbbox); + } + if( rc!=SQLITE_OK ){ + goto splitnode_out; + } } if( (rc = rtreeInsertCell(pRtree, pRight->pParent, &rightbbox, iHeight+1)) ){ goto splitnode_out; @@ -2034,20 +2323,43 @@ splitnode_out: return rc; } +/* +** If node pLeaf is not the root of the r-tree and its pParent pointer is +** still NULL, load all ancestor nodes of pLeaf into memory and populate +** the pLeaf->pParent chain all the way up to the root node. +** +** This operation is required when a row is deleted (or updated - an update +** is implemented as a delete followed by an insert). SQLite provides the +** rowid of the row to delete, which can be used to find the leaf on which +** the entry resides (argument pLeaf). Once the leaf is located, this +** function is called to determine its ancestry. +*/ static int fixLeafParent(Rtree *pRtree, RtreeNode *pLeaf){ int rc = SQLITE_OK; - if( pLeaf->iNode!=1 && pLeaf->pParent==0 ){ - sqlite3_bind_int64(pRtree->pReadParent, 1, pLeaf->iNode); - if( sqlite3_step(pRtree->pReadParent)==SQLITE_ROW ){ - i64 iNode = sqlite3_column_int64(pRtree->pReadParent, 0); - rc = nodeAcquire(pRtree, iNode, 0, &pLeaf->pParent); - }else{ - rc = SQLITE_ERROR; - } - sqlite3_reset(pRtree->pReadParent); - if( rc==SQLITE_OK ){ - rc = fixLeafParent(pRtree, pLeaf->pParent); + RtreeNode *pChild = pLeaf; + while( rc==SQLITE_OK && pChild->iNode!=1 && pChild->pParent==0 ){ + int rc2 = SQLITE_OK; /* sqlite3_reset() return code */ + sqlite3_bind_int64(pRtree->pReadParent, 1, pChild->iNode); + rc = sqlite3_step(pRtree->pReadParent); + if( rc==SQLITE_ROW ){ + RtreeNode *pTest; /* Used to test for reference loops */ + i64 iNode; /* Node number of parent node */ + + /* Before setting pChild->pParent, test that we are not creating a + ** loop of references (as we would if, say, pChild==pParent). We don't + ** want to do this as it leads to a memory leak when trying to delete + ** the referenced counted node structures. + */ + iNode = sqlite3_column_int64(pRtree->pReadParent, 0); + for(pTest=pLeaf; pTest && pTest->iNode!=iNode; pTest=pTest->pParent); + if( !pTest ){ + rc2 = nodeAcquire(pRtree, iNode, 0, &pChild->pParent); + } } + rc = sqlite3_reset(pRtree->pReadParent); + if( rc==SQLITE_OK ) rc = rc2; + if( rc==SQLITE_OK && !pChild->pParent ) rc = SQLITE_CORRUPT; + pChild = pChild->pParent; } return rc; } @@ -2056,18 +2368,24 @@ static int deleteCell(Rtree *, RtreeNode *, int, int); static int removeNode(Rtree *pRtree, RtreeNode *pNode, int iHeight){ int rc; + int rc2; RtreeNode *pParent; int iCell; assert( pNode->nRef==1 ); /* Remove the entry in the parent cell. */ - iCell = nodeParentIndex(pRtree, pNode); - pParent = pNode->pParent; - pNode->pParent = 0; - if( SQLITE_OK!=(rc = deleteCell(pRtree, pParent, iCell, iHeight+1)) - || SQLITE_OK!=(rc = nodeRelease(pRtree, pParent)) - ){ + rc = nodeParentIndex(pRtree, pNode, &iCell); + if( rc==SQLITE_OK ){ + pParent = pNode->pParent; + pNode->pParent = 0; + rc = deleteCell(pRtree, pParent, iCell, iHeight+1); + } + rc2 = nodeRelease(pRtree, pParent); + if( rc==SQLITE_OK ){ + rc = rc2; + } + if( rc!=SQLITE_OK ){ return rc; } @@ -2097,8 +2415,9 @@ static int removeNode(Rtree *pRtree, RtreeNode *pNode, int iHeight){ return SQLITE_OK; } -static void fixBoundingBox(Rtree *pRtree, RtreeNode *pNode){ +static int fixBoundingBox(Rtree *pRtree, RtreeNode *pNode){ RtreeNode *pParent = pNode->pParent; + int rc = SQLITE_OK; if( pParent ){ int ii; int nCell = NCELL(pNode); @@ -2110,10 +2429,13 @@ static void fixBoundingBox(Rtree *pRtree, RtreeNode *pNode){ cellUnion(pRtree, &box, &cell); } box.iRowid = pNode->iNode; - ii = nodeParentIndex(pRtree, pNode); - nodeOverwriteCell(pRtree, pParent, &box, ii); - fixBoundingBox(pRtree, pParent); + rc = nodeParentIndex(pRtree, pNode, &ii); + if( rc==SQLITE_OK ){ + nodeOverwriteCell(pRtree, pParent, &box, ii); + rc = fixBoundingBox(pRtree, pParent); + } } + return rc; } /* @@ -2121,6 +2443,7 @@ static void fixBoundingBox(Rtree *pRtree, RtreeNode *pNode){ ** cell, adjust the r-tree data structure if required. */ static int deleteCell(Rtree *pRtree, RtreeNode *pNode, int iCell, int iHeight){ + RtreeNode *pParent; int rc; if( SQLITE_OK!=(rc = fixLeafParent(pRtree, pNode)) ){ @@ -2137,14 +2460,13 @@ static int deleteCell(Rtree *pRtree, RtreeNode *pNode, int iCell, int iHeight){ ** cell in the parent node so that it tightly contains the updated ** node. */ - if( pNode->iNode!=1 ){ - RtreeNode *pParent = pNode->pParent; - if( (pParent->iNode!=1 || NCELL(pParent)!=1) - && (NCELL(pNode)pParent; + assert( pParent || pNode->iNode==1 ); + if( pParent ){ + if( NCELL(pNode)iNode currently contains @@ -2281,11 +2603,13 @@ static int rtreeInsertCell( rc = SplitNode(pRtree, pNode, pCell, iHeight); #endif }else{ - AdjustTree(pRtree, pNode, pCell); - if( iHeight==0 ){ - rc = rowidWrite(pRtree, pCell->iRowid, pNode->iNode); - }else{ - rc = parentWrite(pRtree, pCell->iRowid, pNode->iNode); + rc = AdjustTree(pRtree, pNode, pCell); + if( rc==SQLITE_OK ){ + if( iHeight==0 ){ + rc = rowidWrite(pRtree, pCell->iRowid, pNode->iNode); + }else{ + rc = parentWrite(pRtree, pCell->iRowid, pNode->iNode); + } } } return rc; @@ -2330,16 +2654,6 @@ static int newRowid(Rtree *pRtree, i64 *piRowid){ return rc; } -#ifndef NDEBUG -static int hashIsEmpty(Rtree *pRtree){ - int ii; - for(ii=0; iiaHash[ii] ); - } - return 1; -} -#endif - /* ** The xUpdate method for rtree module virtual tables. */ @@ -2355,7 +2669,6 @@ static int rtreeUpdate( rtreeReference(pRtree); assert(nData>=1); - assert(hashIsEmpty(pRtree)); /* If azData[0] is not an SQL NULL value, it is the rowid of a ** record to delete from the r-tree table. The following block does @@ -2381,8 +2694,10 @@ static int rtreeUpdate( /* Delete the cell in question from the leaf node. */ if( rc==SQLITE_OK ){ int rc2; - iCell = nodeRowidIndex(pRtree, pLeaf, iDelete); - rc = deleteCell(pRtree, pLeaf, iCell, 0); + rc = nodeRowidIndex(pRtree, pLeaf, iDelete, &iCell); + if( rc==SQLITE_OK ){ + rc = deleteCell(pRtree, pLeaf, iCell, 0); + } rc2 = nodeRelease(pRtree, pLeaf); if( rc==SQLITE_OK ){ rc = rc2; @@ -2404,19 +2719,20 @@ static int rtreeUpdate( ** the root node (the operation that Gutman's paper says to perform ** in this scenario). */ - if( rc==SQLITE_OK && pRtree->iDepth>0 ){ - if( rc==SQLITE_OK && NCELL(pRoot)==1 ){ - RtreeNode *pChild; - i64 iChild = nodeGetRowid(pRtree, pRoot, 0); - rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); - if( rc==SQLITE_OK ){ - rc = removeNode(pRtree, pChild, pRtree->iDepth-1); - } - if( rc==SQLITE_OK ){ - pRtree->iDepth--; - writeInt16(pRoot->zData, pRtree->iDepth); - pRoot->isDirty = 1; - } + if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){ + int rc2; + RtreeNode *pChild; + i64 iChild = nodeGetRowid(pRtree, pRoot, 0); + rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); + if( rc==SQLITE_OK ){ + rc = removeNode(pRtree, pChild, pRtree->iDepth-1); + } + rc2 = nodeRelease(pRtree, pChild); + if( rc==SQLITE_OK ) rc = rc2; + if( rc==SQLITE_OK ){ + pRtree->iDepth--; + writeInt16(pRoot->zData, pRtree->iDepth); + pRoot->isDirty = 1; } } @@ -2706,7 +3022,7 @@ static int rtreeInit( Rtree *pRtree; int nDb; /* Length of string argv[1] */ int nName; /* Length of string argv[2] */ - int eCoordType = (int)pAux; + int eCoordType = (pAux ? RTREE_COORD_INT32 : RTREE_COORD_REAL32); const char *aErrMsg[] = { 0, /* 0 */ @@ -2803,6 +3119,7 @@ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ Rtree tree; int ii; + UNUSED_PARAMETER(nArg); memset(&node, 0, sizeof(RtreeNode)); memset(&tree, 0, sizeof(Rtree)); tree.nDim = sqlite3_value_int(apArg[0]); @@ -2836,6 +3153,7 @@ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ } 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 ){ @@ -2852,14 +3170,11 @@ static void rtreedepth(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ ** function "rtreenode". */ int sqlite3RtreeInit(sqlite3 *db){ - int rc = SQLITE_OK; + const int utf8 = SQLITE_UTF8; + int rc; + rc = sqlite3_create_function(db, "rtreenode", 2, utf8, 0, rtreenode, 0, 0); if( rc==SQLITE_OK ){ - int utf8 = SQLITE_UTF8; - rc = sqlite3_create_function(db, "rtreenode", 2, utf8, 0, rtreenode, 0, 0); - } - if( rc==SQLITE_OK ){ - int utf8 = SQLITE_UTF8; rc = sqlite3_create_function(db, "rtreedepth", 1, utf8, 0,rtreedepth, 0, 0); } if( rc==SQLITE_OK ){ @@ -2874,6 +3189,70 @@ int sqlite3RtreeInit(sqlite3 *db){ 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. +*/ +static void doSqlite3Free(void *p){ + 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. +** +** The scalar user functions return a blob that is interpreted by r-tree +** table MATCH operators. +*/ +static void geomCallback(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){ + RtreeGeomCallback *pGeomCtx = (RtreeGeomCallback *)sqlite3_user_data(ctx); + RtreeMatchArg *pBlob; + int nBlob; + + nBlob = sizeof(RtreeMatchArg) + (nArg-1)*sizeof(double); + pBlob = (RtreeMatchArg *)sqlite3_malloc(nBlob); + if( !pBlob ){ + sqlite3_result_error_nomem(ctx); + }else{ + int i; + pBlob->magic = RTREE_GEOMETRY_MAGIC; + pBlob->xGeom = pGeomCtx->xGeom; + pBlob->pContext = pGeomCtx->pContext; + pBlob->nParam = nArg; + for(i=0; iaParam[i] = sqlite3_value_double(aArg[i]); + } + sqlite3_result_blob(ctx, pBlob, nBlob, doSqlite3Free); + } +} + +/* +** Register a new geometry function for use with the r-tree MATCH operator. +*/ +int sqlite3_rtree_geometry_callback( + sqlite3 *db, + const char *zGeom, + int (*xGeom)(sqlite3_rtree_geometry *, int, double *, int *), + void *pContext +){ + 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->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 + ); +} + #if !SQLITE_CORE int sqlite3_extension_init( sqlite3 *db, diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test index f27cb758..fe5fa0ae 100644 --- a/ext/rtree/rtree1.test +++ b/ext/rtree/rtree1.test @@ -13,7 +13,7 @@ # if {![info exists testdir]} { - set testdir [file join [file dirname $argv0] .. .. test] + set testdir [file join [file dirname [info script]] .. .. test] } source [file join [file dirname [info script]] rtree_util.tcl] source $testdir/tester.tcl diff --git a/ext/rtree/rtree2.test b/ext/rtree/rtree2.test index 307e7e02..f5d15cc6 100644 --- a/ext/rtree/rtree2.test +++ b/ext/rtree/rtree2.test @@ -13,7 +13,7 @@ # if {![info exists testdir]} { - set testdir [file join [file dirname $argv0] .. .. test] + set testdir [file join [file dirname [info script]] .. .. test] } source [file join [file dirname [info script]] rtree_util.tcl] source $testdir/tester.tcl @@ -27,7 +27,7 @@ set ::NROW 1000 set ::NDEL 10 set ::NSELECT 100 -if {[info exists ISQUICK] && $ISQUICK} { +if {[info exists G(isquick)] && $G(isquick)} { set ::NROW 100 set ::NSELECT 10 } diff --git a/ext/rtree/rtree3.test b/ext/rtree/rtree3.test index 1ce131c5..fea55130 100644 --- a/ext/rtree/rtree3.test +++ b/ext/rtree/rtree3.test @@ -14,52 +14,93 @@ # if {![info exists testdir]} { - set testdir [file join [file dirname $argv0] .. .. test] + set testdir [file join [file dirname [info script]] .. .. test] } source $testdir/tester.tcl - +source $testdir/malloc_common.tcl ifcapable !rtree { finish_test return } -# Only run these tests if memory debugging is turned on. +# Test summary: # -source $testdir/malloc_common.tcl -if {!$MEMDEBUG} { - puts "Skipping malloc tests: not compiled with -DSQLITE_MEMDEBUG..." - finish_test - return +# rtree3-1: Test OOM in simple CREATE TABLE, INSERT, DELETE and SELECT +# commands on an almost empty table. +# +# rtree3-2: Test OOM in a DROP TABLE command. +# +# rtree3-3a: Test OOM during a transaction to insert 100 pseudo-random rows. +# +# rtree3-3b: Test OOM during a transaction deleting all entries in the +# database constructed in [rtree3-3a] in pseudo-random order. +# +# rtree3-4a: OOM during "SELECT count(*) FROM ..." on a big table. +# +# rtree3-4b: OOM while deleting rows from a big table. +# +# rtree3-5: Test OOM while inserting rows into a big table. +# +# rtree3-6: Test OOM while deleting all rows of a table, one at a time. +# +# rtree3-7: OOM during an ALTER TABLE RENAME TABLE command. +# +# rtree3-8: Test OOM while registering the r-tree module with sqlite. +# + +do_faultsim_test rtree3-1 -faults oom* -prep { + faultsim_delete_and_reopen +} -body { + execsql { + BEGIN TRANSACTION; + CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2); + INSERT INTO rt VALUES(NULL, 3, 5, 7, 9); + INSERT INTO rt VALUES(NULL, 13, 15, 17, 19); + DELETE FROM rt WHERE ii = 1; + SELECT * FROM rt; + SELECT ii FROM rt WHERE ii = 2; + COMMIT; + } } -do_malloc_test rtree3-1 -sqlbody { - BEGIN TRANSACTION; - CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2); - INSERT INTO rt VALUES(NULL, 3, 5, 7, 9); - INSERT INTO rt VALUES(NULL, 13, 15, 17, 19); - DELETE FROM rt WHERE ii = 1; - SELECT * FROM rt; - SELECT ii FROM rt WHERE ii = 2; - COMMIT; -} -do_malloc_test rtree3-2 -sqlprep { - CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2); - INSERT INTO rt VALUES(NULL, 3, 5, 7, 9); -} -sqlbody { - DROP TABLE rt; -} +do_test rtree3-2.prep { + faultsim_delete_and_reopen + execsql { + CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2); + INSERT INTO rt VALUES(NULL, 3, 5, 7, 9); + } + faultsim_save_and_close +} {} +do_faultsim_test rtree3-2 -faults oom* -prep { + faultsim_restore_and_reopen +} -body { + execsql { DROP TABLE rt } +} +do_malloc_test rtree3-3.prep { + faultsim_delete_and_reopen + execsql { + CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2); + INSERT INTO rt VALUES(NULL, 3, 5, 7, 9); + } + faultsim_save_and_close +} {} -do_malloc_test rtree3-3 -sqlprep { - CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2); - INSERT INTO rt VALUES(NULL, 3, 5, 7, 9); -} -tclbody { +do_faultsim_test rtree3-3a -faults oom* -prep { + faultsim_restore_and_reopen +} -body { db eval BEGIN for {set ii 0} {$ii < 100} {incr ii} { set f [expr rand()] db eval {INSERT INTO rt VALUES(NULL, $f*10.0, $f*10.0, $f*15.0, $f*15.0)} } db eval COMMIT +} +faultsim_save_and_close + +do_faultsim_test rtree3-3b -faults oom* -prep { + faultsim_restore_and_reopen +} -body { db eval BEGIN for {set ii 0} {$ii < 100} {incr ii} { set f [expr rand()] @@ -68,4 +109,129 @@ do_malloc_test rtree3-3 -sqlprep { db eval COMMIT } +do_test rtree3-4.prep { + faultsim_delete_and_reopen + execsql { + BEGIN; + PRAGMA page_size = 512; + CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2); + } + for {set i 0} {$i < 1500} {incr i} { + execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) } + } + execsql { COMMIT } + faultsim_save_and_close +} {} + +do_faultsim_test rtree3-4a -faults oom-* -prep { + faultsim_restore_and_reopen +} -body { + db eval { SELECT count(*) FROM rt } +} -test { + faultsim_test_result {0 1500} +} + +do_faultsim_test rtree3-4b -faults oom-transient -prep { + faultsim_restore_and_reopen +} -body { + db eval { DELETE FROM rt WHERE ii BETWEEN 1 AND 100 } +} -test { + faultsim_test_result {0 {}} +} + +do_test rtree3-5.prep { + faultsim_delete_and_reopen + execsql { + BEGIN; + PRAGMA page_size = 512; + CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2); + } + for {set i 0} {$i < 100} {incr i} { + execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) } + } + execsql { COMMIT } + faultsim_save_and_close +} {} +do_faultsim_test rtree3-5 -faults oom-* -prep { + faultsim_restore_and_reopen +} -body { + for {set i 100} {$i < 110} {incr i} { + execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) } + } +} -test { + faultsim_test_result {0 {}} +} + +do_test rtree3-6.prep { + faultsim_delete_and_reopen + execsql { + BEGIN; + PRAGMA page_size = 512; + CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2); + } + for {set i 0} {$i < 50} {incr i} { + execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) } + } + execsql { COMMIT } + faultsim_save_and_close +} {} +do_faultsim_test rtree3-6 -faults oom-* -prep { + faultsim_restore_and_reopen +} -body { + execsql BEGIN + for {set i 0} {$i < 50} {incr i} { + execsql { DELETE FROM rt WHERE ii=$i } + } + execsql COMMIT +} -test { + faultsim_test_result {0 {}} +} + +do_test rtree3-7.prep { + faultsim_delete_and_reopen + execsql { CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2) } + faultsim_save_and_close +} {} +do_faultsim_test rtree3-7 -faults oom-* -prep { + faultsim_restore_and_reopen +} -body { + execsql { ALTER TABLE rt RENAME TO rt2 } +} -test { + faultsim_test_result {0 {}} +} + +do_faultsim_test rtree3-8 -faults oom-* -prep { + catch { db close } +} -body { + sqlite3 db test.db +} + +do_faultsim_test rtree3-9 -faults oom-* -prep { + sqlite3 db :memory: +} -body { + set rc [register_cube_geom db] + if {$rc != "SQLITE_OK"} { error $rc } +} -test { + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} +} + +do_test rtree3-10.prep { + faultsim_delete_and_reopen + execsql { + CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2, z1, z2); + INSERT INTO rt VALUES(1, 10, 10, 10, 11, 11, 11); + INSERT INTO rt VALUES(2, 5, 6, 6, 7, 7, 8); + } + faultsim_save_and_close +} {} +do_faultsim_test rtree3-10 -faults oom-* -prep { + faultsim_restore_and_reopen + register_cube_geom db + execsql { SELECT * FROM rt } +} -body { + execsql { SELECT ii FROM rt WHERE ii MATCH cube(4.5, 5.5, 6.5, 1, 1, 1) } +} -test { + faultsim_test_result {0 2} +} + finish_test diff --git a/ext/rtree/rtree4.test b/ext/rtree/rtree4.test index fa0d606f..708d335b 100644 --- a/ext/rtree/rtree4.test +++ b/ext/rtree/rtree4.test @@ -13,7 +13,7 @@ # if {![info exists testdir]} { - set testdir [file join [file dirname $argv0] .. .. test] + set testdir [file join [file dirname [info script]] .. .. test] } source $testdir/tester.tcl @@ -23,7 +23,7 @@ ifcapable !rtree { } set ::NROW 2500 -if {[info exists ISQUICK] && $ISQUICK} { +if {[info exists G(isquick)] && $G(isquick)} { set ::NROW 250 } @@ -93,7 +93,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} { for {set i 1} {$i<$::NROW} {incr i} { # Do a random insert # - do_test rtree-$nDim.2.$i.1 { + do_test rtree4-$nDim.2.$i.1 { set vlist {} for {set j 0} {$j<$nDim} {incr j} { set mn [rand 10000] @@ -113,7 +113,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} { lappend where mn$j>=$mn mx$j<=$mx } set where "WHERE [join $where { AND }]" - do_test rtree-$nDim.2.$i.2 { + do_test rtree4-$nDim.2.$i.2 { list $where [db eval "SELECT id FROM rx $where ORDER BY id"] } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]] @@ -126,7 +126,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} { lappend where mx$j>=$mn mn$j<=$mx } set where "WHERE [join $where { AND }]" - do_test rtree-$nDim.2.$i.3 { + do_test rtree4-$nDim.2.$i.3 { list $where [db eval "SELECT id FROM rx $where ORDER BY id"] } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]] @@ -143,7 +143,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} { lappend where mn$j>=$mn mx$j<=$mx } set where "WHERE [join $where { AND }]" - do_test rtree-$nDim.2.$i.3 { + do_test rtree4-$nDim.2.$i.3 { list $where [db eval "SELECT id FROM rx $where ORDER BY id"] } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]] @@ -160,7 +160,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} { lappend where mx$j>$mn mn$j<$mx } set where "WHERE [join $where { AND }]" - do_test rtree-$nDim.2.$i.4 { + do_test rtree4-$nDim.2.$i.4 { list $where [db eval "SELECT id FROM rx $where ORDER BY id"] } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]] @@ -176,7 +176,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} { lappend where mn$j>=-10000 mx$j<10000 } set where "WHERE [join $where { AND }]" - do_test rtree-$nDim.2.$i.5 { + do_test rtree4-$nDim.2.$i.5 { list $where [db eval "SELECT id FROM rx $where ORDER BY id"] } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]] @@ -192,7 +192,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} { lappend where mx$j>-10000 mn$j<=10000 } set where "WHERE [join $where { AND }]" - do_test rtree-$nDim.2.$i.6 { + do_test rtree4-$nDim.2.$i.6 { list $where [db eval "SELECT id FROM rx $where ORDER BY id"] } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]] @@ -208,7 +208,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} { lappend where mn$j>=$mn1 mn$j>$mn2 mx$j<$mx1 mx$j<=$mx2 } set where "WHERE [join [scramble $where] { AND }]" - do_test rtree-$nDim.2.$i.7 { + do_test rtree4-$nDim.2.$i.7 { list $where [db eval "SELECT id FROM rx $where ORDER BY id"] } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]] @@ -224,7 +224,7 @@ for {set nDim 1} {$nDim<=5} {incr nDim} { lappend where mx$j>=$mn1 mx$j>$mn2 mn$j<$mx1 mn$j<=$mx2 } set where "WHERE [join [scramble $where] { AND }]" - do_test rtree-$nDim.2.$i.8 { + do_test rtree4-$nDim.2.$i.8 { list $where [db eval "SELECT id FROM rx $where ORDER BY id"] } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]] } diff --git a/ext/rtree/rtree5.test b/ext/rtree/rtree5.test index 3620619f..ea2946f9 100644 --- a/ext/rtree/rtree5.test +++ b/ext/rtree/rtree5.test @@ -14,7 +14,7 @@ # if {![info exists testdir]} { - set testdir [file join [file dirname $argv0] .. .. test] + set testdir [file join [file dirname [info script]] .. .. test] } source $testdir/tester.tcl diff --git a/ext/rtree/rtree6.test b/ext/rtree/rtree6.test index bab596e0..0a29f441 100644 --- a/ext/rtree/rtree6.test +++ b/ext/rtree/rtree6.test @@ -12,7 +12,7 @@ # if {![info exists testdir]} { - set testdir [file join [file dirname $argv0] .. .. test] + set testdir [file join [file dirname [info script]] .. .. test] } source $testdir/tester.tcl @@ -71,39 +71,39 @@ do_test rtree6-1.5 { rtree_strategy {SELECT * FROM t1,t2 WHERE k=+ii AND x1<10} } {Ca} -do_test rtree6.2.1 { - query_plan {SELECT * FROM t1,t2 WHERE k=+ii AND x1<10} -} [list \ - {TABLE t1 VIRTUAL TABLE INDEX 2:Ca} \ - {TABLE t2 USING PRIMARY KEY} \ -] +do_eqp_test rtree6.2.1 { + SELECT * FROM t1,t2 WHERE k=+ii AND x1<10 +} { + 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2:Ca (~0 rows)} + 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} +} -do_test rtree6.2.2 { - query_plan {SELECT * FROM t1,t2 WHERE k=ii AND x1<10} -} [list \ - {TABLE t1 VIRTUAL TABLE INDEX 2:Ca} \ - {TABLE t2 USING PRIMARY KEY} \ -] +do_eqp_test rtree6.2.2 { + SELECT * FROM t1,t2 WHERE k=ii AND x1<10 +} { + 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2:Ca (~0 rows)} + 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} +} -do_test rtree6.2.3 { - query_plan {SELECT * FROM t1,t2 WHERE k=ii} -} [list \ - {TABLE t1 VIRTUAL TABLE INDEX 2:} \ - {TABLE t2 USING PRIMARY KEY} \ -] +do_eqp_test rtree6.2.3 { + SELECT * FROM t1,t2 WHERE k=ii +} { + 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2: (~0 rows)} + 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} +} -do_test rtree6.2.4 { - query_plan {SELECT * FROM t1,t2 WHERE v=10 and x1<10 and x2>10} -} [list \ - {TABLE t1 VIRTUAL TABLE INDEX 2:CaEb} \ - {TABLE t2} \ -] +do_eqp_test rtree6.2.4 { + SELECT * FROM t1,t2 WHERE v=10 and x1<10 and x2>10 +} { + 0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 2:CaEb (~0 rows)} + 0 1 1 {SCAN TABLE t2 (~100000 rows)} +} -do_test rtree6.2.5 { - query_plan {SELECT * FROM t1,t2 WHERE k=ii AND x13 } } + } + set res +} {1 3 2 4 3 5} +do_test rtree8-1.1.3 { + execsql { SELECT * FROM t1 } +} {1 1 3 2 2 4 3 3 5} + +# Many SELECTs on the same small table. +# +proc nested_select {n} { + set ::max $n + db eval { SELECT * FROM t1 } { + if {$id == $n} { nested_select [expr $n+1] } + } + return $::max +} +do_test rtree8-1.2.1 { populate_t1 50 } {} +do_test rtree8-1.2.2 { nested_select 1 } {51} + +# This test runs many SELECT queries simultaneously against a large +# table, causing a collision in the hash-table used to store r-tree +# nodes internally. +# +populate_t1 1500 +do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {164} +do_test rtree8-1.3.2 { + set rowids [execsql {SELECT min(rowid) FROM t1_rowid GROUP BY nodeno}] + set stmt_list [list] + foreach row $rowids { + set stmt [sqlite3_prepare db "SELECT * FROM t1 WHERE id = $row" -1 tail] + sqlite3_step $stmt + lappend res_list [sqlite3_column_int $stmt 0] + lappend stmt_list $stmt + } +} {} +do_test rtree8-1.3.3 { set res_list } $rowids +do_execsql_test rtree8-1.3.4 { SELECT count(*) FROM t1 } {1500} +do_test rtree8-1.3.5 { + foreach stmt $stmt_list { sqlite3_finalize $stmt } +} {} + + +#------------------------------------------------------------------------- +# The following block of tests - rtree8-2.* - test a couple of database +# corruption cases. In this case things are not corrupted at the b-tree +# level, but the contents of the various tables used internally by an +# r-tree table are inconsistent. +# +populate_t1 50 +do_execsql_test rtree8-2.1.1 { SELECT max(nodeno) FROM t1_node } {5} +do_execsql_test rtree8-2.1.2 { DELETE FROM t1_node } {} +for {set i 1} {$i <= 50} {incr i} { + do_catchsql_test rtree8-2.1.3.$i { + SELECT * FROM t1 WHERE id = $i + } {1 {database disk image is malformed}} +} +do_catchsql_test rtree8-2.1.4 { + SELECT * FROM t1 +} {1 {database disk image is malformed}} +do_catchsql_test rtree8-2.1.5 { + DELETE FROM t1 +} {1 {database disk image is malformed}} + +do_execsql_test rtree8-2.1.6 { + DROP TABLE t1; + CREATE VIRTUAL TABLE t1 USING rtree_i32(id, x1, x2); +} {} + + +populate_t1 50 +do_execsql_test rtree8-2.2.1 { + DELETE FROM t1_parent +} {} +do_catchsql_test rtree8-2.2.2 { + DELETE FROM t1 WHERE id=25 +} {1 {database disk image is malformed}} +do_execsql_test rtree8-2.2.3 { + DROP TABLE t1; + CREATE VIRTUAL TABLE t1 USING rtree_i32(id, x1, x2); +} {} + + +#------------------------------------------------------------------------- +# Test that trying to use the MATCH operator with the r-tree module does +# not confuse it. +# +populate_t1 10 +do_catchsql_test rtree8-3.1 { + SELECT * FROM t1 WHERE x1 MATCH '1234' +} {1 {SQL logic error or missing database}} + +#------------------------------------------------------------------------- +# Test a couple of invalid arguments to rtreedepth(). +# +do_catchsql_test rtree8-4.1 { + SELECT rtreedepth('hello world') +} {1 {Invalid argument to rtreedepth()}} +do_catchsql_test rtree8-4.2 { + SELECT rtreedepth(X'00') +} {1 {Invalid argument to rtreedepth()}} + + +#------------------------------------------------------------------------- +# Delete half of a lopsided tree. +# +do_execsql_test rtree8-5.1 { + CREATE VIRTUAL TABLE t2 USING rtree_i32(id, x1, x2) +} {} +do_test rtree8-5.2 { + execsql BEGIN + for {set i 0} {$i < 100} {incr i} { + execsql { INSERT INTO t2 VALUES($i, 100, 101) } + } + for {set i 100} {$i < 200} {incr i} { + execsql { INSERT INTO t2 VALUES($i, 1000, 1001) } + } + execsql COMMIT +} {} +do_test rtree8-5.3 { + execsql BEGIN + for {set i 0} {$i < 200} {incr i} { + execsql { DELETE FROM t2 WHERE id = $i } + } + execsql COMMIT +} {} + + +finish_test + diff --git a/ext/rtree/rtree9.test b/ext/rtree/rtree9.test new file mode 100644 index 00000000..ddee277e --- /dev/null +++ b/ext/rtree/rtree9.test @@ -0,0 +1,125 @@ +# 2010 August 28 +# +# 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 tests for the r-tree module. Specifically, it tests +# that custom r-tree queries (geometry callbacks) work. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source $testdir/tester.tcl +ifcapable !rtree { finish_test ; return } + +register_cube_geom db + +do_execsql_test rtree9-1.1 { + CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2, y1, y2, z1, z2); + INSERT INTO rt VALUES(1, 1, 2, 1, 2, 1, 2); +} {} +do_execsql_test rtree9-1.2 { + SELECT * FROM rt WHERE id MATCH cube(0, 0, 0, 2, 2, 2); +} {1 1.0 2.0 1.0 2.0 1.0 2.0} +do_execsql_test rtree9-1.3 { + SELECT * FROM rt WHERE id MATCH cube(3, 3, 3, 2, 2, 2); +} {} +do_execsql_test rtree9-1.4 { + DELETE FROM rt; +} {} + + +for {set i 0} {$i < 1000} {incr i} { + set x [expr $i%10] + set y [expr ($i/10)%10] + set z [expr ($i/100)%10] + execsql { INSERT INTO rt VALUES($i, $x, $x+1, $y, $y+1, $z, $z+1) } +} +do_execsql_test rtree9-2.1 { + SELECT id FROM rt WHERE id MATCH cube(2.5, 2.5, 2.5, 1, 1, 1) ORDER BY id; +} {222 223 232 233 322 323 332 333} +do_execsql_test rtree9-2.2 { + SELECT id FROM rt WHERE id MATCH cube(5.5, 5.5, 5.5, 1, 1, 1) ORDER BY id; +} {555 556 565 566 655 656 665 666} + + +do_execsql_test rtree9-3.1 { + CREATE VIRTUAL TABLE rt32 USING rtree_i32(id, x1, x2, y1, y2, z1, z2); +} {} +for {set i 0} {$i < 1000} {incr i} { + set x [expr $i%10] + set y [expr ($i/10)%10] + set z [expr ($i/100)%10] + execsql { INSERT INTO rt32 VALUES($i, $x, $x+1, $y, $y+1, $z, $z+1) } +} +do_execsql_test rtree9-3.2 { + SELECT id FROM rt32 WHERE id MATCH cube(3, 3, 3, 1, 1, 1) ORDER BY id; +} {222 223 224 232 233 234 242 243 244 322 323 324 332 333 334 342 343 344 422 423 424 432 433 434 442 443 444} +do_execsql_test rtree9-3.3 { + SELECT id FROM rt32 WHERE id MATCH cube(5.5, 5.5, 5.5, 1, 1, 1) ORDER BY id; +} {555 556 565 566 655 656 665 666} + + +do_catchsql_test rtree9-4.1 { + SELECT id FROM rt32 WHERE id MATCH cube(5.5, 5.5, 1, 1, 1) ORDER BY id; +} {1 {SQL logic error or missing database}} +for {set x 2} {$x<200} {incr x 2} { + do_catchsql_test rtree9-4.2.[expr $x/2] { + SELECT id FROM rt WHERE id MATCH randomblob($x) + } {1 {SQL logic error or missing database}} +} +do_catchsql_test rtree9-4.3 { + SELECT id FROM rt WHERE id MATCH CAST( + (cube(5.5, 5.5, 5.5, 1, 1, 1) || X'1234567812345678') AS blob + ) +} {1 {SQL logic error or missing database}} + + +#------------------------------------------------------------------------- +# Test the example 2d "circle" geometry callback. +# +register_circle_geom db + +breakpoint +do_execsql_test rtree9-5.1 { + CREATE VIRTUAL TABLE rt2 USING rtree(id, xmin, xmax, ymin, ymax); + + INSERT INTO rt2 VALUES(1, 1, 2, 1, 2); + INSERT INTO rt2 VALUES(2, 1, 2, -2, -1); + INSERT INTO rt2 VALUES(3, -2, -1, -2, -1); + INSERT INTO rt2 VALUES(4, -2, -1, 1, 2); + + INSERT INTO rt2 VALUES(5, 2, 3, 2, 3); + INSERT INTO rt2 VALUES(6, 2, 3, -3, -2); + INSERT INTO rt2 VALUES(7, -3, -2, -3, -2); + INSERT INTO rt2 VALUES(8, -3, -2, 2, 3); + + INSERT INTO rt2 VALUES(9, 1.8, 3, 1.8, 3); + INSERT INTO rt2 VALUES(10, 1.8, 3, -3, -1.8); + INSERT INTO rt2 VALUES(11, -3, -1.8, -3, -1.8); + INSERT INTO rt2 VALUES(12, -3, -1.8, 1.8, 3); + + INSERT INTO rt2 VALUES(13, -15, 15, 1.8, 2.2); + INSERT INTO rt2 VALUES(14, -15, 15, -2.2, -1.8); + INSERT INTO rt2 VALUES(15, 1.8, 2.2, -15, 15); + INSERT INTO rt2 VALUES(16, -2.2, -1.8, -15, 15); + + INSERT INTO rt2 VALUES(17, -100, 100, -100, 100); +} {} + +do_execsql_test rtree9-5.2 { + SELECT id FROM rt2 WHERE id MATCH circle(0.0, 0.0, 2.0); +} {1 2 3 4 13 14 15 16 17} + +do_execsql_test rtree9-5.3 { + UPDATE rt2 SET xmin=xmin+5, ymin=ymin+5, xmax=xmax+5, ymax=ymax+5; + SELECT id FROM rt2 WHERE id MATCH circle(5.0, 5.0, 2.0); +} {1 2 3 4 13 14 15 16 17} + +finish_test diff --git a/ext/rtree/rtreeA.test b/ext/rtree/rtreeA.test new file mode 100644 index 00000000..e377b013 --- /dev/null +++ b/ext/rtree/rtreeA.test @@ -0,0 +1,220 @@ +# 2010 September 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 tests for the r-tree module. Specifically, it tests +# that corrupt or inconsistent databases do not cause crashes in the r-tree +# module. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source $testdir/tester.tcl +ifcapable !rtree { finish_test ; return } + +proc create_t1 {} { + db close + forcedelete test.db + sqlite3 db test.db + execsql { + PRAGMA page_size = 1024; + CREATE VIRTUAL TABLE t1 USING rtree(id, x1, x2, y1, y2); + } +} +proc populate_t1 {} { + execsql BEGIN + for {set i 0} {$i < 500} {incr i} { + set x2 [expr $i+5] + set y2 [expr $i+5] + execsql { INSERT INTO t1 VALUES($i, $i, $x2, $i, $y2) } + } + execsql COMMIT +} + +proc truncate_node {nodeno nTrunc} { + set blob [db one {SELECT data FROM t1_node WHERE nodeno=$nodeno}] + if {$nTrunc<0} {set nTrunc "end-$nTrunc"} + set blob [string range $blob 0 $nTrunc] + db eval { UPDATE t1_node SET data = $blob WHERE nodeno=$nodeno } +} + +proc set_tree_depth {tbl {newvalue ""}} { + set blob [db one "SELECT data FROM ${tbl}_node WHERE nodeno=1"] + + if {$newvalue == ""} { + binary scan $blob Su oldvalue + return $oldvalue + } + + set blob [binary format Sua* $newvalue [string range $blob 2 end]] + db eval "UPDATE ${tbl}_node SET data = \$blob WHERE nodeno=1" + return [set_tree_depth $tbl] +} + +proc set_entry_count {tbl nodeno {newvalue ""}} { + set blob [db one "SELECT data FROM ${tbl}_node WHERE nodeno=$nodeno"] + + if {$newvalue == ""} { + binary scan [string range $blob 2 end] Su oldvalue + return $oldvalue + } + + set blob [binary format a*Sua* \ + [string range $blob 0 1] $newvalue [string range $blob 4 end] + ] + db eval "UPDATE ${tbl}_node SET data = \$blob WHERE nodeno=$nodeno" + return [set_entry_count $tbl $nodeno] +} + + +proc do_corruption_tests {prefix args} { + set testarray [lindex $args end] + set errormsg {database disk image is malformed} + + foreach {z value} [lrange $args 0 end-1] { + set n [string length $z] + if {$n>=2 && [string equal -length $n $z "-error"]} { + set errormsg $value + } + } + + foreach {tn sql} $testarray { + do_catchsql_test $prefix.$tn $sql [list 1 $errormsg] + } +} + +#------------------------------------------------------------------------- +# Test the libraries response if the %_node table is completely empty +# (i.e. the root node is missing), or has been removed from the database +# entirely. +# +create_t1 +populate_t1 +do_execsql_test rtreeA-1.0 { + DELETE FROM t1_node; +} {} + +do_corruption_tests rtreeA-1.1 { + 1 "SELECT * FROM t1" + 2 "SELECT * FROM t1 WHERE rowid=5" + 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)" + 4 "SELECT * FROM t1 WHERE x1<10 AND x2>12" +} + +do_execsql_test rtreeA-1.2.0 { DROP TABLE t1_node } {} +do_corruption_tests rtreeA-1.2 -error "SQL logic error or missing database" { + 1 "SELECT * FROM t1" + 2 "SELECT * FROM t1 WHERE rowid=5" + 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)" + 4 "SELECT * FROM t1 WHERE x1<10 AND x2>12" +} + +#------------------------------------------------------------------------- +# Test the libraries response if some of the entries in the %_node table +# are the wrong size. +# +create_t1 +populate_t1 +do_test rtreeA-2.1.0 { + set nodes [db eval {select nodeno FROM t1_node}] + foreach {a b c} $nodes { truncate_node $c 200 } +} {} +do_corruption_tests rtreeA-2.1 { + 1 "SELECT * FROM t1" + 2 "SELECT * FROM t1 WHERE rowid=5" + 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)" + 4 "SELECT * FROM t1 WHERE x1<10 AND x2>12" +} + +create_t1 +populate_t1 +do_test rtreeA-2.2.0 { truncate_node 1 200 } {} +do_corruption_tests rtreeA-2.2 { + 1 "SELECT * FROM t1" + 2 "SELECT * FROM t1 WHERE rowid=5" + 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)" + 4 "SELECT * FROM t1 WHERE x1<10 AND x2>12" +} + +#------------------------------------------------------------------------- +# Set the "depth" of the tree stored on the root node incorrectly. Test +# that this does not cause any problems. +# +create_t1 +populate_t1 +do_test rtreeA-3.1.0.1 { set_tree_depth t1 } {1} +do_test rtreeA-3.1.0.2 { set_tree_depth t1 3 } {3} +do_corruption_tests rtreeA-3.1 { + 1 "SELECT * FROM t1" + 2 "SELECT * FROM t1 WHERE rowid=5" + 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)" +} + +do_test rtreeA-3.2.0 { set_tree_depth t1 1000 } {1000} +do_corruption_tests rtreeA-3.2 { + 1 "SELECT * FROM t1" + 2 "SELECT * FROM t1 WHERE rowid=5" + 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)" +} + +create_t1 +populate_t1 +do_test rtreeA-3.3.0 { + execsql { DELETE FROM t1 WHERE rowid = 0 } + set_tree_depth t1 65535 +} {65535} +do_corruption_tests rtreeA-3.3 { + 1 "SELECT * FROM t1" + 2 "SELECT * FROM t1 WHERE rowid=5" + 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)" +} + +#------------------------------------------------------------------------- +# Set the "number of entries" field on some nodes incorrectly. +# +create_t1 +populate_t1 +do_test rtreeA-4.1.0 { + set_entry_count t1 1 4000 +} {4000} +do_corruption_tests rtreeA-4.1 { + 1 "SELECT * FROM t1" + 2 "SELECT * FROM t1 WHERE rowid=5" + 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)" + 4 "SELECT * FROM t1 WHERE x1<10 AND x2>12" +} + +#------------------------------------------------------------------------- +# Remove entries from the %_parent table and check that this does not +# cause a crash. +# +create_t1 +populate_t1 +do_execsql_test rtreeA-5.1.0 { DELETE FROM t1_parent } {} +do_corruption_tests rtreeA-5.1 { + 1 "DELETE FROM t1 WHERE rowid = 5" + 2 "DELETE FROM t1" +} + +#------------------------------------------------------------------------- +# Add some bad entries to the %_parent table. +# +create_t1 +populate_t1 +do_execsql_test rtreeA-6.1.0 { + UPDATE t1_parent set parentnode = parentnode+1 +} {} +do_corruption_tests rtreeA-6.1 { + 1 "DELETE FROM t1 WHERE rowid = 5" + 2 "UPDATE t1 SET x1=x1+1, x2=x2+1" +} + + +finish_test diff --git a/ext/rtree/sqlite3rtree.h b/ext/rtree/sqlite3rtree.h new file mode 100644 index 00000000..cffb3000 --- /dev/null +++ b/ext/rtree/sqlite3rtree.h @@ -0,0 +1,56 @@ +/* +** 2010 August 30 +** +** 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. +** +************************************************************************* +*/ + +#ifndef _SQLITE3RTREE_H_ +#define _SQLITE3RTREE_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry; + +/* +** Register a geometry callback named zGeom that can be used as part of an +** R-Tree geometry query as follows: +** +** SELECT ... FROM WHERE MATCH $zGeom(... params ...) +*/ +int sqlite3_rtree_geometry_callback( + sqlite3 *db, + const char *zGeom, + int (*xGeom)(sqlite3_rtree_geometry *, int nCoord, double *aCoord, int *pRes), + void *pContext +); + + +/* +** A pointer to a structure of the following type is passed as the first +** 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 */ + void *pUser; /* Callback implementation user data */ + void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */ +}; + + +#ifdef __cplusplus +} /* end of the 'extern "C"' block */ +#endif + +#endif /* ifndef _SQLITE3RTREE_H_ */ diff --git a/ext/rtree/tkt3363.test b/ext/rtree/tkt3363.test index f0721714..db05ed52 100644 --- a/ext/rtree/tkt3363.test +++ b/ext/rtree/tkt3363.test @@ -13,7 +13,7 @@ # if {![info exists testdir]} { - set testdir [file join [file dirname $argv0] .. .. test] + set testdir [file join [file dirname [info script]] .. .. test] } source [file join [file dirname [info script]] rtree_util.tcl] source $testdir/tester.tcl diff --git a/main.mk b/main.mk index 83228c30..974bbe47 100644 --- a/main.mk +++ b/main.mk @@ -241,13 +241,17 @@ TESTSRC = \ $(TOP)/src/test_intarray.c \ $(TOP)/src/test_journal.c \ $(TOP)/src/test_malloc.c \ + $(TOP)/src/test_multiplex.c \ $(TOP)/src/test_mutex.c \ $(TOP)/src/test_onefile.c \ $(TOP)/src/test_osinst.c \ $(TOP)/src/test_pcache.c \ + $(TOP)/src/test_quota.c \ + $(TOP)/src/test_rtree.c \ $(TOP)/src/test_schema.c \ $(TOP)/src/test_server.c \ $(TOP)/src/test_stat.c \ + $(TOP)/src/test_superlock.c \ $(TOP)/src/test_tclvar.c \ $(TOP)/src/test_thread.c \ $(TOP)/src/test_vfs.c \ @@ -367,7 +371,9 @@ target_source: $(SRC) $(TOP)/tool/vdbe-compress.tcl sqlite3.c: target_source $(TOP)/tool/mksqlite3c.tcl tclsh $(TOP)/tool/mksqlite3c.tcl - cp sqlite3.c tclsqlite3.c + echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c + cat sqlite3.c >>tclsqlite3.c + echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c cat $(TOP)/src/tclsqlite.c >>tclsqlite3.c fts2amal.c: target_source $(TOP)/ext/fts2/mkfts2amal.tcl @@ -521,6 +527,17 @@ soaktest: testfixture$(EXE) sqlite3$(EXE) test: testfixture$(EXE) sqlite3$(EXE) ./testfixture$(EXE) $(TOP)/test/veryquick.test +# The next two rules are used to support the "threadtest" target. Building +# threadtest runs a few thread-safety tests that are implemented in C. This +# target is invoked by the releasetest.tcl script. +# +threadtest3$(EXE): sqlite3.c $(TOP)/test/threadtest3.c + $(TCCX) -O2 sqlite3.c $(TOP)/test/threadtest3.c \ + -o threadtest3$(EXE) $(THREADLIB) + +threadtest: threadtest3$(EXE) + ./threadtest3$(EXE) + sqlite3_analyzer$(EXE): $(TOP)/src/tclsqlite.c sqlite3.c $(TESTSRC) \ $(TOP)/tool/spaceanal.tcl sed \ diff --git a/manifest b/manifest index 9fe69e1d..0411312b 100644 --- a/manifest +++ b/manifest @@ -1,14 +1,14 @@ -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 -C Version\s3.7.2\srelease\scandidate\s1 -D 2010-08-23T18:52:01 +C SQLite\sversion\s3.7.5\srelease\scandidate\s2 +D 2011-01-28T17:03:50.592 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f -F Makefile.in 543f91f24cd7fee774ecc0a61c19704c0c3e78fd +F Makefile.in de6498556d536ae60bb8bb10e8c1ba011448658c F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 F Makefile.vxworks c85ec1d8597fe2f7bc225af12ac1666e21379151 F README cd04a36fbc7ea56932a4052d7d0b7f09f27c33d6 -F VERSION 6062e0026a5ab33dabb4efae38604d40115819ec +F VERSION de8d3477dbf0d6cc226ccc6e046273627eb55fc5 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F addopcodes.awk 17dc593f791f874d2c23a0f9360850ded0286531 F art/2005osaward.gif 0d1851b2a7c1c9d0ccce545f3e14bca42d7fd248 @@ -19,13 +19,14 @@ F art/SQLite_big.gif 2b8e4603b91ba2a2c7062a82ff570d945034bb30 F art/nocopy.gif 716aa07d4bb7250d4e75756073bf8ef9f56bec8f F art/powered_by_sqlite.gif 7fbcd7d3675391fd3d21672c14c05f5999eb60d1 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 +F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F art/src_logo.gif 9341ef09f0e53cd44c0c9b6fc3c16f7f3d6c2ad9 F config.guess 226d9a188c6196f3033ffc651cbc9dcee1a42977 F config.h.in 868fdb48c028421a203470e15c69ada15b9ba673 F config.sub 9ebe4c3b3dab6431ece34f16828b594fb420da55 -F configure a5dab32df872ed3fe248c7f0cbc324595c169781 x -F configure.ac 699040cc9abb7465dca5a2972bc89d227fd8f734 +F configure 0eb10c03a6536d8e5ce52ab70fda0a152d8a3262 x +F configure.ac 87a3c71bbe9c925381c154413eea7f3cdc397244 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/lemon.html f0f682f50210928c07e562621c3b7e8ab912a538 F doc/pager-invariants.txt 870107036470d7c419e93768676fae2f8749cf9e @@ -50,53 +51,58 @@ F ext/fts1/simple_tokenizer.c 1844d72f7194c3fd3d7e4173053911bf0661b70d F ext/fts1/tokenizer.h 0c53421b832366d20d720d21ea3e1f6e66a36ef9 F ext/fts2/README.tokenizers 21e3684ea5a095b55d70f6878b4ce6af5932dfb7 F ext/fts2/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts2/fts2.c 6cbd0fbdfe4699c108199e3c8f7e76f71d597335 +F ext/fts2/fts2.c 238e9e19158ef75fb4155613a870443394fbf7da F ext/fts2/fts2.h da5f76c65163301d1068a971fd32f4119e3c95fa F ext/fts2/fts2_hash.c 2689e42e1107ea67207f725cf69cf8972d00cf93 F ext/fts2/fts2_hash.h 9a5b1be94664139f93217a0770d7144425cffb3a F ext/fts2/fts2_icu.c 1ea9993a39c9783c2e2d7446d055e9d64411dda0 -F ext/fts2/fts2_porter.c 8a6369b0fae98c04db95e4fa95fac7c03d7182ec +F ext/fts2/fts2_porter.c 747056987951f743e955c8479f1df21a565720fe F ext/fts2/fts2_tokenizer.c 26e993a00b2bd5b6e73c155597361710b12ffe25 F ext/fts2/fts2_tokenizer.h a7e46462d935a314b2682287f12f27530a3ee08e -F ext/fts2/fts2_tokenizer1.c 8545ce12b41922004da46e91a7b023b92b76f94e +F ext/fts2/fts2_tokenizer1.c 0123d21078e053bd98fd6186c5c6dc6d67969f2e F ext/fts2/mkfts2amal.tcl 974d5d438cb3f7c4a652639262f82418c1e4cff0 F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9 F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c e818310c473d7703f7818887a3537ec42ae0d528 +F ext/fts3/fts3.c 28ada7d1c700e57b072b2c95d70565b05925fa46 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe -F ext/fts3/fts3Int.h 70528ba8c33991699f96ecc64112122833cdbdb5 -F ext/fts3/fts3_expr.c 42d5697731cd30fbeabd081bb3e6d3df5531f606 +F ext/fts3/fts3Int.h a6c69c1c5e2c8c19172ddff42d262c087dcd7337 +F ext/fts3/fts3_expr.c 5f49e0deaf723724b08100bb3ff40aab02ad0c93 F ext/fts3/fts3_hash.c 3c8f6387a4a7f5305588b203fa7c887d753e1f1c F ext/fts3/fts3_hash.h 8331fb2206c609f9fc4c4735b9ab5ad6137c88ec F ext/fts3/fts3_icu.c ac494aed69835008185299315403044664bda295 -F ext/fts3/fts3_porter.c 8df6f6efcc4e9e31f8bf73a4007c2e9abca1dfba -F ext/fts3/fts3_snippet.c 2c4c921155e4b6befd272041fb903d999ac07d30 -F ext/fts3/fts3_tokenizer.c b4f2d01c24573852755bc92864816785dae39318 +F ext/fts3/fts3_porter.c d61cfd81fb0fd8fbcb25adcaee0ba671aefaa5c2 +F ext/fts3/fts3_snippet.c 196c5e6cde57bfc1907c2d60e9c29590e4f93fb6 +F ext/fts3/fts3_tokenizer.c 055f3dc7369585350b28db1ee0f3b214dca6724d F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3 F ext/fts3/fts3_tokenizer1.c 6e5cbaa588924ac578263a598e4fb9f5c9bb179d -F ext/fts3/fts3_write.c 4b21a0c6f2772b261f14e3a2e80e1e3e849268b0 +F ext/fts3/fts3_write.c 3eea26b9ca4219e1711b0db74fd5a9d448a6afbb +F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100 F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9 F ext/icu/icu.c 850e9a36567bbcce6bd85a4b68243cad8e3c2de2 F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 -F ext/rtree/rtree.c f2fbb6470155316027a8b888e8623bc1819fe443 +F ext/rtree/rtree.c 05b293c85403cf39bb5af0e7c010b0cafeab5e47 F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e -F ext/rtree/rtree1.test 51bb0cd0405970501e63258401ae5ad235a4f468 -F ext/rtree/rtree2.test 7b665c44d25e51b3098068d983a39902b2e2d7a1 -F ext/rtree/rtree3.test dece988c363368af8c11862995c762071894918f -F ext/rtree/rtree4.test 94fdd570ab5bc47244d87d4590023be43ac786bd -F ext/rtree/rtree5.test 92508f5152a50110af6551fa5b769d1bbd7c4ef3 -F ext/rtree/rtree6.test 903720aaab819764c3693aaac0affe8174104ac8 -F ext/rtree/rtree7.test 6fd29fb8e13795c822f4ceeea92ab5d61c96976d +F ext/rtree/rtree1.test dbd4250ac0ad367a262eb9676f7e3080b0368206 +F ext/rtree/rtree2.test acbb3a4ce0f4fbc2c304d2b4b784cfa161856bba +F ext/rtree/rtree3.test a494da55c30ee0bc9b01a91c80c81b387b22d2dc +F ext/rtree/rtree4.test 0061e6f464fd3dc6a79f82454c5a1c3dadbe42af +F ext/rtree/rtree5.test ce3d7ccae2cfd9d2e1052b462424964c9bdcda12 +F ext/rtree/rtree6.test 309806a2a27ef5897d4dd6aee2e8006bf754cc22 +F ext/rtree/rtree7.test bcb647b42920b3b5d025846689147778485cc318 +F ext/rtree/rtree8.test 9772e16da71e17e02bdebf0a5188590f289ab37d +F ext/rtree/rtree9.test df9843d1a9195249c8d3b4ea6aedda2d5c73e9c2 +F ext/rtree/rtreeA.test ace05e729a36e342d40cf94e9efc7b4723d9dcdf F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195 F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea -F ext/rtree/tkt3363.test 2bf324f7908084a5f463de3109db9c6e607feb1b +F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0 +F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 -F main.mk 26ad86cf0689940f19b3d608bbfdb3956b2fb9a7 +F main.mk 05d0f3475dd331896bd607cfb45c5e21b94589ad F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac @@ -109,26 +115,26 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 6be1ad09113570e1fc8dcaff84c9b0b337db5ffc F sqlite3.pc.in ae6f59a76e862f5c561eb32a380228a02afc3cad -F src/alter.c 8dc27638e7e2553e80b2b621f232be5eb1e85ef3 -F src/analyze.c da65ce99bb159b10e85a1e460adbe53a88062500 -F src/attach.c 17bec1f18254d9341369f20f90ba24ce35d20d10 +F src/alter.c 6a0c176e64a34929a4436048066a84ef4f1445b3 +F src/analyze.c a038162344265ac21dfb24b3fcc06c666ebb9c07 +F src/attach.c 252c4f7e36cc219349451ed63e278c60e80b26f3 F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34 -F src/backup.c 8ff0b7018df253c7f30d3f9702b0b16f19209d5c +F src/backup.c 6728d6d48d55b449af76a3e51c0808849cb32a2e F src/bitvec.c af50f1c8c0ff54d6bdb7a80e2fceca5a93670bef F src/btmutex.c 96a12f50f7a17475155971a241d85ec5171573ff -F src/btree.c 5047fb303cdf6806a42676a6f513c57e15b7d69b -F src/btree.h b4ba2fdf6b64c7c376bdfffa826af6b786b151d9 -F src/btreeInt.h 5b034ff54800046cc5870605d683ac1f9134bd99 -F src/build.c 0018d49629fc4807100c988dd191dd95e185bb38 -F src/callback.c da3c38d0ef5d7f04fae371e519bda61aa9cb1704 +F src/btree.c 9004c98fc576306eee4fc0562ffeb362ef53912c +F src/btree.h 10f9296bf4edf034f5adce921b7b4383a56a1c90 +F src/btreeInt.h 20f73dc93b1eeb83afd7259fbc6bd7dcf2df7fe4 +F src/build.c 00a327120d81ace6267e714ae8010c997d55de5d +F src/callback.c a1d1b1c9c85415dff013af033e2fed9c8382d33b F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac -F src/ctime.c 4f3aadad62c6c9f0d4e5a96718516ac4e3c598df -F src/date.c 5dd8448a0bfea8d31fb14cff487d0c06ff8c8b20 +F src/ctime.c 7deec4534f3b5a0c3b4a4cbadf809d321f64f9c4 +F src/date.c 1548fdac51377e4e7833251de878b4058c148e1b F src/delete.c 7ed8a8c8b5f748ece92df173d7e0f7810c899ebd -F src/expr.c 9ee507c3dc6eaa5657cbd1dad026cdeda89c559f +F src/expr.c 1810f3056b11de99cc10e24629edf00e5fbd3a75 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb -F src/fkey.c 58bbf52c6ddd3f64ca40a3230f9e548a83a5cb16 -F src/func.c 464b0dc70618b896c402c574eb04bc5eacf35341 +F src/fkey.c 17950a28f28b23e8ad3feaac5fc88c324d2f600a +F src/func.c cb41f614edc43b00bfeb030f9768e80eaff47edd F src/global.c 02335177cf6946fe5525c6f0755cf181140debf3 F src/hash.c 458488dcc159c301b8e7686280ab209f1fb915af F src/hash.h 2894c932d84d9f892d4b4023a75e501f83050970 @@ -137,52 +143,52 @@ F src/insert.c a4995747c062256582a90b4f87f716e11b067050 F src/journal.c 552839e54d1bf76fb8f7abe51868b66acacf6a0e F src/legacy.c a199d7683d60cef73089e892409113e69c23a99f F src/lempar.c 7f026423f4d71d989e719a743f98a1cbd4e6d99e -F src/loadext.c 6d422ea91cf3d2d00408c5a8f2391cd458da85f8 -F src/main.c 99622181f36d68e9f2a851c7b34263b3dcd03470 -F src/malloc.c 19a468460c7df72de245f10c06bd0625777b7c83 +F src/loadext.c 8af9fcc75708d60b88636ccba38b4a7b3c155c3e +F src/main.c 6653e46db7ecb5a7449d8a12900147192f748b97 +F src/malloc.c 92d59a007d7a42857d4e9454aa25b6b703286be1 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 -F src/mem1.c 89d4ea8d5cdd55635cbaa48ad53132af6294cbb2 -F src/mem2.c 9e5f72e38573db9598fe60d3fa530d473cc8714e +F src/mem1.c 00bd8265c81abb665c48fea1e0c234eb3b922206 +F src/mem2.c e307323e86b5da1853d7111b68fd6b84ad6f09cf F src/mem3.c 9b237d911ba9904142a804be727cc6664873f8a3 -F src/mem5.c eb7a5cb98915dd7a086fa415ce3a5a0f20d0acff -F src/memjournal.c 4a93a25ad9f76c40afa070ffd7187eb3a5fd7aee +F src/mem5.c 6fe00f46997bebb690397cb029719f711e7640e3 +F src/memjournal.c 0ebce851677a7ac035ba1512a7e65851b34530c6 F src/mutex.c 6949180803ff05a7d0e2b9334a95b4fb5a00e23f -F src/mutex.h 6fde601e55fa6c3fae768783c439797ab84c87c6 +F src/mutex.h fe2ef5e1c4dae531d5a544f9241f19c56d26803d F src/mutex_noop.c d5cfbca87168c661a0b118cd8e329a908e453151 F src/mutex_os2.c 6a62583e374ba3ac1a3fcc0da2bfdac7d3942689 -F src/mutex_unix.c abb8c98a6c27c57280e71522d059e929c708d019 -F src/mutex_w32.c b7ed3366a1d44a62a17d4eaefdaa2e7c25f944c2 +F src/mutex_unix.c b4f4e923bb8de93ec3f251fadb50855f23df9579 +F src/mutex_w32.c 3ade5ae71449d1d023f0ebb3184c2ae6aa9307e4 F src/notify.c 976dd0f6171d4588e89e874fcc765e92914b6d30 -F src/os.c 60178f518c4d6c0dcb59f7292232281d7bea2dcf +F src/os.c 22ac61d06e72a0dac900400147333b07b13d8e1d F src/os.h 9dbed8c2b9c1f2f2ebabc09e49829d4777c26bf9 F src/os_common.h a8f95b81eca8a1ab8593d23e94f8a35f35d4078f -F src/os_os2.c 72d0b2e562952a2464308c4ce5f7913ac10bef3e -F src/os_unix.c 11194cbcf6a57456e58022dc537ab8c3497d9bb9 -F src/os_win.c 51cb62f76262d961ea4249489383d714501315a7 -F src/pager.c a5f5d9787b11dfb0b6082e6f5846d00b459a8e19 -F src/pager.h ef8c8f71ab022cc2fff768a1175dd32355be9dcd +F src/os_os2.c 2e452c9f2ca507623ad351c33a8a8b27849b1863 +F src/os_unix.c 1be46a35bad4bec5171e4de88aaff817260eb378 +F src/os_win.c 9abdcdd925416d854eabb0996c96debd92abfef5 +F src/pager.c b0fcbe3038fd08b111e1cf1deddd5f42418004d8 +F src/pager.h 0ea59db2a33bc6c2c02cae34de33367e1effdf76 F src/parse.y 12b7ebd61ea54f0e1b1083ff69cc2c8ce9353d58 -F src/pcache.c 1e9aa2dbc0845b52e1b51cc39753b6d1e041cb07 +F src/pcache.c 09d38c44ab275db581f7a2f6ff8b9bc7f8c0faaa F src/pcache.h c683390d50f856d4cd8e24342ae62027d1bb6050 -F src/pcache1.c e921e8a1d52c93abde63cb6dad1fa39770410c52 -F src/pragma.c 8b24ce00a93de345b6c3bd1e1e2cfba9f63d2325 -F src/prepare.c ce4c35a2b1d5fe916e4a46b70d24a6e997d7c4c6 -F src/printf.c 8ae5082dd38a1b5456030c3755ec3a392cd51506 +F src/pcache1.c d548e31beafa792d1994b663a29a5303569efc4e +F src/pragma.c 8a6cd3c787f882fa44f6490d2411fc26839ce8f3 +F src/prepare.c 395b3fab1b93f45b6aa194b23ebc201221c47b99 +F src/printf.c df2ff3bb5409e8958136933342c46464fbd017e7 F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50 F src/resolve.c 1c0f32b64f8e3f555fe1f732f9d6f501a7f05706 F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697 -F src/select.c 8add6cab889fc02e1492eda8dba462ccf11f51dd -F src/shell.c 8517fc1f9c59ae4007e6cc8b9af91ab231ea2056 -F src/sqlite.h.in 2d72a6242df41c517e38eec8791abcf5484a36f1 -F src/sqlite3ext.h 69dfb8116af51b84a029cddb3b35062354270c89 -F src/sqliteInt.h e33b15e8176442bf7484f0e716edfd1ce03b2979 +F src/select.c 8a7ba246b0b4bb45df7fbc52681728a0e3deaaa7 +F src/shell.c 83c6f0cc5a79a081c7b9ddfe4f557b47e0bad976 +F src/sqlite.h.in 76955fcd1c5371268ecc8afe0ce6c49ea750ae38 +F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754 +F src/sqliteInt.h 45926deaf59b1ce3f55d21d5f91a8cecb6a7eb4c F src/sqliteLimit.h a17dcd3fb775d63b64a43a55c54cb282f9726f44 -F src/status.c 496913d4e8441195f6f2a75b1c95993a45b9b30b +F src/status.c 4997380fbb915426fef9e500b4872e79c99267fc F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e -F src/tclsqlite.c cacee9482417b6fc6043f6bb831ff9496d46242d -F src/test1.c 55005c9781b157b1d215ba145768783b9abae78c +F src/tclsqlite.c 549859dc2c143f3deb6a92636a2d27973652c164 +F src/test1.c 771407a49ae199241f0efb7055634e4a1899c026 F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31 -F src/test3.c 4c21700c73a890a47fc685c1097bfb661346ac94 +F src/test3.c 056093cfef69ff4227a6bdb9108564dc7f45e4bc F src/test4.c 0528360b5025688002a5feb6be906ddce52eaaee F src/test5.c e1a19845625144caf038031234a12185e40d315c F src/test6.c c7256cc21d2409486d094277d5b017e8eced44ba @@ -193,7 +199,7 @@ F src/test_async.c 0612a752896fad42d55c3999a5122af10dcf22ad F src/test_autoext.c 30e7bd98ab6d70a62bb9ba572e4c7df347fe645e F src/test_backup.c c129c91127e9b46e335715ae2e75756e25ba27de F src/test_btree.c 47cd771250f09cdc6e12dda5bc71bc0b3abc96e2 -F src/test_config.c 5a11c51af2156e2d07186930b36f2b8239a4393f +F src/test_config.c 9f025a7f3686c94e82dc6d6bd3cbf0f89cd67487 F src/test_demovfs.c 0aed671636735116fc872c5b03706fd5612488b5 F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc F src/test_func.c 13b582345fb1185a93e46c53310fae8547dcce20 @@ -203,48 +209,53 @@ F src/test_intarray.c d879bbf8e4ce085ab966d1f3c896a7c8b4f5fc99 F src/test_intarray.h 489edb9068bb926583445cb02589344961054207 F src/test_journal.c 785edd54f963aefb3c1628124170a56697c68c70 F src/test_loadext.c df586c27176e3c2cb2e099c78da67bf14379a56e -F src/test_malloc.c 09a88f0c111201dc4f8c20470aa1b5f611d59200 -F src/test_mutex.c ce06b59aca168cd8c520b77159a24352a7469bd3 +F src/test_malloc.c fd6188b1501c0010fb4241ddc9f0d5ac402c688d +F src/test_multiplex.c 5990431a852aa21c9a67da748f23d2cf1e21f8fc +F src/test_mutex.c a6bd7b9cf6e19d989e31392b06ac8d189f0d573e F src/test_onefile.c 40cf9e212a377a6511469384a64b01e6e34b2eec F src/test_osinst.c f408c6a181f2fb04c56273afd5c3e1e82f60392c F src/test_pcache.c 7bf828972ac0d2403f5cfa4cd14da41f8ebe73d8 +F src/test_quota.c b5576f17d701af461effd7ca1e71f0d100071192 +F src/test_rtree.c 30c981837445a4e187ee850a49c4760d9642f7c3 F src/test_schema.c 8c06ef9ddb240c7a0fcd31bc221a6a2aade58bf0 F src/test_server.c bbba05c144b5fc4b52ff650a4328027b3fa5fcc6 F src/test_stat.c f682704b5d1ba8e1d4e7e882a6d7922e2dcf066c +F src/test_superlock.c 2b97936ca127d13962c3605dbc9a4ef269c424cd F src/test_tclvar.c f4dc67d5f780707210d6bb0eb6016a431c04c7fa F src/test_thread.c bedd05cad673dba53326f3aa468cc803038896c0 -F src/test_vfs.c 702e52636113f6b9721da90ef1bf26e07fff414d +F src/test_vfs.c 2ed8853c1e51ac6f9ea091f7ce4e0d618bba8b86 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/tokenize.c 604607d6813e9551cf5189d899e0a25c12681080 F src/trigger.c b8bedb9c0084ceb51a40f54fcca2ce048c8de852 -F src/update.c 1521162d20c2994af1fdc8833e1a88dae09052c8 +F src/update.c 227e6cd512108b84f69421fc6c7aa1b83d60d6e0 F src/utf.c 1baeeac91707a4df97ccc6141ec0f808278af685 -F src/util.c 32aebf04c10e51ad3977a928b7416bed671b620b -F src/vacuum.c 241a8386727c1497eba4955933356dfba6ff8c9f -F src/vdbe.c 66c262a923915e596379b1d597178e04c5d719e4 +F src/util.c ab1c92426494f499f42b9e307537b03e923d75c1 +F src/vacuum.c 924bd1bcee2dfb05376f79845bd3b4cec7b54b2f +F src/vdbe.c 5d310eaf1a4d8383602126fa82e01291ab7d3cf3 F src/vdbe.h 4de0efb4b0fdaaa900cf419b35c458933ef1c6d2 -F src/vdbeInt.h ffd68c4d4229227a5089bec53a1c635146177abc -F src/vdbeapi.c d0f4407e465f261780ad725c1caece7d66a6aa35 -F src/vdbeaux.c c73bcefcebfd3d2cf91bf6a41ef0fb0d884814c6 -F src/vdbeblob.c 258a6010ba7a82b72b327fb24c55790655689256 -F src/vdbemem.c e5673f81a2381b35c60e73ef0a8502be2ab1041e -F src/vdbetrace.c 864cef96919323482ebd9986f2132435115e9cc2 -F src/vtab.c 0e8e0cb30dffb078367e843e84e37ef99236c7e4 -F src/wal.c 5ac2119e23ee4424599d4275b66dc88d612a0543 -F src/wal.h 96669b645e27cd5a111ba59f0cae7743a207bc3c +F src/vdbeInt.h 6e6f28e9bccc6c703dca1372fd661c57b5c15fb0 +F src/vdbeapi.c 8e9324fd35eb70d0b5904bd1af40f2598744dc4d +F src/vdbeaux.c 33448d23b857654dd69ed2103611f5c733606f68 +F src/vdbeblob.c 18955f0ee6b133cd08e1592010cb9a6b11e9984c +F src/vdbemem.c 411649a35686f54268ccabeda175322c4697f5a6 +F src/vdbetrace.c 3ba13bc32bdf16d2bdea523245fd16736bed67b5 +F src/vtab.c b297e8fa656ab5e66244ab15680d68db0adbec30 +F src/wal.c dbca424f71678f663a286ab2a98f947af1d412a7 +F src/wal.h c1aac6593a0b02b15dc625987e619edeab39292e F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f -F src/where.c 7db3e41c2a846f9deeb24f1bbb75461b4010b7b5 +F src/where.c af069e6b53234118014dabfece96a9515b69d76b F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 -F test/all.test 6745008c144bd2956d58864d21f7b304689c1cce +F test/all.test 51756962d522e474338e9b2ebb26e7364d4aa125 F test/alter.test 15f9224868b290d6bf7a63f31437f31aee070636 -F test/alter2.test 52096b711afe5f219e575c6db7a70f7a35df4f63 -F test/alter3.test 25b95a136708f22b87184fa6a4309eea03d65153 -F test/alter4.test 9386ffd1e9c7245f43eca412b2058d747509cc1f +F test/alter2.test 75f731508f1bf27ba09a6075c66cd02216ba464b +F test/alter3.test 8677e48d95536f7a6ed86a1a774744dadcc22b07 +F test/alter4.test 1e5dd6b951e9f65ca66422edff02e56df82dd403 F test/altermalloc.test e81ac9657ed25c6c5bb09bebfa5a047cd8e4acfc -F test/analyze.test bf692e7db414f268a136bade16c03a1bdbb9240c -F test/analyze2.test 59dac6c399c0c5d1a90a11ee7cc606743fb6db93 -F test/analyze3.test 6d4f4b0929545a9d1af803a0608a0c51b92a3537 +F test/analyze.test c1eb87067fc16ece7c07e823d6395fd831b270c5 +F test/analyze2.test 3bde8f0879d9c1f2df3af21fcf42e706d8ee1e43 +F test/analyze3.test 820ddfb7591b49607fbaf77240c7955ac3cabb04 +F test/analyze4.test 757b37875cf9bb528d46f74497bc789c88365045 F test/async.test ad4ba51b77cd118911a3fe1356b0809da9c108c3 F test/async2.test bf5e2ca2c96763b4cba3d016249ad7259a5603b6 F test/async3.test 93edaa9122f498e56ea98c36c72abc407f4fb11e @@ -253,17 +264,17 @@ F test/async5.test f3592d79c84d6e83a5f50d3fd500445f7d97dfdf F test/attach.test ce9660e51768fab93cf129787be886c5d6c4fd81 F test/attach2.test a295d2d7061adcee5884ef4a93c7c96a82765437 F test/attach3.test bd9830bc3a0d22ed1310c9bff6896927937017dc -F test/attachmalloc.test 38d2da5fdaf09ba0add57296967a3061e5842584 -F test/auth.test 8f21c160a4562f54f27618e85bac869efcecbcaf +F test/attachmalloc.test 1d5b821a676f7bf0b00d87cc106b78966789ba57 +F test/auth.test 26cc6f219580191539bf335abe03e55e49310846 F test/auth2.test 270baddc8b9c273682760cffba6739d907bd2882 F test/auth3.test a4755e6a2a2fea547ffe63c874eb569e60a28eb5 F test/autoinc.test 85ef3180a737e6580086a018c09c6f1a52759b46 -F test/autoindex1.test 7df441bf0e7a88644eb80993339dbf1db3a12c68 +F test/autoindex1.test 860fc83f4fefb0c68ad062afc3ff43faa1534fc4 F test/autovacuum.test bb7c0885e6f8f1d633045de48f2b66082162766d F test/autovacuum_ioerr2.test 598b0663074d3673a9c1bc9a16e80971313bafe6 F test/avtrans.test 0252654f4295ddda3b2cce0e894812259e655a85 -F test/backcompat.test 49bd844eb245f0b2b6f2a3f8bebad0065403a9a7 -F test/backup.test 200e64bd91244b73ca8094bc1e03dfc83cc94c2e +F test/backcompat.test 541314d69ec9db3e03630b7616696ddc5048efb1 +F test/backup.test 004d3b78bffd990741ab50133ed4347c25c172b1 F test/backup2.test b7c69f937c912e85ac8a5dbd1e1cf290302b2d49 F test/backup_ioerr.test 1f012e692f42c0442ae652443258f70e9f20fa38 F test/backup_malloc.test 7162d604ec2b4683c4b3799a48657fb8b5e2d450 @@ -284,12 +295,13 @@ F test/boundary3.test 56ef82096b4329aca2be74fa1e2b0f762ea0eb45 F test/boundary4.tcl 0bb4b1a94f4fc5ae59b79b9a2b7a140c405e2983 F test/boundary4.test 89e02fa66397b8a325d5eb102b5806f961f8ec4b F test/busy.test 76b4887f8b9160ba903c1ac22e8ff406ad6ae2f0 -F test/cache.test c4288607b54f2702858492fc4b92828336a1812f -F test/capi2.test 00032d7504b9c14f1b36331670c5e7b0f73e3c5d +F test/cache.test 754baab2f18089fc9bcba7afaeb4dc907c6c6de2 +F test/capi2.test 835d4cee9f542ea50fa8d01f3fe6de80b0627360 F test/capi3.test 1945a2ba75e3f4c49d5beb8fc092115b6292d471 F test/capi3b.test efb2b9cfd127efa84433cd7a2d72ce0454ae0dc4 F test/capi3c.test bea67403a5e37a4b33230ee4723e315a2ffb31e7 -F test/capi3d.test 57d83b690d7364bde02cddbf8339a4b50d80ce23 +F test/capi3d.test cd36571f014f34bdc4421967f6453cbb597d5d16 +F test/capi3e.test 4fda47388ddfbfe807987aa62f46fcbceec9327f F test/cast.test 166951664a0b0a2e0f8fb5997a152490c6363932 F test/check.test db2b29d557544347d28e25b8406f5d5ecc3d1bc3 F test/coalesce.test cee0dccb9fbd2d494b77234bccf9dc6c6786eb91 @@ -305,7 +317,7 @@ F test/collate9.test 3adcc799229545940df2f25308dd1ad65869145a F test/collateA.test b8218ab90d1fa5c59dcf156efabb1b2599c580d6 F test/colmeta.test 087c42997754b8c648819832241daf724f813322 F test/colname.test 08948a4809d22817e0e5de89c7c0a8bd90cb551b -F test/conflict.test f2f2b2950730a9532e11e468070cebf389f5c375 +F test/conflict.test cabc41f7616675df71b4fddabca3bd5d9221915a F test/corrupt.test 1a5bef8b2d178859af69814ecedcd37219a89968 F test/corrupt2.test 808a28d0ca3b97e9aa8c91cd2b485ea2700b76d1 F test/corrupt3.test a399dacccb91c732f6b071c913e70d195af8c058 @@ -333,8 +345,8 @@ F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2 F test/createtab.test 199cf68f44e5d9e87a0b8afc7130fdeb4def3272 F test/cse.test 277350a26264495e86b1785f34d2d0c8600e021c F test/ctime.test 7bd009071e242aac4f18521581536b652b789a47 -F test/date.test 6354b883f922c38046a8efbad187cc95df6da023 -F test/dbstatus.test 946e1399d4574fc5dac934cceedbc76924af3f5a +F test/date.test a18a2ce81add84b17b06559e82ad7bb91bc6ddff +F test/dbstatus.test 175b088308f2ce3f7afb8208f25c10878ee05921 F test/default.test 6faf23ccb300114924353007795aa9a8ec0aa9dc F test/delete.test f7629d9eb245dfca170169cc5c7a735dec34aeb4 F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa @@ -344,22 +356,35 @@ F test/descidx2.test 9f1a0c83fd57f8667c82310ca21b30a350888b5d F test/descidx3.test fe720e8b37d59f4cef808b0bf4e1b391c2e56b6f F test/diskfull.test 0cede7ef9d8f415d9d3944005c76be7589bb5ebb F test/distinctagg.test 1a6ef9c87a58669438fc771450d7a72577417376 -F test/e_expr.test 8a35ce2718c61e871970bda09f4f3e549067c1ba -F test/e_fkey.test 6721a741c6499b3ab7e5385923233343c8f1ad05 +F test/e_createtable.test b8f5286879315d5b7f4cc5ead1afda4846f0c0bb +F test/e_delete.test 55d868b647acc091c261a10b9b0cb0ab660a6acb +F test/e_droptrigger.test ddd4b28ed8a3d81bd5153fa0ab7559529a2ca03a +F test/e_dropview.test b347bab30fc8de67b131594b3cd6f3d3bdaa753d +F test/e_expr.test 4e004d1f5187d4bbc9ca3d55660a8d164dd59f4e +F test/e_fkey.test 38039b840ab19331000b0f0eb1d82baa7208a67a F test/e_fts3.test 75bb0aee26384ef586165e21018a17f7cd843469 +F test/e_insert.test 7390c2da39f16a134dc9a439144768c727757d2c +F test/e_reindex.test a064f0878b8f848fbca38f1f61f82f15a3000c64 +F test/e_resolve.test dcce9308fb13b934ce29591105d031d3e14fbba6 +F test/e_select.test bf385ae3aa0f014c4933ae66fd3e1302138493eb +F test/e_select2.test 5c3d3da19c7b3e90ae444579db2b70098599ab92 +F test/e_update.test 963d6876064e65f318d1c93aaed36a02b9b389bf +F test/e_vacuum.test 057cc29445746fc1d2542984ff0253d511a234bd F test/enc.test e54531cd6bf941ee6760be041dff19a104c7acea F test/enc2.test 6d91a5286f59add0cfcbb2d0da913b76f2242398 F test/enc3.test 5c550d59ff31dccdba5d1a02ae11c7047d77c041 +F test/enc4.test 4b575ef09e0eff896e73bd24076f96c2aa6a42de +F test/eqp.test 69670e7919030f21de29fb99bf1d68f97aedcbdb F test/eval.test bc269c365ba877554948441e91ad5373f9f91be3 -F test/exclusive.test b1f9012cabc124af947165d15ffa62ad20f63db8 -F test/exclusive2.test fcbb1c9ca9739292a0a22a3763243ad6d868086b +F test/exclusive.test 53e1841b422e554cecf0160f937c473d6d0e3062 +F test/exclusive2.test b65264c3e76e1db6c6eda15c02000a40743f6541 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 -F test/expr.test 9f521ae22f00e074959f72ce2e55d46b9ed23f68 +F test/expr.test 620a636cf7b7d4e5834a0b9d83a4da372e24a7b7 F test/fallocate.test 43dc34b8c24be6baffadc3b4401ee15710ce83c6 F test/filectrl.test 97003734290887566e01dded09dc9e99cb937e9e F test/filefmt.test f77c92141960b7933bc6691631d2ad62257ef40a F test/fkey1.test 01c7de578e11747e720c2d9aeef27f239853c4da -F test/fkey2.test e028cd80aa0bd38541c99214e3ba2dfccadffe6f +F test/fkey2.test 080969fe219b3b082b0e097ac18c6af2e5b0631f F test/fkey3.test 42f88d6048d8dc079e2a8cf7baad1cc1483a7620 F test/fkey_malloc.test a5ede29bd2f6e56dea78c3d43fb86dd696c068c8 F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb @@ -399,37 +424,45 @@ F test/fts2r.test b154c30b63061d8725e320fba1a39e2201cadd5e F test/fts2token.test d8070b241a15ff13592a9ae4a8b7c171af6f445a F test/fts3.test 672a040ea57036fb4b6fdc09027c18d7d24ab654 F test/fts3_common.tcl 4d8eec9db565fed9098f45c378f28e1657802011 -F test/fts3aa.test 5327d4c1d9b6c61021696746cc9a6cdc5bf159c0 +F test/fts3aa.test 909d5f530d30a8e36b9328d67285eae6537c79c0 F test/fts3ab.test 09aeaa162aee6513d9ff336b6932211008b9d1f9 F test/fts3ac.test 636ed7486043055d4f126a0e385f2d5a82ebbf63 F test/fts3ad.test e40570cb6f74f059129ad48bcef3d7cbc20dda49 F test/fts3ae.test ce32a13b34b0260928e4213b4481acf801533bda F test/fts3af.test d394978c534eabf22dd0837e718b913fd66b499c F test/fts3ag.test 0b7d303f61ae5d620c4efb5e825713ea34ff9441 -F test/fts3ah.test ba181d6a3dee0c929f0d69df67cac9c47cda6bff +F test/fts3ah.test dc9f66c32c296f1bc8bcc4535126bddfeca62894 F test/fts3ai.test d29cee6ed653e30de478066881cec8aa766531b2 F test/fts3aj.test 584facbc9ac4381a7ec624bfde677340ffc2a5a4 F test/fts3ak.test bd14deafe9d1586e8e9bf032411026ac4f8c925d F test/fts3al.test 07d64326e79bbdbab20ee87fc3328fbf01641c9f F test/fts3am.test 218aa6ba0dfc50c7c16b2022aac5c6be593d08d8 F test/fts3an.test a49ccadc07a2f7d646ec1b81bc09da2d85a85b18 -F test/fts3ao.test 0aa29dd4fc1c8d46b1f7cfe5926f7ac97551bea9 +F test/fts3ao.test b83f99f70e9eec85f27d75801a974b3f820e01f9 F test/fts3atoken.test 25c2070e1e8755d414bf9c8200427b277a9f99fa F test/fts3b.test e93bbb653e52afde110ad53bbd793f14fe7a8984 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958 -F test/fts3cov.test 3a9d8618a3107166530c447e808f8992372e0415 +F test/fts3corrupt.test d874ba27975aa8e5514bf58bf97b473404de0dbb +F test/fts3corrupt2.test 6d96efae2f8a6af3eeaf283aba437e6d0e5447ba +F test/fts3cov.test e0fb00d8b715ddae4a94c305992dfc3ef70353d7 F test/fts3d.test 95fb3c862cbc4297c93fceb9a635543744e9ef52 +F test/fts3defer.test d6cb0db9b5997ecf863d96ff419f83f8f2c87f4f +F test/fts3defer2.test da840efaedebfdd54293d04b36098e2d9872caa6 F test/fts3e.test 1f6c6ac9cc8b772ca256e6b22aaeed50c9350851 F test/fts3expr.test 5e745b2b6348499d9ef8d59015de3182072c564c F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a -F test/fts3malloc.test 059592c4f37ccd30138bbf8e3e5b7982cb5c8f2e +F test/fts3fault.test f83e556465bb69dc8bc676339eca408dce4ca246 +F test/fts3malloc.test 9c8cc3f885bb4dfc66d0460c52f68f45e4710d1b +F test/fts3matchinfo.test cc0b009edbbf575283d5fdb53271179e0d8019ba F test/fts3near.test 2e318ee434d32babd27c167142e2b94ddbab4844 -F test/fts3query.test 2468caf7938dbc3be2e049524320ce4faf2227b3 -F test/fts3rnd.test 707533ce943f490443ce5e696236bb1675a37635 -F test/fts3snippet.test 9f9a4a7e396c5d8ce2898be65ebabc429555430f +F test/fts3query.test ef79d31fdb355d094baec1c1b24b60439a1fb8a2 +F test/fts3rnd.test 2b1a579be557ab8ac54a51b39caa4aa8043cc4ad +F test/fts3shared.test 8bb266521d7c5495c0ae522bb4d376ad5387d4a2 +F test/fts3snippet.test a12f22a3ba4dd59751a57c79b031d07ab5f51ddd F test/fts4aa.test eadf85621c0a113d4c7ad3ccbf8441130e007b8f F test/func.test 6c5ce11e3a0021ca3c0649234e2d4454c89110ca F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f +F test/func3.test 7ba2ca5a1e9bca900ba2c230cf04bd67184bc1bc F test/fuzz.test 77fd50afc12847af50fcf1941679d90adebadde6 F test/fuzz2.test 207d0f9d06db3eaf47a6b7bfc835b8e2fc397167 F test/fuzz3.test aec64345184d1662bd30e6a17851ff659d596dc5 @@ -437,20 +470,22 @@ F test/fuzz_common.tcl a87dfbb88c2a6b08a38e9a070dabd129e617b45b F test/fuzz_malloc.test dd7001ac86d09c154a7dff064f4739c60e2b312c F test/hook.test f04c3412463f8ec117c1c704c74ca0f627ce733a F test/icu.test 70df4faca133254c042d02ae342c0a141f2663f4 -F test/in.test d49419c6df515852f477fa513f3317181d46bc92 +F test/in.test 19b642bb134308980a92249750ea4ce3f6c75c2d F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75 F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0 F test/in4.test 64f3cc1acde1b9161ccdd8e5bde3daefdb5b2617 -F test/incrblob.test e557f262cd2cc088e6bb4d154575a1bbe242edcd +F test/incrblob.test 76e787ca3301d9bfa6906031c626d26f8dd707de F test/incrblob2.test edc3a96e557bd61fb39acc8d2edd43371fbbaa19 +F test/incrblob3.test aedbb35ea1b6450c33b98f2b6ed98e5020be8dc7 F test/incrblob_err.test c577c91d4ed9e8336cdb188b15d6ee2a6fe9604e +F test/incrblobfault.test 917c0292224c64a56ef7215fd633a3a82f805be0 F test/incrvacuum.test 453d1e490d8f5ad2c9b3a54282a0690d6ae56462 F test/incrvacuum2.test 9e22a794899c91b7d8c8e12eaacac8df249faafe F test/incrvacuum_ioerr.test 57d2f5777ab13fa03b87b262a4ea1bad5cfc0291 -F test/index.test cbf301cdb2da43e4eac636c3400c2439af1834ad +F test/index.test df7c00c6edd9504ab71c83a9514f1c5ca0fa54d8 F test/index2.test ee83c6b5e3173a3d7137140d945d9a5d4fdfb9d6 F test/index3.test 423a25c789fc8cc51aaf2a4370bbdde2d9e9eed7 -F test/indexedby.test 5a1180602f2e72c481467bd4cae05dae5dc36f47 +F test/indexedby.test d7367c5a0e8ed8db642824a68126753e0808c706 F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 F test/insert.test aef273dd1cee84cc92407469e6bd1b3cdcb76908 F test/insert2.test 4f3a04d168c728ed5ec2c88842e772606c7ce435 @@ -461,7 +496,7 @@ F test/intarray.test 066b7d7ac38d25bf96f87f1b017bfc687551cdd4 F test/interrupt.test 42e7cf98646fd9cb4a3b131a93ed3c50b9e149f1 F test/intpkey.test 537669fd535f62632ca64828e435b9e54e8d677f F test/io.test 1b895d6774491895cbc75659969f07ca01860c88 -F test/ioerr.test 390785ec65f10aa58a82b048ee12e9052d783fa8 +F test/ioerr.test 622aebd2f24779cafaf5dd3e3c2b349ce40ade3b F test/ioerr2.test 1b56cb80d5b0726ee3ba325ca175734541e32955 F test/ioerr3.test d3cec5e1a11ad6d27527d0d38573fbff14c71bdd F test/ioerr4.test fc6eddfec2efc2f1ed217b9eae4c1c1d3516ce86 @@ -477,28 +512,28 @@ F test/journal2.test 50a3604768494d4a337f194f0a9480e7c57dcb72 F test/journal3.test ff175219be1b02d2f7e54297ad7e491b7533edb6 F test/jrnlmode.test e3fe6c4a2c3213d285650dc8e33aab7eaaa5ce53 F test/jrnlmode2.test a19e28de1a6ec898067e46a122f1b71c9323bf00 -F test/jrnlmode3.test cfcdb12b90e640a23b92785a002d96c0624c8710 +F test/jrnlmode3.test c6522b276ba315fd1416198de6fc1da9e72409fb F test/keyword1.test a2400977a2e4fde43bf33754c2929fda34dbca05 F test/lastinsert.test 474d519c68cb79d07ecae56a763aa7f322c72f51 F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200 -F test/like.test 565d240313f15a8afa8d7098dc9fe303c1e2a496 +F test/like.test 0f64aeaed50b6e3ebaef3af0b3b8f894aed5acca F test/like2.test 3b2ee13149ba4a8a60b59756f4e5d345573852da F test/limit.test 2db7b3b34fb925b8e847d583d2eb67531d0ce67e F test/loadext.test 0393ce12d9616aa87597dd0ec88181de181f6db0 F test/loadext2.test 0bcaeb4d81cd5b6e883fdfea3c1bdbe1f173cbca -F test/lock.test 842e80b6be816c79525a20b098cca066989feed7 +F test/lock.test db74fdf5a73bad29ab3d862ea78bf1068972cc1d F test/lock2.test 5242d8ac4e2d59c403aebff606af449b455aceff F test/lock3.test f271375930711ae044080f4fe6d6eda930870d00 F test/lock4.test c82268c031d39345d05efa672f80b025481b3ae5 F test/lock5.test b2abb5e711bc59b0eae00f6c97a36ec9f458fada -F test/lock6.test 8df56060f396151777390982422c800d026e1722 +F test/lock6.test ad5b387a3a8096afd3c68a55b9535056431b0cf5 F test/lock7.test 64006c84c1c616657e237c7ad6532b765611cf64 -F test/lock_common.tcl 18c637fc89e12f1ac0d27d2186f12c3d3f789e3e -F test/lookaside.test 382e7bc2fab23d902c8eafb1b9ed7ababfff75a6 +F test/lock_common.tcl d279887a0ab16cdb6d935c1203e64113c5a000e9 +F test/lookaside.test 93f07bac140c5bb1d49f3892d2684decafdc7af2 F test/main.test 9d7bbfcc1b52c88ba7b2ba6554068ecf9939f252 F test/make-where7.tcl 05c16b5d4f5d6512881dfec560cb793915932ef9 F test/malloc.test 927e6c8668a1d48c23aa6189bda02aff5a1b83de -F test/malloc3.test 4bc57f850b212f706f3e1b37c4eced1d5a727cd1 +F test/malloc3.test 4128b1e6ffa506103b278ad97af89174f310c7ca F test/malloc4.test 957337613002b7058a85116493a262f679f3a261 F test/malloc5.test 4d16d1bb26d2deddd7c4f480deec341f9b2d0e22 F test/malloc6.test 2f039d9821927eacae43e1831f815e157659a151 @@ -517,11 +552,11 @@ F test/mallocH.test 79b65aed612c9b3ed2dcdaa727c85895fd1bfbdb F test/mallocI.test a88c2b9627c8506bf4703d8397420043a786cdb6 F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e F test/mallocK.test d79968641d1b70d88f6c01bdb9a7eb4a55582cc9 -F test/malloc_common.tcl f4a04b7a733eb114a3da16eb39035cde2c851220 +F test/malloc_common.tcl 660b82ab528521cc4a48ff6df05ca3b6a00d47c5 F test/manydb.test b3d3bc4c25657e7f68d157f031eb4db7b3df0d3c F test/memdb.test 0825155b2290e900264daaaf0334b6dfe69ea498 F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2 -F test/memsubsys1.test 8fb47b7e2523f94c100f5885c5697505524de4b9 +F test/memsubsys1.test 679db68394a5692791737b150852173b3e2fea10 F test/memsubsys2.test 72a731225997ad5e8df89fdbeae9224616b6aecc F test/minmax.test 722d80816f7e096bf2c04f4111f1a6c1ba65453d F test/minmax2.test 33504c01a03bd99226144e4b03f7631a274d66e0 @@ -529,12 +564,13 @@ F test/minmax3.test 66a60eb0f20281b0753249d347c5de0766954cee F test/misc1.test e56baf44656dd68d6475a4b44521045a60241e9b F test/misc2.test a628db7b03e18973e5d446c67696b03de718c9fd F test/misc3.test 72c5dc87a78e7865c5ec7a969fc572913dbe96b6 -F test/misc4.test 91e8ed25c092c2bb4e0bb01864631e2930f8d7de +F test/misc4.test 9c078510fbfff05a9869a0b6d8b86a623ad2c4f6 F test/misc5.test 45b2e3ed5f79af2b4f38ae362eaf4c49674575bd F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91 -F test/misc7.test c5f4e6a82e04e71820c0f9f64f6733f04c8ae0ae +F test/misc7.test 29032efcd3d826fbd409e2a7af873e7939f4a4e3 F test/misuse.test 30b3a458e5a70c31e74c291937b6c82204c59f33 -F test/mutex1.test 5b71777fc127509cd257910c8db799de557a02de +F test/multiplex.test 92a4839213fd8cba8b59f86d42b7a1da1857db39 +F test/mutex1.test 78b2b9bb320e51d156c4efdb71b99b051e7a4b41 F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660 F test/nan.test a44e04df1486fcfb02d32468cbcd3c8e1e433723 F test/notify1.test 8433bc74bd952fb8a6e3f8d7a4c2b28dfd69e310 @@ -543,32 +579,36 @@ F test/notify3.test d60923e186e0900f4812a845fcdfd8eea096e33a F test/notnull.test cc7c78340328e6112a13c3e311a9ab3127114347 F test/null.test a8b09b8ed87852742343b33441a9240022108993 F test/openv2.test af02ed0a9cbc0d2a61b8f35171d4d117e588e4ec -F test/pager1.test 6922029d71a8090169c71a67a141b6b94ad17d50 -F test/pager2.test 0fbb6b6dc40ce1fecfe758c555a748ad2e9beaa3 +F test/pager1.test 7006a8b5dd3df1fe0d51d7da014333d7dc099778 +F test/pager2.test 745b911dde3d1f24ae0870bd433dfa83d7c658c1 F test/pager3.test 3856d9c80839be0668efee1b74811b1b7f7fc95f -F test/pagerfault.test 84c6a4fcfe1a9e450fc1cec86f5baebfb017e53e +F test/pagerfault.test 9de4d3e0c59970b4c6cb8dac511fa242f335d8a7 F test/pagerfault2.test 1f79ea40d1133b2683a2f811b00f2399f7ec2401 +F test/pagerfault3.test 9b413f48a3e9a9a8c26968118f8db19fd7bfb8c7 F test/pageropt.test 8146bf448cf09e87bb1867c2217b921fb5857806 F test/pagesize.test 76aa9f23ecb0741a4ed9d2e16c5fa82671f28efb -F test/pcache.test 4118a183908ecaed343a06fcef3ba82e87e0129d +F test/pcache.test 065aa286e722ab24f2e51792c1f093bf60656b16 F test/pcache2.test 0d85f2ab6963aee28c671d4c71bec038c00a1d16 -F test/permutations.test 17498d1219f922d5a6da893a94c4dc7766fb2426 -F test/pragma.test ed78d200f65c6998df51196cb8c39d5300570f24 +F test/permutations.test c0ce0f3b741dd92a6d4c2671dbacba4b92dd81eb +F test/pragma.test fdfc09067ea104a0c247a1a79d8093b56656f850 F test/pragma2.test 5364893491b9231dd170e3459bfc2e2342658b47 F test/printf.test 05970cde31b1a9f54bd75af60597be75a5c54fea F test/progress.test 5b075c3c790c7b2a61419bc199db87aaf48b8301 F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc F test/quick.test 1681febc928d686362d50057c642f77a02c62e57 +F test/quota.test ddafe133653093eb9a99ccd6264884ae43f9c9b8 F test/quote.test 215897dbe8de1a6f701265836d6601cc6ed103e6 F test/randexpr1.tcl 40dec52119ed3a2b8b2a773bce24b63a3a746459 F test/randexpr1.test 1084050991e9ba22c1c10edd8d84673b501cc25a F test/rdonly.test c267d050a1d9a6a321de502b737daf28821a518d F test/reindex.test 44edd3966b474468b823d481eafef0c305022254 +F test/releasetest.mk 2eced2f9ae701fd0a29e714a241760503ccba25a +F test/releasetest.tcl 627ccd04a113a193c375594bd5d6d051d8220658 F test/rollback.test 1a83118ea6db4e7d8c10eaa63871b5e90502ffdc F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81 F test/rowid.test e58e0acef38b527ed1b0b70d3ada588f804af287 -F test/rtree.test fb372aff108d4371bd0b5e63e106947587ff4310 -F test/savepoint.test 992d6429b6bce16ac172f7431975044ceaeb0803 +F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 +F test/savepoint.test a1bef7ace82cc7922975fa96b06176e9bd5114cf F test/savepoint2.test 9b8543940572a2f01a18298c3135ad0c9f4f67d7 F test/savepoint3.test e328085853b14898d78ceea00dfe7db18bb6a9ec F test/savepoint4.test c8f8159ade6d2acd9128be61e1230f1c1edc6cc0 @@ -577,13 +617,14 @@ F test/savepoint6.test 76d3948568b2cdc0c13a671cadcae75009b183d6 F test/schema.test 8f7999be894260f151adf15c2c7540f1c6d6a481 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 F test/schema3.test 1bc1008e1f8cb5654b248c55f27249366eb7ed38 +F test/schema4.test e6a66e20cc69f0e306667c08be7fda3d11707dc5 F test/securedel.test 328d2921c0ca49bdd3352e516b0377fc07143254 F test/select1.test f67ca2dfc05df41c7b86eb32ca409b427a5f43b0 F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56 F test/select3.test 2ce595f8fb8e2ac10071d3b4e424cadd4634a054 F test/select4.test 44aa6e7110592e18110b0b9cf5c024d37d23be17 F test/select5.test e758b8ef94f69b111df4cb819008856655dcd535 -F test/select6.test 2b5e8500d8ec3dd4c8e0c99eb1431b3d11fcc24c +F test/select6.test cc25a8650cf9a4d4f74e586c45a75f9836516b18 F test/select7.test dad6f00f0d49728a879d6eb6451d4752db0b0abe F test/select8.test 391de11bdd52339c30580dabbbbe97e3e9a3c79d F test/select9.test 74c0fb2c6eecb0219cbed0cbe3df136f8fbf9343 @@ -592,7 +633,7 @@ F test/selectB.test f305cc6660804cb239aab4e2f26b0e288b59958b F test/selectC.test f9bf1bc4581b5b8158caa6e4e4f682acb379fb25 F test/server1.test f5b790d4c0498179151ca8a7715a65a7802c859c F test/shared.test b9114eaea7e748a3a4c8ff7b9ca806c8f95cef3e -F test/shared2.test d6ba4ca1827ea36a1ac23a99e3c36eeac9165450 +F test/shared2.test 7f6ad2d857d0f4e5d6a0b9a897b5e56a6b6ea18c F test/shared3.test d69bdd5f156580876c5345652d21dc2092e85962 F test/shared4.test d0fadacb50bb6981b2fb9dc6d1da30fa1edddf83 F test/shared6.test 990d2584b5db28e6e1f24742c711b26e59757b67 @@ -613,19 +654,20 @@ F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 0e51908951677de5a969b723e03a27a1c45db38b F test/sqllimits1.test e90a0ed94452076f6a10209d378e06b5f75ef0a0 -F test/stat.test 70fe540ffb285947aead5533dfd0c8c12f17f14e -F test/stmt.test 7915bd3e8380b956c095f40f41a775a30716e649 +F test/stat.test c7b20ea43003dc2dc33335e231c27be8284c4a2a +F test/stmt.test 25d64e3dbf9a3ce89558667d7f39d966fe2a71b9 F test/subquery.test b524f57c9574b2c0347045b4510ef795d4686796 F test/subselect.test d24fd8757daf97dafd2e889c73ea4c4272dcf4e4 F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a +F test/superlock.test 8468e057d8a5531ff99e504e77fcc585a0291bf2 F test/sync.test ded6b39d8d8ca3c0c5518516c6371b3316d3e3a3 F test/table.test 04ba066432430657712d167ebf28080fe878d305 F test/tableapi.test 7262a8cbaa9965d429f1cbd2747edc185fa56516 F test/tclsqlite.test 8c154101e704170c2be10f137a5499ac2c6da8d3 -F test/tempdb.test 800c36623d67a2ad1f58784b9c5644e0405af6e6 +F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05 -F test/tester.tcl 6135019fcfac363ea0e11aee670cc97080ab578e +F test/tester.tcl dafe0d30279f6d380d5d2a535781dda91b8cfc3f F test/thread001.test a3e6a7254d1cb057836cb3145b60c10bf5b7e60f F test/thread002.test afd20095e6e845b405df4f2c920cb93301ca69db F test/thread003.test b824d4f52b870ae39fc5bae4d8070eca73085dca @@ -636,23 +678,30 @@ F test/thread2.test e08034b83fe9693ade77049732518e5b3d2d700d F test/thread_common.tcl 2aa6f2fdcd4d6e461169c3e5ca098eebf643b863 F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b F test/threadtest2.c ace893054fa134af3fc8d6e7cfecddb8e3acefb9 -F test/threadtest3.c 58df1e3c060f534fd7fb0702331b0acc41c381d8 +F test/threadtest3.c d6d209190c7110f9a7e6a8154bdc3260efdbf8b7 F test/tkt-02a8e81d44.test 58494de77be2cf249228ada3f313fa399821c6ab F test/tkt-26ff0c2d1e.test 888324e751512972c6e0d1a09df740d8f5aaf660 F test/tkt-2ea2425d34.test 1cf13e6f75d149b3209a0cb32927a82d3d79fb28 F test/tkt-31338dca7e.test 5741cd48de500347a437ba1be58c8335e83c5a5e +F test/tkt-313723c356.test c47f8a9330523e6f35698bf4489bcb29609b53ac +F test/tkt-38cb5df375.test 9e9b19857dba0896a8efdaf334d405ba423492f2 +F test/tkt-3998683a16.test 6d1d04d551ed1704eb3396ca87bb9ccc8c5c1eb7 F test/tkt-3fe897352e.test 10de1a67bd5c66b238a4c96abe55531b37bb4f00 F test/tkt-4a03edc4c8.test 2865e4edbc075b954daa82f8da7cc973033ec76e +F test/tkt-5d863f876e.test 884072c2de496ddbb90c387c9ebc0d4f44a91b8e F test/tkt-5e10420e8d.test 904d1687b3c06d43e5b3555bbcf6802e7c0ffd84 F test/tkt-5ee23731f.test 3581260f2a71e51db94e1506ba6b0f7311d002a9 -F test/tkt-78e04e52ea.test fb5430c675e708f5cbafdf3e7e5593da5145a527 +F test/tkt-78e04e52ea.test ab52f0c1e2de6e46c910f4cc16b086bba05952b7 +F test/tkt-80ba201079.test a09684db1a0bd55b8838f606adccee456a51ddbf F test/tkt-80e031a00f.test 9a154173461a4dbe2de49cda73963e04842d52f7 +F test/tkt-8454a207b9.test c583a9f814a82a2b5ba95207f55001c9f0cd816c F test/tkt-94c04eaadb.test be5ea61cb04dfdc047d19b5c5a9e75fa3da67a7f F test/tkt-9d68c883.test 458f7d82a523d7644b54b497c986378a7d8c8b67 +F test/tkt-b351d95f9.test d14a503c414c5c58fdde3e80f9a3cfef986498c0 F test/tkt-cbd054fa6b.test f14f97ea43662e6f70c9e63287081e8be5d9d589 F test/tkt-d11f09d36e.test fb44f7961aa6d4b632fb7b9768239832210b5fc7 F test/tkt-d82e3f3721.test 731359dfdcdb36fea0559cd33fec39dd0ceae8e6 -F test/tkt-f3e5abed55.test 91713833e266fbdc60f2030e05647ad4762073f6 +F test/tkt-f3e5abed55.test 19fb59268da6f20a69a181b9c14154132d1c65e3 F test/tkt-f777251dc7a.test 6f24c053bc5cdb7e1e19be9a72c8887cf41d5e87 F test/tkt-f973c7ac31.test 1da0ed15ec2c7749fb5ce2828cd69d07153ad9f4 F test/tkt-fc62af4523.test 72825d3febdedcd5593a27989fc05accdbfc2bb4 @@ -668,7 +717,7 @@ F test/tkt1536.test 83ff7a7b6e248016f8d682d4f7a4ae114070d466 F test/tkt1537.test e3a14332de9770be8ff14bd15c19a49cbec10808 F test/tkt1567.test 18023cc3626a365f0118e17b66decedec93b1a6f F test/tkt1644.test 80b6a2bb17885f3cf1cb886d97cdad13232bb869 -F test/tkt1667.test c5e5a9f6c71634b5edd1c54eeb84008771526562 +F test/tkt1667.test 5d208e8d8cbcf82a446b315774290b66b464bc5f F test/tkt1873.test 255a002b9afdcf8b0fa3188984e2c964202340e9 F test/tkt2141.test f543d96f50d5a5dc0bc744f7db74ea166720ce46 F test/tkt2192.test ff40157e5f42e65f844255d220fc6b290470942f @@ -704,7 +753,7 @@ F test/tkt3346.test 6f67c3ed7db94dfc5df4f5f0b63809a1f611e01a F test/tkt3357.test 77c37c6482b526fe89941ce951c22d011f5922ed F test/tkt3419.test 1bbf36d7ea03b638c15804251287c2391f5c1f6b F test/tkt3424.test 61f831bd2b071bd128fa5d00fbda57e656ca5812 -F test/tkt3442.test 89d7b41a4ec4d9d9b40ab8575d648579fb13cb4f +F test/tkt3442.test 0adb70e9fe9cb750a702065a68ad647409dbc158 F test/tkt3457.test edbf54b05cbe5165f00192becbd621038f1615e4 F test/tkt3461.test 228ea328a5a21e8663f80ee3d212a6ad92549a19 F test/tkt3493.test 1686cbde85f8721fc1bdc0ee72f2ef2f63139218 @@ -718,7 +767,7 @@ F test/tkt35xx.test ed9721bd9eb1693b3b4d3cf2a093fa7f92af0c93 F test/tkt3630.test 929f64852103054125200bc825c316d5f75d42f7 F test/tkt3718.test 3b59dcb5c4e7754dacd91e7fd353a61492cc402a F test/tkt3731.test 0c5f4cbffe102d43c3b2188af91a9e36348f974b -F test/tkt3757.test 8f2208930655bbd4f92c14e19e72303a43e098ef +F test/tkt3757.test 10cd679a88675c880533083fc79ac04324525595 F test/tkt3761.test b95ea9c98f21cf91325f18a984887e62caceab33 F test/tkt3762.test 2a9f3b03df44ec49ec0cfa8d5da6574c2a7853df F test/tkt3773.test 430b06567ce40285dfd2c4834a2a61816403efeb @@ -741,6 +790,7 @@ F test/tkt3997.test a335fa41ca3985660a139df7b734a26ef53284bd F test/tkt4018.test 7c2c9ba4df489c676a0a7a0e809a1fb9b2185bd1 F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7 F test/trace.test 4b36a41a3e9c7842151af6da5998f5080cdad9e5 +F test/trace2.test 092bc2c5776272700450d60a36919921095bdc21 F test/trans.test 6e1b4c6a42dba31bd65f8fa5e61a2708e08ddde6 F test/trans2.test d5337e61de45e66b1fcbf9db833fa8c82e624b22 F test/trans3.test d728abaa318ca364dc370e06576aa7e5fbed7e97 @@ -755,22 +805,22 @@ F test/trigger8.test 30cb0530bd7c4728055420e3f739aa00412eafa4 F test/trigger9.test 5b0789f1c5c4600961f8e68511b825b87be53e31 F test/triggerA.test eaf11a29db2a11967d2d4b49d37f92bce598194e F test/triggerB.test 56780c031b454abac2340dbb3b71ac5c56c3d7fe -F test/triggerC.test 2a23edcc00684d084902ba5ec93e721775c3a70a +F test/triggerC.test 8a691ff6dd47df2e57395bbec4b62101fac0f363 F test/triggerD.test c6add3817351451e419f6ff9e9a259b02b6e2de7 -F test/types.test 9a825ec8eea4e965d7113b74c76a78bb5240f2ac +F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff F test/types2.test 3555aacf8ed8dc883356e59efc314707e6247a84 F test/types3.test a0f66bf12f80fad89493535474f7a6d16fa58150 F test/unique.test 083c7fff74695bcc27a71d75699deba3595bc9c2 F test/update.test 8bc86fd7ef1a00014f76dc6a6a7c974df4aef172 F test/utf16align.test 54cd35a27c005a9b6e7815d887718780b6a462ae -F test/vacuum.test 15ae6784e70428b8db64e95c92d84b19e507b719 -F test/vacuum2.test ec57f21d394b7b72249b11f8e4b5d487bab56539 +F test/vacuum.test 29b60e8cc9e573b39676df6c4a75fe9e02d04a09 +F test/vacuum2.test 2165164ed2463816e8c4648d0a779a863ce1a76c F test/vacuum3.test f39ad1428347c5808cd2da7578c470f186a4d0ce F test/vacuum4.test d3f8ecff345f166911568f397d2432c16d2867d9 F test/varint.test ab7b110089a08b9926ed7390e7e97bdefeb74102 F test/veryquick.test 7701bb609fe8bf6535514e8b849a309e8f00573b F test/view.test 45f518205ecdb6dd23a86dd4a99bb4ae945e625d -F test/vtab1.test 9bc4a349a1989bcd064eb3b8fac2f06aca64297a +F test/vtab1.test 7b79832824cbae37ff01a06ed155027f7c15bf9e F test/vtab2.test 7bcffc050da5c68f4f312e49e443063e2d391c0d F test/vtab3.test baad99fd27217f5d6db10660522e0b7192446de1 F test/vtab4.test 942f8b8280b3ea8a41dae20e7822d065ca1cb275 @@ -788,31 +838,33 @@ F test/vtab_alter.test 9e374885248f69e251bdaacf480b04a197f125e5 F test/vtab_err.test 0d4d8eb4def1d053ac7c5050df3024fd47a3fbd8 F test/vtab_shared.test 0eff9ce4f19facbe0a3e693f6c14b80711a4222d F test/wal.test 70227190e713b3e7eb2a7d5ec3510b66db01f327 -F test/wal2.test 223f3e14d475730af772a7f5862d4bcfa7565c3a -F test/wal3.test 695ea0f6c516423c611891df9a285aacd33344e3 +F test/wal2.test 3de797854de175323e7351b5f2514a30d1ee1410 +F test/wal3.test ac51126c36814bce334f66a0a4dbbfa56d429733 F test/wal4.test 3404b048fa5e10605facaf70384e6d2943412e30 +F test/wal6.test 07aa31ca8892d0527f2c5c5a9a2a87aa421dfaa8 F test/wal_common.tcl 895d76138043b86bdccf36494054bdabcf65837b F test/walbak.test 4df1c7369da0301caeb9a48fa45997fd592380e4 F test/walbig.test e882bc1d014afffbfa2b6ba36e0f07d30a633ad0 F test/walcksum.test a37b36375c595e61bdb7e1ec49b5f0979b6fc7ce F test/walcrash.test e763841551d6b23677ccb419797c1589dcbdbaf5 F test/walcrash2.test 019d60b89d96c1937adb2b30b850ac7e86e5a142 -F test/walfault.test 05c470688d742688e455dd56816bd6bcffa298f8 +F test/walfault.test 81ed760def1c1573151d416b0d09178cf006f9fd F test/walhook.test ed00a40ba7255da22d6b66433ab61fab16a63483 -F test/walmode.test 4ecd37284f245247f7935896ab66f6943f0432a0 -F test/walshared.test 985b4a3406b2b2dace1d52a42d26a11dd6900981 +F test/walmode.test 22ddccd073c817ac9ead62b88ac446e8dedc7d2c +F test/walnoshm.test a074428046408f4eb5c6a00e09df8cc97ff93317 +F test/walshared.test 6dda2293880c300baf5d791c307f653094585761 F test/walslow.test d21625e2e99e11c032ce949e8a94661576548933 F test/walthread.test a25a393c068a2b42b44333fa3fdaae9072f1617c F test/where.test de337a3fe0a459ec7c93db16a519657a90552330 F test/where2.test 43d4becaf5a5df854e6c21d624a1cb84c6904554 -F test/where3.test 3bf8006d441b66a57bee02bb420423f84eb8fde3 +F test/where3.test 8ebedae552e13fc7f2b4e8df6cbe72a095347400 F test/where4.test e9b9e2f2f98f00379e6031db6a6fca29bae782a2 F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2 F test/where6.test 5da5a98cec820d488e82708301b96cb8c18a258b -F test/where7.test a0a92b8ce48d9c027fbdd7b764c7de1e1213575a +F test/where7.test aa4cfcd6f66e2a4ef87b6717327325bf4d547502 F test/where8.test a6c740fd286d7883e274e17b6230a9d672a7ab1f F test/where8m.test da346596e19d54f0aba35ebade032a7c47d79739 -F test/where9.test be19e1a92f80985c1a121b4678bf7d2123eaa623 +F test/where9.test 7ee38c3fd67e76789a6ec769f62f6433d3d4a5cf F test/whereA.test 24c234263c8fe358f079d5e57d884fb569d2da0a F test/whereB.test 0def95db3bdec220a731c7e4bec5930327c1d8c5 F test/wherelimit.test 5e9fd41e79bb2b2d588ed999d641d9c965619b31 @@ -821,24 +873,24 @@ F tool/diffdb.c 7524b1b5df217c20cd0431f6789851a4e0cb191b F tool/fragck.tcl 5265a95126abcf6ab357f7efa544787e5963f439 F tool/genfkey.README cf68fddd4643bbe3ff8e31b8b6d8b0a1b85e20f4 F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5 -F tool/lemon.c fe890e2d8d2db1e3f57e2a22503dbb0f6843e517 +F tool/lemon.c dfd81a51b6e27e469ba21d01a75ddf092d429027 F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc F tool/mkkeywordhash.c d2e6b4a5965e23afb80fbe74bb54648cd371f309 F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 -F tool/mksqlite3c.tcl aff0d53f0e84cf919922c0d02e767bdf5eeafb90 -F tool/mksqlite3h.tcl eb100dce83f24b501b325b340f8b5eb8e5106b3b +F tool/mksqlite3c.tcl e0db70c2c52b0e3d0867ca931229e5b90ffe7837 +F tool/mksqlite3h.tcl d76c226a5e8e1f3b5f6593bcabe5e98b3b1ec9ff F tool/mksqlite3internalh.tcl 7b43894e21bcb1bb39e11547ce7e38a063357e87 F tool/omittest.tcl 27d6f6e3b1e95aeb26a1c140e6eb57771c6d794a F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a -F tool/shell1.test 930444cadb71ce9ce78bc6cd14ec21e6b69776ea +F tool/shell1.test c31b0814a9c543db51ca0cc63edb5e77ea532303 F tool/shell2.test 5dc76b8005b465f420fed8241621da7513060ff3 F tool/shell3.test 4fad469e8003938426355afdf34155f08c587836 F tool/shell4.test 35f9c3d452b4e76d5013c63e1fd07478a62f14ce F tool/shell5.test 62bfaf9267296da1b91e4b1c03e44e7b393f6a94 -F tool/showdb.c c7a978cf525ef0f8bc2fd29cd52108dd1dfa605a -F tool/showjournal.c ec3b171be148656827c4949fbfb8ab4370822f87 +F tool/showdb.c 471c0f8fa472e71bb7654500096a5bdb4ea1fb2a +F tool/showjournal.c b62cecaab86a4053d944c276bb5232e4d17ece02 F tool/showwal.c f09e5a80a293919290ec85a6a37c85a5ddcf37d9 F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe F tool/space_used.tcl f714c41a59e326b8b9042f415b628b561bafa06b @@ -849,14 +901,14 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P 21a1e5961bba148fda50cc0b7d472ca74f90808a -R aa9cc30f9dbcb7ea427137065cf4e4be +P 682fe41efd3578e8c9abc7138b61f361c3adbe95 +R 7fccaf9f7c013ddef8926255f6aeb773 U drh -Z e73ee740c6593b4458c2d23b6fa7b923 +Z 4223173e58f25d45d236e232b9c90989 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) -iD8DBQFMcsNUoxKgR168RlERApyRAKCOHvk0Gn1uE2F8YVm3k/w13/3UugCeOh16 -bApiXjM1a7FP+Qr1HX5kWTQ= -=L2Lf +iD8DBQFNQvb6oxKgR168RlERAtOTAJ9BaewewKXL3RGZUy5ycaHwjeEmJACeOPbw +/JKqOugR+37RH7HnLCo9DBk= +=kgPx -----END PGP SIGNATURE----- diff --git a/manifest.uuid b/manifest.uuid index 410be1f1..af0bce60 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -42537b60566f288167f1b5864a5435986838e3a3 +ed759d5a9edb3bba5f48f243df47be29e3fe8cd7 diff --git a/src/alter.c b/src/alter.c index a863c454..1534fdf6 100644 --- a/src/alter.c +++ b/src/alter.c @@ -313,6 +313,11 @@ static char *whereTempTriggers(Parse *pParse, Table *pTab){ } } } + if( zWhere ){ + char *zNew = sqlite3MPrintf(pParse->db, "type='trigger' AND (%s)", zWhere); + sqlite3DbFree(pParse->db, zWhere); + zWhere = zNew; + } return zWhere; } diff --git a/src/analyze.c b/src/analyze.c index 59849456..0a8339ba 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -113,7 +113,8 @@ static void analyzeOneTable( int i; /* Loop counter */ int topOfLoop; /* The top of the loop */ int endOfLoop; /* The end of the loop */ - int addr; /* The address of an instruction */ + int addr = 0; /* The address of an instruction */ + int jZeroRows = 0; /* Jump from here if number of rows is zero */ int iDb; /* Index of database containing pTab */ int regTabname = iMem++; /* Register containing table name */ int regIdxname = iMem++; /* Register containing index name */ @@ -132,8 +133,15 @@ static void analyzeOneTable( #endif v = sqlite3GetVdbe(pParse); - if( v==0 || NEVER(pTab==0) || pTab->pIndex==0 ){ - /* Do no analysis for tables that have no indices */ + if( v==0 || NEVER(pTab==0) ){ + return; + } + if( pTab->tnum==0 ){ + /* Do not gather statistics on views or virtual tables */ + return; + } + if( memcmp(pTab->zName, "sqlite_", 7)==0 ){ + /* Do not gather statistics on system tables */ return; } assert( sqlite3BtreeHoldsAllMutexes(db) ); @@ -150,6 +158,7 @@ static void analyzeOneTable( sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); iIdxCur = pParse->nTab++; + sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0); for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ int nCol = pIdx->nColumn; KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); @@ -164,10 +173,7 @@ static void analyzeOneTable( (char *)pKey, P4_KEYINFO_HANDOFF); VdbeComment((v, "%s", pIdx->zName)); - /* Populate the registers containing the table and index names. */ - if( pTab->pIndex==pIdx ){ - sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0); - } + /* Populate the register containing the index name. */ sqlite3VdbeAddOp4(v, OP_String8, 0, regIdxname, 0, pIdx->zName, 0); #ifdef SQLITE_ENABLE_STAT2 @@ -227,9 +233,10 @@ static void analyzeOneTable( sqlite3VdbeAddOp2(v, OP_AddImm, iMem, 1); for(i=0; iazColl!=0 ); + assert( pIdx->azColl[i]!=0 ); + pColl = sqlite3LocateCollSeq(pParse, pIdx->azColl[i]); + sqlite3VdbeAddOp4(v, OP_Ne, regCol, 0, iMem+nCol+i+1, + (char*)pColl, P4_COLLSEQ); + sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); } if( db->mallocFailed ){ /* If a malloc failure has occurred, then the result of the expression @@ -274,7 +286,11 @@ static void analyzeOneTable( } sqlite3VdbeAddOp2(v, OP_Goto, 0, endOfLoop); for(i=0; i0 then it is always the case the D>0 so division by zero ** is never possible. */ - addr = sqlite3VdbeAddOp1(v, OP_IfNot, iMem); sqlite3VdbeAddOp2(v, OP_SCopy, iMem, regSampleno); + if( jZeroRows==0 ){ + jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, iMem); + } for(i=0; ipIndex==0 ){ + sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pTab->tnum, iDb); + VdbeComment((v, "%s", pTab->zName)); + sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regSampleno); + sqlite3VdbeAddOp1(v, OP_Close, iIdxCur); + }else{ + assert( jZeroRows>0 ); + addr = sqlite3VdbeAddOp0(v, OP_Goto); + sqlite3VdbeJumpHere(v, jZeroRows); + } + sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regRec, "aaa", 0); + sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regRowid); + sqlite3VdbeChangeP5(v, OPFLAG_APPEND); + if( pParse->nMemnMem = regRec; + if( jZeroRows ){ sqlite3VdbeJumpHere(v, addr); } } /* ** Generate code that will cause the most recent index analysis to -** be laoded into internal hash tables where is can be used. +** be loaded into internal hash tables where is can be used. */ static void loadAnalysis(Parse *pParse, int iDb){ Vdbe *v = sqlite3GetVdbe(pParse); @@ -453,33 +493,46 @@ struct analysisInfo { ** This callback is invoked once for each index when reading the ** sqlite_stat1 table. ** -** argv[0] = name of the index -** argv[1] = results of analysis - on integer for each column +** argv[0] = name of the table +** argv[1] = name of the index (might be NULL) +** argv[2] = results of analysis - on integer for each column +** +** Entries for which argv[1]==NULL simply record the number of rows in +** the table. */ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){ analysisInfo *pInfo = (analysisInfo*)pData; Index *pIndex; - int i, c; + Table *pTable; + int i, c, n; unsigned int v; const char *z; - assert( argc==2 ); + assert( argc==3 ); UNUSED_PARAMETER2(NotUsed, argc); - if( argv==0 || argv[0]==0 || argv[1]==0 ){ + if( argv==0 || argv[0]==0 || argv[2]==0 ){ return 0; } - pIndex = sqlite3FindIndex(pInfo->db, argv[0], pInfo->zDatabase); - if( pIndex==0 ){ + pTable = sqlite3FindTable(pInfo->db, argv[0], pInfo->zDatabase); + if( pTable==0 ){ return 0; } - z = argv[1]; - for(i=0; *z && i<=pIndex->nColumn; i++){ + if( argv[1] ){ + pIndex = sqlite3FindIndex(pInfo->db, argv[1], pInfo->zDatabase); + }else{ + pIndex = 0; + } + n = pIndex ? pIndex->nColumn : 0; + z = argv[2]; + for(i=0; *z && i<=n; i++){ v = 0; while( (c=z[0])>='0' && c<='9' ){ v = v*10 + c - '0'; z++; } + if( i==0 ) pTable->nRowEst = v; + if( pIndex==0 ) break; pIndex->aiRowEst[i] = v; if( *z==' ' ) z++; } @@ -555,7 +608,7 @@ int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ /* Load new statistics out of the sqlite_stat1 table */ zSql = sqlite3MPrintf(db, - "SELECT idx, stat FROM %Q.sqlite_stat1", sInfo.zDatabase); + "SELECT tbl, idx, stat FROM %Q.sqlite_stat1", sInfo.zDatabase); if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ @@ -583,8 +636,11 @@ int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ if( rc==SQLITE_OK ){ while( sqlite3_step(pStmt)==SQLITE_ROW ){ - char *zIndex = (char *)sqlite3_column_text(pStmt, 0); - Index *pIdx = sqlite3FindIndex(db, zIndex, sInfo.zDatabase); + char *zIndex; /* Index name */ + Index *pIdx; /* Pointer to the index object */ + + zIndex = (char *)sqlite3_column_text(pStmt, 0); + pIdx = zIndex ? sqlite3FindIndex(db, zIndex, sInfo.zDatabase) : 0; if( pIdx ){ int iSample = sqlite3_column_int(pStmt, 1); if( iSample=0 ){ diff --git a/src/attach.c b/src/attach.c index 30a4207c..e3dc49d9 100644 --- a/src/attach.c +++ b/src/attach.c @@ -124,9 +124,8 @@ static void attachFunc( ** it to obtain the database schema. At this point the schema may ** or may not be initialised. */ - rc = sqlite3BtreeFactory(db, zFile, 0, SQLITE_DEFAULT_CACHE_SIZE, - db->openFlags | SQLITE_OPEN_MAIN_DB, - &aNew->pBt); + rc = sqlite3BtreeOpen(zFile, db, &aNew->pBt, 0, + db->openFlags | SQLITE_OPEN_MAIN_DB); db->nDb++; if( rc==SQLITE_CONSTRAINT ){ rc = SQLITE_ERROR; @@ -367,7 +366,8 @@ void sqlite3Detach(Parse *pParse, Expr *pDbname){ 0, /* xStep */ 0, /* xFinalize */ "sqlite_detach", /* zName */ - 0 /* pHash */ + 0, /* pHash */ + 0 /* pDestructor */ }; codeAttach(pParse, SQLITE_DETACH, &detach_func, pDbname, 0, 0, pDbname); } @@ -388,7 +388,8 @@ void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *pKey){ 0, /* xStep */ 0, /* xFinalize */ "sqlite_attach", /* zName */ - 0 /* pHash */ + 0, /* pHash */ + 0 /* pDestructor */ }; codeAttach(pParse, SQLITE_ATTACH, &attach_func, p, p, pDbname, pKey); } diff --git a/src/backup.c b/src/backup.c index 1fc01e1a..5d8ea7f3 100644 --- a/src/backup.c +++ b/src/backup.c @@ -117,6 +117,16 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){ return pDb->aDb[i].pBt; } +/* +** Attempt to set the page size of the destination to match the page size +** of the source. +*/ +static int setDestPgsz(sqlite3_backup *p){ + int rc; + rc = sqlite3BtreeSetPageSize(p->pDest,sqlite3BtreeGetPageSize(p->pSrc),-1,0); + return rc; +} + /* ** Create an sqlite3_backup process to copy the contents of zSrcDb from ** connection handle pSrcDb to zDestDb in pDestDb. If successful, return @@ -150,7 +160,10 @@ sqlite3_backup *sqlite3_backup_init( ); p = 0; }else { - /* Allocate space for a new sqlite3_backup object */ + /* Allocate space for a new sqlite3_backup object... + ** 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 *)sqlite3_malloc(sizeof(sqlite3_backup)); if( !p ){ sqlite3Error(pDestDb, SQLITE_NOMEM, 0); @@ -167,10 +180,11 @@ sqlite3_backup *sqlite3_backup_init( p->iNext = 1; p->isAttached = 0; - if( 0==p->pSrc || 0==p->pDest ){ - /* One (or both) of the named databases did not exist. An error has - ** already been written into the pDestDb handle. All that is left - ** to do here is free the sqlite3_backup structure. + if( 0==p->pSrc || 0==p->pDest || setDestPgsz(p)==SQLITE_NOMEM ){ + /* 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. */ sqlite3_free(p); p = 0; @@ -427,32 +441,46 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){ */ const i64 iSize = (i64)pgszSrc * (i64)nSrcPage; sqlite3_file * const pFile = sqlite3PagerFile(pDestPager); + i64 iOff; + i64 iEnd; assert( pFile ); assert( (i64)nDestTruncate*(i64)pgszDest >= iSize || ( nDestTruncate==(int)(PENDING_BYTE_PAGE(p->pDest->pBt)-1) && iSize>=PENDING_BYTE && iSize<=PENDING_BYTE+pgszDest )); - if( SQLITE_OK==(rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 1)) - && SQLITE_OK==(rc = backupTruncateFile(pFile, iSize)) - && SQLITE_OK==(rc = sqlite3PagerSync(pDestPager)) + + /* This call ensures that all data required to recreate the original + ** database has been stored in the journal for pDestPager and the + ** journal synced to disk. So at this point we may safely modify + ** the database file in any way, knowing that if a power failure + ** occurs, the original database will be reconstructed from the + ** journal file. */ + rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 1); + + /* Write the extra pages and truncate the database file as required. */ + iEnd = MIN(PENDING_BYTE + pgszDest, iSize); + for( + iOff=PENDING_BYTE+pgszSrc; + rc==SQLITE_OK && iOffpSrc); if( p->pDestDb ){ + /* 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(). */ sqlite3_free(p); } sqlite3_mutex_leave(mutex); diff --git a/src/btree.c b/src/btree.c index 378a2183..aa120159 100644 --- a/src/btree.c +++ b/src/btree.c @@ -918,14 +918,9 @@ static void btreeParseCellPtr( /* This is the (easy) common case where the entire payload fits ** on the local page. No overflow is required. */ - int nSize; /* Total size of cell content in bytes */ - nSize = nPayload + n; + if( (pInfo->nSize = (u16)(n+nPayload))<4 ) pInfo->nSize = 4; pInfo->nLocal = (u16)nPayload; pInfo->iOverflow = 0; - if( (nSize & ~3)==0 ){ - nSize = 4; /* Minimum cell size is 4 */ - } - pInfo->nSize = (u16)nSize; }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 @@ -1672,11 +1667,20 @@ static int btreeInvokeBusyHandler(void *pArg){ ** Open a database file. ** ** zFilename is the name of the database file. If zFilename is NULL -** a new database with a random name is created. This randomly named -** database file will be deleted when sqlite3BtreeClose() is called. +** then an ephemeral database is created. The ephemeral database might +** be exclusively in memory, or it might use a disk-based memory cache. +** Either way, the ephemeral database will be automatically deleted +** when sqlite3BtreeClose() is called. +** ** If zFilename is ":memory:" then an in-memory database is created ** that is automatically destroyed when it is closed. ** +** The "flags" parameter is a bitmask that might contain bits +** BTREE_OMIT_JOURNAL and/or BTREE_NO_READLOCK. The BTREE_NO_READLOCK +** bit is also set if the SQLITE_NoReadlock flags is set in db->flags. +** These flags are passed through into sqlite3PagerOpen() and must +** be the same values as PAGER_OMIT_JOURNAL and PAGER_NO_READLOCK. +** ** If the database is already opened in the same database connection ** and we are in shared cache mode, then the open will fail with an ** SQLITE_CONSTRAINT error. We cannot allow two or more BtShared @@ -1698,22 +1702,38 @@ int sqlite3BtreeOpen( u8 nReserve; /* Byte of unused space on each page */ unsigned char zDbHeader[100]; /* Database header content */ + /* True if opening an ephemeral, temporary database */ + const int isTempDb = zFilename==0 || zFilename[0]==0; + /* Set the variable isMemdb to true for an in-memory database, or - ** false for a file-based database. This symbol is only required if - ** either of the shared-data or autovacuum features are compiled - ** into the library. + ** false for a file-based database. */ -#if !defined(SQLITE_OMIT_SHARED_CACHE) || !defined(SQLITE_OMIT_AUTOVACUUM) - #ifdef SQLITE_OMIT_MEMORYDB - const int isMemdb = 0; - #else - const int isMemdb = zFilename && !strcmp(zFilename, ":memory:"); - #endif +#ifdef SQLITE_OMIT_MEMORYDB + const int isMemdb = 0; +#else + const int isMemdb = (zFilename && strcmp(zFilename, ":memory:")==0) + || (isTempDb && sqlite3TempInMemory(db)); #endif assert( db!=0 ); assert( sqlite3_mutex_held(db->mutex) ); + assert( (flags&0xff)==flags ); /* flags fit in 8 bits */ + /* Only a BTREE_SINGLE database can be BTREE_UNORDERED */ + assert( (flags & BTREE_UNORDERED)==0 || (flags & BTREE_SINGLE)!=0 ); + + /* A BTREE_SINGLE database is always a temporary and/or ephemeral */ + assert( (flags & BTREE_SINGLE)==0 || isTempDb ); + + if( db->flags & SQLITE_NoReadlock ){ + flags |= BTREE_NO_READLOCK; + } + if( isMemdb ){ + flags |= BTREE_MEMORY; + } + if( (vfsFlags & SQLITE_OPEN_MAIN_DB)!=0 && (isMemdb || isTempDb) ){ + vfsFlags = (vfsFlags & ~SQLITE_OPEN_MAIN_DB) | SQLITE_OPEN_TEMP_DB; + } pVfs = db->pVfs; p = sqlite3MallocZero(sizeof(Btree)); if( !p ){ @@ -1731,7 +1751,7 @@ int sqlite3BtreeOpen( ** If this Btree is a candidate for shared cache, try to find an ** existing BtShared object that we can share with */ - if( isMemdb==0 && zFilename && zFilename[0] ){ + if( isMemdb==0 && isTempDb==0 ){ if( vfsFlags & SQLITE_OPEN_SHAREDCACHE ){ int nFullPathname = pVfs->mxPathname+1; char *zFullPathname = sqlite3Malloc(nFullPathname); @@ -1806,6 +1826,7 @@ int sqlite3BtreeOpen( if( rc!=SQLITE_OK ){ goto btree_open_out; } + pBt->openFlags = (u8)flags; pBt->db = db; sqlite3PagerSetBusyhandler(pBt->pPager, btreeInvokeBusyHandler, pBt); p->pBt = pBt; @@ -1910,6 +1931,14 @@ btree_open_out: sqlite3_free(pBt); sqlite3_free(p); *ppBtree = 0; + }else{ + /* If the B-Tree was successfully opened, set the pager-cache size to the + ** default value. Except, when opening on an existing shared pager-cache, + ** do not change the pager-cache size. + */ + if( sqlite3BtreeSchema(p, 0, 0)==0 ){ + sqlite3PagerSetCachesize(p->pBt->pPager, SQLITE_DEFAULT_CACHE_SIZE); + } } if( mutexOpen ){ assert( sqlite3_mutex_held(mutexOpen) ); @@ -2067,11 +2096,17 @@ int sqlite3BtreeSetCacheSize(Btree *p, int mxPage){ ** probability of damage to near zero but with a write performance reduction. */ #ifndef SQLITE_OMIT_PAGER_PRAGMAS -int sqlite3BtreeSetSafetyLevel(Btree *p, int level, int fullSync){ +int sqlite3BtreeSetSafetyLevel( + Btree *p, /* The btree to set the safety level on */ + int level, /* PRAGMA synchronous. 1=OFF, 2=NORMAL, 3=FULL */ + int fullSync, /* PRAGMA fullfsync. */ + int ckptFullSync /* PRAGMA checkpoint_fullfync */ +){ BtShared *pBt = p->pBt; assert( sqlite3_mutex_held(p->db->mutex) ); + assert( level>=1 && level<=3 ); sqlite3BtreeEnter(p); - sqlite3PagerSetSafetyLevel(pBt->pPager, level, fullSync); + sqlite3PagerSetSafetyLevel(pBt->pPager, level, fullSync, ckptFullSync); sqlite3BtreeLeave(p); return SQLITE_OK; } @@ -2346,7 +2381,7 @@ static int lockBtree(BtShared *pBt){ pageSize-usableSize); return rc; } - if( nPageHeader>nPageFile ){ + if( (pBt->db->flags & SQLITE_RecoveryMode)==0 && nPageHeader>nPageFile ){ rc = SQLITE_CORRUPT_BKPT; goto page1_init_failed; } @@ -3129,8 +3164,8 @@ static void btreeEndTransaction(Btree *p){ ** are no active cursors, it also releases the read lock. */ int sqlite3BtreeCommitPhaseTwo(Btree *p){ - BtShared *pBt = p->pBt; + if( p->inTrans==TRANS_NONE ) return SQLITE_OK; sqlite3BtreeEnter(p); btreeIntegrity(p); @@ -3139,6 +3174,7 @@ int sqlite3BtreeCommitPhaseTwo(Btree *p){ */ if( p->inTrans==TRANS_WRITE ){ int rc; + BtShared *pBt = p->pBt; assert( pBt->inTransaction==TRANS_WRITE ); assert( pBt->nTransaction>0 ); rc = sqlite3PagerCommitPhaseTwo(pBt->pPager); @@ -6860,11 +6896,12 @@ int sqlite3BtreeDelete(BtCursor *pCur){ ** BTREE_INTKEY|BTREE_LEAFDATA Used for SQL tables with rowid keys ** BTREE_ZERODATA Used for SQL indices */ -static int btreeCreateTable(Btree *p, int *piTable, int flags){ +static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){ BtShared *pBt = p->pBt; MemPage *pRoot; Pgno pgnoRoot; int rc; + int ptfFlags; /* Page-type flage for the root page of new table */ assert( sqlite3BtreeHoldsMutex(p) ); assert( pBt->inTransaction==TRANS_WRITE ); @@ -6983,8 +7020,14 @@ static int btreeCreateTable(Btree *p, int *piTable, int flags){ } #endif assert( sqlite3PagerIswriteable(pRoot->pDbPage) ); - zeroPage(pRoot, flags | PTF_LEAF); + if( createTabFlags & BTREE_INTKEY ){ + ptfFlags = PTF_INTKEY | PTF_LEAFDATA | PTF_LEAF; + }else{ + ptfFlags = PTF_ZERODATA | PTF_LEAF; + } + zeroPage(pRoot, ptfFlags); sqlite3PagerUnref(pRoot->pDbPage); + assert( (pBt->openFlags & BTREE_SINGLE)==0 || pgnoRoot==2 ); *piTable = (int)pgnoRoot; return SQLITE_OK; } @@ -8050,8 +8093,7 @@ int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){ void sqlite3BtreeCacheOverflow(BtCursor *pCur){ assert( cursorHoldsMutex(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); - assert(!pCur->isIncrblobHandle); - assert(!pCur->aOverflow); + invalidateOverflowCache(pCur); pCur->isIncrblobHandle = 1; } #endif diff --git a/src/btree.h b/src/btree.h index c989307a..90fa7a2e 100644 --- a/src/btree.h +++ b/src/btree.h @@ -67,16 +67,15 @@ int sqlite3BtreeOpen( ** NOTE: These values must match the corresponding PAGER_ values in ** pager.h. */ -#define BTREE_OMIT_JOURNAL 1 /* Do not use journal. No argument */ +#define BTREE_OMIT_JOURNAL 1 /* Do not create or use a rollback journal */ #define BTREE_NO_READLOCK 2 /* Omit readlocks on readonly files */ -#define BTREE_MEMORY 4 /* In-memory DB. No argument */ -#define BTREE_READONLY 8 /* Open the database in read-only mode */ -#define BTREE_READWRITE 16 /* Open for both reading and writing */ -#define BTREE_CREATE 32 /* Create the database if it does not exist */ +#define BTREE_MEMORY 4 /* This is an in-memory DB */ +#define BTREE_SINGLE 8 /* The file contains at most 1 b-tree */ +#define BTREE_UNORDERED 16 /* Use of a hash implementation is OK */ int sqlite3BtreeClose(Btree*); int sqlite3BtreeSetCacheSize(Btree*,int); -int sqlite3BtreeSetSafetyLevel(Btree*,int,int); +int sqlite3BtreeSetSafetyLevel(Btree*,int,int,int); int sqlite3BtreeSyncDisabled(Btree*); int sqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix); int sqlite3BtreeGetPageSize(Btree*); @@ -108,11 +107,17 @@ int sqlite3BtreeCopyFile(Btree *, Btree *); int sqlite3BtreeIncrVacuum(Btree *); /* The flags parameter to sqlite3BtreeCreateTable can be the bitwise OR -** of the following flags: +** of the flags shown below. +** +** Every SQLite table must have either BTREE_INTKEY or BTREE_BLOBKEY set. +** With BTREE_INTKEY, the table key is a 64-bit integer and arbitrary data +** is stored in the leaves. (BTREE_INTKEY is used for SQL tables.) With +** BTREE_BLOBKEY, the key is an arbitrary BLOB and no content is stored +** anywhere - the key is the content. (BTREE_BLOBKEY is used for SQL +** indices.) */ #define BTREE_INTKEY 1 /* Table has only 64-bit signed integer keys */ -#define BTREE_ZERODATA 2 /* Table has keys only - no data */ -#define BTREE_LEAFDATA 4 /* Data stored in leaves only. Implies INTKEY */ +#define BTREE_BLOBKEY 2 /* Table has keys only - no data */ int sqlite3BtreeDropTable(Btree*, int, int*); int sqlite3BtreeClearTable(Btree*, int, int*); diff --git a/src/btreeInt.h b/src/btreeInt.h index 7b46bceb..0e71195b 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -409,16 +409,17 @@ struct BtShared { u8 pageSizeFixed; /* True if the page size can no longer be changed */ u8 secureDelete; /* True if secure_delete is enabled */ u8 initiallyEmpty; /* Database is empty at start of transaction */ + u8 openFlags; /* Flags to sqlite3BtreeOpen() */ #ifndef SQLITE_OMIT_AUTOVACUUM u8 autoVacuum; /* True if auto-vacuum is enabled */ u8 incrVacuum; /* True if incr-vacuum is enabled */ #endif + u8 inTransaction; /* Transaction state */ + u8 doNotUseWAL; /* If true, do not open write-ahead-log file */ 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 */ - u8 inTransaction; /* Transaction state */ - u8 doNotUseWAL; /* If true, do not open write-ahead-log file */ u32 pageSize; /* Total number of bytes on a page */ u32 usableSize; /* Number of usable bytes on each page */ int nTransaction; /* Number of open transactions (read + write) */ @@ -445,8 +446,8 @@ struct BtShared { */ typedef struct CellInfo CellInfo; struct CellInfo { - u8 *pCell; /* Pointer to the start of cell content */ 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 */ @@ -488,20 +489,20 @@ struct BtCursor { 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 */ u8 eState; /* One of the CURSOR_XXX constants (see below) */ - void *pKey; /* Saved key that was cursor's last known position */ - i64 nKey; /* Size of pKey, or last integer key */ - int skipNext; /* Prev() is noop if negative. Next() is noop if positive */ #ifndef SQLITE_OMIT_INCRBLOB - u8 isIncrblobHandle; /* True if this cursor is an incr. io handle */ Pgno *aOverflow; /* Cache of overflow page locations */ + u8 isIncrblobHandle; /* True if this cursor is an incr. io handle */ #endif i16 iPage; /* Index of current page in apPage */ - MemPage *apPage[BTCURSOR_MAX_DEPTH]; /* Pages from root to current page */ u16 aiIdx[BTCURSOR_MAX_DEPTH]; /* Current index in apPage[i] */ + MemPage *apPage[BTCURSOR_MAX_DEPTH]; /* Pages from root to current page */ }; /* diff --git a/src/build.c b/src/build.c index 636b8a69..2cfb1f45 100644 --- a/src/build.c +++ b/src/build.c @@ -726,8 +726,9 @@ void sqlite3StartTable( */ iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName); if( iDb<0 ) return; - if( !OMIT_TEMPDB && isTemp && iDb>1 ){ - /* If creating a temp table, the name may not be qualified */ + if( !OMIT_TEMPDB && isTemp && pName2->n>0 && iDb!=1 ){ + /* If creating a temp table, the name may not be qualified. Unless + ** the database name is "temp" anyway. */ sqlite3ErrorMsg(pParse, "temporary table name must be unqualified"); return; } @@ -775,17 +776,18 @@ void sqlite3StartTable( ** collisions. */ if( !IN_DECLARE_VTAB ){ + char *zDb = db->aDb[iDb].zName; if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ goto begin_table_error; } - pTable = sqlite3FindTable(db, zName, db->aDb[iDb].zName); + pTable = sqlite3FindTable(db, zName, zDb); if( pTable ){ if( !noErr ){ sqlite3ErrorMsg(pParse, "table %T already exists", pName); } goto begin_table_error; } - if( sqlite3FindIndex(db, zName, 0)!=0 && (iDb==0 || !db->init.busy) ){ + if( sqlite3FindIndex(db, zName, zDb)!=0 ){ sqlite3ErrorMsg(pParse, "there is already an index named %s", zName); goto begin_table_error; } @@ -802,6 +804,7 @@ void sqlite3StartTable( pTable->iPKey = -1; pTable->pSchema = db->aDb[iDb].pSchema; pTable->nRef = 1; + pTable->nRowEst = 1000000; assert( pParse->pNewTable==0 ); pParse->pNewTable = pTable; @@ -1648,12 +1651,10 @@ void sqlite3CreateView( } sqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr); p = pParse->pNewTable; - if( p==0 ){ + if( p==0 || pParse->nErr ){ sqlite3SelectDelete(db, pSelect); return; } - assert( pParse->nErr==0 ); /* If sqlite3StartTable return non-NULL then - ** there could not have been an error */ sqlite3TwoPartName(pParse, pName1, pName2, &pName); iDb = sqlite3SchemaToIndex(db, p->pSchema); if( sqlite3FixInit(&sFix, pParse, iDb, "view", pName) @@ -2771,7 +2772,8 @@ Index *sqlite3CreateIndex( sqlite3RefillIndex(pParse, pIndex, iMem); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 0, 0, - sqlite3MPrintf(db, "name='%q'", pIndex->zName), P4_DYNAMIC); + sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName), + P4_DYNAMIC); sqlite3VdbeAddOp1(v, OP_Expire, 0); } } @@ -2832,14 +2834,14 @@ exit_create_index: void sqlite3DefaultRowEst(Index *pIdx){ unsigned *a = pIdx->aiRowEst; int i; + unsigned n; assert( a!=0 ); - a[0] = 1000000; - for(i=pIdx->nColumn; i>=5; i--){ - a[i] = 5; - } - while( i>=1 ){ - a[i] = 11 - i; - i--; + 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; @@ -2899,7 +2901,7 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){ if( v ){ sqlite3BeginWriteOperation(pParse, 1, iDb); sqlite3NestedParse(pParse, - "DELETE FROM %Q.%s WHERE name=%Q", + "DELETE FROM %Q.%s WHERE name=%Q AND type='index'", db->aDb[iDb].zName, SCHEMA_TABLE(iDb), pIndex->zName ); @@ -3391,7 +3393,7 @@ int sqlite3OpenTempDatabase(Parse *pParse){ SQLITE_OPEN_DELETEONCLOSE | SQLITE_OPEN_TEMP_DB; - rc = sqlite3BtreeFactory(db, 0, 0, SQLITE_DEFAULT_CACHE_SIZE, flags, &pBt); + rc = sqlite3BtreeOpen(0, db, &pBt, 0, flags); if( rc!=SQLITE_OK ){ sqlite3ErrorMsg(pParse, "unable to open a temporary database " "file for storing temporary tables"); diff --git a/src/callback.c b/src/callback.c index fdcc9f9c..eaff6d0e 100644 --- a/src/callback.c +++ b/src/callback.c @@ -358,7 +358,7 @@ FuncDef *sqlite3FindFunction( ** priority to built-in functions. ** ** Except, if createFlag is true, that means that we are trying to - ** install a new function. Whatever FuncDef structure is returned will + ** install a new function. Whatever FuncDef structure is returned it will ** have fields overwritten with new information appropriate for the ** new function. But the FuncDefs for built-in functions are read-only. ** So we must not search for built-ins when creating a new function. diff --git a/src/ctime.c b/src/ctime.c index f7248f4f..a128f61a 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -174,6 +174,9 @@ static const char * const azCompileOpt[] = { #ifdef SQLITE_OMIT_AUTOMATIC_INDEX "OMIT_AUTOMATIC_INDEX", #endif +#ifdef SQLITE_OMIT_AUTORESET + "OMIT_AUTORESET", +#endif #ifdef SQLITE_OMIT_AUTOVACUUM "OMIT_AUTOVACUUM", #endif diff --git a/src/date.c b/src/date.c index 04ffbe23..b81049aa 100644 --- a/src/date.c +++ b/src/date.c @@ -133,12 +133,6 @@ end_getDigits: return cnt; } -/* -** Read text from z[] and convert into a floating point number. Return -** the number of digits converted. -*/ -#define getValue sqlite3AtoF - /* ** Parse a timezone extension on the end of a date-time. ** The extension is of the form: @@ -340,7 +334,7 @@ static int parseDateOrTime( const char *zDate, DateTime *p ){ - int isRealNum; /* Return from sqlite3IsNumber(). Not used */ + double r; if( parseYyyyMmDd(zDate,p)==0 ){ return 0; }else if( parseHhMmSs(zDate, p)==0 ){ @@ -348,9 +342,7 @@ static int parseDateOrTime( }else if( sqlite3StrICmp(zDate,"now")==0){ setDateTimeToCurrent(context, p); return 0; - }else if( sqlite3IsNumber(zDate, &isRealNum, SQLITE_UTF8) ){ - double r; - getValue(zDate, &r); + }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8) ){ p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5); p->validJD = 1; return 0; @@ -571,8 +563,9 @@ static int parseModifier(const char *zMod, DateTime *p){ ** weekday N where 0==Sunday, 1==Monday, and so forth. If the ** date is already on the appropriate weekday, this is a no-op. */ - if( strncmp(z, "weekday ", 8)==0 && getValue(&z[8],&r)>0 - && (n=(int)r)==r && n>=0 && r<7 ){ + if( strncmp(z, "weekday ", 8)==0 + && sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8) + && (n=(int)r)==r && n>=0 && r<7 ){ sqlite3_int64 Z; computeYMD_HMS(p); p->validTZ = 0; @@ -627,8 +620,11 @@ static int parseModifier(const char *zMod, DateTime *p){ case '8': case '9': { double rRounder; - n = getValue(z, &r); - assert( n>=1 ); + for(n=1; z[n] && z[n]!=':' && !sqlite3Isspace(z[n]); n++){} + if( !sqlite3AtoF(z, &r, n, SQLITE_UTF8) ){ + rc = 1; + break; + } if( z[n]==':' ){ /* A modifier of the form (+|-)HH:MM:SS.FFF adds (or subtracts) the ** specified number of hours, minutes, seconds, and fractional seconds diff --git a/src/expr.c b/src/expr.c index 33864b2d..b902f451 100644 --- a/src/expr.c +++ b/src/expr.c @@ -484,6 +484,9 @@ Expr *sqlite3PExpr( ){ Expr *p = sqlite3ExprAlloc(pParse->db, op, pToken, 1); sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight); + if( p ) { + sqlite3ExprCheckHeight(pParse, p->nHeight); + } return p; } @@ -555,7 +558,7 @@ void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){ /* Wildcard of the form "?nnn". Convert "nnn" to an integer and ** use it as the variable number */ i64 i; - int bOk = sqlite3Atoi64(&z[1], &i); + int bOk = 0==sqlite3Atoi64(&z[1], &i, sqlite3Strlen30(&z[1]), SQLITE_UTF8); pExpr->iColumn = (ynVar)i; testcase( i==0 ); testcase( i==1 ); @@ -1535,8 +1538,8 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, int *prNotFound){ #endif /* -** Generate code for scalar subqueries used as an expression -** and IN operators. Examples: +** Generate code for scalar subqueries used as a subquery expression, EXISTS, +** or IN operators. Examples: ** ** (SELECT a FROM b) -- subquery ** EXISTS (SELECT a FROM b) -- EXISTS subquery @@ -1597,12 +1600,22 @@ int sqlite3CodeSubselect( assert( testAddr>0 || pParse->db->mallocFailed ); } +#ifndef SQLITE_OMIT_EXPLAIN + if( pParse->explain==2 ){ + char *zMsg = sqlite3MPrintf( + pParse->db, "EXECUTE %s%s SUBQUERY %d", testAddr?"":"CORRELATED ", + pExpr->op==TK_IN?"LIST":"SCALAR", pParse->iNextSelectId + ); + sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC); + } +#endif + switch( pExpr->op ){ case TK_IN: { - char affinity; - KeyInfo keyInfo; - int addr; /* Address of OP_OpenEphemeral instruction */ - Expr *pLeft = pExpr->pLeft; + char affinity; /* Affinity of the LHS of the IN */ + KeyInfo keyInfo; /* Keyinfo for the generated table */ + int addr; /* Address of OP_OpenEphemeral instruction */ + Expr *pLeft = pExpr->pLeft; /* the LHS of the IN operator */ if( rMayHaveNull ){ sqlite3VdbeAddOp2(v, OP_Null, 0, rMayHaveNull); @@ -1625,6 +1638,7 @@ int sqlite3CodeSubselect( */ pExpr->iTable = pParse->nTab++; addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, !isRowid); + if( rMayHaveNull==0 ) sqlite3VdbeChangeP5(v, BTREE_UNORDERED); memset(&keyInfo, 0, sizeof(keyInfo)); keyInfo.nField = 1; @@ -1641,6 +1655,7 @@ int sqlite3CodeSubselect( sqlite3SelectDestInit(&dest, SRT_Set, pExpr->iTable); dest.affinity = (u8)affinity; assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable ); + pExpr->x.pSelect->iLimit = 0; if( sqlite3Select(pParse, pExpr->x.pSelect, &dest) ){ return 0; } @@ -1741,6 +1756,7 @@ int sqlite3CodeSubselect( sqlite3ExprDelete(pParse->db, pSel->pLimit); pSel->pLimit = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, &sqlite3IntTokens[1]); + pSel->iLimit = 0; if( sqlite3Select(pParse, pSel, &dest) ){ return 0; } @@ -1917,7 +1933,7 @@ static void codeReal(Vdbe *v, const char *z, int negateFlag, int iMem){ if( ALWAYS(z!=0) ){ double value; char *zV; - sqlite3AtoF(z, &value); + sqlite3AtoF(z, &value, sqlite3Strlen30(z), SQLITE_UTF8); assert( !sqlite3IsNaN(value) ); /* The new AtoF never returns NaN */ if( negateFlag ) value = -value; zV = dup8bytes(v, (char*)&value); @@ -1931,9 +1947,7 @@ static void codeReal(Vdbe *v, const char *z, int negateFlag, int iMem){ ** Generate an instruction that will put the integer describe by ** text z[0..n-1] into register iMem. ** -** The z[] string will probably not be zero-terminated. But the -** z[n] character is guaranteed to be something that does not look -** like the continuation of the number. +** Expr.u.zToken is always UTF8 and zero-terminated. */ static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){ Vdbe *v = pParse->pVdbe; @@ -1942,13 +1956,14 @@ static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){ if( negFlag ) i = -i; sqlite3VdbeAddOp2(v, OP_Integer, i, iMem); }else{ + int c; + i64 value; const char *z = pExpr->u.zToken; assert( z!=0 ); - if( sqlite3FitsIn64Bits(z, negFlag) ){ - i64 value; + c = sqlite3Atoi64(z, &value, sqlite3Strlen30(z), SQLITE_UTF8); + if( c==0 || (c==2 && negFlag) ){ char *zV; - sqlite3Atoi64(z, &value); - if( negFlag ) value = -value; + if( negFlag ){ value = -value; } zV = dup8bytes(v, (char*)&value); sqlite3VdbeAddOp4(v, OP_Int64, 0, iMem, 0, zV, P4_INT64); }else{ @@ -2233,73 +2248,6 @@ static int usedAsColumnCache(Parse *pParse, int iFrom, int iTo){ } #endif /* SQLITE_DEBUG || SQLITE_COVERAGE_TEST */ -/* -** If the last instruction coded is an ephemeral copy of any of -** the registers in the nReg registers beginning with iReg, then -** convert the last instruction from OP_SCopy to OP_Copy. -*/ -void sqlite3ExprHardCopy(Parse *pParse, int iReg, int nReg){ - VdbeOp *pOp; - Vdbe *v; - - assert( pParse->db->mallocFailed==0 ); - v = pParse->pVdbe; - assert( v!=0 ); - pOp = sqlite3VdbeGetOp(v, -1); - assert( pOp!=0 ); - if( pOp->opcode==OP_SCopy && pOp->p1>=iReg && pOp->p1opcode = OP_Copy; - } -} - -/* -** Generate code to store the value of the iAlias-th alias in register -** target. The first time this is called, pExpr is evaluated to compute -** the value of the alias. The value is stored in an auxiliary register -** and the number of that register is returned. On subsequent calls, -** the register number is returned without generating any code. -** -** Note that in order for this to work, code must be generated in the -** same order that it is executed. -** -** Aliases are numbered starting with 1. So iAlias is in the range -** of 1 to pParse->nAlias inclusive. -** -** pParse->aAlias[iAlias-1] records the register number where the value -** of the iAlias-th alias is stored. If zero, that means that the -** alias has not yet been computed. -*/ -static int codeAlias(Parse *pParse, int iAlias, Expr *pExpr, int target){ -#if 0 - sqlite3 *db = pParse->db; - int iReg; - if( pParse->nAliasAllocnAlias ){ - pParse->aAlias = sqlite3DbReallocOrFree(db, pParse->aAlias, - sizeof(pParse->aAlias[0])*pParse->nAlias ); - testcase( db->mallocFailed && pParse->nAliasAlloc>0 ); - if( db->mallocFailed ) return 0; - memset(&pParse->aAlias[pParse->nAliasAlloc], 0, - (pParse->nAlias-pParse->nAliasAlloc)*sizeof(pParse->aAlias[0])); - pParse->nAliasAlloc = pParse->nAlias; - } - assert( iAlias>0 && iAlias<=pParse->nAlias ); - iReg = pParse->aAlias[iAlias-1]; - if( iReg==0 ){ - if( pParse->iCacheLevel>0 ){ - iReg = sqlite3ExprCodeTarget(pParse, pExpr, target); - }else{ - iReg = ++pParse->nMem; - sqlite3ExprCode(pParse, pExpr, iReg); - pParse->aAlias[iAlias-1] = iReg; - } - } - return iReg; -#else - UNUSED_PARAMETER(iAlias); - return sqlite3ExprCodeTarget(pParse, pExpr, target); -#endif -} - /* ** Generate code into the current Vdbe to evaluate the given ** expression. Attempt to store the results in register "target". @@ -2408,7 +2356,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ break; } case TK_AS: { - inReg = codeAlias(pParse, pExpr->iTable, pExpr->pLeft, target); + inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); break; } #ifndef SQLITE_OMIT_CAST @@ -2840,6 +2788,11 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ opCompare.op = TK_EQ; opCompare.pLeft = &cacheX; 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. */ + regFree1 = 0; } for(i=0; i0 && target<=pParse->nMem ); - inReg = sqlite3ExprCodeTarget(pParse, pExpr, target); - assert( pParse->pVdbe || pParse->db->mallocFailed ); - if( inReg!=target && pParse->pVdbe ){ - sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, inReg, target); + if( pExpr && pExpr->op==TK_REGISTER ){ + sqlite3VdbeAddOp2(pParse->pVdbe, OP_Copy, pExpr->iTable, target); + }else{ + inReg = sqlite3ExprCodeTarget(pParse, pExpr, target); + assert( pParse->pVdbe || pParse->db->mallocFailed ); + if( inReg!=target && pParse->pVdbe ){ + sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, inReg, target); + } } return target; } @@ -3083,9 +3040,22 @@ static int evalConstExpr(Walker *pWalker, Expr *pExpr){ ** 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. */ void sqlite3ExprCodeConstants(Parse *pParse, Expr *pExpr){ Walker w; + if( pParse->cookieGoto ) return; + if( (pParse->db->flags & SQLITE_FactorOutConst)!=0 ) return; w.xExprCallback = evalConstExpr; w.xSelectCallback = 0; w.pParse = pParse; @@ -3109,19 +3079,14 @@ int sqlite3ExprCodeExprList( int i, n; assert( pList!=0 ); assert( target>0 ); + assert( pParse->pVdbe!=0 ); /* Never gets this far otherwise */ n = pList->nExpr; for(pItem=pList->a, i=0; iiAlias ){ - int iReg = codeAlias(pParse, pItem->iAlias, pItem->pExpr, target+i); - Vdbe *v = sqlite3GetVdbe(pParse); - if( iReg!=target+i ){ - sqlite3VdbeAddOp2(v, OP_SCopy, iReg, target+i); - } - }else{ - sqlite3ExprCode(pParse, pItem->pExpr, target+i); - } - if( doHardCopy && !pParse->db->mallocFailed ){ - sqlite3ExprHardCopy(pParse, target, n); + 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); } } return n; diff --git a/src/fkey.c b/src/fkey.c index f0ad40d8..a385b814 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -380,7 +380,7 @@ static void fkLookupParent( sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb); sqlite3VdbeChangeP4(v, -1, (char*)pKey, P4_KEYINFO_HANDOFF); for(i=0; ilookaside.pStart = pStart; @@ -483,14 +508,14 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){ va_start(ap, op); switch( op ){ case SQLITE_DBCONFIG_LOOKASIDE: { - void *pBuf = va_arg(ap, void*); - int sz = va_arg(ap, int); - int cnt = va_arg(ap, int); + void *pBuf = va_arg(ap, void*); /* IMP: R-21112-12275 */ + int sz = va_arg(ap, int); /* IMP: R-47871-25994 */ + int cnt = va_arg(ap, int); /* IMP: R-04460-53386 */ rc = setupLookaside(db, pBuf, sz, cnt); break; } default: { - rc = SQLITE_ERROR; + rc = SQLITE_ERROR; /* IMP: R-42790-23372 */ break; } } @@ -595,11 +620,28 @@ void sqlite3CloseSavepoints(sqlite3 *db){ db->isTransactionSavepoint = 0; } +/* +** Invoke the destructor function associated with FuncDef p, if any. Except, +** if this is not the last copy of the function, do not invoke it. Multiple +** copies of a single function are created when create_function() is called +** with SQLITE_ANY as the encoding. +*/ +static void functionDestroy(sqlite3 *db, FuncDef *p){ + FuncDestructor *pDestructor = p->pDestructor; + if( pDestructor ){ + pDestructor->nRef--; + if( pDestructor->nRef==0 ){ + pDestructor->xDestroy(pDestructor->pUserData); + sqlite3DbFree(db, pDestructor); + } + } +} + /* ** Close an existing SQLite database */ int sqlite3_close(sqlite3 *db){ - HashElem *i; + HashElem *i; /* Hash table iterator */ int j; if( !db ){ @@ -667,6 +709,7 @@ int sqlite3_close(sqlite3 *db){ for(p=db->aFunc.a[j]; p; p=pHash){ pHash = p->pHash; while( p ){ + functionDestroy(db, p); pNext = p->pNext; sqlite3DbFree(db, p); p = pNext; @@ -773,7 +816,7 @@ const char *sqlite3ErrStr(int rc){ /* SQLITE_INTERRUPT */ "interrupted", /* SQLITE_IOERR */ "disk I/O error", /* SQLITE_CORRUPT */ "database disk image is malformed", - /* SQLITE_NOTFOUND */ 0, + /* SQLITE_NOTFOUND */ "unknown operation", /* SQLITE_FULL */ "database or disk is full", /* SQLITE_CANTOPEN */ "unable to open database file", /* SQLITE_PROTOCOL */ "locking protocol", @@ -941,7 +984,8 @@ int sqlite3CreateFunc( void *pUserData, void (*xFunc)(sqlite3_context*,int,sqlite3_value **), void (*xStep)(sqlite3_context*,int,sqlite3_value **), - void (*xFinal)(sqlite3_context*) + void (*xFinal)(sqlite3_context*), + FuncDestructor *pDestructor ){ FuncDef *p; int nName; @@ -969,10 +1013,10 @@ int sqlite3CreateFunc( }else if( enc==SQLITE_ANY ){ int rc; rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF8, - pUserData, xFunc, xStep, xFinal); + pUserData, xFunc, xStep, xFinal, pDestructor); if( rc==SQLITE_OK ){ rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF16LE, - pUserData, xFunc, xStep, xFinal); + pUserData, xFunc, xStep, xFinal, pDestructor); } if( rc!=SQLITE_OK ){ return rc; @@ -1005,6 +1049,15 @@ int sqlite3CreateFunc( if( !p ){ return SQLITE_NOMEM; } + + /* If an older version of the function with a configured destructor is + ** being replaced invoke the destructor function here. */ + functionDestroy(db, p); + + if( pDestructor ){ + pDestructor->nRef++; + } + p->pDestructor = pDestructor; p->flags = 0; p->xFunc = xFunc; p->xStep = xStep; @@ -1019,7 +1072,7 @@ int sqlite3CreateFunc( */ int sqlite3_create_function( sqlite3 *db, - const char *zFunctionName, + const char *zFunc, int nArg, int enc, void *p, @@ -1027,9 +1080,41 @@ int sqlite3_create_function( void (*xStep)(sqlite3_context*,int,sqlite3_value **), void (*xFinal)(sqlite3_context*) ){ - int rc; + return sqlite3_create_function_v2(db, zFunc, nArg, enc, p, xFunc, xStep, + xFinal, 0); +} + +int sqlite3_create_function_v2( + sqlite3 *db, + const char *zFunc, + int nArg, + int enc, + void *p, + void (*xFunc)(sqlite3_context*,int,sqlite3_value **), + void (*xStep)(sqlite3_context*,int,sqlite3_value **), + void (*xFinal)(sqlite3_context*), + void (*xDestroy)(void *) +){ + int rc = SQLITE_ERROR; + FuncDestructor *pArg = 0; sqlite3_mutex_enter(db->mutex); - rc = sqlite3CreateFunc(db, zFunctionName, nArg, enc, p, xFunc, xStep, xFinal); + if( xDestroy ){ + pArg = (FuncDestructor *)sqlite3DbMallocZero(db, sizeof(FuncDestructor)); + if( !pArg ){ + xDestroy(p); + goto out; + } + pArg->xDestroy = xDestroy; + pArg->pUserData = p; + } + rc = sqlite3CreateFunc(db, zFunc, nArg, enc, p, xFunc, xStep, xFinal, pArg); + if( pArg && pArg->nRef==0 ){ + assert( rc!=SQLITE_OK ); + xDestroy(p); + sqlite3DbFree(db, pArg); + } + + out: rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; @@ -1051,7 +1136,7 @@ int sqlite3_create_function16( 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); + rc = sqlite3CreateFunc(db, zFunc8, nArg, eTextRep, p, xFunc, xStep, xFinal,0); sqlite3DbFree(db, zFunc8); rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); @@ -1082,7 +1167,7 @@ int sqlite3_overload_function( sqlite3_mutex_enter(db->mutex); if( sqlite3FindFunction(db, zName, nName, nArg, SQLITE_UTF8, 0)==0 ){ sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8, - 0, sqlite3InvalidFunction, 0, 0); + 0, sqlite3InvalidFunction, 0, 0, 0); } rc = sqlite3ApiExit(db, SQLITE_OK); sqlite3_mutex_leave(db->mutex); @@ -1220,7 +1305,10 @@ int sqlite3WalDefaultHook( ** configured by this function. */ int sqlite3_wal_autocheckpoint(sqlite3 *db, int nFrame){ -#ifndef SQLITE_OMIT_WAL +#ifdef SQLITE_OMIT_WAL + UNUSED_PARAMETER(db); + UNUSED_PARAMETER(nFrame); +#else if( nFrame>0 ){ sqlite3_wal_hook(db, sqlite3WalDefaultHook, SQLITE_INT_TO_PTR(nFrame)); }else{ @@ -1350,60 +1438,6 @@ int sqlite3TempInMemory(const sqlite3 *db){ #endif } -/* -** This routine is called to create a connection to a database BTree -** driver. If zFilename is the name of a file, then that file is -** opened and used. If zFilename is the magic name ":memory:" then -** the database is stored in memory (and is thus forgotten as soon as -** the connection is closed.) If zFilename is NULL then the database -** is a "virtual" database for transient use only and is deleted as -** soon as the connection is closed. -** -** A virtual database can be either a disk file (that is automatically -** deleted when the file is closed) or it an be held entirely in memory. -** The sqlite3TempInMemory() function is used to determine which. -*/ -int sqlite3BtreeFactory( - sqlite3 *db, /* Main database when opening aux otherwise 0 */ - const char *zFilename, /* Name of the file containing the BTree database */ - int omitJournal, /* if TRUE then do not journal this file */ - int nCache, /* How many pages in the page cache */ - int vfsFlags, /* Flags passed through to vfsOpen */ - Btree **ppBtree /* Pointer to new Btree object written here */ -){ - int btFlags = 0; - int rc; - - assert( sqlite3_mutex_held(db->mutex) ); - assert( ppBtree != 0); - if( omitJournal ){ - btFlags |= BTREE_OMIT_JOURNAL; - } - if( db->flags & SQLITE_NoReadlock ){ - btFlags |= BTREE_NO_READLOCK; - } -#ifndef SQLITE_OMIT_MEMORYDB - if( zFilename==0 && sqlite3TempInMemory(db) ){ - zFilename = ":memory:"; - } -#endif - - if( (vfsFlags & SQLITE_OPEN_MAIN_DB)!=0 && (zFilename==0 || *zFilename==0) ){ - vfsFlags = (vfsFlags & ~SQLITE_OPEN_MAIN_DB) | SQLITE_OPEN_TEMP_DB; - } - rc = sqlite3BtreeOpen(zFilename, (sqlite3 *)db, ppBtree, btFlags, vfsFlags); - - /* If the B-Tree was successfully opened, set the pager-cache size to the - ** default value. Except, if the call to BtreeOpen() returned a handle - ** open on an existing shared pager-cache, do not change the pager-cache - ** size. - */ - if( rc==SQLITE_OK && 0==sqlite3BtreeSchema(*ppBtree, 0, 0) ){ - sqlite3BtreeSetCacheSize(*ppBtree, nCache); - } - return rc; -} - /* ** Return UTF-8 encoded English language explanation of the most recent ** error. @@ -1568,13 +1602,12 @@ static int createCollation( } pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 1); - if( pColl ){ - pColl->xCmp = xCompare; - pColl->pUser = pCtx; - pColl->xDel = xDel; - pColl->enc = (u8)(enc2 | (enc & SQLITE_UTF16_ALIGNED)); - pColl->type = collType; - } + if( pColl==0 ) return SQLITE_NOMEM; + pColl->xCmp = xCompare; + pColl->pUser = pCtx; + pColl->xDel = xDel; + pColl->enc = (u8)(enc2 | (enc & SQLITE_UTF16_ALIGNED)); + pColl->type = collType; sqlite3Error(db, SQLITE_OK, 0); return SQLITE_OK; } @@ -1646,17 +1679,39 @@ static const int aHardLimit[] = { */ int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ int oldLimit; + + + /* 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_".) + */ + assert( aHardLimit[SQLITE_LIMIT_LENGTH]==SQLITE_MAX_LENGTH ); + assert( aHardLimit[SQLITE_LIMIT_SQL_LENGTH]==SQLITE_MAX_SQL_LENGTH ); + assert( aHardLimit[SQLITE_LIMIT_COLUMN]==SQLITE_MAX_COLUMN ); + assert( aHardLimit[SQLITE_LIMIT_EXPR_DEPTH]==SQLITE_MAX_EXPR_DEPTH ); + assert( aHardLimit[SQLITE_LIMIT_COMPOUND_SELECT]==SQLITE_MAX_COMPOUND_SELECT); + assert( aHardLimit[SQLITE_LIMIT_VDBE_OP]==SQLITE_MAX_VDBE_OP ); + assert( aHardLimit[SQLITE_LIMIT_FUNCTION_ARG]==SQLITE_MAX_FUNCTION_ARG ); + 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) ); + + if( limitId<0 || limitId>=SQLITE_N_LIMIT ){ return -1; } oldLimit = db->aLimit[limitId]; - if( newLimit>=0 ){ + if( newLimit>=0 ){ /* IMP: R-52476-28732 */ if( newLimit>aHardLimit[limitId] ){ - newLimit = aHardLimit[limitId]; + newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */ } db->aLimit[limitId] = newLimit; } - return oldLimit; + return oldLimit; /* IMP: R-53341-35419 */ } /* @@ -1680,6 +1735,24 @@ static int openDatabase( if( rc ) return rc; #endif + /* Only allow sensible combinations of bits in the flags argument. + ** Throw an error if any non-sense combination is used. If we + ** do not block illegal combinations here, it could trigger + ** assert() statements in deeper layers. Sensible combinations + ** are: + ** + ** 1: SQLITE_OPEN_READONLY + ** 2: SQLITE_OPEN_READWRITE + ** 6: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE + */ + assert( SQLITE_OPEN_READONLY == 0x01 ); + 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; + if( sqlite3GlobalConfig.bCoreMutex==0 ){ isThreadsafe = 0; }else if( flags & SQLITE_OPEN_NOMUTEX ){ @@ -1713,7 +1786,8 @@ static int openDatabase( SQLITE_OPEN_SUBJOURNAL | SQLITE_OPEN_MASTER_JOURNAL | SQLITE_OPEN_NOMUTEX | - SQLITE_OPEN_FULLMUTEX + SQLITE_OPEN_FULLMUTEX | + SQLITE_OPEN_WAL ); /* Allocate the sqlite data structure */ @@ -1747,6 +1821,9 @@ static int openDatabase( #endif #if SQLITE_DEFAULT_RECURSIVE_TRIGGERS | SQLITE_RecTriggers +#endif +#if defined(SQLITE_DEFAULT_FOREIGN_KEYS) && SQLITE_DEFAULT_FOREIGN_KEYS + | SQLITE_ForeignKeys #endif ; sqlite3HashInit(&db->aCollSeq); @@ -1785,9 +1862,8 @@ static int openDatabase( /* Open the backend database driver */ db->openFlags = flags; - rc = sqlite3BtreeFactory(db, zFilename, 0, SQLITE_DEFAULT_CACHE_SIZE, - flags | SQLITE_OPEN_MAIN_DB, - &db->aDb[0].pBt); + rc = sqlite3BtreeOpen(zFilename, db, &db->aDb[0].pBt, 0, + flags | SQLITE_OPEN_MAIN_DB); if( rc!=SQLITE_OK ){ if( rc==SQLITE_IOERR_NOMEM ){ rc = SQLITE_NOMEM; @@ -2283,8 +2359,13 @@ int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){ assert( pPager!=0 ); fd = sqlite3PagerFile(pPager); assert( fd!=0 ); - if( fd->pMethods ){ + if( op==SQLITE_FCNTL_FILE_POINTER ){ + *(sqlite3_file**)pArg = fd; + rc = SQLITE_OK; + }else if( fd->pMethods ){ rc = sqlite3OsFileControl(fd, op, pArg); + }else{ + rc = SQLITE_NOTFOUND; } sqlite3BtreeLeave(pBtree); } @@ -2494,6 +2575,22 @@ int sqlite3_test_control(int op, ...){ break; } + /* sqlite3_test_control(SQLITE_TESTCTRL_SCRATCHMALLOC, sz, &pNew, pFree); + ** + ** Pass pFree into sqlite3ScratchFree(). + ** If sz>0 then allocate a scratch buffer into pNew. + */ + case SQLITE_TESTCTRL_SCRATCHMALLOC: { + void *pFree, **ppNew; + int sz; + sz = va_arg(ap, int); + ppNew = va_arg(ap, void**); + pFree = va_arg(ap, void*); + if( sz ) *ppNew = sqlite3ScratchMalloc(sz); + sqlite3ScratchFree(pFree); + break; + } + } va_end(ap); #endif /* SQLITE_OMIT_BUILTIN_TEST */ diff --git a/src/malloc.c b/src/malloc.c index b36b44f7..a3a9e0fe 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -15,6 +15,66 @@ #include "sqliteInt.h" #include +/* +** 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. +*/ +int 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 + ** SQLITE_ENABLE_MEMORY_MANAGEMENT. */ + UNUSED_PARAMETER(n); + return 0; +#endif +} + +/* +** An instance of the following object records the location of +** each unused scratch buffer. +*/ +typedef struct ScratchFreeslot { + struct ScratchFreeslot *pNext; /* Next unused scratch buffer */ +} ScratchFreeslot; + +/* +** State information local to the memory allocation subsystem. +*/ +static SQLITE_WSD struct Mem0Global { + sqlite3_mutex *mutex; /* Mutex to serialize access */ + + /* + ** The alarm callback and its arguments. The mem0.mutex lock will + ** be held while the callback is running. Recursive calls into + ** the memory subsystem are allowed, but no new callbacks will be + ** issued. + */ + sqlite3_int64 alarmThreshold; + void (*alarmCallback)(void*, sqlite3_int64,int); + void *alarmArg; + + /* + ** Pointers to the end of sqlite3GlobalConfig.pScratch memory + ** (so that a range test can be used to determine if an allocation + ** being freed came from pScratch) and a pointer to the list of + ** unused scratch allocations. + */ + void *pScratchEnd; + ScratchFreeslot *pScratchFree; + u32 nScratchFree; + + /* + ** True if heap is nearly "full" where "full" is defined by the + ** sqlite3_soft_heap_limit() setting. + */ + int nearlyFull; +} mem0 = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +#define mem0 GLOBAL(struct Mem0Global, mem0) + /* ** This routine runs when the memory allocator sees that the ** total memory allocation is about to exceed the soft heap @@ -29,79 +89,67 @@ static void softHeapLimitEnforcer( sqlite3_release_memory(allocSize); } +/* +** Change the alarm callback +*/ +static int sqlite3MemoryAlarm( + void(*xCallback)(void *pArg, sqlite3_int64 used,int N), + void *pArg, + sqlite3_int64 iThreshold +){ + int nUsed; + sqlite3_mutex_enter(mem0.mutex); + mem0.alarmCallback = xCallback; + mem0.alarmArg = pArg; + mem0.alarmThreshold = iThreshold; + nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); + mem0.nearlyFull = (iThreshold>0 && iThreshold<=nUsed); + sqlite3_mutex_leave(mem0.mutex); + return SQLITE_OK; +} + +#ifndef SQLITE_OMIT_DEPRECATED +/* +** Deprecated external interface. Internal/core SQLite code +** should call sqlite3MemoryAlarm. +*/ +int sqlite3_memory_alarm( + void(*xCallback)(void *pArg, sqlite3_int64 used,int N), + void *pArg, + sqlite3_int64 iThreshold +){ + return sqlite3MemoryAlarm(xCallback, pArg, iThreshold); +} +#endif + /* ** Set the soft heap-size limit for the library. Passing a zero or ** negative value indicates no limit. */ -void sqlite3_soft_heap_limit(int n){ - sqlite3_uint64 iLimit; - int overage; - if( n<0 ){ - iLimit = 0; - }else{ - iLimit = n; - } +sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 n){ + sqlite3_int64 priorLimit; + sqlite3_int64 excess; #ifndef SQLITE_OMIT_AUTOINIT sqlite3_initialize(); #endif - if( iLimit>0 ){ - sqlite3MemoryAlarm(softHeapLimitEnforcer, 0, iLimit); + sqlite3_mutex_enter(mem0.mutex); + priorLimit = mem0.alarmThreshold; + sqlite3_mutex_leave(mem0.mutex); + if( n<0 ) return priorLimit; + if( n>0 ){ + sqlite3MemoryAlarm(softHeapLimitEnforcer, 0, n); }else{ sqlite3MemoryAlarm(0, 0, 0); } - overage = (int)(sqlite3_memory_used() - (i64)n); - if( overage>0 ){ - sqlite3_release_memory(overage); - } + excess = sqlite3_memory_used() - n; + if( excess>0 ) sqlite3_release_memory((int)(excess & 0x7fffffff)); + return priorLimit; } - -/* -** 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. -*/ -int sqlite3_release_memory(int n){ -#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT - int nRet = 0; - nRet += sqlite3PcacheReleaseMemory(n-nRet); - return nRet; -#else - UNUSED_PARAMETER(n); - return SQLITE_OK; -#endif +void sqlite3_soft_heap_limit(int n){ + if( n<0 ) n = 0; + sqlite3_soft_heap_limit64(n); } -/* -** State information local to the memory allocation subsystem. -*/ -static SQLITE_WSD struct Mem0Global { - /* Number of free pages for scratch and page-cache memory */ - u32 nScratchFree; - u32 nPageFree; - - sqlite3_mutex *mutex; /* Mutex to serialize access */ - - /* - ** The alarm callback and its arguments. The mem0.mutex lock will - ** be held while the callback is running. Recursive calls into - ** the memory subsystem are allowed, but no new callbacks will be - ** issued. - */ - sqlite3_int64 alarmThreshold; - void (*alarmCallback)(void*, sqlite3_int64,int); - void *alarmArg; - - /* - ** Pointers to the end of sqlite3GlobalConfig.pScratch and - ** sqlite3GlobalConfig.pPage to a block of memory that records - ** which pages are available. - */ - u32 *aScratchFree; - u32 *aPageFree; -} mem0 = { 0, 0, 0, 0, 0, 0, 0, 0 }; - -#define mem0 GLOBAL(struct Mem0Global, mem0) - /* ** Initialize the memory allocation subsystem. */ @@ -114,36 +162,45 @@ int sqlite3MallocInit(void){ mem0.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); } if( sqlite3GlobalConfig.pScratch && sqlite3GlobalConfig.szScratch>=100 - && sqlite3GlobalConfig.nScratch>=0 ){ - int i; - sqlite3GlobalConfig.szScratch = ROUNDDOWN8(sqlite3GlobalConfig.szScratch-4); - mem0.aScratchFree = (u32*)&((char*)sqlite3GlobalConfig.pScratch) - [sqlite3GlobalConfig.szScratch*sqlite3GlobalConfig.nScratch]; - for(i=0; i0 ){ + int i, n, sz; + ScratchFreeslot *pSlot; + sz = ROUNDDOWN8(sqlite3GlobalConfig.szScratch); + sqlite3GlobalConfig.szScratch = sz; + pSlot = (ScratchFreeslot*)sqlite3GlobalConfig.pScratch; + n = sqlite3GlobalConfig.nScratch; + mem0.pScratchFree = pSlot; + mem0.nScratchFree = n; + for(i=0; ipNext = (ScratchFreeslot*)(sz+(char*)pSlot); + pSlot = pSlot->pNext; + } + pSlot->pNext = 0; + mem0.pScratchEnd = (void*)&pSlot[1]; }else{ + mem0.pScratchEnd = 0; sqlite3GlobalConfig.pScratch = 0; sqlite3GlobalConfig.szScratch = 0; + sqlite3GlobalConfig.nScratch = 0; } - if( sqlite3GlobalConfig.pPage && sqlite3GlobalConfig.szPage>=512 - && sqlite3GlobalConfig.nPage>=1 ){ - int i; - int overhead; - int sz = ROUNDDOWN8(sqlite3GlobalConfig.szPage); - int n = sqlite3GlobalConfig.nPage; - overhead = (4*n + sz - 1)/sz; - sqlite3GlobalConfig.nPage -= overhead; - mem0.aPageFree = (u32*)&((char*)sqlite3GlobalConfig.pPage) - [sqlite3GlobalConfig.szPage*sqlite3GlobalConfig.nPage]; - for(i=0; i= mem0.alarmThreshold ){ + mem0.nearlyFull = 1; sqlite3MallocAlarm(nFull); + }else{ + mem0.nearlyFull = 0; } } p = sqlite3GlobalConfig.m.xMalloc(nFull); +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT if( p==0 && mem0.alarmCallback ){ sqlite3MallocAlarm(nFull); p = sqlite3GlobalConfig.m.xMalloc(nFull); } +#endif if( p ){ nFull = sqlite3MallocSize(p); sqlite3StatusAdd(SQLITE_STATUS_MEMORY_USED, nFull); @@ -263,7 +295,9 @@ static int mallocWithAlarm(int n, void **pp){ */ void *sqlite3Malloc(int n){ void *p; - if( n<=0 || n>=0x7fffff00 ){ + if( n<=0 /* IMP: R-65312-04917 */ + || 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 @@ -277,6 +311,7 @@ void *sqlite3Malloc(int n){ }else{ p = sqlite3GlobalConfig.m.xMalloc(n); } + assert( EIGHT_BYTE_ALIGNMENT(p) ); /* IMP: R-04675-44850 */ return p; } @@ -315,59 +350,65 @@ void *sqlite3ScratchMalloc(int n){ void *p; assert( n>0 ); + sqlite3_mutex_enter(mem0.mutex); + 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); + } + sqlite3MemdebugSetType(p, MEMTYPE_SCRATCH); + } + assert( sqlite3_mutex_notheld(mem0.mutex) ); + + #if SQLITE_THREADSAFE==0 && !defined(NDEBUG) - /* Verify that no more than two scratch allocation per thread - ** is outstanding at one time. (This is only checked in the + /* 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 ); -#endif - - if( sqlite3GlobalConfig.szScratch=(void*)mem0.aScratchFree ){ + +#if SQLITE_THREADSAFE==0 && !defined(NDEBUG) + /* Verify that no more than two scratch allocation per thread + ** is 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 && scratchAllocOut<=2 ); + scratchAllocOut--; +#endif + + if( p>=sqlite3GlobalConfig.pScratch && ppNext = mem0.pScratchFree; + mem0.pScratchFree = pSlot; + mem0.nScratchFree++; + assert( mem0.nScratchFree<=sqlite3GlobalConfig.nScratch ); + sqlite3StatusAdd(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) ); sqlite3MemdebugSetType(p, MEMTYPE_HEAP); @@ -382,26 +423,6 @@ void sqlite3ScratchFree(void *p){ }else{ sqlite3GlobalConfig.m.xFree(p); } - }else{ - int i; - i = (int)((u8*)p - (u8*)sqlite3GlobalConfig.pScratch); - i /= sqlite3GlobalConfig.szScratch; - assert( i>=0 && i=1 && scratchAllocOut<=2 ); - scratchAllocOut = 0; -#endif - } } } @@ -442,7 +463,7 @@ int sqlite3DbMallocSize(sqlite3 *db, void *p){ ** Free memory previously obtained from sqlite3Malloc(). */ void sqlite3_free(void *p){ - if( p==0 ) return; + if( p==0 ) return; /* IMP: R-49053-54554 */ assert( sqlite3MemdebugNoType(p, MEMTYPE_DB) ); assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); if( sqlite3GlobalConfig.bMemstat ){ @@ -489,10 +510,10 @@ void *sqlite3Realloc(void *pOld, int nBytes){ int nOld, nNew; void *pNew; if( pOld==0 ){ - return sqlite3Malloc(nBytes); + return sqlite3Malloc(nBytes); /* IMP: R-28354-25769 */ } if( nBytes<=0 ){ - sqlite3_free(pOld); + sqlite3_free(pOld); /* IMP: R-31593-10574 */ return 0; } if( nBytes>=0x7fffff00 ){ @@ -500,6 +521,9 @@ void *sqlite3Realloc(void *pOld, int nBytes){ return 0; } 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); if( nOld==nNew ){ pNew = pOld; @@ -525,6 +549,7 @@ void *sqlite3Realloc(void *pOld, int nBytes){ }else{ pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); } + assert( EIGHT_BYTE_ALIGNMENT(pNew) ); /* IMP: R-04675-44850 */ return pNew; } @@ -591,14 +616,20 @@ void *sqlite3DbMallocRaw(sqlite3 *db, int n){ if( db->mallocFailed ){ return 0; } - if( db->lookaside.bEnabled && n<=db->lookaside.sz - && (pBuf = db->lookaside.pFree)!=0 ){ - db->lookaside.pFree = pBuf->pNext; - db->lookaside.nOut++; - if( db->lookaside.nOut>db->lookaside.mxOut ){ - db->lookaside.mxOut = db->lookaside.nOut; + if( db->lookaside.bEnabled ){ + if( n>db->lookaside.sz ){ + db->lookaside.anStat[1]++; + }else if( (pBuf = db->lookaside.pFree)==0 ){ + db->lookaside.anStat[2]++; + }else{ + db->lookaside.pFree = pBuf->pNext; + db->lookaside.nOut++; + db->lookaside.anStat[0]++; + if( db->lookaside.nOut>db->lookaside.mxOut ){ + db->lookaside.mxOut = db->lookaside.nOut; + } + return (void*)pBuf; } - return (void*)pBuf; } } #else diff --git a/src/mem1.c b/src/mem1.c index 1a018399..61fbf4bd 100644 --- a/src/mem1.c +++ b/src/mem1.c @@ -89,7 +89,7 @@ static int sqlite3MemSize(void *pPrior){ static void *sqlite3MemRealloc(void *pPrior, int nByte){ sqlite3_int64 *p = (sqlite3_int64*)pPrior; assert( pPrior!=0 && nByte>0 ); - nByte = ROUND8(nByte); + assert( nByte==ROUND8(nByte) ); /* EV: R-46199-30249 */ p--; p = realloc(p, nByte+8 ); if( p ){ diff --git a/src/mem2.c b/src/mem2.c index 83f12fdb..26448ea8 100644 --- a/src/mem2.c +++ b/src/mem2.c @@ -344,6 +344,7 @@ static void *sqlite3MemRealloc(void *pPrior, int nByte){ struct MemBlockHdr *pOldHdr; void *pNew; assert( mem.disallow==0 ); + assert( (nByte & 7)==0 ); /* EV: R-46199-30249 */ pOldHdr = sqlite3MemsysGetHeader(pPrior); pNew = sqlite3MemMalloc(nByte); if( pNew ){ diff --git a/src/mem5.c b/src/mem5.c index a828cf81..2fdfac14 100644 --- a/src/mem5.c +++ b/src/mem5.c @@ -395,7 +395,7 @@ static void *memsys5Realloc(void *pPrior, int nBytes){ int nOld; void *p; assert( pPrior!=0 ); - assert( (nBytes&(nBytes-1))==0 ); + assert( (nBytes&(nBytes-1))==0 ); /* EV: R-46199-30249 */ assert( nBytes>=0 ); if( nBytes==0 ){ return 0; diff --git a/src/memjournal.c b/src/memjournal.c index 68c2ff82..3e66e215 100644 --- a/src/memjournal.c +++ b/src/memjournal.c @@ -252,8 +252,7 @@ int sqlite3IsMemJournal(sqlite3_file *pJfd){ } /* -** Return the number of bytes required to store a MemJournal that uses vfs -** pVfs to create the underlying on-disk files. +** Return the number of bytes required to store a MemJournal file descriptor. */ int sqlite3MemJournalSize(void){ return sizeof(MemJournal); diff --git a/src/mutex.h b/src/mutex.h index 037d4875..c24f3da4 100644 --- a/src/mutex.h +++ b/src/mutex.h @@ -63,8 +63,8 @@ #define sqlite3_mutex_enter(X) #define sqlite3_mutex_try(X) SQLITE_OK #define sqlite3_mutex_leave(X) -#define sqlite3_mutex_held(X) 1 -#define sqlite3_mutex_notheld(X) 1 +#define sqlite3_mutex_held(X) ((void)(X),1) +#define sqlite3_mutex_notheld(X) ((void)(X),1) #define sqlite3MutexAlloc(X) ((sqlite3_mutex*)8) #define sqlite3MutexInit() SQLITE_OK #define sqlite3MutexEnd() diff --git a/src/mutex_unix.c b/src/mutex_unix.c index 196975e7..aa9a8cf2 100644 --- a/src/mutex_unix.c +++ b/src/mutex_unix.c @@ -99,7 +99,7 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; } **
  • SQLITE_MUTEX_STATIC_MEM2 **
  • SQLITE_MUTEX_STATIC_PRNG **
  • SQLITE_MUTEX_STATIC_LRU -**
  • SQLITE_MUTEX_STATIC_LRU2 +**
  • SQLITE_MUTEX_STATIC_PMEM ** ** ** The first two constants cause sqlite3_mutex_alloc() to create diff --git a/src/mutex_w32.c b/src/mutex_w32.c index fba964a4..8e257a91 100644 --- a/src/mutex_w32.c +++ b/src/mutex_w32.c @@ -156,7 +156,7 @@ static int winMutexEnd(void){ **
  • SQLITE_MUTEX_STATIC_MEM2 **
  • SQLITE_MUTEX_STATIC_PRNG **
  • SQLITE_MUTEX_STATIC_LRU -**
  • SQLITE_MUTEX_STATIC_LRU2 +**
  • SQLITE_MUTEX_STATIC_PMEM ** ** ** The first two constants cause sqlite3_mutex_alloc() to create diff --git a/src/os.c b/src/os.c index 35b48f1a..ba0438ad 100644 --- a/src/os.c +++ b/src/os.c @@ -183,6 +183,12 @@ int sqlite3OsSleep(sqlite3_vfs *pVfs, int nMicro){ } int sqlite3OsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ int rc; + /* IMPLEMENTATION-OF: R-49045-42493 SQLite will use the xCurrentTimeInt64() + ** method to get the current date and time if that method is available + ** (if iVersion is 2 or greater and the function pointer is not NULL) and + ** will fall back to xCurrentTime() if xCurrentTimeInt64() is + ** unavailable. + */ if( pVfs->iVersion>=2 && pVfs->xCurrentTimeInt64 ){ rc = pVfs->xCurrentTimeInt64(pVfs, pTimeOut); }else{ diff --git a/src/os_os2.c b/src/os_os2.c index 7ac0cc7c..df5ad102 100644 --- a/src/os_os2.c +++ b/src/os_os2.c @@ -533,7 +533,7 @@ static int os2FileControl(sqlite3_file *id, int op, void *pArg){ return SQLITE_OK; } } - return SQLITE_ERROR; + return SQLITE_NOTFOUND; } /* diff --git a/src/os_unix.c b/src/os_unix.c index e3ea4f22..fa200ae8 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -119,7 +119,9 @@ #include #include #include +#ifndef SQLITE_OMIT_WAL #include +#endif #if SQLITE_ENABLE_LOCKING_STYLE # include @@ -3133,8 +3135,11 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ return proxyFileControl(id,op,pArg); } #endif /* SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) */ + case SQLITE_FCNTL_SYNC_OMITTED: { + return SQLITE_OK; /* A no-op */ + } } - return SQLITE_ERROR; + return SQLITE_NOTFOUND; } /* @@ -3559,7 +3564,7 @@ static int unixShmMap( pShmNode->apRegion = apNew; while(pShmNode->nRegion<=iRegion){ void *pMem = mmap(0, szRegion, PROT_READ|PROT_WRITE, - MAP_SHARED, pShmNode->h, iRegion*szRegion + MAP_SHARED, pShmNode->h, pShmNode->nRegion*szRegion ); if( pMem==MAP_FAILED ){ rc = SQLITE_IOERR; @@ -4077,11 +4082,21 @@ static int fillInUnixFile( */ UNUSED_PARAMETER(isDelete); + /* Usually the path zFilename should not be a relative pathname. The + ** exception is when opening the proxy "conch" file in builds that + ** include the special Apple locking styles. + */ +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE + assert( zFilename==0 || zFilename[0]=='/' + || pVfs->pAppData==(void*)&autolockIoFinder ); +#else + assert( zFilename==0 || zFilename[0]=='/' ); +#endif + OSTRACE(("OPEN %-3d %s\n", h, zFilename)); pNew->h = h; pNew->dirfd = dirfd; pNew->fileFlags = 0; - assert( zFilename==0 || zFilename[0]=='/' ); /* Never a relative pathname */ pNew->zPath = zFilename; #if OS_VXWORKS @@ -4421,9 +4436,24 @@ static int findCreateFileMode( int nDb; /* Number of valid bytes in zDb */ struct stat sStat; /* Output of stat() on database file */ - nDb = sqlite3Strlen30(zPath) - ((flags & SQLITE_OPEN_WAL) ? 4 : 8); + /* zPath is a path to a WAL or journal file. The following block derives + ** the path to the associated database file from zPath. This block handles + ** the following naming conventions: + ** + ** "-journal" + ** "-wal" + ** "-journal-NNNN" + ** "-wal-NNNN" + ** + ** where NNNN is a 4 digit decimal number. The NNNN naming schemes are + ** used by the test_multiplex.c module. + */ + nDb = sqlite3Strlen30(zPath) - 1; + while( nDb>0 && zPath[nDb]!='l' ) nDb--; + nDb -= ((flags & SQLITE_OPEN_WAL) ? 3 : 7); memcpy(zDb, zPath, nDb); zDb[nDb] = '\0'; + if( 0==stat(zDb, &sStat) ){ *pMode = sStat.st_mode & 0777; }else{ @@ -4838,7 +4868,7 @@ static void *unixDlOpen(sqlite3_vfs *NotUsed, const char *zFilename){ ** error message. */ static void unixDlError(sqlite3_vfs *NotUsed, int nBuf, char *zBufOut){ - char *zErr; + const char *zErr; UNUSED_PARAMETER(NotUsed); unixEnterMutex(); zErr = dlerror(); @@ -4975,7 +5005,7 @@ static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){ #if defined(NO_GETTOD) time_t t; time(&t); - *piNow = ((sqlite3_int64)i)*1000 + unixEpoch; + *piNow = ((sqlite3_int64)t)*1000 + unixEpoch; #elif OS_VXWORKS struct timespec sNow; clock_gettime(CLOCK_REALTIME, &sNow); @@ -5378,17 +5408,21 @@ extern int gethostuuid(uuid_t id, const struct timespec *wait); ** bytes of writable memory. */ static int proxyGetHostID(unsigned char *pHostID, int *pError){ - struct timespec timeout = {1, 0}; /* 1 sec timeout */ - assert(PROXY_HOSTIDLEN == sizeof(uuid_t)); memset(pHostID, 0, PROXY_HOSTIDLEN); - if( gethostuuid(pHostID, &timeout) ){ - int err = errno; - if( pError ){ - *pError = err; +#if defined(__MAX_OS_X_VERSION_MIN_REQUIRED)\ + && __MAC_OS_X_VERSION_MIN_REQUIRED<1050 + { + static const struct timespec timeout = {1, 0}; /* 1 sec timeout */ + if( gethostuuid(pHostID, &timeout) ){ + int err = errno; + if( pError ){ + *pError = err; + } + return SQLITE_IOERR; } - return SQLITE_IOERR; } +#endif #ifdef SQLITE_TEST /* simulate multiple hosts by creating unique hostid file paths */ if( sqlite3_hostid_num != 0){ @@ -5429,27 +5463,27 @@ static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){ pathLen = strlcpy(tPath, cPath, MAXPATHLEN); if( pathLen>MAXPATHLEN || pathLen<6 || (strlcpy(&tPath[pathLen-5], "break", 6) != 5) ){ - sprintf(errmsg, "path error (len %d)", (int)pathLen); + sqlite3_snprintf(sizeof(errmsg),errmsg,"path error (len %d)",(int)pathLen); goto end_breaklock; } /* read the conch content */ readLen = pread(conchFile->h, buf, PROXY_MAXCONCHLEN, 0); if( readLenmutex ) sqlite3_mutex_free(p->mutex); for(i=0; inRegion; i++){ - UnmapViewOfFile(p->aRegion[i].pMap); - CloseHandle(p->aRegion[i].hMap); + bRc = UnmapViewOfFile(p->aRegion[i].pMap); + OSTRACE(("SHM-PURGE pid-%d unmap region=%d %s\n", + (int)GetCurrentProcessId(), i, + bRc ? "ok" : "failed")); + bRc = CloseHandle(p->aRegion[i].hMap); + OSTRACE(("SHM-PURGE pid-%d close region=%d %s\n", + (int)GetCurrentProcessId(), i, + bRc ? "ok" : "failed")); } if( p->hFile.h != INVALID_HANDLE_VALUE ){ SimulateIOErrorBenign(1); @@ -1465,10 +1483,11 @@ static int winOpenSharedMemory(winFile *pDbFd){ rc = SQLITE_NOMEM; goto shm_open_err; } + rc = winOpen(pDbFd->pVfs, pShmNode->zFilename, /* Name of the file (UTF-8) */ (sqlite3_file*)&pShmNode->hFile, /* File handle here */ - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, /* Mode flags */ + SQLITE_OPEN_WAL | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, /* Mode flags */ 0); if( SQLITE_OK!=rc ){ rc = SQLITE_CANTOPEN_BKPT; @@ -1776,10 +1795,18 @@ static int winShmMap( hMap = CreateFileMapping(pShmNode->hFile.h, NULL, PAGE_READWRITE, 0, nByte, NULL ); + OSTRACE(("SHM-MAP pid-%d create region=%d nbyte=%d %s\n", + (int)GetCurrentProcessId(), pShmNode->nRegion, nByte, + hMap ? "ok" : "failed")); if( hMap ){ + int iOffset = pShmNode->nRegion*szRegion; + int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; pMap = MapViewOfFile(hMap, FILE_MAP_WRITE | FILE_MAP_READ, - 0, 0, nByte + 0, iOffset - iOffsetShift, szRegion + iOffsetShift ); + OSTRACE(("SHM-MAP pid-%d map region=%d offset=%d size=%d %s\n", + (int)GetCurrentProcessId(), pShmNode->nRegion, iOffset, szRegion, + pMap ? "ok" : "failed")); } if( !pMap ){ pShmNode->lastErrno = GetLastError(); @@ -1796,8 +1823,10 @@ static int winShmMap( shmpage_out: if( pShmNode->nRegion>iRegion ){ + int iOffset = iRegion*szRegion; + int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; char *p = (char *)pShmNode->aRegion[iRegion].pMap; - *pp = (void *)&p[iRegion*szRegion]; + *pp = (void *)&p[iOffsetShift]; }else{ *pp = 0; } @@ -2024,9 +2053,60 @@ static int winOpen( int isTemp = 0; #endif winFile *pFile = (winFile*)id; - void *zConverted; /* Filename in OS encoding */ - const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ - char zTmpname[MAX_PATH+1]; /* Buffer used to create temp filename */ + void *zConverted; /* Filename in OS encoding */ + const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ + + /* If argument zPath is a NULL pointer, this function is required to open + ** a temporary file. Use this buffer to store the file name in. + */ + char zTmpname[MAX_PATH+1]; /* Buffer used to create temp filename */ + + int rc = SQLITE_OK; /* Function Return Code */ +#if !defined(NDEBUG) || SQLITE_OS_WINCE + int eType = flags&0xFFFFFF00; /* Type of file to open */ +#endif + + int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE); + int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE); + int isCreate = (flags & SQLITE_OPEN_CREATE); +#ifndef NDEBUG + int isReadonly = (flags & SQLITE_OPEN_READONLY); +#endif + int isReadWrite = (flags & SQLITE_OPEN_READWRITE); + +#ifndef NDEBUG + int isOpenJournal = (isCreate && ( + eType==SQLITE_OPEN_MASTER_JOURNAL + || eType==SQLITE_OPEN_MAIN_JOURNAL + || eType==SQLITE_OPEN_WAL + )); +#endif + + /* Check the following statements are true: + ** + ** (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 + ** 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 + || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL + ); assert( id!=0 ); UNUSED_PARAMETER(pVfs); @@ -2037,7 +2117,8 @@ static int winOpen( ** temporary file name to use */ if( !zUtf8Name ){ - int rc = getTempname(MAX_PATH+1, zTmpname); + assert(isDelete && !isOpenJournal); + rc = getTempname(MAX_PATH+1, zTmpname); if( rc!=SQLITE_OK ){ return rc; } @@ -2050,29 +2131,31 @@ static int winOpen( return SQLITE_NOMEM; } - if( flags & SQLITE_OPEN_READWRITE ){ + if( isReadWrite ){ 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" ** as it is usually understood. */ - assert(!(flags & SQLITE_OPEN_EXCLUSIVE) || (flags & SQLITE_OPEN_CREATE)); - if( flags & SQLITE_OPEN_EXCLUSIVE ){ + if( isExclusive ){ /* Creates a new file, only if it does not already exist. */ /* If the file exists, it fails. */ dwCreationDisposition = CREATE_NEW; - }else if( flags & SQLITE_OPEN_CREATE ){ + }else if( isCreate ){ /* Open existing file, or create if it doesn't exist */ dwCreationDisposition = OPEN_ALWAYS; }else{ /* Opens a file, only if it exists. */ dwCreationDisposition = OPEN_EXISTING; } + dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; - if( flags & SQLITE_OPEN_DELETEONCLOSE ){ + + if( isDelete ){ #if SQLITE_OS_WINCE dwFlagsAndAttributes = FILE_ATTRIBUTE_HIDDEN; isTemp = 1; @@ -2089,6 +2172,7 @@ static int winOpen( #if SQLITE_OS_WINCE dwFlagsAndAttributes |= FILE_FLAG_RANDOM_ACCESS; #endif + if( isNT() ){ h = CreateFileW((WCHAR*)zConverted, dwDesiredAccess, @@ -2114,26 +2198,30 @@ static int winOpen( ); #endif } + OSTRACE(("OPEN %d %s 0x%lx %s\n", h, zName, dwDesiredAccess, h==INVALID_HANDLE_VALUE ? "failed" : "ok")); + if( h==INVALID_HANDLE_VALUE ){ pFile->lastErrno = GetLastError(); free(zConverted); - if( flags & SQLITE_OPEN_READWRITE ){ + if( isReadWrite ){ return winOpen(pVfs, zName, id, - ((flags|SQLITE_OPEN_READONLY)&~SQLITE_OPEN_READWRITE), pOutFlags); + ((flags|SQLITE_OPEN_READONLY)&~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)), pOutFlags); }else{ return SQLITE_CANTOPEN_BKPT; } } + if( pOutFlags ){ - if( flags & SQLITE_OPEN_READWRITE ){ + if( isReadWrite ){ *pOutFlags = SQLITE_OPEN_READWRITE; }else{ *pOutFlags = SQLITE_OPEN_READONLY; } } + memset(pFile, 0, sizeof(*pFile)); pFile->pMethod = &winIoMethod; pFile->h = h; @@ -2142,9 +2230,9 @@ static int winOpen( pFile->pShm = 0; pFile->zPath = zName; pFile->sectorSize = getSectorSize(pVfs, zUtf8Name); + #if SQLITE_OS_WINCE - if( (flags & (SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_DB)) == - (SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_DB) + if( isReadWrite && eType==SQLITE_OPEN_MAIN_DB && !winceCreateLock(zName, pFile) ){ CloseHandle(h); @@ -2158,8 +2246,9 @@ static int winOpen( { free(zConverted); } + OpenCounter(+1); - return SQLITE_OK; + return rc; } /* @@ -2678,6 +2767,13 @@ int sqlite3_os_init(void){ winCurrentTimeInt64, /* xCurrentTimeInt64 */ }; +#ifndef SQLITE_OMIT_WAL + /* get memory map allocation granularity */ + memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); + GetSystemInfo(&winSysInfo); + assert(winSysInfo.dwAllocationGranularity > 0); +#endif + sqlite3_vfs_register(&winVfs, 1); return SQLITE_OK; } diff --git a/src/pager.c b/src/pager.c index f0b5b775..80f3b0e7 100644 --- a/src/pager.c +++ b/src/pager.c @@ -615,7 +615,8 @@ struct Pager { u8 noReadlock; /* Do not bother to obtain readlocks */ u8 noSync; /* Do not sync the journal if true */ u8 fullSync; /* Do extra syncs of the journal for robustness */ - u8 sync_flags; /* One of SYNC_NORMAL or SYNC_FULL */ + u8 ckptSyncFlags; /* SYNC_NORMAL or SYNC_FULL for checkpoint */ + u8 syncFlags; /* SYNC_NORMAL or SYNC_FULL otherwise */ u8 tempFile; /* zFilename is a temporary file */ u8 readOnly; /* True for a read-only database */ u8 memDb; /* True to inhibit all file I/O */ @@ -926,7 +927,9 @@ static int assert_pager_state(Pager *p){ return 1; } +#endif /* ifndef NDEBUG */ +#ifdef SQLITE_DEBUG /* ** Return a pointer to a human readable string in a static buffer ** containing the state of the Pager object passed as an argument. This @@ -1050,7 +1053,7 @@ static int write32bits(sqlite3_file *fd, i64 offset, u32 val){ static int pagerUnlockDb(Pager *pPager, int eLock){ int rc = SQLITE_OK; - assert( !pPager->exclusiveMode ); + assert( !pPager->exclusiveMode || pPager->eLock==eLock ); assert( eLock==NO_LOCK || eLock==SHARED_LOCK ); assert( eLock!=NO_LOCK || pagerUseWal(pPager)==0 ); if( isOpen(pPager->fd) ){ @@ -1297,7 +1300,7 @@ static int zeroJournalHdr(Pager *pPager, int doTruncate){ rc = sqlite3OsWrite(pPager->jfd, zeroHdr, sizeof(zeroHdr), 0); } if( rc==SQLITE_OK && !pPager->noSync ){ - rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->sync_flags); + rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->syncFlags); } /* At this point the transaction is committed but the write lock @@ -2474,15 +2477,21 @@ static int pager_truncate(Pager *pPager, Pgno nPage){ && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) ){ i64 currentSize, newSize; + int szPage = pPager->pageSize; assert( pPager->eLock==EXCLUSIVE_LOCK ); /* TODO: Is it safe to use Pager.dbFileSize here? */ rc = sqlite3OsFileSize(pPager->fd, ¤tSize); - newSize = pPager->pageSize*(i64)nPage; + newSize = szPage*(i64)nPage; if( rc==SQLITE_OK && currentSize!=newSize ){ if( currentSize>newSize ){ rc = sqlite3OsTruncate(pPager->fd, newSize); }else{ - rc = sqlite3OsWrite(pPager->fd, "", 1, newSize-1); + char *pTmp = pPager->pTmpSpace; + memset(pTmp, 0, szPage); + testcase( (newSize-szPage) < currentSize ); + testcase( (newSize-szPage) == currentSize ); + testcase( (newSize-szPage) > currentSize ); + rc = sqlite3OsWrite(pPager->fd, pTmp, szPage, newSize-szPage); } if( rc==SQLITE_OK ){ pPager->dbFileSize = nPage; @@ -2746,10 +2755,10 @@ end_playback: rc = readMasterJournal(pPager->jfd, zMaster, pPager->pVfs->mxPathname+1); testcase( rc!=SQLITE_OK ); } - if( rc==SQLITE_OK && !pPager->noSync + if( rc==SQLITE_OK && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) ){ - rc = sqlite3OsSync(pPager->fd, pPager->sync_flags); + rc = sqlite3PagerSync(pPager); } if( rc==SQLITE_OK ){ rc = pager_end_transaction(pPager, zMaster[0]!='\0'); @@ -2912,24 +2921,61 @@ static int pagerRollbackWal(Pager *pPager){ return rc; } + +/* +** Update the value of the change-counter at offsets 24 and 92 in +** the header and the sqlite version number at offset 96. +** +** This is an unconditional update. See also the pager_incr_changecounter() +** routine which only updates the change-counter if the update is actually +** needed, as determined by the pPager->changeCountDone state variable. +*/ +static void pager_write_changecounter(PgHdr *pPg){ + u32 change_counter; + + /* Increment the value just read and write it back to byte 24. */ + change_counter = sqlite3Get4byte((u8*)pPg->pPager->dbFileVers)+1; + put32bits(((char*)pPg->pData)+24, change_counter); + + /* Also store the SQLite version number in bytes 96..99 and in + ** bytes 92..95 store the change counter for which the version number + ** is valid. */ + put32bits(((char*)pPg->pData)+92, change_counter); + put32bits(((char*)pPg->pData)+96, SQLITE_VERSION_NUMBER); +} + /* ** This function is a wrapper around sqlite3WalFrames(). As well as logging ** the contents of the list of pages headed by pList (connected by pDirty), ** this function notifies any active backup processes that the pages have -** changed. +** changed. +** +** The list of pages passed into this routine is always sorted by page number. +** Hence, if page 1 appears anywhere on the list, it will be the first page. */ static int pagerWalFrames( Pager *pPager, /* Pager object */ PgHdr *pList, /* List of frames to log */ Pgno nTruncate, /* Database size after this commit */ int isCommit, /* True if this is a commit */ - int sync_flags /* Flags to pass to OsSync() (or 0) */ + int syncFlags /* Flags to pass to OsSync() (or 0) */ ){ int rc; /* Return code */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_CHECK_PAGES) + PgHdr *p; /* For looping over pages */ +#endif assert( pPager->pWal ); +#ifdef SQLITE_DEBUG + /* Verify that the page list is in accending order */ + for(p=pList; p && p->pDirty; p=p->pDirty){ + assert( p->pgno < p->pDirty->pgno ); + } +#endif + + if( pList->pgno==1 ) pager_write_changecounter(pList); rc = sqlite3WalFrames(pPager->pWal, - pPager->pageSize, pList, nTruncate, isCommit, sync_flags + pPager->pageSize, pList, nTruncate, isCommit, syncFlags ); if( rc==SQLITE_OK && pPager->pBackup ){ PgHdr *p; @@ -2939,9 +2985,8 @@ static int pagerWalFrames( } #ifdef SQLITE_CHECK_PAGES - { - PgHdr *p; - for(p=pList; p; p=p->pDirty) pager_set_pagehash(p); + for(p=pList; p; p=p->pDirty){ + pager_set_pagehash(p); } #endif @@ -2971,12 +3016,13 @@ static int pagerBeginReadTransaction(Pager *pPager){ sqlite3WalEndReadTransaction(pPager->pWal); rc = sqlite3WalBeginReadTransaction(pPager->pWal, &changed); - if( rc==SQLITE_OK && changed ){ + if( rc!=SQLITE_OK || changed ){ pager_reset(pPager); } return rc; } +#endif /* ** This function is called as part of the transition from PAGER_OPEN @@ -3033,7 +3079,7 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){ return SQLITE_OK; } - +#ifndef SQLITE_OMIT_WAL /* ** Check if the *-wal file that corresponds to the database opened by pPager ** exists if the database is not empy, or verify that the *-wal file does @@ -3258,14 +3304,49 @@ void sqlite3PagerSetCachesize(Pager *pPager, int mxPage){ ** assurance that the journal will not be corrupted to the ** point of causing damage to the database during rollback. ** +** The above is for a rollback-journal mode. For WAL mode, OFF continues +** to mean that no syncs ever occur. NORMAL means that the WAL is synced +** prior to the start of checkpoint and that the database file is synced +** at the conclusion of the checkpoint if the entire content of the WAL +** was written back into the database. But no sync operations occur for +** an ordinary commit in NORMAL mode with WAL. FULL means that the WAL +** file is synced following each commit operation, in addition to the +** syncs associated with NORMAL. +** +** Do not confuse synchronous=FULL with SQLITE_SYNC_FULL. The +** SQLITE_SYNC_FULL macro means to use the MacOSX-style full-fsync +** using fcntl(F_FULLFSYNC). SQLITE_SYNC_NORMAL means to do an +** ordinary fsync() call. There is no difference between SQLITE_SYNC_FULL +** and SQLITE_SYNC_NORMAL on platforms other than MacOSX. But the +** synchronous=FULL versus synchronous=NORMAL setting determines when +** the xSync primitive is called and is relevant to all platforms. +** ** Numeric values associated with these states are OFF==1, NORMAL=2, ** and FULL=3. */ #ifndef SQLITE_OMIT_PAGER_PRAGMAS -void sqlite3PagerSetSafetyLevel(Pager *pPager, int level, int bFullFsync){ +void sqlite3PagerSetSafetyLevel( + Pager *pPager, /* The pager to set safety level for */ + int level, /* PRAGMA synchronous. 1=OFF, 2=NORMAL, 3=FULL */ + int bFullFsync, /* PRAGMA fullfsync */ + int bCkptFullFsync /* PRAGMA checkpoint_fullfsync */ +){ + assert( level>=1 && level<=3 ); pPager->noSync = (level==1 || pPager->tempFile) ?1:0; pPager->fullSync = (level==3 && !pPager->tempFile) ?1:0; - pPager->sync_flags = (bFullFsync?SQLITE_SYNC_FULL:SQLITE_SYNC_NORMAL); + if( pPager->noSync ){ + pPager->syncFlags = 0; + pPager->ckptSyncFlags = 0; + }else if( bFullFsync ){ + pPager->syncFlags = SQLITE_SYNC_FULL; + pPager->ckptSyncFlags = SQLITE_SYNC_FULL; + }else if( bCkptFullFsync ){ + pPager->syncFlags = SQLITE_SYNC_NORMAL; + pPager->ckptSyncFlags = SQLITE_SYNC_FULL; + }else{ + pPager->syncFlags = SQLITE_SYNC_NORMAL; + pPager->ckptSyncFlags = SQLITE_SYNC_NORMAL; + } } #endif @@ -3444,9 +3525,8 @@ int sqlite3PagerMaxPageCount(Pager *pPager, int mxPage){ if( mxPage>0 ){ pPager->mxPgno = mxPage; } - if( pPager->eState!=PAGER_OPEN && pPager->mxPgnodbSize ){ - pPager->mxPgno = pPager->dbSize; - } + assert( pPager->eState!=PAGER_OPEN ); /* Called only by OP_MaxPgcnt */ + assert( pPager->mxPgno>=pPager->dbSize ); /* OP_MaxPgcnt enforces this */ return pPager->mxPgno; } @@ -3651,10 +3731,7 @@ int sqlite3PagerClose(Pager *pPager){ /* pPager->errCode = 0; */ pPager->exclusiveMode = 0; #ifndef SQLITE_OMIT_WAL - sqlite3WalClose(pPager->pWal, - (pPager->noSync ? 0 : pPager->sync_flags), - pPager->pageSize, pTmp - ); + sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, pPager->pageSize, pTmp); pPager->pWal = 0; #endif pager_reset(pPager); @@ -3820,7 +3897,7 @@ static int syncJournal(Pager *pPager, int newHdr){ if( pPager->fullSync && 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){ PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager))); IOTRACE(("JSYNC %p\n", pPager)) - rc = sqlite3OsSync(pPager->jfd, pPager->sync_flags); + rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags); if( rc!=SQLITE_OK ) return rc; } IOTRACE(("JHDR %p %lld\n", pPager, pPager->journalHdr)); @@ -3832,8 +3909,8 @@ static int syncJournal(Pager *pPager, int newHdr){ if( 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){ PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager))); IOTRACE(("JSYNC %p\n", pPager)) - rc = sqlite3OsSync(pPager->jfd, pPager->sync_flags| - (pPager->sync_flags==SQLITE_SYNC_FULL?SQLITE_SYNC_DATAONLY:0) + rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags| + (pPager->syncFlags==SQLITE_SYNC_FULL?SQLITE_SYNC_DATAONLY:0) ); if( rc!=SQLITE_OK ) return rc; } @@ -3934,6 +4011,7 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ char *pData; /* Data to write */ assert( (pList->flags&PGHDR_NEED_SYNC)==0 ); + if( pList->pgno==1 ) pager_write_changecounter(pList); /* Encode the database */ CODEC2(pPager, pList->pData, pgno, 6, return SQLITE_NOMEM, pData); @@ -4226,6 +4304,13 @@ int sqlite3PagerOpen( /* Set the output variable to NULL in case an error occurs. */ *ppPager = 0; +#ifndef SQLITE_OMIT_MEMORYDB + if( flags & PAGER_MEMORY ){ + memDb = 1; + zFilename = 0; + } +#endif + /* Compute and store the full pathname in an allocated buffer pointed ** to by zPathname, length nPathname. Or, if this is a temporary file, ** leave both nPathname and zPathname set to 0. @@ -4236,17 +4321,8 @@ int sqlite3PagerOpen( if( zPathname==0 ){ return SQLITE_NOMEM; } -#ifndef SQLITE_OMIT_MEMORYDB - if( strcmp(zFilename,":memory:")==0 ){ - memDb = 1; - zPathname[0] = 0; - }else -#endif - { - zPathname[0] = 0; /* Make sure initialized even if FullPathname() fails */ - rc = sqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname); - } - + zPathname[0] = 0; /* Make sure initialized even if FullPathname() fails */ + rc = sqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname); nPathname = sqlite3Strlen30(zPathname); if( rc==SQLITE_OK && nPathname+8>pVfs->mxPathname ){ /* This branch is taken when the journal path required by @@ -4301,19 +4377,15 @@ int sqlite3PagerOpen( /* Fill in the Pager.zFilename and Pager.zJournal buffers, if required. */ if( zPathname ){ + assert( nPathname>0 ); pPager->zJournal = (char*)(pPtr += nPathname + 1); memcpy(pPager->zFilename, zPathname, nPathname); memcpy(pPager->zJournal, zPathname, nPathname); memcpy(&pPager->zJournal[nPathname], "-journal", 8); - if( pPager->zFilename[0]==0 ){ - pPager->zJournal[0] = 0; - } #ifndef SQLITE_OMIT_WAL - else{ - pPager->zWal = &pPager->zJournal[nPathname+8+1]; - memcpy(pPager->zWal, zPathname, nPathname); - memcpy(&pPager->zWal[nPathname], "-wal", 4); - } + pPager->zWal = &pPager->zJournal[nPathname+8+1]; + memcpy(pPager->zWal, zPathname, nPathname); + memcpy(&pPager->zWal[nPathname], "-wal", 4); #endif sqlite3_free(zPathname); } @@ -4322,9 +4394,10 @@ int sqlite3PagerOpen( /* Open the pager file. */ - if( zFilename && zFilename[0] && !memDb ){ + if( zFilename && zFilename[0] ){ int fout = 0; /* VFS flags returned by xOpen() */ rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout); + assert( !memDb ); readOnly = (fout&SQLITE_OPEN_READONLY); /* If the file was successfully opened for read/write access, @@ -4428,7 +4501,8 @@ int sqlite3PagerOpen( assert( useJournal || pPager->tempFile ); pPager->noSync = pPager->tempFile; pPager->fullSync = pPager->noSync ?0:1; - pPager->sync_flags = SQLITE_SYNC_NORMAL; + pPager->syncFlags = pPager->noSync ? 0 : SQLITE_SYNC_NORMAL; + pPager->ckptSyncFlags = pPager->syncFlags; /* pPager->pFirst = 0; */ /* pPager->pFirstSynced = 0; */ /* pPager->pLast = 0; */ @@ -4528,7 +4602,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){ sqlite3BeginBenignMalloc(); if( pagerLockDb(pPager, RESERVED_LOCK)==SQLITE_OK ){ sqlite3OsDelete(pVfs, pPager->zJournal, 0); - pagerUnlockDb(pPager, SHARED_LOCK); + if( !pPager->exclusiveMode ) pagerUnlockDb(pPager, SHARED_LOCK); } sqlite3EndBenignMalloc(); }else{ @@ -4778,7 +4852,9 @@ int sqlite3PagerSharedLock(Pager *pPager){ ** mode. Otherwise, the following function call is a no-op. */ rc = pagerOpenWalIfPresent(pPager); +#ifndef SQLITE_OMIT_WAL assert( pPager->pWal==0 || rc==SQLITE_OK ); +#endif } if( pagerUseWal(pPager) ){ @@ -5207,29 +5283,29 @@ static int pager_write(PgHdr *pPg){ 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 + ** rollback journal might not yet be open. Open it now if this is the case. + ** + ** This is done before calling sqlite3PcacheMakeDirty() on the page. + ** Otherwise, if it were done after calling sqlite3PcacheMakeDirty(), then + ** an error might occur and the pager would end up in WRITER_LOCKED state + ** with pages marked as dirty in the cache. + */ + if( pPager->eState==PAGER_WRITER_LOCKED ){ + rc = pager_open_journal(pPager); + if( rc!=SQLITE_OK ) return rc; + } + assert( pPager->eState>=PAGER_WRITER_CACHEMOD ); + assert( assert_pager_state(pPager) ); + /* 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) ){ assert( !pagerUseWal(pPager) ); - assert( pPager->eState>=PAGER_WRITER_CACHEMOD ); }else{ - - /* If we get this far, it means that the page needs to be - ** written to the transaction journal or the checkpoint journal - ** or both. - ** - ** Higher level routines have already obtained the necessary locks - ** to begin the write-transaction, but the rollback journal might not - ** yet be open. Open it now if this is the case. - */ - if( pPager->eState==PAGER_WRITER_LOCKED ){ - rc = pager_open_journal(pPager); - if( rc!=SQLITE_OK ) return rc; - } - assert( pPager->eState>=PAGER_WRITER_CACHEMOD ); - assert( assert_pager_state(pPager) ); /* The transaction journal now exists and we have a RESERVED or an ** EXCLUSIVE lock on the main database file. Write the current page to @@ -5456,7 +5532,13 @@ void sqlite3PagerDontWrite(PgHdr *pPg){ /* ** This routine is called to increment the value of the database file ** change-counter, stored as a 4-byte big-endian integer starting at -** byte offset 24 of the pager file. +** byte offset 24 of the pager file. The secondary change counter at +** 92 is also updated, as is the SQLite version number at offset 96. +** +** But this only happens if the pPager->changeCountDone flag is false. +** To avoid excess churning of page 1, the update only happens once. +** See also the pager_write_changecounter() routine that does an +** unconditional update of the change counters. ** ** If the isDirectMode flag is zero, then this is done by calling ** sqlite3PagerWrite() on page 1, then modifying the contents of the @@ -5497,7 +5579,6 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ if( !pPager->changeCountDone && pPager->dbSize>0 ){ PgHdr *pPgHdr; /* Reference to page 1 */ - u32 change_counter; /* Initial value of change-counter field */ assert( !pPager->tempFile && isOpen(pPager->fd) ); @@ -5515,16 +5596,8 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ } if( rc==SQLITE_OK ){ - /* Increment the value just read and write it back to byte 24. */ - change_counter = sqlite3Get4byte((u8*)pPager->dbFileVers); - change_counter++; - put32bits(((char*)pPgHdr->pData)+24, change_counter); - - /* Also store the SQLite version number in bytes 96..99 and in - ** bytes 92..95 store the change counter for which the version number - ** is valid. */ - put32bits(((char*)pPgHdr->pData)+92, change_counter); - put32bits(((char*)pPgHdr->pData)+96, SQLITE_VERSION_NUMBER); + /* Actually do the update of the change counter */ + pager_write_changecounter(pPgHdr); /* If running in direct mode, write the contents of page 1 to the file. */ if( DIRECT_MODE ){ @@ -5549,19 +5622,20 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ } /* -** Sync the pager file to disk. This is a no-op for in-memory files +** Sync the database file to disk. This is a no-op for in-memory databases ** or pages with the Pager.noSync flag set. ** -** If successful, or called on a pager for which it is a no-op, this +** 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. */ int sqlite3PagerSync(Pager *pPager){ - int rc; /* Return code */ - assert( !MEMDB ); - if( pPager->noSync ){ - rc = SQLITE_OK; - }else{ - rc = sqlite3OsSync(pPager->fd, pPager->sync_flags); + int rc = SQLITE_OK; + if( !pPager->noSync ){ + assert( !MEMDB ); + rc = sqlite3OsSync(pPager->fd, pPager->syncFlags); + }else if( isOpen(pPager->fd) ){ + assert( !MEMDB ); + sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SYNC_OMITTED, (void *)&rc); } return rc; } @@ -5650,7 +5724,7 @@ int sqlite3PagerCommitPhaseOne( PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache); if( pList ){ rc = pagerWalFrames(pPager, pList, pPager->dbSize, 1, - (pPager->fullSync ? pPager->sync_flags : 0) + (pPager->fullSync ? pPager->syncFlags : 0) ); } if( rc==SQLITE_OK ){ @@ -5780,8 +5854,8 @@ int sqlite3PagerCommitPhaseOne( } /* Finally, sync the database file. */ - if( !pPager->noSync && !noSync ){ - rc = sqlite3OsSync(pPager->fd, pPager->sync_flags); + if( !noSync ){ + rc = sqlite3PagerSync(pPager); } IOTRACE(("DBSYNC %p\n", pPager)) } @@ -5893,7 +5967,17 @@ int sqlite3PagerRollback(Pager *pPager){ rc2 = pager_end_transaction(pPager, pPager->setMaster); if( rc==SQLITE_OK ) rc = rc2; }else if( !isOpen(pPager->jfd) || pPager->eState==PAGER_WRITER_LOCKED ){ + int eState = pPager->eState; rc = pager_end_transaction(pPager, 0); + if( !MEMDB && eState>PAGER_WRITER_LOCKED ){ + /* This can happen using journal_mode=off. Move the pager to the error + ** state to indicate that the contents of the cache may not be trusted. + ** Any active readers will get SQLITE_ABORT. + */ + pPager->errCode = SQLITE_ABORT; + pPager->eState = PAGER_ERROR; + return rc; + } }else{ rc = pager_playback(pPager, 0); } @@ -6352,7 +6436,8 @@ int sqlite3PagerLockingMode(Pager *pPager, int eMode){ || eMode==PAGER_LOCKINGMODE_EXCLUSIVE ); assert( PAGER_LOCKINGMODE_QUERY<0 ); assert( PAGER_LOCKINGMODE_NORMAL>=0 && PAGER_LOCKINGMODE_EXCLUSIVE>=0 ); - if( eMode>=0 && !pPager->tempFile ){ + assert( pPager->exclusiveMode || 0==sqlite3WalHeapMemory(pPager->pWal) ); + if( eMode>=0 && !pPager->tempFile && !sqlite3WalHeapMemory(pPager->pWal) ){ pPager->exclusiveMode = (u8)eMode; } return (int)pPager->exclusiveMode; @@ -6521,10 +6606,8 @@ int sqlite3PagerCheckpoint(Pager *pPager){ int rc = SQLITE_OK; if( pPager->pWal ){ u8 *zBuf = (u8 *)pPager->pTmpSpace; - rc = sqlite3WalCheckpoint(pPager->pWal, - (pPager->noSync ? 0 : pPager->sync_flags), - pPager->pageSize, zBuf - ); + rc = sqlite3WalCheckpoint(pPager->pWal, pPager->ckptSyncFlags, + pPager->pageSize, zBuf); } return rc; } @@ -6539,9 +6622,61 @@ int sqlite3PagerWalCallback(Pager *pPager){ */ int sqlite3PagerWalSupported(Pager *pPager){ const sqlite3_io_methods *pMethods = pPager->fd->pMethods; - return pMethods->iVersion>=2 && pMethods->xShmMap!=0; + return pPager->exclusiveMode || (pMethods->iVersion>=2 && pMethods->xShmMap); } +/* +** Attempt to take an exclusive lock on the database file. If a PENDING lock +** is obtained instead, immediately release it. +*/ +static int pagerExclusiveLock(Pager *pPager){ + int rc; /* Return code */ + + assert( pPager->eLock==SHARED_LOCK || pPager->eLock==EXCLUSIVE_LOCK ); + rc = pagerLockDb(pPager, EXCLUSIVE_LOCK); + if( rc!=SQLITE_OK ){ + /* If the attempt to grab the pending lock failed, release the + ** exclusive lock that may have been obtained instead. */ + pagerUnlockDb(pPager, SHARED_LOCK); + } + + return rc; +} + +/* +** Call sqlite3WalOpen() to open the WAL handle. If the pager is in +** exclusive-locking mode when this function is called, take an EXCLUSIVE +** lock on the database file and use heap-memory to store the wal-index +** in. Otherwise, use the normal shared-memory. +*/ +static int pagerOpenWal(Pager *pPager){ + int rc = SQLITE_OK; + + assert( pPager->pWal==0 && pPager->tempFile==0 ); + assert( pPager->eLock==SHARED_LOCK || pPager->eLock==EXCLUSIVE_LOCK || pPager->noReadlock); + + /* If the pager is already in exclusive-mode, the WAL module will use + ** heap-memory for the wal-index instead of the VFS shared-memory + ** implementation. Take the exclusive lock now, before opening the WAL + ** file, to make sure this is safe. + */ + if( pPager->exclusiveMode ){ + rc = pagerExclusiveLock(pPager); + } + + /* Open the connection to the log file. If this operation fails, + ** (e.g. due to malloc() failure), return an error code. + */ + if( rc==SQLITE_OK ){ + rc = sqlite3WalOpen(pPager->pVfs, + pPager->fd, pPager->zWal, pPager->exclusiveMode, &pPager->pWal + ); + } + + return rc; +} + + /* ** The caller must be holding a SHARED lock on the database file to call ** this function. @@ -6575,11 +6710,7 @@ int sqlite3PagerOpenWal( /* Close any rollback journal previously open */ sqlite3OsClose(pPager->jfd); - /* Open the connection to the log file. If this operation fails, - ** (e.g. due to malloc() failure), unlock the database file and - ** return an error code. - */ - rc = sqlite3WalOpen(pPager->pVfs, pPager->fd, pPager->zWal, &pPager->pWal); + rc = pagerOpenWal(pPager); if( rc==SQLITE_OK ){ pPager->journalMode = PAGER_JOURNALMODE_WAL; pPager->eState = PAGER_OPEN; @@ -6618,8 +6749,7 @@ int sqlite3PagerCloseWal(Pager *pPager){ ); } if( rc==SQLITE_OK && logexists ){ - rc = sqlite3WalOpen(pPager->pVfs, pPager->fd, - pPager->zWal, &pPager->pWal); + rc = pagerOpenWal(pPager); } } @@ -6627,17 +6757,11 @@ int sqlite3PagerCloseWal(Pager *pPager){ ** the database file, the log and log-summary files will be deleted. */ if( rc==SQLITE_OK && pPager->pWal ){ - rc = pagerLockDb(pPager, EXCLUSIVE_LOCK); + rc = pagerExclusiveLock(pPager); if( rc==SQLITE_OK ){ - rc = sqlite3WalClose(pPager->pWal, - (pPager->noSync ? 0 : pPager->sync_flags), - pPager->pageSize, (u8*)pPager->pTmpSpace - ); + rc = sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, + pPager->pageSize, (u8*)pPager->pTmpSpace); pPager->pWal = 0; - }else{ - /* If we cannot get an EXCLUSIVE lock, downgrade the PENDING lock - ** that we did get back to SHARED. */ - pagerUnlockDb(pPager, SQLITE_LOCK_SHARED); } } return rc; diff --git a/src/pager.h b/src/pager.h index 0f100d5c..e775b0c1 100644 --- a/src/pager.h +++ b/src/pager.h @@ -59,6 +59,7 @@ typedef struct PgHdr DbPage; */ #define PAGER_OMIT_JOURNAL 0x0001 /* Do not use a rollback journal */ #define PAGER_NO_READLOCK 0x0002 /* Omit readlocks on readonly files */ +#define PAGER_MEMORY 0x0004 /* In-memory database */ /* ** Valid values for the second argument to sqlite3PagerLockingMode(). @@ -102,7 +103,7 @@ void sqlite3PagerSetBusyhandler(Pager*, int(*)(void *), void *); int sqlite3PagerSetPagesize(Pager*, u32*, int); int sqlite3PagerMaxPageCount(Pager*, int); void sqlite3PagerSetCachesize(Pager*, int); -void sqlite3PagerSetSafetyLevel(Pager*,int,int); +void sqlite3PagerSetSafetyLevel(Pager*,int,int,int); int sqlite3PagerLockingMode(Pager *, int); int sqlite3PagerSetJournalMode(Pager *, int); int sqlite3PagerGetJournalMode(Pager*); diff --git a/src/pcache.c b/src/pcache.c index 23ea0a7c..242f3071 100644 --- a/src/pcache.c +++ b/src/pcache.c @@ -142,12 +142,16 @@ static void pcacheUnpin(PgHdr *p){ */ int sqlite3PcacheInitialize(void){ if( sqlite3GlobalConfig.pcache.xInit==0 ){ + /* IMPLEMENTATION-OF: R-26801-64137 If the xInit() method is NULL, then the + ** built-in default page cache is used instead of the application defined + ** page cache. */ sqlite3PCacheSetDefault(); } return sqlite3GlobalConfig.pcache.xInit(sqlite3GlobalConfig.pcache.pArg); } void sqlite3PcacheShutdown(void){ if( sqlite3GlobalConfig.pcache.xShutdown ){ + /* IMPLEMENTATION-OF: R-26000-56589 The xShutdown() method may be NULL. */ sqlite3GlobalConfig.pcache.xShutdown(sqlite3GlobalConfig.pcache.pArg); } } diff --git a/src/pcache1.c b/src/pcache1.c index f9927b89..ad443954 100644 --- a/src/pcache1.c +++ b/src/pcache1.c @@ -22,24 +22,62 @@ typedef struct PCache1 PCache1; typedef struct PgHdr1 PgHdr1; typedef struct PgFreeslot PgFreeslot; +typedef struct PGroup PGroup; -/* Pointers to structures of this type are cast and returned as -** opaque sqlite3_pcache* handles +/* 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 +** 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: +** +** (1) Every PCache is the sole member of its own PGroup. There is +** one PGroup per PCache. +** +** (2) There is a single global PGroup that all PCaches are a member +** of. +** +** Mode 1 uses more memory (since PCache instances are not able to rob +** unused pages from other PCaches) but it also operates without a mutex, +** and is therefore often faster. Mode 2 requires a mutex in order to be +** threadsafe, but is able recycle pages more efficient. +** +** For mode (1), PGroup.mutex is NULL. For mode (2) there is only a single +** PGroup which is the pcache1.grp global variable and its mutex is +** SQLITE_MUTEX_STATIC_LRU. +*/ +struct PGroup { + sqlite3_mutex *mutex; /* MUTEX_STATIC_LRU or NULL */ + int nMaxPage; /* Sum of nMax for purgeable caches */ + int nMinPage; /* Sum of nMin for purgeable caches */ + int mxPinned; /* nMaxpage + 10 - nMinPage */ + int nCurrentPage; /* Number of purgeable pages allocated */ + PgHdr1 *pLruHead, *pLruTail; /* LRU list of unpinned pages */ +}; + +/* Each page cache is an instance of the following object. Every +** open database file (including each in-memory database and each +** temporary or transient database) has a single page cache which +** is an instance of this object. +** +** Pointers to structures of this type are cast and returned as +** opaque sqlite3_pcache* handles. */ struct PCache1 { /* Cache configuration parameters. Page size (szPage) and the purgeable ** flag (bPurgeable) are set when the cache is created. nMax may be ** modified at any time by a call to the pcache1CacheSize() method. - ** The global mutex must be held when accessing nMax. + ** The PGroup mutex must be held when accessing nMax. */ + PGroup *pGroup; /* PGroup this cache belongs to */ int szPage; /* Size of allocated pages in bytes */ int bPurgeable; /* True if cache is purgeable */ unsigned int nMin; /* Minimum number of pages reserved */ unsigned int nMax; /* Configured "cache_size" value */ + unsigned int n90pct; /* nMax*9/10 */ /* Hash table of all pages. The following variables may only be accessed - ** when the accessor is holding the global mutex (see pcache1EnterMutex() - ** and pcache1LeaveMutex()). + ** when the accessor is holding the PGroup mutex. */ unsigned int nRecyclable; /* Number of pages in the LRU list */ unsigned int nPage; /* Total number of pages in apHash */ @@ -75,18 +113,27 @@ struct PgFreeslot { ** Global data used by this cache. */ static SQLITE_WSD struct PCacheGlobal { - sqlite3_mutex *mutex; /* static mutex MUTEX_STATIC_LRU */ + PGroup grp; /* The global PGroup for mode (2) */ - int nMaxPage; /* Sum of nMaxPage for purgeable caches */ - int nMinPage; /* Sum of nMinPage for purgeable caches */ - int nCurrentPage; /* Number of purgeable pages allocated */ - PgHdr1 *pLruHead, *pLruTail; /* LRU list of unpinned pages */ - - /* Variables related to SQLITE_CONFIG_PAGECACHE settings. */ - int szSlot; /* Size of each free slot */ - void *pStart, *pEnd; /* Bounds of pagecache malloc range */ - PgFreeslot *pFree; /* Free page blocks */ - int isInit; /* True if initialized */ + /* Variables related to SQLITE_CONFIG_PAGECACHE settings. The + ** szSlot, nSlot, pStart, pEnd, nReserve, and isInit values are all + ** fixed at sqlite3_initialize() time and do not require mutex protection. + ** The nFreeSlot and pFree values do require mutex protection. + */ + int isInit; /* True if initialized */ + int szSlot; /* Size of each free slot */ + int nSlot; /* The number of pcache slots */ + int nReserve; /* Try to keep nFreeSlot above this */ + void *pStart, *pEnd; /* Bounds of pagecache malloc range */ + /* Above requires no mutex. Use mutex below for variable that follow. */ + sqlite3_mutex *mutex; /* Mutex for accessing the following: */ + int nFreeSlot; /* Number of unused pcache slots */ + PgFreeslot *pFree; /* Free page blocks */ + /* The following value requires a mutex to change. We skip the mutex on + ** reading because (1) most platforms read a 32-bit integer atomically and + ** (2) even if an incorrect value is read, no great harm is done since this + ** is really just an optimization. */ + int bUnderPressure; /* True if low on PAGECACHE memory */ } pcache1_g; /* @@ -112,10 +159,10 @@ static SQLITE_WSD struct PCacheGlobal { #define PAGE_TO_PGHDR1(c, p) (PgHdr1*)(((char*)p) + c->szPage) /* -** Macros to enter and leave the global LRU mutex. +** Macros to enter and leave the PCache LRU mutex. */ -#define pcache1EnterMutex() sqlite3_mutex_enter(pcache1.mutex) -#define pcache1LeaveMutex() sqlite3_mutex_leave(pcache1.mutex) +#define pcache1EnterMutex(X) sqlite3_mutex_enter((X)->mutex) +#define pcache1LeaveMutex(X) sqlite3_mutex_leave((X)->mutex) /******************************************************************************/ /******** Page Allocation/SQLITE_CONFIG_PCACHE Related Functions **************/ @@ -125,14 +172,20 @@ static SQLITE_WSD struct PCacheGlobal { ** supplied to use for the page-cache by passing the SQLITE_CONFIG_PAGECACHE ** verb to sqlite3_config(). Parameter pBuf points to an allocation large ** enough to contain 'n' buffers of 'sz' bytes each. +** +** This routine is called from sqlite3_initialize() and so it is guaranteed +** to be serialized already. There is no need for further mutexing. */ void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){ if( pcache1.isInit ){ PgFreeslot *p; sz = ROUNDDOWN8(sz); pcache1.szSlot = sz; + pcache1.nSlot = pcache1.nFreeSlot = n; + pcache1.nReserve = n>90 ? 10 : (n/10 + 1); pcache1.pStart = pBuf; pcache1.pFree = 0; + pcache1.bUnderPressure = 0; while( n-- ){ p = (PgFreeslot*)pBuf; p->pNext = pcache1.pFree; @@ -148,30 +201,36 @@ void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){ ** configured using sqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no ** such buffer exists or there is no space left in it, this function falls ** back to sqlite3Malloc(). +** +** Multiple threads can run this routine at the same time. Global variables +** in pcache1 need to be protected via mutex. */ static void *pcache1Alloc(int nByte){ - void *p; - assert( sqlite3_mutex_held(pcache1.mutex) ); + void *p = 0; + assert( sqlite3_mutex_notheld(pcache1.grp.mutex) ); sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, nByte); - if( nByte<=pcache1.szSlot && pcache1.pFree ){ - assert( pcache1.isInit ); + if( nByte<=pcache1.szSlot ){ + sqlite3_mutex_enter(pcache1.mutex); p = (PgHdr1 *)pcache1.pFree; - pcache1.pFree = pcache1.pFree->pNext; - sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, 1); - }else{ - - /* Allocate a new buffer using sqlite3Malloc. Before doing so, exit the - ** global pcache mutex and unlock the pager-cache object pCache. This is - ** so that if the attempt to allocate a new buffer causes the the - ** configured soft-heap-limit to be breached, it will be possible to - ** reclaim memory from this pager-cache. + if( p ){ + pcache1.pFree = pcache1.pFree->pNext; + pcache1.nFreeSlot--; + pcache1.bUnderPressure = pcache1.nFreeSlot=0 ); + sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, 1); + } + sqlite3_mutex_leave(pcache1.mutex); + } + if( p==0 ){ + /* Memory is not available in the SQLITE_CONFIG_PAGECACHE pool. Get + ** it from sqlite3Malloc instead. */ - pcache1LeaveMutex(); p = sqlite3Malloc(nByte); - pcache1EnterMutex(); if( p ){ int sz = sqlite3MallocSize(p); + sqlite3_mutex_enter(pcache1.mutex); sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz); + sqlite3_mutex_leave(pcache1.mutex); } sqlite3MemdebugSetType(p, MEMTYPE_PCACHE); } @@ -182,30 +241,35 @@ static void *pcache1Alloc(int nByte){ ** Free an allocated buffer obtained from pcache1Alloc(). */ static void pcache1Free(void *p){ - assert( sqlite3_mutex_held(pcache1.mutex) ); if( p==0 ) return; if( p>=pcache1.pStart && ppNext = pcache1.pFree; pcache1.pFree = pSlot; + pcache1.nFreeSlot++; + pcache1.bUnderPressure = pcache1.nFreeSlot=pcache1.pStart && pbPurgeable ){ - pcache1.nCurrentPage++; + pCache->pGroup->nCurrentPage++; } }else{ p = 0; @@ -246,8 +310,9 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache){ */ static void pcache1FreePage(PgHdr1 *p){ if( ALWAYS(p) ){ - if( p->pCache->bPurgeable ){ - pcache1.nCurrentPage--; + PCache1 *pCache = p->pCache; + if( pCache->bPurgeable ){ + pCache->pGroup->nCurrentPage--; } pcache1Free(PGHDR1_TO_PAGE(p)); } @@ -259,20 +324,39 @@ static void pcache1FreePage(PgHdr1 *p){ ** exists, this function falls back to sqlite3Malloc(). */ void *sqlite3PageMalloc(int sz){ - void *p; - pcache1EnterMutex(); - p = pcache1Alloc(sz); - pcache1LeaveMutex(); - return p; + return pcache1Alloc(sz); } /* ** Free an allocated buffer obtained from sqlite3PageMalloc(). */ void sqlite3PageFree(void *p){ - pcache1EnterMutex(); pcache1Free(p); - pcache1LeaveMutex(); +} + + +/* +** Return true if it desirable to avoid allocating a new page cache +** entry. +** +** If memory was allocated specifically to the page cache using +** SQLITE_CONFIG_PAGECACHE but that memory has all been used, then +** it is desirable to avoid allocating a new page cache entry because +** presumably SQLITE_CONFIG_PAGECACHE was suppose to be sufficient +** for all page cache needs and we should not need to spill the +** allocation onto the heap. +** +** Or, the heap is used for all page cache memory put the heap is +** under memory pressure, then again it is desirable to avoid +** allocating a new page cache entry in order to avoid stressing +** the heap even further. +*/ +static int pcache1UnderMemoryPressure(PCache1 *pCache){ + if( pcache1.nSlot && pCache->szPage<=pcache1.szSlot ){ + return pcache1.bUnderPressure; + }else{ + return sqlite3HeapNearlyFull(); + } } /******************************************************************************/ @@ -282,25 +366,25 @@ void sqlite3PageFree(void *p){ ** This function is used to resize the hash table used by the cache passed ** as the first argument. ** -** The global mutex must be held when this function is called. +** The PCache mutex must be held when this function is called. */ static int pcache1ResizeHash(PCache1 *p){ PgHdr1 **apNew; unsigned int nNew; unsigned int i; - assert( sqlite3_mutex_held(pcache1.mutex) ); + assert( sqlite3_mutex_held(p->pGroup->mutex) ); nNew = p->nHash*2; if( nNew<256 ){ nNew = 256; } - pcache1LeaveMutex(); + pcache1LeaveMutex(p->pGroup); if( p->nHash ){ sqlite3BeginBenignMalloc(); } apNew = (PgHdr1 **)sqlite3_malloc(sizeof(PgHdr1 *)*nNew); if( p->nHash ){ sqlite3EndBenignMalloc(); } - pcache1EnterMutex(); + pcache1EnterMutex(p->pGroup); if( apNew ){ memset(apNew, 0, sizeof(PgHdr1 *)*nNew); for(i=0; inHash; i++){ @@ -323,25 +407,33 @@ static int pcache1ResizeHash(PCache1 *p){ /* ** This function is used internally to remove the page pPage from the -** global LRU list, if is part of it. If pPage is not part of the global +** 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 global mutex must be held when this function is called. +** 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){ - assert( sqlite3_mutex_held(pcache1.mutex) ); - if( pPage && (pPage->pLruNext || pPage==pcache1.pLruTail) ){ + PCache1 *pCache; + PGroup *pGroup; + + if( pPage==0 ) return; + pCache = pPage->pCache; + pGroup = pCache->pGroup; + 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( pcache1.pLruHead==pPage ){ - pcache1.pLruHead = pPage->pLruNext; + if( pGroup->pLruHead==pPage ){ + pGroup->pLruHead = pPage->pLruNext; } - if( pcache1.pLruTail==pPage ){ - pcache1.pLruTail = pPage->pLruPrev; + if( pGroup->pLruTail==pPage ){ + pGroup->pLruTail = pPage->pLruPrev; } pPage->pLruNext = 0; pPage->pLruPrev = 0; @@ -354,13 +446,14 @@ static void pcache1PinPage(PgHdr1 *pPage){ ** Remove the page supplied as an argument from the hash table ** (PCache1.apHash structure) that it is currently stored in. ** -** The global mutex must be held when this function is called. +** The PGroup mutex must be held when this function is called. */ static void pcache1RemoveFromHash(PgHdr1 *pPage){ unsigned int h; PCache1 *pCache = pPage->pCache; PgHdr1 **pp; + assert( sqlite3_mutex_held(pCache->pGroup->mutex) ); h = pPage->iKey % pCache->nHash; for(pp=&pCache->apHash[h]; (*pp)!=pPage; pp=&(*pp)->pNext); *pp = (*pp)->pNext; @@ -369,13 +462,14 @@ static void pcache1RemoveFromHash(PgHdr1 *pPage){ } /* -** If there are currently more than pcache.nMaxPage pages allocated, try -** to recycle pages to reduce the number allocated to pcache.nMaxPage. +** If there are currently more than nMaxPage pages allocated, try +** to recycle pages to reduce the number allocated to nMaxPage. */ -static void pcache1EnforceMaxPage(void){ - assert( sqlite3_mutex_held(pcache1.mutex) ); - while( pcache1.nCurrentPage>pcache1.nMaxPage && pcache1.pLruTail ){ - PgHdr1 *p = pcache1.pLruTail; +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 ); pcache1PinPage(p); pcache1RemoveFromHash(p); pcache1FreePage(p); @@ -387,15 +481,15 @@ static void pcache1EnforceMaxPage(void){ ** greater than or equal to iLimit. Any pinned pages that meet this ** criteria are unpinned before they are discarded. ** -** The global mutex must be held when this function is called. +** The PCache mutex must be held when this function is called. */ static void pcache1TruncateUnsafe( - PCache1 *pCache, - unsigned int iLimit + PCache1 *pCache, /* The cache to truncate */ + unsigned int iLimit /* Drop pages with this pgno or larger */ ){ - TESTONLY( unsigned int nPage = 0; ) /* Used to assert pCache->nPage is correct */ + TESTONLY( unsigned int nPage = 0; ) /* To assert pCache->nPage is correct */ unsigned int h; - assert( sqlite3_mutex_held(pcache1.mutex) ); + assert( sqlite3_mutex_held(pCache->pGroup->mutex) ); for(h=0; hnHash; h++){ PgHdr1 **pp = &pCache->apHash[h]; PgHdr1 *pPage; @@ -425,8 +519,10 @@ static int pcache1Init(void *NotUsed){ assert( pcache1.isInit==0 ); memset(&pcache1, 0, sizeof(pcache1)); if( sqlite3GlobalConfig.bCoreMutex ){ - pcache1.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU); + pcache1.grp.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU); + pcache1.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_PMEM); } + pcache1.grp.mxPinned = 10; pcache1.isInit = 1; return SQLITE_OK; } @@ -448,18 +544,47 @@ static void pcache1Shutdown(void *NotUsed){ ** Allocate a new cache. */ static sqlite3_pcache *pcache1Create(int szPage, int bPurgeable){ - PCache1 *pCache; + PCache1 *pCache; /* The newly created page cache */ + PGroup *pGroup; /* The group the new page cache will belong to */ + int sz; /* Bytes of memory required to allocate the new cache */ - pCache = (PCache1 *)sqlite3_malloc(sizeof(PCache1)); + /* + ** The seperateCache variable is true if each PCache has its own private + ** PGroup. In other words, separateCache is true for mode (1) where no + ** mutexing is required. + ** + ** * Always use a unified cache (mode-2) if ENABLE_MEMORY_MANAGEMENT + ** + ** * Always use a unified cache in single-threaded applications + ** + ** * Otherwise (if multi-threaded and ENABLE_MEMORY_MANAGEMENT is off) + ** use separate caches (mode-1) + */ +#if defined(SQLITE_ENABLE_MEMORY_MANAGEMENT) || SQLITE_THREADSAFE==0 + const int separateCache = 0; +#else + int separateCache = sqlite3GlobalConfig.bCoreMutex>0; +#endif + + sz = sizeof(PCache1) + sizeof(PGroup)*separateCache; + pCache = (PCache1 *)sqlite3_malloc(sz); if( pCache ){ - memset(pCache, 0, sizeof(PCache1)); + memset(pCache, 0, sz); + if( separateCache ){ + pGroup = (PGroup*)&pCache[1]; + pGroup->mxPinned = 10; + }else{ + pGroup = &pcache1_g.grp; + } + pCache->pGroup = pGroup; pCache->szPage = szPage; pCache->bPurgeable = (bPurgeable ? 1 : 0); if( bPurgeable ){ pCache->nMin = 10; - pcache1EnterMutex(); - pcache1.nMinPage += pCache->nMin; - pcache1LeaveMutex(); + pcache1EnterMutex(pGroup); + pGroup->nMinPage += pCache->nMin; + pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; + pcache1LeaveMutex(pGroup); } } return (sqlite3_pcache *)pCache; @@ -473,11 +598,14 @@ static sqlite3_pcache *pcache1Create(int szPage, int bPurgeable){ static void pcache1Cachesize(sqlite3_pcache *p, int nMax){ PCache1 *pCache = (PCache1 *)p; if( pCache->bPurgeable ){ - pcache1EnterMutex(); - pcache1.nMaxPage += (nMax - pCache->nMax); + PGroup *pGroup = pCache->pGroup; + pcache1EnterMutex(pGroup); + pGroup->nMaxPage += (nMax - pCache->nMax); + pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; pCache->nMax = nMax; - pcache1EnforceMaxPage(); - pcache1LeaveMutex(); + pCache->n90pct = pCache->nMax*9/10; + pcache1EnforceMaxPage(pGroup); + pcache1LeaveMutex(pGroup); } } @@ -486,9 +614,10 @@ static void pcache1Cachesize(sqlite3_pcache *p, int nMax){ */ static int pcache1Pagecount(sqlite3_pcache *p){ int n; - pcache1EnterMutex(); - n = ((PCache1 *)p)->nPage; - pcache1LeaveMutex(); + PCache1 *pCache = (PCache1*)p; + pcache1EnterMutex(pCache->pGroup); + n = pCache->nPage; + pcache1LeaveMutex(pCache->pGroup); return n; } @@ -516,14 +645,16 @@ static int pcache1Pagecount(sqlite3_pcache *p){ ** 2. If createFlag==0 and the page is not already in the cache, NULL is ** returned. ** -** 3. If createFlag is 1, and the page is not already in the cache, -** and if either of the following are true, return NULL: +** 3. If createFlag is 1, and the page is not already in the cache, then +** return NULL (do not allocate a new page) if any of the following +** conditions are true: ** ** (a) the number of pages pinned by the cache is greater than ** PCache1.nMax, or +** ** (b) the number of pages pinned by the cache is greater than ** the sum of nMax for all purgeable caches, less the sum of -** nMin for all other purgeable caches. +** nMin for all other purgeable caches, or ** ** 4. If none of the first three conditions apply and the cache is marked ** as purgeable, and if one of the following is true: @@ -535,6 +666,9 @@ static int pcache1Pagecount(sqlite3_pcache *p){ ** already equal to or greater than the sum of nMax for all ** purgeable caches, ** +** (c) The system is under memory pressure and wants to avoid +** unnecessary pages cache entry allocations +** ** then attempt to recycle a page from the LRU list. If it is the right ** size, return the recycled buffer. Otherwise, free the buffer and ** proceed to step 5. @@ -542,30 +676,50 @@ static int pcache1Pagecount(sqlite3_pcache *p){ ** 5. Otherwise, allocate and return a new page buffer. */ static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){ - unsigned int nPinned; + int nPinned; PCache1 *pCache = (PCache1 *)p; + PGroup *pGroup; PgHdr1 *pPage = 0; assert( pCache->bPurgeable || createFlag!=1 ); - pcache1EnterMutex(); - if( createFlag==1 ) sqlite3BeginBenignMalloc(); + assert( pCache->bPurgeable || pCache->nMin==0 ); + assert( pCache->bPurgeable==0 || pCache->nMin==10 ); + assert( pCache->nMin==0 || pCache->bPurgeable ); + pcache1EnterMutex(pGroup = pCache->pGroup); - /* Search the hash table for an existing entry. */ + /* 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); } + /* Step 2: Abort if no existing page is found and createFlag is 0 */ if( pPage || createFlag==0 ){ pcache1PinPage(pPage); goto fetch_out; } - /* Step 3 of header comment. */ + /* 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 */ nPinned = pCache->nPage - pCache->nRecyclable; + assert( nPinned>=0 ); + assert( pGroup->mxPinned == pGroup->nMaxPage + 10 - pGroup->nMinPage ); + assert( pCache->n90pct == pCache->nMax*9/10 ); if( createFlag==1 && ( - nPinned>=(pcache1.nMaxPage+pCache->nMin-pcache1.nMinPage) - || nPinned>=(pCache->nMax * 9 / 10) + nPinned>=pGroup->mxPinned + || nPinned>=(int)pCache->n90pct + || pcache1UnderMemoryPressure(pCache) )){ goto fetch_out; } @@ -574,18 +728,22 @@ static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){ goto fetch_out; } - /* Step 4. Try to recycle a page buffer if appropriate. */ - if( pCache->bPurgeable && pcache1.pLruTail && ( - (pCache->nPage+1>=pCache->nMax) || pcache1.nCurrentPage>=pcache1.nMaxPage + /* Step 4. Try to recycle a page. */ + if( pCache->bPurgeable && pGroup->pLruTail && ( + (pCache->nPage+1>=pCache->nMax) + || pGroup->nCurrentPage>=pGroup->nMaxPage + || pcache1UnderMemoryPressure(pCache) )){ - pPage = pcache1.pLruTail; + PCache1 *pOtherCache; + pPage = pGroup->pLruTail; pcache1RemoveFromHash(pPage); pcache1PinPage(pPage); - if( pPage->pCache->szPage!=pCache->szPage ){ + if( (pOtherCache = pPage->pCache)->szPage!=pCache->szPage ){ pcache1FreePage(pPage); pPage = 0; }else{ - pcache1.nCurrentPage -= (pPage->pCache->bPurgeable - pCache->bPurgeable); + pGroup->nCurrentPage -= + (pOtherCache->bPurgeable - pCache->bPurgeable); } } @@ -593,7 +751,11 @@ static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){ ** attempt to allocate a new one. */ if( !pPage ){ + if( createFlag==1 ) sqlite3BeginBenignMalloc(); + pcache1LeaveMutex(pGroup); pPage = pcache1AllocPage(pCache); + pcache1EnterMutex(pGroup); + if( createFlag==1 ) sqlite3EndBenignMalloc(); } if( pPage ){ @@ -612,8 +774,7 @@ fetch_out: if( pPage && iKey>pCache->iMaxKey ){ pCache->iMaxKey = iKey; } - if( createFlag==1 ) sqlite3EndBenignMalloc(); - pcache1LeaveMutex(); + pcache1LeaveMutex(pGroup); return (pPage ? PGHDR1_TO_PAGE(pPage) : 0); } @@ -626,37 +787,34 @@ fetch_out: static void pcache1Unpin(sqlite3_pcache *p, void *pPg, int reuseUnlikely){ PCache1 *pCache = (PCache1 *)p; PgHdr1 *pPage = PAGE_TO_PGHDR1(pCache, pPg); + PGroup *pGroup = pCache->pGroup; assert( pPage->pCache==pCache ); - pcache1EnterMutex(); + pcache1EnterMutex(pGroup); /* It is an error to call this function if the page is already - ** part of the global LRU list. + ** part of the PGroup LRU list. */ assert( pPage->pLruPrev==0 && pPage->pLruNext==0 ); - assert( pcache1.pLruHead!=pPage && pcache1.pLruTail!=pPage ); + assert( pGroup->pLruHead!=pPage && pGroup->pLruTail!=pPage ); - if( reuseUnlikely || pcache1.nCurrentPage>pcache1.nMaxPage ){ + if( reuseUnlikely || pGroup->nCurrentPage>pGroup->nMaxPage ){ pcache1RemoveFromHash(pPage); pcache1FreePage(pPage); }else{ - /* Add the page to the global LRU list. Normally, the page is added to - ** the head of the list (last page to be recycled). However, if the - ** reuseUnlikely flag passed to this function is true, the page is added - ** to the tail of the list (first page to be recycled). - */ - if( pcache1.pLruHead ){ - pcache1.pLruHead->pLruPrev = pPage; - pPage->pLruNext = pcache1.pLruHead; - pcache1.pLruHead = pPage; + /* Add the page to the PGroup LRU list. */ + if( pGroup->pLruHead ){ + pGroup->pLruHead->pLruPrev = pPage; + pPage->pLruNext = pGroup->pLruHead; + pGroup->pLruHead = pPage; }else{ - pcache1.pLruTail = pPage; - pcache1.pLruHead = pPage; + pGroup->pLruTail = pPage; + pGroup->pLruHead = pPage; } pCache->nRecyclable++; } - pcache1LeaveMutex(); + pcache1LeaveMutex(pCache->pGroup); } /* @@ -675,7 +833,7 @@ static void pcache1Rekey( assert( pPage->iKey==iOld ); assert( pPage->pCache==pCache ); - pcache1EnterMutex(); + pcache1EnterMutex(pCache->pGroup); h = iOld%pCache->nHash; pp = &pCache->apHash[h]; @@ -692,7 +850,7 @@ static void pcache1Rekey( pCache->iMaxKey = iNew; } - pcache1LeaveMutex(); + pcache1LeaveMutex(pCache->pGroup); } /* @@ -704,12 +862,12 @@ static void pcache1Rekey( */ static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){ PCache1 *pCache = (PCache1 *)p; - pcache1EnterMutex(); + pcache1EnterMutex(pCache->pGroup); if( iLimit<=pCache->iMaxKey ){ pcache1TruncateUnsafe(pCache, iLimit); pCache->iMaxKey = iLimit-1; } - pcache1LeaveMutex(); + pcache1LeaveMutex(pCache->pGroup); } /* @@ -719,12 +877,15 @@ static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){ */ static void pcache1Destroy(sqlite3_pcache *p){ PCache1 *pCache = (PCache1 *)p; - pcache1EnterMutex(); + PGroup *pGroup = pCache->pGroup; + assert( pCache->bPurgeable || (pCache->nMax==0 && pCache->nMin==0) ); + pcache1EnterMutex(pGroup); pcache1TruncateUnsafe(pCache, 0); - pcache1.nMaxPage -= pCache->nMax; - pcache1.nMinPage -= pCache->nMin; - pcache1EnforceMaxPage(); - pcache1LeaveMutex(); + pGroup->nMaxPage -= pCache->nMax; + pGroup->nMinPage -= pCache->nMin; + pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; + pcache1EnforceMaxPage(pGroup); + pcache1LeaveMutex(pGroup); sqlite3_free(pCache->apHash); sqlite3_free(pCache); } @@ -763,16 +924,18 @@ void sqlite3PCacheSetDefault(void){ */ int sqlite3PcacheReleaseMemory(int nReq){ int nFree = 0; + assert( sqlite3_mutex_notheld(pcache1.grp.mutex) ); + assert( sqlite3_mutex_notheld(pcache1.mutex) ); if( pcache1.pStart==0 ){ PgHdr1 *p; - pcache1EnterMutex(); - while( (nReq<0 || nFreepLruNext){ + for(p=pcache1.grp.pLruHead; p; p=p->pLruNext){ nRecyclable++; } - *pnCurrent = pcache1.nCurrentPage; - *pnMax = pcache1.nMaxPage; - *pnMin = pcache1.nMinPage; + *pnCurrent = pcache1.grp.nCurrentPage; + *pnMax = pcache1.grp.nMaxPage; + *pnMin = pcache1.grp.nMinPage; *pnRecyclable = nRecyclable; } #endif diff --git a/src/pragma.c b/src/pragma.c index 0f3ce3c5..31985438 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -35,7 +35,7 @@ static u8 getSafetyLevel(const char *z){ static const u8 iValue[] = {1, 0, 0, 0, 1, 1, 2}; int i, n; if( sqlite3Isdigit(*z) ){ - return (u8)atoi(z); + return (u8)sqlite3Atoi(z); } n = sqlite3Strlen30(z); for(i=0; i=0&&i<=2)?i:0); } #endif /* ifndef SQLITE_OMIT_AUTOVACUUM */ @@ -172,6 +172,7 @@ static int flagPragma(Parse *pParse, const char *zLeft, const char *zRight){ { "empty_result_callbacks", SQLITE_NullCallback }, { "legacy_file_format", SQLITE_LegacyFileFmt }, { "fullfsync", SQLITE_FullFSync }, + { "checkpoint_fullfsync", SQLITE_CkptFullFSync }, { "reverse_unordered_selects", SQLITE_ReverseOrder }, #ifndef SQLITE_OMIT_AUTOMATIC_INDEX { "automatic_index", SQLITE_AutoIndex }, @@ -383,7 +384,7 @@ void sqlite3Pragma( sqlite3VdbeChangeP1(v, addr+1, iDb); sqlite3VdbeChangeP1(v, addr+6, SQLITE_DEFAULT_CACHE_SIZE); }else{ - int size = atoi(zRight); + int size = sqlite3Atoi(zRight); if( size<0 ) size = -size; sqlite3BeginWriteOperation(pParse, 0, iDb); sqlite3VdbeAddOp2(v, OP_Integer, size, 1); @@ -412,35 +413,13 @@ void sqlite3Pragma( /* Malloc may fail when setting the page-size, as there is an internal ** buffer that the pager module resizes using sqlite3_realloc(). */ - db->nextPagesize = atoi(zRight); + db->nextPagesize = sqlite3Atoi(zRight); if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize, -1, 0) ){ db->mallocFailed = 1; } } }else - /* - ** PRAGMA [database.]max_page_count - ** PRAGMA [database.]max_page_count=N - ** - ** The first form reports the current setting for the - ** maximum number of pages in the database file. The - ** second form attempts to change this setting. Both - ** forms return the current setting. - */ - if( sqlite3StrICmp(zLeft,"max_page_count")==0 ){ - Btree *pBt = pDb->pBt; - int newMax = 0; - assert( pBt!=0 ); - if( zRight ){ - newMax = atoi(zRight); - } - if( ALWAYS(pBt) ){ - newMax = sqlite3BtreeMaxPageCount(pBt, newMax); - } - returnSingleInt(pParse, "max_page_count", newMax); - }else - /* ** PRAGMA [database.]secure_delete ** PRAGMA [database.]secure_delete=ON/OFF @@ -467,19 +446,33 @@ void sqlite3Pragma( }else /* + ** PRAGMA [database.]max_page_count + ** PRAGMA [database.]max_page_count=N + ** + ** The first form reports the current setting for the + ** maximum number of pages in the database file. The + ** second form attempts to change this setting. Both + ** forms return the current setting. + ** ** PRAGMA [database.]page_count ** ** Return the number of pages in the specified database. */ - if( sqlite3StrICmp(zLeft,"page_count")==0 ){ + if( sqlite3StrICmp(zLeft,"page_count")==0 + || sqlite3StrICmp(zLeft,"max_page_count")==0 + ){ int iReg; if( sqlite3ReadSchema(pParse) ) goto pragma_out; sqlite3CodeVerifySchema(pParse, iDb); iReg = ++pParse->nMem; - sqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg); + if( zLeft[0]=='p' ){ + sqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg); + }else{ + sqlite3VdbeAddOp3(v, OP_MaxPgcnt, iDb, iReg, sqlite3Atoi(zRight)); + } sqlite3VdbeAddOp2(v, OP_ResultRow, iReg, 1); sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "page_count", SQLITE_STATIC); + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLeft, SQLITE_TRANSIENT); }else /* @@ -587,7 +580,7 @@ void sqlite3Pragma( Pager *pPager = sqlite3BtreePager(pDb->pBt); i64 iLimit = -2; if( zRight ){ - sqlite3Atoi64(zRight, &iLimit); + sqlite3Atoi64(zRight, &iLimit, 1000000, SQLITE_UTF8); if( iLimit<-1 ) iLimit = -1; } iLimit = sqlite3PagerJournalSizeLimit(pPager, iLimit); @@ -701,7 +694,7 @@ void sqlite3Pragma( if( !zRight ){ returnSingleInt(pParse, "cache_size", pDb->pSchema->cache_size); }else{ - int size = atoi(zRight); + int size = sqlite3Atoi(zRight); if( size<0 ) size = -size; pDb->pSchema->cache_size = size; sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size); @@ -1094,7 +1087,7 @@ void sqlite3Pragma( /* Set the maximum error count */ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; if( zRight ){ - mxErr = atoi(zRight); + sqlite3GetInt32(zRight, &mxErr); if( mxErr<=0 ){ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; } @@ -1351,7 +1344,7 @@ void sqlite3Pragma( }; int addr = sqlite3VdbeAddOpList(v, ArraySize(setCookie), setCookie); sqlite3VdbeChangeP1(v, addr, iDb); - sqlite3VdbeChangeP1(v, addr+1, atoi(zRight)); + sqlite3VdbeChangeP1(v, addr+1, sqlite3Atoi(zRight)); sqlite3VdbeChangeP1(v, addr+2, iDb); sqlite3VdbeChangeP2(v, addr+2, iCookie); }else{ @@ -1412,8 +1405,7 @@ void sqlite3Pragma( */ if( sqlite3StrICmp(zLeft, "wal_autocheckpoint")==0 ){ if( zRight ){ - int nAuto = atoi(zRight); - sqlite3_wal_autocheckpoint(db, nAuto); + sqlite3_wal_autocheckpoint(db, sqlite3Atoi(zRight)); } returnSingleInt(pParse, "wal_autocheckpoint", db->xWalCallback==sqlite3WalDefaultHook ? @@ -1503,7 +1495,8 @@ void sqlite3Pragma( #ifndef SQLITE_OMIT_PAGER_PRAGMAS if( db->autoCommit ){ sqlite3BtreeSetSafetyLevel(pDb->pBt, pDb->safety_level, - (db->flags&SQLITE_FullFSync)!=0); + (db->flags&SQLITE_FullFSync)!=0, + (db->flags&SQLITE_CkptFullFSync)!=0); } #endif pragma_out: diff --git a/src/prepare.c b/src/prepare.c index 74accd76..62a33b67 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -79,7 +79,7 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ assert( db->init.busy ); db->init.iDb = iDb; - db->init.newTnum = atoi(argv[1]); + db->init.newTnum = sqlite3Atoi(argv[1]); db->init.orphanTrigger = 0; TESTONLY(rcp = ) sqlite3_prepare(db, argv[2], -1, &pStmt, 0); rc = db->errCode; @@ -628,13 +628,13 @@ static int sqlite3Prepare( if( rc==SQLITE_OK && pParse->pVdbe && pParse->explain ){ static const char * const azColName[] = { "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", - "order", "from", "detail" + "selectid", "order", "from", "detail" }; int iFirst, mx; if( pParse->explain==2 ){ - sqlite3VdbeSetNumCols(pParse->pVdbe, 3); + sqlite3VdbeSetNumCols(pParse->pVdbe, 4); iFirst = 8; - mx = 11; + mx = 12; }else{ sqlite3VdbeSetNumCols(pParse->pVdbe, 8); iFirst = 0; @@ -784,7 +784,7 @@ int sqlite3_prepare_v2( */ static int sqlite3Prepare16( sqlite3 *db, /* Database handle. */ - const void *zSql, /* UTF-8 encoded SQL statement. */ + const void *zSql, /* UTF-16 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ int saveSqlFlag, /* True to save SQL text into the sqlite3_stmt */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ @@ -834,7 +834,7 @@ static int sqlite3Prepare16( */ int sqlite3_prepare16( sqlite3 *db, /* Database handle. */ - const void *zSql, /* UTF-8 encoded SQL statement. */ + 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 */ @@ -846,7 +846,7 @@ int sqlite3_prepare16( } int sqlite3_prepare16_v2( sqlite3 *db, /* Database handle. */ - const void *zSql, /* UTF-8 encoded SQL statement. */ + 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 */ diff --git a/src/printf.c b/src/printf.c index da2fdf61..c88bb300 100644 --- a/src/printf.c +++ b/src/printf.c @@ -763,6 +763,7 @@ void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){ return; } }else{ + char *zOld = (p->zText==p->zBase ? 0 : p->zText); i64 szNew = p->nChar; szNew += N + 1; if( szNew > p->mxAlloc ){ @@ -773,13 +774,12 @@ void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){ p->nAlloc = (int)szNew; } if( p->useMalloc==1 ){ - zNew = sqlite3DbMallocRaw(p->db, p->nAlloc ); + zNew = sqlite3DbRealloc(p->db, zOld, p->nAlloc); }else{ - zNew = sqlite3_malloc(p->nAlloc); + zNew = sqlite3_realloc(zOld, p->nAlloc); } if( zNew ){ - memcpy(zNew, p->zText, p->nChar); - sqlite3StrAccumReset(p); + if( zOld==0 ) memcpy(zNew, p->zText, p->nChar); p->zText = zNew; }else{ p->mallocFailed = 1; @@ -934,21 +934,28 @@ char *sqlite3_mprintf(const char *zFormat, ...){ ** current locale settings. This is important for SQLite because we ** are not able to use a "," as the decimal point in place of "." as ** specified by some locales. +** +** Oops: The first two arguments of sqlite3_snprintf() are backwards +** from the snprintf() standard. Unfortunately, it is too late to change +** this without breaking compatibility, so we just have to live with the +** mistake. +** +** sqlite3_vsnprintf() is the varargs version. */ +char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_list ap){ + StrAccum acc; + if( n<=0 ) return zBuf; + sqlite3StrAccumInit(&acc, zBuf, n, 0); + acc.useMalloc = 0; + sqlite3VXPrintf(&acc, 0, zFormat, ap); + return sqlite3StrAccumFinish(&acc); +} char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ char *z; va_list ap; - StrAccum acc; - - if( n<=0 ){ - return zBuf; - } - sqlite3StrAccumInit(&acc, zBuf, n, 0); - acc.useMalloc = 0; va_start(ap,zFormat); - sqlite3VXPrintf(&acc, 0, zFormat, ap); + z = sqlite3_vsnprintf(n, zBuf, zFormat, ap); va_end(ap); - z = sqlite3StrAccumFinish(&acc); return z; } diff --git a/src/select.c b/src/select.c index 17184b67..6f64df8e 100644 --- a/src/select.c +++ b/src/select.c @@ -442,7 +442,6 @@ static void pushOntoSorter( sqlite3VdbeAddOp1(v, OP_Last, pOrderBy->iECursor); sqlite3VdbeAddOp1(v, OP_Delete, pOrderBy->iECursor); sqlite3VdbeJumpHere(v, addr2); - pSelect->iLimit = 0; } } @@ -491,11 +490,13 @@ static void codeDistinct( sqlite3ReleaseTempReg(pParse, r1); } +#ifndef SQLITE_OMIT_SUBQUERY /* ** Generate an error message when a SELECT is used within a subexpression ** (example: "a IN (SELECT * FROM table)") but it has more than 1 result -** column. We do this in a subroutine because the error occurs in multiple -** places. +** column. We do this in a subroutine because the error used to occur +** in multiple places. (The error only occurs in one place now, but we +** retain the subroutine to minimize code disruption.) */ static int checkForMultiColumnSelectError( Parse *pParse, /* Parse context. */ @@ -511,6 +512,7 @@ static int checkForMultiColumnSelectError( return 0; } } +#endif /* ** This routine generates the code for the inside of the inner loop @@ -590,10 +592,6 @@ static void selectInnerLoop( } } - if( checkForMultiColumnSelectError(pParse, pDest, pEList->nExpr) ){ - return; - } - switch( eDest ){ /* In this mode, write each query result to the key of the temporary ** table iParm. @@ -722,11 +720,11 @@ static void selectInnerLoop( #endif } - /* Jump to the end of the loop if the LIMIT is reached. + /* 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( p->iLimit ){ - assert( pOrderBy==0 ); /* If there is an ORDER BY, the call to - ** pushOntoSorter() would have cleared p->iLimit */ + if( pOrderBy==0 && p->iLimit ){ sqlite3VdbeAddOp3(v, OP_IfZero, p->iLimit, iBreak, -1); } } @@ -773,6 +771,88 @@ static KeyInfo *keyInfoFromExprList(Parse *pParse, ExprList *pList){ return pInfo; } +#ifndef SQLITE_OMIT_COMPOUND_SELECT +/* +** Name of the connection operator, used for error messages. +*/ +static const char *selectOpName(int id){ + char *z; + switch( id ){ + case TK_ALL: z = "UNION ALL"; break; + case TK_INTERSECT: z = "INTERSECT"; break; + case TK_EXCEPT: z = "EXCEPT"; break; + default: z = "UNION"; break; + } + return z; +} +#endif /* SQLITE_OMIT_COMPOUND_SELECT */ + +#ifndef SQLITE_OMIT_EXPLAIN +/* +** Unless an "EXPLAIN QUERY PLAN" command is being processed, this function +** is a no-op. Otherwise, it adds a single row of output to the EQP result, +** where the caption is of the form: +** +** "USE TEMP B-TREE FOR xxx" +** +** where xxx is one of "DISTINCT", "ORDER BY" or "GROUP BY". Exactly which +** is determined by the zUsage argument. +*/ +static void explainTempTable(Parse *pParse, const char *zUsage){ + if( pParse->explain==2 ){ + Vdbe *v = pParse->pVdbe; + char *zMsg = sqlite3MPrintf(pParse->db, "USE TEMP B-TREE FOR %s", zUsage); + sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC); + } +} + +/* +** Unless an "EXPLAIN QUERY PLAN" command is being processed, this function +** is a no-op. Otherwise, it adds a single row of output to the EQP result, +** where the caption is of one of the two forms: +** +** "COMPOSITE SUBQUERIES iSub1 and iSub2 (op)" +** "COMPOSITE SUBQUERIES iSub1 and iSub2 USING TEMP B-TREE (op)" +** +** where iSub1 and iSub2 are the integers passed as the corresponding +** function parameters, and op is the text representation of the parameter +** of the same name. The parameter "op" must be one of TK_UNION, TK_EXCEPT, +** TK_INTERSECT or TK_ALL. The first form is used if argument bUseTmp is +** false, or the second form if it is true. +*/ +static void explainComposite( + Parse *pParse, /* Parse context */ + int op, /* One of TK_UNION, TK_EXCEPT etc. */ + int iSub1, /* Subquery id 1 */ + int iSub2, /* Subquery id 2 */ + int bUseTmp /* True if a temp table was used */ +){ + assert( op==TK_UNION || op==TK_EXCEPT || op==TK_INTERSECT || op==TK_ALL ); + if( pParse->explain==2 ){ + Vdbe *v = pParse->pVdbe; + char *zMsg = sqlite3MPrintf( + pParse->db, "COMPOUND SUBQUERIES %d AND %d %s(%s)", iSub1, iSub2, + bUseTmp?"USING TEMP B-TREE ":"", selectOpName(op) + ); + sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC); + } +} + +/* +** Assign expression b to lvalue a. A second, no-op, version of this macro +** is provided when SQLITE_OMIT_EXPLAIN is defined. This allows the code +** in sqlite3Select() to assign values to structure member variables that +** only exist if SQLITE_OMIT_EXPLAIN is not defined without polluting the +** code with #ifndef directives. +*/ +# define explainSetInteger(a, b) a = b + +#else +/* No-op versions of the explainXXX() functions and macros. */ +# define explainTempTable(y,z) +# define explainComposite(v,w,x,y,z) +# define explainSetInteger(y,z) +#endif /* ** If the inner loop was generated using a non-null pOrderBy argument, @@ -861,10 +941,6 @@ static void generateSortTail( sqlite3ReleaseTempReg(pParse, regRow); sqlite3ReleaseTempReg(pParse, regRowid); - /* LIMIT has been implemented by the pushOntoSorter() routine. - */ - assert( p->iLimit==0 ); - /* The bottom of the loop */ sqlite3VdbeResolveLabel(v, addrContinue); @@ -1124,22 +1200,6 @@ static void generateColumnNames( generateColumnTypes(pParse, pTabList, pEList); } -#ifndef SQLITE_OMIT_COMPOUND_SELECT -/* -** Name of the connection operator, used for error messages. -*/ -static const char *selectOpName(int id){ - char *z; - switch( id ){ - case TK_ALL: z = "UNION ALL"; break; - case TK_INTERSECT: z = "INTERSECT"; break; - case TK_EXCEPT: z = "EXCEPT"; break; - default: z = "UNION"; break; - } - return z; -} -#endif /* SQLITE_OMIT_COMPOUND_SELECT */ - /* ** Given a an expression list (which is really the list of expressions ** that form the result set of a SELECT statement) compute appropriate @@ -1302,6 +1362,7 @@ Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){ assert( db->lookaside.bEnabled==0 ); pTab->nRef = 1; pTab->zName = 0; + pTab->nRowEst = 1000000; selectColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol); selectAddColumnTypeAndCollation(pParse, pTab->nCol, pTab->aCol, pSelect); pTab->iPKey = -1; @@ -1372,6 +1433,8 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ VdbeComment((v, "LIMIT counter")); if( n==0 ){ sqlite3VdbeAddOp2(v, OP_Goto, 0, iBreak); + }else{ + if( p->nSelectRow > (double)n ) p->nSelectRow = (double)n; } }else{ sqlite3ExprCode(pParse, p->pLimit, iLimit); @@ -1472,6 +1535,10 @@ static int multiSelect( 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 */ +#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. @@ -1503,6 +1570,7 @@ static int multiSelect( if( dest.eDest==SRT_EphemTab ){ assert( p->pEList ); sqlite3VdbeAddOp2(v, OP_OpenEphemeral, dest.iParm, p->pEList->nExpr); + sqlite3VdbeChangeP5(v, BTREE_UNORDERED); dest.eDest = SRT_Table; } @@ -1528,9 +1596,11 @@ static int multiSelect( switch( p->op ){ case TK_ALL: { int addr = 0; + int nLimit; assert( !pPrior->pLimit ); pPrior->pLimit = p->pLimit; pPrior->pOffset = p->pOffset; + explainSetInteger(iSub1, pParse->iNextSelectId); rc = sqlite3Select(pParse, pPrior, &dest); p->pLimit = 0; p->pOffset = 0; @@ -1544,10 +1614,18 @@ static int multiSelect( addr = sqlite3VdbeAddOp1(v, OP_IfZero, p->iLimit); VdbeComment((v, "Jump ahead if LIMIT reached")); } + explainSetInteger(iSub2, pParse->iNextSelectId); rc = sqlite3Select(pParse, p, &dest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; p->pPrior = pPrior; + p->nSelectRow += pPrior->nSelectRow; + if( pPrior->pLimit + && sqlite3ExprIsInteger(pPrior->pLimit, &nLimit) + && p->nSelectRow > (double)nLimit + ){ + p->nSelectRow = (double)nLimit; + } if( addr ){ sqlite3VdbeJumpHere(v, addr); } @@ -1591,6 +1669,7 @@ static int multiSelect( */ assert( !pPrior->pOrderBy ); sqlite3SelectDestInit(&uniondest, priorOp, unionTab); + explainSetInteger(iSub1, pParse->iNextSelectId); rc = sqlite3Select(pParse, pPrior, &uniondest); if( rc ){ goto multi_select_end; @@ -1610,6 +1689,7 @@ static int multiSelect( pOffset = p->pOffset; p->pOffset = 0; uniondest.eDest = op; + explainSetInteger(iSub2, pParse->iNextSelectId); rc = sqlite3Select(pParse, p, &uniondest); testcase( rc!=SQLITE_OK ); /* Query flattening in sqlite3Select() might refill p->pOrderBy. @@ -1618,6 +1698,7 @@ static int multiSelect( pDelete = p->pPrior; p->pPrior = pPrior; p->pOrderBy = 0; + if( p->op==TK_UNION ) p->nSelectRow += pPrior->nSelectRow; sqlite3ExprDelete(db, p->pLimit); p->pLimit = pLimit; p->pOffset = pOffset; @@ -1675,6 +1756,7 @@ static int multiSelect( /* Code the SELECTs to our left into temporary table "tab1". */ sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); + explainSetInteger(iSub1, pParse->iNextSelectId); rc = sqlite3Select(pParse, pPrior, &intersectdest); if( rc ){ goto multi_select_end; @@ -1691,10 +1773,12 @@ static int multiSelect( pOffset = p->pOffset; p->pOffset = 0; intersectdest.iParm = tab2; + explainSetInteger(iSub2, pParse->iNextSelectId); rc = sqlite3Select(pParse, p, &intersectdest); testcase( rc!=SQLITE_OK ); pDelete = p->pPrior; p->pPrior = pPrior; + if( p->nSelectRow>pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow; sqlite3ExprDelete(db, p->pLimit); p->pLimit = pLimit; p->pOffset = pOffset; @@ -1727,6 +1811,8 @@ static int multiSelect( } } + explainComposite(pParse, p->op, iSub1, iSub2, p->op!=TK_ALL); + /* Compute collating sequences used by ** temporary tables needed to implement the compound select. ** Attach the KeyInfo structure to all temporary tables. @@ -2070,6 +2156,10 @@ static int multiSelectOrderBy( ExprList *pOrderBy; /* The ORDER BY clause */ int nOrderBy; /* Number of terms in the ORDER BY clause */ int *aPermute; /* Mapping from ORDER BY terms to result set columns */ +#ifndef SQLITE_OMIT_EXPLAIN + int iSub1; /* EQP id of left-hand query */ + int iSub2; /* EQP id of right-hand query */ +#endif assert( p->pOrderBy!=0 ); assert( pKeyDup==0 ); /* "Managed" code needs this. Ticket #3382. */ @@ -2181,7 +2271,6 @@ static int multiSelectOrderBy( /* Separate the left and the right query from one another */ p->pPrior = 0; - pPrior->pRightmost = 0; sqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER"); if( pPrior->pPrior==0 ){ sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER"); @@ -2224,6 +2313,7 @@ static int multiSelectOrderBy( */ VdbeNoopComment((v, "Begin coroutine for left SELECT")); pPrior->iLimit = regLimitA; + explainSetInteger(iSub1, pParse->iNextSelectId); sqlite3Select(pParse, pPrior, &destA); sqlite3VdbeAddOp2(v, OP_Integer, 1, regEofA); sqlite3VdbeAddOp1(v, OP_Yield, regAddrA); @@ -2238,6 +2328,7 @@ static int multiSelectOrderBy( savedOffset = p->iOffset; p->iLimit = regLimitB; p->iOffset = 0; + explainSetInteger(iSub2, pParse->iNextSelectId); sqlite3Select(pParse, p, &destB); p->iLimit = savedLimit; p->iOffset = savedOffset; @@ -2274,6 +2365,7 @@ static int multiSelectOrderBy( sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); sqlite3VdbeAddOp1(v, OP_Yield, regAddrB); sqlite3VdbeAddOp2(v, OP_Goto, 0, addrEofA); + p->nSelectRow += pPrior->nSelectRow; } /* Generate a subroutine to run when the results from select B @@ -2281,6 +2373,7 @@ static int multiSelectOrderBy( */ 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); @@ -2368,6 +2461,7 @@ static int multiSelectOrderBy( /*** TBD: Insert subroutine calls to close cursors on incomplete **** subqueries ****/ + explainComposite(pParse, p->op, iSub1, iSub2, 0); return SQLITE_OK; } #endif @@ -3101,6 +3195,7 @@ static int selectExpander(Walker *pWalker, Select *p){ while( pSel->pPrior ){ pSel = pSel->pPrior; } selectColumnsFromExprList(pParse, pSel->pEList, &pTab->nCol, &pTab->aCol); pTab->iPKey = -1; + pTab->nRowEst = 1000000; pTab->tabFlags |= TF_Ephemeral; #endif }else{ @@ -3465,7 +3560,7 @@ static void updateAccumulator(Parse *pParse, AggInfo *pAggInfo){ if( pList ){ nArg = pList->nExpr; regAgg = sqlite3GetTempRange(pParse, nArg); - sqlite3ExprCodeExprList(pParse, pList, regAgg, 0); + sqlite3ExprCodeExprList(pParse, pList, regAgg, 1); }else{ nArg = 0; regAgg = 0; @@ -3594,6 +3689,11 @@ int sqlite3Select( int iEnd; /* Address of the end of the query */ sqlite3 *db; /* The database connection */ +#ifndef SQLITE_OMIT_EXPLAIN + int iRestoreSelectId = pParse->iSelectId; + pParse->iSelectId = pParse->iNextSelectId++; +#endif + db = pParse->db; if( p==0 || db->mallocFailed || pParse->nErr ){ return 1; @@ -3625,6 +3725,15 @@ int sqlite3Select( v = sqlite3GetVdbe(pParse); if( v==0 ) goto select_end; + /* If writing to memory or generating a set + ** only a single column may be output. + */ +#ifndef SQLITE_OMIT_SUBQUERY + if( checkForMultiColumnSelectError(pParse, pDest, pEList->nExpr) ){ + goto select_end; + } +#endif + /* Generate code for all sub-queries in the FROM clause */ #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) @@ -3656,8 +3765,10 @@ int sqlite3Select( }else{ sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); assert( pItem->isPopulated==0 ); + explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId); sqlite3Select(pParse, pSub, &dest); pItem->isPopulated = 1; + pItem->pTab->nRowEst = (unsigned)pSub->nSelectRow; } if( /*pParse->nErr ||*/ db->mallocFailed ){ goto select_end; @@ -3691,19 +3802,12 @@ int sqlite3Select( mxSelect = db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT]; if( mxSelect && cnt>mxSelect ){ sqlite3ErrorMsg(pParse, "too many terms in compound SELECT"); - return 1; + goto select_end; } } - return multiSelect(pParse, p, pDest); - } -#endif - - /* If writing to memory or generating a set - ** only a single column may be output. - */ -#ifndef SQLITE_OMIT_SUBQUERY - if( checkForMultiColumnSelectError(pParse, pDest, pEList->nExpr) ){ - goto select_end; + rc = multiSelect(pParse, p, pDest); + explainSetInteger(pParse->iSelectId, iRestoreSelectId); + return rc; } #endif @@ -3715,7 +3819,6 @@ int sqlite3Select( p->pGroupBy = sqlite3ExprListDup(db, p->pEList, 0); pGroupBy = p->pGroupBy; p->selFlags &= ~SF_Distinct; - isDistinct = 0; } /* If there is both a GROUP BY and an ORDER BY clause and they are @@ -3758,17 +3861,19 @@ int sqlite3Select( /* Set the limiter. */ iEnd = sqlite3VdbeMakeLabel(v); + p->nSelectRow = (double)LARGEST_INT64; computeLimitRegisters(pParse, p, iEnd); /* Open a virtual index to use for the distinct set. */ - if( isDistinct ){ + if( p->selFlags & SF_Distinct ){ KeyInfo *pKeyInfo; assert( isAgg || pGroupBy ); distinct = pParse->nTab++; pKeyInfo = keyInfoFromExprList(pParse, p->pEList); sqlite3VdbeAddOp4(v, OP_OpenEphemeral, distinct, 0, 0, (char*)pKeyInfo, P4_KEYINFO_HANDOFF); + sqlite3VdbeChangeP5(v, BTREE_UNORDERED); }else{ distinct = -1; } @@ -3780,6 +3885,7 @@ int sqlite3Select( */ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, &pOrderBy, 0); if( pWInfo==0 ) goto select_end; + if( pWInfo->nRowOut < p->nSelectRow ) p->nSelectRow = pWInfo->nRowOut; /* If sorting index that was created by a prior OP_OpenEphemeral ** instruction ended up not being needed, then change the OP_OpenEphemeral @@ -3824,6 +3930,9 @@ int sqlite3Select( for(k=pGroupBy->nExpr, pItem=pGroupBy->a; k>0; k--, pItem++){ pItem->iAlias = 0; } + if( p->nSelectRow>(double)100 ) p->nSelectRow = (double)100; + }else{ + p->nSelectRow = (double)1; } @@ -3920,6 +4029,9 @@ int sqlite3Select( int nCol; int nGroupBy; + explainTempTable(pParse, + isDistinct && !(p->selFlags&SF_Distinct)?"DISTINCT":"GROUP BY"); + groupBySort = 1; nGroupBy = pGroupBy->nExpr; nCol = nGroupBy + 1; @@ -4181,10 +4293,15 @@ int sqlite3Select( } /* endif aggregate query */ + if( distinct>=0 ){ + explainTempTable(pParse, "DISTINCT"); + } + /* 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); } @@ -4201,6 +4318,7 @@ int sqlite3Select( ** successful coding of the SELECT. */ select_end: + explainSetInteger(pParse->iSelectId, iRestoreSelectId); /* Identify column names if results of the SELECT are to be output. */ diff --git a/src/shell.c b/src/shell.c index c5aa40cf..33dc1ac3 100644 --- a/src/shell.c +++ b/src/shell.c @@ -38,10 +38,14 @@ # include #endif +#ifdef HAVE_EDITLINE +# include +#endif #if defined(HAVE_READLINE) && HAVE_READLINE==1 # include # include -#else +#endif +#if !defined(HAVE_EDITLINE) && (!defined(HAVE_READLINE) || HAVE_READLINE!=1) # define readline(p) local_getline(p,stdin) # define add_history(X) # define read_history(X) @@ -980,7 +984,7 @@ static int display_stats( 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 Allocations: %d (max %d)\n", iCur, iHiwtr); + fprintf(pArg->out, "Number of Outstanding Allocations: %d (max %d)\n", iCur, iHiwtr); /* ** Not currently used by the CLI. ** iHiwtr = iCur = -1; @@ -1019,6 +1023,12 @@ static int display_stats( 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); @@ -2538,6 +2548,7 @@ int main(int argc, char **argv){ /* 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. */ for(i=1; i0x7fff0000 ) szHeap = 0x7fff0000; +#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) + sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); +#endif } } if( i @@ -782,7 +818,8 @@ typedef struct sqlite3_mutex sqlite3_mutex; **
  • [SQLITE_OPEN_TRANSIENT_DB] **
  • [SQLITE_OPEN_SUBJOURNAL] **
  • [SQLITE_OPEN_MASTER_JOURNAL] -** +**
  • [SQLITE_OPEN_WAL] +** )^ ** ** The file I/O implementation can use the object type flags to ** change the way it deals with files. For example, an application @@ -801,10 +838,11 @@ typedef struct sqlite3_mutex sqlite3_mutex; ** ** ** The [SQLITE_OPEN_DELETEONCLOSE] flag means the file should be -** deleted when it is closed. The [SQLITE_OPEN_DELETEONCLOSE] -** will be set for TEMP databases, journals and for subjournals. +** deleted when it is closed. ^The [SQLITE_OPEN_DELETEONCLOSE] +** will be set for TEMP databases and their journals, transient +** databases, and subjournals. ** -** The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction +** ^The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction ** with the [SQLITE_OPEN_CREATE] flag, which are both directly ** analogous to the O_EXCL and O_CREAT flags of the POSIX open() ** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the @@ -813,7 +851,7 @@ typedef struct sqlite3_mutex sqlite3_mutex; ** It is not used to indicate the file should be opened ** for exclusive access. ** -** At least szOsFile bytes of memory are allocated by SQLite +** ^At least szOsFile bytes of memory are allocated by SQLite ** to hold the [sqlite3_file] structure passed as the third ** argument to xOpen. The xOpen method does not have to ** allocate the structure; it should just fill it in. Note that @@ -823,13 +861,13 @@ typedef struct sqlite3_mutex sqlite3_mutex; ** element will be valid after xOpen returns regardless of the success ** or failure of the xOpen call. ** -** The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS] +** ^The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS] ** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to ** test whether a file is readable and writable, or [SQLITE_ACCESS_READ] ** to test whether a file is at least readable. The file can be a ** directory. ** -** SQLite will always allocate at least mxPathname+1 bytes for the +** ^SQLite will always allocate at least mxPathname+1 bytes for the ** output buffer xFullPathname. The exact size of the output buffer ** is also passed as a parameter to both methods. If the output buffer ** is not large enough, [SQLITE_CANTOPEN] should be returned. Since this is @@ -843,10 +881,10 @@ typedef struct sqlite3_mutex sqlite3_mutex; ** of good-quality randomness into zOut. The return value is ** the actual number of bytes of randomness obtained. ** The xSleep() method causes the calling thread to sleep for at -** least the number of microseconds given. The xCurrentTime() +** least the number of microseconds given. ^The xCurrentTime() ** method returns a Julian Day Number for the current date and time as ** a floating point value. -** The xCurrentTimeInt64() method returns, as an integer, the Julian +** ^The xCurrentTimeInt64() method returns, as an integer, the Julian ** Day Number multipled by 86400000 (the number of milliseconds in ** a 24-hour day). ** ^SQLite will use the xCurrentTimeInt64() method to get the current @@ -1243,7 +1281,7 @@ struct sqlite3_mem_methods { **
      **
    • [sqlite3_memory_used()] **
    • [sqlite3_memory_highwater()] -**
    • [sqlite3_soft_heap_limit()] +**
    • [sqlite3_soft_heap_limit64()] **
    • [sqlite3_status()] **
    )^ ** ^Memory allocation statistics are enabled by default unless SQLite is @@ -1257,15 +1295,14 @@ struct sqlite3_mem_methods { ** aligned memory buffer from which the scrach 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. The sz parameter should be a few bytes -** larger than the actual scratch space required due to internal overhead. +** argument must be a multiple of 16. ** 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 one scratch buffer per thread. So -** N should be set to 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 scratch memory beyond -** what is provided by this configuration option, then +** ^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 +** scratch memory beyond what is provided by this configuration option, then ** [sqlite3_malloc()] will be used to obtain the memory needed. ** **
    SQLITE_CONFIG_PAGECACHE
    @@ -1285,8 +1322,7 @@ struct sqlite3_mem_methods { ** 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 implementation might use one or more of the N buffers to hold -** memory accounting information. The pointer in the first argument must +** The pointer in the first argument must ** be aligned to an 8-byte boundary or subsequent behavior of SQLite ** will be undefined. ** @@ -1415,8 +1451,14 @@ struct sqlite3_mem_methods { ** or equal to the product of the second and third arguments. The buffer ** must be aligned to an 8-byte boundary. ^If the second argument to ** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally -** rounded down to the next smaller -** multiple of 8. See also: [SQLITE_CONFIG_LOOKASIDE] +** rounded down to the next smaller multiple of 8. ^(The lookaside memory +** configuration for a database connection can only be changed when that +** connection is not currently using lookaside memory, or in other words +** when the "current value" returned by +** [sqlite3_db_status](D,[SQLITE_CONFIG_LOOKASIDE],...) is zero. +** Any attempt to change the lookaside memory configuration when lookaside +** memory is in use leaves the configuration unchanged and returns +** [SQLITE_BUSY].)^ ** ** */ @@ -1721,6 +1763,9 @@ int sqlite3_busy_timeout(sqlite3*, int ms); /* ** CAPI3REF: Convenience Routines For Running Queries ** +** This is a legacy interface that is preserved for backwards compatibility. +** Use of this interface is not recommended. +** ** Definition: A result table is memory data structure created by the ** [sqlite3_get_table()] interface. A result table records the ** complete query results from one or more queries. @@ -1741,7 +1786,7 @@ int sqlite3_busy_timeout(sqlite3*, int ms); ** It is not safe to pass a result table directly to [sqlite3_free()]. ** A result table should be deallocated using [sqlite3_free_table()]. ** -** As an example of the result table format, suppose a query result +** ^(As an example of the result table format, suppose a query result ** is as follows: ** **
    @@ -1765,7 +1810,7 @@ int sqlite3_busy_timeout(sqlite3*, int ms);
     **        azResult[5] = "28";
     **        azResult[6] = "Cindy";
     **        azResult[7] = "21";
    -** 
    +** )^ ** ** ^The sqlite3_get_table() function evaluates one or more ** semicolon-separated SQL statements in the zero-terminated UTF-8 @@ -1773,19 +1818,19 @@ int sqlite3_busy_timeout(sqlite3*, int ms); ** pointer given in its 3rd parameter. ** ** After the application has finished with the result from sqlite3_get_table(), -** it should pass the result table pointer to sqlite3_free_table() in order to +** it must pass the result table pointer to sqlite3_free_table() in order to ** release the memory that was malloced. Because of the way the ** [sqlite3_malloc()] happens within sqlite3_get_table(), the calling ** function must not try to call [sqlite3_free()] directly. Only ** [sqlite3_free_table()] is able to release the memory properly and safely. ** -** ^(The sqlite3_get_table() interface is implemented as a wrapper around +** The sqlite3_get_table() interface is implemented as a wrapper around ** [sqlite3_exec()]. The sqlite3_get_table() routine does not have access ** to any internal data structures of SQLite. It uses only the public ** 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()].)^ +** [sqlite3_errmsg()]. */ int sqlite3_get_table( sqlite3 *db, /* An open database */ @@ -1810,7 +1855,7 @@ void sqlite3_free_table(char **result); ** NULL pointer if [sqlite3_malloc()] is unable to allocate enough ** memory to hold the resulting string. ** -** ^(In sqlite3_snprintf() routine is similar to "snprintf()" from +** ^(The sqlite3_snprintf() routine is similar to "snprintf()" from ** the standard C library. The result is written into the ** buffer supplied as the second parameter whose size is given by ** the first parameter. Note that the order of the @@ -1829,6 +1874,8 @@ void sqlite3_free_table(char **result); ** the zero terminator. So the longest string that can be completely ** written will be n-1 characters. ** +** ^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 @@ -1892,6 +1939,7 @@ void sqlite3_free_table(char **result); char *sqlite3_mprintf(const char*,...); char *sqlite3_vmprintf(const char*, va_list); char *sqlite3_snprintf(int,char*,const char*, ...); +char *sqlite3_vsnprintf(int,char*,const char*, va_list); /* ** CAPI3REF: Memory Allocation Subsystem @@ -1937,7 +1985,9 @@ char *sqlite3_snprintf(int,char*,const char*, ...); ** is not freed. ** ** ^The memory returned by sqlite3_malloc() and sqlite3_realloc() -** is always aligned to at least an 8 byte boundary. +** 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 ** the SQLITE_OMIT_MEMORY_ALLOCATION which would cause the built-in @@ -2195,17 +2245,28 @@ SQLITE_EXPERIMENTAL void *sqlite3_profile(sqlite3*, /* ** CAPI3REF: Query Progress Callbacks ** -** ^This routine configures a callback function - the -** progress callback - that is invoked periodically during long -** running calls to [sqlite3_exec()], [sqlite3_step()] and -** [sqlite3_get_table()]. An example use for this +** ^The sqlite3_progress_handler(D,N,X,P) interface causes the callback +** function X to be invoked periodically during long running calls to +** [sqlite3_exec()], [sqlite3_step()] and [sqlite3_get_table()] for +** database connection D. An example use for this ** interface is to keep a GUI updated during a large query. ** +** ^The parameter P is passed through as the only parameter to the +** callback function X. ^The parameter N is the number of +** [virtual machine instructions] that are evaluated between successive +** invocations of the callback X. +** +** ^Only a single progress handler may be defined at one time per +** [database connection]; setting a new progress handler cancels the +** old one. ^Setting parameter X to NULL disables the progress handler. +** ^The progress handler is also disabled by setting N to a value less +** than 1. +** ** ^If the progress callback returns non-zero, the operation is ** interrupted. This feature can be used to implement a ** "Cancel" button on a GUI progress dialog box. ** -** The progress handler must not do anything that will modify +** The progress handler callback must not do anything that will modify ** 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. @@ -2256,7 +2317,7 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** case the database must already exist, otherwise an error is returned.)^ ** ** ^(
    [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]
    -**
    The database is opened for reading and writing, and is creates it if +**
    The database is opened for reading and writing, and is created if ** it does not already exist. This is the behavior that is always used for ** sqlite3_open() and sqlite3_open16().
    )^ ** @@ -2264,7 +2325,7 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** If the 3rd parameter to sqlite3_open_v2() is not one of the ** combinations shown above or one of the combinations shown above combined ** with the [SQLITE_OPEN_NOMUTEX], [SQLITE_OPEN_FULLMUTEX], -** [SQLITE_OPEN_SHAREDCACHE] and/or [SQLITE_OPEN_SHAREDCACHE] flags, +** [SQLITE_OPEN_SHAREDCACHE] and/or [SQLITE_OPEN_PRIVATECACHE] flags, ** then the behavior is undefined. ** ** ^If the [SQLITE_OPEN_NOMUTEX] flag is set, then the database connection @@ -2389,17 +2450,22 @@ typedef struct sqlite3_stmt sqlite3_stmt; ** [database connection] whose limit is to be set or queried. The ** second parameter is one of the [limit categories] that define a ** class of constructs to be size limited. The third parameter is the -** new limit for that construct. The function returns the old limit.)^ +** new limit for that construct.)^ ** ** ^If the new limit is a negative number, the limit is unchanged. -** ^(For the limit category of SQLITE_LIMIT_XYZ there is a +** ^(For each limit category SQLITE_LIMIT_NAME there is a ** [limits | hard upper bound] -** set by a compile-time C preprocessor macro named -** [limits | SQLITE_MAX_XYZ]. +** set at compile-time by a C preprocessor macro called +** [limits | SQLITE_MAX_NAME]. ** (The "_LIMIT_" in the name is changed to "_MAX_".))^ ** ^Attempts to increase a limit above its hard upper bound are ** silently truncated to the hard upper bound. ** +** ^Regardless of whether or not the limit was changed, the +** [sqlite3_limit()] interface returns the prior value of the limit. +** ^Hence, to find the current value of a limit without changing it, +** simply invoke this interface with the third parameter set to -1. +** ** Run-time limits are intended for use in applications that manage ** both their own internal database and also databases that are controlled ** by untrusted external sources. An example application might be a @@ -2428,7 +2494,7 @@ int sqlite3_limit(sqlite3*, int id, int newVal); ** **
    ** ^(
    SQLITE_LIMIT_LENGTH
    -**
    The maximum size of any string or BLOB or table row.
    )^ +**
    The maximum size of any string or BLOB or table row, in bytes.
    )^ ** ** ^(
    SQLITE_LIMIT_SQL_LENGTH
    **
    The maximum length of an SQL statement, in bytes.
    )^ @@ -2446,7 +2512,9 @@ int sqlite3_limit(sqlite3*, int id, int newVal); ** ** ^(
    SQLITE_LIMIT_VDBE_OP
    **
    The maximum number of instructions in a virtual machine program -** used to implement an SQL statement.
    )^ +** used to implement an SQL statement. This limit is not currently +** enforced, though that might be added in some future release of +** SQLite.)^ ** ** ^(
    SQLITE_LIMIT_FUNCTION_ARG
    **
    The maximum number of arguments on a function.
    )^ @@ -2459,8 +2527,7 @@ int sqlite3_limit(sqlite3*, int id, int newVal); ** [GLOB] operators.)^ ** ** ^(
    SQLITE_LIMIT_VARIABLE_NUMBER
    -**
    The maximum number of variables in an SQL statement that can -** be bound.
    )^ +**
    The maximum index number of any [parameter] in an SQL statement.)^ ** ** ^(
    SQLITE_LIMIT_TRIGGER_DEPTH
    **
    The maximum depth of recursion for triggers.
    )^ @@ -2532,12 +2599,7 @@ int sqlite3_limit(sqlite3*, int id, int newVal); **
  • ** ^If the database schema changes, instead of returning [SQLITE_SCHEMA] as it ** always used to do, [sqlite3_step()] will automatically recompile the SQL -** statement and try to run it again. ^If the schema has changed in -** a way that makes the statement no longer valid, [sqlite3_step()] will still -** return [SQLITE_SCHEMA]. But unlike the legacy behavior, [SQLITE_SCHEMA] is -** now a fatal error. Calling [sqlite3_prepare_v2()] again will not make the -** error go away. Note: use [sqlite3_errmsg()] to find the text -** of the parsing error that results in an [SQLITE_SCHEMA] return. +** statement and try to run it again. **
  • ** **
  • @@ -2550,11 +2612,16 @@ int sqlite3_limit(sqlite3*, int id, int newVal); **
  • ** **
  • -** ^If the value of a [parameter | host parameter] in the WHERE clause might -** change the query plan for a statement, then the statement may be -** automatically recompiled (as if there had been a schema change) on the first -** [sqlite3_step()] call following any change to the -** [sqlite3_bind_text | bindings] of the [parameter]. +** ^If the specific value bound to [parameter | host parameter] in the +** WHERE clause might influence the choice of query plan for a statement, +** then the statement will be automatically recompiled, as if there had been +** a schema change, on the first [sqlite3_step()] call following any change +** 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_STAT2] compile-time option is enabled. +** the **
  • ** */ @@ -2596,6 +2663,37 @@ int sqlite3_prepare16_v2( */ const char *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 +** and only if the [prepared statement] X makes no direct changes to +** the content of the database file. +** +** Note that [application-defined SQL functions] or +** [virtual tables] might change the database indirectly as a side effect. +** ^(For example, if an application defines a function "eval()" that +** calls [sqlite3_exec()], then the following SQL statement would +** change the database file through side-effects: +** +**
    +**    SELECT eval('DELETE FROM t1') FROM t2;
    +** 
    +** +** But because the [SELECT] statement does not change the database file +** directly, sqlite3_stmt_readonly() would still return true.)^ +** +** ^Transaction control statements such as [BEGIN], [COMMIT], [ROLLBACK], +** [SAVEPOINT], and [RELEASE] cause sqlite3_stmt_readonly() to return true, +** since the statements themselves do not actually modify the database but +** rather they control the timing of when other statements modify the +** 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. +*/ +int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); + /* ** CAPI3REF: Dynamically Typed Value Object ** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value} @@ -2621,7 +2719,7 @@ const char *sqlite3_sql(sqlite3_stmt *pStmt); ** then there is no distinction between protected and unprotected ** sqlite3_value objects and they can be used interchangeably. However, ** for maximum code portability it is recommended that applications -** still make the distinction between between protected and unprotected +** still make the distinction between protected and unprotected ** sqlite3_value objects even when not strictly required. ** ** ^The sqlite3_value objects that are passed as parameters into the @@ -2695,7 +2793,10 @@ typedef struct sqlite3_context sqlite3_context; ** ** ^The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and ** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or -** string after SQLite has finished with it. ^If the fifth argument is +** 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. +** ^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 @@ -2816,6 +2917,8 @@ int sqlite3_clear_bindings(sqlite3_stmt*); ** ^Return the number of columns in the result set returned by the ** [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()] */ int sqlite3_column_count(sqlite3_stmt *pStmt); @@ -2981,13 +3084,17 @@ const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** be the case that the same database connection is being used by two or ** more threads at the same moment in time. ** -** For all versions of SQLite up to and including 3.6.23.1, it was required -** after sqlite3_step() returned anything other than [SQLITE_ROW] that -** [sqlite3_reset()] be called before any subsequent invocation of -** sqlite3_step(). Failure to invoke [sqlite3_reset()] in this way would -** result in an [SQLITE_MISUSE] return from sqlite3_step(). But after -** version 3.6.23.1, sqlite3_step() began calling [sqlite3_reset()] -** automatically in this circumstance rather than returning [SQLITE_MISUSE]. +** For all versions of SQLite up to and including 3.6.23.1, a call to +** [sqlite3_reset()] was required after sqlite3_step() returned anything +** other than [SQLITE_ROW] before any subsequent invocation of +** sqlite3_step(). Failure to reset the prepared statement using +** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from +** sqlite3_step(). But after version 3.6.23.1, sqlite3_step() began +** calling [sqlite3_reset()] automatically in this circumstance rather +** than returning [SQLITE_MISUSE]. This is not considered a compatibility +** break because any application that ever receives an SQLITE_MISUSE error +** is broken by definition. The [SQLITE_OMIT_AUTORESET] compile-time option +** can be used to restore the legacy behavior. ** ** Goofy Interface Alert: In the legacy interface, the sqlite3_step() ** API always returns a generic error code, [SQLITE_ERROR], following any @@ -3006,8 +3113,14 @@ int sqlite3_step(sqlite3_stmt*); /* ** CAPI3REF: Number of columns in a result set ** -** ^The sqlite3_data_count(P) the number of columns in the -** of the result set of [prepared statement] P. +** ^The sqlite3_data_count(P) interface returns the number of columns in the +** current row of the result set of [prepared statement] P. +** ^If prepared statement P does not have results ready to return +** (via calls to the [sqlite3_column_int | sqlite3_column_*()] of +** interfaces) then sqlite3_data_count(P) returns 0. +** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer. +** +** See also: [sqlite3_column_count()] */ int sqlite3_data_count(sqlite3_stmt *pStmt); @@ -3087,18 +3200,26 @@ int sqlite3_data_count(sqlite3_stmt *pStmt); ** ^If the result is a numeric value then sqlite3_column_bytes() uses ** [sqlite3_snprintf()] to convert that value to a UTF-8 string and returns ** the number of bytes in that string. -** ^The value returned does not include the zero terminator at the end -** of the string. ^For clarity: the value returned is the number of +** ^If the result is NULL, then sqlite3_column_bytes() returns zero. +** +** ^If the result is a BLOB or UTF-16 string then the sqlite3_column_bytes16() +** routine returns the number of bytes in that BLOB or string. +** ^If the result is a UTF-8 string, then sqlite3_column_bytes16() converts +** the string to UTF-16 and then returns the number of bytes. +** ^If the result is a numeric value then sqlite3_column_bytes16() uses +** [sqlite3_snprintf()] to convert that value to a UTF-16 string and returns +** the number of bytes in that string. +** ^If the result is NULL, then sqlite3_column_bytes16() returns zero. +** +** ^The values returned by [sqlite3_column_bytes()] and +** [sqlite3_column_bytes16()] do not include the zero terminators at the end +** of the string. ^For clarity: the values returned by +** [sqlite3_column_bytes()] and [sqlite3_column_bytes16()] are the number of ** bytes in the string, not the number of characters. ** ** ^Strings returned by sqlite3_column_text() and sqlite3_column_text16(), ** even empty strings, are always zero terminated. ^The return -** value from sqlite3_column_blob() for a zero-length BLOB is an arbitrary -** pointer, possibly even a NULL pointer. -** -** ^The sqlite3_column_bytes16() routine is similar to sqlite3_column_bytes() -** but leaves the result in UTF-16 in native byte order instead of UTF-8. -** ^The zero terminator is not included in this count. +** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer. ** ** ^The object returned by [sqlite3_column_value()] is an ** [unprotected sqlite3_value] object. An unprotected sqlite3_value object @@ -3143,10 +3264,10 @@ int sqlite3_data_count(sqlite3_stmt *pStmt); ** used in the table for brevity and because they are familiar to most ** C programmers. ** -** ^Note that when type conversions occur, pointers returned by prior +** Note that when type conversions occur, pointers returned by prior ** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or ** sqlite3_column_text16() may be invalidated. -** ^(Type conversions and pointer invalidations might occur +** Type conversions and pointer invalidations might occur ** in the following cases: ** **
      @@ -3159,22 +3280,22 @@ int sqlite3_data_count(sqlite3_stmt *pStmt); **
    • The initial content is UTF-16 text and sqlite3_column_bytes() or ** sqlite3_column_text() is called. The content must be converted ** to UTF-8.
    • -**
    )^ +** ** ** ^Conversions between UTF-16be and UTF-16le are always done in place and do ** not invalidate a prior pointer, though of course the content of the buffer -** that the prior pointer points to will have been modified. Other kinds +** that the prior pointer references will have been modified. Other kinds ** of conversion are done in place when it is possible, but sometimes they ** are not possible and in those cases prior pointers are invalidated. ** -** ^(The safest and easiest to remember policy is to invoke these routines +** The safest and easiest to remember policy is to invoke these routines ** in one of the following ways: ** **
      **
    • sqlite3_column_text() followed by sqlite3_column_bytes()
    • **
    • sqlite3_column_blob() followed by sqlite3_column_bytes()
    • **
    • sqlite3_column_text16() followed by sqlite3_column_bytes16()
    • -**
    )^ +** ** ** In other words, you should call sqlite3_column_text(), ** sqlite3_column_blob(), or sqlite3_column_text16() first to force the result @@ -3212,17 +3333,26 @@ sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); ** CAPI3REF: Destroy A Prepared Statement Object ** ** ^The sqlite3_finalize() function is called to delete a [prepared statement]. -** ^If the statement was executed successfully or not executed at all, then -** SQLITE_OK is returned. ^If execution of the statement failed then an -** [error code] or [extended error code] is returned. +** ^If the most recent evaluation of the statement encountered no errors or +** or if the statement is never been evaluated, then sqlite3_finalize() returns +** SQLITE_OK. ^If the most recent evaluation of statement S failed, then +** sqlite3_finalize(S) returns the appropriate [error code] or +** [extended error code]. ** -** ^This routine can be called at any point during the execution of the -** [prepared statement]. ^If the virtual machine has not -** completed execution when this routine is called, that is like -** encountering an error or an [sqlite3_interrupt | interrupt]. -** ^Incomplete updates may be rolled back and transactions canceled, -** depending on the circumstances, and the -** [error code] returned will be [SQLITE_ABORT]. +** ^The sqlite3_finalize(S) routine can be called at any point during +** the life cycle of [prepared statement] S: +** before statement S is ever evaluated, after +** one or more calls to [sqlite3_reset()], or after any call +** to [sqlite3_step()] regardless of whether or not the statement has +** completed execution. +** +** ^Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op. +** +** The application must finalize every [prepared statement] in order to avoid +** 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. */ int sqlite3_finalize(sqlite3_stmt *pStmt); @@ -3258,23 +3388,25 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** KEYWORDS: {application-defined SQL function} ** KEYWORDS: {application-defined SQL functions} ** -** ^These two functions (collectively known as "function creation routines") +** ^These functions (collectively known as "function creation routines") ** are used to add SQL functions or aggregates or to redefine the behavior -** of existing SQL functions or aggregates. The only difference between the -** two is that the second parameter, the name of the (scalar) function or -** aggregate, is encoded in UTF-8 for sqlite3_create_function() and UTF-16 -** for sqlite3_create_function16(). +** of existing SQL functions or aggregates. The only differences between +** these routines are the text encoding expected for +** the the second parameter (the name of the function being created) +** and the presence or absence of a destructor callback for +** the application data pointer. ** ** ^The first parameter is the [database connection] to which the SQL ** function is to be added. ^If an application uses more than one database ** connection then application-defined SQL functions must be added ** to each database connection separately. ** -** The second parameter is the name of the SQL function to be created or -** redefined. ^The length of the name is limited to 255 bytes, exclusive of -** the zero-terminator. Note that the name length limit is in bytes, not -** characters. ^Any attempt to create a function with a longer name -** will result in [SQLITE_ERROR] being returned. +** ^The second parameter is the name of the SQL function to be created or +** redefined. ^The length of the name is limited to 255 bytes in a UTF-8 +** representation, exclusive of the zero-terminator. ^Note that the name +** length limit is in UTF-8 bytes, not characters nor UTF-16 bytes. +** ^Any attempt to create a function with a longer name +** will result in [SQLITE_MISUSE] being returned. ** ** ^The third parameter (nArg) ** is the number of arguments that the SQL function or @@ -3284,10 +3416,10 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** parameter is less than -1 or greater than 127 then the behavior is ** undefined. ** -** The fourth parameter, eTextRep, specifies what +** ^The fourth parameter, eTextRep, specifies what ** [SQLITE_UTF8 | text encoding] this SQL function prefers for -** its parameters. Any SQL function implementation should be able to work -** work with UTF-8, UTF-16le, or UTF-16be. But some implementations may be +** 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. @@ -3299,13 +3431,24 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** function can gain access to this pointer using [sqlite3_user_data()].)^ ** -** The seventh, eighth and ninth parameters, xFunc, xStep and xFinal, are +** ^The sixth, seventh and eighth parameters, xFunc, xStep and xFinal, are ** pointers to C-language functions that implement the SQL function or ** aggregate. ^A scalar SQL function requires an implementation of the xFunc -** callback only; NULL pointers should be passed as the xStep and xFinal +** callback only; NULL pointers must be passed as the xStep and xFinal ** parameters. ^An aggregate SQL function requires an implementation of xStep -** and xFinal and NULL should be passed for xFunc. ^To delete an existing -** SQL function or aggregate, pass NULL for all three function callbacks. +** and xFinal and NULL pointer must be passed for xFunc. ^To delete an existing +** SQL function or aggregate, pass NULL poiners for all three function +** callbacks. +** +** ^(If the ninth parameter to sqlite3_create_function_v2() is not NULL, +** then it is destructor for the application data pointer. +** The destructor is invoked when the function is deleted, either by being +** overloaded or when the database connection closes.)^ +** ^The destructor is also invoked if the call to +** sqlite3_create_function_v2() fails. +** ^When the destructor callback of the tenth parameter is invoked, it +** is passed a single argument which is a copy of the application data +** pointer which was the fifth parameter to sqlite3_create_function_v2(). ** ** ^It is permitted to register multiple implementations of the same ** functions with the same name but with either differing numbers of @@ -3321,11 +3464,6 @@ int sqlite3_reset(sqlite3_stmt *pStmt); ** between UTF8 and UTF16. ** ** ^Built-in functions may be overloaded by new application-defined functions. -** ^The first application-defined function with a given name overrides all -** built-in functions in the same [database connection] with the same name. -** ^Subsequent application-defined functions of the same name only override -** prior application-defined functions that are an exact match for the -** number of parameters and preferred encoding. ** ** ^An application-defined function is permitted to call other ** SQLite interfaces. However, such calls must not @@ -3352,6 +3490,17 @@ int sqlite3_create_function16( void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ); +int sqlite3_create_function_v2( + 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*), + void(*xDestroy)(void*) +); /* ** CAPI3REF: Text Encodings @@ -3395,7 +3544,7 @@ SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),void ** The xFunc (for scalar functions) or xStep (for aggregates) parameters ** to [sqlite3_create_function()] and [sqlite3_create_function16()] ** define callbacks that implement the SQL functions and aggregates. -** The 4th parameter to these callbacks is an array of pointers to +** The 3rd parameter to these callbacks is an array of pointers to ** [protected sqlite3_value] objects. There is one [sqlite3_value] object for ** each parameter to the SQL function. These routines are used to ** extract values from the [sqlite3_value] objects. @@ -3698,46 +3847,79 @@ void sqlite3_result_zeroblob(sqlite3_context*, int n); /* ** CAPI3REF: Define New Collating Sequences ** -** These functions are used to add new collation sequences to the -** [database connection] specified as the first argument. +** ^These functions add, remove, or modify a [collation] associated +** with the [database connection] specified as the first argument. ** -** ^The name of the new collation sequence is specified as a UTF-8 string +** ^The name of the collation is a UTF-8 string ** for sqlite3_create_collation() and sqlite3_create_collation_v2() -** and a UTF-16 string for sqlite3_create_collation16(). ^In all cases -** the name is passed as the second function argument. +** and a UTF-16 string in native byte order for sqlite3_create_collation16(). +** ^Collation names that compare equal according to [sqlite3_strnicmp()] are +** considered to be the same name. ** -** ^The third argument may be one of the constants [SQLITE_UTF8], -** [SQLITE_UTF16LE], or [SQLITE_UTF16BE], indicating that the user-supplied -** routine expects to be passed pointers to strings encoded using UTF-8, -** UTF-16 little-endian, or UTF-16 big-endian, respectively. ^The -** third argument might also be [SQLITE_UTF16] to indicate that the routine -** expects pointers to be UTF-16 strings in the native byte order, or the -** argument can be [SQLITE_UTF16_ALIGNED] if the -** the routine expects pointers to 16-bit word aligned strings -** of UTF-16 in the native byte order. +** ^(The third argument (eTextRep) must be one of the constants: +**
      +**
    • [SQLITE_UTF8], +**
    • [SQLITE_UTF16LE], +**
    • [SQLITE_UTF16BE], +**
    • [SQLITE_UTF16], or +**
    • [SQLITE_UTF16_ALIGNED]. +**
    )^ +** ^The eTextRep argument determines the encoding of strings passed +** to the collating function callback, xCallback. +** ^The [SQLITE_UTF16] and [SQLITE_UTF16_ALIGNED] values for eTextRep +** force strings to be UTF16 with native byte order. +** ^The [SQLITE_UTF16_ALIGNED] value for eTextRep forces strings to begin +** on an even byte address. ** -** A pointer to the user supplied routine must be passed as the fifth -** argument. ^If it is NULL, this is the same as deleting the collation -** sequence (so that SQLite cannot call it any more). -** ^Each time the application supplied function is invoked, it is passed -** as its first parameter a copy of the void* passed as the fourth argument -** to sqlite3_create_collation() or sqlite3_create_collation16(). +** ^The fourth argument, pArg, is a application data pointer that is passed +** through as the first argument to the collating function callback. ** -** ^The remaining arguments to the application-supplied routine are two strings, -** each represented by a (length, data) pair and encoded in the encoding -** that was passed as the third argument when the collation sequence was -** registered. The application defined collation routine should -** return negative, zero or positive if the first string is less than, -** equal to, or greater than the second string. i.e. (STRING1 - STRING2). +** ^The fifth argument, xCallback, is a pointer to the collating function. +** ^Multiple collating functions can be registered using the same name but +** with different eTextRep parameters and SQLite will use whichever +** function requires the least amount of data transformation. +** ^If the xCallback argument is NULL then the collating function is +** deleted. ^When all collating functions having the same name are deleted, +** that collation is no longer usable. +** +** ^The collating function callback is invoked with a copy of the pArg +** application data pointer and with two strings in the encoding specified +** by the eTextRep argument. The collating function must return an +** integer that is negative, zero, or positive +** if the first string is less than, equal to, or greater than the second, +** respectively. A collating function must alway return the same answer +** given the same inputs. If two or more collating functions are registered +** to the same collation name (using different eTextRep values) then all +** must give an equivalent answer when invoked with equivalent strings. +** The collating function must obey the following properties for all +** strings A, B, and C: +** +**
      +**
    1. If A==B then B==A. +**
    2. If A==B and B==C then A==C. +**
    3. If A<B THEN B>A. +**
    4. If A<B and B<C then A<C. +**
    +** +** If a collating function fails any of the above constraints and that +** collating function is registered and used, then the behavior of SQLite +** is undefined. ** ** ^The sqlite3_create_collation_v2() works like sqlite3_create_collation() -** except that it takes an extra argument which is a destructor for -** the collation. ^The destructor is called when the collation is -** destroyed and is passed a copy of the fourth parameter void* pointer -** of the sqlite3_create_collation_v2(). -** ^Collations are destroyed when they are overridden by later calls to the -** collation creation functions or when the [database connection] is closed -** using [sqlite3_close()]. +** with the addition that the xDestroy callback is invoked on pArg when +** the collating function is deleted. +** ^Collating functions are deleted when they are overridden by later +** calls to the collation creation functions or when the +** [database connection] is closed using [sqlite3_close()]. +** +** ^The xDestroy callback is not called if the +** sqlite3_create_collation_v2() function fails. Applications that invoke +** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should +** check the return code and dispose of the application data pointer +** themselves rather than expecting SQLite to deal with it for them. +** This is different from every other SQLite interface. The inconsistency +** is unfortunate but cannot be changed without breaking backwards +** compatibility. ** ** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()]. */ @@ -3745,14 +3927,14 @@ int sqlite3_create_collation( sqlite3*, const char *zName, int eTextRep, - void*, + void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); int sqlite3_create_collation_v2( sqlite3*, const char *zName, int eTextRep, - void*, + void *pArg, int(*xCompare)(void*,int,const void*,int,const void*), void(*xDestroy)(void*) ); @@ -3760,7 +3942,7 @@ int sqlite3_create_collation16( sqlite3*, const void *zName, int eTextRep, - void*, + void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); @@ -3849,16 +4031,19 @@ void sqlite3_activate_cerod( /* ** CAPI3REF: Suspend Execution For A Short Time ** -** ^The sqlite3_sleep() function causes the current thread to suspend execution +** The sqlite3_sleep() function causes the current thread to suspend execution ** for at least a number of milliseconds specified in its parameter. ** -** ^If the operating system does not support sleep requests with +** If the operating system does not support sleep requests with ** millisecond time resolution, then the time will be rounded up to -** the nearest second. ^The number of milliseconds of sleep actually +** the nearest second. The number of milliseconds of sleep actually ** requested from the operating system is returned. ** ** ^SQLite implements this interface by calling the xSleep() -** method of the default [sqlite3_vfs] object. +** 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. */ int sqlite3_sleep(int); @@ -4080,40 +4265,73 @@ int sqlite3_enable_shared_cache(int); ** pages to improve performance is an example of non-essential memory. ** ^sqlite3_release_memory() returns the number of bytes actually freed, ** which might be more or less than the amount requested. +** ^The sqlite3_release_memory() routine is a no-op returning zero +** if SQLite is not compiled with [SQLITE_ENABLE_MEMORY_MANAGEMENT]. */ int sqlite3_release_memory(int); /* ** CAPI3REF: Impose A Limit On Heap Size ** -** ^The sqlite3_soft_heap_limit() interface places a "soft" limit -** on the amount of heap memory that may be allocated by SQLite. -** ^If an internal allocation is requested that would exceed the -** soft heap limit, [sqlite3_release_memory()] is invoked one or -** more times to free up some space before the allocation is performed. +** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the +** soft limit on the amount of heap memory that may be allocated by SQLite. +** ^SQLite strives to keep heap memory utilization below the soft heap +** limit by reducing the number of pages held in the page cache +** as heap memory usages approaches the limit. +** ^The soft heap limit is "soft" because even though SQLite strives to stay +** below the limit, it will exceed the limit rather than generate +** an [SQLITE_NOMEM] error. In other words, the soft heap limit +** is advisory only. ** -** ^The limit is called "soft" because if [sqlite3_release_memory()] -** cannot free sufficient memory to prevent the limit from being exceeded, -** the memory is allocated anyway and the current operation proceeds. +** ^The return value from sqlite3_soft_heap_limit64() is the size of +** the soft heap limit prior to the call. ^If the argument N is negative +** then no change is made to the soft heap limit. Hence, the current +** size of the soft heap limit can be determined by invoking +** sqlite3_soft_heap_limit64() with a negative argument. ** -** ^A negative or zero value for N means that there is no soft heap limit and -** [sqlite3_release_memory()] will only be called when memory is exhausted. -** ^The default value for the soft heap limit is zero. +** ^If the argument N is zero then the soft heap limit is disabled. ** -** ^(SQLite makes a best effort to honor the soft heap limit. -** But if the soft heap limit cannot be honored, execution will -** continue without error or notification.)^ This is why the limit is -** called a "soft" limit. It is advisory only. +** ^(The soft heap limit is not enforced in the current implementation +** if one or more of following conditions are true: ** -** Prior to SQLite version 3.5.0, this routine only constrained the memory -** allocated by a single thread - the same thread in which this routine -** runs. Beginning with SQLite version 3.5.0, the soft heap limit is -** applied to all threads. The value specified for the soft heap limit -** is an upper bound on the total memory allocation for all threads. In -** version 3.5.0 there is no mechanism for limiting the heap usage for -** individual threads. +**
      +**
    • The soft heap limit is set to zero. +**
    • Memory accounting is disabled using a combination of the +** [sqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and +** the [SQLITE_DEFAULT_MEMSTATUS] compile-time option. +**
    • An alternative page cache implementation is specifed using +** [sqlite3_config]([SQLITE_CONFIG_PCACHE],...). +**
    • The page cache allocates from its own memory pool supplied +** by [sqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than +** from the heap. +**
    )^ +** +** Beginning with SQLite version 3.7.3, the soft heap limit is enforced +** regardless of whether or not the [SQLITE_ENABLE_MEMORY_MANAGEMENT] +** compile-time option is invoked. With [SQLITE_ENABLE_MEMORY_MANAGEMENT], +** the soft heap limit is enforced on every memory allocation. Without +** [SQLITE_ENABLE_MEMORY_MANAGEMENT], the soft heap limit is only enforced +** when memory is allocated by the page cache. Testing suggests that because +** the page cache is the predominate memory user in SQLite, most +** applications will achieve adequate soft heap limit enforcement without +** 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. */ -void sqlite3_soft_heap_limit(int); +sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); + +/* +** CAPI3REF: Deprecated Soft Heap Limit Interface +** DEPRECATED +** +** 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_DEPRECATED void sqlite3_soft_heap_limit(int N); + /* ** CAPI3REF: Extract Metadata About A Column Of A Table @@ -4237,34 +4455,47 @@ int sqlite3_load_extension( int sqlite3_enable_load_extension(sqlite3 *db, int onoff); /* -** CAPI3REF: Automatically Load An Extensions +** CAPI3REF: Automatically Load Statically Linked Extensions ** -** ^This API can be invoked at program startup in order to register -** one or more statically linked extensions that will be available -** to all new [database connections]. +** ^This interface causes the xEntryPoint() function to be invoked for +** each new [database connection] that is created. The idea here is that +** xEntryPoint() is the entry point for a statically linked SQLite extension +** that is to be automatically loaded into all new database connections. ** -** ^(This routine stores a pointer to the extension entry point -** in an array that is obtained from [sqlite3_malloc()]. That memory -** is deallocated by [sqlite3_reset_auto_extension()].)^ +** ^(Even though the function prototype shows that xEntryPoint() takes +** no arguments and returns void, SQLite invokes xEntryPoint() with three +** arguments and expects and integer result as if the signature of the +** entry point where as follows: ** -** ^This function registers an extension entry point that is -** automatically invoked whenever a new [database connection] -** is opened using [sqlite3_open()], [sqlite3_open16()], -** or [sqlite3_open_v2()]. -** ^Duplicate extensions are detected so calling this routine -** multiple times with the same extension is harmless. -** ^Automatic extensions apply across all threads. +**
    +**    int xEntryPoint(
    +**      sqlite3 *db,
    +**      const char **pzErrMsg,
    +**      const struct sqlite3_api_routines *pThunk
    +**    );
    +** 
    )^ +** +** If the xEntryPoint routine encounters an error, it should make *pzErrMsg +** point to an appropriate error message (obtained from [sqlite3_mprintf()]) +** and return an appropriate [error code]. ^SQLite ensures that *pzErrMsg +** is NULL before calling the xEntryPoint(). ^SQLite will invoke +** [sqlite3_free()] on *pzErrMsg after xEntryPoint() returns. ^If any +** xEntryPoint() returns an error, the [sqlite3_open()], [sqlite3_open16()], +** or [sqlite3_open_v2()] call that provoked the xEntryPoint() will fail. +** +** ^Calling sqlite3_auto_extension(X) with an entry point X that is already +** on the list of automatic extensions is a harmless no-op. ^No entry point +** will be called more than once for each database connection that is opened. +** +** See also: [sqlite3_reset_auto_extension()]. */ int sqlite3_auto_extension(void (*xEntryPoint)(void)); /* ** CAPI3REF: Reset Automatic Extension Loading ** -** ^(This function disables all previously registered automatic -** extensions. It undoes the effect of all prior -** [sqlite3_auto_extension()] calls.)^ -** -** ^This function disables automatic extensions in all threads. +** ^This interface disables all automatic extensions previously +** registered using [sqlite3_auto_extension()]. */ void sqlite3_reset_auto_extension(void); @@ -4444,7 +4675,9 @@ struct sqlite3_index_info { ** ^The sqlite3_create_module_v2() interface has a fifth parameter which ** is a pointer to a destructor for the pClientData. ^SQLite will ** invoke the destructor function (if it is not NULL) when SQLite -** no longer needs the pClientData pointer. ^The sqlite3_create_module() +** no longer needs the pClientData pointer. ^The destructor will also +** 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. */ @@ -4627,6 +4860,30 @@ int sqlite3_blob_open( sqlite3_blob **ppBlob ); +/* +** CAPI3REF: Move a BLOB Handle to a New Row +** +** ^This function is used to move an existing blob handle so that it points +** to a different row of the same database table. ^The new row is identified +** by the rowid value passed as the second argument. Only the row can be +** changed. ^The database, table and column on which the blob handle is open +** remain the same. Moving an existing blob handle to a new row can be +** faster than closing the existing handle and opening a new one. +** +** ^(The new row must meet the same criteria as for [sqlite3_blob_open()] - +** it must exist and there must be either a blob or text value stored in +** the nominated column.)^ ^If the new row is not present in the table, or if +** it does not contain a blob or text value, or if another error occurs, an +** SQLite error code is returned and the blob handle is considered aborted. +** ^All subsequent calls to [sqlite3_blob_read()], [sqlite3_blob_write()] or +** [sqlite3_blob_reopen()] on an aborted blob handle immediately return +** 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_EXPERIMENTAL int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); + /* ** CAPI3REF: Close A BLOB Handle ** @@ -4903,7 +5160,7 @@ void sqlite3_mutex_leave(sqlite3_mutex*); ** ** ^The xMutexInit method defined by this structure is invoked as ** part of system initialization by the sqlite3_initialize() function. -** ^The xMutexInit routine is calle by SQLite exactly once for each +** ^The xMutexInit routine is called by SQLite exactly once for each ** effective call to [sqlite3_initialize()]. ** ** ^The xMutexEnd method defined by this structure is invoked as @@ -5015,7 +5272,8 @@ int sqlite3_mutex_notheld(sqlite3_mutex*); #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 /* lru page list */ +#define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ +#define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ /* ** CAPI3REF: Retrieve the mutex for a database connection @@ -5034,7 +5292,7 @@ sqlite3_mutex *sqlite3_db_mutex(sqlite3*); ** ^The [sqlite3_file_control()] interface makes a direct call to the ** xFileControl method for the [sqlite3_io_methods] object associated ** with a particular database identified by the second argument. ^The -** name of the database "main" for the main database or "temp" for the +** name of the database is "main" for the main database or "temp" for the ** TEMP database, or the name that appears after the AS keyword for ** databases that are added using the [ATTACH] SQL command. ** ^A NULL pointer can be used in place of "main" to refer to the @@ -5044,6 +5302,12 @@ sqlite3_mutex *sqlite3_db_mutex(sqlite3*); ** the xFileControl method. ^The return value of the xFileControl ** method becomes the return value of this routine. ** +** ^The SQLITE_FCNTL_FILE_POINTER value for the op parameter causes +** a pointer to the underlying [sqlite3_file] object to be written into +** the space pointed to by the 4th parameter. ^The SQLITE_FCNTL_FILE_POINTER +** case is a short-circuit path which does not actually invoke the +** underlying sqlite3_io_methods.xFileControl method. +** ** ^If the second parameter (zDbName) does not match the name of any ** open database file, then SQLITE_ERROR is returned. ^This error ** code is not remembered and will not be recalled by [sqlite3_errcode()] @@ -5100,7 +5364,8 @@ int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_ISKEYWORD 16 #define SQLITE_TESTCTRL_PGHDRSZ 17 -#define SQLITE_TESTCTRL_LAST 17 +#define SQLITE_TESTCTRL_SCRATCHMALLOC 18 +#define SQLITE_TESTCTRL_LAST 18 /* ** CAPI3REF: SQLite Runtime Status @@ -5119,7 +5384,7 @@ int sqlite3_test_control(int op, ...); ** ^(Other parameters record only the highwater mark and not the current ** value. For these latter parameters nothing is written into *pCurrent.)^ ** -** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a +** ^The sqlite3_status() routine returns SQLITE_OK on success and a ** non-zero [error code] on failure. ** ** This routine is threadsafe but is not atomic. This routine can be @@ -5159,7 +5424,8 @@ int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); ** The value written into the *pCurrent parameter is undefined.)^ ** ** ^(
    SQLITE_STATUS_MALLOC_COUNT
    -**
    This parameter records the number of separate memory allocations.
    )^ +**
    This parameter records the number of separate memory allocations +** currently checked out.
    )^ ** ** ^(
    SQLITE_STATUS_PAGECACHE_USED
    **
    This parameter returns the number of pages used out of the @@ -5169,7 +5435,7 @@ int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); ** ** ^(
    SQLITE_STATUS_PAGECACHE_OVERFLOW
    **
    This parameter returns the number of bytes of page cache -** allocation which could not be statisfied by the [SQLITE_CONFIG_PAGECACHE] +** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE] ** buffer and where forced to overflow to [sqlite3_malloc()]. The ** returned value includes allocations that overflowed because they ** where too large (they were larger than the "sz" parameter to @@ -5192,7 +5458,7 @@ int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); ** ** ^(
    SQLITE_STATUS_SCRATCH_OVERFLOW
    **
    This parameter returns the number of bytes of scratch memory -** allocation which could not be statisfied by the [SQLITE_CONFIG_SCRATCH] +** allocation which could not be satisfied by the [SQLITE_CONFIG_SCRATCH] ** buffer and where forced to overflow to [sqlite3_malloc()]. The values ** returned include overflows because the requested allocation was too ** larger (that is, because the requested allocation was larger than the @@ -5241,6 +5507,9 @@ int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); ** the resetFlg is true, then the highest instantaneous value is ** reset back down to the current value. ** +** ^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()]. */ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); @@ -5262,6 +5531,28 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); **
    This parameter returns the number of lookaside memory slots currently ** checked out.
    )^ ** +** ^(
    SQLITE_DBSTATUS_LOOKASIDE_HIT
    +**
    This parameter returns the number malloc attempts that were +** satisfied using lookaside memory. Only the high-water value is meaningful; +** the current value is always zero. +** checked out.
    )^ +** +** ^(
    SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE
    +**
    This parameter returns the number malloc attempts that might have +** been satisfied using lookaside memory but failed due to the amount of +** memory requested being larger than the lookaside slot size. +** Only the high-water value is meaningful; +** the current value is always zero. +** checked out.
    )^ +** +** ^(
    SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL
    +**
    This parameter returns the number malloc attempts that might have +** been satisfied using lookaside memory but failed due to all lookaside +** memory already being in use. +** Only the high-water value is meaningful; +** the current value is always zero. +** checked out.
    )^ +** ** ^(
    SQLITE_DBSTATUS_CACHE_USED
    **
    This parameter returns the approximate number of of bytes of heap ** memory used by all pager caches associated with the database connection.)^ @@ -5284,11 +5575,14 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); **
    **
    */ -#define SQLITE_DBSTATUS_LOOKASIDE_USED 0 -#define SQLITE_DBSTATUS_CACHE_USED 1 -#define SQLITE_DBSTATUS_SCHEMA_USED 2 -#define SQLITE_DBSTATUS_STMT_USED 3 -#define SQLITE_DBSTATUS_MAX 3 /* Largest defined DBSTATUS */ +#define SQLITE_DBSTATUS_LOOKASIDE_USED 0 +#define SQLITE_DBSTATUS_CACHE_USED 1 +#define SQLITE_DBSTATUS_SCHEMA_USED 2 +#define SQLITE_DBSTATUS_STMT_USED 3 +#define SQLITE_DBSTATUS_LOOKASIDE_HIT 4 +#define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5 +#define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6 +#define SQLITE_DBSTATUS_MAX 6 /* Largest defined DBSTATUS */ /* @@ -5367,32 +5661,42 @@ typedef struct sqlite3_pcache sqlite3_pcache; ** ** ^(The [sqlite3_config]([SQLITE_CONFIG_PCACHE], ...) interface can ** register an alternative page cache implementation by passing in an -** instance of the sqlite3_pcache_methods structure.)^ The majority of the -** heap memory used by SQLite is used by the page cache to cache data read -** from, or ready to be written to, the database file. By implementing a -** custom page cache using this API, an application can control more -** precisely the amount of memory consumed by SQLite, the way in which +** instance of the sqlite3_pcache_methods structure.)^ +** In many applications, most of the heap memory allocated by +** SQLite is used for the page cache. +** By implementing a +** custom page cache using this API, an application can better control +** the amount of memory consumed by SQLite, the way in which ** that memory is allocated and released, and the policies used to ** determine exactly which parts of a database file are cached and for ** how long. ** +** The alternative page cache mechanism is an +** extreme measure that is only needed by the most demanding applications. +** The built-in page cache is recommended for most uses. +** ** ^(The contents of the sqlite3_pcache_methods structure are copied to an ** internal buffer by SQLite within the call to [sqlite3_config]. Hence ** the application may discard the parameter after the call to ** [sqlite3_config()] returns.)^ ** -** ^The xInit() method is called once for each call to [sqlite3_initialize()] +** ^(The xInit() method is called once for each effective +** call to [sqlite3_initialize()])^ ** (usually only once during the lifetime of the process). ^(The xInit() ** method is passed a copy of the sqlite3_pcache_methods.pArg value.)^ -** ^The xInit() method can set up up global structures and/or any mutexes +** The intent of the xInit() method is to set up global data structures ** required by the custom page cache implementation. +** ^(If the xInit() method is NULL, then the +** built-in default page cache is used instead of the application defined +** page cache.)^ ** -** ^The xShutdown() method is called from within [sqlite3_shutdown()], -** if the application invokes this API. It can be used to clean up +** ^The xShutdown() method is called by [sqlite3_shutdown()]. +** It can be used to clean up ** any outstanding resources before process shutdown, if required. +** ^The xShutdown() method may be NULL. ** -** ^SQLite holds a [SQLITE_MUTEX_RECURSIVE] mutex when it invokes -** the xInit method, so the xInit method need not be threadsafe. ^The +** ^SQLite automatically serializes calls to the xInit method, +** so the xInit method need not be threadsafe. ^The ** xShutdown method is only called from [sqlite3_shutdown()] so it does ** not need to be threadsafe either. All other methods must be threadsafe ** in multithreaded applications. @@ -5400,47 +5704,52 @@ typedef struct sqlite3_pcache sqlite3_pcache; ** ^SQLite will never invoke xInit() more than once without an intervening ** call to xShutdown(). ** -** ^The xCreate() method is used to construct a new cache instance. SQLite -** will typically create one cache instance for each open database file, +** ^SQLite invokes the xCreate() method to construct a new cache instance. +** SQLite will typically create one cache instance for each open database file, ** though this is not guaranteed. ^The ** first parameter, szPage, is the size in bytes of the pages that must ** be allocated by the cache. ^szPage will not be a power of two. ^szPage ** will the page size of the database file that is to be cached plus an -** increment (here called "R") of about 100 or 200. ^SQLite will use the +** increment (here called "R") of less than 250. SQLite will use the ** extra R bytes on each page to store metadata about the underlying ** database page on disk. The value of R depends ** on the SQLite version, the target platform, and how SQLite was compiled. -** ^R is constant for a particular build of SQLite. ^The second argument to +** ^(R is constant for a particular build of SQLite. Except, there are two +** distinct values of R when SQLite is compiled with the proprietary +** ZIPVFS extension.)^ ^The second argument to ** xCreate(), bPurgeable, is true if the cache being created will ** be used to cache database pages of a file stored on disk, or -** false if it is used for an in-memory database. ^The cache implementation +** false if it is used for an in-memory database. The cache implementation ** does not have to do anything special based with the value of bPurgeable; ** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will ** never invoke xUnpin() except to deliberately delete a page. -** ^In other words, a cache created with bPurgeable set to false will +** ^In other words, calls to xUnpin() on a cache with bPurgeable set to +** false will always have the "discard" flag set to true. +** ^Hence, a cache created with bPurgeable false will ** never contain any unpinned pages. ** ** ^(The xCachesize() method may be called at any time by SQLite to set the ** suggested maximum cache-size (number of pages stored by) the cache ** instance passed as the first argument. This is the value configured using -** the SQLite "[PRAGMA cache_size]" command.)^ ^As with the bPurgeable +** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable ** parameter, the implementation is not required to do anything with this ** value; it is advisory only. ** -** ^The xPagecount() method should return the number of pages currently -** stored in the cache. +** The xPagecount() method must return the number of pages currently +** stored in the cache, both pinned and unpinned. ** -** ^The xFetch() method is used to fetch a page and return a pointer to it. -** ^A 'page', in this context, is a buffer of szPage bytes aligned at an -** 8-byte boundary. ^The page to be fetched is determined by the key. ^The -** mimimum key value is 1. After it has been retrieved using xFetch, the page +** The xFetch() method locates a page in the cache and returns a pointer to +** the page, or a NULL pointer. +** A "page", in this context, means a buffer of szPage bytes aligned at an +** 8-byte boundary. The page to be fetched is determined by the key. ^The +** mimimum key value is 1. After it has been retrieved using xFetch, the page ** is considered to be "pinned". ** -** ^If the requested page is already in the page cache, then the page cache +** If the requested page is already in the page cache, then the page cache ** implementation must return a pointer to the page buffer with its content -** intact. ^(If the requested page is not already in the cache, then the -** behavior of the cache implementation is determined by the value of the -** createFlag parameter passed to xFetch, according to the following table: +** intact. If the requested page is not already in the cache, then the +** cache implementation should use the value of the createFlag +** parameter to help it determined what action to take: ** ** **
    createFlag Behaviour when page is not already in cache @@ -5449,36 +5758,35 @@ typedef struct sqlite3_pcache sqlite3_pcache; ** Otherwise return NULL. **
    2 Make every effort to allocate a new page. Only return ** NULL if allocating a new page is effectively impossible. -**
    )^ +** ** -** SQLite will normally invoke xFetch() with a createFlag of 0 or 1. If -** a call to xFetch() with createFlag==1 returns NULL, then SQLite will +** ^(SQLite will normally invoke xFetch() with a createFlag of 0 or 1. SQLite +** will only use a createFlag of 2 after a prior call with a createFlag of 1 +** failed.)^ In between the to xFetch() calls, SQLite may ** attempt to unpin one or more cache pages by spilling the content of -** pinned pages to disk and synching the operating system disk cache. After -** attempting to unpin pages, the xFetch() method will be invoked again with -** a createFlag of 2. +** pinned pages to disk and synching the operating system disk cache. ** ** ^xUnpin() is called by SQLite with a pointer to a currently pinned page -** as its second argument. ^(If the third parameter, discard, is non-zero, -** then the page should be evicted from the cache. In this case SQLite -** assumes that the next time the page is retrieved from the cache using -** the xFetch() method, it will be zeroed.)^ ^If the discard parameter is -** zero, then the page is considered to be unpinned. ^The cache implementation +** as its second argument. If the third parameter, discard, is non-zero, +** then the page must be evicted from the cache. +** ^If the discard parameter is +** zero, then the page may be discarded or retained at the discretion of +** page cache implementation. ^The page cache implementation ** may choose to evict unpinned pages at any time. ** -** ^(The cache is not required to perform any reference counting. A single +** The cache must not perform any reference counting. A single ** call to xUnpin() unpins the page regardless of the number of prior calls -** to xFetch().)^ +** to xFetch(). ** -** ^The xRekey() method is used to change the key value associated with the -** page passed as the second argument from oldKey to newKey. ^If the cache -** previously contains an entry associated with newKey, it should be +** The xRekey() method is used to change the key value associated with the +** page passed as the second argument. If the cache +** previously contains an entry associated with newKey, it must be ** discarded. ^Any prior cache entry associated with newKey is guaranteed not ** to be pinned. ** -** ^When SQLite calls the xTruncate() method, the cache must discard all +** When SQLite calls the xTruncate() method, the cache must discard all ** existing cache entries with page numbers (keys) greater than or equal -** to the value of the iLimit parameter passed to xTruncate(). ^If any +** to the value of the iLimit parameter passed to xTruncate(). If any ** of these pages are pinned, they are implicitly unpinned, meaning that ** they can be safely discarded. ** @@ -5524,11 +5832,12 @@ typedef struct sqlite3_backup sqlite3_backup; ** ** See Also: [Using the SQLite Online Backup API] ** -** ^Exclusive access is required to the destination database for the -** duration of the operation. ^However the source database is only -** read-locked while it is actually being read; it is not locked -** continuously for the entire backup operation. ^Thus, the backup may be -** performed on a live source database without preventing other users from +** ^SQLite holds a write transaction open on the destination database file +** for the duration of the backup operation. +** ^The source database is read-locked only while it is being read; +** it is not locked continuously for the entire backup operation. +** ^Thus, the backup may be performed on a live source database without +** preventing other database connections from ** reading or writing to the source database while the backup is underway. ** ** ^(To perform a backup operation: @@ -5555,11 +5864,11 @@ typedef struct sqlite3_backup sqlite3_backup; ** 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 file with +** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. ** ** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is -** returned and an error code and error message are store3d in the +** 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() ** can be retrieved using the [sqlite3_errcode()], [sqlite3_errmsg()], and/or @@ -5576,7 +5885,7 @@ typedef struct sqlite3_backup sqlite3_backup; ** the source and destination databases specified by [sqlite3_backup] object B. ** ^If N is negative, all remaining source pages are copied. ** ^If sqlite3_backup_step(B,N) successfully copies N pages and there -** are still more pages to be copied, then the function resturns [SQLITE_OK]. +** are still more pages to be copied, then the function returns [SQLITE_OK]. ** ^If sqlite3_backup_step(B,N) successfully finishes copying all pages ** from source to destination, then it returns [SQLITE_DONE]. ** ^If an error occurs while running sqlite3_backup_step(B,N), @@ -5590,7 +5899,7 @@ typedef struct sqlite3_backup sqlite3_backup; **
  • the destination database was opened read-only, or **
  • the destination database is using write-ahead-log journaling ** and the destination and source page sizes differ, or -**
  • The destination database is an in-memory database and the +**
  • the destination database is an in-memory database and the ** destination and source page sizes differ. ** )^ ** @@ -5921,7 +6230,8 @@ void *sqlite3_wal_hook( ** from SQL. ** ** ^Every new [database connection] defaults to having the auto-checkpoint -** enabled with a threshold of 1000 pages. The use of this interface +** 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. */ diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 0d37bbe0..e45e691f 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -191,6 +191,27 @@ struct sqlite3_api_routines { sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*); const char *(*sql)(sqlite3_stmt*); int (*status)(int,int*,int*,int); + int (*backup_finish)(sqlite3_backup*); + sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*); + int (*backup_pagecount)(sqlite3_backup*); + int (*backup_remaining)(sqlite3_backup*); + int (*backup_step)(sqlite3_backup*,int); + const char *(*compileoption_get)(int); + int (*compileoption_used)(const char*); + int (*create_function_v2)(sqlite3*,const char*,int,int,void*,void (*xFunc)(sqlite3_context*,int,sqlite3_value**),void (*xStep)(sqlite3_context*,int,sqlite3_value**),void (*xFinal)(sqlite3_context*),void(*xDestroy)(void*)); + int (*db_config)(sqlite3*,int,...); + sqlite3_mutex *(*db_mutex)(sqlite3*); + int (*db_status)(sqlite3*,int,int*,int*,int); + int (*extended_errcode)(sqlite3*); + void (*log)(int,const char*,...); + sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64); + const char *(*sourceid)(void); + int (*stmt_status)(sqlite3_stmt*,int,int); + int (*strnicmp)(const char*,const char*,int); + int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*); + int (*wal_autocheckpoint)(sqlite3*,int); + int (*wal_checkpoint)(sqlite3*,const char*); + void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*); }; /* @@ -370,6 +391,27 @@ struct sqlite3_api_routines { #define sqlite3_next_stmt sqlite3_api->next_stmt #define sqlite3_sql sqlite3_api->sql #define sqlite3_status sqlite3_api->status +#define sqlite3_backup_finish sqlite3_api->backup_finish +#define sqlite3_backup_init sqlite3_api->backup_init +#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount +#define sqlite3_backup_remaining sqlite3_api->backup_remaining +#define sqlite3_backup_step sqlite3_api->backup_step +#define sqlite3_compileoption_get sqlite3_api->compileoption_get +#define sqlite3_compileoption_used sqlite3_api->compileoption_used +#define sqlite3_create_function_v2 sqlite3_api->create_function_v2 +#define sqlite3_db_config sqlite3_api->db_config +#define sqlite3_db_mutex sqlite3_api->db_mutex +#define sqlite3_db_status sqlite3_api->db_status +#define sqlite3_extended_errcode sqlite3_api->extended_errcode +#define sqlite3_log sqlite3_api->log +#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64 +#define sqlite3_sourceid sqlite3_api->sourceid +#define sqlite3_stmt_status sqlite3_api->stmt_status +#define sqlite3_strnicmp sqlite3_api->strnicmp +#define sqlite3_unlock_notify sqlite3_api->unlock_notify +#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint +#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint +#define sqlite3_wal_hook sqlite3_api->wal_hook #endif /* SQLITE_CORE */ #define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api = 0; diff --git a/src/sqliteInt.h b/src/sqliteInt.h index c44c25fa..41c46fa2 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -114,15 +114,21 @@ #endif /* -** The SQLITE_THREADSAFE macro must be defined as either 0 or 1. +** 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 libary 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 +** We support that for legacy. */ #if !defined(SQLITE_THREADSAFE) #if defined(THREADSAFE) # define SQLITE_THREADSAFE THREADSAFE #else -# define SQLITE_THREADSAFE 1 +# define SQLITE_THREADSAFE 1 /* IMP: R-07272-22309 */ #endif #endif @@ -600,6 +606,7 @@ typedef struct Expr Expr; typedef struct ExprList ExprList; typedef struct ExprSpan ExprSpan; typedef struct FKey FKey; +typedef struct FuncDestructor FuncDestructor; typedef struct FuncDef FuncDef; typedef struct FuncDefHash FuncDefHash; typedef struct IdList IdList; @@ -730,6 +737,7 @@ struct Lookaside { u8 bMalloced; /* True if pStart obtained from sqlite3_malloc() */ int nOut; /* Number of buffers currently checked out */ int mxOut; /* Highwater mark for nOut */ + int anStat[3]; /* 0: hits. 1: size misses. 2: full misses */ LookasideSlot *pFree; /* List of available buffers */ void *pStart; /* First byte of available memory space */ void *pEnd; /* First byte past end of available space */ @@ -808,6 +816,7 @@ struct sqlite3 { struct Vdbe *pVdbe; /* List of active virtual machines */ int activeVdbeCnt; /* Number of VDBEs currently executing */ int writeVdbeCnt; /* Number of active VDBEs that are writing */ + int vdbeExecCnt; /* Number of nested calls to VdbeExec() */ void (*xTrace)(void*,const char*); /* Trace function */ void *pTraceArg; /* Argument to the trace function */ void (*xProfile)(void*,const char*,u64); /* Profiling function */ @@ -907,13 +916,14 @@ struct sqlite3 { #define SQLITE_ReadUncommitted 0x0080000 /* For shared-cache mode */ #define SQLITE_LegacyFileFmt 0x00100000 /* Create new databases in format 1 */ #define SQLITE_FullFSync 0x00200000 /* Use full fsync on the backend */ -#define SQLITE_LoadExtension 0x00400000 /* Enable load_extension */ +#define SQLITE_CkptFullFSync 0x00400000 /* Use full fsync for checkpoint */ #define SQLITE_RecoveryMode 0x00800000 /* Ignore schema errors */ #define SQLITE_ReverseOrder 0x01000000 /* Reverse unordered SELECTs */ #define SQLITE_RecTriggers 0x02000000 /* Enable recursive triggers */ #define SQLITE_ForeignKeys 0x04000000 /* Enforce foreign key constraints */ #define SQLITE_AutoIndex 0x08000000 /* Enable automatic indexes */ #define SQLITE_PreferBuiltin 0x10000000 /* Preference to built-in funcs */ +#define SQLITE_LoadExtension 0x20000000 /* Enable load_extension */ /* ** Bits of the sqlite3.flags field that are used by the @@ -926,6 +936,7 @@ struct sqlite3 { #define SQLITE_IndexSearch 0x08 /* Disable indexes for searching */ #define SQLITE_IndexCover 0x10 /* Disable index covering table */ #define SQLITE_GroupByOrder 0x20 /* Disable GROUPBY cover of ORDERBY */ +#define SQLITE_FactorOutConst 0x40 /* Disable factoring out constants */ #define SQLITE_OptMask 0xff /* Mask of all disablable opts */ /* @@ -956,6 +967,27 @@ struct FuncDef { void (*xFinalize)(sqlite3_context*); /* Aggregate finalizer */ char *zName; /* SQL name of the function. */ FuncDef *pHash; /* Next with a different name but the same hash */ + FuncDestructor *pDestructor; /* Reference counted destructor function */ +}; + +/* +** This structure encapsulates a user-function destructor callback (as +** configured using create_function_v2()) and a reference counter. When +** create_function_v2() is called to create a function with a destructor, +** a single object of this type is allocated. FuncDestructor.nRef is set to +** the number of FuncDef objects created (either 1 or 3, depending on whether +** or not the specified encoding is SQLITE_ANY). The FuncDef.pDestructor +** member of each of the new FuncDef objects is set to point to the allocated +** FuncDestructor. +** +** Thereafter, when one of the FuncDef objects is deleted, the reference +** count on this object is decremented. When it reaches 0, the destructor +** is invoked and the FuncDestructor structure freed. +*/ +struct FuncDestructor { + int nRef; + void (*xDestroy)(void *); + void *pUserData; }; /* @@ -996,15 +1028,15 @@ struct FuncDef { */ #define FUNCTION(zName, nArg, iArg, bNC, xFunc) \ {nArg, SQLITE_UTF8, bNC*SQLITE_FUNC_NEEDCOLL, \ - SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0} + 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, \ - pArg, 0, xFunc, 0, 0, #zName, 0} + 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} + {nArg, 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} + SQLITE_INT_TO_PTR(arg), 0, 0, xStep,xFinal,#zName,0,0} /* ** All current savepoints are stored in a linked list starting at @@ -1224,6 +1256,7 @@ struct Table { Column *aCol; /* Information about each column */ Index *pIndex; /* List of SQL indexes on this table. */ int tnum; /* Root BTree node for this table (see note above) */ + unsigned nRowEst; /* Estimated rows in table - from sqlite_stat1 table */ Select *pSelect; /* NULL for tables. Points to definition if a view. */ u16 nRef; /* Number of pointers to this Table */ u8 tabFlags; /* Mask of TF_* values */ @@ -1792,6 +1825,9 @@ struct SrcList { u8 isPopulated; /* Temporary table associated with SELECT is populated */ u8 jointype; /* Type of join between this able and the previous */ u8 notIndexed; /* True if there is a NOT INDEXED clause */ +#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 */ IdList *pUsing; /* The USING clause of a join */ @@ -1830,6 +1866,7 @@ struct SrcList { struct WherePlan { u32 wsFlags; /* WHERE_* flags that describe the strategy */ u32 nEq; /* Number of == constraints */ + double nRow; /* Estimated number of rows (for EQP) */ union { Index *pIdx; /* Index when WHERE_INDEXED is true */ struct WhereTerm *pTerm; /* WHERE clause term for OR-search */ @@ -1914,6 +1951,7 @@ struct WhereInfo { int nLevel; /* Number of nested loop */ struct WhereClause *pWC; /* Decomposition of the WHERE clause */ double savedNQueryLoop; /* pParse->nQueryLoop outside the WHERE loop */ + double nRowOut; /* Estimated number of output rows */ WhereLevel a[1]; /* Information about each nest loop in WHERE */ }; @@ -1989,6 +2027,7 @@ struct Select { Expr *pOffset; /* OFFSET expression. NULL means not used. */ int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */ int addrOpenEphm[3]; /* OP_OpenEphem opcodes related to this select */ + double nSelectRow; /* Estimated number of result rows */ }; /* @@ -2184,6 +2223,11 @@ struct Parse { int nHeight; /* Expression tree height of current sub-select */ Table *pZombieTab; /* List of Table objects to delete after code gen */ TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ + +#ifndef SQLITE_OMIT_EXPLAIN + int iSelectId; + int iNextSelectId; +#endif }; #ifdef SQLITE_OMIT_VIRTUALTABLE @@ -2478,7 +2522,6 @@ int sqlite3CantopenError(int); ** Internal function prototypes */ int sqlite3StrICmp(const char *, const char *); -int sqlite3IsNumber(const char*, int*, u8); int sqlite3Strlen30(const char*); #define sqlite3StrNICmp sqlite3_strnicmp @@ -2502,7 +2545,7 @@ void *sqlite3PageMalloc(int); void sqlite3PageFree(void*); void sqlite3MemSetDefault(void); void sqlite3BenignMallocHooks(void (*)(void), void (*)(void)); -int sqlite3MemoryAlarm(void (*)(void*, sqlite3_int64, int), void*, sqlite3_int64); +int sqlite3HeapNearlyFull(void); /* ** On systems with ample stack space and that support alloca(), make @@ -2673,7 +2716,6 @@ void sqlite3ExprCachePop(Parse*, int); void sqlite3ExprCacheRemove(Parse*, int, int); void sqlite3ExprCacheClear(Parse*); void sqlite3ExprCacheAffinityChange(Parse*, int, int); -void sqlite3ExprHardCopy(Parse*,int,int); int sqlite3ExprCode(Parse*, Expr*, int); int sqlite3ExprCodeTemp(Parse*, Expr*, int*); int sqlite3ExprCodeTarget(Parse*, Expr*, int); @@ -2793,17 +2835,15 @@ void sqlite3DeferForeignKey(Parse*, int); #endif void sqlite3Attach(Parse*, Expr*, Expr*, Expr*); void sqlite3Detach(Parse*, Expr*); -int sqlite3BtreeFactory(sqlite3 *db, const char *zFilename, - int omitJournal, int nCache, int flags, Btree **ppBtree); int sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*); int sqlite3FixSrcList(DbFixer*, SrcList*); int sqlite3FixSelect(DbFixer*, Select*); int sqlite3FixExpr(DbFixer*, Expr*); int sqlite3FixExprList(DbFixer*, ExprList*); int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); -int sqlite3AtoF(const char *z, double*); +int sqlite3AtoF(const char *z, double*, int, u8); int sqlite3GetInt32(const char *, int*); -int sqlite3FitsIn64Bits(const char *, int); +int sqlite3Atoi(const char*); int sqlite3Utf16ByteLen(const void *pData, int nChar); int sqlite3Utf8CharLen(const char *pData, int nByte); int sqlite3Utf8Read(const u8*, const u8**); @@ -2849,7 +2889,7 @@ void sqlite3TableAffinityStr(Vdbe *, Table *); char sqlite3CompareAffinity(Expr *pExpr, char aff2); int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity); char sqlite3ExprAffinity(Expr *pExpr); -int sqlite3Atoi64(const char*, i64*); +int sqlite3Atoi64(const char*, i64*, int, u8); void sqlite3Error(sqlite3*, int, const char*,...); void *sqlite3HexToBlob(sqlite3*, const char *z, int n); int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); @@ -2920,7 +2960,9 @@ int sqlite3SchemaToIndex(sqlite3 *db, Schema *); KeyInfo *sqlite3IndexKeyinfo(Parse *, Index *); int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, void (*)(sqlite3_context*,int,sqlite3_value **), - void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*)); + void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*), + FuncDestructor *pDestructor +); int sqlite3ApiExit(sqlite3 *db, int); int sqlite3OpenTempDatabase(Parse *); diff --git a/src/status.c b/src/status.c index 7f05f1c6..96759fda 100644 --- a/src/status.c +++ b/src/status.c @@ -116,6 +116,22 @@ int sqlite3_db_status( break; } + case SQLITE_DBSTATUS_LOOKASIDE_HIT: + case SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE: + case SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL: { + testcase( op==SQLITE_DBSTATUS_LOOKASIDE_HIT ); + testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE ); + testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL ); + assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)>=0 ); + assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)<3 ); + *pCurrent = 0; + *pHighwater = db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT]; + if( resetFlag ){ + db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT] = 0; + } + break; + } + /* ** Return an approximation for the amount of memory currently used ** by all pagers associated with the given database connection. The diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 5b5b4b38..99939c1f 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -670,6 +670,7 @@ static void DbUpdateHandler( Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zTbl, -1)); Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(rowid)); Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pCmd); } static void tclCollateNeeded( @@ -3014,22 +3015,31 @@ static int DbMain(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ ** if the extension only supplies one new name!) The "sqlite" command is ** used to open a new SQLite database. See the DbMain() routine above ** for additional information. +** +** The EXTERN macros are required by TCL in order to work on windows. */ -int Sqlite3_Init(Tcl_Interp *interp){ +EXTERN int Sqlite3_Init(Tcl_Interp *interp){ Tcl_InitStubs(interp, "8.4", 0); Tcl_CreateObjCommand(interp, "sqlite3", (Tcl_ObjCmdProc*)DbMain, 0, 0); Tcl_PkgProvide(interp, "sqlite3", PACKAGE_VERSION); + +#ifndef SQLITE_3_SUFFIX_ONLY + /* The "sqlite" alias is undocumented. It is here only to support + ** legacy scripts. All new scripts should use only the "sqlite3" + ** command. + */ Tcl_CreateObjCommand(interp, "sqlite", (Tcl_ObjCmdProc*)DbMain, 0, 0); - Tcl_PkgProvide(interp, "sqlite", PACKAGE_VERSION); +#endif + return TCL_OK; } -int Tclsqlite3_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); } -int Sqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; } -int Tclsqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; } -int Sqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; } -int Tclsqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; } -int Sqlite3_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK; } -int Tclsqlite3_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK;} +EXTERN int Tclsqlite3_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); } +EXTERN int Sqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; } +EXTERN int Tclsqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; } +EXTERN int Sqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; } +EXTERN int Tclsqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; } +EXTERN int Sqlite3_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK; } +EXTERN int Tclsqlite3_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK;} #ifndef SQLITE_3_SUFFIX_ONLY @@ -3567,6 +3577,15 @@ static void init_all(Tcl_Interp *interp){ extern int Sqlitetestintarray_Init(Tcl_Interp*); extern int Sqlitetestvfs_Init(Tcl_Interp *); extern int SqlitetestStat_Init(Tcl_Interp*); + extern int Sqlitetestrtree_Init(Tcl_Interp*); + extern int Sqlitequota_Init(Tcl_Interp*); + extern int Sqlitemultiplex_Init(Tcl_Interp*); + extern int SqliteSuperlock_Init(Tcl_Interp*); + +#ifdef SQLITE_ENABLE_ZIPVFS + extern int Zipvfs_Init(Tcl_Interp*); + Zipvfs_Init(interp); +#endif Sqliteconfig_Init(interp); Sqlitetest1_Init(interp); @@ -3595,6 +3614,10 @@ static void init_all(Tcl_Interp *interp){ Sqlitetestintarray_Init(interp); Sqlitetestvfs_Init(interp); SqlitetestStat_Init(interp); + Sqlitetestrtree_Init(interp); + Sqlitequota_Init(interp); + Sqlitemultiplex_Init(interp); + SqliteSuperlock_Init(interp); Tcl_CreateObjCommand(interp,"load_testfixture_extensions",init_all_cmd,0,0); diff --git a/src/test1.c b/src/test1.c index f458cdb4..cf7e06b9 100644 --- a/src/test1.c +++ b/src/test1.c @@ -1235,7 +1235,7 @@ static int sqlite3_mprintf_int64( return TCL_ERROR; } for(i=2; i<5; i++){ - if( !sqlite3Atoi64(argv[i], &a[i-2]) ){ + if( sqlite3Atoi64(argv[i], &a[i-2], 1000000, SQLITE_UTF8) ){ Tcl_AppendResult(interp, "argument is not a valid 64-bit integer", 0); return TCL_ERROR; } @@ -1590,6 +1590,81 @@ static int test_table_column_metadata( #ifndef SQLITE_OMIT_INCRBLOB +static int blobHandleFromObj( + Tcl_Interp *interp, + Tcl_Obj *pObj, + sqlite3_blob **ppBlob +){ + char *z; + int n; + + z = Tcl_GetStringFromObj(pObj, &n); + if( n==0 ){ + *ppBlob = 0; + }else{ + int notUsed; + Tcl_Channel channel; + ClientData instanceData; + + channel = Tcl_GetChannel(interp, z, ¬Used); + if( !channel ) return TCL_ERROR; + + Tcl_Flush(channel); + Tcl_Seek(channel, 0, SEEK_SET); + + instanceData = Tcl_GetChannelInstanceData(channel); + *ppBlob = *((sqlite3_blob **)instanceData); + } + + return TCL_OK; +} + +/* +** sqlite3_blob_bytes CHANNEL +*/ +static int test_blob_bytes( + ClientData clientData, /* Not used */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3_blob *pBlob; + int nByte; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "CHANNEL"); + return TCL_ERROR; + } + + if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR; + nByte = sqlite3_blob_bytes(pBlob); + Tcl_SetObjResult(interp, Tcl_NewIntObj(nByte)); + + return TCL_OK; +} + +/* +** sqlite3_blob_close CHANNEL +*/ +static int test_blob_close( + ClientData clientData, /* Not used */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3_blob *pBlob; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "CHANNEL"); + return TCL_ERROR; + } + + if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR; + sqlite3_blob_close(pBlob); + + return TCL_OK; +} + /* ** sqlite3_blob_read CHANNEL OFFSET N ** @@ -1611,13 +1686,10 @@ static int test_blob_read( int objc, /* Number of arguments */ Tcl_Obj *CONST objv[] /* Command arguments */ ){ - Tcl_Channel channel; - ClientData instanceData; sqlite3_blob *pBlob; - int notUsed; int nByte; int iOffset; - unsigned char *zBuf; + unsigned char *zBuf = 0; int rc; if( objc!=4 ){ @@ -1625,19 +1697,16 @@ static int test_blob_read( return TCL_ERROR; } - channel = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), ¬Used); - if( !channel - || TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iOffset) + if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR; + if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iOffset) || TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &nByte) - || nByte<0 || iOffset<0 ){ return TCL_ERROR; } - instanceData = Tcl_GetChannelInstanceData(channel); - pBlob = *((sqlite3_blob **)instanceData); - - zBuf = (unsigned char *)Tcl_Alloc(nByte); + if( nByte>0 ){ + zBuf = (unsigned char *)Tcl_Alloc(nByte); + } rc = sqlite3_blob_read(pBlob, zBuf, nByte, iOffset); if( rc==SQLITE_OK ){ Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(zBuf, nByte)); @@ -1669,10 +1738,7 @@ static int test_blob_write( int objc, /* Number of arguments */ Tcl_Obj *CONST objv[] /* Command arguments */ ){ - Tcl_Channel channel; - ClientData instanceData; sqlite3_blob *pBlob; - int notUsed; int iOffset; int rc; @@ -1684,14 +1750,11 @@ static int test_blob_write( return TCL_ERROR; } - channel = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), ¬Used); - if( !channel || TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iOffset) ){ + if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR; + if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iOffset) ){ return TCL_ERROR; } - instanceData = Tcl_GetChannelInstanceData(channel); - pBlob = *((sqlite3_blob **)instanceData); - zBuf = Tcl_GetByteArrayFromObj(objv[3], &nBuf); if( objc==5 && Tcl_GetIntFromObj(interp, objv[4], &nBuf) ){ return TCL_ERROR; @@ -1703,6 +1766,33 @@ static int test_blob_write( return (rc==SQLITE_OK ? TCL_OK : TCL_ERROR); } + +static int test_blob_reopen( + ClientData clientData, /* Not used */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + Tcl_WideInt iRowid; + sqlite3_blob *pBlob; + int rc; + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "CHANNEL ROWID"); + return TCL_ERROR; + } + + if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR; + if( Tcl_GetWideIntFromObj(interp, objv[2], &iRowid) ) return TCL_ERROR; + + rc = sqlite3_blob_reopen(pBlob, iRowid); + if( rc!=SQLITE_OK ){ + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_VOLATILE); + } + + return (rc==SQLITE_OK ? TCL_OK : TCL_ERROR); +} + #endif /* @@ -1790,6 +1880,129 @@ static int test_create_collation_v2( return TCL_OK; } +/* +** USAGE: sqlite3_create_function_v2 DB NAME NARG ENC ?SWITCHES? +** +** Available switches are: +** +** -func SCRIPT +** -step SCRIPT +** -final SCRIPT +** -destroy SCRIPT +*/ +typedef struct CreateFunctionV2 CreateFunctionV2; +struct CreateFunctionV2 { + Tcl_Interp *interp; + Tcl_Obj *pFunc; /* Script for function invocation */ + Tcl_Obj *pStep; /* Script for agg. step invocation */ + Tcl_Obj *pFinal; /* Script for agg. finalization invocation */ + Tcl_Obj *pDestroy; /* Destructor script */ +}; +static void cf2Func(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){ +} +static void cf2Step(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){ +} +static void cf2Final(sqlite3_context *ctx){ +} +static void cf2Destroy(void *pUser){ + CreateFunctionV2 *p = (CreateFunctionV2 *)pUser; + + if( p->interp && p->pDestroy ){ + int rc = Tcl_EvalObjEx(p->interp, p->pDestroy, 0); + if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp); + } + + if( p->pFunc ) Tcl_DecrRefCount(p->pFunc); + if( p->pStep ) Tcl_DecrRefCount(p->pStep); + if( p->pFinal ) Tcl_DecrRefCount(p->pFinal); + if( p->pDestroy ) Tcl_DecrRefCount(p->pDestroy); + sqlite3_free(p); +} +static int test_create_function_v2( + ClientData clientData, /* Not used */ + Tcl_Interp *interp, /* The invoking TCL interpreter */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3 *db; + const char *zFunc; + int nArg; + int enc; + CreateFunctionV2 *p; + int i; + int rc; + + struct EncTable { + const char *zEnc; + int enc; + } aEnc[] = { + {"utf8", SQLITE_UTF8 }, + {"utf16", SQLITE_UTF16 }, + {"utf16le", SQLITE_UTF16LE }, + {"utf16be", SQLITE_UTF16BE }, + {"any", SQLITE_ANY }, + {"0", 0 } + }; + + if( objc<5 || (objc%2)==0 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB NAME NARG ENC SWITCHES..."); + return TCL_ERROR; + } + + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + zFunc = Tcl_GetString(objv[2]); + if( Tcl_GetIntFromObj(interp, objv[3], &nArg) ) return TCL_ERROR; + if( Tcl_GetIndexFromObjStruct(interp, objv[4], aEnc, sizeof(aEnc[0]), + "encoding", 0, &enc) + ){ + return TCL_ERROR; + } + enc = aEnc[enc].enc; + + p = sqlite3_malloc(sizeof(CreateFunctionV2)); + assert( p ); + memset(p, 0, sizeof(CreateFunctionV2)); + p->interp = interp; + + for(i=5; ipFunc = objv[i+1]; break; + case 1: p->pStep = objv[i+1]; break; + case 2: p->pFinal = objv[i+1]; break; + case 3: p->pDestroy = objv[i+1]; break; + } + } + if( p->pFunc ) p->pFunc = Tcl_DuplicateObj(p->pFunc); + if( p->pStep ) p->pStep = Tcl_DuplicateObj(p->pStep); + if( p->pFinal ) p->pFinal = Tcl_DuplicateObj(p->pFinal); + if( p->pDestroy ) p->pDestroy = Tcl_DuplicateObj(p->pDestroy); + + if( p->pFunc ) Tcl_IncrRefCount(p->pFunc); + if( p->pStep ) Tcl_IncrRefCount(p->pStep); + if( p->pFinal ) Tcl_IncrRefCount(p->pFinal); + if( p->pDestroy ) Tcl_IncrRefCount(p->pDestroy); + + rc = sqlite3_create_function_v2(db, zFunc, nArg, enc, (void *)p, + (p->pFunc ? cf2Func : 0), + (p->pStep ? cf2Step : 0), + (p->pFinal ? cf2Final : 0), + cf2Destroy + ); + if( rc!=SQLITE_OK ){ + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); + return TCL_ERROR; + } + return TCL_OK; +} + /* ** Usage: sqlite3_load_extension DB-HANDLE FILE ?PROC? */ @@ -2086,6 +2299,33 @@ static int test_next_stmt( return TCL_OK; } +/* +** Usage: sqlite3_stmt_readonly STMT +** +** Return true if STMT is a NULL pointer or a pointer to a statement +** that is guaranteed to leave the database unmodified. +*/ +static int test_stmt_readonly( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3_stmt *pStmt; + int rc; + + if( objc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", + Tcl_GetStringFromObj(objv[0], 0), " STMT", 0); + return TCL_ERROR; + } + + if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; + rc = sqlite3_stmt_readonly(pStmt); + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(rc)); + return TCL_OK; +} + /* ** Usage: sqlite3_reset STMT @@ -2291,7 +2531,7 @@ static int test_collate_func( int nB, const void *zB ){ Tcl_Interp *i = pTestCollateInterp; - int encin = (int)pCtx; + int encin = SQLITE_PTR_TO_INT(pCtx); int res; int n; @@ -2420,7 +2660,7 @@ static void test_collate_needed_cb( } zNeededCollation[i] = 0; sqlite3_create_collation( - db, "test_collate", ENC(db), (void *)enc, test_collate_func); + db, "test_collate", ENC(db), SQLITE_INT_TO_PTR(enc), test_collate_func); } /* @@ -2469,8 +2709,8 @@ static int alignmentCollFunc( ){ int rc, n; n = nKey10 && 1==(1&(int)pKey1) ) unaligned_string_counter++; - if( nKey2>0 && 1==(1&(int)pKey2) ) unaligned_string_counter++; + if( nKey1>0 && 1==(1&(SQLITE_PTR_TO_INT(pKey1))) ) unaligned_string_counter++; + if( nKey2>0 && 1==(1&(SQLITE_PTR_TO_INT(pKey2))) ) unaligned_string_counter++; rc = memcmp(pKey1, pKey2, n); if( rc==0 ){ rc = nKey1 - nKey2; @@ -4266,20 +4506,17 @@ static int test_soft_heap_limit( int objc, Tcl_Obj *CONST objv[] ){ - static int softHeapLimit = 0; - int amt; + sqlite3_int64 amt; + sqlite3_int64 N = -1; if( objc!=1 && objc!=2 ){ Tcl_WrongNumArgs(interp, 1, objv, "?N?"); return TCL_ERROR; } - amt = softHeapLimit; if( objc==2 ){ - int N; - if( Tcl_GetIntFromObj(interp, objv[1], &N) ) return TCL_ERROR; - sqlite3_soft_heap_limit(N); - softHeapLimit = N; + if( Tcl_GetWideIntFromObj(interp, objv[1], &N) ) return TCL_ERROR; } - Tcl_SetObjResult(interp, Tcl_NewIntObj(amt)); + amt = sqlite3_soft_heap_limit64(N); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(amt)); return TCL_OK; } @@ -4564,13 +4801,13 @@ static int file_control_test( } if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; rc = sqlite3_file_control(db, 0, 0, &iArg); - assert( rc==SQLITE_ERROR ); + assert( rc==SQLITE_NOTFOUND ); rc = sqlite3_file_control(db, "notadatabase", SQLITE_FCNTL_LOCKSTATE, &iArg); assert( rc==SQLITE_ERROR ); rc = sqlite3_file_control(db, "main", -1, &iArg); - assert( rc==SQLITE_ERROR ); + assert( rc==SQLITE_NOTFOUND ); rc = sqlite3_file_control(db, "temp", -1, &iArg); - assert( rc==SQLITE_ERROR ); + assert( rc==SQLITE_NOTFOUND || rc==SQLITE_ERROR ); return TCL_OK; } @@ -5021,6 +5258,122 @@ static int runAsObjProc( return cmdInfo.objProc(cmdInfo.objClientData, interp, objc-1, objv+1); } +#ifndef SQLITE_OMIT_EXPLAIN +/* +** WARNING: The following function, printExplainQueryPlan() is an exact +** copy of example code from eqp.in (eqp.html). If this code is modified, +** then the documentation copy needs to be modified as well. +*/ +/* +** Argument pStmt is a prepared SQL statement. This function compiles +** an EXPLAIN QUERY PLAN command to report on the prepared statement, +** and prints the report to stdout using printf(). +*/ +int printExplainQueryPlan(sqlite3_stmt *pStmt){ + const char *zSql; /* Input SQL */ + char *zExplain; /* SQL with EXPLAIN QUERY PLAN prepended */ + sqlite3_stmt *pExplain; /* Compiled EXPLAIN QUERY PLAN command */ + int rc; /* Return code from sqlite3_prepare_v2() */ + + zSql = sqlite3_sql(pStmt); + if( zSql==0 ) return SQLITE_ERROR; + + zExplain = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zSql); + if( zExplain==0 ) return SQLITE_NOMEM; + + rc = sqlite3_prepare_v2(sqlite3_db_handle(pStmt), zExplain, -1, &pExplain, 0); + sqlite3_free(zExplain); + if( rc!=SQLITE_OK ) return rc; + + while( SQLITE_ROW==sqlite3_step(pExplain) ){ + int iSelectid = sqlite3_column_int(pExplain, 0); + int iOrder = sqlite3_column_int(pExplain, 1); + int iFrom = sqlite3_column_int(pExplain, 2); + const char *zDetail = (const char *)sqlite3_column_text(pExplain, 3); + + printf("%d %d %d %s\n", iSelectid, iOrder, iFrom, zDetail); + } + + return sqlite3_finalize(pExplain); +} + +static int test_print_eqp( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; + sqlite3_stmt *pStmt; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "STMT"); + return TCL_ERROR; + } + if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; + rc = printExplainQueryPlan(pStmt); + Tcl_SetResult(interp, (char *)t1ErrorName(rc), 0); + return TCL_OK; +} +#endif /* SQLITE_OMIT_EXPLAIN */ + +/* +** optimization_control DB OPT BOOLEAN +** +** Enable or disable query optimizations using the sqlite3_test_control() +** interface. Disable if BOOLEAN is false and enable if BOOLEAN is true. +** OPT is the name of the optimization to be disabled. +*/ +static int optimization_control( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int i; + sqlite3 *db; + const char *zOpt; + int onoff; + int mask; + static const struct { + const char *zOptName; + int mask; + } aOpt[] = { + { "all", SQLITE_OptMask }, + { "query-flattener", SQLITE_QueryFlattener }, + { "column-cache", SQLITE_ColumnCache }, + { "index-sort", SQLITE_IndexSort }, + { "index-search", SQLITE_IndexSearch }, + { "index-cover", SQLITE_IndexCover }, + { "groupby-order", SQLITE_GroupByOrder }, + { "factor-constants", SQLITE_FactorOutConst }, + }; + + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB OPT BOOLEAN"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + if( Tcl_GetBooleanFromObj(interp, objv[3], &onoff) ) return TCL_ERROR; + zOpt = Tcl_GetString(objv[2]); + for(i=0; i=sizeof(aOpt)/sizeof(aOpt[0]) ){ + Tcl_AppendResult(interp, "unknown optimization - should be one of:", + (char*)0); + for(i=0; i +#include +#include "sqliteInt.h" + +/************************ Shim Definitions ******************************/ + +/* This is the limit on the chunk size. It may be changed by calling +** the sqlite3_multiplex_set() interface. +*/ +#define SQLITE_MULTIPLEX_CHUNK_SIZE 0x40000000 +/* Default limit on number of chunks. Care should be taken +** so that values for chunks numbers fit in the SQLITE_MULTIPLEX_EXT_FMT +** format specifier. It may be changed by calling +** the sqlite3_multiplex_set() interface. +*/ +#define SQLITE_MULTIPLEX_MAX_CHUNKS 32 + +/* If SQLITE_MULTIPLEX_EXT_OVWR is defined, the +** last SQLITE_MULTIPLEX_EXT_SZ characters of the +** filename will be overwritten, otherwise, the +** multiplex extension is simply appended to the filename. +** Ex. (undefined) test.db -> test.db01 +** (defined) test.db -> test.01 +** Chunk 0 does not have a modified extension. +*/ +#define SQLITE_MULTIPLEX_EXT_FMT "%02d" +#define SQLITE_MULTIPLEX_EXT_SZ 2 + +/************************ Object Definitions ******************************/ + +/* Forward declaration of all object types */ +typedef struct multiplexGroup multiplexGroup; +typedef struct multiplexConn multiplexConn; + +/* +** A "multiplex group" is a collection of files that collectively +** makeup a single SQLite DB file. This allows the size of the DB +** to exceed the limits imposed by the file system. +** +** There is an instance of the following object for each defined multiplex +** group. +*/ +struct multiplexGroup { + sqlite3_file **pReal; /* Handles to each chunk */ + char *bOpen; /* 0 if chunk not opened */ + char *zName; /* Base filename of this group */ + int nName; /* Length of base filename */ + int flags; /* Flags used for original opening */ + multiplexGroup *pNext, *pPrev; /* Doubly linked list of all group objects */ +}; + +/* +** An instance of the following object represents each open connection +** to a file that is multiplex'ed. This object is a +** subclass of sqlite3_file. The sqlite3_file object for the underlying +** VFS is appended to this structure. +*/ +struct multiplexConn { + sqlite3_file base; /* Base class - must be first */ + multiplexGroup *pGroup; /* The underlying group of files */ +}; + +/************************* Global Variables **********************************/ +/* +** All global variables used by this file are containing within the following +** gMultiplex structure. +*/ +static struct { + /* The pOrigVfs is the real, original underlying VFS implementation. + ** Most operations pass-through to the real VFS. This value is read-only + ** during operation. It is only modified at start-time and thus does not + ** require a mutex. + */ + sqlite3_vfs *pOrigVfs; + + /* The sThisVfs is the VFS structure used by this shim. It is initialized + ** at start-time and thus does not require a mutex + */ + sqlite3_vfs sThisVfs; + + /* The sIoMethods defines the methods used by sqlite3_file objects + ** associated with this shim. It is initialized at start-time and does + ** not require a mutex. + ** + ** When the underlying VFS is called to open a file, it might return + ** either a version 1 or a version 2 sqlite3_file object. This shim + ** has to create a wrapper sqlite3_file of the same version. Hence + ** there are two I/O method structures, one for version 1 and the other + ** for version 2. + */ + sqlite3_io_methods sIoMethodsV1; + sqlite3_io_methods sIoMethodsV2; + + /* True when this shim has been initialized. + */ + int isInitialized; + + /* For run-time access any of the other global data structures in this + ** shim, the following mutex must be held. + */ + sqlite3_mutex *pMutex; + + /* List of multiplexGroup objects. + */ + multiplexGroup *pGroups; + + /* Chunk params. + */ + int nChunkSize; + int nMaxChunks; + + /* Storage for temp file names. Allocated during + ** initialization to the max pathname of the underlying VFS. + */ + char *zName; + +} gMultiplex; + +/************************* Utility Routines *********************************/ +/* +** Acquire and release the mutex used to serialize access to the +** list of multiplexGroups. +*/ +static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); } +static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); } + +/* Translate an sqlite3_file* that is really a multiplexGroup* into +** the sqlite3_file* for the underlying original VFS. +*/ +static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc, int *pOutFlags){ + multiplexGroup *pGroup = pConn->pGroup; + sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ + if( iChunkpReal[iChunk]; /* Real file descriptor */ + if( !pGroup->bOpen[iChunk] ){ + memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); + if( iChunk ){ +#ifdef SQLITE_MULTIPLEX_EXT_OVWR + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, iChunk); +#else + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, iChunk); +#endif + } + *rc = pOrigVfs->xOpen(pOrigVfs, gMultiplex.zName, pSubOpen, pGroup->flags, pOutFlags); + if( *rc==SQLITE_OK ){ + pGroup->bOpen[iChunk] = -1; + return pSubOpen; + } + return NULL; + } + *rc = SQLITE_OK; + return pSubOpen; + } + *rc = SQLITE_FULL; + return NULL; +} + +/************************* VFS Method Wrappers *****************************/ + +/* +** This is the xOpen method used for the "multiplex" VFS. +** +** Most of the work is done by the underlying original VFS. This method +** simply links the new file into the appropriate multiplex group if it is a +** file that needs to be tracked. +*/ +static int multiplexOpen( + sqlite3_vfs *pVfs, /* The multiplex VFS */ + const char *zName, /* Name of file to be opened */ + sqlite3_file *pConn, /* Fill in this file descriptor */ + int flags, /* Flags to control the opening */ + int *pOutFlags /* Flags showing results of opening */ +){ + int rc; /* Result code */ + multiplexConn *pMultiplexOpen; /* The new multiplex file descriptor */ + multiplexGroup *pGroup; /* Corresponding multiplexGroup object */ + sqlite3_file *pSubOpen; /* Real file descriptor */ + sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ + int nName = sqlite3Strlen30(zName); + int i; + int sz; + + UNUSED_PARAMETER(pVfs); + + /* We need to create a group structure and manage + ** access to this group of files. + */ + multiplexEnter(); + pMultiplexOpen = (multiplexConn*)pConn; + /* allocate space for group */ + sz = sizeof(multiplexGroup) /* multiplexGroup */ + + (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks) /* pReal[] */ + + (pOrigVfs->szOsFile*gMultiplex.nMaxChunks) /* *pReal */ + + gMultiplex.nMaxChunks /* bOpen[] */ + + nName + 1; /* zName */ +#ifndef SQLITE_MULTIPLEX_EXT_OVWR + sz += SQLITE_MULTIPLEX_EXT_SZ; + assert(nName+SQLITE_MULTIPLEX_EXT_SZ < pOrigVfs->mxPathname); +#else + assert(nName >= SQLITE_MULTIPLEX_EXT_SZ); + assert(nName < pOrigVfs->mxPathname); +#endif + pGroup = sqlite3_malloc( sz ); + if( pGroup==0 ){ + rc=SQLITE_NOMEM; + }else{ + /* assign pointers to extra space allocated */ + char *p = (char *)&pGroup[1]; + pMultiplexOpen->pGroup = pGroup; + memset(pGroup, 0, sz); + pGroup->pReal = (sqlite3_file **)p; + p += (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks); + for(i=0; ipReal[i] = (sqlite3_file *)p; + p += pOrigVfs->szOsFile; + } + pGroup->bOpen = p; + p += gMultiplex.nMaxChunks; + pGroup->zName = p; + /* save off base filename, name length, and original open flags */ + memcpy(pGroup->zName, zName, nName+1); + pGroup->nName = nName; + pGroup->flags = flags; + pSubOpen = multiplexSubOpen(pMultiplexOpen, 0, &rc, pOutFlags); + if( pSubOpen ){ + if( pSubOpen->pMethods->iVersion==1 ){ + pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1; + }else{ + pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV2; + } + /* place this group at the head of our list */ + pGroup->pNext = gMultiplex.pGroups; + if( gMultiplex.pGroups ) gMultiplex.pGroups->pPrev = pGroup; + gMultiplex.pGroups = pGroup; + }else{ + sqlite3_free(pGroup); + } + } + multiplexLeave(); + return rc; +} + +/* +** This is the xDelete method used for the "multiplex" VFS. +** It attempts to delete the filename specified, as well +** as additional files with the SQLITE_MULTIPLEX_EXT_FMT extension. +*/ +static int multiplexDelete( + sqlite3_vfs *pVfs, /* The multiplex VFS */ + const char *zName, /* Name of file to delete */ + int syncDir +){ + sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ + int rc = SQLITE_OK; + int nName = sqlite3Strlen30(zName); + int i; + + UNUSED_PARAMETER(pVfs); + + multiplexEnter(); + memcpy(gMultiplex.zName, zName, nName+1); + for(i=0; ixAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists); + if( rc2==SQLITE_OK && exists){ + /* if it exists, delete it */ + rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, syncDir); + if( rc2!=SQLITE_OK ) rc = rc2; + }else{ + /* stop at first "gap" */ + break; + } + } + multiplexLeave(); + return rc; +} + +static int multiplexAccess(sqlite3_vfs *a, const char *b, int c, int *d){ + return gMultiplex.pOrigVfs->xAccess(gMultiplex.pOrigVfs, b, c, d); +} +static int multiplexFullPathname(sqlite3_vfs *a, const char *b, int c, char *d){ + return gMultiplex.pOrigVfs->xFullPathname(gMultiplex.pOrigVfs, b, c, d); +} +static void *multiplexDlOpen(sqlite3_vfs *a, const char *b){ + return gMultiplex.pOrigVfs->xDlOpen(gMultiplex.pOrigVfs, b); +} +static void multiplexDlError(sqlite3_vfs *a, int b, char *c){ + gMultiplex.pOrigVfs->xDlError(gMultiplex.pOrigVfs, b, c); +} +static void (*multiplexDlSym(sqlite3_vfs *a, void *b, const char *c))(void){ + return gMultiplex.pOrigVfs->xDlSym(gMultiplex.pOrigVfs, b, c); +} +static void multiplexDlClose(sqlite3_vfs *a, void *b){ + gMultiplex.pOrigVfs->xDlClose(gMultiplex.pOrigVfs, b); +} +static int multiplexRandomness(sqlite3_vfs *a, int b, char *c){ + return gMultiplex.pOrigVfs->xRandomness(gMultiplex.pOrigVfs, b, c); +} +static int multiplexSleep(sqlite3_vfs *a, int b){ + return gMultiplex.pOrigVfs->xSleep(gMultiplex.pOrigVfs, b); +} +static int multiplexCurrentTime(sqlite3_vfs *a, double *b){ + return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b); +} +static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){ + return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c); +} +static int multiplexCurrentTimeInt64(sqlite3_vfs *a, sqlite3_int64 *b){ + return gMultiplex.pOrigVfs->xCurrentTimeInt64(gMultiplex.pOrigVfs, b); +} + +/************************ I/O Method Wrappers *******************************/ + +/* xClose requests get passed through to the original VFS. +** We loop over all open chunk handles and close them. +** The group structure for this file is unlinked from +** our list of groups and freed. +*/ +static int multiplexClose(sqlite3_file *pConn){ + multiplexConn *p = (multiplexConn*)pConn; + multiplexGroup *pGroup = p->pGroup; + int rc = SQLITE_OK; + int i; + multiplexEnter(); + /* close any open handles */ + for(i=0; ibOpen[i] ){ + sqlite3_file *pSubOpen = pGroup->pReal[i]; + int rc2 = pSubOpen->pMethods->xClose(pSubOpen); + if( rc2!=SQLITE_OK ) rc = rc2; + pGroup->bOpen[i] = 0; + } + } + /* remove from linked list */ + if( pGroup->pNext ) pGroup->pNext->pPrev = pGroup->pPrev; + if( pGroup->pPrev ){ + pGroup->pPrev->pNext = pGroup->pNext; + }else{ + gMultiplex.pGroups = pGroup->pNext; + } + sqlite3_free(pGroup); + multiplexLeave(); + return rc; +} + +/* Pass xRead requests thru to the original VFS after +** determining the correct chunk to operate on. +** Break up reads across chunk boundaries. +*/ +static int multiplexRead( + sqlite3_file *pConn, + void *pBuf, + int iAmt, + sqlite3_int64 iOfst +){ + multiplexConn *p = (multiplexConn*)pConn; + int rc = SQLITE_OK; + multiplexEnter(); + while( iAmt > 0 ){ + int i = (int)(iOfst/gMultiplex.nChunkSize); + sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); + if( pSubOpen ){ + int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize; + if( extra<0 ) extra = 0; + iAmt -= extra; + rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize); + if( rc!=SQLITE_OK ) break; + pBuf = (char *)pBuf + iAmt; + iOfst += iAmt; + iAmt = extra; + }else{ + rc = SQLITE_IOERR_READ; + break; + } + } + multiplexLeave(); + return rc; +} + +/* Pass xWrite requests thru to the original VFS after +** determining the correct chunk to operate on. +** Break up writes across chunk boundaries. +*/ +static int multiplexWrite( + sqlite3_file *pConn, + const void *pBuf, + int iAmt, + sqlite3_int64 iOfst +){ + multiplexConn *p = (multiplexConn*)pConn; + int rc = SQLITE_OK; + multiplexEnter(); + while( iAmt > 0 ){ + int i = (int)(iOfst/gMultiplex.nChunkSize); + sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); + if( pSubOpen ){ + int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize; + if( extra<0 ) extra = 0; + iAmt -= extra; + rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize); + if( rc!=SQLITE_OK ) break; + pBuf = (char *)pBuf + iAmt; + iOfst += iAmt; + iAmt = extra; + }else{ + rc = SQLITE_IOERR_WRITE; + break; + } + } + multiplexLeave(); + return rc; +} + +/* Pass xTruncate requests thru to the original VFS after +** determining the correct chunk to operate on. Delete any +** chunks above the truncate mark. +*/ +static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){ + multiplexConn *p = (multiplexConn*)pConn; + multiplexGroup *pGroup = p->pGroup; + int rc = SQLITE_OK; + int rc2; + int i; + sqlite3_file *pSubOpen; + sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ + multiplexEnter(); + memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); + /* delete the chunks above the truncate limit */ + for(i=(int)(size/gMultiplex.nChunkSize)+1; ibOpen[i] ){ + pSubOpen = pGroup->pReal[i]; + rc2 = pSubOpen->pMethods->xClose(pSubOpen); + if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; + pGroup->bOpen[i] = 0; + } +#ifdef SQLITE_MULTIPLEX_EXT_OVWR + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i); +#else + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i); +#endif + rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0); + if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; + } + pSubOpen = multiplexSubOpen(p, (int)(size/gMultiplex.nChunkSize), &rc2, NULL); + if( pSubOpen ){ + rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size%gMultiplex.nChunkSize); + if( rc2!=SQLITE_OK ) rc = rc2; + }else{ + rc = SQLITE_IOERR_TRUNCATE; + } + multiplexLeave(); + return rc; +} + +/* Pass xSync requests through to the original VFS without change +*/ +static int multiplexSync(sqlite3_file *pConn, int flags){ + multiplexConn *p = (multiplexConn*)pConn; + multiplexGroup *pGroup = p->pGroup; + int rc = SQLITE_OK; + int i; + multiplexEnter(); + for(i=0; ibOpen[i] ){ + sqlite3_file *pSubOpen = pGroup->pReal[i]; + int rc2 = pSubOpen->pMethods->xSync(pSubOpen, flags); + if( rc2!=SQLITE_OK ) rc = rc2; + } + } + multiplexLeave(); + return rc; +} + +/* Pass xFileSize requests through to the original VFS. +** Aggregate the size of all the chunks before returning. +*/ +static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ + multiplexConn *p = (multiplexConn*)pConn; + multiplexGroup *pGroup = p->pGroup; + int rc = SQLITE_OK; + int rc2; + int i; + multiplexEnter(); + *pSize = 0; + for(i=0; ibOpen[i] ){ + pSubOpen = pGroup->pReal[i]; + }else{ + sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ + int exists = 0; + memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); + if( i ){ +#ifdef SQLITE_MULTIPLEX_EXT_OVWR + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i); +#else + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i); +#endif + } + rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists); + if( rc2==SQLITE_OK && exists){ + /* if it exists, open it */ + pSubOpen = multiplexSubOpen(p, i, &rc, NULL); + }else{ + /* stop at first "gap" */ + break; + } + } + if( pSubOpen ){ + sqlite3_int64 sz; + rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); + if( rc2!=SQLITE_OK ){ + rc = rc2; + }else{ + if( sz>gMultiplex.nChunkSize ){ + rc = SQLITE_IOERR_FSTAT; + } + *pSize += sz; + } + }else{ + break; + } + } + multiplexLeave(); + return rc; +} + +/* Pass xLock requests through to the original VFS unchanged. +*/ +static int multiplexLock(sqlite3_file *pConn, int lock){ + multiplexConn *p = (multiplexConn*)pConn; + int rc; + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + return pSubOpen->pMethods->xLock(pSubOpen, lock); + } + return SQLITE_BUSY; +} + +/* Pass xUnlock requests through to the original VFS unchanged. +*/ +static int multiplexUnlock(sqlite3_file *pConn, int lock){ + multiplexConn *p = (multiplexConn*)pConn; + int rc; + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + return pSubOpen->pMethods->xUnlock(pSubOpen, lock); + } + return SQLITE_IOERR_UNLOCK; +} + +/* Pass xCheckReservedLock requests through to the original VFS unchanged. +*/ +static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){ + multiplexConn *p = (multiplexConn*)pConn; + int rc; + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut); + } + return SQLITE_IOERR_CHECKRESERVEDLOCK; +} + +/* Pass xFileControl requests through to the original VFS unchanged. +*/ +static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){ + multiplexConn *p = (multiplexConn*)pConn; + int rc; + sqlite3_file *pSubOpen; + if ( op==SQLITE_FCNTL_SIZE_HINT || op==SQLITE_FCNTL_CHUNK_SIZE ) return SQLITE_OK; + pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); + } + return SQLITE_ERROR; +} + +/* Pass xSectorSize requests through to the original VFS unchanged. +*/ +static int multiplexSectorSize(sqlite3_file *pConn){ + multiplexConn *p = (multiplexConn*)pConn; + int rc; + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + return pSubOpen->pMethods->xSectorSize(pSubOpen); + } + return SQLITE_DEFAULT_SECTOR_SIZE; +} + +/* Pass xDeviceCharacteristics requests through to the original VFS unchanged. +*/ +static int multiplexDeviceCharacteristics(sqlite3_file *pConn){ + multiplexConn *p = (multiplexConn*)pConn; + int rc; + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen); + } + return 0; +} + +/* Pass xShmMap requests through to the original VFS unchanged. +*/ +static int multiplexShmMap( + sqlite3_file *pConn, /* Handle open on database file */ + int iRegion, /* Region to retrieve */ + int szRegion, /* Size of regions */ + int bExtend, /* True to extend file if necessary */ + void volatile **pp /* OUT: Mapped memory */ +){ + multiplexConn *p = (multiplexConn*)pConn; + int rc; + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp); + } + return SQLITE_IOERR; +} + +/* Pass xShmLock requests through to the original VFS unchanged. +*/ +static int multiplexShmLock( + sqlite3_file *pConn, /* Database file holding the shared memory */ + int ofst, /* First lock to acquire or release */ + int n, /* Number of locks to acquire or release */ + int flags /* What to do with the lock */ +){ + multiplexConn *p = (multiplexConn*)pConn; + int rc; + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags); + } + return SQLITE_BUSY; +} + +/* Pass xShmBarrier requests through to the original VFS unchanged. +*/ +static void multiplexShmBarrier(sqlite3_file *pConn){ + multiplexConn *p = (multiplexConn*)pConn; + int rc; + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + pSubOpen->pMethods->xShmBarrier(pSubOpen); + } +} + +/* Pass xShmUnmap requests through to the original VFS unchanged. +*/ +static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){ + multiplexConn *p = (multiplexConn*)pConn; + int rc; + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag); + } + return SQLITE_OK; +} + +/************************** Public Interfaces *****************************/ +/* +** Initialize the multiplex VFS shim. Use the VFS named zOrigVfsName +** as the VFS that does the actual work. Use the default if +** zOrigVfsName==NULL. +** +** The multiplex VFS shim is named "multiplex". It will become the default +** VFS if makeDefault is non-zero. +** +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once +** during start-up. +*/ +int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){ + sqlite3_vfs *pOrigVfs; + if( gMultiplex.isInitialized ) return SQLITE_MISUSE; + pOrigVfs = sqlite3_vfs_find(zOrigVfsName); + if( pOrigVfs==0 ) return SQLITE_ERROR; + assert( pOrigVfs!=&gMultiplex.sThisVfs ); + gMultiplex.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( !gMultiplex.pMutex ){ + return SQLITE_NOMEM; + } + gMultiplex.zName = sqlite3_malloc(pOrigVfs->mxPathname); + if( !gMultiplex.zName ){ + sqlite3_mutex_free(gMultiplex.pMutex); + return SQLITE_NOMEM; + } + gMultiplex.nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE; + gMultiplex.nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS; + gMultiplex.pGroups = NULL; + gMultiplex.isInitialized = 1; + gMultiplex.pOrigVfs = pOrigVfs; + gMultiplex.sThisVfs = *pOrigVfs; + gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn); + gMultiplex.sThisVfs.zName = "multiplex"; + gMultiplex.sThisVfs.xOpen = multiplexOpen; + gMultiplex.sThisVfs.xDelete = multiplexDelete; + gMultiplex.sThisVfs.xAccess = multiplexAccess; + gMultiplex.sThisVfs.xFullPathname = multiplexFullPathname; + gMultiplex.sThisVfs.xDlOpen = multiplexDlOpen; + gMultiplex.sThisVfs.xDlError = multiplexDlError; + gMultiplex.sThisVfs.xDlSym = multiplexDlSym; + gMultiplex.sThisVfs.xDlClose = multiplexDlClose; + gMultiplex.sThisVfs.xRandomness = multiplexRandomness; + gMultiplex.sThisVfs.xSleep = multiplexSleep; + gMultiplex.sThisVfs.xCurrentTime = multiplexCurrentTime; + gMultiplex.sThisVfs.xGetLastError = multiplexGetLastError; + gMultiplex.sThisVfs.xCurrentTimeInt64 = multiplexCurrentTimeInt64; + + gMultiplex.sIoMethodsV1.iVersion = 1; + gMultiplex.sIoMethodsV1.xClose = multiplexClose; + gMultiplex.sIoMethodsV1.xRead = multiplexRead; + gMultiplex.sIoMethodsV1.xWrite = multiplexWrite; + gMultiplex.sIoMethodsV1.xTruncate = multiplexTruncate; + gMultiplex.sIoMethodsV1.xSync = multiplexSync; + gMultiplex.sIoMethodsV1.xFileSize = multiplexFileSize; + gMultiplex.sIoMethodsV1.xLock = multiplexLock; + gMultiplex.sIoMethodsV1.xUnlock = multiplexUnlock; + gMultiplex.sIoMethodsV1.xCheckReservedLock = multiplexCheckReservedLock; + gMultiplex.sIoMethodsV1.xFileControl = multiplexFileControl; + gMultiplex.sIoMethodsV1.xSectorSize = multiplexSectorSize; + gMultiplex.sIoMethodsV1.xDeviceCharacteristics = multiplexDeviceCharacteristics; + gMultiplex.sIoMethodsV2 = gMultiplex.sIoMethodsV1; + gMultiplex.sIoMethodsV2.iVersion = 2; + gMultiplex.sIoMethodsV2.xShmMap = multiplexShmMap; + gMultiplex.sIoMethodsV2.xShmLock = multiplexShmLock; + gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier; + gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap; + sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault); + return SQLITE_OK; +} + +/* +** Shutdown the multiplex system. +** +** All SQLite database connections must be closed before calling this +** routine. +** +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while +** shutting down in order to free all remaining multiplex groups. +*/ +int sqlite3_multiplex_shutdown(void){ + if( gMultiplex.isInitialized==0 ) return SQLITE_MISUSE; + if( gMultiplex.pGroups ) return SQLITE_MISUSE; + gMultiplex.isInitialized = 0; + sqlite3_free(gMultiplex.zName); + sqlite3_mutex_free(gMultiplex.pMutex); + sqlite3_vfs_unregister(&gMultiplex.sThisVfs); + memset(&gMultiplex, 0, sizeof(gMultiplex)); + return SQLITE_OK; +} + +/* +** Adjust chunking params. VFS should be initialized first. +** No files should be open. Re-intializing will reset these +** to the default. +*/ +int sqlite3_multiplex_set( + int nChunkSize, /* Max chunk size */ + int nMaxChunks /* Max number of chunks */ +){ + if( !gMultiplex.isInitialized ) return SQLITE_MISUSE; + if( gMultiplex.pGroups ) return SQLITE_MISUSE; + if( nChunkSize<32 ) return SQLITE_MISUSE; + if( nMaxChunks<1 ) return SQLITE_MISUSE; + if( nMaxChunks>99 ) return SQLITE_MISUSE; + multiplexEnter(); + gMultiplex.nChunkSize = nChunkSize; + gMultiplex.nMaxChunks = nMaxChunks; + multiplexLeave(); + return SQLITE_OK; +} + +/***************************** Test Code ***********************************/ +#ifdef SQLITE_TEST +#include + +extern const char *sqlite3TestErrorName(int); + + +/* +** tclcmd: sqlite3_multiplex_initialize NAME MAKEDEFAULT +*/ +static int test_multiplex_initialize( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + const char *zName; /* Name of new multiplex VFS */ + int makeDefault; /* True to make the new VFS the default */ + int rc; /* Value returned by multiplex_initialize() */ + + UNUSED_PARAMETER(clientData); + + /* Process arguments */ + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT"); + return TCL_ERROR; + } + zName = Tcl_GetString(objv[1]); + if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR; + if( zName[0]=='\0' ) zName = 0; + + /* Call sqlite3_multiplex_initialize() */ + rc = sqlite3_multiplex_initialize(zName, makeDefault); + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); + + return TCL_OK; +} + +/* +** tclcmd: sqlite3_multiplex_shutdown +*/ +static int test_multiplex_shutdown( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; /* Value returned by multiplex_shutdown() */ + + UNUSED_PARAMETER(clientData); + + if( objc!=1 ){ + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + + /* Call sqlite3_multiplex_shutdown() */ + rc = sqlite3_multiplex_shutdown(); + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); + + return TCL_OK; +} + +/* +** tclcmd: sqlite3_multiplex_set CHUNK_SIZE MAX_CHUNKS +*/ +static int test_multiplex_set( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int nChunkSize; /* Max chunk size */ + int nMaxChunks; /* Max number of chunks */ + int rc; /* Value returned by sqlite3_multiplex_set() */ + + UNUSED_PARAMETER(clientData); + + /* Process arguments */ + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "CHUNK_SIZE MAX_CHUNKS"); + return TCL_ERROR; + } + if( Tcl_GetIntFromObj(interp, objv[1], &nChunkSize) ) return TCL_ERROR; + if( Tcl_GetIntFromObj(interp, objv[2], &nMaxChunks) ) return TCL_ERROR; + + /* Invoke sqlite3_multiplex_set() */ + rc = sqlite3_multiplex_set(nChunkSize, nMaxChunks); + + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_multiplex_dump +*/ +static int test_multiplex_dump( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + Tcl_Obj *pResult; + Tcl_Obj *pGroupTerm; + multiplexGroup *pGroup; + int i; + int nChunks = 0; + + UNUSED_PARAMETER(clientData); + UNUSED_PARAMETER(objc); + UNUSED_PARAMETER(objv); + + pResult = Tcl_NewObj(); + multiplexEnter(); + for(pGroup=gMultiplex.pGroups; pGroup; pGroup=pGroup->pNext){ + pGroupTerm = Tcl_NewObj(); + + pGroup->zName[pGroup->nName] = '\0'; + Tcl_ListObjAppendElement(interp, pGroupTerm, + Tcl_NewStringObj(pGroup->zName, -1)); + Tcl_ListObjAppendElement(interp, pGroupTerm, + Tcl_NewIntObj(pGroup->nName)); + Tcl_ListObjAppendElement(interp, pGroupTerm, + Tcl_NewIntObj(pGroup->flags)); + + /* count number of chunks with open handles */ + for(i=0; ibOpen[i] ) nChunks++; + } + Tcl_ListObjAppendElement(interp, pGroupTerm, + Tcl_NewIntObj(nChunks)); + + Tcl_ListObjAppendElement(interp, pGroupTerm, + Tcl_NewIntObj(gMultiplex.nChunkSize)); + Tcl_ListObjAppendElement(interp, pGroupTerm, + Tcl_NewIntObj(gMultiplex.nMaxChunks)); + + Tcl_ListObjAppendElement(interp, pResult, pGroupTerm); + } + multiplexLeave(); + Tcl_SetObjResult(interp, pResult); + return TCL_OK; +} + +/* +** This routine registers the custom TCL commands defined in this +** module. This should be the only procedure visible from outside +** of this module. +*/ +int Sqlitemultiplex_Init(Tcl_Interp *interp){ + static struct { + char *zName; + Tcl_ObjCmdProc *xProc; + } aCmd[] = { + { "sqlite3_multiplex_initialize", test_multiplex_initialize }, + { "sqlite3_multiplex_shutdown", test_multiplex_shutdown }, + { "sqlite3_multiplex_set", test_multiplex_set }, + { "sqlite3_multiplex_dump", test_multiplex_dump }, + }; + int i; + + for(i=0; i +#include + +/* +** For an build without mutexes, no-op the mutex calls. +*/ +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 +#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) +#define sqlite3_mutex_free(X) +#define sqlite3_mutex_enter(X) +#define sqlite3_mutex_try(X) SQLITE_OK +#define sqlite3_mutex_leave(X) +#define sqlite3_mutex_held(X) ((void)(X),1) +#define sqlite3_mutex_notheld(X) ((void)(X),1) +#endif /* SQLITE_THREADSAFE==0 */ + + +/************************ Object Definitions ******************************/ + +/* Forward declaration of all object types */ +typedef struct quotaGroup quotaGroup; +typedef struct quotaConn quotaConn; +typedef struct quotaFile quotaFile; + +/* +** A "quota group" is a collection of files whose collective size we want +** to limit. Each quota group is defined by a GLOB pattern. +** +** There is an instance of the following object for each defined quota +** group. This object records the GLOB pattern that defines which files +** belong to the quota group. The object also remembers the size limit +** for the group (the quota) and the callback to be invoked when the +** sum of the sizes of the files within the group goes over the limit. +** +** A quota group must be established (using sqlite3_quota_set(...)) +** prior to opening any of the database connections that access files +** within the quota group. +*/ +struct quotaGroup { + const char *zPattern; /* Filename pattern to be quotaed */ + sqlite3_int64 iLimit; /* Upper bound on total file size */ + sqlite3_int64 iSize; /* Current size of all files */ + void (*xCallback)( /* Callback invoked when going over quota */ + const char *zFilename, /* Name of file whose size increases */ + sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ + sqlite3_int64 iSize, /* Total size of all files in the group */ + void *pArg /* Client data */ + ); + void *pArg; /* Third argument to the xCallback() */ + void (*xDestroy)(void*); /* Optional destructor for pArg */ + quotaGroup *pNext, **ppPrev; /* Doubly linked list of all quota objects */ + quotaFile *pFiles; /* Files within this group */ +}; + +/* +** An instance of this structure represents a single file that is part +** of a quota group. A single file can be opened multiple times. In +** order keep multiple openings of the same file from causing the size +** of the file to count against the quota multiple times, each file +** has a unique instance of this object and multiple open connections +** to the same file each point to a single instance of this object. +*/ +struct quotaFile { + char *zFilename; /* Name of this file */ + quotaGroup *pGroup; /* Quota group to which this file belongs */ + sqlite3_int64 iSize; /* Current size of this file */ + int nRef; /* Number of times this file is open */ + quotaFile *pNext, **ppPrev; /* Linked list of files in the same group */ +}; + +/* +** An instance of the following object represents each open connection +** to a file that participates in quota tracking. This object is a +** subclass of sqlite3_file. The sqlite3_file object for the underlying +** VFS is appended to this structure. +*/ +struct quotaConn { + sqlite3_file base; /* Base class - must be first */ + quotaFile *pFile; /* The underlying file */ + /* The underlying VFS sqlite3_file is appended to this object */ +}; + +/************************* Global Variables **********************************/ +/* +** All global variables used by this file are containing within the following +** gQuota structure. +*/ +static struct { + /* The pOrigVfs is the real, original underlying VFS implementation. + ** Most operations pass-through to the real VFS. This value is read-only + ** during operation. It is only modified at start-time and thus does not + ** require a mutex. + */ + sqlite3_vfs *pOrigVfs; + + /* The sThisVfs is the VFS structure used by this shim. It is initialized + ** at start-time and thus does not require a mutex + */ + sqlite3_vfs sThisVfs; + + /* The sIoMethods defines the methods used by sqlite3_file objects + ** associated with this shim. It is initialized at start-time and does + ** not require a mutex. + ** + ** When the underlying VFS is called to open a file, it might return + ** either a version 1 or a version 2 sqlite3_file object. This shim + ** has to create a wrapper sqlite3_file of the same version. Hence + ** there are two I/O method structures, one for version 1 and the other + ** for version 2. + */ + sqlite3_io_methods sIoMethodsV1; + sqlite3_io_methods sIoMethodsV2; + + /* True when this shim as been initialized. + */ + int isInitialized; + + /* For run-time access any of the other global data structures in this + ** shim, the following mutex must be held. + */ + sqlite3_mutex *pMutex; + + /* List of quotaGroup objects. + */ + quotaGroup *pGroup; + +} gQuota; + +/************************* Utility Routines *********************************/ +/* +** Acquire and release the mutex used to serialize access to the +** list of quotaGroups. +*/ +static void quotaEnter(void){ sqlite3_mutex_enter(gQuota.pMutex); } +static void quotaLeave(void){ sqlite3_mutex_leave(gQuota.pMutex); } + + +/* If the reference count and threshold for a quotaGroup are both +** zero, then destroy the quotaGroup. +*/ +static void quotaGroupDeref(quotaGroup *pGroup){ + if( pGroup->pFiles==0 && pGroup->iLimit==0 ){ + *pGroup->ppPrev = pGroup->pNext; + if( pGroup->pNext ) pGroup->pNext->ppPrev = pGroup->ppPrev; + if( pGroup->xDestroy ) pGroup->xDestroy(pGroup->pArg); + sqlite3_free(pGroup); + } +} + +/* +** Return TRUE if string z matches glob pattern zGlob. +** +** 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. +** +*/ +static int quotaStrglob(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 && quotaStrglob(zGlob-1,z)==0 ){ + z++; + } + return (*z)!=0; + } + while( (c2 = (*(z++)))!=0 ){ + while( c2!=c ){ + c2 = *(z++); + if( c2==0 ) return 0; + } + if( quotaStrglob(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; +} + + +/* Find a quotaGroup given the filename. +** +** Return a pointer to the quotaGroup object. Return NULL if not found. +*/ +static quotaGroup *quotaGroupFind(const char *zFilename){ + quotaGroup *p; + for(p=gQuota.pGroup; p && quotaStrglob(p->zPattern, zFilename)==0; + p=p->pNext){} + return p; +} + +/* Translate an sqlite3_file* that is really a quotaConn* into +** the sqlite3_file* for the underlying original VFS. +*/ +static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){ + quotaConn *p = (quotaConn*)pConn; + return (sqlite3_file*)&p[1]; +} + +/************************* VFS Method Wrappers *****************************/ +/* +** This is the xOpen method used for the "quota" VFS. +** +** Most of the work is done by the underlying original VFS. This method +** simply links the new file into the appropriate quota group if it is a +** file that needs to be tracked. +*/ +static int quotaOpen( + sqlite3_vfs *pVfs, /* The quota VFS */ + const char *zName, /* Name of file to be opened */ + sqlite3_file *pConn, /* Fill in this file descriptor */ + int flags, /* Flags to control the opening */ + int *pOutFlags /* Flags showing results of opening */ +){ + int rc; /* Result code */ + quotaConn *pQuotaOpen; /* The new quota file descriptor */ + quotaFile *pFile; /* Corresponding quotaFile obj */ + quotaGroup *pGroup; /* The group file belongs to */ + sqlite3_file *pSubOpen; /* Real file descriptor */ + sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */ + + /* If the file is not a main database file or a WAL, then use the + ** normal xOpen method. + */ + if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){ + return pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags); + } + + /* If the name of the file does not match any quota group, then + ** use the normal xOpen method. + */ + quotaEnter(); + pGroup = quotaGroupFind(zName); + if( pGroup==0 ){ + rc = pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags); + }else{ + /* If we get to this point, it means the file needs to be quota tracked. + */ + pQuotaOpen = (quotaConn*)pConn; + pSubOpen = quotaSubOpen(pConn); + rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags); + if( rc==SQLITE_OK ){ + for(pFile=pGroup->pFiles; pFile && strcmp(pFile->zFilename, zName); + pFile=pFile->pNext){} + if( pFile==0 ){ + int nName = strlen(zName); + pFile = sqlite3_malloc( sizeof(*pFile) + nName + 1 ); + if( pFile==0 ){ + quotaLeave(); + pSubOpen->pMethods->xClose(pSubOpen); + return SQLITE_NOMEM; + } + memset(pFile, 0, sizeof(*pFile)); + pFile->zFilename = (char*)&pFile[1]; + memcpy(pFile->zFilename, zName, nName+1); + pFile->pNext = pGroup->pFiles; + if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext; + pFile->ppPrev = &pGroup->pFiles; + pGroup->pFiles = pFile; + pFile->pGroup = pGroup; + } + pFile->nRef++; + pQuotaOpen->pFile = pFile; + if( pSubOpen->pMethods->iVersion==1 ){ + pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1; + }else{ + pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2; + } + } + } + quotaLeave(); + return rc; +} + +/************************ I/O Method Wrappers *******************************/ + +/* xClose requests get passed through to the original VFS. But we +** also have to unlink the quotaConn from the quotaFile and quotaGroup. +** The quotaFile and/or quotaGroup are freed if they are no longer in use. +*/ +static int quotaClose(sqlite3_file *pConn){ + quotaConn *p = (quotaConn*)pConn; + quotaFile *pFile = p->pFile; + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + int rc; + rc = pSubOpen->pMethods->xClose(pSubOpen); + quotaEnter(); + pFile->nRef--; + if( pFile->nRef==0 ){ + quotaGroup *pGroup = pFile->pGroup; + pGroup->iSize -= pFile->iSize; + if( pFile->pNext ) pFile->pNext->ppPrev = pFile->ppPrev; + *pFile->ppPrev = pFile->pNext; + quotaGroupDeref(pGroup); + sqlite3_free(pFile); + } + quotaLeave(); + return rc; +} + +/* Pass xRead requests directory thru to the original VFS without +** further processing. +*/ +static int quotaRead( + sqlite3_file *pConn, + void *pBuf, + int iAmt, + sqlite3_int64 iOfst +){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + return pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst); +} + +/* Check xWrite requests to see if they expand the file. If they do, +** the perform a quota check before passing them through to the +** original VFS. +*/ +static int quotaWrite( + sqlite3_file *pConn, + const void *pBuf, + int iAmt, + sqlite3_int64 iOfst +){ + quotaConn *p = (quotaConn*)pConn; + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + sqlite3_int64 iEnd = iOfst+iAmt; + quotaGroup *pGroup; + quotaFile *pFile = p->pFile; + sqlite3_int64 szNew; + + if( pFile->iSizepGroup; + quotaEnter(); + szNew = pGroup->iSize - pFile->iSize + iEnd; + if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ + if( pGroup->xCallback ){ + pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, + pGroup->pArg); + } + if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ + quotaLeave(); + return SQLITE_FULL; + } + } + pGroup->iSize = szNew; + pFile->iSize = iEnd; + quotaLeave(); + } + return pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst); +} + +/* Pass xTruncate requests thru to the original VFS. If the +** success, update the file size. +*/ +static int quotaTruncate(sqlite3_file *pConn, sqlite3_int64 size){ + quotaConn *p = (quotaConn*)pConn; + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + int rc = pSubOpen->pMethods->xTruncate(pSubOpen, size); + quotaFile *pFile = p->pFile; + quotaGroup *pGroup; + if( rc==SQLITE_OK ){ + quotaEnter(); + pGroup = pFile->pGroup; + pGroup->iSize -= pFile->iSize; + pFile->iSize = size; + pGroup->iSize += size; + quotaLeave(); + } + return rc; +} + +/* Pass xSync requests through to the original VFS without change +*/ +static int quotaSync(sqlite3_file *pConn, int flags){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + return pSubOpen->pMethods->xSync(pSubOpen, flags); +} + +/* Pass xFileSize requests through to the original VFS but then +** update the quotaGroup with the new size before returning. +*/ +static int quotaFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ + quotaConn *p = (quotaConn*)pConn; + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + quotaFile *pFile = p->pFile; + quotaGroup *pGroup; + sqlite3_int64 sz; + int rc; + + rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); + if( rc==SQLITE_OK ){ + quotaEnter(); + pGroup = pFile->pGroup; + pGroup->iSize -= pFile->iSize; + pFile->iSize = sz; + pGroup->iSize += sz; + quotaLeave(); + *pSize = sz; + } + return rc; +} + +/* Pass xLock requests through to the original VFS unchanged. +*/ +static int quotaLock(sqlite3_file *pConn, int lock){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + return pSubOpen->pMethods->xLock(pSubOpen, lock); +} + +/* Pass xUnlock requests through to the original VFS unchanged. +*/ +static int quotaUnlock(sqlite3_file *pConn, int lock){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + return pSubOpen->pMethods->xUnlock(pSubOpen, lock); +} + +/* Pass xCheckReservedLock requests through to the original VFS unchanged. +*/ +static int quotaCheckReservedLock(sqlite3_file *pConn, int *pResOut){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut); +} + +/* Pass xFileControl requests through to the original VFS unchanged. +*/ +static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); +} + +/* Pass xSectorSize requests through to the original VFS unchanged. +*/ +static int quotaSectorSize(sqlite3_file *pConn){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + return pSubOpen->pMethods->xSectorSize(pSubOpen); +} + +/* Pass xDeviceCharacteristics requests through to the original VFS unchanged. +*/ +static int quotaDeviceCharacteristics(sqlite3_file *pConn){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen); +} + +/* Pass xShmMap requests through to the original VFS unchanged. +*/ +static int quotaShmMap( + sqlite3_file *pConn, /* Handle open on database file */ + int iRegion, /* Region to retrieve */ + int szRegion, /* Size of regions */ + int bExtend, /* True to extend file if necessary */ + void volatile **pp /* OUT: Mapped memory */ +){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp); +} + +/* Pass xShmLock requests through to the original VFS unchanged. +*/ +static int quotaShmLock( + sqlite3_file *pConn, /* Database file holding the shared memory */ + int ofst, /* First lock to acquire or release */ + int n, /* Number of locks to acquire or release */ + int flags /* What to do with the lock */ +){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags); +} + +/* Pass xShmBarrier requests through to the original VFS unchanged. +*/ +static void quotaShmBarrier(sqlite3_file *pConn){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + pSubOpen->pMethods->xShmBarrier(pSubOpen); +} + +/* Pass xShmUnmap requests through to the original VFS unchanged. +*/ +static int quotaShmUnmap(sqlite3_file *pConn, int deleteFlag){ + sqlite3_file *pSubOpen = quotaSubOpen(pConn); + return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag); +} + +/************************** Public Interfaces *****************************/ +/* +** Initialize the quota VFS shim. Use the VFS named zOrigVfsName +** as the VFS that does the actual work. Use the default if +** zOrigVfsName==NULL. +** +** The quota VFS shim is named "quota". It will become the default +** VFS if makeDefault is non-zero. +** +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once +** during start-up. +*/ +int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault){ + sqlite3_vfs *pOrigVfs; + if( gQuota.isInitialized ) return SQLITE_MISUSE; + pOrigVfs = sqlite3_vfs_find(zOrigVfsName); + if( pOrigVfs==0 ) return SQLITE_ERROR; + assert( pOrigVfs!=&gQuota.sThisVfs ); + gQuota.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( !gQuota.pMutex ){ + return SQLITE_NOMEM; + } + gQuota.isInitialized = 1; + gQuota.pOrigVfs = pOrigVfs; + gQuota.sThisVfs = *pOrigVfs; + gQuota.sThisVfs.xOpen = quotaOpen; + gQuota.sThisVfs.szOsFile += sizeof(quotaConn); + gQuota.sThisVfs.zName = "quota"; + gQuota.sIoMethodsV1.iVersion = 1; + gQuota.sIoMethodsV1.xClose = quotaClose; + gQuota.sIoMethodsV1.xRead = quotaRead; + gQuota.sIoMethodsV1.xWrite = quotaWrite; + gQuota.sIoMethodsV1.xTruncate = quotaTruncate; + gQuota.sIoMethodsV1.xSync = quotaSync; + gQuota.sIoMethodsV1.xFileSize = quotaFileSize; + gQuota.sIoMethodsV1.xLock = quotaLock; + gQuota.sIoMethodsV1.xUnlock = quotaUnlock; + gQuota.sIoMethodsV1.xCheckReservedLock = quotaCheckReservedLock; + gQuota.sIoMethodsV1.xFileControl = quotaFileControl; + gQuota.sIoMethodsV1.xSectorSize = quotaSectorSize; + gQuota.sIoMethodsV1.xDeviceCharacteristics = quotaDeviceCharacteristics; + gQuota.sIoMethodsV2 = gQuota.sIoMethodsV1; + gQuota.sIoMethodsV2.iVersion = 2; + gQuota.sIoMethodsV2.xShmMap = quotaShmMap; + gQuota.sIoMethodsV2.xShmLock = quotaShmLock; + gQuota.sIoMethodsV2.xShmBarrier = quotaShmBarrier; + gQuota.sIoMethodsV2.xShmUnmap = quotaShmUnmap; + sqlite3_vfs_register(&gQuota.sThisVfs, makeDefault); + return SQLITE_OK; +} + +/* +** Shutdown the quota system. +** +** All SQLite database connections must be closed before calling this +** routine. +** +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly one while +** shutting down in order to free all remaining quota groups. +*/ +int sqlite3_quota_shutdown(void){ + quotaGroup *pGroup; + if( gQuota.isInitialized==0 ) return SQLITE_MISUSE; + for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){ + if( pGroup->pFiles ) return SQLITE_MISUSE; + } + while( gQuota.pGroup ){ + pGroup = gQuota.pGroup; + gQuota.pGroup = pGroup->pNext; + pGroup->iLimit = 0; + quotaGroupDeref(pGroup); + } + gQuota.isInitialized = 0; + sqlite3_mutex_free(gQuota.pMutex); + sqlite3_vfs_unregister(&gQuota.sThisVfs); + memset(&gQuota, 0, sizeof(gQuota)); + return SQLITE_OK; +} + +/* +** Create or destroy a quota group. +** +** The quota group is defined by the zPattern. When calling this routine +** with a zPattern for a quota group that already exists, this routine +** merely updates the iLimit, xCallback, and pArg values for that quota +** group. If zPattern is new, then a new quota group is created. +** +** If the iLimit for a quota group is set to zero, then the quota group +** is disabled and will be deleted when the last database connection using +** the quota group is closed. +** +** Calling this routine on a zPattern that does not exist and with a +** zero iLimit is a no-op. +** +** A quota group must exist with a non-zero iLimit prior to opening +** database connections if those connections are to participate in the +** quota group. Creating a quota group does not affect database connections +** that are already open. +*/ +int sqlite3_quota_set( + const char *zPattern, /* The filename pattern */ + sqlite3_int64 iLimit, /* New quota to set for this quota group */ + void (*xCallback)( /* Callback invoked when going over quota */ + const char *zFilename, /* Name of file whose size increases */ + sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ + sqlite3_int64 iSize, /* Total size of all files in the group */ + void *pArg /* Client data */ + ), + void *pArg, /* client data passed thru to callback */ + void (*xDestroy)(void*) /* Optional destructor for pArg */ +){ + quotaGroup *pGroup; + quotaEnter(); + pGroup = gQuota.pGroup; + while( pGroup && strcmp(pGroup->zPattern, zPattern)!=0 ){ + pGroup = pGroup->pNext; + } + if( pGroup==0 ){ + int nPattern = strlen(zPattern); + if( iLimit<=0 ){ + quotaLeave(); + return SQLITE_OK; + } + pGroup = sqlite3_malloc( sizeof(*pGroup) + nPattern + 1 ); + if( pGroup==0 ){ + quotaLeave(); + return SQLITE_NOMEM; + } + memset(pGroup, 0, sizeof(*pGroup)); + pGroup->zPattern = (char*)&pGroup[1]; + memcpy((char *)pGroup->zPattern, zPattern, nPattern+1); + if( gQuota.pGroup ) gQuota.pGroup->ppPrev = &pGroup->pNext; + pGroup->pNext = gQuota.pGroup; + pGroup->ppPrev = &gQuota.pGroup; + gQuota.pGroup = pGroup; + } + pGroup->iLimit = iLimit; + pGroup->xCallback = xCallback; + if( pGroup->xDestroy && pGroup->pArg!=pArg ){ + pGroup->xDestroy(pGroup->pArg); + } + pGroup->pArg = pArg; + pGroup->xDestroy = xDestroy; + quotaGroupDeref(pGroup); + quotaLeave(); + return SQLITE_OK; +} + + +/***************************** Test Code ***********************************/ +#ifdef SQLITE_TEST +#include + +/* +** Argument passed to a TCL quota-over-limit callback. +*/ +typedef struct TclQuotaCallback TclQuotaCallback; +struct TclQuotaCallback { + Tcl_Interp *interp; /* Interpreter in which to run the script */ + Tcl_Obj *pScript; /* Script to be run */ +}; + +extern const char *sqlite3TestErrorName(int); + + +/* +** This is the callback from a quota-over-limit. +*/ +static void tclQuotaCallback( + const char *zFilename, /* Name of file whose size increases */ + sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ + sqlite3_int64 iSize, /* Total size of all files in the group */ + void *pArg /* Client data */ +){ + TclQuotaCallback *p; /* Callback script object */ + Tcl_Obj *pEval; /* Script to evaluate */ + Tcl_Obj *pVarname; /* Name of variable to pass as 2nd arg */ + unsigned int rnd; /* Random part of pVarname */ + int rc; /* Tcl error code */ + + p = (TclQuotaCallback *)pArg; + if( p==0 ) return; + + pVarname = Tcl_NewStringObj("::piLimit_", -1); + Tcl_IncrRefCount(pVarname); + sqlite3_randomness(sizeof(rnd), (void *)&rnd); + Tcl_AppendObjToObj(pVarname, Tcl_NewIntObj((int)(rnd&0x7FFFFFFF))); + Tcl_ObjSetVar2(p->interp, pVarname, 0, Tcl_NewWideIntObj(*piLimit), 0); + + pEval = Tcl_DuplicateObj(p->pScript); + Tcl_IncrRefCount(pEval); + Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zFilename, -1)); + Tcl_ListObjAppendElement(0, pEval, pVarname); + Tcl_ListObjAppendElement(0, pEval, Tcl_NewWideIntObj(iSize)); + rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); + + if( rc==TCL_OK ){ + Tcl_Obj *pLimit = Tcl_ObjGetVar2(p->interp, pVarname, 0, 0); + rc = Tcl_GetWideIntFromObj(p->interp, pLimit, piLimit); + Tcl_UnsetVar(p->interp, Tcl_GetString(pVarname), 0); + } + + Tcl_DecrRefCount(pEval); + Tcl_DecrRefCount(pVarname); + if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp); +} + +/* +** Destructor for a TCL quota-over-limit callback. +*/ +static void tclCallbackDestructor(void *pObj){ + TclQuotaCallback *p = (TclQuotaCallback*)pObj; + if( p ){ + Tcl_DecrRefCount(p->pScript); + sqlite3_free((char *)p); + } +} + +/* +** tclcmd: sqlite3_quota_initialize NAME MAKEDEFAULT +*/ +static int test_quota_initialize( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + const char *zName; /* Name of new quota VFS */ + int makeDefault; /* True to make the new VFS the default */ + int rc; /* Value returned by quota_initialize() */ + + /* Process arguments */ + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT"); + return TCL_ERROR; + } + zName = Tcl_GetString(objv[1]); + if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR; + if( zName[0]=='\0' ) zName = 0; + + /* Call sqlite3_quota_initialize() */ + rc = sqlite3_quota_initialize(zName, makeDefault); + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); + + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_shutdown +*/ +static int test_quota_shutdown( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; /* Value returned by quota_shutdown() */ + + if( objc!=1 ){ + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + + /* Call sqlite3_quota_shutdown() */ + rc = sqlite3_quota_shutdown(); + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); + + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_set PATTERN LIMIT SCRIPT +*/ +static int test_quota_set( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + const char *zPattern; /* File pattern to configure */ + sqlite3_int64 iLimit; /* Initial quota in bytes */ + Tcl_Obj *pScript; /* Tcl script to invoke to increase quota */ + int rc; /* Value returned by quota_set() */ + TclQuotaCallback *p; /* Callback object */ + int nScript; /* Length of callback script */ + void (*xDestroy)(void*); /* Optional destructor for pArg */ + void (*xCallback)(const char *, sqlite3_int64 *, sqlite3_int64, void *); + + /* Process arguments */ + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 1, objv, "PATTERN LIMIT SCRIPT"); + return TCL_ERROR; + } + zPattern = Tcl_GetString(objv[1]); + if( Tcl_GetWideIntFromObj(interp, objv[2], &iLimit) ) return TCL_ERROR; + pScript = objv[3]; + Tcl_GetStringFromObj(pScript, &nScript); + + if( nScript>0 ){ + /* Allocate a TclQuotaCallback object */ + p = (TclQuotaCallback *)sqlite3_malloc(sizeof(TclQuotaCallback)); + if( !p ){ + Tcl_SetResult(interp, (char *)"SQLITE_NOMEM", TCL_STATIC); + return TCL_OK; + } + memset(p, 0, sizeof(TclQuotaCallback)); + p->interp = interp; + Tcl_IncrRefCount(pScript); + p->pScript = pScript; + xDestroy = tclCallbackDestructor; + xCallback = tclQuotaCallback; + }else{ + p = 0; + xDestroy = 0; + xCallback = 0; + } + + /* Invoke sqlite3_quota_set() */ + rc = sqlite3_quota_set(zPattern, iLimit, xCallback, (void*)p, xDestroy); + + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_dump +*/ +static int test_quota_dump( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + Tcl_Obj *pResult; + Tcl_Obj *pGroupTerm; + Tcl_Obj *pFileTerm; + quotaGroup *pGroup; + quotaFile *pFile; + + pResult = Tcl_NewObj(); + quotaEnter(); + for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){ + pGroupTerm = Tcl_NewObj(); + Tcl_ListObjAppendElement(interp, pGroupTerm, + Tcl_NewStringObj(pGroup->zPattern, -1)); + Tcl_ListObjAppendElement(interp, pGroupTerm, + Tcl_NewWideIntObj(pGroup->iLimit)); + Tcl_ListObjAppendElement(interp, pGroupTerm, + Tcl_NewWideIntObj(pGroup->iSize)); + for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){ + pFileTerm = Tcl_NewObj(); + Tcl_ListObjAppendElement(interp, pFileTerm, + Tcl_NewStringObj(pFile->zFilename, -1)); + Tcl_ListObjAppendElement(interp, pFileTerm, + Tcl_NewWideIntObj(pFile->iSize)); + Tcl_ListObjAppendElement(interp, pFileTerm, + Tcl_NewWideIntObj(pFile->nRef)); + Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm); + } + Tcl_ListObjAppendElement(interp, pResult, pGroupTerm); + } + quotaLeave(); + Tcl_SetObjResult(interp, pResult); + return TCL_OK; +} + +/* +** This routine registers the custom TCL commands defined in this +** module. This should be the only procedure visible from outside +** of this module. +*/ +int Sqlitequota_Init(Tcl_Interp *interp){ + static struct { + char *zName; + Tcl_ObjCmdProc *xProc; + } aCmd[] = { + { "sqlite3_quota_initialize", test_quota_initialize }, + { "sqlite3_quota_shutdown", test_quota_shutdown }, + { "sqlite3_quota_set", test_quota_set }, + { "sqlite3_quota_dump", test_quota_dump }, + }; + int i; + + for(i=0; i + +/* Solely for the UNUSED_PARAMETER() macro. */ +#include "sqliteInt.h" + +/* +** Type used to cache parameter information for the "circle" r-tree geometry +** callback. +*/ +typedef struct Circle Circle; +struct Circle { + struct Box { + double xmin; + double xmax; + double ymin; + double ymax; + } aBox[2]; + double centerx; + double centery; + double radius; +}; + +/* +** Destructor function for Circle objects allocated by circle_geom(). +*/ +static void circle_del(void *p){ + sqlite3_free(p); +} + +/* +** Implementation of "circle" r-tree geometry callback. +*/ +static int circle_geom( + sqlite3_rtree_geometry *p, + int nCoord, + double *aCoord, + int *pRes +){ + int i; /* Iterator variable */ + Circle *pCircle; /* Structure defining circular region */ + double xmin, xmax; /* X dimensions of box being tested */ + double ymin, ymax; /* X dimensions of box being tested */ + + if( p->pUser==0 ){ + /* If pUser is still 0, then the parameter values have not been tested + ** for correctness or stored into a Circle structure yet. Do this now. */ + + /* This geometry callback is for use with a 2-dimensional r-tree table. + ** Return an error if the table does not have exactly 2 dimensions. */ + if( nCoord!=4 ) return SQLITE_ERROR; + + /* Test that the correct number of parameters (3) have been supplied, + ** and that the parameters are in range (that the radius of the circle + ** radius is greater than zero). */ + if( p->nParam!=3 || p->aParam[2]<0.0 ) return SQLITE_ERROR; + + /* Allocate a structure to cache parameter data in. Return SQLITE_NOMEM + ** if the allocation fails. */ + pCircle = (Circle *)(p->pUser = sqlite3_malloc(sizeof(Circle))); + if( !pCircle ) return SQLITE_NOMEM; + p->xDelUser = circle_del; + + /* Record the center and radius of the circular region. One way that + ** tested bounding boxes that intersect the circular region are detected + ** is by testing if each corner of the bounding box lies within radius + ** units of the center of the circle. */ + pCircle->centerx = p->aParam[0]; + pCircle->centery = p->aParam[1]; + pCircle->radius = p->aParam[2]; + + /* Define two bounding box regions. The first, aBox[0], extends to + ** infinity in the X dimension. It covers the same range of the Y dimension + ** as the circular region. The second, aBox[1], extends to infinity in + ** the Y dimension and is constrained to the range of the circle in the + ** X dimension. + ** + ** Then imagine each box is split in half along its short axis by a line + ** that intersects the center of the circular region. A bounding box + ** being tested can be said to intersect the circular region if it contains + ** points from each half of either of the two infinite bounding boxes. + */ + pCircle->aBox[0].xmin = pCircle->centerx; + pCircle->aBox[0].xmax = pCircle->centerx; + pCircle->aBox[0].ymin = pCircle->centery + pCircle->radius; + pCircle->aBox[0].ymax = pCircle->centery - pCircle->radius; + pCircle->aBox[1].xmin = pCircle->centerx + pCircle->radius; + pCircle->aBox[1].xmax = pCircle->centerx - pCircle->radius; + pCircle->aBox[1].ymin = pCircle->centery; + pCircle->aBox[1].ymax = pCircle->centery; + } + + pCircle = (Circle *)p->pUser; + xmin = aCoord[0]; + xmax = aCoord[1]; + ymin = aCoord[2]; + ymax = aCoord[3]; + + /* Check if any of the 4 corners of the bounding-box being tested lie + ** inside the circular region. If they do, then the bounding-box does + ** intersect the region of interest. Set the output variable to true and + ** return SQLITE_OK in this case. */ + for(i=0; i<4; i++){ + double x = (i&0x01) ? xmax : xmin; + double y = (i&0x02) ? ymax : ymin; + double d2; + + d2 = (x-pCircle->centerx)*(x-pCircle->centerx); + d2 += (y-pCircle->centery)*(y-pCircle->centery); + if( d2<(pCircle->radius*pCircle->radius) ){ + *pRes = 1; + return SQLITE_OK; + } + } + + /* Check if the bounding box covers any other part of the circular region. + ** See comments above for a description of how this test works. If it does + ** cover part of the circular region, set the output variable to true + ** and return SQLITE_OK. */ + for(i=0; i<2; i++){ + if( xmin<=pCircle->aBox[i].xmin + && xmax>=pCircle->aBox[i].xmax + && ymin<=pCircle->aBox[i].ymin + && ymax>=pCircle->aBox[i].ymax + ){ + *pRes = 1; + return SQLITE_OK; + } + } + + /* The specified bounding box does not intersect the circular region. Set + ** the output variable to zero and return SQLITE_OK. */ + *pRes = 0; + return SQLITE_OK; +} + +/* END of implementation of "circle" geometry callback. +************************************************************************** +*************************************************************************/ + +#include +#include "tcl.h" + +typedef struct Cube Cube; +struct Cube { + double x; + double y; + double z; + double width; + double height; + double depth; +}; + +static void cube_context_free(void *p){ + sqlite3_free(p); +} + +/* +** The context pointer registered along with the 'cube' callback is +** always ((void *)&gHere). This is just to facilitate testing, it is not +** actually used for anything. +*/ +static int gHere = 42; + +/* +** Implementation of a simple r-tree geom callback to test for intersection +** of r-tree rows with a "cube" shape. Cubes are defined by six scalar +** coordinates as follows: +** +** cube(x, y, z, width, height, depth) +** +** The width, height and depth parameters must all be greater than zero. +*/ +static int cube_geom( + sqlite3_rtree_geometry *p, + int nCoord, + double *aCoord, + int *piRes +){ + Cube *pCube = (Cube *)p->pUser; + + assert( p->pContext==(void *)&gHere ); + + if( pCube==0 ){ + if( p->nParam!=6 || nCoord!=6 + || p->aParam[3]<=0.0 || p->aParam[4]<=0.0 || p->aParam[5]<=0.0 + ){ + return SQLITE_ERROR; + } + pCube = (Cube *)sqlite3_malloc(sizeof(Cube)); + if( !pCube ){ + return SQLITE_NOMEM; + } + pCube->x = p->aParam[0]; + pCube->y = p->aParam[1]; + pCube->z = p->aParam[2]; + pCube->width = p->aParam[3]; + pCube->height = p->aParam[4]; + pCube->depth = p->aParam[5]; + + p->pUser = (void *)pCube; + p->xDelUser = cube_context_free; + } + + assert( nCoord==6 ); + *piRes = 0; + if( aCoord[0]<=(pCube->x+pCube->width) + && aCoord[1]>=pCube->x + && aCoord[2]<=(pCube->y+pCube->height) + && aCoord[3]>=pCube->y + && aCoord[4]<=(pCube->z+pCube->depth) + && aCoord[5]>=pCube->z + ){ + *piRes = 1; + } + + return SQLITE_OK; +} + +static int register_cube_geom( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ +#ifndef SQLITE_ENABLE_RTREE + UNUSED_PARAMETER(clientData); + UNUSED_PARAMETER(interp); + UNUSED_PARAMETER(objc); + UNUSED_PARAMETER(objv); +#else + extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**); + extern const char *sqlite3TestErrorName(int); + sqlite3 *db; + int rc; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + rc = sqlite3_rtree_geometry_callback(db, "cube", cube_geom, (void *)&gHere); + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); +#endif + return TCL_OK; +} + +static int register_circle_geom( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ +#ifndef SQLITE_ENABLE_RTREE + UNUSED_PARAMETER(clientData); + UNUSED_PARAMETER(interp); + UNUSED_PARAMETER(objc); + UNUSED_PARAMETER(objv); +#else + extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**); + extern const char *sqlite3TestErrorName(int); + sqlite3 *db; + int rc; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + rc = sqlite3_rtree_geometry_callback(db, "circle", circle_geom, 0); + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); +#endif + return TCL_OK; +} + +int Sqlitetestrtree_Init(Tcl_Interp *interp){ + Tcl_CreateObjCommand(interp, "register_cube_geom", register_cube_geom, 0, 0); + Tcl_CreateObjCommand(interp, "register_circle_geom",register_circle_geom,0,0); + return TCL_OK; +} diff --git a/src/test_superlock.c b/src/test_superlock.c new file mode 100644 index 00000000..936fcad0 --- /dev/null +++ b/src/test_superlock.c @@ -0,0 +1,356 @@ +/* +** 2010 November 19 +** +** 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. +** +************************************************************************* +** Example code for obtaining an exclusive lock on an SQLite database +** file. This method is complicated, but works for both WAL and rollback +** mode database files. The interface to the example code in this file +** consists of the following two functions: +** +** sqlite3demo_superlock() +** sqlite3demo_superunlock() +*/ + +#include +#include /* memset(), strlen() */ +#include /* assert() */ + +/* +** A structure to collect a busy-handler callback and argument and a count +** of the number of times it has been invoked. +*/ +struct SuperlockBusy { + int (*xBusy)(void*,int); /* Pointer to busy-handler function */ + void *pBusyArg; /* First arg to pass to xBusy */ + int nBusy; /* Number of times xBusy has been invoked */ +}; +typedef struct SuperlockBusy SuperlockBusy; + +/* +** An instance of the following structure is allocated for each active +** superlock. The opaque handle returned by sqlite3demo_superlock() is +** actually a pointer to an instance of this structure. +*/ +struct Superlock { + sqlite3 *db; /* Database handle used to lock db */ + int bWal; /* True if db is a WAL database */ +}; +typedef struct Superlock Superlock; + +/* +** The pCtx pointer passed to this function is actually a pointer to a +** SuperlockBusy structure. Invoke the busy-handler function encapsulated +** by the structure and return the result. +*/ +static int superlockBusyHandler(void *pCtx, int UNUSED){ + SuperlockBusy *pBusy = (SuperlockBusy *)pCtx; + if( pBusy->xBusy==0 ) return 0; + return pBusy->xBusy(pBusy->pBusyArg, pBusy->nBusy++); +} + +/* +** This function is used to determine if the main database file for +** connection db is open in WAL mode or not. If no error occurs and the +** database file is in WAL mode, set *pbWal to true and return SQLITE_OK. +** If it is not in WAL mode, set *pbWal to false. +** +** If an error occurs, return an SQLite error code. The value of *pbWal +** is undefined in this case. +*/ +static int superlockIsWal(Superlock *pLock){ + int rc; /* Return Code */ + sqlite3_stmt *pStmt; /* Compiled PRAGMA journal_mode statement */ + + rc = sqlite3_prepare(pLock->db, "PRAGMA main.journal_mode", -1, &pStmt, 0); + if( rc!=SQLITE_OK ) return rc; + + pLock->bWal = 0; + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zMode = (const char *)sqlite3_column_text(pStmt, 0); + if( zMode && strlen(zMode)==3 && sqlite3_strnicmp("wal", zMode, 3)==0 ){ + pLock->bWal = 1; + } + } + + return sqlite3_finalize(pStmt); +} + +/* +** Obtain an exclusive shm-lock on nByte bytes starting at offset idx +** of the file fd. If the lock cannot be obtained immediately, invoke +** the busy-handler until either it is obtained or the busy-handler +** callback returns 0. +*/ +static int superlockShmLock( + sqlite3_file *fd, /* Database file handle */ + int idx, /* Offset of shm-lock to obtain */ + int nByte, /* Number of consective bytes to lock */ + SuperlockBusy *pBusy /* Busy-handler wrapper object */ +){ + int rc; + int (*xShmLock)(sqlite3_file*, int, int, int) = fd->pMethods->xShmLock; + do { + rc = xShmLock(fd, idx, nByte, SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE); + }while( rc==SQLITE_BUSY && superlockBusyHandler((void *)pBusy, 0) ); + return rc; +} + +/* +** Obtain the extra locks on the database file required for WAL databases. +** Invoke the supplied busy-handler as required. +*/ +static int superlockWalLock( + sqlite3 *db, /* Database handle open on WAL database */ + SuperlockBusy *pBusy /* Busy handler wrapper object */ +){ + int rc; /* Return code */ + sqlite3_file *fd = 0; /* Main database file handle */ + void volatile *p = 0; /* Pointer to first page of shared memory */ + + /* Obtain a pointer to the sqlite3_file object open on the main db file. */ + rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd); + if( rc!=SQLITE_OK ) return rc; + + /* Obtain the "recovery" lock. Normally, this lock is only obtained by + ** clients running database recovery. + */ + rc = superlockShmLock(fd, 2, 1, pBusy); + if( rc!=SQLITE_OK ) return rc; + + /* Zero the start of the first shared-memory page. This means that any + ** clients that open read or write transactions from this point on will + ** have to run recovery before proceeding. Since they need the "recovery" + ** lock that this process is holding to do that, no new read or write + ** transactions may now be opened. Nor can a checkpoint be run, for the + ** same reason. + */ + rc = fd->pMethods->xShmMap(fd, 0, 32*1024, 1, &p); + if( rc!=SQLITE_OK ) return rc; + memset((void *)p, 0, 32); + + /* Obtain exclusive locks on all the "read-lock" slots. Once these locks + ** are held, it is guaranteed that there are no active reader, writer or + ** checkpointer clients. + */ + rc = superlockShmLock(fd, 3, SQLITE_SHM_NLOCK-3, pBusy); + return rc; +} + +/* +** Release a superlock held on a database file. The argument passed to +** this function must have been obtained from a successful call to +** sqlite3demo_superlock(). +*/ +void sqlite3demo_superunlock(void *pLock){ + Superlock *p = (Superlock *)pLock; + if( p->bWal ){ + int rc; /* Return code */ + int flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE; + sqlite3_file *fd = 0; + rc = sqlite3_file_control(p->db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd); + if( rc==SQLITE_OK ){ + fd->pMethods->xShmLock(fd, 2, 1, flags); + fd->pMethods->xShmLock(fd, 3, SQLITE_SHM_NLOCK-3, flags); + } + } + sqlite3_close(p->db); + sqlite3_free(p); +} + +/* +** Obtain a superlock on the database file identified by zPath, using the +** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is +** returned and output variable *ppLock is populated with an opaque handle +** that may be used with sqlite3demo_superunlock() to release the lock. +** +** If an error occurs, *ppLock is set to 0 and an SQLite error code +** (e.g. SQLITE_BUSY) is returned. +** +** If a required lock cannot be obtained immediately and the xBusy parameter +** to this function is not NULL, then xBusy is invoked in the same way +** as a busy-handler registered with SQLite (using sqlite3_busy_handler()) +** until either the lock can be obtained or the busy-handler function returns +** 0 (indicating "give up"). +*/ +int sqlite3demo_superlock( + const char *zPath, /* Path to database file to lock */ + const char *zVfs, /* VFS to use to access database file */ + int (*xBusy)(void*,int), /* Busy handler callback */ + void *pBusyArg, /* Context arg for busy handler */ + void **ppLock /* OUT: Context to pass to superunlock() */ +){ + SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */ + int rc; /* Return code */ + Superlock *pLock; + + pLock = sqlite3_malloc(sizeof(Superlock)); + if( !pLock ) return SQLITE_NOMEM; + memset(pLock, 0, sizeof(Superlock)); + + /* Open a database handle on the file to superlock. */ + rc = sqlite3_open_v2( + zPath, &pLock->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs + ); + + /* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not + ** a WAL database, this is all we need to do. + ** + ** A wrapper function is used to invoke the busy-handler instead of + ** registering the busy-handler function supplied by the user directly + ** with SQLite. This is because the same busy-handler function may be + ** invoked directly later on when attempting to obtain the extra locks + ** required in WAL mode. By using the wrapper, we are able to guarantee + ** that the "nBusy" integer parameter passed to the users busy-handler + ** represents the total number of busy-handler invocations made within + ** this call to sqlite3demo_superlock(), including any made during the + ** "BEGIN EXCLUSIVE". + */ + if( rc==SQLITE_OK ){ + busy.xBusy = xBusy; + busy.pBusyArg = pBusyArg; + sqlite3_busy_handler(pLock->db, superlockBusyHandler, (void *)&busy); + rc = sqlite3_exec(pLock->db, "BEGIN EXCLUSIVE", 0, 0, 0); + } + + /* If the BEGIN EXCLUSIVE was executed successfully and this is a WAL + ** database, call superlockWalLock() to obtain the extra locks required + ** to prevent readers, writers and/or checkpointers from accessing the + ** db while this process is holding the superlock. + ** + ** Before attempting any WAL locks, commit the transaction started above + ** to drop the WAL read and write locks currently held. Otherwise, the + ** new WAL locks may conflict with the old. + */ + if( rc==SQLITE_OK ){ + if( SQLITE_OK==(rc = superlockIsWal(pLock)) && pLock->bWal ){ + rc = sqlite3_exec(pLock->db, "COMMIT", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = superlockWalLock(pLock->db, &busy); + } + } + } + + if( rc!=SQLITE_OK ){ + sqlite3demo_superunlock(pLock); + *ppLock = 0; + }else{ + *ppLock = pLock; + } + + return rc; +} + +/* +** End of example code. Everything below here is the test harness. +************************************************************************** +************************************************************************** +*************************************************************************/ + + +#ifdef SQLITE_TEST + +#include + +struct InterpAndScript { + Tcl_Interp *interp; + Tcl_Obj *pScript; +}; +typedef struct InterpAndScript InterpAndScript; + +static void superunlock_del(ClientData cd){ + sqlite3demo_superunlock((void *)cd); +} + +static int superunlock_cmd( + ClientData cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + if( objc!=1 ){ + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); + return TCL_OK; +} + +static int superlock_busy(void *pCtx, int nBusy){ + InterpAndScript *p = (InterpAndScript *)pCtx; + Tcl_Obj *pEval; /* Script to evaluate */ + int iVal = 0; /* Value to return */ + + pEval = Tcl_DuplicateObj(p->pScript); + Tcl_IncrRefCount(pEval); + Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy)); + Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); + Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal); + Tcl_DecrRefCount(pEval); + + return iVal; +} + +/* +** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT +*/ +static int superlock_cmd( + ClientData cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + void *pLock; /* Lock context */ + char *zPath; + char *zVfs = 0; + InterpAndScript busy = {0, 0}; + int (*xBusy)(void*,int) = 0; /* Busy handler callback */ + int rc; /* Return code from sqlite3demo_superlock() */ + + if( objc<3 || objc>5 ){ + Tcl_WrongNumArgs( + interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?"); + return TCL_ERROR; + } + + zPath = Tcl_GetString(objv[2]); + + if( objc>3 ){ + zVfs = Tcl_GetString(objv[3]); + if( strlen(zVfs)==0 ) zVfs = 0; + } + if( objc>4 ){ + busy.interp = interp; + busy.pScript = objv[4]; + xBusy = superlock_busy; + } + + rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock); + assert( rc==SQLITE_OK || pLock==0 ); + assert( rc!=SQLITE_OK || pLock!=0 ); + + if( rc!=SQLITE_OK ){ + extern const char *sqlite3ErrStr(int); + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0); + return TCL_ERROR; + } + + Tcl_CreateObjCommand( + interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del + ); + Tcl_SetObjResult(interp, objv[1]); + return TCL_OK; +} + +int SqliteSuperlock_Init(Tcl_Interp *interp){ + Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0); + return TCL_OK; +} +#endif diff --git a/src/test_vfs.c b/src/test_vfs.c index c606cfbe..64b3cb57 100644 --- a/src/test_vfs.c +++ b/src/test_vfs.c @@ -26,6 +26,7 @@ ** -default BOOLEAN (True to make the vfs default. Default false) ** -szosfile INTEGER (Value for sqlite3_vfs.szOsFile) ** -mxpathname INTEGER (Value for sqlite3_vfs.mxPathname) +** -iversion INTEGER (Value for sqlite3_vfs.iVersion) */ #include "sqlite3.h" @@ -539,6 +540,7 @@ static int tvfsOpen( pFd->zFilename = zName; pFd->pVfs = pVfs; pFd->pReal = (sqlite3_file *)&pFd[1]; + memset(pTestfile, 0, sizeof(TestvfsFile)); pTestfile->pFd = pFd; /* Evaluate the Tcl script: diff --git a/src/update.c b/src/update.c index 44f047b4..8bf58d76 100644 --- a/src/update.c +++ b/src/update.c @@ -638,6 +638,7 @@ static void updateVirtualTable( assert( v ); ephemTab = pParse->nTab++; sqlite3VdbeAddOp2(v, OP_OpenEphemeral, ephemTab, pTab->nCol+1+(pRowid!=0)); + sqlite3VdbeChangeP5(v, BTREE_UNORDERED); /* fill the ephemeral table */ diff --git a/src/util.c b/src/util.c index ab31424e..dfa127be 100644 --- a/src/util.c +++ b/src/util.c @@ -215,6 +215,12 @@ int sqlite3Dequote(char *z){ /* ** Some systems have stricmp(). Others have strcasecmp(). Because ** there is no consistency, we will define our own. +** +** IMPLEMENTATION-OF: R-20522-24639 The sqlite3_strnicmp() API allows +** 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. */ int sqlite3StrICmp(const char *zLeft, const char *zRight){ register unsigned char *a, *b; @@ -232,121 +238,111 @@ int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){ } /* -** Return TRUE if z is a pure numeric string. Return FALSE and leave -** *realnum unchanged if the string contains any character which is not -** part of a number. +** The string z[] is an text representation of a real number. +** Convert this string to a double and write it into *pResult. ** -** If the string is pure numeric, set *realnum to TRUE if the string -** contains the '.' character or an "E+000" style exponentiation suffix. -** Otherwise set *realnum to FALSE. Note that just becaue *realnum is -** false does not mean that the number can be successfully converted into -** an integer - it might be too big. +** The string z[] is length bytes in length (bytes, not characters) and +** uses the encoding enc. The string is not necessarily zero-terminated. ** -** An empty string is considered non-numeric. +** Return TRUE if the result is a valid real number (or integer) and FALSE +** if the string is empty or contains extraneous text. Valid numbers +** are in one of these formats: +** +** [+-]digits[E[+-]digits] +** [+-]digits.[digits][E[+-]digits] +** [+-].digits[E[+-]digits] +** +** Leading and trailing whitespace is ignored for the purpose of determining +** validity. +** +** If some prefix of the input string is a valid number, this routine +** returns FALSE but it still converts the prefix and writes the result +** into *pResult. */ -int sqlite3IsNumber(const char *z, int *realnum, u8 enc){ +int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ +#ifndef SQLITE_OMIT_FLOATING_POINT int incr = (enc==SQLITE_UTF8?1:2); - if( enc==SQLITE_UTF16BE ) z++; - if( *z=='-' || *z=='+' ) z += incr; - if( !sqlite3Isdigit(*z) ){ - return 0; - } - z += incr; - *realnum = 0; - while( sqlite3Isdigit(*z) ){ z += incr; } -#ifndef SQLITE_OMIT_FLOATING_POINT - if( *z=='.' ){ - z += incr; - if( !sqlite3Isdigit(*z) ) return 0; - while( sqlite3Isdigit(*z) ){ z += incr; } - *realnum = 1; - } - if( *z=='e' || *z=='E' ){ - z += incr; - if( *z=='+' || *z=='-' ) z += incr; - if( !sqlite3Isdigit(*z) ) return 0; - while( sqlite3Isdigit(*z) ){ z += incr; } - *realnum = 1; - } -#endif - return *z==0; -} - -/* -** The string z[] is an ASCII representation of a real number. -** Convert this string to a double. -** -** This routine assumes that z[] really is a valid number. If it -** is not, the result is undefined. -** -** This routine is used instead of the library atof() function because -** the library atof() might want to use "," as the decimal point instead -** of "." depending on how locale is set. But that would cause problems -** for SQL. So this routine always uses "." regardless of locale. -*/ -int sqlite3AtoF(const char *z, double *pResult){ -#ifndef SQLITE_OMIT_FLOATING_POINT - const char *zBegin = z; + const char *zEnd = z + length; /* sign * significand * (10 ^ (esign * exponent)) */ - int sign = 1; /* sign of significand */ - i64 s = 0; /* significand */ - int d = 0; /* adjust exponent for shifting decimal point */ - int esign = 1; /* sign of exponent */ - int e = 0; /* exponent */ + int sign = 1; /* sign of significand */ + i64 s = 0; /* significand */ + int d = 0; /* adjust exponent for shifting decimal point */ + int esign = 1; /* sign of exponent */ + int e = 0; /* exponent */ + int eValid = 1; /* True exponent is either not used or is well-formed */ double result; int nDigits = 0; + *pResult = 0.0; /* Default return value, in case of an error */ + + if( enc==SQLITE_UTF16BE ) z++; + /* skip leading spaces */ - while( sqlite3Isspace(*z) ) z++; + while( z=zEnd ) return 0; + /* get sign of significand */ if( *z=='-' ){ sign = -1; - z++; + z+=incr; }else if( *z=='+' ){ - z++; + z+=incr; } + /* skip leading zeroes */ - while( z[0]=='0' ) z++, nDigits++; + while( z=zEnd ) goto do_atof_calc; /* if decimal point is present */ if( *z=='.' ){ - z++; + z+=incr; /* copy digits from after decimal to significand ** (decrease exponent by d to shift decimal right) */ - while( sqlite3Isdigit(*z) && s<((LARGEST_INT64-9)/10) ){ + while( z=zEnd ) goto do_atof_calc; /* if exponent is present */ if( *z=='e' || *z=='E' ){ - z++; + z+=incr; + eValid = 0; + if( z>=zEnd ) goto do_atof_calc; /* get sign of exponent */ if( *z=='-' ){ esign = -1; - z++; + z+=incr; }else if( *z=='+' ){ - z++; + z+=incr; } /* copy digits to exponent */ - while( sqlite3Isdigit(*z) ){ + while( z=zEnd && nDigits>0 && eValid; #else - return sqlite3Atoi64(z, pResult); + return !sqlite3Atoi64(z, pResult, length, enc); #endif /* SQLITE_OMIT_FLOATING_POINT */ } @@ -416,20 +412,26 @@ int sqlite3AtoF(const char *z, double *pResult){ ** Compare the 19-character string zNum against the text representation ** value 2^63: 9223372036854775808. Return negative, zero, or positive ** if zNum is less than, equal to, or greater than the string. +** Note that zNum must contain exactly 19 characters. ** ** Unlike memcmp() this routine is guaranteed to return the difference ** in the values of the last digit if the only difference is in the ** last digit. So, for example, ** -** compare2pow63("9223372036854775800") +** compare2pow63("9223372036854775800", 1) ** ** will return -8. */ -static int compare2pow63(const char *zNum){ - int c; - c = memcmp(zNum,"922337203685477580",18)*10; +static int compare2pow63(const char *zNum, int incr){ + int c = 0; + int i; + /* 012345678901234567 */ + const char *pow63 = "922337203685477580"; + for(i=0; c==0 && i<18; i++){ + c = (zNum[i*incr]-pow63[i])*10; + } if( c==0 ){ - c = zNum[18] - '8'; + c = zNum[18*incr] - '8'; testcase( c==(-1) ); testcase( c==0 ); testcase( c==(+1) ); @@ -439,94 +441,60 @@ static int compare2pow63(const char *zNum){ /* -** Return TRUE if zNum is a 64-bit signed integer and write -** the value of the integer into *pNum. If zNum is not an integer -** or is an integer that is too large to be expressed with 64 bits, -** then return false. +** Convert zNum to a 64-bit signed integer and write +** the value of the integer into *pNum. +** If zNum is exactly 9223372036854665808, return 2. +** This is a special case as the context will determine +** if it is too big (used as a negative). +** If zNum is not an integer or is an integer that +** is too large to be expressed with 64 bits, +** then return 1. Otherwise return 0. ** -** When this routine was originally written it dealt with only -** 32-bit numbers. At that time, it was much faster than the -** atoi() library routine in RedHat 7.2. +** 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. */ -int sqlite3Atoi64(const char *zNum, i64 *pNum){ +int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc){ + int incr = (enc==SQLITE_UTF8?1:2); i64 v = 0; - int neg; - int i, c; + int neg = 0; /* assume positive */ + int i; + int c = 0; const char *zStart; - while( sqlite3Isspace(*zNum) ) zNum++; + const char *zEnd = zNum + length; + if( enc==SQLITE_UTF16BE ) zNum++; + while( zNum=zEnd ) goto do_atoi_calc; if( *zNum=='-' ){ neg = 1; - zNum++; + zNum+=incr; }else if( *zNum=='+' ){ - neg = 0; - zNum++; - }else{ - neg = 0; + zNum+=incr; } +do_atoi_calc: zStart = zNum; - while( zNum[0]=='0' ){ zNum++; } /* Skip over leading zeros. Ticket #2454 */ - for(i=0; (c=zNum[i])>='0' && c<='9'; i++){ + while( zNum='0' && c<='9'; i+=incr){ v = v*10 + c - '0'; } *pNum = neg ? -v : v; testcase( i==18 ); testcase( i==19 ); testcase( i==20 ); - if( c!=0 || (i==0 && zStart==zNum) || i>19 ){ + if( (c!=0 && &zNum[i]19*incr ){ /* zNum is empty or contains non-numeric text or is longer - ** than 19 digits (thus guaranting that it is too large) */ - return 0; - }else if( i<19 ){ - /* Less than 19 digits, so we know that it fits in 64 bits */ + ** than 19 digits (thus guaranteeing that it is too large) */ return 1; + }else if( i<19*incr ){ + /* Less than 19 digits, so we know that it fits in 64 bits */ + return 0; }else{ /* 19-digit numbers must be no larger than 9223372036854775807 if positive ** or 9223372036854775808 if negative. Note that 9223372036854665808 - ** is 2^63. */ - return compare2pow63(zNum)='0' && zNum[0]<='9' ); /* zNum is an unsigned number */ - - if( negFlag ) neg = 1-neg; - while( *zNum=='0' ){ - zNum++; /* Skip leading zeros. Ticket #2454 */ - } - for(i=0; zNum[i]; i++){ assert( zNum[i]>='0' && zNum[i]<='9' ); } - testcase( i==18 ); - testcase( i==19 ); - testcase( i==20 ); - if( i<19 ){ - /* Guaranteed to fit if less than 19 digits */ - return 1; - }else if( i>19 ){ - /* Guaranteed to be too big if greater than 19 digits */ - return 0; - }else{ - /* Compare against 2^63. */ - return compare2pow63(zNum)activeVdbeCnt>1 ){ + sqlite3SetString(pzErrMsg, db,"cannot VACUUM - SQL statements in progress"); + return SQLITE_ERROR; + } /* Save the current value of the database flags so that it can be ** restored before returning. Then set the writable-schema flag, and diff --git a/src/vdbe.c b/src/vdbe.c index 1e7fc167..eddd1e59 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -46,6 +46,17 @@ #include "sqliteInt.h" #include "vdbeInt.h" +/* +** Invoke this macro on memory cells just prior to changing the +** value of the cell. This macro verifies that shallow copies are +** not misused. +*/ +#ifdef SQLITE_DEBUG +# define memAboutToChange(P,M) sqlite3VdbeMemPrepareToChange(P,M) +#else +# define memAboutToChange(P,M) +#endif + /* ** The following global variable is incremented every time a cursor ** moves, either by the OP_SeekXX, OP_Next, or OP_Prev opcodes. The test @@ -238,31 +249,17 @@ static VdbeCursor *allocateCursor( */ static void applyNumericAffinity(Mem *pRec){ if( (pRec->flags & (MEM_Real|MEM_Int))==0 ){ - int realnum; + double rValue; + i64 iValue; u8 enc = pRec->enc; - sqlite3VdbeMemNulTerminate(pRec); - if( (pRec->flags&MEM_Str) && sqlite3IsNumber(pRec->z, &realnum, enc) ){ - i64 value; - char *zUtf8 = pRec->z; -#ifndef SQLITE_OMIT_UTF16 - if( enc!=SQLITE_UTF8 ){ - assert( pRec->db ); - zUtf8 = sqlite3Utf16to8(pRec->db, pRec->z, pRec->n, enc); - if( !zUtf8 ) return; - } -#endif - if( !realnum && sqlite3Atoi64(zUtf8, &value) ){ - pRec->u.i = value; - MemSetTypeFlag(pRec, MEM_Int); - }else{ - sqlite3AtoF(zUtf8, &pRec->r); - MemSetTypeFlag(pRec, MEM_Real); - } -#ifndef SQLITE_OMIT_UTF16 - if( enc!=SQLITE_UTF8 ){ - sqlite3DbFree(pRec->db, zUtf8); - } -#endif + 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; } } } @@ -314,13 +311,13 @@ static void applyAffinity( ** 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. -** -** This is an EXPERIMENTAL api and is subject to change or removal. */ int sqlite3_value_numeric_type(sqlite3_value *pVal){ Mem *pMem = (Mem*)pVal; - applyNumericAffinity(pMem); - sqlite3VdbeMemStoreType(pMem); + if( pMem->type==SQLITE_TEXT ){ + applyNumericAffinity(pMem); + sqlite3VdbeMemStoreType(pMem); + } return pMem->type; } @@ -667,6 +664,7 @@ int sqlite3VdbeExec( assert( pOp->p2>0 ); assert( pOp->p2<=p->nMem ); pOut = &aMem[pOp->p2]; + memAboutToChange(p, pOut); sqlite3VdbeMemReleaseExternal(pOut); pOut->flags = MEM_Int; } @@ -676,25 +674,30 @@ int sqlite3VdbeExec( if( (pOp->opflags & OPFLG_IN1)!=0 ){ assert( pOp->p1>0 ); assert( pOp->p1<=p->nMem ); + assert( memIsValid(&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 ); + assert( memIsValid(&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 ); + assert( memIsValid(&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 ); + memAboutToChange(p, &aMem[pOp->p2]); } if( (pOp->opflags & OPFLG_OUT3)!=0 ){ assert( pOp->p3>0 ); assert( pOp->p3<=p->nMem ); + memAboutToChange(p, &aMem[pOp->p3]); } #endif @@ -756,6 +759,7 @@ case OP_Goto: { /* jump */ case OP_Gosub: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; assert( (pIn1->flags & MEM_Dyn)==0 ); + memAboutToChange(p, pIn1); pIn1->flags = MEM_Int; pIn1->u.i = pc; REGISTER_TRACE(pOp->p1, pIn1); @@ -961,11 +965,7 @@ case OP_Null: { /* out2-prerelease */ /* Opcode: Blob P1 P2 * P4 ** ** P4 points to a blob of data P1 bytes long. Store this -** blob in register P2. This instruction is not coded directly -** by the compiler. Instead, the compiler layer specifies -** an OP_HexBlob opcode, with the hex string representation of -** the blob as P4. This opcode is transformed to an OP_Blob -** the first time it is executed. +** blob in register P2. */ case OP_Blob: { /* out2-prerelease */ assert( pOp->p1 <= SQLITE_MAX_LENGTH ); @@ -1019,6 +1019,8 @@ case OP_Move: { while( n-- ){ assert( pOut<=&aMem[p->nMem] ); assert( pIn1<=&aMem[p->nMem] ); + assert( memIsValid(pIn1) ); + memAboutToChange(p, pOut); zMalloc = pOut->zMalloc; pOut->zMalloc = 0; sqlite3VdbeMemMove(pOut, pIn1); @@ -1064,6 +1066,9 @@ case OP_SCopy: { /* in1, out2 */ 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; } @@ -1122,6 +1127,10 @@ case OP_ResultRow: { */ pMem = p->pResultSet = &aMem[pOp->p1]; for(i=0; ip2; 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]); sqlite3VdbeMemStoreType(&pMem[i]); REGISTER_TRACE(pOp->p1+i, &pMem[i]); @@ -1347,12 +1356,17 @@ case OP_Function: { n = pOp->p5; apVal = p->apArg; assert( apVal || n==0 ); + assert( pOp->p3>0 && pOp->p3<=p->nMem ); + pOut = &aMem[pOp->p3]; + memAboutToChange(p, pOut); assert( n==0 || (pOp->p2>0 && pOp->p2+n<=p->nMem+1) ); assert( pOp->p3p2 || pOp->p3>=pOp->p2+n ); pArg = &aMem[pOp->p2]; for(i=0; ip2+i, pArg); } @@ -1366,8 +1380,6 @@ case OP_Function: { ctx.pFunc = ctx.pVdbeFunc->pFunc; } - assert( pOp->p3>0 && pOp->p3<=p->nMem ); - pOut = &aMem[pOp->p3]; ctx.s.flags = MEM_Null; ctx.s.db = db; ctx.s.xDel = 0; @@ -1387,7 +1399,7 @@ case OP_Function: { assert( pOp[-1].opcode==OP_CollSeq ); ctx.pColl = pOp[-1].p4.pColl; } - (*ctx.pFunc->xFunc)(&ctx, n, apVal); + (*ctx.pFunc->xFunc)(&ctx, n, apVal); /* IMP: R-24505-23230 */ if( db->mallocFailed ){ /* Even though a malloc() has failed, the implementation of the ** user function may have called an sqlite3_result_XXX() function @@ -1439,7 +1451,7 @@ case OP_Function: { /* Opcode: ShiftLeft P1 P2 P3 * * ** ** Shift the integer value in register P2 to the left by the -** number of bits specified by the integer in regiser P1. +** 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. */ @@ -1487,6 +1499,7 @@ case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */ */ case OP_AddImm: { /* in1 */ pIn1 = &aMem[pOp->p1]; + memAboutToChange(p, pIn1); sqlite3VdbeMemIntegerify(pIn1); pIn1->u.i += pOp->p2; break; @@ -1546,6 +1559,7 @@ case OP_RealAffinity: { /* in1 */ */ 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; @@ -1592,16 +1606,14 @@ case OP_ToBlob: { /* same as TK_TO_BLOB, in1 */ */ case OP_ToNumeric: { /* same as TK_TO_NUMERIC, in1 */ pIn1 = &aMem[pOp->p1]; - if( (pIn1->flags & (MEM_Null|MEM_Int|MEM_Real))==0 ){ - sqlite3VdbeMemNumerify(pIn1); - } + sqlite3VdbeMemNumerify(pIn1); break; } #endif /* SQLITE_OMIT_CAST */ /* Opcode: ToInt P1 * * * * ** -** Force the value in register P1 be an integer. If +** 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. @@ -1628,6 +1640,7 @@ case OP_ToInt: { /* same as TK_TO_INT, in1 */ */ case OP_ToReal: { /* same as TK_TO_REAL, in1 */ pIn1 = &aMem[pOp->p1]; + memAboutToChange(p, pIn1); if( (pIn1->flags & MEM_Null)==0 ){ sqlite3VdbeMemRealify(pIn1); } @@ -1642,7 +1655,7 @@ case OP_ToReal: { /* same as TK_TO_REAL, in1 */ ** ** If the SQLITE_JUMPIFNULL bit of P5 is set and either reg(P1) or ** reg(P3) is NULL then take the jump. If the SQLITE_JUMPIFNULL -** bit is clear then fall thru if either operand is NULL. +** bit is clear then fall through if either operand is NULL. ** ** The SQLITE_AFF_MASK portion of P5 must be an affinity character - ** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made @@ -1770,6 +1783,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ if( pOp->p5 & SQLITE_STOREP2 ){ pOut = &aMem[pOp->p2]; + memAboutToChange(p, pOut); MemSetTypeFlag(pOut, MEM_Int); pOut->u.i = res; REGISTER_TRACE(pOp->p2, pOut); @@ -1801,8 +1815,8 @@ case OP_Permutation: { /* Opcode: Compare P1 P2 P3 P4 * ** -** Compare to vectors of registers in reg(P1)..reg(P1+P3-1) (all this -** one "A") and in reg(P2)..reg(P2+P3-1) ("B"). Save the result of +** 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. ** ** P4 is a KeyInfo structure that defines collating sequences and sort @@ -1842,6 +1856,8 @@ case OP_Compare: { #endif /* SQLITE_DEBUG */ for(i=0; inField ); @@ -2067,6 +2083,7 @@ case OP_Column: { assert( p1nCursor ); assert( pOp->p3>0 && pOp->p3<=p->nMem ); pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); MemSetTypeFlag(pDest, MEM_Null); zRec = 0; @@ -2114,6 +2131,7 @@ case OP_Column: { }else if( pC->pseudoTableReg>0 ){ pReg = &aMem[pC->pseudoTableReg]; assert( pReg->flags & MEM_Blob ); + assert( memIsValid(pReg) ); payloadSize = pReg->n; zRec = pReg->z; pC->cacheStatus = (pOp->p5&OPFLAG_CLEARCACHE) ? CACHE_STALE : p->cacheCtr; @@ -2336,6 +2354,7 @@ case OP_Affinity: { pIn1 = &aMem[pOp->p1]; while( (cAff = *(zAffinity++))!=0 ){ assert( pIn1 <= &p->aMem[p->nMem] ); + assert( memIsValid(pIn1) ); ExpandBlob(pIn1); applyAffinity(pIn1, cAff, encoding); pIn1++; @@ -2345,12 +2364,9 @@ case OP_Affinity: { /* Opcode: MakeRecord P1 P2 P3 P4 * ** -** Convert P2 registers beginning with P1 into a single entry -** suitable for use as a data record in a database table or as a key -** in an index. The details of the format are irrelevant as long as -** the OP_Column opcode can decode the record later. -** Refer to source code comments for the details of the record -** format. +** 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. ** ** P4 may be a string that is P2 characters long. The nth character of the ** string indicates the column affinity that should be used for the nth @@ -2405,10 +2421,16 @@ case OP_MakeRecord: { pLast = &pData0[nField-1]; file_format = p->minWriteFileFormat; + /* Identify the output register */ + assert( pOp->p3p1 || pOp->p3>=pOp->p1+pOp->p2 ); + pOut = &aMem[pOp->p3]; + memAboutToChange(p, pOut); + /* Loop through the elements that will make up the record to figure ** out how much space is required for the new record. */ for(pRec=pData0; pRec<=pLast; pRec++){ + assert( memIsValid(pRec) ); if( zAffinity ){ applyAffinity(pRec, zAffinity[pRec-pData0], encoding); } @@ -2443,8 +2465,6 @@ case OP_MakeRecord: { ** be one of the input registers (because the following call to ** sqlite3VdbeMemGrow() could clobber the value before it is used). */ - assert( pOp->p3p1 || pOp->p3>=pOp->p1+pOp->p2 ); - pOut = &aMem[pOp->p3]; if( sqlite3VdbeMemGrow(pOut, (int)nByte, 0) ){ goto no_mem; } @@ -2613,6 +2633,7 @@ case OP_Savepoint: { if( p1==SAVEPOINT_ROLLBACK && (db->flags&SQLITE_InternChanges)!=0 ){ sqlite3ExpirePreparedStatements(db); sqlite3ResetInternalSchema(db, 0); + db->flags = (db->flags | SQLITE_InternChanges); } } @@ -2991,6 +3012,8 @@ case OP_OpenWrite: { assert( p2>0 ); assert( p2<=p->nMem ); pIn2 = &aMem[p2]; + assert( memIsValid(pIn2) ); + assert( (pIn2->flags & MEM_Int)!=0 ); sqlite3VdbeMemIntegerify(pIn2); p2 = (int)pIn2->u.i; /* The p2 value always comes from a prior OP_CreateTable opcode and @@ -3013,6 +3036,7 @@ case OP_OpenWrite: { pCur = allocateCursor(p, pOp->p1, nField, iDb, 1); if( pCur==0 ) goto no_mem; pCur->nullRow = 1; + pCur->isOrdered = 1; rc = sqlite3BtreeCursor(pX, p2, wrFlag, pKeyInfo, pCur->pCursor); pCur->pKeyInfo = pKeyInfo; @@ -3063,7 +3087,7 @@ case OP_OpenWrite: { case OP_OpenAutoindex: case OP_OpenEphemeral: { VdbeCursor *pCx; - static const int openFlags = + static const int vfsFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | @@ -3074,21 +3098,21 @@ case OP_OpenEphemeral: { pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, 1); if( pCx==0 ) goto no_mem; pCx->nullRow = 1; - rc = sqlite3BtreeFactory(db, 0, 1, SQLITE_DEFAULT_TEMP_CACHE_SIZE, openFlags, - &pCx->pBt); + rc = sqlite3BtreeOpen(0, db, &pCx->pBt, + BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, vfsFlags); if( rc==SQLITE_OK ){ rc = sqlite3BtreeBeginTrans(pCx->pBt, 1); } if( rc==SQLITE_OK ){ /* If a transient index is required, create it by calling - ** sqlite3BtreeCreateTable() with the BTREE_ZERODATA flag before + ** 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 INTKEY table). + ** automatically created table with root-page 1 (an BLOB_INTKEY table). */ if( pOp->p4.pKeyInfo ){ int pgno; assert( pOp->p4type==P4_KEYINFO ); - rc = sqlite3BtreeCreateTable(pCx->pBt, &pgno, BTREE_ZERODATA); + rc = sqlite3BtreeCreateTable(pCx->pBt, &pgno, BTREE_BLOBKEY); if( rc==SQLITE_OK ){ assert( pgno==MASTER_ROOT+1 ); rc = sqlite3BtreeCursor(pCx->pBt, pgno, 1, @@ -3102,6 +3126,7 @@ case OP_OpenEphemeral: { pCx->isTable = 1; } } + pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); pCx->isIndex = !pCx->isTable; break; } @@ -3217,6 +3242,7 @@ case OP_SeekGt: { /* jump, in3 */ assert( OP_SeekLe == OP_SeekLt+1 ); assert( OP_SeekGe == OP_SeekLt+2 ); assert( OP_SeekGt == OP_SeekLt+3 ); + assert( pC->isOrdered ); if( pC->pCursor!=0 ){ oc = pOp->opcode; pC->nullRow = 0; @@ -3299,6 +3325,9 @@ case OP_SeekGt: { /* jump, in3 */ assert( oc!=OP_SeekLt || r.flags==0 ); r.aMem = &aMem[pOp->p3]; +#ifdef SQLITE_DEBUG + { int i; for(i=0; ipCursor, &r, 0, 0, &res); if( rc!=SQLITE_OK ){ @@ -3423,11 +3452,14 @@ case OP_Found: { /* jump, in3 */ r.pKeyInfo = pC->pKeyInfo; r.nField = (u16)pOp->p4.i; r.aMem = pIn3; +#ifdef SQLITE_DEBUG + { int i; for(i=0; iflags & MEM_Blob ); - ExpandBlob(pIn3); + assert( (pIn3->flags & MEM_Zero)==0 ); /* zeroblobs already expanded */ pIdxKey = sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, aTempRec, sizeof(aTempRec)); if( pIdxKey==0 ){ @@ -3520,6 +3552,9 @@ case OP_IsUnique: { /* jump, in3 */ r.nField = nField + 1; r.flags = UNPACKED_PREFIX_SEARCH; r.aMem = aMx; +#ifdef SQLITE_DEBUG + { int i; for(i=0; ip3<=p->nMem ); pMem = &aMem[pOp->p3]; + memAboutToChange(p, pMem); } + assert( memIsValid(pMem) ); REGISTER_TRACE(pOp->p3, pMem); sqlite3VdbeMemIntegerify(pMem); @@ -3715,29 +3752,36 @@ case OP_NewRowid: { /* out2-prerelease */ sqlite3BtreeSetCachedRowid(pC->pCursor, vuseRandomRowid ){ - /* IMPLEMENTATION-OF: R-48598-02938 If the largest ROWID is equal to the + /* IMPLEMENTATION-OF: R-07677-41881 If the largest ROWID is equal to the ** largest possible integer (9223372036854775807) then the database - ** engine starts picking candidate ROWIDs at random until it finds one - ** that is not previously used. - */ + ** 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 */ v = db->lastRowid; + v &= (MAX_ROWID>>1); /* ensure doesn't go negative */ + v++; /* ensure non-zero */ cnt = 0; - do{ - if( cnt==0 && (v&0xffffff)==v ){ - v++; + while( ((rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, 0, (u64)v, + 0, &res))==SQLITE_OK) + && (res==0) + && (++cnt<100)){ + /* collision - try another random rowid */ + sqlite3_randomness(sizeof(v), &v); + if( cnt<5 ){ + /* try "small" random rowids for the initial attempts */ + v &= 0xffffff; }else{ - sqlite3_randomness(sizeof(v), &v); - if( cnt<5 ) v &= 0xffffff; + v &= (MAX_ROWID>>1); /* ensure doesn't go negative */ } - rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, 0, (u64)v, 0, &res); - cnt++; - }while( cnt<100 && rc==SQLITE_OK && res==0 ); + v++; /* ensure non-zero */ + } if( rc==SQLITE_OK && res==0 ){ rc = SQLITE_FULL; /* IMP: R-38219-53002 */ goto abort_due_to_error; } + assert( v>0 ); /* EV: R-40812-03570 */ } pC->rowidIsValid = 0; pC->deferredMoveto = 0; @@ -3805,6 +3849,7 @@ case OP_InsertInt: { pData = &aMem[pOp->p2]; assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( memIsValid(pData) ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->pCursor!=0 ); @@ -3815,6 +3860,7 @@ case OP_InsertInt: { if( pOp->opcode==OP_Insert ){ pKey = &aMem[pOp->p3]; assert( pKey->flags & MEM_Int ); + assert( memIsValid(pKey) ); REGISTER_TRACE(pOp->p3, pKey); iKey = pKey->u.i; }else{ @@ -3962,6 +4008,7 @@ case OP_RowData: { i64 n64; pOut = &aMem[pOp->p2]; + memAboutToChange(p, pOut); /* Note that RowKey and RowData are really exactly the same instruction */ assert( pOp->p1>=0 && pOp->p1nCursor ); @@ -4290,6 +4337,9 @@ case OP_IdxDelete: { r.nField = (u16)pOp->p3; r.flags = 0; r.aMem = &aMem[pOp->p2]; +#ifdef SQLITE_DEBUG + { int i; for(i=0; ip1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); + assert( pC->isOrdered ); if( ALWAYS(pC->pCursor!=0) ){ assert( pC->deferredMoveto==0 ); assert( pOp->p5==0 || pOp->p5==1 ); @@ -4382,6 +4433,9 @@ case OP_IdxGE: { /* jump */ r.flags = UNPACKED_IGNORE_ROWID; } r.aMem = &aMem[pOp->p3]; +#ifdef SQLITE_DEBUG + { int i; for(i=0; iopcode==OP_IdxLT ){ res = -res; @@ -4481,6 +4535,8 @@ case OP_Clear: { if( pOp->p3 ){ p->nChange += nChange; if( pOp->p3>0 ){ + assert( memIsValid(&aMem[pOp->p3]) ); + memAboutToChange(p, &aMem[pOp->p3]); aMem[pOp->p3].u.i += nChange; } } @@ -4522,9 +4578,9 @@ case OP_CreateTable: { /* out2-prerelease */ assert( pDb->pBt!=0 ); if( pOp->opcode==OP_CreateTable ){ /* flags = BTREE_INTKEY; */ - flags = BTREE_LEAFDATA|BTREE_INTKEY; + flags = BTREE_INTKEY; }else{ - flags = BTREE_ZERODATA; + flags = BTREE_BLOBKEY; } rc = sqlite3BtreeCreateTable(pDb->pBt, &pgno, flags); pOut->u.i = pgno; @@ -4843,6 +4899,7 @@ case OP_Program: { /* jump */ pProgram = pOp->p4.pProgram; pRt = &aMem[pOp->p3]; + assert( memIsValid(pRt) ); assert( pProgram->nOp>0 ); /* If the p5 flag is clear, then recursive invocation of triggers is @@ -5012,6 +5069,7 @@ case OP_MemMax: { /* in2 */ }else{ pIn1 = &aMem[pOp->p1]; } + assert( memIsValid(pIn1) ); sqlite3VdbeMemIntegerify(pIn1); pIn2 = &aMem[pOp->p2]; sqlite3VdbeMemIntegerify(pIn2); @@ -5096,7 +5154,9 @@ case OP_AggStep: { apVal = p->apArg; assert( apVal || n==0 ); for(i=0; ip4.pFunc; @@ -5116,7 +5176,7 @@ case OP_AggStep: { assert( pOp[-1].opcode==OP_CollSeq ); ctx.pColl = pOp[-1].p4.pColl; } - (ctx.pFunc->xStep)(&ctx, n, apVal); + (ctx.pFunc->xStep)(&ctx, n, apVal); /* IMP: R-24505-23230 */ if( ctx.isError ){ sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3_value_text(&ctx.s)); rc = ctx.isError; @@ -5491,6 +5551,7 @@ case OP_VFilter: { /* jump */ 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; @@ -5546,6 +5607,7 @@ case OP_VColumn: { assert( pCur->pVtabCursor ); assert( pOp->p3>0 && pOp->p3<=p->nMem ); pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); if( pCur->nullRow ){ sqlite3VdbeMemSetNull(pDest); break; @@ -5644,10 +5706,12 @@ case OP_VRename: { pVtab = pOp->p4.pVtab->pVtab; pName = &aMem[pOp->p1]; assert( pVtab->pModule->xRename ); + assert( memIsValid(pName) ); REGISTER_TRACE(pOp->p1, pName); assert( pName->flags & MEM_Str ); rc = pVtab->pModule->xRename(pVtab, pName->z); importVtabErrMsg(p, pVtab); + p->expired = 0; break; } @@ -5694,6 +5758,8 @@ case OP_VUpdate: { apArg = p->apArg; pX = &aMem[pOp->p3]; for(i=0; iaDb[pOp->p1].pBt; + newMax = 0; + if( pOp->p3 ){ + newMax = sqlite3BtreeLastPage(pBt); + if( newMax < (unsigned)pOp->p3 ) newMax = (unsigned)pOp->p3; + } + pOut->u.i = sqlite3BtreeMaxPageCount(pBt, newMax); + break; +} +#endif + + #ifndef SQLITE_OMIT_TRACE /* Opcode: Trace * * * P4 * ** diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 4a19f14c..b42729d2 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -39,16 +39,14 @@ typedef unsigned char Bool; ** ** Every cursor that the virtual machine has open is represented by an ** instance of the following structure. -** -** If the VdbeCursor.isTriggerRow flag is set it means that this cursor is -** really a single row that represents the NEW or OLD pseudo-table of -** a row trigger. The data for the row is stored in VdbeCursor.pData and -** the rowid is in VdbeCursor.iKey. */ 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) */ - i64 lastRowid; /* Last rowid from a Next or NextIdx operation */ + 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 */ @@ -57,14 +55,12 @@ struct VdbeCursor { 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 */ - i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */ - Btree *pBt; /* Separate file holding temporary table */ - int pseudoTableReg; /* Register holding pseudotable content. */ - KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */ - int nField; /* Number of fields in the header */ - i64 seqCount; /* Sequence counter */ + Bool isOrdered; /* True if the underlying table is BTREE_UNORDERED */ 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 */ /* Result of last sqlite3BtreeMoveto() done by an OP_NotExists or ** OP_IsUnique opcode on this cursor. */ @@ -96,26 +92,34 @@ typedef struct VdbeCursor VdbeCursor; ** restoring the state of the VM to as it was before the sub-program ** began executing. ** -** Frames are stored in a linked list headed at Vdbe.pParent. Vdbe.pParent -** is the parent of the current frame, or zero if the current frame -** is the main Vdbe program. +** The memory for a VdbeFrame object is allocated and managed by a memory +** cell in the parent (calling) frame. When the memory cell is deleted or +** overwritten, the VdbeFrame object is not freed immediately. Instead, it +** is linked into the Vdbe.pDelFrame list. The contents of the Vdbe.pDelFrame +** list is deleted when the VM is reset in VdbeHalt(). The reason for doing +** this instead of deleting the VdbeFrame immediately is to avoid recursive +** calls to sqlite3VdbeMemRelease() when the memory cells belonging to the +** child frame are released. +** +** The currently executing frame is stored in Vdbe.pFrame. Vdbe.pFrame is +** set to NULL if the currently executing frame is the main program. */ typedef struct VdbeFrame VdbeFrame; struct VdbeFrame { Vdbe *v; /* VM this frame belongs to */ - int pc; /* Program Counter */ - Op *aOp; /* Program instructions */ + int pc; /* Program Counter in parent (calling) frame */ + Op *aOp; /* Program instructions for parent frame */ int nOp; /* Size of aOp array */ - Mem *aMem; /* Array of memory cells */ + Mem *aMem; /* Array of memory cells for parent frame */ int nMem; /* Number of entries in aMem */ - VdbeCursor **apCsr; /* Element of Vdbe cursors */ + VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */ u16 nCursor; /* Number of entries in apCsr */ void *token; /* Copy of SubProgram.token */ int nChildMem; /* Number of memory cells for child frame */ int nChildCsr; /* Number of cursors for child frame */ i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */ int nChange; /* Statement changes (Vdbe.nChanges) */ - VdbeFrame *pParent; /* Parent of this frame */ + VdbeFrame *pParent; /* Parent of this frame, or NULL if parent is main */ }; #define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))]) @@ -128,29 +132,27 @@ struct VdbeFrame { /* ** 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. A value (and therefore Mem structure) -** has the following properties: -** -** Each value has a manifest type. The manifest type of the value stored -** in a Mem struct is returned by the MemType(Mem*) macro. The type is -** one of SQLITE_NULL, SQLITE_INTEGER, SQLITE_REAL, SQLITE_TEXT or -** SQLITE_BLOB. +** 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 { - i64 i; /* Integer value. */ + 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; - double r; /* Real value */ - sqlite3 *db; /* The associated database connection */ - char *z; /* String or BLOB value */ 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 */ +#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() */ }; @@ -166,9 +168,6 @@ struct Mem { ** database (see below for exceptions). If the MEM_Term flag is also ** set, then the string is nul terminated. The MEM_Int and MEM_Real ** flags may coexist with the MEM_Str flag. -** -** Multiple of these values can appear in Mem.flags. But only one -** at a time can appear in Mem.type. */ #define MEM_Null 0x0001 /* Value is NULL */ #define MEM_Str 0x0002 /* Value is a string */ @@ -177,6 +176,7 @@ struct Mem { #define MEM_Blob 0x0010 /* Value is a BLOB */ #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_TypeMask 0x00ff /* Mask of type bits */ /* Whenever Mem contains a valid string or blob representation, one of @@ -190,19 +190,25 @@ struct Mem { #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 #undef MEM_Zero #define MEM_Zero 0x0000 #endif - /* ** Clear any existing type flags from a Mem and replace them with f */ #define MemSetTypeFlag(p, f) \ ((p)->flags = ((p)->flags&~(MEM_TypeMask|MEM_Zero))|f) +/* +** 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 +#endif + /* A VdbeFunc is just a FuncDef (defined in sqliteInt.h) that contains ** additional information about auxiliary information bound to arguments @@ -244,23 +250,11 @@ struct sqlite3_context { CollSeq *pColl; /* Collating sequence */ }; -/* -** A Set structure is used for quick testing to see if a value -** is part of a small set. Sets are used to implement code like -** this: -** x.y IN ('hi','hoo','hum') -*/ -typedef struct Set Set; -struct Set { - Hash hash; /* A set is just a hash table */ - HashElem *prev; /* Previously accessed hash elemen */ -}; - /* ** 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_compile() +** 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 @@ -273,31 +267,31 @@ struct Set { */ struct Vdbe { sqlite3 *db; /* The database connection that owns this statement */ - Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */ - int nOp; /* Number of instructions in the program */ - int nOpAlloc; /* Number of slots allocated for aOp[] */ Op *aOp; /* Space to hold the virtual machine's program */ - int nLabel; /* Number of labels used */ - int nLabelAlloc; /* Number of slots allocated in aLabel[] */ - int *aLabel; /* Space to hold the labels */ + 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 */ + 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 nLabelAlloc; /* Number of slots allocated in aLabel[] */ + int *aLabel; /* Space to hold the labels */ u16 nResColumn; /* Number of columns in one row of the result set */ u16 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 */ - u8 errorAction; /* Recovery action to do in case of an error */ - u8 okVar; /* True if azVar[] has been initialized */ - ynVar nVar; /* Number of entries in aVar[] */ Mem *aVar; /* Values for the OP_Variable opcode. */ char **azVar; /* Name of variables */ - u32 magic; /* Magic number for sanity checking */ - int nMem; /* Number of memory locations currently allocated */ - Mem *aMem; /* The memory locations */ + ynVar nVar; /* Number of entries in aVar[] */ u32 cacheCtr; /* VdbeCursor row cache generation counter */ int pc; /* The program counter */ int rc; /* Value to return */ - char *zErrMsg; /* Error message written here */ + u8 errorAction; /* Recovery action to do in case of an error */ + u8 okVar; /* True if azVar[] has been initialized */ u8 explain; /* True if EXPLAIN present on SQL command */ u8 changeCntOn; /* True to update the change-counter */ u8 expired; /* True if the VM needs to be recompiled */ @@ -309,18 +303,21 @@ struct Vdbe { u8 isPrepareV2; /* True if prepared with prepare_v2() */ int nChange; /* Number of db changes made since last reset */ int btreeMask; /* Bitmask of db->aDb[] entries referenced */ - i64 startTime; /* Time when query started - used for profiling */ - BtreeMutexArray aMutex; /* An array of Btree used here and needing locks */ + int iStatement; /* Statement number (or 0 if has not opened stmt) */ int aCounter[3]; /* Counters used by sqlite3_stmt_status() */ - char *zSql; /* Text of the SQL statement that generated this */ - void *pFree; /* Free this when deleting the vdbe */ + BtreeMutexArray aMutex; /* An array of Btree used here and needing locks */ +#ifndef SQLITE_OMIT_TRACE + i64 startTime; /* Time when query started - used for profiling */ +#endif i64 nFkConstraint; /* Number of imm. FK constraints this VM */ i64 nStmtDefCons; /* Number of def. constraints when stmt started */ - int iStatement; /* Statement number (or 0 if has not opened stmt) */ + 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 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 */ @@ -391,6 +388,10 @@ void sqlite3VdbeFrameDelete(VdbeFrame*); int sqlite3VdbeFrameRestore(VdbeFrame *); void sqlite3VdbeMemStoreType(Mem *pMem); +#ifdef SQLITE_DEBUG +void sqlite3VdbeMemPrepareToChange(Vdbe*,Mem*); +#endif + #ifndef SQLITE_OMIT_FOREIGN_KEY int sqlite3VdbeCheckFk(Vdbe *, int); #else diff --git a/src/vdbeapi.c b/src/vdbeapi.c index afb4a1b6..5578b868 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -65,6 +65,8 @@ static int vdbeSafetyNotNull(Vdbe *p){ int 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; }else{ Vdbe *v = (Vdbe*)pStmt; @@ -141,7 +143,7 @@ const void *sqlite3_value_blob(sqlite3_value *pVal){ sqlite3VdbeMemExpandBlob(p); p->flags &= ~MEM_Str; p->flags |= MEM_Blob; - return p->z; + return p->n ? p->z : 0; }else{ return sqlite3_value_text(pVal); } @@ -343,11 +345,30 @@ static int sqlite3Step(Vdbe *p){ assert(p); if( p->magic!=VDBE_MAGIC_RUN ){ /* We used to require that sqlite3_reset() be called before retrying - ** sqlite3_step() after any error. But after 3.6.23, we changed this - ** so that sqlite3_reset() would be called automatically instead of - ** throwing the error. + ** sqlite3_step() after any error or after SQLITE_DONE. But beginning + ** with version 3.7.0, we changed this so that sqlite3_reset() would + ** be called automatically instead of throwing the SQLITE_MISUSE error. + ** This "automatic-reset" change is not technically an incompatibility, + ** since any application that receives an SQLITE_MISUSE is broken by + ** definition. + ** + ** Nevertheless, some published applications that were originally written + ** for version 3.6.23 or earlier do in fact depend on SQLITE_MISUSE + ** returns, and the so were broken by the automatic-reset change. As a + ** a work-around, the SQLITE_OMIT_AUTORESET compile-time restores the + ** 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 ){ + sqlite3_reset((sqlite3_stmt*)p); + }else{ + return SQLITE_MISUSE_BKPT; + } +#else sqlite3_reset((sqlite3_stmt*)p); +#endif } /* Check that malloc() has not failed. If it has, return early. */ @@ -389,7 +410,9 @@ static int sqlite3Step(Vdbe *p){ }else #endif /* SQLITE_OMIT_EXPLAIN */ { + db->vdbeExecCnt++; rc = sqlite3VdbeExec(p); + db->vdbeExecCnt--; } #ifndef SQLITE_OMIT_TRACE @@ -495,6 +518,12 @@ void *sqlite3_user_data(sqlite3_context *p){ /* ** Extract the user data from a sqlite3_context structure and return a ** pointer to it. +** +** IMPLEMENTATION-OF: R-46798-50301 The sqlite3_context_db_handle() interface +** 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. */ sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){ assert( p && p->pFunc ); @@ -677,7 +706,7 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ #if defined(SQLITE_DEBUG) && defined(__GNUC__) __attribute__((aligned(8))) #endif - = {{0}, (double)0, 0, "", 0, MEM_Null, SQLITE_NULL, 0, 0, 0 }; + = {0, "", (double)0, {0}, 0, MEM_Null, SQLITE_NULL, 0, 0, 0 }; if( pVm && ALWAYS(pVm->db) ){ sqlite3_mutex_enter(pVm->db->mutex); @@ -704,8 +733,7 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ ** sqlite3_column_real() ** sqlite3_column_bytes() ** sqlite3_column_bytes16() -** -** But not for sqlite3_column_blob(), which never calls malloc(). +** sqiite3_column_blob() */ static void columnMallocFailure(sqlite3_stmt *pStmt) { @@ -973,6 +1001,12 @@ static int vdbeUnbind(Vdbe *p, int i){ /* 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 + ** parameter in the WHERE clause might influence the choice of query plan + ** for a statement, then the statement will be automatically recompiled, + ** as if there had been a schema change, on the first sqlite3_step() call + ** following any change to the bindings of that parameter. */ if( p->isPrepareV2 && ((i<32 && p->expmask & ((u32)1 << i)) || p->expmask==0xffffffff) @@ -1009,6 +1043,8 @@ static int bindText( rc = sqlite3ApiExit(p->db, rc); } sqlite3_mutex_leave(p->db->mutex); + }else if( xDel!=SQLITE_STATIC && xDel!=SQLITE_TRANSIENT ){ + xDel((void*)zData); } return rc; } @@ -1251,6 +1287,14 @@ sqlite3 *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. +*/ +int sqlite3_stmt_readonly(sqlite3_stmt *pStmt){ + return pStmt ? ((Vdbe*)pStmt)->readOnly : 1; +} + /* ** Return a pointer to the next prepared statement after pStmt associated ** with database connection pDb. If pStmt is NULL, return the first diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 5a4ad849..de69e544 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -408,7 +408,7 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ pOp->opflags = sqlite3OpcodeProperty[opcode]; if( opcode==OP_Function || opcode==OP_AggStep ){ if( pOp->p5>nMaxArgs ) nMaxArgs = pOp->p5; - }else if( opcode==OP_Transaction && pOp->p2!=0 ){ + }else if( (opcode==OP_Transaction && pOp->p2!=0) || opcode==OP_Vacuum ){ p->readOnly = 0; #ifndef SQLITE_OMIT_VIRTUALTABLE }else if( opcode==OP_VUpdate ){ @@ -1182,12 +1182,10 @@ int sqlite3VdbeList( pMem->type = SQLITE_INTEGER; pMem++; - if( p->explain==1 ){ - pMem->flags = MEM_Int; - pMem->u.i = pOp->p3; /* P3 */ - 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 */ assert( p->db->mallocFailed ); @@ -1232,7 +1230,7 @@ int sqlite3VdbeList( } } - p->nResColumn = 8 - 5*(p->explain-1); + p->nResColumn = 8 - 4*(p->explain-1); p->rc = SQLITE_OK; rc = SQLITE_ROW; } @@ -1539,6 +1537,11 @@ static void closeAllCursors(Vdbe *p){ if( p->aMem ){ releaseMemArray(&p->aMem[1], p->nMem); } + while( p->pDelFrame ){ + VdbeFrame *pDel = p->pDelFrame; + p->pDelFrame = pDel->pParent; + sqlite3VdbeFrameDelete(pDel); + } } /* @@ -1755,9 +1758,10 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ Btree *pBt = db->aDb[i].pBt; if( sqlite3BtreeIsInTrans(pBt) ){ char const *zFile = sqlite3BtreeGetJournalname(pBt); - if( zFile==0 || zFile[0]==0 ){ + if( zFile==0 ){ continue; /* Ignore TEMP and :memory: databases */ } + assert( zFile[0]!=0 ); if( !needSync && !sqlite3BtreeSyncDisabled(pBt) ){ needSync = 1; } diff --git a/src/vdbeblob.c b/src/vdbeblob.c index b2b9f0ed..f26cc87e 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -26,11 +26,82 @@ struct Incrblob { int flags; /* Copy of "flags" passed to sqlite3_blob_open() */ int nByte; /* Size of open blob, in bytes */ int iOffset; /* Byte offset of blob in cursor data */ + int iCol; /* Table column this handle is open on */ BtCursor *pCsr; /* Cursor pointing at blob row */ sqlite3_stmt *pStmt; /* Statement holding cursor open */ sqlite3 *db; /* The associated database */ }; + +/* +** This function is used by both blob_open() and blob_reopen(). It seeks +** the b-tree cursor associated with blob handle p to point to row iRow. +** If successful, SQLITE_OK is returned and subsequent calls to +** sqlite3_blob_read() or sqlite3_blob_write() access the specified row. +** +** If an error occurs, or if the specified row does not exist or does not +** contain a value of type TEXT or BLOB in the column nominated when the +** blob handle was opened, then an error code is returned and *pzErr may +** be set to point to a buffer containing an error message. It is the +** responsibility of the caller to free the error message buffer using +** sqlite3DbFree(). +** +** If an error does occur, then the b-tree cursor is closed. All subsequent +** calls to sqlite3_blob_read(), blob_write() or blob_reopen() will +** immediately return SQLITE_ABORT. +*/ +static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){ + int rc; /* Error code */ + char *zErr = 0; /* Error message */ + Vdbe *v = (Vdbe *)p->pStmt; + + /* Set the value of the SQL statements only variable to integer iRow. + ** This is done directly instead of using sqlite3_bind_int64() to avoid + ** triggering asserts related to mutexes. + */ + 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]; + 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->nByte = sqlite3VdbeSerialTypeLen(type); + p->pCsr = v->apCsr[0]->pCursor; + sqlite3BtreeEnterCursor(p->pCsr); + sqlite3BtreeCacheOverflow(p->pCsr); + sqlite3BtreeLeaveCursor(p->pCsr); + } + } + + if( rc==SQLITE_ROW ){ + rc = SQLITE_OK; + }else if( p->pStmt ){ + rc = sqlite3_finalize(p->pStmt); + p->pStmt = 0; + if( rc==SQLITE_OK ){ + zErr = sqlite3MPrintf(p->db, "no such rowid: %lld", iRow); + rc = SQLITE_ERROR; + }else{ + zErr = sqlite3MPrintf(p->db, "%s", sqlite3_errmsg(p->db)); + } + } + + assert( rc!=SQLITE_OK || zErr==0 ); + assert( rc!=SQLITE_ROW && rc!=SQLITE_DONE ); + + *pzErr = zErr; + return rc; +} + /* ** Open a blob handle. */ @@ -71,29 +142,35 @@ int sqlite3_blob_open( {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, 9, 1}, /* 6: Seek the cursor */ + {OP_NotExists, 0, 10, 1}, /* 6: Seek the cursor */ {OP_Column, 0, 0, 1}, /* 7 */ {OP_ResultRow, 1, 0, 0}, /* 8 */ - {OP_Close, 0, 0, 0}, /* 9 */ - {OP_Halt, 0, 0, 0}, /* 10 */ + {OP_Goto, 0, 5, 0}, /* 9 */ + {OP_Close, 0, 0, 0}, /* 10 */ + {OP_Halt, 0, 0, 0}, /* 11 */ }; - Vdbe *v = 0; int rc = SQLITE_OK; char *zErr = 0; Table *pTab; - Parse *pParse; + Parse *pParse = 0; + Incrblob *pBlob = 0; + flags = !!flags; /* flags = (flags ? 1 : 0); */ *ppBlob = 0; + sqlite3_mutex_enter(db->mutex); + + pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob)); + if( !pBlob ) goto blob_open_out; pParse = sqlite3StackAllocRaw(db, sizeof(*pParse)); - if( pParse==0 ){ - rc = SQLITE_NOMEM; - goto blob_open_out; - } + if( !pParse ) goto blob_open_out; + do { memset(pParse, 0, sizeof(Parse)); pParse->db = db; + sqlite3DbFree(db, zErr); + zErr = 0; sqlite3BtreeEnterAll(db); pTab = sqlite3LocateTable(pParse, 0, zTable, zDb); @@ -119,7 +196,7 @@ int sqlite3_blob_open( } /* Now search pTab for the exact column. */ - for(iCol=0; iCol < pTab->nCol; iCol++) { + for(iCol=0; iColnCol; iCol++) { if( sqlite3StrICmp(pTab->aCol[iCol].zName, zColumn)==0 ){ break; } @@ -173,11 +250,14 @@ int sqlite3_blob_open( } } - v = sqlite3VdbeCreate(db); - if( v ){ + pBlob->pStmt = (sqlite3_stmt *)sqlite3VdbeCreate(db); + 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); - flags = !!flags; /* flags = (flags ? 1 : 0); */ + /* Configure the OP_Transaction */ sqlite3VdbeChangeP1(v, 0, iDb); @@ -220,65 +300,25 @@ int sqlite3_blob_open( } } + pBlob->flags = flags; + pBlob->iCol = iCol; + pBlob->db = db; sqlite3BtreeLeaveAll(db); if( db->mallocFailed ){ goto blob_open_out; } - - sqlite3_bind_int64((sqlite3_stmt *)v, 1, iRow); - rc = sqlite3_step((sqlite3_stmt *)v); - if( rc!=SQLITE_ROW ){ - nAttempt++; - rc = sqlite3_finalize((sqlite3_stmt *)v); - sqlite3DbFree(db, zErr); - zErr = sqlite3MPrintf(db, sqlite3_errmsg(db)); - v = 0; - } - } while( nAttempt<5 && rc==SQLITE_SCHEMA ); - - if( rc==SQLITE_ROW ){ - /* The row-record has been opened successfully. Check that the - ** column in question contains text or a blob. If it contains - ** text, it is up to the caller to get the encoding right. - */ - Incrblob *pBlob; - u32 type = v->apCsr[0]->aType[iCol]; - - if( type<12 ){ - sqlite3DbFree(db, zErr); - zErr = sqlite3MPrintf(db, "cannot open value of type %s", - type==0?"null": type==7?"real": "integer" - ); - rc = SQLITE_ERROR; - goto blob_open_out; - } - pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob)); - if( db->mallocFailed ){ - sqlite3DbFree(db, pBlob); - goto blob_open_out; - } - pBlob->flags = flags; - pBlob->pCsr = v->apCsr[0]->pCursor; - sqlite3BtreeEnterCursor(pBlob->pCsr); - sqlite3BtreeCacheOverflow(pBlob->pCsr); - sqlite3BtreeLeaveCursor(pBlob->pCsr); - pBlob->pStmt = (sqlite3_stmt *)v; - pBlob->iOffset = v->apCsr[0]->aOffset[iCol]; - pBlob->nByte = sqlite3VdbeSerialTypeLen(type); - pBlob->db = db; - *ppBlob = (sqlite3_blob *)pBlob; - rc = SQLITE_OK; - }else if( rc==SQLITE_OK ){ - sqlite3DbFree(db, zErr); - zErr = sqlite3MPrintf(db, "no such rowid: %lld", iRow); - rc = SQLITE_ERROR; - } + sqlite3_bind_int64(pBlob->pStmt, 1, iRow); + rc = blobSeekToRow(pBlob, iRow, &zErr); + } while( (++nAttempt)<5 && rc==SQLITE_SCHEMA ); blob_open_out: - if( v && (rc!=SQLITE_OK || db->mallocFailed) ){ - sqlite3VdbeFinalize(v); + if( rc==SQLITE_OK && db->mallocFailed==0 ){ + *ppBlob = (sqlite3_blob *)pBlob; + }else{ + if( pBlob && pBlob->pStmt ) sqlite3VdbeFinalize((Vdbe *)pBlob->pStmt); + sqlite3DbFree(db, pBlob); } - sqlite3Error(db, rc, zErr); + sqlite3Error(db, rc, (zErr ? "%s" : 0), zErr); sqlite3DbFree(db, zErr); sqlite3StackFree(db, pParse); rc = sqlite3ApiExit(db, rc); @@ -331,7 +371,7 @@ static int blobReadWrite( /* Request is out of range. Return a transient error. */ rc = SQLITE_ERROR; sqlite3Error(db, SQLITE_ERROR, 0); - } else if( v==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. */ @@ -379,7 +419,47 @@ int sqlite3_blob_write(sqlite3_blob *pBlob, const void *z, int n, int iOffset){ */ int sqlite3_blob_bytes(sqlite3_blob *pBlob){ Incrblob *p = (Incrblob *)pBlob; - return p ? p->nByte : 0; + return (p && p->pStmt) ? p->nByte : 0; +} + +/* +** Move an existing blob handle to point to a different row of the same +** database table. +** +** If an error occurs, or if the specified row does not exist or does not +** 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. +*/ +int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ + int rc; + Incrblob *p = (Incrblob *)pBlob; + sqlite3 *db; + + if( p==0 ) return SQLITE_MISUSE_BKPT; + db = p->db; + sqlite3_mutex_enter(db->mutex); + + if( p->pStmt==0 ){ + /* If there is no statement handle, then the blob-handle has + ** already been invalidated. Return SQLITE_ABORT in this case. + */ + rc = SQLITE_ABORT; + }else{ + char *zErr; + rc = blobSeekToRow(p, iRow, &zErr); + if( rc!=SQLITE_OK ){ + sqlite3Error(db, rc, (zErr ? "%s" : 0), zErr); + sqlite3DbFree(db, zErr); + } + assert( rc!=SQLITE_SCHEMA ); + } + + rc = sqlite3ApiExit(db, rc); + assert( rc==SQLITE_OK || p->pStmt==0 ); + sqlite3_mutex_leave(db->mutex); + return rc; } #endif /* #ifndef SQLITE_OMIT_INCRBLOB */ diff --git a/src/vdbemem.c b/src/vdbemem.c index e376a656..4831d806 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -132,6 +132,9 @@ int sqlite3VdbeMemMakeWriteable(Mem *pMem){ pMem->z[pMem->n] = 0; pMem->z[pMem->n+1] = 0; pMem->flags |= MEM_Term; +#ifdef SQLITE_DEBUG + pMem->pScopyFrom = 0; +#endif } return SQLITE_OK; @@ -252,7 +255,7 @@ int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ ctx.s.db = pMem->db; ctx.pMem = pMem; ctx.pFunc = pFunc; - pFunc->xFinalize(&ctx); + 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)); @@ -365,13 +368,9 @@ i64 sqlite3VdbeIntValue(Mem *pMem){ return doubleToInt64(pMem->r); }else if( flags & (MEM_Str|MEM_Blob) ){ i64 value; - pMem->flags |= MEM_Str; - if( sqlite3VdbeChangeEncoding(pMem, SQLITE_UTF8) - || sqlite3VdbeMemNulTerminate(pMem) ){ - return 0; - } - assert( pMem->z ); - sqlite3Atoi64(pMem->z, &value); + assert( pMem->z || pMem->n==0 ); + testcase( pMem->z==0 ); + sqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc); return value; }else{ return 0; @@ -394,14 +393,7 @@ double sqlite3VdbeRealValue(Mem *pMem){ }else if( pMem->flags & (MEM_Str|MEM_Blob) ){ /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ double val = (double)0; - pMem->flags |= MEM_Str; - if( sqlite3VdbeChangeEncoding(pMem, SQLITE_UTF8) - || sqlite3VdbeMemNulTerminate(pMem) ){ - /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ - return (double)0; - } - assert( pMem->z ); - sqlite3AtoF(pMem->z, &val); + sqlite3AtoF(pMem->z, &val, pMem->n, pMem->enc); return val; }else{ /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ @@ -474,21 +466,19 @@ int sqlite3VdbeMemRealify(Mem *pMem){ ** as much of the string as we can and ignore the rest. */ int sqlite3VdbeMemNumerify(Mem *pMem){ - int rc; - assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))==0 ); - assert( (pMem->flags & (MEM_Blob|MEM_Str))!=0 ); - assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); - rc = sqlite3VdbeChangeEncoding(pMem, SQLITE_UTF8); - if( rc ) return rc; - rc = sqlite3VdbeMemNulTerminate(pMem); - if( rc ) return rc; - if( sqlite3Atoi64(pMem->z, &pMem->u.i) ){ - MemSetTypeFlag(pMem, MEM_Int); - }else{ - pMem->r = sqlite3VdbeRealValue(pMem); - MemSetTypeFlag(pMem, MEM_Real); - sqlite3VdbeIntegerAffinity(pMem); + if( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))==0 ){ + 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); + 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; } @@ -497,7 +487,9 @@ int sqlite3VdbeMemNumerify(Mem *pMem){ */ void sqlite3VdbeMemSetNull(Mem *pMem){ if( pMem->flags & MEM_Frame ){ - sqlite3VdbeFrameDelete(pMem->u.pFrame); + VdbeFrame *pFrame = pMem->u.pFrame; + pFrame->pParent = pFrame->v->pDelFrame; + pFrame->v->pDelFrame = pFrame; } if( pMem->flags & MEM_RowSet ){ sqlite3RowSetClear(pMem->u.pRowSet); @@ -593,6 +585,28 @@ int sqlite3VdbeMemTooBig(Mem *p){ return 0; } +#ifdef SQLITE_DEBUG +/* +** This routine prepares a memory cell for modication 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. +*/ +void sqlite3VdbeMemPrepareToChange(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->pScopyFrom = 0; + } + } + pMem->pScopyFrom = 0; +} +#endif /* SQLITE_DEBUG */ + /* ** Size of struct Mem not including the Mem.zMalloc member. */ @@ -961,7 +975,7 @@ const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){ return 0; } } - sqlite3VdbeMemNulTerminate(pVal); + sqlite3VdbeMemNulTerminate(pVal); /* IMP: R-59893-45467 */ }else{ assert( (pVal->flags&MEM_Blob)==0 ); sqlite3VdbeMemStringify(pVal, enc); @@ -1009,6 +1023,8 @@ int sqlite3ValueFromExpr( int op; char *zVal = 0; sqlite3_value *pVal = 0; + int negInt = 1; + const char *zNeg = ""; if( !pExpr ){ *ppVal = 0; @@ -1026,13 +1042,24 @@ int sqlite3ValueFromExpr( if( NEVER(op==TK_REGISTER) ) op = pExpr->op2; #endif + /* Handle negative integers in a single step. This is needed in the + ** case when the value is -9223372036854775808. + */ + if( op==TK_UMINUS + && (pExpr->pLeft->op==TK_INTEGER || pExpr->pLeft->op==TK_FLOAT) ){ + pExpr = pExpr->pLeft; + op = pExpr->op; + negInt = -1; + zNeg = "-"; + } + if( op==TK_STRING || op==TK_FLOAT || op==TK_INTEGER ){ pVal = sqlite3ValueNew(db); if( pVal==0 ) goto no_mem; if( ExprHasProperty(pExpr, EP_IntValue) ){ - sqlite3VdbeMemSetInt64(pVal, (i64)pExpr->u.iValue); + sqlite3VdbeMemSetInt64(pVal, (i64)pExpr->u.iValue*negInt); }else{ - zVal = sqlite3DbStrDup(db, pExpr->u.zToken); + 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; @@ -1042,14 +1069,18 @@ int sqlite3ValueFromExpr( }else{ sqlite3ValueApplyAffinity(pVal, affinity, SQLITE_UTF8); } + if( pVal->flags & (MEM_Int|MEM_Real) ) pVal->flags &= ~MEM_Str; if( enc!=SQLITE_UTF8 ){ sqlite3VdbeChangeEncoding(pVal, enc); } }else if( op==TK_UMINUS ) { + /* This branch happens for multiple negative signs. Ex: -(-5) */ if( SQLITE_OK==sqlite3ValueFromExpr(db,pExpr->pLeft,enc,affinity,&pVal) ){ + sqlite3VdbeMemNumerify(pVal); pVal->u.i = -1 * pVal->u.i; /* (double)-1 In case of SQLITE_OMIT_FLOATING_POINT... */ pVal->r = (double)-1 * pVal->r; + sqlite3ValueApplyAffinity(pVal, affinity, enc); } } #ifndef SQLITE_OMIT_BLOB_LITERAL diff --git a/src/vdbetrace.c b/src/vdbetrace.c index 0d6b2121..b422fa9e 100644 --- a/src/vdbetrace.c +++ b/src/vdbetrace.c @@ -44,9 +44,12 @@ static int findNextHostParameter(const char *zSql, int *pnToken){ } /* -** Return a pointer to a string in memory obtained form sqlite3DbMalloc() which -** holds a copy of zRawSql but with host parameters expanded to their -** current bindings. +** This function returns a pointer to a nul-terminated string in memory +** obtained from sqlite3DbMalloc(). If sqlite3.vdbeExecCnt is 1, then the +** string contains a copy of zRawSql but with host parameters expanded to +** their current bindings. Or, if sqlite3.vdbeExecCnt is greater than 1, +** then the returned string holds a copy of zRawSql with "-- " prepended +** to each line of text. ** ** The calling function is responsible for making sure the memory returned ** is eventually freed. @@ -77,63 +80,72 @@ char *sqlite3VdbeExpandSql( sqlite3StrAccumInit(&out, zBase, sizeof(zBase), db->aLimit[SQLITE_LIMIT_LENGTH]); out.db = db; - while( zRawSql[0] ){ - n = findNextHostParameter(zRawSql, &nToken); - assert( n>0 ); - sqlite3StrAccumAppend(&out, zRawSql, n); - zRawSql += n; - assert( zRawSql[0] || nToken==0 ); - if( nToken==0 ) break; - if( zRawSql[0]=='?' ){ - if( nToken>1 ){ - assert( sqlite3Isdigit(zRawSql[1]) ); - sqlite3GetInt32(&zRawSql[1], &idx); - }else{ - idx = nextIndex; - } - }else{ - assert( zRawSql[0]==':' || zRawSql[0]=='$' || zRawSql[0]=='@' ); - testcase( zRawSql[0]==':' ); - testcase( zRawSql[0]=='$' ); - testcase( zRawSql[0]=='@' ); - idx = sqlite3VdbeParameterIndex(p, zRawSql, nToken); - assert( idx>0 ); + if( db->vdbeExecCnt>1 ){ + while( *zRawSql ){ + const char *zStart = zRawSql; + while( *(zRawSql++)!='\n' && *zRawSql ); + sqlite3StrAccumAppend(&out, "-- ", 3); + sqlite3StrAccumAppend(&out, zStart, zRawSql-zStart); } - zRawSql += nToken; - nextIndex = idx + 1; - 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); - }else if( pVar->flags & MEM_Real ){ - sqlite3XPrintf(&out, "%!.15g", pVar->r); - }else if( pVar->flags & MEM_Str ){ + }else{ + while( zRawSql[0] ){ + n = findNextHostParameter(zRawSql, &nToken); + assert( n>0 ); + sqlite3StrAccumAppend(&out, zRawSql, n); + zRawSql += n; + assert( zRawSql[0] || nToken==0 ); + if( nToken==0 ) break; + if( zRawSql[0]=='?' ){ + if( nToken>1 ){ + assert( sqlite3Isdigit(zRawSql[1]) ); + sqlite3GetInt32(&zRawSql[1], &idx); + }else{ + idx = nextIndex; + } + }else{ + assert( zRawSql[0]==':' || zRawSql[0]=='$' || zRawSql[0]=='@' ); + testcase( zRawSql[0]==':' ); + testcase( zRawSql[0]=='$' ); + testcase( zRawSql[0]=='@' ); + idx = sqlite3VdbeParameterIndex(p, zRawSql, nToken); + assert( idx>0 ); + } + zRawSql += nToken; + nextIndex = idx + 1; + 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); + }else if( pVar->flags & MEM_Real ){ + sqlite3XPrintf(&out, "%!.15g", pVar->r); + }else if( pVar->flags & MEM_Str ){ #ifndef SQLITE_OMIT_UTF16 - u8 enc = ENC(db); - if( enc!=SQLITE_UTF8 ){ - Mem utf8; - memset(&utf8, 0, sizeof(utf8)); - utf8.db = db; - sqlite3VdbeMemSetStr(&utf8, pVar->z, pVar->n, enc, SQLITE_STATIC); - sqlite3VdbeChangeEncoding(&utf8, SQLITE_UTF8); - sqlite3XPrintf(&out, "'%.*q'", utf8.n, utf8.z); - sqlite3VdbeMemRelease(&utf8); - }else + u8 enc = ENC(db); + if( enc!=SQLITE_UTF8 ){ + Mem utf8; + memset(&utf8, 0, sizeof(utf8)); + utf8.db = db; + sqlite3VdbeMemSetStr(&utf8, pVar->z, pVar->n, enc, SQLITE_STATIC); + sqlite3VdbeChangeEncoding(&utf8, SQLITE_UTF8); + sqlite3XPrintf(&out, "'%.*q'", utf8.n, utf8.z); + sqlite3VdbeMemRelease(&utf8); + }else #endif - { - sqlite3XPrintf(&out, "'%.*q'", pVar->n, pVar->z); + { + sqlite3XPrintf(&out, "'%.*q'", pVar->n, pVar->z); + } + }else if( pVar->flags & MEM_Zero ){ + sqlite3XPrintf(&out, "zeroblob(%d)", pVar->u.nZero); + }else{ + assert( pVar->flags & MEM_Blob ); + sqlite3StrAccumAppend(&out, "x'", 2); + for(i=0; in; i++){ + sqlite3XPrintf(&out, "%02x", pVar->z[i]&0xff); + } + sqlite3StrAccumAppend(&out, "'", 1); } - }else if( pVar->flags & MEM_Zero ){ - sqlite3XPrintf(&out, "zeroblob(%d)", pVar->u.nZero); - }else{ - assert( pVar->flags & MEM_Blob ); - sqlite3StrAccumAppend(&out, "x'", 2); - for(i=0; in; i++){ - sqlite3XPrintf(&out, "%02x", pVar->z[i]&0xff); - } - sqlite3StrAccumAppend(&out, "'", 1); } } return sqlite3StrAccumFinish(&out); diff --git a/src/vtab.c b/src/vtab.c index 5104eebb..e460ee59 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -371,7 +371,7 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddOp2(v, OP_Expire, 0, 0); - zWhere = sqlite3MPrintf(db, "name='%q'", pTab->zName); + zWhere = sqlite3MPrintf(db, "name='%q' AND type='table'", pTab->zName); sqlite3VdbeAddOp4(v, OP_ParseSchema, iDb, 1, 0, zWhere, P4_DYNAMIC); sqlite3VdbeAddOp4(v, OP_VCreate, iDb, 0, 0, pTab->zName, sqlite3Strlen30(pTab->zName) + 1); @@ -672,7 +672,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ } db->pVTab = 0; }else{ - sqlite3Error(db, SQLITE_ERROR, zErr); + sqlite3Error(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr); sqlite3DbFree(db, zErr); rc = SQLITE_ERROR; } diff --git a/src/wal.c b/src/wal.c index 22ce1fa7..ff327bf6 100644 --- a/src/wal.c +++ b/src/wal.c @@ -428,6 +428,13 @@ struct Wal { #endif }; +/* +** Candidate values for Wal.exclusiveMode. +*/ +#define WAL_NORMAL_MODE 0 +#define WAL_EXCLUSIVE_MODE 1 +#define WAL_HEAPMEMORY_MODE 2 + /* ** Each page of the wal-index mapping contains a hash-table made up of ** an array of HASHTABLE_NSLOT elements of the following type. @@ -451,14 +458,14 @@ typedef u16 ht_slot; */ struct WalIterator { int iPrior; /* Last result returned from the iterator */ - int nSegment; /* Size of the aSegment[] array */ + int nSegment; /* Number of entries in aSegment[] */ struct WalSegment { int iNext; /* Next slot in aIndex[] not yet returned */ ht_slot *aIndex; /* i0, i1, i2... such that aPgno[iN] ascend */ u32 *aPgno; /* Array of page numbers. */ - int nEntry; /* Max size of aPgno[] and aIndex[] arrays */ + int nEntry; /* Nr. of entries in aPgno[] and aIndex[] */ int iZero; /* Frame number associated with aPgno[0] */ - } aSegment[1]; /* One for every 32KB page in the WAL */ + } aSegment[1]; /* One for every 32KB page in the wal-index */ }; /* @@ -514,9 +521,14 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){ /* Request a pointer to the required page from the VFS */ if( pWal->apWiData[iPage]==0 ){ - rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, - pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] - ); + if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){ + pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ); + if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM; + }else{ + rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, + pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] + ); + } } *ppPage = pWal->apWiData[iPage]; @@ -599,6 +611,12 @@ static void walChecksumBytes( aOut[1] = s2; } +static void walShmBarrier(Wal *pWal){ + if( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE ){ + sqlite3OsShmBarrier(pWal->pDbFd); + } +} + /* ** Write the header information in pWal->hdr into the wal-index. ** @@ -613,7 +631,7 @@ static void walIndexWriteHdr(Wal *pWal){ pWal->hdr.iVersion = WALINDEX_MAX_VERSION; walChecksumBytes(1, (u8*)&pWal->hdr, nCksum, 0, pWal->hdr.aCksum); memcpy((void *)&aHdr[1], (void *)&pWal->hdr, sizeof(WalIndexHdr)); - sqlite3OsShmBarrier(pWal->pDbFd); + walShmBarrier(pWal); memcpy((void *)&aHdr[0], (void *)&pWal->hdr, sizeof(WalIndexHdr)); } @@ -1185,7 +1203,15 @@ recovery_error: ** Close an open wal-index. */ static void walIndexClose(Wal *pWal, int isDelete){ - sqlite3OsShmUnmap(pWal->pDbFd, isDelete); + if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){ + int i; + for(i=0; inWiData; i++){ + sqlite3_free((void *)pWal->apWiData[i]); + pWal->apWiData[i] = 0; + } + }else{ + sqlite3OsShmUnmap(pWal->pDbFd, isDelete); + } } /* @@ -1207,6 +1233,7 @@ int sqlite3WalOpen( sqlite3_vfs *pVfs, /* vfs module to open wal and wal-index */ sqlite3_file *pDbFd, /* The open database file */ const char *zWalName, /* Name of the WAL file */ + int bNoShm, /* True to run in heap-memory mode */ Wal **ppWal /* OUT: Allocated Wal handle */ ){ int rc; /* Return Code */ @@ -1240,6 +1267,7 @@ int sqlite3WalOpen( pRet->pDbFd = pDbFd; pRet->readLock = -1; pRet->zWalName = zWalName; + pRet->exclusiveMode = (bNoShm ? WAL_HEAPMEMORY_MODE: WAL_NORMAL_MODE); /* Open file handle on the write-ahead log file. */ flags = (SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_WAL); @@ -1301,9 +1329,29 @@ static int walIteratorNext( /* ** This function merges two sorted lists into a single sorted list. +** +** aLeft[] and aRight[] are arrays of indices. The sort key is +** aContent[aLeft[]] and aContent[aRight[]]. Upon entry, the following +** is guaranteed for all Jhdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16); testcase( szPage<=32768 ); testcase( szPage>=65536 ); - if( pWal->hdr.mxFrame==0 ) return SQLITE_OK; + pInfo = walCkptInfo(pWal); + if( pInfo->nBackfill>=pWal->hdr.mxFrame ) return SQLITE_OK; /* Allocate the iterator */ rc = walIteratorInit(pWal, &pIter); @@ -1567,7 +1631,6 @@ static int walCheckpoint( */ mxSafeFrame = pWal->hdr.mxFrame; mxPage = pWal->hdr.nPage; - pInfo = walCkptInfo(pWal); for(i=1; iaReadMark[i]; if( mxSafeFrame>=y ){ @@ -1673,7 +1736,9 @@ int sqlite3WalClose( */ rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE); if( rc==SQLITE_OK ){ - pWal->exclusiveMode = 1; + if( pWal->exclusiveMode==WAL_NORMAL_MODE ){ + pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; + } rc = sqlite3WalCheckpoint(pWal, sync_flags, nBuf, zBuf); if( rc==SQLITE_OK ){ isDelete = 1; @@ -1729,7 +1794,7 @@ static int walIndexTryHdr(Wal *pWal, int *pChanged){ */ aHdr = walIndexHdr(pWal); memcpy(&h1, (void *)&aHdr[0], sizeof(h1)); - sqlite3OsShmBarrier(pWal->pDbFd); + walShmBarrier(pWal); memcpy(&h2, (void *)&aHdr[1], sizeof(h2)); if( memcmp(&h1, &h2, sizeof(h1))!=0 ){ @@ -1930,7 +1995,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** and can be safely ignored. */ rc = walLockShared(pWal, WAL_READ_LOCK(0)); - sqlite3OsShmBarrier(pWal->pDbFd); + walShmBarrier(pWal); if( rc==SQLITE_OK ){ if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){ /* It is not safe to allow the reader to continue here if frames @@ -2024,7 +2089,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** log-wrap (either of which would require an exclusive lock on ** WAL_READ_LOCK(mxI)) has not occurred since the snapshot was valid. */ - sqlite3OsShmBarrier(pWal->pDbFd); + walShmBarrier(pWal); if( pInfo->aReadMark[mxI]!=mxReadMark || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){ @@ -2366,7 +2431,7 @@ int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ ** ** SQLITE_OK is returned if no error is encountered (regardless of whether ** or not pWal->hdr.mxFrame is modified). An SQLite error code is returned -** if some error +** if an error occurs. */ static int walRestartLog(Wal *pWal){ int rc = SQLITE_OK; @@ -2399,6 +2464,8 @@ static int walRestartLog(Wal *pWal){ for(i=1; iaReadMark[i] = READMARK_NOT_USED; assert( pInfo->aReadMark[0]==0 ); walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); + }else if( rc!=SQLITE_BUSY ){ + return rc; } } walUnlockShared(pWal, WAL_READ_LOCK(0)); @@ -2478,7 +2545,7 @@ int sqlite3WalFrames( return rc; } } - assert( pWal->szPage==szPage ); + assert( (int)pWal->szPage==szPage ); /* Write the log file. */ for(p=pList; p; p=p->pDirty){ @@ -2665,13 +2732,14 @@ int sqlite3WalCallback(Wal *pWal){ ** on the main database file before invoking this operation. ** ** If op is negative, then do a dry-run of the op==1 case but do -** not actually change anything. The pager uses this to see if it +** not actually change anything. The pager uses this to see if it ** should acquire the database exclusive lock prior to invoking ** the op==1 case. */ int sqlite3WalExclusiveMode(Wal *pWal, int op){ int rc; assert( pWal->writeLock==0 ); + assert( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE || op==-1 ); /* pWal->readLock is usually set, but might be -1 if there was a ** prior error while attempting to acquire are read-lock. This cannot @@ -2705,4 +2773,13 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op){ return rc; } +/* +** Return true if the argument is non-NULL and the WAL module is using +** heap-memory for the wal-index. Otherwise, if the argument is NULL or the +** WAL module is using shared-memory, return false. +*/ +int sqlite3WalHeapMemory(Wal *pWal){ + return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ); +} + #endif /* #ifndef SQLITE_OMIT_WAL */ diff --git a/src/wal.h b/src/wal.h index f20dfa8e..35f695c8 100644 --- a/src/wal.h +++ b/src/wal.h @@ -35,6 +35,7 @@ # define sqlite3WalCheckpoint(u,v,w,x) 0 # define sqlite3WalCallback(z) 0 # define sqlite3WalExclusiveMode(y,z) 0 +# define sqlite3WalHeapMemory(z) 0 #else #define WAL_SAVEPOINT_NDATA 4 @@ -45,7 +46,7 @@ typedef struct Wal Wal; /* Open and close a connection to a write-ahead log. */ -int sqlite3WalOpen(sqlite3_vfs*, sqlite3_file*, const char *zName, Wal**); +int sqlite3WalOpen(sqlite3_vfs*, sqlite3_file*, const char *zName, int, Wal**); int sqlite3WalClose(Wal *pWal, int sync_flags, int, u8 *); /* Used by readers to open (lock) and close (unlock) a snapshot. A @@ -102,5 +103,11 @@ int sqlite3WalCallback(Wal *pWal); */ int sqlite3WalExclusiveMode(Wal *pWal, int op); +/* Return true if the argument is non-NULL and the WAL module is using +** heap-memory for the wal-index. Otherwise, if the argument is NULL or the +** WAL module is using shared-memory, return false. +*/ +int sqlite3WalHeapMemory(Wal *pWal); + #endif /* ifndef SQLITE_OMIT_WAL */ #endif /* _WAL_H_ */ diff --git a/src/where.c b/src/where.c index 9040ad0d..4a5026f5 100644 --- a/src/where.c +++ b/src/where.c @@ -192,7 +192,6 @@ struct WhereMaskSet { struct WhereCost { WherePlan plan; /* The lookup strategy */ double rCost; /* Overall cost of pursuing this search strategy */ - double nRow; /* Estimated number of output rows */ Bitmask used; /* Bitmask of cursors used by this plan */ }; @@ -235,10 +234,11 @@ struct WhereCost { #define WHERE_COLUMN_IN 0x00040000 /* x IN (...) */ #define WHERE_COLUMN_NULL 0x00080000 /* x IS NULL */ #define WHERE_INDEXED 0x000f0000 /* Anything that uses an index */ -#define WHERE_NOT_FULLSCAN 0x000f3000 /* Does not do a full table scan */ +#define WHERE_NOT_FULLSCAN 0x100f3000 /* Does not do a full table scan */ #define WHERE_IN_ABLE 0x000f1000 /* Able to support an IN operator */ #define WHERE_TOP_LIMIT 0x00100000 /* xEXPR or x>=EXPR constraint */ +#define WHERE_BOTH_LIMIT 0x00300000 /* Both x>EXPR and xpReprepare; - pVal = sqlite3VdbeGetValue(pReprepare, pRight->iColumn, SQLITE_AFF_NONE); + int iCol = pRight->iColumn; + pVal = sqlite3VdbeGetValue(pReprepare, iCol, SQLITE_AFF_NONE); if( pVal && sqlite3_value_type(pVal)==SQLITE_TEXT ){ z = (char *)sqlite3_value_text(pVal); } - sqlite3VdbeSetVarmask(pParse->pVdbe, pRight->iColumn); + sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); /* IMP: R-23257-02778 */ assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER ); }else if( op==TK_STRING ){ z = pRight->u.zToken; @@ -691,7 +692,7 @@ static int isLikeOrGlob( *ppPrefix = pPrefix; if( op==TK_VARIABLE ){ Vdbe *v = pParse->pVdbe; - sqlite3VdbeSetVarmask(v, pRight->iColumn); + sqlite3VdbeSetVarmask(v, pRight->iColumn); /* IMP: R-23257-02778 */ 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 @@ -1555,7 +1556,8 @@ static void TRACE_IDX_OUTPUTS(sqlite3_index_info *p){ ** Required because bestIndex() is called by bestOrClauseIndex() */ static void bestIndex( - Parse*, WhereClause*, struct SrcList_item*, Bitmask, ExprList*, WhereCost*); + Parse*, WhereClause*, struct SrcList_item*, + Bitmask, Bitmask, ExprList*, WhereCost*); /* ** This routine attempts to find an scanning strategy that can be used @@ -1568,7 +1570,8 @@ static void bestOrClauseIndex( Parse *pParse, /* The parsing context */ WhereClause *pWC, /* The WHERE clause */ struct SrcList_item *pSrc, /* The FROM clause term to search */ - Bitmask notReady, /* Mask of cursors that are not available */ + Bitmask notReady, /* Mask of cursors not available for indexing */ + Bitmask notValid, /* Cursors not available for any purpose */ ExprList *pOrderBy, /* The ORDER BY clause */ WhereCost *pCost /* Lowest cost query plan */ ){ @@ -1578,8 +1581,9 @@ static void bestOrClauseIndex( WhereTerm * const pWCEnd = &pWC->a[pWC->nTerm]; /* End of pWC->a[] */ WhereTerm *pTerm; /* A single term of the WHERE clause */ - /* No OR-clause optimization allowed if the NOT INDEXED clause is used */ - if( pSrc->notIndexed ){ + /* No OR-clause optimization allowed if the INDEXED BY or NOT INDEXED clauses + ** are used */ + if( pSrc->notIndexed || pSrc->pIndex!=0 ){ return; } @@ -1604,7 +1608,7 @@ static void bestOrClauseIndex( )); if( pOrTerm->eOperator==WO_AND ){ WhereClause *pAndWC = &pOrTerm->u.pAndInfo->wc; - bestIndex(pParse, pAndWC, pSrc, notReady, 0, &sTermCost); + bestIndex(pParse, pAndWC, pSrc, notReady, notValid, 0, &sTermCost); }else if( pOrTerm->leftCursor==iCur ){ WhereClause tempWC; tempWC.pParse = pWC->pParse; @@ -1612,12 +1616,12 @@ static void bestOrClauseIndex( tempWC.op = TK_AND; tempWC.a = pOrTerm; tempWC.nTerm = 1; - bestIndex(pParse, &tempWC, pSrc, notReady, 0, &sTermCost); + bestIndex(pParse, &tempWC, pSrc, notReady, notValid, 0, &sTermCost); }else{ continue; } rTotal += sTermCost.rCost; - nRow += sTermCost.nRow; + nRow += sTermCost.plan.nRow; used |= sTermCost.used; if( rTotal>=pCost->rCost ) break; } @@ -1636,8 +1640,8 @@ static void bestOrClauseIndex( WHERETRACE(("... multi-index OR cost=%.9g nrow=%.9g\n", rTotal, nRow)); if( rTotalrCost ){ pCost->rCost = rTotal; - pCost->nRow = nRow; pCost->used = used; + pCost->plan.nRow = nRow; pCost->plan.wsFlags = flags; pCost->plan.u.pTerm = pTerm; } @@ -1705,7 +1709,7 @@ static void bestAutomaticIndex( assert( pParse->nQueryLoop >= (double)1 ); pTable = pSrc->pTab; - nTableRow = pTable->pIndex ? pTable->pIndex->aiRowEst[0] : 1000000; + nTableRow = pTable->nRowEst; logN = estLog(nTableRow); costTempIdx = 2*logN*(nTableRow/pParse->nQueryLoop + 1); if( costTempIdx>=pCost->rCost ){ @@ -1721,7 +1725,7 @@ static void bestAutomaticIndex( WHERETRACE(("auto-index reduces cost from %.2f to %.2f\n", pCost->rCost, costTempIdx)); pCost->rCost = costTempIdx; - pCost->nRow = logN + 1; + pCost->plan.nRow = logN + 1; pCost->plan.wsFlags = WHERE_TEMP_INDEX; pCost->used = pTerm->prereqRight; break; @@ -2059,7 +2063,8 @@ static void bestVirtualIndex( Parse *pParse, /* The parsing context */ WhereClause *pWC, /* The WHERE clause */ struct SrcList_item *pSrc, /* The FROM clause term to search */ - Bitmask notReady, /* Mask of cursors that are not available */ + Bitmask notReady, /* Mask of cursors not available for index */ + Bitmask notValid, /* Cursors not valid for any purpose */ ExprList *pOrderBy, /* The order by clause */ WhereCost *pCost, /* Lowest cost query plan */ sqlite3_index_info **ppIdxInfo /* Index information passed to xBestIndex */ @@ -2189,7 +2194,7 @@ static void bestVirtualIndex( /* Try to find a more efficient access pattern by using multiple indexes ** to optimize an OR expression within the WHERE clause. */ - bestOrClauseIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost); + bestOrClauseIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost); } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -2310,12 +2315,11 @@ static int valueFromExpr( u8 aff, sqlite3_value **pp ){ - /* The evalConstExpr() function will have already converted any TK_VARIABLE - ** expression involved in an comparison into a TK_REGISTER. */ - assert( pExpr->op!=TK_VARIABLE ); - if( pExpr->op==TK_REGISTER && pExpr->op2==TK_VARIABLE ){ + if( pExpr->op==TK_VARIABLE + || (pExpr->op==TK_REGISTER && pExpr->op2==TK_VARIABLE) + ){ int iVar = pExpr->iColumn; - sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); + sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); /* IMP: R-23257-02778 */ *pp = sqlite3VdbeGetValue(pParse->pReprepare, iVar, aff); return SQLITE_OK; } @@ -2470,7 +2474,8 @@ static void bestBtreeIndex( Parse *pParse, /* The parsing context */ WhereClause *pWC, /* The WHERE clause */ struct SrcList_item *pSrc, /* The FROM clause term to search */ - Bitmask notReady, /* Mask of cursors that are not available */ + Bitmask notReady, /* Mask of cursors not available for indexing */ + Bitmask notValid, /* Cursors not available for any purpose */ ExprList *pOrderBy, /* The ORDER BY clause */ WhereCost *pCost /* Lowest cost query plan */ ){ @@ -2512,23 +2517,14 @@ static void bestBtreeIndex( sPk.nColumn = 1; sPk.aiColumn = &aiColumnPk; sPk.aiRowEst = aiRowEstPk; - aiRowEstPk[1] = 1; sPk.onError = OE_Replace; sPk.pTable = pSrc->pTab; + aiRowEstPk[0] = pSrc->pTab->nRowEst; + aiRowEstPk[1] = 1; pFirst = pSrc->pTab->pIndex; if( pSrc->notIndexed==0 ){ sPk.pNext = pFirst; } - /* The aiRowEstPk[0] is an estimate of the total number of rows in the - ** table. Get this information from the ANALYZE information if it is - ** available. If not available, assume the table 1 million rows in size. - */ - if( pFirst ){ - assert( pFirst->aiRowEst!=0 ); /* Allocated together with pFirst */ - aiRowEstPk[0] = pFirst->aiRowEst[0]; - }else{ - aiRowEstPk[0] = 1000000; - } pProbe = &sPk; wsFlagMask = ~( WHERE_COLUMN_IN|WHERE_COLUMN_EQ|WHERE_COLUMN_NULL|WHERE_COLUMN_RANGE @@ -2741,16 +2737,16 @@ static void bestBtreeIndex( ** with this step if we already know this index will not be chosen. ** Also, never reduce the output row count below 2 using this step. ** - ** Do not reduce the output row count if pSrc is the only table that - ** is notReady; if notReady is a power of two. This will be the case - ** when the main sqlite3WhereBegin() loop is scanning for a table with - ** and "optimal" index, and on such a scan the output row count - ** reduction is not valid because it does not update the "pCost->used" - ** bitmap. The notReady bitmap will also be a power of two when we - ** are scanning for the last table in a 64-way join. We are willing - ** to bypass this optimization in that corner case. + ** It is critical that the notValid mask be used here instead of + ** the notReady mask. When computing an "optimal" index, the notReady + ** mask will only have one bit set - the bit for the current table. + ** The notValid mask, on the other hand, always has all bits set for + ** tables that are not in outer loops. If notReady is used here instead + ** of notValid, then a optimal index that depends on inner joins loops + ** might be selected even when there exists an optimal index that has + ** no such dependency. */ - if( nRow>2 && cost<=pCost->rCost && (notReady & (notReady-1))!=0 ){ + if( nRow>2 && cost<=pCost->rCost ){ int k; /* Loop counter */ int nSkipEq = nEq; /* Number of == constraints to skip */ int nSkipRange = nBound; /* Number of < constraints to skip */ @@ -2759,7 +2755,7 @@ static void bestBtreeIndex( thisTab = getMask(pWC->pMaskSet, iCur); for(pTerm=pWC->a, k=pWC->nTerm; nRow>2 && k; k--, pTerm++){ if( pTerm->wtFlags & TERM_VIRTUAL ) continue; - if( (pTerm->prereqAll & notReady)!=thisTab ) continue; + if( (pTerm->prereqAll & notValid)!=thisTab ) continue; if( pTerm->eOperator & (WO_EQ|WO_IN|WO_ISNULL) ){ if( nSkipEq ){ /* Ignore the first nEq equality matches since the index @@ -2801,11 +2797,11 @@ static void bestBtreeIndex( ** index and its cost in the pCost structure. */ if( (!pIdx || wsFlags) - && (costrCost || (cost<=pCost->rCost && nRownRow)) + && (costrCost || (cost<=pCost->rCost && nRowplan.nRow)) ){ pCost->rCost = cost; - pCost->nRow = nRow; pCost->used = used; + pCost->plan.nRow = nRow; pCost->plan.wsFlags = (wsFlags&wsFlagMask); pCost->plan.nEq = nEq; pCost->plan.u.pIdx = pIdx; @@ -2841,7 +2837,7 @@ static void bestBtreeIndex( pCost->plan.u.pIdx ? pCost->plan.u.pIdx->zName : "ipk") )); - bestOrClauseIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost); + bestOrClauseIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost); bestAutomaticIndex(pParse, pWC, pSrc, notReady, pCost); pCost->plan.wsFlags |= eqTermMask; } @@ -2856,14 +2852,15 @@ static void bestIndex( Parse *pParse, /* The parsing context */ WhereClause *pWC, /* The WHERE clause */ struct SrcList_item *pSrc, /* The FROM clause term to search */ - Bitmask notReady, /* Mask of cursors that are not available */ + Bitmask notReady, /* Mask of cursors not available for indexing */ + Bitmask notValid, /* Cursors not available for any purpose */ ExprList *pOrderBy, /* The ORDER BY clause */ WhereCost *pCost /* Lowest cost query plan */ ){ #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pSrc->pTab) ){ sqlite3_index_info *p = 0; - bestVirtualIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost, &p); + bestVirtualIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost,&p); if( p->needToFreeIdxStr ){ sqlite3_free(p->idxStr); } @@ -2871,7 +2868,7 @@ static void bestIndex( }else #endif { - bestBtreeIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost); + bestBtreeIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost); } } @@ -3133,6 +3130,161 @@ static int codeAllEqualityTerms( return regBase; } +#ifndef SQLITE_OMIT_EXPLAIN +/* +** This routine is a helper for explainIndexRange() below +** +** pStr holds the text of an expression that we are building up one term +** at a time. This routine adds a new term to the end of the expression. +** Terms are separated by AND so add the "AND" text for second and subsequent +** terms only. +*/ +static void explainAppendTerm( + StrAccum *pStr, /* The text expression being built */ + 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); + 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. +** +** 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, WhereLevel *pLevel, Table *pTab){ + WherePlan *pPlan = &pLevel->plan; + Index *pIndex = pPlan->u.pIdx; + int nEq = pPlan->nEq; + int i, j; + Column *aCol = pTab->aCol; + int *aiColumn = pIndex->aiColumn; + StrAccum txt; + + if( nEq==0 && (pPlan->wsFlags & (WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))==0 ){ + return 0; + } + sqlite3StrAccumInit(&txt, 0, 0, SQLITE_MAX_LENGTH); + txt.db = db; + sqlite3StrAccumAppend(&txt, " (", 2); + for(i=0; i"); + } + if( pPlan->wsFlags&WHERE_TOP_LIMIT ){ + explainAppendTerm(&txt, i, aCol[aiColumn[j]].zName, "<"); + } + sqlite3StrAccumAppend(&txt, ")", 1); + return sqlite3StrAccumFinish(&txt); +} + +/* +** 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. +*/ +static void 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 ){ + u32 flags = pLevel->plan.wsFlags; + 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 */ + sqlite3_int64 nRow; /* Expected number of rows visited by scan */ + int iId = pParse->iSelectId; /* Select id (left-most output column) */ + int isSearch; /* True for a SEARCH. False for SCAN. */ + + if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_ONETABLE_ONLY) ) return; + + isSearch = (pLevel->plan.nEq>0) + || (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0 + || (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX)); + + zMsg = sqlite3MPrintf(db, "%s", isSearch?"SEARCH":"SCAN"); + if( pItem->pSelect ){ + zMsg = sqlite3MAppendf(db, zMsg, "%s SUBQUERY %d", zMsg,pItem->iSelectId); + }else{ + zMsg = sqlite3MAppendf(db, zMsg, "%s TABLE %s", zMsg, pItem->zName); + } + + if( pItem->zAlias ){ + zMsg = sqlite3MAppendf(db, zMsg, "%s AS %s", zMsg, pItem->zAlias); + } + if( (flags & WHERE_INDEXED)!=0 ){ + char *zWhere = explainIndexRange(db, pLevel, pItem->pTab); + zMsg = sqlite3MAppendf(db, zMsg, "%s USING %s%sINDEX%s%s%s", zMsg, + ((flags & WHERE_TEMP_INDEX)?"AUTOMATIC ":""), + ((flags & WHERE_IDX_ONLY)?"COVERING ":""), + ((flags & WHERE_TEMP_INDEX)?"":" "), + ((flags & WHERE_TEMP_INDEX)?"": pLevel->plan.u.pIdx->zName), + zWhere + ); + sqlite3DbFree(db, zWhere); + }else if( flags & (WHERE_ROWID_EQ|WHERE_ROWID_RANGE) ){ + zMsg = sqlite3MAppendf(db, zMsg, "%s USING INTEGER PRIMARY KEY", zMsg); + + if( flags&WHERE_ROWID_EQ ){ + zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid=?)", zMsg); + }else if( (flags&WHERE_BOTH_LIMIT)==WHERE_BOTH_LIMIT ){ + zMsg = sqlite3MAppendf(db, zMsg, "%s (rowid>? AND rowid?)", zMsg); + }else if( flags&WHERE_TOP_LIMIT ){ + zMsg = sqlite3MAppendf(db, zMsg, "%s (rowidplan.u.pVtabIdx; + zMsg = sqlite3MAppendf(db, zMsg, "%s VIRTUAL TABLE INDEX %d:%s", zMsg, + pVtabIdx->idxNum, pVtabIdx->idxStr); + } +#endif + if( wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX) ){ + testcase( wctrlFlags & WHERE_ORDERBY_MIN ); + nRow = 1; + }else{ + nRow = (sqlite3_int64)pLevel->plan.nRow; + } + zMsg = sqlite3MAppendf(db, zMsg, "%s (~%lld rows)", zMsg, nRow); + sqlite3VdbeAddOp4(v, OP_Explain, iId, iLevel, iFrom, zMsg, P4_DYNAMIC); + } +} +#else +# define explainOneScan(u,v,w,x,y,z) +#endif /* SQLITE_OMIT_EXPLAIN */ + + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. @@ -3540,7 +3692,7 @@ static Bitmask codeOneLoopStart( r1 = sqlite3GetTempReg(pParse); testcase( pLevel->plan.wsFlags & WHERE_BTM_LIMIT ); testcase( pLevel->plan.wsFlags & WHERE_TOP_LIMIT ); - if( pLevel->plan.wsFlags & (WHERE_BTM_LIMIT|WHERE_TOP_LIMIT) ){ + if( (pLevel->plan.wsFlags & (WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0 ){ sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, nEq, r1); sqlite3VdbeAddOp2(v, OP_IsNull, r1, addrCont); } @@ -3674,6 +3826,9 @@ static Bitmask codeOneLoopStart( WHERE_OMIT_OPEN | WHERE_OMIT_CLOSE | WHERE_FORCE_TABLE | WHERE_ONETABLE_ONLY); if( pSubWInfo ){ + explainOneScan( + pParse, pOrTab, &pSubWInfo->a[0], iLevel, pLevel->iFrom, 0 + ); if( (wctrlFlags & WHERE_DUPLICATES_OK)==0 ){ int iSet = ((ii==pOrWc->nTerm-1)?-1:ii); int r; @@ -4069,6 +4224,7 @@ WhereInfo *sqlite3WhereBegin( memset(&bestPlan, 0, sizeof(bestPlan)); bestPlan.rCost = SQLITE_BIG_DBL; + WHERETRACE(("*** Begin search for loop %d ***\n", i)); /* Loop through the remaining entries in the FROM clause to find the ** next nested loop. The loop tests all FROM clause entries @@ -4087,9 +4243,15 @@ WhereInfo *sqlite3WhereBegin( ** other FROM clause terms that are notReady. If no notReady terms are ** used then the "optimal" query plan works. ** + ** Note that the WhereCost.nRow parameter for an optimal scan might + ** not be as small as it would be if the table really were the innermost + ** join. The nRow value can be reduced by WHERE clause constraints + ** that do not use indices. But this nRow reduction only happens if the + ** table really is the innermost join. + ** ** The second loop iteration is only performed if no optimal scan - ** strategies were found by the first loop. This 2nd iteration is used to - ** search for the lowest cost scan overall. + ** strategies were found by the first iteration. This second iteration + ** is used to search for the lowest cost scan overall. ** ** Previous versions of SQLite performed only the second iteration - ** the next outermost loop was always that with the lowest overall @@ -4102,14 +4264,14 @@ WhereInfo *sqlite3WhereBegin( ** ** The best strategy is to iterate through table t1 first. However it ** is not possible to determine this with a simple greedy algorithm. - ** However, since the cost of a linear scan through table t2 is the same + ** Since the cost of a linear scan through table t2 is the same ** as the cost of a linear scan through table t1, a simple greedy ** algorithm may choose to use t2 for the outer loop, which is a much ** costlier approach. */ nUnconstrained = 0; notIndexed = 0; - for(isOptimal=(iFrom=0; isOptimal--){ + for(isOptimal=(iFrom=0 && bestJ<0; isOptimal--){ Bitmask mask; /* Mask of tables not yet ready */ for(j=iFrom, pTabItem=&pTabList->a[j]; jpIndex==0 ) nUnconstrained++; + WHERETRACE(("=== trying table %d with isOptimal=%d ===\n", + j, isOptimal)); assert( pTabItem->pTab ); #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pTabItem->pTab) ){ sqlite3_index_info **pp = &pWInfo->a[j].pIdxInfo; - bestVirtualIndex(pParse, pWC, pTabItem, mask, pOrderBy, &sCost, pp); + bestVirtualIndex(pParse, pWC, pTabItem, mask, notReady, pOrderBy, + &sCost, pp); }else #endif { - bestBtreeIndex(pParse, pWC, pTabItem, mask, pOrderBy, &sCost); + bestBtreeIndex(pParse, pWC, pTabItem, mask, notReady, pOrderBy, + &sCost); } assert( isOptimal || (sCost.used¬Ready)==0 ); @@ -4175,10 +4341,12 @@ WhereInfo *sqlite3WhereBegin( && (nUnconstrained==0 || pTabItem->pIndex==0 /* (3) */ || NEVER((sCost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0)) && (bestJ<0 || sCost.rCost=0 ); assert( notReady & getMask(pMaskSet, pTabList->a[bestJ].iCursor) ); - WHERETRACE(("*** Optimizer selects table %d for loop %d\n", bestJ, - pLevel-pWInfo->a)); + WHERETRACE(("*** Optimizer selects table %d for loop %d" + " with cost=%g and nRow=%g\n", + bestJ, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow)); if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 ){ *ppOrderBy = 0; } @@ -4203,7 +4372,9 @@ WhereInfo *sqlite3WhereBegin( } notReady &= ~getMask(pMaskSet, pTabList->a[bestJ].iCursor); pLevel->iFrom = (u8)bestJ; - if( bestPlan.nRow>=(double)1 ) pParse->nQueryLoop *= bestPlan.nRow; + if( bestPlan.plan.nRow>=(double)1 ){ + pParse->nQueryLoop *= bestPlan.plan.nRow; + } /* Check that if the table scanned by this loop iteration had an ** INDEXED BY clause attached to it, that the named index is being @@ -4251,44 +4422,15 @@ WhereInfo *sqlite3WhereBegin( */ sqlite3CodeVerifySchema(pParse, -1); /* Insert the cookie verifier Goto */ notReady = ~(Bitmask)0; + pWInfo->nRowOut = (double)1; for(i=0, pLevel=pWInfo->a; iexplain==2 ){ - char *zMsg; - struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom]; - zMsg = sqlite3MPrintf(db, "TABLE %s", pItem->zName); - if( pItem->zAlias ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s AS %s", zMsg, pItem->zAlias); - } - if( (pLevel->plan.wsFlags & WHERE_TEMP_INDEX)!=0 ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s WITH AUTOMATIC INDEX", zMsg); - }else if( (pLevel->plan.wsFlags & WHERE_INDEXED)!=0 ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s WITH INDEX %s", - zMsg, pLevel->plan.u.pIdx->zName); - }else if( pLevel->plan.wsFlags & WHERE_MULTI_OR ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s VIA MULTI-INDEX UNION", zMsg); - }else if( pLevel->plan.wsFlags & (WHERE_ROWID_EQ|WHERE_ROWID_RANGE) ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s USING PRIMARY KEY", zMsg); - } -#ifndef SQLITE_OMIT_VIRTUALTABLE - else if( (pLevel->plan.wsFlags & WHERE_VIRTUALTABLE)!=0 ){ - sqlite3_index_info *pVtabIdx = pLevel->plan.u.pVtabIdx; - zMsg = sqlite3MAppendf(db, zMsg, "%s VIRTUAL TABLE INDEX %d:%s", zMsg, - pVtabIdx->idxNum, pVtabIdx->idxStr); - } -#endif - if( pLevel->plan.wsFlags & WHERE_ORDERBY ){ - zMsg = sqlite3MAppendf(db, zMsg, "%s ORDER BY", zMsg); - } - sqlite3VdbeAddOp4(v, OP_Explain, i, pLevel->iFrom, 0, zMsg, P4_DYNAMIC); - } -#endif /* SQLITE_OMIT_EXPLAIN */ pTabItem = &pTabList->a[pLevel->iFrom]; pTab = pTabItem->pTab; pLevel->iTabCur = pTabItem->iCursor; + pWInfo->nRowOut *= pLevel->plan.nRow; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); if( (pTab->tabFlags & TF_Ephemeral)!=0 || pTab->pSelect ){ /* Do nothing */ @@ -4344,8 +4486,10 @@ WhereInfo *sqlite3WhereBegin( */ notReady = ~(Bitmask)0; for(i=0; ia[i]; + explainOneScan(pParse, pTabList, pLevel, i, pLevel->iFrom, wctrlFlags); notReady = codeOneLoopStart(pWInfo, i, wctrlFlags, notReady); - pWInfo->iContinue = pWInfo->a[i].addrCont; + pWInfo->iContinue = pLevel->addrCont; } #ifdef SQLITE_TEST /* For testing and debugging use only */ diff --git a/test/all.test b/test/all.test index 8ccc0a64..96712466 100644 --- a/test/all.test +++ b/test/all.test @@ -16,6 +16,7 @@ source $testdir/permutations.test run_test_suite full +run_test_suite no_optimization run_test_suite memsubsys1 run_test_suite memsubsys2 run_test_suite singlethread diff --git a/test/alter2.test b/test/alter2.test index b5d700de..712960c0 100644 --- a/test/alter2.test +++ b/test/alter2.test @@ -27,11 +27,6 @@ ifcapable {!pragma} return # do_not_use_codec -# These tests do not work if there is a codec. -# -#if {[catch {sqlite3 -has_codec} r] || $r} return -# - # The file format change affects the way row-records stored in tables (but # not indices) are interpreted. Before version 3.1.3, a row-record for a # table with N columns was guaranteed to contain exactly N fields. As diff --git a/test/alter3.test b/test/alter3.test index 69efc50e..74ba33c5 100644 --- a/test/alter3.test +++ b/test/alter3.test @@ -28,7 +28,7 @@ ifcapable !altertable { # Determine if there is a codec available on this test. # -if {[catch {sqlite3 -has_codec} r] || $r} { +if {[catch {sqlite3 -has-codec} r] || $r} { set has_codec 1 } else { set has_codec 0 @@ -55,6 +55,7 @@ proc get_file_format {{fname test.db}} { do_test alter3-1.1 { execsql { + PRAGMA legacy_file_format=ON; CREATE TABLE abc(a, b, c); SELECT sql FROM sqlite_master; } @@ -198,6 +199,7 @@ do_test alter3-4.1 { file delete -force test.db set ::DB [sqlite3 db test.db] execsql { + PRAGMA legacy_file_format=ON; CREATE TABLE t1(a, b); INSERT INTO t1 VALUES(1, 100); INSERT INTO t1 VALUES(2, 300); diff --git a/test/alter4.test b/test/alter4.test index 992e0db6..738db3fd 100644 --- a/test/alter4.test +++ b/test/alter4.test @@ -26,14 +26,6 @@ ifcapable !altertable { return } -# Determine if there is a codec available on this test. -# -if {[catch {sqlite3 -has_codec} r] || $r} { - set has_codec 1 -} else { - set has_codec 0 -} - # Test Organisation: # ------------------ @@ -47,12 +39,6 @@ if {[catch {sqlite3 -has_codec} r] || $r} { # alter4-7.*: Test that VACUUM resets the file-format. # -# This procedure returns the value of the file-format in file 'test.db'. -# -proc get_file_format {{fname test.db}} { - return [hexio_get_int [hexio_read $fname 44 4]] -} - do_test alter4-1.1 { execsql { CREATE TEMP TABLE abc(a, b, c); @@ -182,11 +168,6 @@ do_test alter4-3.2 { SELECT * FROM t1; } } {1 100 {} 2 300 {}} -if {!$has_codec} { - do_test alter4-3.3 { - get_file_format - } {3} -} ifcapable schema_version { do_test alter4-3.4 { execsql { @@ -217,11 +198,6 @@ do_test alter4-4.2 { SELECT * FROM t1; } } {1 100 {hello world} 2 300 {hello world}} -if {!$has_codec} { - do_test alter4-4.3 { - get_file_format - } {3} -} ifcapable schema_version { do_test alter4-4.4 { execsql { @@ -267,11 +243,6 @@ ifcapable attach { } } {31} } - if {!$has_codec} { - do_test alter4-5.5 { - list [get_file_format test2.db] [get_file_format] - } {2 3} - } do_test alter4-5.6 { execsql { ALTER TABLE aux.t1 ADD COLUMN d DEFAULT 1000; @@ -333,42 +304,6 @@ ifcapable trigger&&tempdb { } {b 1 2 a 1 2 b 3 4 a 3 4} } -if {!$has_codec} { - ifcapable vacuum { - do_test alter4-7.1 { - execsql { - VACUUM; - } - get_file_format - } {1} - do_test alter4-7.2 { - execsql { - CREATE TEMP TABLE abc(a, b, c); - ALTER TABLE abc ADD d DEFAULT NULL; - } - get_file_format - } {2} - do_test alter4-7.3 { - execsql { - ALTER TABLE abc ADD e DEFAULT 10; - } - get_file_format - } {3} - do_test alter4-7.4 { - execsql { - ALTER TABLE abc ADD f DEFAULT NULL; - } - get_file_format - } {3} - do_test alter4-7.5 { - execsql { - VACUUM; - } - get_file_format - } {1} - } -} - # Ticket #1183 - Make sure adding columns to large tables does not cause # memory corruption (as was the case before this bug was fixed). do_test alter4-8.1 { diff --git a/test/analyze.test b/test/analyze.test index 5bd653a9..177936c2 100644 --- a/test/analyze.test +++ b/test/analyze.test @@ -73,7 +73,7 @@ do_test analyze-1.6.3 { } {1 {table sqlite_stat1 may not be indexed}} do_test analyze-1.7 { execsql { - SELECT * FROM sqlite_stat1 + SELECT * FROM sqlite_stat1 WHERE idx NOT NULL } } {} do_test analyze-1.8 { @@ -83,7 +83,7 @@ do_test analyze-1.8 { } {0 {}} do_test analyze-1.9 { execsql { - SELECT * FROM sqlite_stat1 + SELECT * FROM sqlite_stat1 WHERE idx NOT NULL } } {} do_test analyze-1.10 { @@ -96,7 +96,7 @@ do_test analyze-1.11 { execsql { SELECT * FROM sqlite_stat1 } -} {} +} {t1 {} 0} do_test analyze-1.12 { catchsql { ANALYZE t1; @@ -106,7 +106,7 @@ do_test analyze-1.13 { execsql { SELECT * FROM sqlite_stat1 } -} {} +} {t1 {} 0} # Create some indices that can be analyzed. But do not yet add # data. Without data in the tables, no analysis is done. @@ -117,21 +117,21 @@ do_test analyze-2.1 { ANALYZE main.t1; SELECT * FROM sqlite_stat1 ORDER BY idx; } -} {} +} {t1 {} 0} do_test analyze-2.2 { execsql { CREATE INDEX t1i2 ON t1(b); ANALYZE t1; SELECT * FROM sqlite_stat1 ORDER BY idx; } -} {} +} {t1 {} 0} do_test analyze-2.3 { execsql { CREATE INDEX t1i3 ON t1(a,b); ANALYZE main; SELECT * FROM sqlite_stat1 ORDER BY idx; } -} {} +} {t1 {} 0} # Start adding data to the table. Verify that the analysis # is done correctly. diff --git a/test/analyze2.test b/test/analyze2.test index 7a606bb7..039fb378 100644 --- a/test/analyze2.test +++ b/test/analyze2.test @@ -22,6 +22,8 @@ ifcapable !stat2 { return } +set testprefix analyze2 + # Do not use a codec for tests in this file, as the database file is # manipulated directly using tcl scripts (using the [hexio_write] command). # @@ -119,36 +121,56 @@ do_test analyze2-2.1 { execsql COMMIT execsql ANALYZE } {} -do_test analyze2-2.2 { - eqp "SELECT * FROM t1 WHERE x>500 AND y>700" -} {0 0 {TABLE t1 WITH INDEX t1_y}} -do_test analyze2-2.3 { - eqp "SELECT * FROM t1 WHERE x>700 AND y>500" -} {0 0 {TABLE t1 WITH INDEX t1_x}} -do_test analyze2-2.3 { - eqp "SELECT * FROM t1 WHERE y>700 AND x>500" -} {0 0 {TABLE t1 WITH INDEX t1_y}} -do_test analyze2-2.4 { - eqp "SELECT * FROM t1 WHERE y>500 AND x>700" -} {0 0 {TABLE t1 WITH INDEX t1_x}} -do_test analyze2-2.5 { - eqp "SELECT * FROM t1 WHERE x BETWEEN 100 AND 200 AND y BETWEEN 400 AND 700" -} {0 0 {TABLE t1 WITH INDEX t1_x}} -do_test analyze2-2.6 { - eqp "SELECT * FROM t1 WHERE x BETWEEN 100 AND 500 AND y BETWEEN 400 AND 700" -} {0 0 {TABLE t1 WITH INDEX t1_y}} -do_test analyze2-2.7 { - eqp "SELECT * FROM t1 WHERE x BETWEEN -400 AND -300 AND y BETWEEN 100 AND 300" -} {0 0 {TABLE t1 WITH INDEX t1_x}} -do_test analyze2-2.8 { - eqp "SELECT * FROM t1 WHERE x BETWEEN 100 AND 300 AND y BETWEEN -400 AND -300" -} {0 0 {TABLE t1 WITH INDEX t1_y}} -do_test analyze2-2.9 { - eqp "SELECT * FROM t1 WHERE x BETWEEN 500 AND 100 AND y BETWEEN 100 AND 300" -} {0 0 {TABLE t1 WITH INDEX t1_x}} -do_test analyze2-2.10 { - eqp "SELECT * FROM t1 WHERE x BETWEEN 100 AND 300 AND y BETWEEN 500 AND 100" -} {0 0 {TABLE t1 WITH INDEX t1_y}} +do_eqp_test 2.2 { + SELECT * FROM t1 WHERE x>500 AND y>700 +} { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1_y (y>?) (~100 rows)} +} +do_eqp_test 2.3 { + SELECT * FROM t1 WHERE x>700 AND y>500 +} { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1_x (x>?) (~100 rows)} +} +do_eqp_test 2.3 { + SELECT * FROM t1 WHERE y>700 AND x>500 +} { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1_y (y>?) (~100 rows)} +} +do_eqp_test 2.4 { + SELECT * FROM t1 WHERE y>500 AND x>700 +} { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1_x (x>?) (~100 rows)} +} +do_eqp_test 2.5 { + SELECT * FROM t1 WHERE x BETWEEN 100 AND 200 AND y BETWEEN 400 AND 700 +} { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1_x (x>? AND x? AND y? AND x? AND y? AND x? AND y'h'" -} {0 0 {TABLE t1 WITH INDEX t1_y}} -do_test analyze2-3.6 { - eqp "SELECT * FROM t1 WHERE x<444 AND y>'h'" -} {0 0 {TABLE t1 WITH INDEX t1_y}} -do_test analyze2-3.7 { - eqp "SELECT * FROM t1 WHERE x<221 AND y>'g'" -} {0 0 {TABLE t1 WITH INDEX t1_x}} +do_eqp_test 3.3 { + SELECT * FROM t1 WHERE x BETWEEN 100 AND 500 AND y BETWEEN 'a' AND 'b' +} { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1_y (y>? AND y? AND x'h' +} { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1_y (y>?) (~66 rows)} +} +do_eqp_test 3.6 { + SELECT * FROM t1 WHERE x<444 AND y>'h' +} { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1_y (y>?) (~66 rows)} +} +do_eqp_test 3.7 { + SELECT * FROM t1 WHERE x<221 AND y>'g' +} { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1_x (x 'A' AND a < 'C' AND b > 'A' AND b < 'C'" -} {0 0 {TABLE t3 WITH INDEX t3b}} -do_test analyze2-4.5 { - eqp "SELECT * FROM t3 WHERE a > 'A' AND a < 'c' AND b > 'A' AND b < 'c'" -} {0 0 {TABLE t3 WITH INDEX t3a}} +do_eqp_test 4.4 { + SELECT * FROM t3 WHERE a > 'A' AND a < 'C' AND b > 'A' AND b < 'C' +} { + 0 0 0 {SEARCH TABLE t3 USING INDEX t3b (b>? AND b 'A' AND a < 'c' AND b > 'A' AND b < 'c' +} { + 0 0 0 {SEARCH TABLE t3 USING INDEX t3a (a>? AND a'ccc'" - } {0 0 {TABLE t4 WITH INDEX t4x}} - do_test analyze2-5.4 { - eqp "SELECT * FROM t4 AS t41, t4 AS t42 WHERE t41.x>'ccc' AND t42.x>'ggg'" - } {0 1 {TABLE t4 AS t42 WITH INDEX t4x} 1 0 {TABLE t4 AS t41 WITH INDEX t4x}} - do_test analyze2-5.5 { - eqp "SELECT * FROM t4 AS t41, t4 AS t42 WHERE t41.x>'ddd' AND t42.x>'ccc'" - } {0 0 {TABLE t4 AS t41 WITH INDEX t4x} 1 1 {TABLE t4 AS t42 WITH INDEX t4x}} + do_eqp_test 5.3 { + SELECT * FROM t4 WHERE x>'ccc' + } {0 0 0 {SEARCH TABLE t4 USING COVERING INDEX t4x (x>?) (~800 rows)}} + do_eqp_test 5.4 { + SELECT * FROM t4 AS t41, t4 AS t42 WHERE t41.x>'ccc' AND t42.x>'ggg' + } { + 0 0 1 {SEARCH TABLE t4 AS t42 USING COVERING INDEX t4x (x>?) (~300 rows)} + 0 1 0 {SEARCH TABLE t4 AS t41 USING COVERING INDEX t4x (x>?) (~800 rows)} + } + do_eqp_test 5.5 { + SELECT * FROM t4 AS t41, t4 AS t42 WHERE t41.x>'ddd' AND t42.x>'ccc' + } { + 0 0 0 {SEARCH TABLE t4 AS t41 USING COVERING INDEX t4x (x>?) (~700 rows)} + 0 1 1 {SEARCH TABLE t4 AS t42 USING COVERING INDEX t4x (x>?) (~800 rows)} + } } #-------------------------------------------------------------------- @@ -306,7 +348,7 @@ do_test analyze2-6.1.1 { t5.a = 1 AND t6.a = 1 AND t6.b = 1 } -} {0 1 {TABLE t6 WITH INDEX t6i} 1 0 {TABLE t5 USING PRIMARY KEY}} +} {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a=? AND b=?) (~9 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-6.1.2 { db cache flush execsql ANALYZE @@ -314,14 +356,14 @@ do_test analyze2-6.1.2 { t5.a = 1 AND t6.a = 1 AND t6.b = 1 } -} {0 0 {TABLE t5 WITH INDEX t5i} 1 1 {TABLE t6 USING PRIMARY KEY}} +} {0 0 0 {SEARCH TABLE t5 USING COVERING INDEX t5i (a=?) (~1 rows)} 0 1 1 {SEARCH TABLE t6 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-6.1.3 { sqlite3 db test.db eqp { SELECT * FROM t5,t6 WHERE t5.rowid=t6.rowid AND t5.a = 1 AND t6.a = 1 AND t6.b = 1 } -} {0 0 {TABLE t5 WITH INDEX t5i} 1 1 {TABLE t6 USING PRIMARY KEY}} +} {0 0 0 {SEARCH TABLE t5 USING COVERING INDEX t5i (a=?) (~1 rows)} 0 1 1 {SEARCH TABLE t6 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-6.1.4 { execsql { PRAGMA writable_schema = 1; @@ -332,7 +374,7 @@ do_test analyze2-6.1.4 { t5.a = 1 AND t6.a = 1 AND t6.b = 1 } -} {0 0 {TABLE t5 WITH INDEX t5i} 1 1 {TABLE t6 USING PRIMARY KEY}} +} {0 0 0 {SEARCH TABLE t5 USING COVERING INDEX t5i (a=?) (~1 rows)} 0 1 1 {SEARCH TABLE t6 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-6.1.5 { execsql { PRAGMA writable_schema = 1; @@ -343,7 +385,7 @@ do_test analyze2-6.1.5 { t5.a = 1 AND t6.a = 1 AND t6.b = 1 } -} {0 1 {TABLE t6 WITH INDEX t6i} 1 0 {TABLE t5 USING PRIMARY KEY}} +} {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a=? AND b=?) (~9 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-6.1.6 { execsql { PRAGMA writable_schema = 1; @@ -354,7 +396,7 @@ do_test analyze2-6.1.6 { t5.a = 1 AND t6.a = 1 AND t6.b = 1 } -} {0 0 {TABLE t5 WITH INDEX t5i} 1 1 {TABLE t6 USING PRIMARY KEY}} +} {0 0 0 {SEARCH TABLE t5 USING COVERING INDEX t5i (a=?) (~1 rows)} 0 1 1 {SEARCH TABLE t6 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-6.2.1 { execsql { @@ -366,7 +408,7 @@ do_test analyze2-6.2.1 { t5.a>1 AND t5.a<15 AND t6.a>1 } -} {0 0 {TABLE t5 WITH INDEX t5i} 1 1 {TABLE t6 USING PRIMARY KEY}} +} {0 0 0 {SEARCH TABLE t5 USING COVERING INDEX t5i (a>? AND a1 AND t5.a<15 AND t6.a>1 } -} {0 1 {TABLE t6 WITH INDEX t6i} 1 0 {TABLE t5 USING PRIMARY KEY}} +} {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-6.2.3 { sqlite3 db test.db eqp { SELECT * FROM t5,t6 WHERE t5.rowid=t6.rowid AND t5.a>1 AND t5.a<15 AND t6.a>1 } -} {0 1 {TABLE t6 WITH INDEX t6i} 1 0 {TABLE t5 USING PRIMARY KEY}} +} {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-6.2.4 { execsql { PRAGMA writable_schema = 1; @@ -392,7 +434,7 @@ do_test analyze2-6.2.4 { t5.a>1 AND t5.a<15 AND t6.a>1 } -} {0 0 {TABLE t5 WITH INDEX t5i} 1 1 {TABLE t6 USING PRIMARY KEY}} +} {0 0 0 {SEARCH TABLE t5 USING COVERING INDEX t5i (a>? AND a1 AND t5.a<15 AND t6.a>1 } -} {0 0 {TABLE t5 WITH INDEX t5i} 1 1 {TABLE t6 USING PRIMARY KEY}} +} {0 0 0 {SEARCH TABLE t5 USING COVERING INDEX t5i (a>? AND a1 AND t5.a<15 AND t6.a>1 } -} {0 1 {TABLE t6 WITH INDEX t6i} 1 0 {TABLE t5 USING PRIMARY KEY}} +} {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} #-------------------------------------------------------------------- # These tests, analyze2-7.*, test that the sqlite_stat2 functionality @@ -459,7 +501,7 @@ ifcapable shared_cache { t5.a>1 AND t5.a<15 AND t6.a>1 } db1 - } {0 1 {TABLE t6 WITH INDEX t6i} 1 0 {TABLE t5 USING PRIMARY KEY}} + } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-7.6 { incr_schema_cookie test.db execsql { SELECT * FROM sqlite_master } db2 @@ -467,7 +509,7 @@ ifcapable shared_cache { t5.a>1 AND t5.a<15 AND t6.a>1 } db2 - } {0 1 {TABLE t6 WITH INDEX t6i} 1 0 {TABLE t5 USING PRIMARY KEY}} + } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-7.7 { incr_schema_cookie test.db execsql { SELECT * FROM sqlite_master } db1 @@ -475,7 +517,7 @@ ifcapable shared_cache { t5.a>1 AND t5.a<15 AND t6.a>1 } db1 - } {0 1 {TABLE t6 WITH INDEX t6i} 1 0 {TABLE t5 USING PRIMARY KEY}} + } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-7.8 { execsql { DELETE FROM sqlite_stat2 } db2 @@ -484,14 +526,14 @@ ifcapable shared_cache { t5.a>1 AND t5.a<15 AND t6.a>1 } db1 - } {0 1 {TABLE t6 WITH INDEX t6i} 1 0 {TABLE t5 USING PRIMARY KEY}} + } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-7.9 { execsql { SELECT * FROM sqlite_master } db2 eqp { SELECT * FROM t5,t6 WHERE t5.rowid=t6.rowid AND t5.a>1 AND t5.a<15 AND t6.a>1 } db2 - } {0 1 {TABLE t6 WITH INDEX t6i} 1 0 {TABLE t5 USING PRIMARY KEY}} + } {0 0 1 {SEARCH TABLE t6 USING COVERING INDEX t6i (a>?) (~2 rows)} 0 1 0 {SEARCH TABLE t5 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)}} do_test analyze2-7.10 { incr_schema_cookie test.db @@ -500,7 +542,7 @@ ifcapable shared_cache { t5.a>1 AND t5.a<15 AND t6.a>1 } db1 - } {0 0 {TABLE t5 WITH INDEX t5i} 1 1 {TABLE t6 USING PRIMARY KEY}} + } {0 0 0 {SEARCH TABLE t5 USING COVERING INDEX t5i (a>? AND a200 AND x<300 } -} {0 0 {TABLE t1 WITH INDEX i1}} -do_test analyze3-1.1.3 { - eqp { SELECT sum(y) FROM t1 WHERE x>0 AND x<1100 } -} {0 0 {TABLE t1}} +do_eqp_test analyze3-1.1.2 { + SELECT sum(y) FROM t1 WHERE x>200 AND x<300 +} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (x>? AND x0 AND x<1100 +} {0 0 0 {SCAN TABLE t1 (~111 rows)}} do_test analyze3-1.1.4 { sf_execsql { SELECT sum(y) FROM t1 WHERE x>200 AND x<300 } @@ -144,12 +144,12 @@ do_test analyze3-1.2.1 { ANALYZE; } } {} -do_test analyze3-1.2.2 { - eqp { SELECT sum(y) FROM t2 WHERE x>1 AND x<2 } -} {0 0 {TABLE t2 WITH INDEX i2}} -do_test analyze3-1.2.3 { - eqp { SELECT sum(y) FROM t2 WHERE x>0 AND x<99 } -} {0 0 {TABLE t2}} +do_eqp_test analyze3-1.2.2 { + SELECT sum(y) FROM t2 WHERE x>1 AND x<2 +} {0 0 0 {SEARCH TABLE t2 USING INDEX i2 (x>? AND x0 AND x<99 +} {0 0 0 {SCAN TABLE t2 (~111 rows)}} do_test analyze3-1.2.4 { sf_execsql { SELECT sum(y) FROM t2 WHERE x>12 AND x<20 } } {161 0 4760} @@ -191,12 +191,12 @@ do_test analyze3-1.3.1 { ANALYZE; } } {} -do_test analyze3-1.3.2 { - eqp { SELECT sum(y) FROM t3 WHERE x>200 AND x<300 } -} {0 0 {TABLE t3 WITH INDEX i3}} -do_test analyze3-1.3.3 { - eqp { SELECT sum(y) FROM t3 WHERE x>0 AND x<1100 } -} {0 0 {TABLE t3}} +do_eqp_test analyze3-1.3.2 { + SELECT sum(y) FROM t3 WHERE x>200 AND x<300 +} {0 0 0 {SEARCH TABLE t3 USING INDEX i3 (x>? AND x0 AND x<1100 +} {0 0 0 {SCAN TABLE t3 (~111 rows)}} do_test analyze3-1.3.4 { sf_execsql { SELECT sum(y) FROM t3 WHERE x>200 AND x<300 } @@ -246,12 +246,12 @@ do_test analyze3-2.1 { } execsql COMMIT } {} -do_test analyze3-2.2 { - eqp { SELECT count(a) FROM t1 WHERE b LIKE 'a%' } -} {0 0 {TABLE t1 WITH INDEX i1}} -do_test analyze3-2.3 { - eqp { SELECT count(a) FROM t1 WHERE b LIKE '%a' } -} {0 0 {TABLE t1}} +do_eqp_test analyze3-2.2 { + SELECT count(a) FROM t1 WHERE b LIKE 'a%' +} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (b>? AND b prev.owner_change_date + AND later.owner_change_date <= s.date_of_registration||' 00:00:00') + ) y ON x.sheep_no = y.sheep_no + WHERE y.sheep_no IS NULL + ORDER BY x.registering_flock; +} { + 1 0 0 {SCAN TABLE sheep AS s (~1000000 rows)} + 1 1 1 {SEARCH TABLE flock_owner AS prev USING INDEX sqlite_autoindex_flock_owner_1 (flock_no=? AND owner_change_date? AND owner_change_date=2 }] if { $isMemDest==0 || $pgsz_dest == 1024 } { diff --git a/test/cache.test b/test/cache.test index 3f42a47d..8d801f05 100644 --- a/test/cache.test +++ b/test/cache.test @@ -51,13 +51,90 @@ do_test cache-1.2 { # set cache_size [pager_cache_size db] for {set ii 0} {$ii < 10} {incr ii} { - do_test cache-1.3.$ii { execsql {SELECT * FROM abc} pager_cache_size db } $::cache_size - } -sqlite3_soft_heap_limit $cmdlinearg(soft-heap-limit) +#------------------------------------------------------------------------- +# This block of tests checks that it is possible to set the cache_size of a +# database to a small (< 10) value. More specifically: +# +# cache-2.1.*: Test that "PRAGMA cache_size" appears to work with small +# values. +# cache-2.2.*: Test that "PRAGMA main.cache_size" appears to work with +# small values. +# cache-2.3.*: Test cache_size=1 correctly spills/flushes the cache. +# cache-2.4.*: Test cache_size=0 correctly spills/flushes the cache. +# +# +db_delete_and_reopen +do_execsql_test cache-2.0 { + PRAGMA auto_vacuum=OFF; + PRAGMA journal_mode=DELETE; + CREATE TABLE t1(a, b); + CREATE TABLE t2(c, d); + INSERT INTO t1 VALUES('x', 'y'); + INSERT INTO t2 VALUES('i', 'j'); +} {delete} + +for {set i 0} {$i < 20} {incr i} { + do_execsql_test cache-2.1.$i.1 "PRAGMA cache_size = $i" + do_execsql_test cache-2.1.$i.2 "PRAGMA cache_size" $i + do_execsql_test cache-2.1.$i.3 "SELECT * FROM t1" {x y} + do_execsql_test cache-2.1.$i.4 "PRAGMA cache_size" $i +} +for {set i 0} {$i < 20} {incr i} { + do_execsql_test cache-2.2.$i.1 "PRAGMA main.cache_size = $i" + do_execsql_test cache-2.2.$i.2 "PRAGMA main.cache_size" $i + do_execsql_test cache-2.2.$i.3 "SELECT * FROM t1" {x y} + do_execsql_test cache-2.2.$i.4 "PRAGMA main.cache_size" $i +} + +# Tests for cache_size = 1. +# +do_execsql_test cache-2.3.1 { + PRAGMA cache_size = 1; + BEGIN; + INSERT INTO t1 VALUES(1, 2); + PRAGMA lock_status; +} {main reserved temp closed} +do_test cache-2.3.2 { pager_cache_size db } 2 +do_execsql_test cache-2.3.3 { + INSERT INTO t2 VALUES(1, 2); + PRAGMA lock_status; +} {main exclusive temp closed} +do_test cache-2.3.4 { pager_cache_size db } 2 +do_execsql_test cache-2.3.5 COMMIT +do_test cache-2.3.6 { pager_cache_size db } 1 + +do_execsql_test cache-2.3.7 { + SELECT * FROM t1 UNION SELECT * FROM t2; +} {1 2 i j x y} +do_test cache-2.3.8 { pager_cache_size db } 1 + +# Tests for cache_size = 0. +# +do_execsql_test cache-2.4.1 { + PRAGMA cache_size = 0; + BEGIN; + INSERT INTO t1 VALUES(1, 2); + PRAGMA lock_status; +} {main reserved temp closed} +do_test cache-2.4.2 { pager_cache_size db } 2 +do_execsql_test cache-2.4.3 { + INSERT INTO t2 VALUES(1, 2); + PRAGMA lock_status; +} {main exclusive temp closed} +do_test cache-2.4.4 { pager_cache_size db } 2 +do_execsql_test cache-2.4.5 COMMIT + +do_test cache-2.4.6 { pager_cache_size db } 0 +do_execsql_test cache-2.4.7 { + SELECT * FROM t1 UNION SELECT * FROM t2; +} {1 2 i j x y} +do_test cache-2.4.8 { pager_cache_size db } 0 + +sqlite3_soft_heap_limit $cmdlinearg(soft-heap-limit) finish_test diff --git a/test/capi2.test b/test/capi2.test index b4aa970c..8b36ac69 100644 --- a/test/capi2.test +++ b/test/capi2.test @@ -74,9 +74,15 @@ do_test capi2-1.7 { # This used to be SQLITE_MISUSE. But now we automatically reset prepared # statements. -do_test capi2-1.8 { - sqlite3_step $VM -} {SQLITE_ROW} +ifcapable autoreset { + do_test capi2-1.8 { + sqlite3_step $VM + } {SQLITE_ROW} +} else { + do_test capi2-1.8 { + sqlite3_step $VM + } {SQLITE_MISUSE} +} # Update: In v2, once SQLITE_MISUSE is returned the statement handle cannot # be interrogated for more information. However in v3, since the column diff --git a/test/capi3d.test b/test/capi3d.test index e346029f..49e64476 100644 --- a/test/capi3d.test +++ b/test/capi3d.test @@ -10,7 +10,8 @@ #*********************************************************************** # This file implements regression tests for SQLite library. # -# This file is devoted to testing the sqlite3_next_stmt interface. +# This file is devoted to testing the sqlite3_next_stmt and +# sqlite3_stmt_readonly interfaces. # # $Id: capi3d.test,v 1.2 2008/07/14 15:11:20 drh Exp $ # @@ -88,6 +89,28 @@ for {set i 1} {$i<=100} {incr i} { sqlite3_next_stmt db 0 } {} } - + +# Tests for the is-read-only interface. +# +proc test_is_readonly {testname sql truth} { + do_test $testname [format { + set DB [sqlite3_connection_pointer db] + set STMT [sqlite3_prepare $DB {%s} -1 TAIL] + set rc [sqlite3_stmt_readonly $STMT] + sqlite3_finalize $STMT + set rc + } $sql] $truth +} + +test_is_readonly capi3d-2.1 {SELECT * FROM sqlite_master} 1 +test_is_readonly capi3d-2.2 {CREATE TABLE t1(x)} 0 +db eval {CREATE TABLE t1(x)} +test_is_readonly capi3d-2.3 {INSERT INTO t1 VALUES(5)} 0 +test_is_readonly capi3d-2.4 {UPDATE t1 SET x=x+1 WHERE x<0} 0 +test_is_readonly capi3d-2.5 {SELECT * FROM t1} 1 +do_test capi3-2.99 { + sqlite3_stmt_readonly 0 +} 1 + finish_test diff --git a/test/capi3e.test b/test/capi3e.test new file mode 100644 index 00000000..388c17be --- /dev/null +++ b/test/capi3e.test @@ -0,0 +1,120 @@ +# 2010 Novemeber 18 +# +# 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 implements regression tests for SQLite library. The +# focus of this script testing the callback-free C/C++ API. +# +# $Id: capi3e.test,v 1.70 2009/01/09 02:49:32 drh Exp $ +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Make sure the system encoding is utf-8. Otherwise, if the system encoding +# is other than utf-8, [file isfile $x] may not refer to the same file +# as [sqlite3 db $x]. +encoding system utf-8 + +# Do not use a codec for tests in this file, as the database file is +# manipulated directly using tcl scripts (using the [hexio_write] command). +# +do_not_use_codec + +# Return the UTF-16 representation of the supplied UTF-8 string $str. +# If $nt is true, append two 0x00 bytes as a nul terminator. +proc utf16 {str {nt 1}} { + set r [encoding convertto unicode $str] + if {$nt} { + append r "\x00\x00" + } + return $r +} + +# Return the UTF-8 representation of the supplied UTF-16 string $str. +proc utf8 {str} { + # If $str ends in two 0x00 0x00 bytes, knock these off before + # converting to UTF-8 using TCL. + binary scan $str \c* vals + if {[lindex $vals end]==0 && [lindex $vals end-1]==0} { + set str [binary format \c* [lrange $vals 0 end-2]] + } + + set r [encoding convertfrom unicode $str] + return $r +} + +# These tests complement those in capi2.test. They are organized +# as follows: +# +# capi3e-1.*: Test sqlite3_open with various UTF8 filenames +# capi3e-2.*: Test sqlite3_open16 with various UTF8 filenames +# capi3e-3.*: Test ATTACH with various UTF8 filenames + +db close + +# here's the list of file names we're testing +set names {t 1 t. 1. t.d 1.d t-1 1-1 t.db ä.db ë.db ö.db ü.db ÿ.db} + +set i 0 +foreach name $names { + incr i + do_test capi3e-1.1.$i { + set db2 [sqlite3_open $name {}] + sqlite3_errcode $db2 + } {SQLITE_OK} + do_test capi3e-1.2.$i { + sqlite3_close $db2 + } {SQLITE_OK} + do_test capi3e-1.3.$i { + file isfile $name + } {1} +} + +set i 0 +foreach name $names { + incr i + do_test capi3e-2.1.$i { + set db2 [sqlite3_open16 [utf16 $name] {}] + sqlite3_errcode $db2 + } {SQLITE_OK} + do_test capi3e-2.2.$i { + sqlite3_close $db2 + } {SQLITE_OK} + do_test capi3e-2.3.$i { + file isfile $name + } {1} +} + +ifcapable attach { + do_test capi3e-3.1 { + sqlite3 db2 base.db + } {} + set i 0 + foreach name $names { + incr i + do_test capi3e-3.2.$i { + db2 eval "ATTACH DATABASE '$name' AS db$i;" + } {} + do_test capi3e-3.3.$i { + db2 eval "DETACH DATABASE db$i;" + } {} + } + do_test capi3e-3.4 { + db2 close + } {} +} + +# clean up +forcedelete base.db +foreach name $names { + forcedelete $name +} + +finish_test diff --git a/test/conflict.test b/test/conflict.test index c612e3b3..17c7263f 100644 --- a/test/conflict.test +++ b/test/conflict.test @@ -307,7 +307,7 @@ foreach {i conf1 cmd t0 t1 t2 t3 t4} { 16 {} {UPDATE OR ROLLBACK} 1 {1 2 3 4} 0 0 0 } { if {$t0} {set t1 {column a is not unique}} - if {[info exists TEMP_STORE] && $TEMP_STORE>=2} { + if {[info exists TEMP_STORE] && $TEMP_STORE==3} { set t3 0 } else { set t3 [expr {$t3+$t4}] diff --git a/test/date.test b/test/date.test index 29b5ed4d..9bfec126 100644 --- a/test/date.test +++ b/test/date.test @@ -84,7 +84,7 @@ for {set i 0} {$i<1000} {incr i} { datetest 2.3 {date('2003-10-22','weekday 0')} 2003-10-26 datetest 2.4 {date('2003-10-22','weekday 1')} 2003-10-27 datetest 2.4a {date('2003-10-22','weekday 1')} 2003-10-27 -datetest 2.4b {date('2003-10-22','weekday 1x')} 2003-10-27 +datetest 2.4b {date('2003-10-22','weekday 1x')} NULL datetest 2.4c {date('2003-10-22','weekday -1')} NULL datetest 2.4d {date('2003-10-22','weakday 1x')} NULL datetest 2.4e {date('2003-10-22','weekday ')} NULL diff --git a/test/dbstatus.test b/test/dbstatus.test index 5cb87015..202f34a0 100644 --- a/test/dbstatus.test +++ b/test/dbstatus.test @@ -15,6 +15,13 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +# Memory statistics must be enabled for this test. +db close +sqlite3_shutdown +sqlite3_config_memstatus 1 +sqlite3_initialize +sqlite3 db test.db + # Make sure sqlite3_db_config() and sqlite3_db_status are working. # diff --git a/test/e_createtable.test b/test/e_createtable.test new file mode 100644 index 00000000..66964843 --- /dev/null +++ b/test/e_createtable.test @@ -0,0 +1,1930 @@ +# 2010 September 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 implements tests to verify that the "testable statements" in +# the lang_createtable.html document are correct. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +set ::testprefix e_createtable + +# Test organization: +# +# e_createtable-0.*: Test that the syntax diagrams are correct. +# +# e_createtable-1.*: Test statements related to table and database names, +# the TEMP and TEMPORARY keywords, and the IF NOT EXISTS clause. +# +# e_createtable-2.*: Test "CREATE TABLE AS" statements. +# + +proc do_createtable_tests {nm args} { + uplevel do_select_tests [list e_createtable-$nm] $args +} + + +#------------------------------------------------------------------------- +# This command returns a serialized tcl array mapping from the name of +# each attached database to a list of tables in that database. For example, +# if the database schema is created with: +# +# CREATE TABLE t1(x); +# CREATE TEMP TABLE t2(x); +# CREATE TEMP TABLE t3(x); +# +# Then this command returns "main t1 temp {t2 t3}". +# +proc table_list {} { + set res [list] + db eval { pragma database_list } a { + set dbname $a(name) + set master $a(name).sqlite_master + if {$dbname == "temp"} { set master sqlite_temp_master } + lappend res $dbname [ + db eval "SELECT DISTINCT tbl_name FROM $master ORDER BY tbl_name" + ] + } + set res +} + + +# EVIDENCE-OF: R-25262-01881 -- syntax diagram type-name +# +do_createtable_tests 0.1.1 -repair { + drop_all_tables +} { + 1 "CREATE TABLE t1(c1 one)" {} + 2 "CREATE TABLE t1(c1 one two)" {} + 3 "CREATE TABLE t1(c1 one two three)" {} + 4 "CREATE TABLE t1(c1 one two three four)" {} + 5 "CREATE TABLE t1(c1 one two three four(14))" {} + 6 "CREATE TABLE t1(c1 one two three four(14, 22))" {} + 7 "CREATE TABLE t1(c1 var(+14, -22.3))" {} + 8 "CREATE TABLE t1(c1 var(1.0e10))" {} +} +do_createtable_tests 0.1.2 -error { + near "%s": syntax error +} { + 1 "CREATE TABLE t1(c1 one(number))" {number} +} + + +# EVIDENCE-OF: R-18762-12428 -- syntax diagram column-constraint +# +# Note: Not shown in the syntax diagram is the "NULL" constraint. This +# is the opposite of "NOT NULL" - it implies that the column may +# take a NULL value. This is the default anyway, so this type of +# constraint is rarely used. +# +do_createtable_tests 0.2.1 -repair { + drop_all_tables + execsql { CREATE TABLE t2(x PRIMARY KEY) } +} { + 1.1 "CREATE TABLE t1(c1 text PRIMARY KEY)" {} + 1.2 "CREATE TABLE t1(c1 text PRIMARY KEY ASC)" {} + 1.3 "CREATE TABLE t1(c1 text PRIMARY KEY DESC)" {} + 1.4 "CREATE TABLE t1(c1 text CONSTRAINT cons PRIMARY KEY DESC)" {} + + 2.1 "CREATE TABLE t1(c1 text NOT NULL)" {} + 2.2 "CREATE TABLE t1(c1 text CONSTRAINT nm NOT NULL)" {} + 2.3 "CREATE TABLE t1(c1 text NULL)" {} + 2.4 "CREATE TABLE t1(c1 text CONSTRAINT nm NULL)" {} + + 3.1 "CREATE TABLE t1(c1 text UNIQUE)" {} + 3.2 "CREATE TABLE t1(c1 text CONSTRAINT un UNIQUE)" {} + + 4.1 "CREATE TABLE t1(c1 text CHECK(c1!=0))" {} + 4.2 "CREATE TABLE t1(c1 text CONSTRAINT chk CHECK(c1!=0))" {} + + 5.1 "CREATE TABLE t1(c1 text DEFAULT 1)" {} + 5.2 "CREATE TABLE t1(c1 text DEFAULT -1)" {} + 5.3 "CREATE TABLE t1(c1 text DEFAULT +1)" {} + 5.4 "CREATE TABLE t1(c1 text DEFAULT -45.8e22)" {} + 5.5 "CREATE TABLE t1(c1 text DEFAULT (1+1))" {} + 5.6 "CREATE TABLE t1(c1 text CONSTRAINT \"1 2\" DEFAULT (1+1))" {} + + 6.1 "CREATE TABLE t1(c1 text COLLATE nocase)" {} + 6.2 "CREATE TABLE t1(c1 text CONSTRAINT 'a x' COLLATE nocase)" {} + + 7.1 "CREATE TABLE t1(c1 REFERENCES t2)" {} + 7.2 "CREATE TABLE t1(c1 CONSTRAINT abc REFERENCES t2)" {} + + 8.1 { + CREATE TABLE t1(c1 + PRIMARY KEY NOT NULL UNIQUE CHECK(c1 IS 'ten') DEFAULT 123 REFERENCES t1 + ); + } {} + 8.2 { + CREATE TABLE t1(c1 + REFERENCES t1 DEFAULT 123 CHECK(c1 IS 'ten') UNIQUE NOT NULL PRIMARY KEY + ); + } {} +} + +# EVIDENCE-OF: R-17905-31923 -- syntax diagram table-constraint +# +do_createtable_tests 0.3.1 -repair { + drop_all_tables + execsql { CREATE TABLE t2(x PRIMARY KEY) } +} { + 1.1 "CREATE TABLE t1(c1, c2, PRIMARY KEY(c1))" {} + 1.2 "CREATE TABLE t1(c1, c2, PRIMARY KEY(c1, c2))" {} + 1.3 "CREATE TABLE t1(c1, c2, PRIMARY KEY(c1, c2) ON CONFLICT IGNORE)" {} + + 2.1 "CREATE TABLE t1(c1, c2, UNIQUE(c1))" {} + 2.2 "CREATE TABLE t1(c1, c2, UNIQUE(c1, c2))" {} + 2.3 "CREATE TABLE t1(c1, c2, UNIQUE(c1, c2) ON CONFLICT IGNORE)" {} + + 3.1 "CREATE TABLE t1(c1, c2, CHECK(c1 IS NOT c2))" {} + + 4.1 "CREATE TABLE t1(c1, c2, FOREIGN KEY(c1) REFERENCES t2)" {} +} + +# EVIDENCE-OF: R-18765-31171 -- syntax diagram column-def +# +do_createtable_tests 0.4.1 -repair { + drop_all_tables +} { + 1 {CREATE TABLE t1( + col1, + col2 TEXT, + col3 INTEGER UNIQUE, + col4 VARCHAR(10, 10) PRIMARY KEY, + "name with spaces" REFERENCES t1 + ); + } {} +} + +# EVIDENCE-OF: R-59573-11075 -- syntax diagram create-table-stmt +# +do_createtable_tests 0.5.1 -repair { + drop_all_tables + execsql { CREATE TABLE t2(a, b, c) } +} { + 1 "CREATE TABLE t1(a, b, c)" {} + 2 "CREATE TEMP TABLE t1(a, b, c)" {} + 3 "CREATE TEMPORARY TABLE t1(a, b, c)" {} + 4 "CREATE TABLE IF NOT EXISTS t1(a, b, c)" {} + 5 "CREATE TEMP TABLE IF NOT EXISTS t1(a, b, c)" {} + 6 "CREATE TEMPORARY TABLE IF NOT EXISTS t1(a, b, c)" {} + + 7 "CREATE TABLE main.t1(a, b, c)" {} + 8 "CREATE TEMP TABLE temp.t1(a, b, c)" {} + 9 "CREATE TEMPORARY TABLE temp.t1(a, b, c)" {} + 10 "CREATE TABLE IF NOT EXISTS main.t1(a, b, c)" {} + 11 "CREATE TEMP TABLE IF NOT EXISTS temp.t1(a, b, c)" {} + 12 "CREATE TEMPORARY TABLE IF NOT EXISTS temp.t1(a, b, c)" {} + + 13 "CREATE TABLE t1 AS SELECT * FROM t2" {} + 14 "CREATE TEMP TABLE t1 AS SELECT c, b, a FROM t2" {} + 15 "CREATE TABLE t1 AS SELECT count(*), max(b), min(a) FROM t2" {} +} + +# EVIDENCE-OF: R-32138-02228 -- syntax diagram foreign-key-clause +# +# 1: Explicit parent-key columns. +# 2: Implicit child-key columns. +# +# 1: MATCH FULL +# 2: MATCH PARTIAL +# 3: MATCH SIMPLE +# 4: MATCH STICK +# 5: +# +# 1: ON DELETE SET NULL +# 2: ON DELETE SET DEFAULT +# 3: ON DELETE CASCADE +# 4: ON DELETE RESTRICT +# 5: ON DELETE NO ACTION +# 6: +# +# 1: ON UPDATE SET NULL +# 2: ON UPDATE SET DEFAULT +# 3: ON UPDATE CASCADE +# 4: ON UPDATE RESTRICT +# 5: ON UPDATE NO ACTION +# 6: +# +# 1: NOT DEFERRABLE INITIALLY DEFERRED +# 2: NOT DEFERRABLE INITIALLY IMMEDIATE +# 3: NOT DEFERRABLE +# 4: DEFERRABLE INITIALLY DEFERRED +# 5: DEFERRABLE INITIALLY IMMEDIATE +# 6: DEFERRABLE +# 7: +# +do_createtable_tests 0.6.1 -repair { + drop_all_tables + execsql { CREATE TABLE t2(x PRIMARY KEY, y) } + execsql { CREATE TABLE t3(i, j, UNIQUE(i, j) ) } +} { + 11146 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH FULL + ON DELETE SET NULL ON UPDATE RESTRICT DEFERRABLE + )} {} + 11412 { CREATE TABLE t1(a + REFERENCES t2(x) + ON DELETE RESTRICT ON UPDATE SET NULL MATCH FULL + NOT DEFERRABLE INITIALLY IMMEDIATE + )} {} + 12135 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH PARTIAL + ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY IMMEDIATE + )} {} + 12427 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH PARTIAL + ON DELETE RESTRICT ON UPDATE SET DEFAULT + )} {} + 12446 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH PARTIAL + ON DELETE RESTRICT ON UPDATE RESTRICT DEFERRABLE + )} {} + 12522 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH PARTIAL + ON DELETE NO ACTION ON UPDATE SET DEFAULT NOT DEFERRABLE INITIALLY IMMEDIATE + )} {} + 13133 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH SIMPLE + ON DELETE SET NULL ON UPDATE CASCADE NOT DEFERRABLE + )} {} + 13216 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH SIMPLE + ON DELETE SET DEFAULT ON UPDATE SET NULL DEFERRABLE + )} {} + 13263 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH SIMPLE + ON DELETE SET DEFAULT NOT DEFERRABLE + )} {} + 13421 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH SIMPLE + ON DELETE RESTRICT ON UPDATE SET DEFAULT NOT DEFERRABLE INITIALLY DEFERRED + )} {} + 13432 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH SIMPLE + ON DELETE RESTRICT ON UPDATE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE + )} {} + 13523 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH SIMPLE + ON DELETE NO ACTION ON UPDATE SET DEFAULT NOT DEFERRABLE + )} {} + 14336 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH STICK + ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE + )} {} + 14611 { CREATE TABLE t1(a + REFERENCES t2(x) MATCH STICK + ON UPDATE SET NULL NOT DEFERRABLE INITIALLY DEFERRED + )} {} + 15155 { CREATE TABLE t1(a + REFERENCES t2(x) + ON DELETE SET NULL ON UPDATE NO ACTION DEFERRABLE INITIALLY IMMEDIATE + )} {} + 15453 { CREATE TABLE t1(a + REFERENCES t2(x) ON DELETE RESTRICT ON UPDATE NO ACTION NOT DEFERRABLE + )} {} + 15661 { CREATE TABLE t1(a + REFERENCES t2(x) NOT DEFERRABLE INITIALLY DEFERRED + )} {} + 21115 { CREATE TABLE t1(a + REFERENCES t2 MATCH FULL + ON DELETE SET NULL ON UPDATE SET NULL DEFERRABLE INITIALLY IMMEDIATE + )} {} + 21123 { CREATE TABLE t1(a + REFERENCES t2 MATCH FULL + ON DELETE SET NULL ON UPDATE SET DEFAULT NOT DEFERRABLE + )} {} + 21217 { CREATE TABLE t1(a + REFERENCES t2 MATCH FULL ON DELETE SET DEFAULT ON UPDATE SET NULL + )} {} + 21362 { CREATE TABLE t1(a + REFERENCES t2 MATCH FULL + ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE + )} {} + 22143 { CREATE TABLE t1(a + REFERENCES t2 MATCH PARTIAL + ON DELETE SET NULL ON UPDATE RESTRICT NOT DEFERRABLE + )} {} + 22156 { CREATE TABLE t1(a + REFERENCES t2 MATCH PARTIAL + ON DELETE SET NULL ON UPDATE NO ACTION DEFERRABLE + )} {} + 22327 { CREATE TABLE t1(a + REFERENCES t2 MATCH PARTIAL ON DELETE CASCADE ON UPDATE SET DEFAULT + )} {} + 22663 { CREATE TABLE t1(a + REFERENCES t2 MATCH PARTIAL NOT DEFERRABLE + )} {} + 23236 { CREATE TABLE t1(a + REFERENCES t2 MATCH SIMPLE + ON DELETE SET DEFAULT ON UPDATE CASCADE DEFERRABLE + )} {} + 24155 { CREATE TABLE t1(a + REFERENCES t2 MATCH STICK + ON DELETE SET NULL ON UPDATE NO ACTION DEFERRABLE INITIALLY IMMEDIATE + )} {} + 24522 { CREATE TABLE t1(a + REFERENCES t2 MATCH STICK + ON DELETE NO ACTION ON UPDATE SET DEFAULT NOT DEFERRABLE INITIALLY IMMEDIATE + )} {} + 24625 { CREATE TABLE t1(a + REFERENCES t2 MATCH STICK + ON UPDATE SET DEFAULT DEFERRABLE INITIALLY IMMEDIATE + )} {} + 25454 { CREATE TABLE t1(a + REFERENCES t2 + ON DELETE RESTRICT ON UPDATE NO ACTION DEFERRABLE INITIALLY DEFERRED + )} {} +} + +#------------------------------------------------------------------------- +# Test cases e_createtable-1.* - test statements related to table and +# database names, the TEMP and TEMPORARY keywords, and the IF NOT EXISTS +# clause. +# +drop_all_tables +forcedelete test.db2 test.db3 + +do_execsql_test e_createtable-1.0 { + ATTACH 'test.db2' AS auxa; + ATTACH 'test.db3' AS auxb; +} {} + +# EVIDENCE-OF: R-17899-04554 Table names that begin with "sqlite_" are +# reserved for internal use. It is an error to attempt to create a table +# with a name that starts with "sqlite_". +# +do_createtable_tests 1.1.1 -error { + object name reserved for internal use: %s +} { + 1 "CREATE TABLE sqlite_abc(a, b, c)" sqlite_abc + 2 "CREATE TABLE temp.sqlite_helloworld(x)" sqlite_helloworld + 3 {CREATE TABLE auxa."sqlite__"(x, y)} sqlite__ + 4 {CREATE TABLE auxb."sqlite_"(z)} sqlite_ + 5 {CREATE TABLE "SQLITE_TBL"(z)} SQLITE_TBL +} +do_createtable_tests 1.1.2 { + 1 "CREATE TABLE sqlit_abc(a, b, c)" {} + 2 "CREATE TABLE temp.sqlitehelloworld(x)" {} + 3 {CREATE TABLE auxa."sqlite"(x, y)} {} + 4 {CREATE TABLE auxb."sqlite-"(z)} {} + 5 {CREATE TABLE "SQLITE-TBL"(z)} {} +} + + +# EVIDENCE-OF: R-10195-31023 If a is specified, it +# must be either "main", "temp", or the name of an attached database. +# +# EVIDENCE-OF: R-39822-07822 In this case the new table is created in +# the named database. +# +# Test cases 1.2.* test the first of the two requirements above. The +# second is verified by cases 1.3.*. +# +do_createtable_tests 1.2.1 -error { + unknown database %s +} { + 1 "CREATE TABLE george.t1(a, b)" george + 2 "CREATE TABLE _.t1(a, b)" _ +} +do_createtable_tests 1.2.2 { + 1 "CREATE TABLE main.abc(a, b, c)" {} + 2 "CREATE TABLE temp.helloworld(x)" {} + 3 {CREATE TABLE auxa."t 1"(x, y)} {} + 4 {CREATE TABLE auxb.xyz(z)} {} +} +drop_all_tables +do_createtable_tests 1.3 -tclquery { + unset -nocomplain X + array set X [table_list] + list $X(main) $X(temp) $X(auxa) $X(auxb) +} { + 1 "CREATE TABLE main.abc(a, b, c)" {abc {} {} {}} + 2 "CREATE TABLE main.t1(a, b, c)" {{abc t1} {} {} {}} + 3 "CREATE TABLE temp.tmp(a, b, c)" {{abc t1} tmp {} {}} + 4 "CREATE TABLE auxb.tbl(x, y)" {{abc t1} tmp {} tbl} + 5 "CREATE TABLE auxb.t1(k, v)" {{abc t1} tmp {} {t1 tbl}} + 6 "CREATE TABLE auxa.next(c, d)" {{abc t1} tmp next {t1 tbl}} +} + +# EVIDENCE-OF: R-18895-27365 If the "TEMP" or "TEMPORARY" keyword occurs +# between the "CREATE" and "TABLE" then the new table is created in the +# temp database. +# +drop_all_tables +do_createtable_tests 1.4 -tclquery { + unset -nocomplain X + array set X [table_list] + list $X(main) $X(temp) $X(auxa) $X(auxb) +} { + 1 "CREATE TEMP TABLE t1(a, b)" {{} t1 {} {}} + 2 "CREATE TEMPORARY TABLE t2(a, b)" {{} {t1 t2} {} {}} +} + +# EVIDENCE-OF: R-49439-47561 It is an error to specify both a +# and the TEMP or TEMPORARY keyword, unless the +# is "temp". +# +drop_all_tables +do_createtable_tests 1.5.1 -error { + temporary table name must be unqualified +} { + 1 "CREATE TEMP TABLE main.t1(a, b)" {} + 2 "CREATE TEMPORARY TABLE auxa.t2(a, b)" {} + 3 "CREATE TEMP TABLE auxb.t3(a, b)" {} + 4 "CREATE TEMPORARY TABLE main.xxx(x)" {} +} +drop_all_tables +do_createtable_tests 1.5.2 -tclquery { + unset -nocomplain X + array set X [table_list] + list $X(main) $X(temp) $X(auxa) $X(auxb) +} { + 1 "CREATE TEMP TABLE temp.t1(a, b)" {{} t1 {} {}} + 2 "CREATE TEMPORARY TABLE temp.t2(a, b)" {{} {t1 t2} {} {}} + 3 "CREATE TEMP TABLE TEMP.t3(a, b)" {{} {t1 t2 t3} {} {}} + 4 "CREATE TEMPORARY TABLE TEMP.xxx(x)" {{} {t1 t2 t3 xxx} {} {}} +} + +# EVIDENCE-OF: R-00917-09393 If no database name is specified and the +# TEMP keyword is not present then the table is created in the main +# database. +# +drop_all_tables +do_createtable_tests 1.6 -tclquery { + unset -nocomplain X + array set X [table_list] + list $X(main) $X(temp) $X(auxa) $X(auxb) +} { + 1 "CREATE TABLE t1(a, b)" {t1 {} {} {}} + 2 "CREATE TABLE t2(a, b)" {{t1 t2} {} {} {}} + 3 "CREATE TABLE t3(a, b)" {{t1 t2 t3} {} {} {}} + 4 "CREATE TABLE xxx(x)" {{t1 t2 t3 xxx} {} {} {}} +} + +drop_all_tables +do_execsql_test e_createtable-1.7.0 { + CREATE TABLE t1(x, y); + CREATE INDEX i1 ON t1(x); + CREATE VIEW v1 AS SELECT * FROM t1; + + CREATE TABLE auxa.tbl1(x, y); + CREATE INDEX auxa.idx1 ON tbl1(x); + CREATE VIEW auxa.view1 AS SELECT * FROM tbl1; +} {} + +# EVIDENCE-OF: R-01232-54838 It is usually an error to attempt to create +# a new table in a database that already contains a table, index or view +# of the same name. +# +# Test cases 1.7.1.* verify that creating a table in a database with a +# table/index/view of the same name does fail. 1.7.2.* tests that creating +# a table with the same name as a table/index/view in a different database +# is Ok. +# +do_createtable_tests 1.7.1 -error { %s } { + 1 "CREATE TABLE t1(a, b)" {{table t1 already exists}} + 2 "CREATE TABLE i1(a, b)" {{there is already an index named i1}} + 3 "CREATE TABLE v1(a, b)" {{table v1 already exists}} + 4 "CREATE TABLE auxa.tbl1(a, b)" {{table tbl1 already exists}} + 5 "CREATE TABLE auxa.idx1(a, b)" {{there is already an index named idx1}} + 6 "CREATE TABLE auxa.view1(a, b)" {{table view1 already exists}} +} +do_createtable_tests 1.7.2 { + 1 "CREATE TABLE auxa.t1(a, b)" {} + 2 "CREATE TABLE auxa.i1(a, b)" {} + 3 "CREATE TABLE auxa.v1(a, b)" {} + 4 "CREATE TABLE tbl1(a, b)" {} + 5 "CREATE TABLE idx1(a, b)" {} + 6 "CREATE TABLE view1(a, b)" {} +} + +# EVIDENCE-OF: R-33917-24086 However, if the "IF NOT EXISTS" clause is +# specified as part of the CREATE TABLE statement and a table or view of +# the same name already exists, the CREATE TABLE command simply has no +# effect (and no error message is returned). +# +drop_all_tables +do_execsql_test e_createtable-1.8.0 { + CREATE TABLE t1(x, y); + CREATE INDEX i1 ON t1(x); + CREATE VIEW v1 AS SELECT * FROM t1; + CREATE TABLE auxa.tbl1(x, y); + CREATE INDEX auxa.idx1 ON tbl1(x); + CREATE VIEW auxa.view1 AS SELECT * FROM tbl1; +} {} +do_createtable_tests 1.8 { + 1 "CREATE TABLE IF NOT EXISTS t1(a, b)" {} + 2 "CREATE TABLE IF NOT EXISTS auxa.tbl1(a, b)" {} + 3 "CREATE TABLE IF NOT EXISTS v1(a, b)" {} + 4 "CREATE TABLE IF NOT EXISTS auxa.view1(a, b)" {} +} + +# EVIDENCE-OF: R-16465-40078 An error is still returned if the table +# cannot be created because of an existing index, even if the "IF NOT +# EXISTS" clause is specified. +# +do_createtable_tests 1.9 -error { %s } { + 1 "CREATE TABLE IF NOT EXISTS i1(a, b)" + {{there is already an index named i1}} + 2 "CREATE TABLE IF NOT EXISTS auxa.idx1(a, b)" + {{there is already an index named idx1}} +} + +# EVIDENCE-OF: R-05513-33819 It is not an error to create a table that +# has the same name as an existing trigger. +# +drop_all_tables +do_execsql_test e_createtable-1.10.0 { + CREATE TABLE t1(x, y); + CREATE TABLE auxb.t2(x, y); + + CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN + SELECT 1; + END; + CREATE TRIGGER auxb.tr2 AFTER INSERT ON t2 BEGIN + SELECT 1; + END; +} {} +do_createtable_tests 1.10 { + 1 "CREATE TABLE tr1(a, b)" {} + 2 "CREATE TABLE tr2(a, b)" {} + 3 "CREATE TABLE auxb.tr1(a, b)" {} + 4 "CREATE TABLE auxb.tr2(a, b)" {} +} + +# EVIDENCE-OF: R-22283-14179 Tables are removed using the DROP TABLE +# statement. +# +drop_all_tables +do_execsql_test e_createtable-1.11.0 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(a, b); + CREATE TABLE auxa.t3(a, b); + CREATE TABLE auxa.t4(a, b); +} {} + +do_execsql_test e_createtable-1.11.1.1 { + SELECT * FROM t1; + SELECT * FROM t2; + SELECT * FROM t3; + SELECT * FROM t4; +} {} +do_execsql_test e_createtable-1.11.1.2 { DROP TABLE t1 } {} +do_catchsql_test e_createtable-1.11.1.3 { + SELECT * FROM t1 +} {1 {no such table: t1}} +do_execsql_test e_createtable-1.11.1.4 { DROP TABLE t3 } {} +do_catchsql_test e_createtable-1.11.1.5 { + SELECT * FROM t3 +} {1 {no such table: t3}} + +do_execsql_test e_createtable-1.11.2.1 { + SELECT name FROM sqlite_master; + SELECT name FROM auxa.sqlite_master; +} {t2 t4} +do_execsql_test e_createtable-1.11.2.2 { DROP TABLE t2 } {} +do_execsql_test e_createtable-1.11.2.3 { DROP TABLE t4 } {} +do_execsql_test e_createtable-1.11.2.4 { + SELECT name FROM sqlite_master; + SELECT name FROM auxa.sqlite_master; +} {} + +#------------------------------------------------------------------------- +# Test cases e_createtable-2.* - test statements related to the CREATE +# TABLE AS ... SELECT statement. +# + +# Three Tcl commands: +# +# select_column_names SQL +# The argument must be a SELECT statement. Return a list of the names +# of the columns of the result-set that would be returned by executing +# the SELECT. +# +# table_column_names TBL +# The argument must be a table name. Return a list of column names, from +# left to right, for the table. +# +# table_column_decltypes TBL +# The argument must be a table name. Return a list of column declared +# types, from left to right, for the table. +# +proc sci {select cmd} { + set res [list] + set STMT [sqlite3_prepare_v2 db $select -1 dummy] + for {set i 0} {$i < [sqlite3_column_count $STMT]} {incr i} { + lappend res [$cmd $STMT $i] + } + sqlite3_finalize $STMT + set res +} +proc tci {tbl cmd} { sci "SELECT * FROM $tbl" $cmd } +proc select_column_names {sql} { sci $sql sqlite3_column_name } +proc table_column_names {tbl} { tci $tbl sqlite3_column_name } +proc table_column_decltypes {tbl} { tci $tbl sqlite3_column_decltype } + +# Create a database schema. This schema is used by tests 2.1.* through 2.3.*. +# +drop_all_tables +do_execsql_test e_createtable-2.0 { + CREATE TABLE t1(a, b, c); + CREATE TABLE t2(d, e, f); + CREATE TABLE t3(g BIGINT, h VARCHAR(10)); + CREATE TABLE t4(i BLOB, j ANYOLDATA); + CREATE TABLE t5(k FLOAT, l INTEGER); + CREATE TABLE t6(m DEFAULT 10, n DEFAULT 5, PRIMARY KEY(m, n)); + CREATE TABLE t7(x INTEGER PRIMARY KEY); + CREATE TABLE t8(o COLLATE nocase DEFAULT 'abc'); + CREATE TABLE t9(p NOT NULL, q DOUBLE CHECK (q!=0), r STRING UNIQUE); +} {} + +# EVIDENCE-OF: R-64828-59568 The table has the same number of columns as +# the rows returned by the SELECT statement. The name of each column is +# the same as the name of the corresponding column in the result set of +# the SELECT statement. +# +do_createtable_tests 2.1 -tclquery { + table_column_names x1 +} -repair { + catchsql { DROP TABLE x1 } +} { + 1 "CREATE TABLE x1 AS SELECT * FROM t1" {a b c} + 2 "CREATE TABLE x1 AS SELECT c, b, a FROM t1" {c b a} + 3 "CREATE TABLE x1 AS SELECT * FROM t1, t2" {a b c d e f} + 4 "CREATE TABLE x1 AS SELECT count(*) FROM t1" {count(*)} + 5 "CREATE TABLE x1 AS SELECT count(a) AS a, max(b) FROM t1" {a max(b)} +} + +# EVIDENCE-OF: R-37111-22855 The declared type of each column is +# determined by the expression affinity of the corresponding expression +# in the result set of the SELECT statement, as follows: Expression +# Affinity Column Declared Type TEXT "TEXT" NUMERIC "NUM" INTEGER "INT" +# REAL "REAL" NONE "" (empty string) +# +do_createtable_tests 2.2 -tclquery { + table_column_decltypes x1 +} -repair { + catchsql { DROP TABLE x1 } +} { + 1 "CREATE TABLE x1 AS SELECT a FROM t1" {""} + 2 "CREATE TABLE x1 AS SELECT * FROM t3" {INT TEXT} + 3 "CREATE TABLE x1 AS SELECT * FROM t4" {"" NUM} + 4 "CREATE TABLE x1 AS SELECT * FROM t5" {REAL INT} +} + +# EVIDENCE-OF: R-16667-09772 A table created using CREATE TABLE AS has +# no PRIMARY KEY and no constraints of any kind. The default value of +# each column is NULL. The default collation sequence for each column of +# the new table is BINARY. +# +# The following tests create tables based on SELECT statements that read +# from tables that have primary keys, constraints and explicit default +# collation sequences. None of this is transfered to the definition of +# the new table as stored in the sqlite_master table. +# +# Tests 2.3.2.* show that the default value of each column is NULL. +# +do_createtable_tests 2.3.1 -query { + SELECT sql FROM sqlite_master ORDER BY rowid DESC LIMIT 1 +} { + 1 "CREATE TABLE x1 AS SELECT * FROM t6" {{CREATE TABLE x1(m,n)}} + 2 "CREATE TABLE x2 AS SELECT * FROM t7" {{CREATE TABLE x2(x INT)}} + 3 "CREATE TABLE x3 AS SELECT * FROM t8" {{CREATE TABLE x3(o)}} + 4 "CREATE TABLE x4 AS SELECT * FROM t9" {{CREATE TABLE x4(p,q REAL,r NUM)}} +} +do_execsql_test e_createtable-2.3.2.1 { + INSERT INTO x1 DEFAULT VALUES; + INSERT INTO x2 DEFAULT VALUES; + INSERT INTO x3 DEFAULT VALUES; + INSERT INTO x4 DEFAULT VALUES; +} {} +db nullvalue null +do_execsql_test e_createtable-2.3.2.2 { SELECT * FROM x1 } {null null} +do_execsql_test e_createtable-2.3.2.3 { SELECT * FROM x2 } {null} +do_execsql_test e_createtable-2.3.2.4 { SELECT * FROM x3 } {null} +do_execsql_test e_createtable-2.3.2.5 { SELECT * FROM x4 } {null null null} +db nullvalue {} + +drop_all_tables +do_execsql_test e_createtable-2.4.0 { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES('i', 'one'); + INSERT INTO t1 VALUES('ii', 'two'); + INSERT INTO t1 VALUES('iii', 'three'); +} {} + +# EVIDENCE-OF: R-24153-28352 Tables created using CREATE TABLE AS are +# initially populated with the rows of data returned by the SELECT +# statement. +# +# EVIDENCE-OF: R-08224-30249 Rows are assigned contiguously ascending +# rowid values, starting with 1, in the order that they are returned by +# the SELECT statement. +# +# Each test case below is specified as the name of a table to create +# using "CREATE TABLE ... AS SELECT ..." and a SELECT statement to use in +# creating it. The table is created. +# +# Test cases 2.4.*.1 check that after it has been created, the data in the +# table is the same as the data returned by the SELECT statement executed as +# a standalone command, verifying the first testable statement above. +# +# Test cases 2.4.*.2 check that the rowids were allocated contiguously +# as required by the second testable statement above. That the rowids +# from the contiguous block were allocated to rows in the order rows are +# returned by the SELECT statement is verified by 2.4.*.1. +# +# EVIDENCE-OF: R-32365-09043 A "CREATE TABLE ... AS SELECT" statement +# creates and populates a database table based on the results of a +# SELECT statement. +# +# The above is also considered to be tested by the following. It is +# clear that tables are being created and populated by the command in +# question. +# +foreach {tn tbl select} { + 1 x1 "SELECT * FROM t1" + 2 x2 "SELECT * FROM t1 ORDER BY x DESC" + 3 x3 "SELECT * FROM t1 ORDER BY x ASC" +} { + # Create the table using a "CREATE TABLE ... AS SELECT ..." command. + execsql [subst {CREATE TABLE $tbl AS $select}] + + # Check that the rows inserted into the table, sorted in ascending rowid + # order, match those returned by executing the SELECT statement as a + # standalone command. + do_execsql_test e_createtable-2.4.$tn.1 [subst { + SELECT * FROM $tbl ORDER BY rowid; + }] [execsql $select] + + # Check that the rowids in the new table are a contiguous block starting + # with rowid 1. Note that this will fail if SELECT statement $select + # returns 0 rows (as max(rowid) will be NULL). + do_execsql_test e_createtable-2.4.$tn.2 [subst { + SELECT min(rowid), count(rowid)==max(rowid) FROM $tbl + }] {1 1} +} + +#-------------------------------------------------------------------------- +# Test cases for column defintions in CREATE TABLE statements that do not +# use a SELECT statement. Not including data constraints. In other words, +# tests for the specification of: +# +# * declared types, +# * default values, and +# * default collation sequences. +# + +# EVIDENCE-OF: R-27219-49057 Unlike most SQL databases, SQLite does not +# restrict the type of data that may be inserted into a column based on +# the columns declared type. +# +# Test this by creating a few tables with varied declared types, then +# inserting various different types of values into them. +# +drop_all_tables +do_execsql_test e_createtable-3.1.0 { + CREATE TABLE t1(x VARCHAR(10), y INTEGER, z DOUBLE); + CREATE TABLE t2(a DATETIME, b STRING, c REAL); + CREATE TABLE t3(o, t); +} {} + +# value type -> declared column type +# ---------------------------------- +# integer -> VARCHAR(10) +# string -> INTEGER +# blob -> DOUBLE +# +do_execsql_test e_createtable-3.1.1 { + INSERT INTO t1 VALUES(14, 'quite a lengthy string', X'555655'); + SELECT * FROM t1; +} {14 {quite a lengthy string} UVU} + +# string -> DATETIME +# integer -> STRING +# time -> REAL +# +do_execsql_test e_createtable-3.1.2 { + INSERT INTO t2 VALUES('not a datetime', 13, '12:41:59'); + SELECT * FROM t2; +} {{not a datetime} 13 12:41:59} + +# EVIDENCE-OF: R-10565-09557 The declared type of a column is used to +# determine the affinity of the column only. +# +# Affinities are tested in more detail elsewhere (see document +# datatype3.html). Here, just test that affinity transformations +# consistent with the expected affinity of each column (based on +# the declared type) appear to take place. +# +# Affinities of t1 (test cases 3.2.1.*): TEXT, INTEGER, REAL +# Affinities of t2 (test cases 3.2.2.*): NUMERIC, NUMERIC, REAL +# Affinities of t3 (test cases 3.2.3.*): NONE, NONE +# +do_execsql_test e_createtable-3.2.0 { DELETE FROM t1; DELETE FROM t2; } {} + +do_createtable_tests 3.2.1 -query { + SELECT quote(x), quote(y), quote(z) FROM t1 ORDER BY rowid DESC LIMIT 1; +} { + 1 "INSERT INTO t1 VALUES(15, '22.0', '14')" {'15' 22 14.0} + 2 "INSERT INTO t1 VALUES(22.0, 22.0, 22.0)" {'22.0' 22 22.0} +} +do_createtable_tests 3.2.2 -query { + SELECT quote(a), quote(b), quote(c) FROM t2 ORDER BY rowid DESC LIMIT 1; +} { + 1 "INSERT INTO t2 VALUES(15, '22.0', '14')" {15 22 14.0} + 2 "INSERT INTO t2 VALUES(22.0, 22.0, 22.0)" {22 22 22.0} +} +do_createtable_tests 3.2.3 -query { + SELECT quote(o), quote(t) FROM t3 ORDER BY rowid DESC LIMIT 1; +} { + 1 "INSERT INTO t3 VALUES('15', '22.0')" {'15' '22.0'} + 2 "INSERT INTO t3 VALUES(15, 22.0)" {15 22.0} +} + +# EVIDENCE-OF: R-42316-09582 If there is no explicit DEFAULT clause +# attached to a column definition, then the default value of the column +# is NULL. +# +# None of the columns in table t1 have an explicit DEFAULT clause. +# So testing that the default value of all columns in table t1 is +# NULL serves to verify the above. +# +do_createtable_tests 3.2.3 -query { + SELECT quote(x), quote(y), quote(z) FROM t1 +} -repair { + execsql { DELETE FROM t1 } +} { + 1 "INSERT INTO t1(x, y) VALUES('abc', 'xyz')" {'abc' 'xyz' NULL} + 2 "INSERT INTO t1(x, z) VALUES('abc', 'xyz')" {'abc' NULL 'xyz'} + 3 "INSERT INTO t1 DEFAULT VALUES" {NULL NULL NULL} +} + +# EVIDENCE-OF: R-62940-43005 An explicit DEFAULT clause may specify that +# the default value is NULL, a string constant, a blob constant, a +# signed-number, or any constant expression enclosed in parentheses. An +# explicit default value may also be one of the special case-independent +# keywords CURRENT_TIME, CURRENT_DATE or CURRENT_TIMESTAMP. +# +do_execsql_test e_createtable-3.3.1 { + CREATE TABLE t4( + a DEFAULT NULL, + b DEFAULT 'string constant', + c DEFAULT X'424C4F42', + d DEFAULT 1, + e DEFAULT -1, + f DEFAULT 3.14, + g DEFAULT -3.14, + h DEFAULT ( substr('abcd', 0, 2) || 'cd' ), + i DEFAULT CURRENT_TIME, + j DEFAULT CURRENT_DATE, + k DEFAULT CURRENT_TIMESTAMP + ); +} {} + +# EVIDENCE-OF: R-10288-43169 For the purposes of the DEFAULT clause, an +# expression is considered constant provided that it does not contain +# any sub-queries or string constants enclosed in double quotes. +# +do_createtable_tests 3.4.1 -error { + default value of column [x] is not constant +} { + 1 {CREATE TABLE t5(x DEFAULT ( (SELECT 1) ))} {} + 2 {CREATE TABLE t5(x DEFAULT ( "abc" ))} {} + 3 {CREATE TABLE t5(x DEFAULT ( 1 IN (SELECT 1) ))} {} + 4 {CREATE TABLE t5(x DEFAULT ( EXISTS (SELECT 1) ))} {} +} +do_createtable_tests 3.4.2 -repair { + catchsql { DROP TABLE t5 } +} { + 1 {CREATE TABLE t5(x DEFAULT ( 'abc' ))} {} + 2 {CREATE TABLE t5(x DEFAULT ( 1 IN (1, 2, 3) ))} {} +} + +# EVIDENCE-OF: R-18814-23501 Each time a row is inserted into the table +# by an INSERT statement that does not provide explicit values for all +# table columns the values stored in the new row are determined by their +# default values +# +# Verify this with some assert statements for which all, some and no +# columns lack explicit values. +# +set sqlite_current_time 1000000000 +do_createtable_tests 3.5 -query { + SELECT quote(a), quote(b), quote(c), quote(d), quote(e), quote(f), + quote(g), quote(h), quote(i), quote(j), quote(k) + FROM t4 ORDER BY rowid DESC LIMIT 1; +} { + 1 "INSERT INTO t4 DEFAULT VALUES" { + NULL {'string constant'} X'424C4F42' 1 -1 3.14 -3.14 + 'acd' '01:46:40' '2001-09-09' {'2001-09-09 01:46:40'} + } + + 2 "INSERT INTO t4(a, b, c) VALUES(1, 2, 3)" { + 1 2 3 1 -1 3.14 -3.14 'acd' '01:46:40' '2001-09-09' {'2001-09-09 01:46:40'} + } + + 3 "INSERT INTO t4(k, j, i) VALUES(1, 2, 3)" { + NULL {'string constant'} X'424C4F42' 1 -1 3.14 -3.14 'acd' 3 2 1 + } + + 4 "INSERT INTO t4(a,b,c,d,e,f,g,h,i,j,k) VALUES(1,2,3,4,5,6,7,8,9,10,11)" { + 1 2 3 4 5 6 7 8 9 10 11 + } +} + +# EVIDENCE-OF: R-12572-62501 If the default value of the column is a +# constant NULL, text, blob or signed-number value, then that value is +# used directly in the new row. +# +do_execsql_test e_createtable-3.6.1 { + CREATE TABLE t5( + a DEFAULT NULL, + b DEFAULT 'text value', + c DEFAULT X'424C4F42', + d DEFAULT -45678.6, + e DEFAULT 394507 + ); +} {} +do_execsql_test e_createtable-3.6.2 { + INSERT INTO t5 DEFAULT VALUES; + SELECT quote(a), quote(b), quote(c), quote(d), quote(e) FROM t5; +} {NULL {'text value'} X'424C4F42' -45678.6 394507} + +# EVIDENCE-OF: R-60616-50251 If the default value of a column is an +# expression in parentheses, then the expression is evaluated once for +# each row inserted and the results used in the new row. +# +# Test case 3.6.4 demonstrates that the expression is evaluated +# separately for each row if the INSERT is an "INSERT INTO ... SELECT ..." +# command. +# +set ::nextint 0 +proc nextint {} { incr ::nextint } +db func nextint nextint + +do_execsql_test e_createtable-3.7.1 { + CREATE TABLE t6(a DEFAULT ( nextint() ), b DEFAULT ( nextint() )); +} {} +do_execsql_test e_createtable-3.7.2 { + INSERT INTO t6 DEFAULT VALUES; + SELECT quote(a), quote(b) FROM t6; +} {1 2} +do_execsql_test e_createtable-3.7.3 { + INSERT INTO t6(a) VALUES('X'); + SELECT quote(a), quote(b) FROM t6; +} {1 2 'X' 3} +do_execsql_test e_createtable-3.7.4 { + INSERT INTO t6(a) SELECT a FROM t6; + SELECT quote(a), quote(b) FROM t6; +} {1 2 'X' 3 1 4 'X' 5} + +# EVIDENCE-OF: R-18683-56219 If the default value of a column is +# CURRENT_TIME, CURRENT_DATE or CURRENT_DATETIME, then the value used in +# the new row is a text representation of the current UTC date and/or +# time. +# +# This is difficult to test literally without knowing what time the +# user will run the tests. Instead, we test that the three cases +# above set the value to the current date and/or time according to +# the xCurrentTime() method of the VFS. Which is usually the same +# as UTC. In this case, however, we instrument it to always return +# a time equivalent to "2001-09-09 01:46:40 UTC". +# +set sqlite_current_time 1000000000 +do_execsql_test e_createtable-3.8.1 { + CREATE TABLE t7( + a DEFAULT CURRENT_TIME, + b DEFAULT CURRENT_DATE, + c DEFAULT CURRENT_TIMESTAMP + ); +} {} +do_execsql_test e_createtable-3.8.2 { + INSERT INTO t7 DEFAULT VALUES; + SELECT quote(a), quote(b), quote(c) FROM t7; +} {'01:46:40' '2001-09-09' {'2001-09-09 01:46:40'}} + + +# EVIDENCE-OF: R-62327-53843 For CURRENT_TIME, the format of the value +# is "HH:MM:SS". +# +# EVIDENCE-OF: R-03775-43471 For CURRENT_DATE, "YYYY-MM-DD". +# +# EVIDENCE-OF: R-07677-44926 The format for CURRENT_TIMESTAMP is +# "YYYY-MM-DD HH:MM:SS". +# +# The three above are demonstrated by tests 1, 2 and 3 below. +# Respectively. +# +do_createtable_tests 3.8.3 -query { + SELECT a, b, c FROM t7 ORDER BY rowid DESC LIMIT 1; +} { + 1 "INSERT INTO t7(b, c) VALUES('x', 'y')" {01:46:40 x y} + 2 "INSERT INTO t7(c, a) VALUES('x', 'y')" {y 2001-09-09 x} + 3 "INSERT INTO t7(a, b) VALUES('x', 'y')" {x y {2001-09-09 01:46:40}} +} + +# EVIDENCE-OF: R-55061-47754 The COLLATE clause specifies the name of a +# collating sequence to use as the default collation sequence for the +# column. +# +# EVIDENCE-OF: R-40275-54363 If no COLLATE clause is specified, the +# default collation sequence is BINARY. +# +do_execsql_test e_createtable-3-9.1 { + CREATE TABLE t8(a COLLATE nocase, b COLLATE rtrim, c COLLATE binary, d); + INSERT INTO t8 VALUES('abc', 'abc', 'abc', 'abc'); + INSERT INTO t8 VALUES('abc ', 'abc ', 'abc ', 'abc '); + INSERT INTO t8 VALUES('ABC ', 'ABC ', 'ABC ', 'ABC '); + INSERT INTO t8 VALUES('ABC', 'ABC', 'ABC', 'ABC'); +} {} +do_createtable_tests 3.9 { + 2 "SELECT a FROM t8 ORDER BY a, rowid" {abc ABC {abc } {ABC }} + 3 "SELECT b FROM t8 ORDER BY b, rowid" {{ABC } ABC abc {abc }} + 4 "SELECT c FROM t8 ORDER BY c, rowid" {ABC {ABC } abc {abc }} + 5 "SELECT d FROM t8 ORDER BY d, rowid" {ABC {ABC } abc {abc }} +} + +# EVIDENCE-OF: R-25473-20557 The number of columns in a table is limited +# by the SQLITE_MAX_COLUMN compile-time parameter. +# +proc columns {n} { + set res [list] + for {set i 0} {$i < $n} {incr i} { lappend res "c$i" } + join $res ", " +} +do_execsql_test e_createtable-3.10.1 [subst { + CREATE TABLE t9([columns $::SQLITE_MAX_COLUMN]); +}] {} +do_catchsql_test e_createtable-3.10.2 [subst { + CREATE TABLE t10([columns [expr $::SQLITE_MAX_COLUMN+1]]); +}] {1 {too many columns on t10}} + +# EVIDENCE-OF: R-27775-64721 Both of these limits can be lowered at +# runtime using the sqlite3_limit() C/C++ interface. +# +# A 30,000 byte blob consumes 30,003 bytes of record space. A record +# that contains 3 such blobs consumes (30,000*3)+1 bytes of space. Tests +# 3.11.4 and 3.11.5, which verify that SQLITE_MAX_LENGTH may be lowered +# at runtime, are based on this calculation. +# +sqlite3_limit db SQLITE_LIMIT_COLUMN 500 +do_execsql_test e_createtable-3.11.1 [subst { + CREATE TABLE t10([columns 500]); +}] {} +do_catchsql_test e_createtable-3.11.2 [subst { + CREATE TABLE t11([columns 501]); +}] {1 {too many columns on t11}} + +# Check that it is not possible to raise the column limit above its +# default compile time value. +# +sqlite3_limit db SQLITE_LIMIT_COLUMN [expr $::SQLITE_MAX_COLUMN+2] +do_catchsql_test e_createtable-3.11.3 [subst { + CREATE TABLE t11([columns [expr $::SQLITE_MAX_COLUMN+1]]); +}] {1 {too many columns on t11}} + +sqlite3_limit db SQLITE_LIMIT_LENGTH 90010 +do_execsql_test e_createtable-3.11.4 { + CREATE TABLE t12(a, b, c); + INSERT INTO t12 VALUES(randomblob(30000),randomblob(30000),randomblob(30000)); +} {} +do_catchsql_test e_createtable-3.11.5 { + INSERT INTO t12 VALUES(randomblob(30001),randomblob(30000),randomblob(30000)); +} {1 {string or blob too big}} + +#------------------------------------------------------------------------- +# Tests for statements regarding constraints (PRIMARY KEY, UNIQUE, NOT +# NULL and CHECK constraints). +# + +# EVIDENCE-OF: R-52382-54248 Each table in SQLite may have at most one +# PRIMARY KEY. +# +# EVIDENCE-OF: R-18080-47271 If there is more than one PRIMARY KEY +# clause in a single CREATE TABLE statement, it is an error. +# +# To test the two above, show that zero primary keys is Ok, one primary +# key is Ok, and two or more primary keys is an error. +# +drop_all_tables +do_createtable_tests 4.1.1 { + 1 "CREATE TABLE t1(a, b, c)" {} + 2 "CREATE TABLE t2(a PRIMARY KEY, b, c)" {} + 3 "CREATE TABLE t3(a, b, c, PRIMARY KEY(a))" {} + 4 "CREATE TABLE t4(a, b, c, PRIMARY KEY(c,b,a))" {} +} +do_createtable_tests 4.1.2 -error { + table "t5" has more than one primary key +} { + 1 "CREATE TABLE t5(a PRIMARY KEY, b PRIMARY KEY, c)" {} + 2 "CREATE TABLE t5(a, b PRIMARY KEY, c, PRIMARY KEY(a))" {} + 3 "CREATE TABLE t5(a INTEGER PRIMARY KEY, b PRIMARY KEY, c)" {} + 4 "CREATE TABLE t5(a INTEGER PRIMARY KEY, b, c, PRIMARY KEY(b, c))" {} + 5 "CREATE TABLE t5(a PRIMARY KEY, b, c, PRIMARY KEY(a))" {} + 6 "CREATE TABLE t5(a INTEGER PRIMARY KEY, b, c, PRIMARY KEY(a))" {} +} + +proc table_pk {tbl} { + set pk [list] + db eval "pragma table_info($tbl)" a { + if {$a(pk)} { lappend pk $a(name) } + } + set pk +} + +# EVIDENCE-OF: R-41411-18837 If the keywords PRIMARY KEY are added to a +# column definition, then the primary key for the table consists of that +# single column. +# +# The above is tested by 4.2.1.* +# +# EVIDENCE-OF: R-31775-48204 Or, if a PRIMARY KEY clause is specified as +# a table-constraint, then the primary key of the table consists of the +# list of columns specified as part of the PRIMARY KEY clause. +# +# The above is tested by 4.2.2.* +# +do_createtable_tests 4.2 -repair { + catchsql { DROP TABLE t5 } +} -tclquery { + table_pk t5 +} { + 1.1 "CREATE TABLE t5(a, b INTEGER PRIMARY KEY, c)" {b} + 1.2 "CREATE TABLE t5(a PRIMARY KEY, b, c)" {a} + + 2.1 "CREATE TABLE t5(a, b, c, PRIMARY KEY(a))" {a} + 2.2 "CREATE TABLE t5(a, b, c, PRIMARY KEY(c,b,a))" {a b c} + 2.3 "CREATE TABLE t5(a, b INTEGER PRIMARY KEY, c)" {b} +} + +# EVIDENCE-OF: R-33986-09410 Each row in a table with a primary key must +# feature a unique combination of values in its primary key columns. +# +# EVIDENCE-OF: R-39102-06737 If an INSERT or UPDATE statement attempts +# to modify the table content so that two or more rows feature identical +# primary key values, it is a constraint violation. +# +drop_all_tables +do_execsql_test 4.3.0 { + CREATE TABLE t1(x PRIMARY KEY, y); + INSERT INTO t1 VALUES(0, 'zero'); + INSERT INTO t1 VALUES(45.5, 'one'); + INSERT INTO t1 VALUES('brambles', 'two'); + INSERT INTO t1 VALUES(X'ABCDEF', 'three'); + + CREATE TABLE t2(x, y, PRIMARY KEY(x, y)); + INSERT INTO t2 VALUES(0, 'zero'); + INSERT INTO t2 VALUES(45.5, 'one'); + INSERT INTO t2 VALUES('brambles', 'two'); + INSERT INTO t2 VALUES(X'ABCDEF', 'three'); +} {} + +do_createtable_tests 4.3.1 -error { %s not unique } { + 1 "INSERT INTO t1 VALUES(0, 0)" {"column x is"} + 2 "INSERT INTO t1 VALUES(45.5, 'abc')" {"column x is"} + 3 "INSERT INTO t1 VALUES(0.0, 'abc')" {"column x is"} + 4 "INSERT INTO t1 VALUES('brambles', 'abc')" {"column x is"} + 5 "INSERT INTO t1 VALUES(X'ABCDEF', 'abc')" {"column x is"} + + 6 "INSERT INTO t2 VALUES(0, 'zero')" {"columns x, y are"} + 7 "INSERT INTO t2 VALUES(45.5, 'one')" {"columns x, y are"} + 8 "INSERT INTO t2 VALUES(0.0, 'zero')" {"columns x, y are"} + 9 "INSERT INTO t2 VALUES('brambles', 'two')" {"columns x, y are"} + 10 "INSERT INTO t2 VALUES(X'ABCDEF', 'three')" {"columns x, y are"} +} +do_createtable_tests 4.3.2 { + 1 "INSERT INTO t1 VALUES(-1, 0)" {} + 2 "INSERT INTO t1 VALUES(45.2, 'abc')" {} + 3 "INSERT INTO t1 VALUES(0.01, 'abc')" {} + 4 "INSERT INTO t1 VALUES('bramble', 'abc')" {} + 5 "INSERT INTO t1 VALUES(X'ABCDEE', 'abc')" {} + + 6 "INSERT INTO t2 VALUES(0, 0)" {} + 7 "INSERT INTO t2 VALUES(45.5, 'abc')" {} + 8 "INSERT INTO t2 VALUES(0.0, 'abc')" {} + 9 "INSERT INTO t2 VALUES('brambles', 'abc')" {} + 10 "INSERT INTO t2 VALUES(X'ABCDEF', 'abc')" {} +} +do_createtable_tests 4.3.3 -error { %s not unique } { + 1 "UPDATE t1 SET x=0 WHERE y='two'" {"column x is"} + 2 "UPDATE t1 SET x='brambles' WHERE y='three'" {"column x is"} + 3 "UPDATE t1 SET x=45.5 WHERE y='zero'" {"column x is"} + 4 "UPDATE t1 SET x=X'ABCDEF' WHERE y='one'" {"column x is"} + 5 "UPDATE t1 SET x=0.0 WHERE y='three'" {"column x is"} + + 6 "UPDATE t2 SET x=0, y='zero' WHERE y='two'" {"columns x, y are"} + 7 "UPDATE t2 SET x='brambles', y='two' WHERE y='three'" + {"columns x, y are"} + 8 "UPDATE t2 SET x=45.5, y='one' WHERE y='zero'" {"columns x, y are"} + 9 "UPDATE t2 SET x=X'ABCDEF', y='three' WHERE y='one'" + {"columns x, y are"} + 10 "UPDATE t2 SET x=0.0, y='zero' WHERE y='three'" + {"columns x, y are"} +} + + +# EVIDENCE-OF: R-52572-02078 For the purposes of determining the +# uniqueness of primary key values, NULL values are considered distinct +# from all other values, including other NULLs. +# +do_createtable_tests 4.4 { + 1 "INSERT INTO t1 VALUES(NULL, 0)" {} + 2 "INSERT INTO t1 VALUES(NULL, 0)" {} + 3 "INSERT INTO t1 VALUES(NULL, 0)" {} + + 4 "INSERT INTO t2 VALUES(NULL, 'zero')" {} + 5 "INSERT INTO t2 VALUES(NULL, 'one')" {} + 6 "INSERT INTO t2 VALUES(NULL, 'two')" {} + 7 "INSERT INTO t2 VALUES(NULL, 'three')" {} + + 8 "INSERT INTO t2 VALUES(0, NULL)" {} + 9 "INSERT INTO t2 VALUES(45.5, NULL)" {} + 10 "INSERT INTO t2 VALUES(0.0, NULL)" {} + 11 "INSERT INTO t2 VALUES('brambles', NULL)" {} + 12 "INSERT INTO t2 VALUES(X'ABCDEF', NULL)" {} + + 13 "INSERT INTO t2 VALUES(NULL, NULL)" {} + 14 "INSERT INTO t2 VALUES(NULL, NULL)" {} +} + +# EVIDENCE-OF: R-61866-38053 Unless the column is an INTEGER PRIMARY KEY +# SQLite allows NULL values in a PRIMARY KEY column. +# +# If the column is an integer primary key, attempting to insert a NULL +# into the column triggers the auto-increment behaviour. Attempting +# to use UPDATE to set an ipk column to a NULL value is an error. +# +do_createtable_tests 4.5.1 { + 1 "SELECT count(*) FROM t1 WHERE x IS NULL" 3 + 2 "SELECT count(*) FROM t2 WHERE x IS NULL" 6 + 3 "SELECT count(*) FROM t2 WHERE y IS NULL" 7 + 4 "SELECT count(*) FROM t2 WHERE x IS NULL AND y IS NULL" 2 +} +do_execsql_test 4.5.2 { + CREATE TABLE t3(s, u INTEGER PRIMARY KEY, v); + INSERT INTO t3 VALUES(1, NULL, 2); + INSERT INTO t3 VALUES('x', NULL, 'y'); + SELECT u FROM t3; +} {1 2} +do_catchsql_test 4.5.3 { + INSERT INTO t3 VALUES(2, 5, 3); + UPDATE t3 SET u = NULL WHERE s = 2; +} {1 {datatype mismatch}} + +# EVIDENCE-OF: R-00227-21080 A UNIQUE constraint is similar to a PRIMARY +# KEY constraint, except that a single table may have any number of +# UNIQUE constraints. +# +drop_all_tables +do_createtable_tests 4.6 { + 1 "CREATE TABLE t1(a UNIQUE, b UNIQUE)" {} + 2 "CREATE TABLE t2(a UNIQUE, b, c, UNIQUE(c, b))" {} + 3 "CREATE TABLE t3(a, b, c, UNIQUE(a), UNIQUE(b), UNIQUE(c))" {} + 4 "CREATE TABLE t4(a, b, c, UNIQUE(a, b, c))" {} +} + +# EVIDENCE-OF: R-55240-58877 For each UNIQUE constraint on the table, +# each row must feature a unique combination of values in the columns +# identified by the UNIQUE constraint. +# +# EVIDENCE-OF: R-47733-51480 If an INSERT or UPDATE statement attempts +# to modify the table content so that two or more rows feature identical +# values in a set of columns that are subject to a UNIQUE constraint, it +# is a constraint violation. +# +do_execsql_test 4.7.0 { + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(4.3, 5.5); + INSERT INTO t1 VALUES('reveal', 'variableness'); + INSERT INTO t1 VALUES(X'123456', X'654321'); + + INSERT INTO t4 VALUES('xyx', 1, 1); + INSERT INTO t4 VALUES('xyx', 2, 1); + INSERT INTO t4 VALUES('uvw', 1, 1); +} +do_createtable_tests 4.7.1 -error { %s not unique } { + 1 "INSERT INTO t1 VALUES(1, 'one')" {{column a is}} + 2 "INSERT INTO t1 VALUES(4.3, 'two')" {{column a is}} + 3 "INSERT INTO t1 VALUES('reveal', 'three')" {{column a is}} + 4 "INSERT INTO t1 VALUES(X'123456', 'four')" {{column a is}} + + 5 "UPDATE t1 SET a = 1 WHERE rowid=2" {{column a is}} + 6 "UPDATE t1 SET a = 4.3 WHERE rowid=3" {{column a is}} + 7 "UPDATE t1 SET a = 'reveal' WHERE rowid=4" {{column a is}} + 8 "UPDATE t1 SET a = X'123456' WHERE rowid=1" {{column a is}} + + 9 "INSERT INTO t4 VALUES('xyx', 1, 1)" {{columns a, b, c are}} + 10 "INSERT INTO t4 VALUES('xyx', 2, 1)" {{columns a, b, c are}} + 11 "INSERT INTO t4 VALUES('uvw', 1, 1)" {{columns a, b, c are}} + + 12 "UPDATE t4 SET a='xyx' WHERE rowid=3" {{columns a, b, c are}} + 13 "UPDATE t4 SET b=1 WHERE rowid=2" {{columns a, b, c are}} + 14 "UPDATE t4 SET a=0, b=0, c=0" {{columns a, b, c are}} +} + +# EVIDENCE-OF: R-21289-11559 As with PRIMARY KEY constraints, for the +# purposes of UNIQUE constraints NULL values are considered distinct +# from all other values (including other NULLs). +# +do_createtable_tests 4.8 { + 1 "INSERT INTO t1 VALUES(NULL, NULL)" {} + 2 "INSERT INTO t1 VALUES(NULL, NULL)" {} + 3 "UPDATE t1 SET a = NULL" {} + 4 "UPDATE t1 SET b = NULL" {} + + 5 "INSERT INTO t4 VALUES(NULL, NULL, NULL)" {} + 6 "INSERT INTO t4 VALUES(NULL, NULL, NULL)" {} + 7 "UPDATE t4 SET a = NULL" {} + 8 "UPDATE t4 SET b = NULL" {} + 9 "UPDATE t4 SET c = NULL" {} +} + +# EVIDENCE-OF: R-26983-26377 INTEGER PRIMARY KEY columns aside, both +# UNIQUE and PRIMARY KEY constraints are implemented by creating an +# index in the database (in the same way as a "CREATE UNIQUE INDEX" +# statement would). +do_createtable_tests 4.9 -repair drop_all_tables -query { + SELECT count(*) FROM sqlite_master WHERE type='index' +} { + 1 "CREATE TABLE t1(a TEXT PRIMARY KEY, b)" 1 + 2 "CREATE TABLE t1(a INTEGER PRIMARY KEY, b)" 0 + 3 "CREATE TABLE t1(a TEXT UNIQUE, b)" 1 + 4 "CREATE TABLE t1(a PRIMARY KEY, b TEXT UNIQUE)" 2 + 5 "CREATE TABLE t1(a PRIMARY KEY, b, c, UNIQUE(c, b))" 2 +} + +# EVIDENCE-OF: R-02252-33116 Such an index is used like any other index +# in the database to optimize queries. +# +do_execsql_test 4.10.0 { + CREATE TABLE t1(a, b PRIMARY KEY); + CREATE TABLE t2(a, b, c, UNIQUE(b, c)); +} +do_createtable_tests 4.10 { + 1 "EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b = 5" + {0 0 0 {SEARCH TABLE t1 USING INDEX sqlite_autoindex_t1_1 (b=?) (~1 rows)}} + + 2 "EXPLAIN QUERY PLAN SELECT * FROM t2 ORDER BY b, c" + {0 0 0 {SCAN TABLE t2 USING INDEX sqlite_autoindex_t2_1 (~1000000 rows)}} + + 3 "EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE b=10 AND c>10" + {0 0 0 {SEARCH TABLE t2 USING INDEX sqlite_autoindex_t2_1 (b=? AND c>?) (~3 rows)}} +} + +# EVIDENCE-OF: R-45493-35653 A CHECK constraint may be attached to a +# column definition or specified as a table constraint. In practice it +# makes no difference. +# +# All the tests that deal with CHECK constraints below (4.11.* and +# 4.12.*) are run once for a table with the check constraint attached +# to a column definition, and once with a table where the check +# condition is specified as a table constraint. +# +# EVIDENCE-OF: R-55435-14303 Each time a new row is inserted into the +# table or an existing row is updated, the expression associated with +# each CHECK constraint is evaluated and cast to a NUMERIC value in the +# same way as a CAST expression. If the result is zero (integer value 0 +# or real value 0.0), then a constraint violation has occurred. +# +drop_all_tables +do_execsql_test 4.11 { + CREATE TABLE x1(a TEXT, b INTEGER CHECK( b>0 )); + CREATE TABLE t1(a TEXT, b INTEGER, CHECK( b>0 )); + INSERT INTO x1 VALUES('x', 'xx'); + INSERT INTO x1 VALUES('y', 'yy'); + INSERT INTO t1 SELECT * FROM x1; + + CREATE TABLE x2(a CHECK( a||b ), b); + CREATE TABLE t2(a, b, CHECK( a||b )); + INSERT INTO x2 VALUES(1, 'xx'); + INSERT INTO x2 VALUES(1, 'yy'); + INSERT INTO t2 SELECT * FROM x2; +} + +do_createtable_tests 4.11 -error {constraint failed} { + 1a "INSERT INTO x1 VALUES('one', 0)" {} + 1b "INSERT INTO t1 VALUES('one', -4.0)" {} + + 2a "INSERT INTO x2 VALUES('abc', 1)" {} + 2b "INSERT INTO t2 VALUES('abc', 1)" {} + + 3a "INSERT INTO x2 VALUES(0, 'abc')" {} + 3b "INSERT INTO t2 VALUES(0, 'abc')" {} + + 4a "UPDATE t1 SET b=-1 WHERE rowid=1" {} + 4b "UPDATE x1 SET b=-1 WHERE rowid=1" {} + + 4a "UPDATE x2 SET a='' WHERE rowid=1" {} + 4b "UPDATE t2 SET a='' WHERE rowid=1" {} +} + +# EVIDENCE-OF: R-34109-39108 If the CHECK expression evaluates to NULL, +# or any other non-zero value, it is not a constraint violation. +# +do_createtable_tests 4.12 { + 1a "INSERT INTO x1 VALUES('one', NULL)" {} + 1b "INSERT INTO t1 VALUES('one', NULL)" {} + + 2a "INSERT INTO x1 VALUES('one', 2)" {} + 2b "INSERT INTO t1 VALUES('one', 2)" {} + + 3a "INSERT INTO x2 VALUES(1, 'abc')" {} + 3b "INSERT INTO t2 VALUES(1, 'abc')" {} +} + +# EVIDENCE-OF: R-02060-64547 A NOT NULL constraint may only be attached +# to a column definition, not specified as a table constraint. +# +drop_all_tables +do_createtable_tests 4.13.1 { + 1 "CREATE TABLE t1(a NOT NULL, b)" {} + 2 "CREATE TABLE t2(a PRIMARY KEY NOT NULL, b)" {} + 3 "CREATE TABLE t3(a NOT NULL, b NOT NULL, c NOT NULL UNIQUE)" {} +} +do_createtable_tests 4.13.2 -error { + near "NOT": syntax error +} { + 1 "CREATE TABLE t4(a, b, NOT NULL(a))" {} + 2 "CREATE TABLE t4(a PRIMARY KEY, b, NOT NULL(a))" {} + 3 "CREATE TABLE t4(a, b, c UNIQUE, NOT NULL(a, b, c))" {} +} + +# EVIDENCE-OF: R-31795-57643 a NOT NULL constraint dictates that the +# associated column may not contain a NULL value. Attempting to set the +# column value to NULL when inserting a new row or updating an existing +# one causes a constraint violation. +# +# These tests use the tables created by 4.13. +# +do_execsql_test 4.14.0 { + INSERT INTO t1 VALUES('x', 'y'); + INSERT INTO t1 VALUES('z', NULL); + + INSERT INTO t2 VALUES('x', 'y'); + INSERT INTO t2 VALUES('z', NULL); + + INSERT INTO t3 VALUES('x', 'y', 'z'); + INSERT INTO t3 VALUES(1, 2, 3); +} +do_createtable_tests 4.14 -error { + %s may not be NULL +} { + 1 "INSERT INTO t1 VALUES(NULL, 'a')" {t1.a} + 2 "INSERT INTO t2 VALUES(NULL, 'b')" {t2.a} + 3 "INSERT INTO t3 VALUES('c', 'd', NULL)" {t3.c} + 4 "INSERT INTO t3 VALUES('e', NULL, 'f')" {t3.b} + 5 "INSERT INTO t3 VALUES(NULL, 'g', 'h')" {t3.a} +} + +# EVIDENCE-OF: R-42511-39459 PRIMARY KEY, UNIQUE and NOT NULL +# constraints may be explicitly assigned a default conflict resolution +# algorithm by including a conflict-clause in their definitions. +# +# Conflict clauses: ABORT, ROLLBACK, IGNORE, FAIL, REPLACE +# +# Test cases 4.15.*, 4.16.* and 4.17.* focus on PRIMARY KEY, NOT NULL +# and UNIQUE constraints, respectively. +# +drop_all_tables +do_execsql_test 4.15.0 { + CREATE TABLE t1_ab(a PRIMARY KEY ON CONFLICT ABORT, b); + CREATE TABLE t1_ro(a PRIMARY KEY ON CONFLICT ROLLBACK, b); + CREATE TABLE t1_ig(a PRIMARY KEY ON CONFLICT IGNORE, b); + CREATE TABLE t1_fa(a PRIMARY KEY ON CONFLICT FAIL, b); + CREATE TABLE t1_re(a PRIMARY KEY ON CONFLICT REPLACE, b); + CREATE TABLE t1_xx(a PRIMARY KEY, b); + + INSERT INTO t1_ab VALUES(1, 'one'); + INSERT INTO t1_ab VALUES(2, 'two'); + INSERT INTO t1_ro SELECT * FROM t1_ab; + INSERT INTO t1_ig SELECT * FROM t1_ab; + INSERT INTO t1_fa SELECT * FROM t1_ab; + INSERT INTO t1_re SELECT * FROM t1_ab; + INSERT INTO t1_xx SELECT * FROM t1_ab; + + CREATE TABLE t2_ab(a, b NOT NULL ON CONFLICT ABORT); + CREATE TABLE t2_ro(a, b NOT NULL ON CONFLICT ROLLBACK); + CREATE TABLE t2_ig(a, b NOT NULL ON CONFLICT IGNORE); + CREATE TABLE t2_fa(a, b NOT NULL ON CONFLICT FAIL); + CREATE TABLE t2_re(a, b NOT NULL ON CONFLICT REPLACE); + CREATE TABLE t2_xx(a, b NOT NULL); + + INSERT INTO t2_ab VALUES(1, 'one'); + INSERT INTO t2_ab VALUES(2, 'two'); + INSERT INTO t2_ro SELECT * FROM t2_ab; + INSERT INTO t2_ig SELECT * FROM t2_ab; + INSERT INTO t2_fa SELECT * FROM t2_ab; + INSERT INTO t2_re SELECT * FROM t2_ab; + INSERT INTO t2_xx SELECT * FROM t2_ab; + + CREATE TABLE t3_ab(a, b, UNIQUE(a, b) ON CONFLICT ABORT); + CREATE TABLE t3_ro(a, b, UNIQUE(a, b) ON CONFLICT ROLLBACK); + CREATE TABLE t3_ig(a, b, UNIQUE(a, b) ON CONFLICT IGNORE); + CREATE TABLE t3_fa(a, b, UNIQUE(a, b) ON CONFLICT FAIL); + CREATE TABLE t3_re(a, b, UNIQUE(a, b) ON CONFLICT REPLACE); + CREATE TABLE t3_xx(a, b, UNIQUE(a, b)); + + INSERT INTO t3_ab VALUES(1, 'one'); + INSERT INTO t3_ab VALUES(2, 'two'); + INSERT INTO t3_ro SELECT * FROM t3_ab; + INSERT INTO t3_ig SELECT * FROM t3_ab; + INSERT INTO t3_fa SELECT * FROM t3_ab; + INSERT INTO t3_re SELECT * FROM t3_ab; + INSERT INTO t3_xx SELECT * FROM t3_ab; +} + +foreach {tn tbl res ac data} { + 1 t1_ab {1 {column a is not unique}} 0 {1 one 2 two 3 three} + 2 t1_ro {1 {column a is not unique}} 1 {1 one 2 two} + 3 t1_fa {1 {column a is not unique}} 0 {1 one 2 two 3 three 4 string} + 4 t1_ig {0 {}} 0 {1 one 2 two 3 three 4 string 6 string} + 5 t1_re {0 {}} 0 {1 one 2 two 4 string 3 string 6 string} + 6 t1_xx {1 {column a is not unique}} 0 {1 one 2 two 3 three} +} { + catchsql COMMIT + do_execsql_test 4.15.$tn.1 "BEGIN; INSERT INTO $tbl VALUES(3, 'three')" + + do_catchsql_test 4.15.$tn.2 " + INSERT INTO $tbl SELECT ((a%2)*a+3), 'string' FROM $tbl; + " $res + + do_test e_createtable-4.15.$tn.3 { sqlite3_get_autocommit db } $ac + do_execsql_test 4.15.$tn.4 "SELECT * FROM $tbl" $data +} +foreach {tn tbl res ac data} { + 1 t2_ab {1 {t2_ab.b may not be NULL}} 0 {1 one 2 two 3 three} + 2 t2_ro {1 {t2_ro.b may not be NULL}} 1 {1 one 2 two} + 3 t2_fa {1 {t2_fa.b may not be NULL}} 0 {1 one 2 two 3 three 4 xx} + 4 t2_ig {0 {}} 0 {1 one 2 two 3 three 4 xx 6 xx} + 5 t2_re {1 {t2_re.b may not be NULL}} 0 {1 one 2 two 3 three} + 6 t2_xx {1 {t2_xx.b may not be NULL}} 0 {1 one 2 two 3 three} +} { + catchsql COMMIT + do_execsql_test 4.16.$tn.1 "BEGIN; INSERT INTO $tbl VALUES(3, 'three')" + + do_catchsql_test 4.16.$tn.2 " + INSERT INTO $tbl SELECT a+3, CASE a WHEN 2 THEN NULL ELSE 'xx' END FROM $tbl + " $res + + do_test e_createtable-4.16.$tn.3 { sqlite3_get_autocommit db } $ac + do_execsql_test 4.16.$tn.4 "SELECT * FROM $tbl" $data +} +foreach {tn tbl res ac data} { + 1 t3_ab {1 {columns a, b are not unique}} 0 {1 one 2 two 3 three} + 2 t3_ro {1 {columns a, b are not unique}} 1 {1 one 2 two} + 3 t3_fa {1 {columns a, b are not unique}} 0 {1 one 2 two 3 three 4 three} + 4 t3_ig {0 {}} 0 {1 one 2 two 3 three 4 three 6 three} + 5 t3_re {0 {}} 0 {1 one 2 two 4 three 3 three 6 three} + 6 t3_xx {1 {columns a, b are not unique}} 0 {1 one 2 two 3 three} +} { + catchsql COMMIT + do_execsql_test 4.17.$tn.1 "BEGIN; INSERT INTO $tbl VALUES(3, 'three')" + + do_catchsql_test 4.17.$tn.2 " + INSERT INTO $tbl SELECT ((a%2)*a+3), 'three' FROM $tbl + " $res + + do_test e_createtable-4.17.$tn.3 { sqlite3_get_autocommit db } $ac + do_execsql_test 4.17.$tn.4 "SELECT * FROM $tbl" $data +} +catchsql COMMIT + +# EVIDENCE-OF: R-12645-39772 Or, if a constraint definition does not +# include a conflict-clause or it is a CHECK constraint, the default +# conflict resolution algorithm is ABORT. +# +# The first half of the above is tested along with explicit ON +# CONFLICT clauses above (specifically, the tests involving t1_xx, t2_xx +# and t3_xx). The following just tests that the default conflict +# handling for CHECK constraints is ABORT. +# +do_execsql_test 4.18.1 { + CREATE TABLE t4(a, b CHECK (b!=10)); + INSERT INTO t4 VALUES(1, 2); + INSERT INTO t4 VALUES(3, 4); +} +do_execsql_test 4.18.2 { BEGIN; INSERT INTO t4 VALUES(5, 6) } +do_catchsql_test 4.18.3 { + INSERT INTO t4 SELECT a+4, b+4 FROM t4 +} {1 {constraint failed}} +do_test e_createtable-4.18.4 { sqlite3_get_autocommit db } 0 +do_execsql_test 4.18.5 { SELECT * FROM t4 } {1 2 3 4 5 6} + +# EVIDENCE-OF: R-19114-56113 Different constraints within the same table +# may have different default conflict resolution algorithms. +# +do_execsql_test 4.19.0 { + CREATE TABLE t5(a NOT NULL ON CONFLICT IGNORE, b NOT NULL ON CONFLICT ABORT); +} +do_catchsql_test 4.19.1 { INSERT INTO t5 VALUES(NULL, 'not null') } {0 {}} +do_execsql_test 4.19.2 { SELECT * FROM t5 } {} +do_catchsql_test 4.19.3 { INSERT INTO t5 VALUES('not null', NULL) } \ + {1 {t5.b may not be NULL}} +do_execsql_test 4.19.4 { SELECT * FROM t5 } {} + +#------------------------------------------------------------------------ +# Tests for INTEGER PRIMARY KEY and rowid related statements. +# + +# EVIDENCE-OF: R-52584-04009 The rowid value can be accessed using one +# of the special case-independent names "rowid", "oid", or "_rowid_" in +# place of a column name. +# +drop_all_tables +do_execsql_test 5.1.0 { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES('one', 'first'); + INSERT INTO t1 VALUES('two', 'second'); + INSERT INTO t1 VALUES('three', 'third'); +} +do_createtable_tests 5.1 { + 1 "SELECT rowid FROM t1" {1 2 3} + 2 "SELECT oid FROM t1" {1 2 3} + 3 "SELECT _rowid_ FROM t1" {1 2 3} + 4 "SELECT ROWID FROM t1" {1 2 3} + 5 "SELECT OID FROM t1" {1 2 3} + 6 "SELECT _ROWID_ FROM t1" {1 2 3} + 7 "SELECT RoWiD FROM t1" {1 2 3} + 8 "SELECT OiD FROM t1" {1 2 3} + 9 "SELECT _RoWiD_ FROM t1" {1 2 3} +} + +# EVIDENCE-OF: R-26501-17306 If a table contains a user defined column +# named "rowid", "oid" or "_rowid_", then that name always refers the +# explicitly declared column and cannot be used to retrieve the integer +# rowid value. +# +do_execsql_test 5.2.0 { + CREATE TABLE t2(oid, b); + CREATE TABLE t3(a, _rowid_); + CREATE TABLE t4(a, b, rowid); + + INSERT INTO t2 VALUES('one', 'two'); + INSERT INTO t2 VALUES('three', 'four'); + + INSERT INTO t3 VALUES('five', 'six'); + INSERT INTO t3 VALUES('seven', 'eight'); + + INSERT INTO t4 VALUES('nine', 'ten', 'eleven'); + INSERT INTO t4 VALUES('twelve', 'thirteen', 'fourteen'); +} +do_createtable_tests 5.2 { + 1 "SELECT oid, rowid, _rowid_ FROM t2" {one 1 1 three 2 2} + 2 "SELECT oid, rowid, _rowid_ FROM t3" {1 1 six 2 2 eight} + 3 "SELECT oid, rowid, _rowid_ FROM t4" {1 eleven 1 2 fourteen 2} +} + + +# Argument $tbl is the name of a table in the database. Argument $col is +# the name of one of the tables columns. Return 1 if $col is an alias for +# the rowid, or 0 otherwise. +# +proc is_integer_primary_key {tbl col} { + lindex [db eval [subst { + DELETE FROM $tbl; + INSERT INTO $tbl ($col) VALUES(0); + SELECT (rowid==$col) FROM $tbl; + DELETE FROM $tbl; + }]] 0 +} + +# EVIDENCE-OF: R-53738-31673 With one exception, if a table has a +# primary key that consists of a single column, and the declared type of +# that column is "INTEGER" in any mixture of upper and lower case, then +# the column becomes an alias for the rowid. +# +# EVIDENCE-OF: R-45951-08347 if the declaration of a column with +# declared type "INTEGER" includes an "PRIMARY KEY DESC" clause, it does +# not become an alias for the rowid and is not classified as an integer +# primary key. +# +do_createtable_tests 5.3 -tclquery { + is_integer_primary_key t5 pk +} -repair { + catchsql { DROP TABLE t5 } +} { + 1 "CREATE TABLE t5(pk integer primary key)" 1 + 2 "CREATE TABLE t5(pk integer, primary key(pk))" 1 + 3 "CREATE TABLE t5(pk integer, v integer, primary key(pk))" 1 + 4 "CREATE TABLE t5(pk integer, v integer, primary key(pk, v))" 0 + 5 "CREATE TABLE t5(pk int, v integer, primary key(pk, v))" 0 + 6 "CREATE TABLE t5(pk int, v integer, primary key(pk))" 0 + 7 "CREATE TABLE t5(pk int primary key, v integer)" 0 + 8 "CREATE TABLE t5(pk inTEger primary key)" 1 + 9 "CREATE TABLE t5(pk inteGEr, primary key(pk))" 1 + 10 "CREATE TABLE t5(pk INTEGER, v integer, primary key(pk))" 1 +} + +# EVIDENCE-OF: R-41444-49665 Other integer type names like "INT" or +# "BIGINT" or "SHORT INTEGER" or "UNSIGNED INTEGER" causes the primary +# key column to behave as an ordinary table column with integer affinity +# and a unique index, not as an alias for the rowid. +# +do_execsql_test 5.4.1 { + CREATE TABLE t6(pk INT primary key); + CREATE TABLE t7(pk BIGINT primary key); + CREATE TABLE t8(pk SHORT INTEGER primary key); + CREATE TABLE t9(pk UNSIGNED INTEGER primary key); +} +do_test e_createtable-5.4.2.1 { is_integer_primary_key t6 pk } 0 +do_test e_createtable-5.4.2.2 { is_integer_primary_key t7 pk } 0 +do_test e_createtable-5.4.2.3 { is_integer_primary_key t8 pk } 0 +do_test e_createtable-5.4.2.4 { is_integer_primary_key t9 pk } 0 + +do_execsql_test 5.4.3 { + INSERT INTO t6 VALUES('2.0'); + INSERT INTO t7 VALUES('2.0'); + INSERT INTO t8 VALUES('2.0'); + INSERT INTO t9 VALUES('2.0'); + SELECT typeof(pk), pk FROM t6; + SELECT typeof(pk), pk FROM t7; + SELECT typeof(pk), pk FROM t8; + SELECT typeof(pk), pk FROM t9; +} {integer 2 integer 2 integer 2 integer 2} + +do_catchsql_test 5.4.4.1 { + INSERT INTO t6 VALUES(2) +} {1 {column pk is not unique}} +do_catchsql_test 5.4.4.2 { + INSERT INTO t7 VALUES(2) +} {1 {column pk is not unique}} +do_catchsql_test 5.4.4.3 { + INSERT INTO t8 VALUES(2) +} {1 {column pk is not unique}} +do_catchsql_test 5.4.4.4 { + INSERT INTO t9 VALUES(2) +} {1 {column pk is not unique}} + +# EVIDENCE-OF: R-56094-57830 the following three table declarations all +# cause the column "x" to be an alias for the rowid (an integer primary +# key): CREATE TABLE t(x INTEGER PRIMARY KEY ASC, y, z); CREATE TABLE +# t(x INTEGER, y, z, PRIMARY KEY(x ASC)); CREATE TABLE t(x INTEGER, y, +# z, PRIMARY KEY(x DESC)); +# +# EVIDENCE-OF: R-20149-25884 the following declaration does not result +# in "x" being an alias for the rowid: CREATE TABLE t(x INTEGER PRIMARY +# KEY DESC, y, z); +# +do_createtable_tests 5 -tclquery { + is_integer_primary_key t x +} -repair { + catchsql { DROP TABLE t } +} { + 5.1 "CREATE TABLE t(x INTEGER PRIMARY KEY ASC, y, z)" 1 + 5.2 "CREATE TABLE t(x INTEGER, y, z, PRIMARY KEY(x ASC))" 1 + 5.3 "CREATE TABLE t(x INTEGER, y, z, PRIMARY KEY(x DESC))" 1 + 6.1 "CREATE TABLE t(x INTEGER PRIMARY KEY DESC, y, z)" 0 +} + +# EVIDENCE-OF: R-03733-29734 Rowid values may be modified using an +# UPDATE statement in the same way as any other column value can, either +# using one of the built-in aliases ("rowid", "oid" or "_rowid_") or by +# using an alias created by an integer primary key. +# +do_execsql_test 5.7.0 { + CREATE TABLE t10(a, b); + INSERT INTO t10 VALUES('ten', 10); + + CREATE TABLE t11(a, b INTEGER PRIMARY KEY); + INSERT INTO t11 VALUES('ten', 10); +} +do_createtable_tests 5.7.1 -query { + SELECT rowid, _rowid_, oid FROM t10; +} { + 1 "UPDATE t10 SET rowid = 5" {5 5 5} + 2 "UPDATE t10 SET _rowid_ = 6" {6 6 6} + 3 "UPDATE t10 SET oid = 7" {7 7 7} +} +do_createtable_tests 5.7.2 -query { + SELECT rowid, _rowid_, oid, b FROM t11; +} { + 1 "UPDATE t11 SET rowid = 5" {5 5 5 5} + 2 "UPDATE t11 SET _rowid_ = 6" {6 6 6 6} + 3 "UPDATE t11 SET oid = 7" {7 7 7 7} + 4 "UPDATE t11 SET b = 8" {8 8 8 8} +} + +# EVIDENCE-OF: R-58706-14229 Similarly, an INSERT statement may provide +# a value to use as the rowid for each row inserted. +# +do_createtable_tests 5.8.1 -query { + SELECT rowid, _rowid_, oid FROM t10; +} -repair { + execsql { DELETE FROM t10 } +} { + 1 "INSERT INTO t10(oid) VALUES(15)" {15 15 15} + 2 "INSERT INTO t10(rowid) VALUES(16)" {16 16 16} + 3 "INSERT INTO t10(_rowid_) VALUES(17)" {17 17 17} + 4 "INSERT INTO t10(a, b, oid) VALUES(1,2,3)" {3 3 3} +} +do_createtable_tests 5.8.2 -query { + SELECT rowid, _rowid_, oid, b FROM t11; +} -repair { + execsql { DELETE FROM t11 } +} { + 1 "INSERT INTO t11(oid) VALUES(15)" {15 15 15 15} + 2 "INSERT INTO t11(rowid) VALUES(16)" {16 16 16 16} + 3 "INSERT INTO t11(_rowid_) VALUES(17)" {17 17 17 17} + 4 "INSERT INTO t11(a, b) VALUES(1,2)" {2 2 2 2} +} + +# EVIDENCE-OF: R-32326-44592 Unlike normal SQLite columns, an integer +# primary key or rowid column must contain integer values. Integer +# primary key or rowid columns are not able to hold floating point +# values, strings, BLOBs, or NULLs. +# +# This is considered by the tests for the following 3 statements, +# which show that: +# +# 1. Attempts to UPDATE a rowid column to a non-integer value fail, +# 2. Attempts to INSERT a real, string or blob value into a rowid +# column fail, and +# 3. Attempting to INSERT a NULL value into a rowid column causes the +# system to automatically select an integer value to use. +# + + +# EVIDENCE-OF: R-64224-62578 If an UPDATE statement attempts to set an +# integer primary key or rowid column to a NULL or blob value, or to a +# string or real value that cannot be losslessly converted to an +# integer, a "datatype mismatch" error occurs and the statement is +# aborted. +# +drop_all_tables +do_execsql_test 5.9.0 { + CREATE TABLE t12(x INTEGER PRIMARY KEY, y); + INSERT INTO t12 VALUES(5, 'five'); +} +do_createtable_tests 5.9.1 -query { SELECT typeof(x), x FROM t12 } { + 1 "UPDATE t12 SET x = 4" {integer 4} + 2 "UPDATE t12 SET x = 10.0" {integer 10} + 3 "UPDATE t12 SET x = '12.0'" {integer 12} + 4 "UPDATE t12 SET x = '-15.0'" {integer -15} +} +do_createtable_tests 5.9.2 -error { + datatype mismatch +} { + 1 "UPDATE t12 SET x = 4.1" {} + 2 "UPDATE t12 SET x = 'hello'" {} + 3 "UPDATE t12 SET x = NULL" {} + 4 "UPDATE t12 SET x = X'ABCD'" {} + 5 "UPDATE t12 SET x = X'3900'" {} + 6 "UPDATE t12 SET x = X'39'" {} +} + +# EVIDENCE-OF: R-05734-13629 If an INSERT statement attempts to insert a +# blob value, or a string or real value that cannot be losslessly +# converted to an integer into an integer primary key or rowid column, a +# "datatype mismatch" error occurs and the statement is aborted. +# +do_execsql_test 5.10.0 { DELETE FROM t12 } +do_createtable_tests 5.10.1 -error { + datatype mismatch +} { + 1 "INSERT INTO t12(x) VALUES(4.1)" {} + 2 "INSERT INTO t12(x) VALUES('hello')" {} + 3 "INSERT INTO t12(x) VALUES(X'ABCD')" {} + 4 "INSERT INTO t12(x) VALUES(X'3900')" {} + 5 "INSERT INTO t12(x) VALUES(X'39')" {} +} +do_createtable_tests 5.10.2 -query { + SELECT typeof(x), x FROM t12 +} -repair { + execsql { DELETE FROM t12 } +} { + 1 "INSERT INTO t12(x) VALUES(4)" {integer 4} + 2 "INSERT INTO t12(x) VALUES(10.0)" {integer 10} + 3 "INSERT INTO t12(x) VALUES('12.0')" {integer 12} + 4 "INSERT INTO t12(x) VALUES('4e3')" {integer 4000} + 5 "INSERT INTO t12(x) VALUES('-14.0')" {integer -14} +} + +# EVIDENCE-OF: R-07986-46024 If an INSERT statement attempts to insert a +# NULL value into a rowid or integer primary key column, the system +# chooses an integer value to use as the rowid automatically. +# +do_execsql_test 5.11.0 { DELETE FROM t12 } +do_createtable_tests 5.11 -query { + SELECT typeof(x), x FROM t12 WHERE y IS (SELECT max(y) FROM t12) +} { + 1 "INSERT INTO t12 DEFAULT VALUES" {integer 1} + 2 "INSERT INTO t12(y) VALUES(5)" {integer 2} + 3 "INSERT INTO t12(x,y) VALUES(NULL, 10)" {integer 3} + 4 "INSERT INTO t12(x,y) SELECT NULL, 15 FROM t12" + {integer 4 integer 5 integer 6} + 5 "INSERT INTO t12(y) SELECT 20 FROM t12 LIMIT 3" + {integer 7 integer 8 integer 9} +} + +finish_test diff --git a/test/e_delete.test b/test/e_delete.test new file mode 100644 index 00000000..de391b11 --- /dev/null +++ b/test/e_delete.test @@ -0,0 +1,470 @@ +# 2010 September 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 implements tests to verify that the "testable statements" in +# the lang_delete.html document are correct. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +proc do_delete_tests {args} { + uplevel do_select_tests $args +} + +do_execsql_test e_delete-0.0 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a); +} {} + +# EVIDENCE-OF: R-24177-52883 -- syntax diagram delete-stmt +# +# EVIDENCE-OF: R-12802-60464 -- syntax diagram qualified-table-name +# +do_delete_tests e_delete-0.1 { + 1 "DELETE FROM t1" {} + 2 "DELETE FROM t1 INDEXED BY i1" {} + 3 "DELETE FROM t1 NOT INDEXED" {} + 4 "DELETE FROM main.t1" {} + 5 "DELETE FROM main.t1 INDEXED BY i1" {} + 6 "DELETE FROM main.t1 NOT INDEXED" {} + 7 "DELETE FROM t1 WHERE a>2" {} + 8 "DELETE FROM t1 INDEXED BY i1 WHERE a>2" {} + 9 "DELETE FROM t1 NOT INDEXED WHERE a>2" {} + 10 "DELETE FROM main.t1 WHERE a>2" {} + 11 "DELETE FROM main.t1 INDEXED BY i1 WHERE a>2" {} + 12 "DELETE FROM main.t1 NOT INDEXED WHERE a>2" {} +} + +# EVIDENCE-OF: R-20205-17349 If the WHERE clause is not present, all +# records in the table are deleted. +# +drop_all_tables +do_test e_delete-1.0 { + db transaction { + foreach t {t1 t2 t3 t4 t5 t6} { + execsql [string map [list %T% $t] { + CREATE TABLE %T%(x, y); + INSERT INTO %T% VALUES(1, 'one'); + INSERT INTO %T% VALUES(2, 'two'); + INSERT INTO %T% VALUES(3, 'three'); + INSERT INTO %T% VALUES(4, 'four'); + INSERT INTO %T% VALUES(5, 'five'); + }] + } + } +} {} +do_delete_tests e_delete-1.1 { + 1 "DELETE FROM t1 ; SELECT * FROM t1" {} + 2 "DELETE FROM main.t2 ; SELECT * FROM t2" {} +} + +# EVIDENCE-OF: R-30203-16177 If a WHERE clause is supplied, then only +# those rows for which the result of evaluating the WHERE clause as a +# boolean expression is true are deleted. +# +do_delete_tests e_delete-1.2 { + 1 "DELETE FROM t3 WHERE 1 ; SELECT x FROM t3" {} + 2 "DELETE FROM main.t4 WHERE 0 ; SELECT x FROM t4" {1 2 3 4 5} + 3 "DELETE FROM t4 WHERE 0.0 ; SELECT x FROM t4" {1 2 3 4 5} + 4 "DELETE FROM t4 WHERE NULL ; SELECT x FROM t4" {1 2 3 4 5} + 5 "DELETE FROM t4 WHERE y!='two'; SELECT x FROM t4" {2} + 6 "DELETE FROM t4 WHERE y='two' ; SELECT x FROM t4" {} + 7 "DELETE FROM t5 WHERE x=(SELECT max(x) FROM t5);SELECT x FROM t5" {1 2 3 4} + 8 "DELETE FROM t5 WHERE (SELECT max(x) FROM t4) ;SELECT x FROM t5" {1 2 3 4} + 9 "DELETE FROM t5 WHERE (SELECT max(x) FROM t6) ;SELECT x FROM t5" {} + 10 "DELETE FROM t6 WHERE y>'seven' ; SELECT y FROM t6" {one four five} +} + + +#------------------------------------------------------------------------- +# Tests for restrictions on DELETE statements that appear within trigger +# programs. +# +forcedelete test.db2 +forcedelete test.db3 +do_execsql_test e_delete-2.0 { + ATTACH 'test.db2' AS aux; + ATTACH 'test.db3' AS aux2; + + CREATE TABLE temp.t7(a, b); INSERT INTO temp.t7 VALUES(1, 2); + CREATE TABLE main.t7(a, b); INSERT INTO main.t7 VALUES(3, 4); + CREATE TABLE aux.t7(a, b); INSERT INTO aux.t7 VALUES(5, 6); + CREATE TABLE aux2.t7(a, b); INSERT INTO aux2.t7 VALUES(7, 8); + + CREATE TABLE main.t8(a, b); INSERT INTO main.t8 VALUES(1, 2); + CREATE TABLE aux.t8(a, b); INSERT INTO aux.t8 VALUES(3, 4); + CREATE TABLE aux2.t8(a, b); INSERT INTO aux2.t8 VALUES(5, 6); + + CREATE TABLE aux.t9(a, b); INSERT INTO aux.t9 VALUES(1, 2); + CREATE TABLE aux2.t9(a, b); INSERT INTO aux2.t9 VALUES(3, 4); + + CREATE TABLE aux2.t10(a, b); INSERT INTO aux2.t10 VALUES(1, 2); +} {} + + +# EVIDENCE-OF: R-09681-58560 The table-name specified as part of a +# DELETE statement within a trigger body must be unqualified. +# +# EVIDENCE-OF: R-36771-43788 In other words, the database-name. prefix +# on the table name is not allowed within triggers. +# +do_delete_tests e_delete-2.1 -error { + qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers +} { + 1 { + CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN + DELETE FROM main.t2; + END; + } {} + + 2 { + CREATE TRIGGER tr1 BEFORE UPDATE ON t2 BEGIN + DELETE FROM temp.t7 WHERE a=new.a; + END; + } {} + + 3 { + CREATE TRIGGER tr1 AFTER UPDATE ON t8 BEGIN + DELETE FROM aux2.t8 WHERE b!=a; + END; + } {} +} + +# EVIDENCE-OF: R-28818-63526 If the table to which the trigger is +# attached is not in the temp database, then DELETE statements within +# the trigger body must operate on tables within the same database as +# it. +# +# This is tested in two parts. First, check that if a table of the +# specified name does not exist, an error is raised. Secondly, test +# that if tables with the specified name exist in multiple databases, +# the local database table is used. +# +do_delete_tests e_delete-2.2.1 -error { no such table: %s } { + 1 { + CREATE TRIGGER main.tr1 AFTER INSERT ON main.t7 BEGIN + DELETE FROM t9; + END; + INSERT INTO main.t7 VALUES(1, 2); + } {main.t9} + + 2 { + CREATE TRIGGER aux.tr2 BEFORE UPDATE ON t9 BEGIN + DELETE FROM t10; + END; + UPDATE t9 SET a=1; + } {aux.t10} +} +do_execsql_test e_delete-2.2.X { + DROP TRIGGER main.tr1; + DROP TRIGGER aux.tr2; +} {} + +do_delete_tests e_delete-2.2.2 { + 1 { + CREATE TRIGGER aux.tr1 AFTER INSERT ON t8 BEGIN + DELETE FROM t9; + END; + INSERT INTO aux.t8 VALUES(1, 2); + + SELECT count(*) FROM aux.t9 + UNION ALL + SELECT count(*) FROM aux2.t9; + } {0 1} + + 2 { + CREATE TRIGGER main.tr1 AFTER INSERT ON t8 BEGIN + DELETE FROM t7; + END; + INSERT INTO main.t8 VALUES(1, 2); + + SELECT count(*) FROM temp.t7 + UNION ALL + SELECT count(*) FROM main.t7 + UNION ALL + SELECT count(*) FROM aux.t7 + UNION ALL + SELECT count(*) FROM aux2.t7; + } {1 0 1 1} +} + +# EVIDENCE-OF: R-31567-38587 If the table to which the trigger is +# attached is in the TEMP database, then the unqualified name of the +# table being deleted is resolved in the same way as it is for a +# top-level statement (by searching first the TEMP database, then the +# main database, then any other databases in the order they were +# attached). +# +do_execsql_test e_delete-2.3.0 { + DROP TRIGGER aux.tr1; + DROP TRIGGER main.tr1; + DELETE FROM main.t8 WHERE oid>1; + DELETE FROM aux.t8 WHERE oid>1; + INSERT INTO aux.t9 VALUES(1, 2); + INSERT INTO main.t7 VALUES(3, 4); +} {} +do_execsql_test e_delete-2.3.1 { + SELECT count(*) FROM temp.t7 UNION ALL SELECT count(*) FROM main.t7 UNION ALL + SELECT count(*) FROM aux.t7 UNION ALL SELECT count(*) FROM aux2.t7; + + SELECT count(*) FROM main.t8 UNION ALL SELECT count(*) FROM aux.t8 + UNION ALL SELECT count(*) FROM aux2.t8; + + SELECT count(*) FROM aux.t9 UNION ALL SELECT count(*) FROM aux2.t9; + + SELECT count(*) FROM aux2.t10; +} {1 1 1 1 1 1 1 1 1 1} +do_execsql_test e_delete-2.3.2 { + CREATE TRIGGER temp.tr1 AFTER INSERT ON t7 BEGIN + DELETE FROM t7; + DELETE FROM t8; + DELETE FROM t9; + DELETE FROM t10; + END; + INSERT INTO temp.t7 VALUES('hello', 'world'); +} {} +do_execsql_test e_delete-2.3.3 { + SELECT count(*) FROM temp.t7 UNION ALL SELECT count(*) FROM main.t7 UNION ALL + SELECT count(*) FROM aux.t7 UNION ALL SELECT count(*) FROM aux2.t7; + + SELECT count(*) FROM main.t8 UNION ALL SELECT count(*) FROM aux.t8 + UNION ALL SELECT count(*) FROM aux2.t8; + + SELECT count(*) FROM aux.t9 UNION ALL SELECT count(*) FROM aux2.t9; + + SELECT count(*) FROM aux2.t10; +} {0 1 1 1 0 1 1 0 1 0} + +# EVIDENCE-OF: R-28691-49464 The INDEXED BY and NOT INDEXED clauses are +# not allowed on DELETE statements within triggers. +# +do_execsql_test e_delete-2.4.0 { + CREATE INDEX i8 ON t8(a, b); +} {} +do_delete_tests e_delete-2.4 -error { + the %s %s clause is not allowed on UPDATE or DELETE statements within triggers +} { + 1 { + CREATE TRIGGER tr3 AFTER INSERT ON t8 BEGIN + DELETE FROM t8 INDEXED BY i8 WHERE a=5; + END; + } {INDEXED BY} + 2 { + CREATE TRIGGER tr3 AFTER INSERT ON t8 BEGIN + DELETE FROM t8 NOT INDEXED WHERE a=5; + END; + } {NOT INDEXED} +} + +ifcapable update_delete_limit { + +# EVIDENCE-OF: R-64942-06615 The LIMIT and ORDER BY clauses (described +# below) are unsupported for DELETE statements within triggers. +# +do_delete_tests e_delete-2.5 -error { near "%s": syntax error } { + 1 { + CREATE TRIGGER tr3 AFTER INSERT ON t8 BEGIN + DELETE FROM t8 LIMIT 10; + END; + } {LIMIT} + 2 { + CREATE TRIGGER tr3 AFTER INSERT ON t8 BEGIN + DELETE FROM t8 ORDER BY a LIMIT 5; + END; + } {ORDER} +} + +# EVIDENCE-OF: R-40026-10531 If SQLite is compiled with the +# SQLITE_ENABLE_UPDATE_DELETE_LIMIT compile-time option, then the syntax +# of the DELETE statement is extended by the addition of optional ORDER +# BY and LIMIT clauses: +# +# EVIDENCE-OF: R-45897-01670 -- syntax diagram delete-stmt-limited +# +do_delete_tests e_delete-3.1 { + 1 "DELETE FROM t1 LIMIT 5" {} + 2 "DELETE FROM t1 LIMIT 5-1 OFFSET 2+2" {} + 3 "DELETE FROM t1 LIMIT 2+2, 16/4" {} + 4 "DELETE FROM t1 ORDER BY x LIMIT 5" {} + 5 "DELETE FROM t1 ORDER BY x LIMIT 5-1 OFFSET 2+2" {} + 6 "DELETE FROM t1 ORDER BY x LIMIT 2+2, 16/4" {} + 7 "DELETE FROM t1 WHERE x>2 LIMIT 5" {} + 8 "DELETE FROM t1 WHERE x>2 LIMIT 5-1 OFFSET 2+2" {} + 9 "DELETE FROM t1 WHERE x>2 LIMIT 2+2, 16/4" {} + 10 "DELETE FROM t1 WHERE x>2 ORDER BY x LIMIT 5" {} + 11 "DELETE FROM t1 WHERE x>2 ORDER BY x LIMIT 5-1 OFFSET 2+2" {} + 12 "DELETE FROM t1 WHERE x>2 ORDER BY x LIMIT 2+2, 16/4" {} +} + +drop_all_tables +proc rebuild_t1 {} { + catchsql { DROP TABLE t1 } + execsql { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + INSERT INTO t1 VALUES(3, 'three'); + INSERT INTO t1 VALUES(4, 'four'); + INSERT INTO t1 VALUES(5, 'five'); + } +} + +# EVIDENCE-OF: R-44062-08550 If a DELETE statement has a LIMIT clause, +# the maximum number of rows that will be deleted is found by evaluating +# the accompanying expression and casting it to an integer value. +# +rebuild_t1 +do_delete_tests e_delete-3.2 -repair rebuild_t1 -query { + SELECT a FROM t1 +} { + 1 "DELETE FROM t1 LIMIT 3" {4 5} + 2 "DELETE FROM t1 LIMIT 1+1" {3 4 5} + 3 "DELETE FROM t1 LIMIT '4'" {5} + 4 "DELETE FROM t1 LIMIT '1.0'" {2 3 4 5} +} + +# EVIDENCE-OF: R-02661-56399 If the result of the evaluating the LIMIT +# clause cannot be losslessly converted to an integer value, it is an +# error. +# +do_delete_tests e_delete-3.3 -error { datatype mismatch } { + 1 "DELETE FROM t1 LIMIT 'abc'" {} + 2 "DELETE FROM t1 LIMIT NULL" {} + 3 "DELETE FROM t1 LIMIT X'ABCD'" {} + 4 "DELETE FROM t1 LIMIT 1.2" {} +} + +# EVIDENCE-OF: R-00598-03741 A negative LIMIT value is interpreted as +# "no limit". +# +do_delete_tests e_delete-3.4 -repair rebuild_t1 -query { + SELECT a FROM t1 +} { + 1 "DELETE FROM t1 LIMIT -1" {} + 2 "DELETE FROM t1 LIMIT 2-4" {} + 3 "DELETE FROM t1 LIMIT -4.0" {} + 4 "DELETE FROM t1 LIMIT 5*-1" {} +} + +# EVIDENCE-OF: R-26377-49195 If the DELETE statement also has an OFFSET +# clause, then it is similarly evaluated and cast to an integer value. +# Again, it is an error if the value cannot be losslessly converted to +# an integer. +# +do_delete_tests e_delete-3.5 -error { datatype mismatch } { + 1 "DELETE FROM t1 LIMIT 1 OFFSET 'abc'" {} + 2 "DELETE FROM t1 LIMIT 1 OFFSET NULL" {} + 3 "DELETE FROM t1 LIMIT 1 OFFSET X'ABCD'" {} + 4 "DELETE FROM t1 LIMIT 1 OFFSET 1.2" {} + 5 "DELETE FROM t1 LIMIT 'abc', 1" {} + 6 "DELETE FROM t1 LIMIT NULL, 1" {} + 7 "DELETE FROM t1 LIMIT X'ABCD', 1" {} + 8 "DELETE FROM t1 LIMIT 1.2, 1" {} +} + + +# EVIDENCE-OF: R-64004-53814 If there is no OFFSET clause, or the +# calculated integer value is negative, the effective OFFSET value is +# zero. +# +do_delete_tests e_delete-3.6 -repair rebuild_t1 -query { + SELECT a FROM t1 +} { + 1a "DELETE FROM t1 LIMIT 3 OFFSET 0" {4 5} + 1b "DELETE FROM t1 LIMIT 3" {4 5} + 1c "DELETE FROM t1 LIMIT 3 OFFSET -1" {4 5} + 2a "DELETE FROM t1 LIMIT 1+1 OFFSET 0" {3 4 5} + 2b "DELETE FROM t1 LIMIT 1+1" {3 4 5} + 2c "DELETE FROM t1 LIMIT 1+1 OFFSET 2-5" {3 4 5} + 3a "DELETE FROM t1 LIMIT '4' OFFSET 0" {5} + 3b "DELETE FROM t1 LIMIT '4'" {5} + 3c "DELETE FROM t1 LIMIT '4' OFFSET -1.0" {5} + 4a "DELETE FROM t1 LIMIT '1.0' OFFSET 0" {2 3 4 5} + 4b "DELETE FROM t1 LIMIT '1.0'" {2 3 4 5} + 4c "DELETE FROM t1 LIMIT '1.0' OFFSET -11" {2 3 4 5} +} + +# EVIDENCE-OF: R-48141-52334 If the DELETE statement has an ORDER BY +# clause, then all rows that would be deleted in the absence of the +# LIMIT clause are sorted according to the ORDER BY. The first M rows, +# where M is the value found by evaluating the OFFSET clause expression, +# are skipped, and the following N, where N is the value of the LIMIT +# expression, are deleted. +# +do_delete_tests e_delete-3.7 -repair rebuild_t1 -query { + SELECT a FROM t1 +} { + 1 "DELETE FROM t1 ORDER BY b LIMIT 2" {1 2 3} + 2 "DELETE FROM t1 ORDER BY length(b), a LIMIT 3" {3 5} + 3 "DELETE FROM t1 ORDER BY a DESC LIMIT 1 OFFSET 0" {1 2 3 4} + 4 "DELETE FROM t1 ORDER BY a DESC LIMIT 1 OFFSET 1" {1 2 3 5} + 5 "DELETE FROM t1 ORDER BY a DESC LIMIT 1 OFFSET 2" {1 2 4 5} +} + +# EVIDENCE-OF: R-64535-08414 If there are less than N rows remaining +# after taking the OFFSET clause into account, or if the LIMIT clause +# evaluated to a negative value, then all remaining rows are deleted. +# +do_delete_tests e_delete-3.8 -repair rebuild_t1 -query { + SELECT a FROM t1 +} { + 1 "DELETE FROM t1 ORDER BY a ASC LIMIT 10" {} + 2 "DELETE FROM t1 ORDER BY a ASC LIMIT -1" {} + 3 "DELETE FROM t1 ORDER BY a ASC LIMIT 4 OFFSET 2" {1 2} +} + +# EVIDENCE-OF: R-37284-06965 If the DELETE statement has no ORDER BY +# clause, then all rows that would be deleted in the absence of the +# LIMIT clause are assembled in an arbitrary order before applying the +# LIMIT and OFFSET clauses to determine the subset that are actually +# deleted. +# +# In practice, the "arbitrary order" is rowid order. +# +do_delete_tests e_delete-3.9 -repair rebuild_t1 -query { + SELECT a FROM t1 +} { + 1 "DELETE FROM t1 LIMIT 2" {3 4 5} + 2 "DELETE FROM t1 LIMIT 3" {4 5} + 3 "DELETE FROM t1 LIMIT 1 OFFSET 0" {2 3 4 5} + 4 "DELETE FROM t1 LIMIT 1 OFFSET 1" {1 3 4 5} + 5 "DELETE FROM t1 LIMIT 1 OFFSET 2" {1 2 4 5} +} + + +# EVIDENCE-OF: R-26627-30313 The ORDER BY clause on an DELETE statement +# is used only to determine which rows fall within the LIMIT. The order +# in which rows are deleted is arbitrary and is not influenced by the +# ORDER BY clause. +# +# In practice, rows are always deleted in rowid order. +# +do_delete_tests e_delete-3.10 -repair { + rebuild_t1 + catchsql { DROP TABLE t1log } + execsql { + CREATE TABLE t1log(x); + CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN + INSERT INTO t1log VALUES(old.a); + END; + } +} -query { + SELECT x FROM t1log +} { + 1 "DELETE FROM t1 ORDER BY a DESC LIMIT 2" {4 5} + 2 "DELETE FROM t1 ORDER BY a DESC LIMIT -1" {1 2 3 4 5} + 3 "DELETE FROM t1 ORDER BY a ASC LIMIT 2" {1 2} + 4 "DELETE FROM t1 ORDER BY a ASC LIMIT -1" {1 2 3 4 5} +} + +} + +finish_test diff --git a/test/e_droptrigger.test b/test/e_droptrigger.test new file mode 100644 index 00000000..0c148317 --- /dev/null +++ b/test/e_droptrigger.test @@ -0,0 +1,218 @@ +# 2010 November 29 +# +# 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 implements tests to verify that the "testable statements" in +# the lang_droptrigger.html document are correct. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix e_droptrigger + +ifcapable !trigger { finish_test ; return } + +proc do_droptrigger_tests {nm args} { + uplevel do_select_tests [list e_createtable-$nm] $args +} + +proc list_all_triggers {{db db}} { + set res [list] + $db eval { PRAGMA database_list } { + if {$name == "temp"} { + set tbl sqlite_temp_master + } else { + set tbl "$name.sqlite_master" + } + lappend res {*}[ + db eval "SELECT '$name.' || name FROM $tbl WHERE type = 'trigger'" + ] + } + set res +} + + +proc droptrigger_reopen_db {{event INSERT}} { + db close + forcedelete test.db test.db2 + sqlite3 db test.db + + set ::triggers_fired [list] + proc r {x} { lappend ::triggers_fired $x } + db func r r + + db eval " + ATTACH 'test.db2' AS aux; + + CREATE TEMP TABLE t1(a, b); + INSERT INTO t1 VALUES('a', 'b'); + CREATE TRIGGER tr1 AFTER $event ON t1 BEGIN SELECT r('temp.tr1') ; END; + + CREATE TABLE t2(a, b); + INSERT INTO t2 VALUES('a', 'b'); + CREATE TRIGGER tr1 BEFORE $event ON t2 BEGIN SELECT r('main.tr1') ; END; + CREATE TRIGGER tr2 AFTER $event ON t2 BEGIN SELECT r('main.tr2') ; END; + + CREATE TABLE aux.t3(a, b); + INSERT INTO t3 VALUES('a', 'b'); + CREATE TRIGGER aux.tr1 BEFORE $event ON t3 BEGIN SELECT r('aux.tr1') ; END; + CREATE TRIGGER aux.tr2 AFTER $event ON t3 BEGIN SELECT r('aux.tr2') ; END; + CREATE TRIGGER aux.tr3 AFTER $event ON t3 BEGIN SELECT r('aux.tr3') ; END; + " +} + + +# EVIDENCE-OF: R-52650-16855 -- syntax diagram drop-trigger-stmt +# +do_droptrigger_tests 1.1 -repair { + droptrigger_reopen_db +} -tclquery { + list_all_triggers +} { + 1 "DROP TRIGGER main.tr1" + {main.tr2 temp.tr1 aux.tr1 aux.tr2 aux.tr3} + 2 "DROP TRIGGER IF EXISTS main.tr1" + {main.tr2 temp.tr1 aux.tr1 aux.tr2 aux.tr3} + 3 "DROP TRIGGER tr1" + {main.tr1 main.tr2 aux.tr1 aux.tr2 aux.tr3} + 4 "DROP TRIGGER IF EXISTS tr1" + {main.tr1 main.tr2 aux.tr1 aux.tr2 aux.tr3} + + 5 "DROP TRIGGER aux.tr1" + {main.tr1 main.tr2 temp.tr1 aux.tr2 aux.tr3} + 6 "DROP TRIGGER IF EXISTS aux.tr1" + {main.tr1 main.tr2 temp.tr1 aux.tr2 aux.tr3} + + 7 "DROP TRIGGER IF EXISTS aux.xxx" + {main.tr1 main.tr2 temp.tr1 aux.tr1 aux.tr2 aux.tr3} + 8 "DROP TRIGGER IF EXISTS aux.xxx" + {main.tr1 main.tr2 temp.tr1 aux.tr1 aux.tr2 aux.tr3} +} + +# EVIDENCE-OF: R-61172-15671 The DROP TRIGGER statement removes a +# trigger created by the CREATE TRIGGER statement. +# +foreach {tn tbl droptrigger before after} { + 1 t1 "DROP TRIGGER tr1" {temp.tr1} {} + 2 t2 "DROP TRIGGER tr1" {main.tr1 main.tr2} {main.tr1 main.tr2} + 3 t3 "DROP TRIGGER tr1" {aux.tr1 aux.tr3 aux.tr2} {aux.tr1 aux.tr3 aux.tr2} + + 4 t1 "DROP TRIGGER tr2" {temp.tr1} {temp.tr1} + 5 t2 "DROP TRIGGER tr2" {main.tr1 main.tr2} {main.tr1} + 6 t3 "DROP TRIGGER tr2" {aux.tr1 aux.tr3 aux.tr2} {aux.tr1 aux.tr3 aux.tr2} + + 7 t1 "DROP TRIGGER tr3" {temp.tr1} {temp.tr1} + 8 t2 "DROP TRIGGER tr3" {main.tr1 main.tr2} {main.tr1 main.tr2} + 9 t3 "DROP TRIGGER tr3" {aux.tr1 aux.tr3 aux.tr2} {aux.tr1 aux.tr2} +} { + + do_test 2.$tn.1 { + droptrigger_reopen_db + execsql " INSERT INTO $tbl VALUES('1', '2') " + set ::triggers_fired + } $before + + do_test 2.$tn.2 { + droptrigger_reopen_db + execsql $droptrigger + execsql " INSERT INTO $tbl VALUES('1', '2') " + set ::triggers_fired + } $after +} + +# EVIDENCE-OF: R-50239-29811 Once removed, the trigger definition is no +# longer present in the sqlite_master (or sqlite_temp_master) table and +# is not fired by any subsequent INSERT, UPDATE or DELETE statements. +# +# Test cases e_droptrigger-1.* test the first part of this statement +# (that dropped triggers do not appear in the schema table), and tests +# droptrigger-2.* test that dropped triggers are not fired by INSERT +# statements. The following tests verify that they are not fired by +# UPDATE or DELETE statements. +# +foreach {tn tbl droptrigger before after} { + 1 t1 "DROP TRIGGER tr1" {temp.tr1} {} + 2 t2 "DROP TRIGGER tr1" {main.tr1 main.tr2} {main.tr1 main.tr2} + 3 t3 "DROP TRIGGER tr1" {aux.tr1 aux.tr3 aux.tr2} {aux.tr1 aux.tr3 aux.tr2} + + 4 t1 "DROP TRIGGER tr2" {temp.tr1} {temp.tr1} + 5 t2 "DROP TRIGGER tr2" {main.tr1 main.tr2} {main.tr1} + 6 t3 "DROP TRIGGER tr2" {aux.tr1 aux.tr3 aux.tr2} {aux.tr1 aux.tr3 aux.tr2} + + 7 t1 "DROP TRIGGER tr3" {temp.tr1} {temp.tr1} + 8 t2 "DROP TRIGGER tr3" {main.tr1 main.tr2} {main.tr1 main.tr2} + 9 t3 "DROP TRIGGER tr3" {aux.tr1 aux.tr3 aux.tr2} {aux.tr1 aux.tr2} +} { + + do_test 3.1.$tn.1 { + droptrigger_reopen_db UPDATE + execsql "UPDATE $tbl SET a = 'abc'" + set ::triggers_fired + } $before + + do_test 3.1.$tn.2 { + droptrigger_reopen_db UPDATE + execsql $droptrigger + execsql "UPDATE $tbl SET a = 'abc'" + set ::triggers_fired + } $after +} +foreach {tn tbl droptrigger before after} { + 1 t1 "DROP TRIGGER tr1" {temp.tr1} {} + 2 t2 "DROP TRIGGER tr1" {main.tr1 main.tr2} {main.tr1 main.tr2} + 3 t3 "DROP TRIGGER tr1" {aux.tr1 aux.tr3 aux.tr2} {aux.tr1 aux.tr3 aux.tr2} + + 4 t1 "DROP TRIGGER tr2" {temp.tr1} {temp.tr1} + 5 t2 "DROP TRIGGER tr2" {main.tr1 main.tr2} {main.tr1} + 6 t3 "DROP TRIGGER tr2" {aux.tr1 aux.tr3 aux.tr2} {aux.tr1 aux.tr3 aux.tr2} + + 7 t1 "DROP TRIGGER tr3" {temp.tr1} {temp.tr1} + 8 t2 "DROP TRIGGER tr3" {main.tr1 main.tr2} {main.tr1 main.tr2} + 9 t3 "DROP TRIGGER tr3" {aux.tr1 aux.tr3 aux.tr2} {aux.tr1 aux.tr2} +} { + + do_test 3.2.$tn.1 { + droptrigger_reopen_db DELETE + execsql "DELETE FROM $tbl" + set ::triggers_fired + } $before + + do_test 3.2.$tn.2 { + droptrigger_reopen_db DELETE + execsql $droptrigger + execsql "DELETE FROM $tbl" + set ::triggers_fired + } $after +} + +# EVIDENCE-OF: R-37808-62273 Note that triggers are automatically +# dropped when the associated table is dropped. +# +do_test 4.1 { + droptrigger_reopen_db + list_all_triggers +} {main.tr1 main.tr2 temp.tr1 aux.tr1 aux.tr2 aux.tr3} +do_test 4.2 { + droptrigger_reopen_db + execsql "DROP TABLE t1" + list_all_triggers +} {main.tr1 main.tr2 aux.tr1 aux.tr2 aux.tr3} +do_test 4.3 { + droptrigger_reopen_db + execsql "DROP TABLE t1" + list_all_triggers +} {main.tr1 main.tr2 aux.tr1 aux.tr2 aux.tr3} +do_test 4.4 { + droptrigger_reopen_db + execsql "DROP TABLE t1" + list_all_triggers +} {main.tr1 main.tr2 aux.tr1 aux.tr2 aux.tr3} + +finish_test diff --git a/test/e_dropview.test b/test/e_dropview.test new file mode 100644 index 00000000..447e5c35 --- /dev/null +++ b/test/e_dropview.test @@ -0,0 +1,192 @@ +# 2010 November 30 +# +# 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 implements tests to verify that the "testable statements" in +# the lang_dropview.html document are correct. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix e_dropview + +proc dropview_reopen_db {} { + db close + forcedelete test.db test.db2 + sqlite3 db test.db + + db eval { + ATTACH 'test.db2' AS aux; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES('a main', 'b main'); + CREATE VIEW v1 AS SELECT * FROM t1; + CREATE VIEW v2 AS SELECT * FROM t1; + + CREATE TEMP TABLE t1(a, b); + INSERT INTO temp.t1 VALUES('a temp', 'b temp'); + CREATE VIEW temp.v1 AS SELECT * FROM t1; + + CREATE TABLE aux.t1(a, b); + INSERT INTO aux.t1 VALUES('a aux', 'b aux'); + CREATE VIEW aux.v1 AS SELECT * FROM t1; + CREATE VIEW aux.v2 AS SELECT * FROM t1; + CREATE VIEW aux.v3 AS SELECT * FROM t1; + } +} + +proc list_all_views {{db db}} { + set res [list] + $db eval { PRAGMA database_list } { + set tbl "$name.sqlite_master" + if {$name == "temp"} { set tbl sqlite_temp_master } + + set sql "SELECT '$name.' || name FROM $tbl WHERE type = 'view'" + lappend res {*}[$db eval $sql] + } + set res +} + +proc list_all_data {{db db}} { + set res [list] + $db eval { PRAGMA database_list } { + set tbl "$name.sqlite_master" + if {$name == "temp"} { set tbl sqlite_temp_master } + + db eval "SELECT '$name.' || name AS x FROM $tbl WHERE type = 'table'" { + lappend res [list $x [db eval "SELECT * FROM $x"]] + } + } + set res +} + +proc do_dropview_tests {nm args} { + uplevel do_select_tests $nm $args +} + +# EVIDENCE-OF: R-21739-51207 -- syntax diagram drop-view-stmt +# +# All paths in the syntax diagram for DROP VIEW are tested by tests 1.*. +# +do_dropview_tests 1 -repair { + dropview_reopen_db +} -tclquery { + list_all_views +} { + 1 "DROP VIEW v1" {main.v1 main.v2 aux.v1 aux.v2 aux.v3} + 2 "DROP VIEW v2" {main.v1 temp.v1 aux.v1 aux.v2 aux.v3} + 3 "DROP VIEW main.v1" {main.v2 temp.v1 aux.v1 aux.v2 aux.v3} + 4 "DROP VIEW main.v2" {main.v1 temp.v1 aux.v1 aux.v2 aux.v3} + 5 "DROP VIEW IF EXISTS v1" {main.v1 main.v2 aux.v1 aux.v2 aux.v3} + 6 "DROP VIEW IF EXISTS v2" {main.v1 temp.v1 aux.v1 aux.v2 aux.v3} + 7 "DROP VIEW IF EXISTS main.v1" {main.v2 temp.v1 aux.v1 aux.v2 aux.v3} + 8 "DROP VIEW IF EXISTS main.v2" {main.v1 temp.v1 aux.v1 aux.v2 aux.v3} +} + +# EVIDENCE-OF: R-27002-52307 The DROP VIEW statement removes a view +# created by the CREATE VIEW statement. +# +dropview_reopen_db +do_execsql_test 2.1 { + CREATE VIEW "new view" AS SELECT * FROM t1 AS x, t1 AS y; + SELECT * FROM "new view"; +} {{a main} {b main} {a main} {b main}} +do_execsql_test 2.2 {; + SELECT * FROM sqlite_master WHERE name = 'new view'; +} { + view {new view} {new view} 0 + {CREATE VIEW "new view" AS SELECT * FROM t1 AS x, t1 AS y} +} +do_execsql_test 2.3 { + DROP VIEW "new view"; + SELECT * FROM sqlite_master WHERE name = 'new view'; +} {} +do_catchsql_test 2.4 { + SELECT * FROM "new view" +} {1 {no such table: new view}} + +# EVIDENCE-OF: R-00359-41639 The view definition is removed from the +# database schema, but no actual data in the underlying base tables is +# modified. +# +# For each view in the database, check that it can be queried. Then drop +# it. Check that it can no longer be queried and is no longer listed +# in any schema table. Then check that the contents of the db tables have +# not changed +# +set databasedata [list_all_data] + +do_execsql_test 3.1.0 { SELECT * FROM temp.v1 } {{a temp} {b temp}} +do_execsql_test 3.1.1 { DROP VIEW temp.v1 } {} +do_catchsql_test 3.1.2 { SELECT * FROM temp.v1 } {1 {no such table: temp.v1}} +do_test 3.1.3 { list_all_views } {main.v1 main.v2 aux.v1 aux.v2 aux.v3} +do_test 3.1.4 { list_all_data } $databasedata + +do_execsql_test 3.2.0 { SELECT * FROM v1 } {{a main} {b main}} +do_execsql_test 3.2.1 { DROP VIEW v1 } {} +do_catchsql_test 3.2.2 { SELECT * FROM main.v1 } {1 {no such table: main.v1}} +do_test 3.2.3 { list_all_views } {main.v2 aux.v1 aux.v2 aux.v3} +do_test 3.2.4 { list_all_data } $databasedata + +do_execsql_test 3.3.0 { SELECT * FROM v2 } {{a main} {b main}} +do_execsql_test 3.3.1 { DROP VIEW v2 } {} +do_catchsql_test 3.3.2 { SELECT * FROM main.v2 } {1 {no such table: main.v2}} +do_test 3.3.3 { list_all_views } {aux.v1 aux.v2 aux.v3} +do_test 3.3.4 { list_all_data } $databasedata + +do_execsql_test 3.4.0 { SELECT * FROM v1 } {{a aux} {b aux}} +do_execsql_test 3.4.1 { DROP VIEW v1 } {} +do_catchsql_test 3.4.2 { SELECT * FROM v1 } {1 {no such table: v1}} +do_test 3.4.3 { list_all_views } {aux.v2 aux.v3} +do_test 3.4.4 { list_all_data } $databasedata + +do_execsql_test 3.4.0 { SELECT * FROM aux.v2 } {{a aux} {b aux}} +do_execsql_test 3.4.1 { DROP VIEW aux.v2 } {} +do_catchsql_test 3.4.2 { SELECT * FROM aux.v2 } {1 {no such table: aux.v2}} +do_test 3.4.3 { list_all_views } {aux.v3} +do_test 3.4.4 { list_all_data } $databasedata + +do_execsql_test 3.5.0 { SELECT * FROM v3 } {{a aux} {b aux}} +do_execsql_test 3.5.1 { DROP VIEW v3 } {} +do_catchsql_test 3.5.2 { SELECT * FROM v3 } {1 {no such table: v3}} +do_test 3.5.3 { list_all_views } {} +do_test 3.5.4 { list_all_data } $databasedata + +# EVIDENCE-OF: R-25558-37487 If the specified view cannot be found and +# the IF EXISTS clause is not present, it is an error. +# +do_dropview_tests 4 -repair { + dropview_reopen_db +} -errorformat { + no such view: %s +} { + 1 "DROP VIEW xx" xx + 2 "DROP VIEW main.xx" main.xx + 3 "DROP VIEW temp.v2" temp.v2 +} + +# EVIDENCE-OF: R-07490-32536 If the specified view cannot be found and +# an IF EXISTS clause is present in the DROP VIEW statement, then the +# statement is a no-op. +# +do_dropview_tests 5 -repair { + dropview_reopen_db +} -tclquery { + list_all_views + expr {[list_all_views] == "main.v1 main.v2 temp.v1 aux.v1 aux.v2 aux.v3"} +} { + 1 "DROP VIEW IF EXISTS xx" 1 + 2 "DROP VIEW IF EXISTS main.xx" 1 + 3 "DROP VIEW IF EXISTS temp.v2" 1 +} + + + + +finish_test diff --git a/test/e_expr.test b/test/e_expr.test index a5c4457a..437e88b1 100644 --- a/test/e_expr.test +++ b/test/e_expr.test @@ -17,6 +17,17 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl source $testdir/malloc_common.tcl + +proc do_expr_test {tn expr type value} { + uplevel do_execsql_test $tn [list "SELECT typeof($expr), $expr"] [ + list [list $type $value] + ] +} + +proc do_qexpr_test {tn expr value} { + uplevel do_execsql_test $tn [list "SELECT quote($expr)"] [list $value] +} + # Set up three global variables: # # ::opname An array mapping from SQL operator to an easy to parse @@ -413,8 +424,7 @@ do_execsql_test e_expr-10.1.3 { SELECT typeof('5.1') } {text} do_execsql_test e_expr-10.1.4 { SELECT typeof(X'ABCD') } {blob} do_execsql_test e_expr-10.1.5 { SELECT typeof(NULL) } {null} -# EVIDENCE-OF: R-26921-59298 Scientific notation is supported for -# floating point literal values. +# "Scientific notation is supported for point literal values." # do_execsql_test e_expr-10.2.1 { SELECT typeof(3.4e-02) } {real} do_execsql_test e_expr-10.2.2 { SELECT typeof(3e+5) } {real} @@ -617,7 +627,7 @@ do_test e_expr-11.7.1 { sqlite3_finalize $stmt } SQLITE_OK #------------------------------------------------------------------------- # "Test" the syntax diagrams in lang_expr.html. # -# EVIDENCE-OF: R-04177-20688 -- syntax diagram signed-number +# EVIDENCE-OF: R-62067-43884 -- syntax diagram signed-number # do_execsql_test e_expr-12.1.1 { SELECT 0, +0, -0 } {0 0 0} do_execsql_test e_expr-12.1.2 { SELECT 1, +1, -1 } {1 1 -1} @@ -632,7 +642,7 @@ do_execsql_test e_expr-12.1.6 { SELECT 0.0001, +0.0001, -0.0001 } {0.0001 0.0001 -0.0001} -# EVIDENCE-OF: R-30740-26723 -- syntax diagram literal-value +# EVIDENCE-OF: R-21258-25489 -- syntax diagram literal-value # set sqlite_current_time 1 do_execsql_test e_expr-12.2.1 {SELECT 123} {123} @@ -927,8 +937,14 @@ do_execsql_test e_expr-14.4.3 { SELECT 'ac' LIKE 'aBc' } 0 # 'a' LIKE 'A' is TRUE but # 'æ' LIKE 'Æ' is FALSE. # +# The restriction to ASCII characters does not apply if the ICU +# library is compiled in. When ICU is enabled SQLite does not act +# as it does "by default". +# do_execsql_test e_expr-14.5.1 { SELECT 'A' LIKE 'a' } 1 -do_execsql_test e_expr-14.5.2 "SELECT '\u00c6' LIKE '\u00e6'" 0 +ifcapable !icu { + do_execsql_test e_expr-14.5.2 "SELECT '\u00c6' LIKE '\u00e6'" 0 +} # EVIDENCE-OF: R-56683-13731 If the optional ESCAPE clause is present, # then the expression following the ESCAPE keyword must evaluate to a @@ -975,7 +991,8 @@ proc likefunc {args} { eval lappend ::likeargs $args return 1 } -db func like likefunc +db func like -argcount 2 likefunc +db func like -argcount 3 likefunc set ::likeargs [list] do_execsql_test e_expr-15.1.1 { SELECT 'abc' LIKE 'def' } 1 do_test e_expr-15.1.2 { set likeargs } {def abc} @@ -1046,12 +1063,16 @@ sqlite3 db test.db # default and so use of the REGEXP operator will normally result in an # error message. # -do_catchsql_test e_expr-18.1.1 { - SELECT regexp('abc', 'def') -} {1 {no such function: regexp}} -do_catchsql_test e_expr-18.1.2 { - SELECT 'abc' REGEXP 'def' -} {1 {no such function: REGEXP}} +# There is a regexp function if ICU is enabled though. +# +ifcapable !icu { + do_catchsql_test e_expr-18.1.1 { + SELECT regexp('abc', 'def') + } {1 {no such function: regexp}} + do_catchsql_test e_expr-18.1.2 { + SELECT 'abc' REGEXP 'def' + } {1 {no such function: REGEXP}} +} # EVIDENCE-OF: R-33693-50180 The REGEXP operator is a special syntax for # the regexp() user function. @@ -1102,5 +1123,714 @@ do_execsql_test e_expr-19.2.3 { SELECT 'X' NOT MATCH 'Y' } 0 do_test e_expr-19.2.4 { set matchargs } {Y X} sqlite3 db test.db +#------------------------------------------------------------------------- +# Test cases for the testable statements related to the CASE expression. +# +# EVIDENCE-OF: R-15199-61389 There are two basic forms of the CASE +# expression: those with a base expression and those without. +# +do_execsql_test e_expr-20.1 { + SELECT CASE WHEN 1 THEN 'true' WHEN 0 THEN 'false' ELSE 'else' END; +} {true} +do_execsql_test e_expr-20.2 { + SELECT CASE 0 WHEN 1 THEN 'true' WHEN 0 THEN 'false' ELSE 'else' END; +} {false} + +proc var {nm} { + lappend ::varlist $nm + return [set "::$nm"] +} +db func var var + +# EVIDENCE-OF: R-30638-59954 In a CASE without a base expression, each +# WHEN expression is evaluated and the result treated as a boolean, +# starting with the leftmost and continuing to the right. +# +foreach {a b c} {0 0 0} break +set varlist [list] +do_execsql_test e_expr-21.1.1 { + SELECT CASE WHEN var('a') THEN 'A' + WHEN var('b') THEN 'B' + WHEN var('c') THEN 'C' END +} {{}} +do_test e_expr-21.1.2 { set varlist } {a b c} +set varlist [list] +do_execsql_test e_expr-21.1.3 { + SELECT CASE WHEN var('c') THEN 'C' + WHEN var('b') THEN 'B' + WHEN var('a') THEN 'A' + ELSE 'no result' + END +} {{no result}} +do_test e_expr-21.1.4 { set varlist } {c b a} + +# EVIDENCE-OF: R-39009-25596 The result of the CASE expression is the +# evaluation of the THEN expression that corresponds to the first WHEN +# expression that evaluates to true. +# +foreach {a b c} {0 1 0} break +do_execsql_test e_expr-21.2.1 { + SELECT CASE WHEN var('a') THEN 'A' + WHEN var('b') THEN 'B' + WHEN var('c') THEN 'C' + ELSE 'no result' + END +} {B} +foreach {a b c} {0 1 1} break +do_execsql_test e_expr-21.2.2 { + SELECT CASE WHEN var('a') THEN 'A' + WHEN var('b') THEN 'B' + WHEN var('c') THEN 'C' + ELSE 'no result' + END +} {B} +foreach {a b c} {0 0 1} break +do_execsql_test e_expr-21.2.3 { + SELECT CASE WHEN var('a') THEN 'A' + WHEN var('b') THEN 'B' + WHEN var('c') THEN 'C' + ELSE 'no result' + END +} {C} + +# EVIDENCE-OF: R-24227-04807 Or, if none of the WHEN expressions +# evaluate to true, the result of evaluating the ELSE expression, if +# any. +# +foreach {a b c} {0 0 0} break +do_execsql_test e_expr-21.3.1 { + SELECT CASE WHEN var('a') THEN 'A' + WHEN var('b') THEN 'B' + WHEN var('c') THEN 'C' + ELSE 'no result' + END +} {{no result}} + +# EVIDENCE-OF: R-14168-07579 If there is no ELSE expression and none of +# the WHEN expressions are true, then the overall result is NULL. +# +db nullvalue null +do_execsql_test e_expr-21.3.2 { + SELECT CASE WHEN var('a') THEN 'A' + WHEN var('b') THEN 'B' + WHEN var('c') THEN 'C' + END +} {null} +db nullvalue {} + +# EVIDENCE-OF: R-13943-13592 A NULL result is considered untrue when +# evaluating WHEN terms. +# +do_execsql_test e_expr-21.4.1 { + SELECT CASE WHEN NULL THEN 'A' WHEN 1 THEN 'B' END +} {B} +do_execsql_test e_expr-21.4.2 { + SELECT CASE WHEN 0 THEN 'A' WHEN NULL THEN 'B' ELSE 'C' END +} {C} + +# EVIDENCE-OF: R-38620-19499 In a CASE with a base expression, the base +# expression is evaluated just once and the result is compared against +# the evaluation of each WHEN expression from left to right. +# +# Note: This test case tests the "evaluated just once" part of the above +# statement. Tests associated with the next two statements test that the +# comparisons take place. +# +foreach {a b c} [list [expr 3] [expr 4] [expr 5]] break +set ::varlist [list] +do_execsql_test e_expr-22.1.1 { + SELECT CASE var('a') WHEN 1 THEN 'A' WHEN 2 THEN 'B' WHEN 3 THEN 'C' END +} {C} +do_test e_expr-22.1.2 { set ::varlist } {a} + +# EVIDENCE-OF: R-07667-49537 The result of the CASE expression is the +# evaluation of the THEN expression that corresponds to the first WHEN +# expression for which the comparison is true. +# +do_execsql_test e_expr-22.2.1 { + SELECT CASE 23 WHEN 1 THEN 'A' WHEN 23 THEN 'B' WHEN 23 THEN 'C' END +} {B} +do_execsql_test e_expr-22.2.2 { + SELECT CASE 1 WHEN 1 THEN 'A' WHEN 23 THEN 'B' WHEN 23 THEN 'C' END +} {A} + +# EVIDENCE-OF: R-47543-32145 Or, if none of the WHEN expressions +# evaluate to a value equal to the base expression, the result of +# evaluating the ELSE expression, if any. +# +do_execsql_test e_expr-22.3.1 { + SELECT CASE 24 WHEN 1 THEN 'A' WHEN 23 THEN 'B' WHEN 23 THEN 'C' ELSE 'D' END +} {D} + +# EVIDENCE-OF: R-54721-48557 If there is no ELSE expression and none of +# the WHEN expressions produce a result equal to the base expression, +# the overall result is NULL. +# +do_execsql_test e_expr-22.4.1 { + SELECT CASE 24 WHEN 1 THEN 'A' WHEN 23 THEN 'B' WHEN 23 THEN 'C' END +} {{}} +db nullvalue null +do_execsql_test e_expr-22.4.2 { + SELECT CASE 24 WHEN 1 THEN 'A' WHEN 23 THEN 'B' WHEN 23 THEN 'C' END +} {null} +db nullvalue {} + +# EVIDENCE-OF: R-11479-62774 When comparing a base expression against a +# WHEN expression, the same collating sequence, affinity, and +# NULL-handling rules apply as if the base expression and WHEN +# expression are respectively the left- and right-hand operands of an = +# operator. +# +proc rev {str} { + set ret "" + set chars [split $str] + for {set i [expr [llength $chars]-1]} {$i>=0} {incr i -1} { + append ret [lindex $chars $i] + } + set ret +} +proc reverse {lhs rhs} { + string compare [rev $lhs] [ref $rhs] +} +db collate reverse reverse +do_execsql_test e_expr-23.1.1 { + CREATE TABLE t1( + a TEXT COLLATE NOCASE, + b COLLATE REVERSE, + c INTEGER, + d BLOB + ); + INSERT INTO t1 VALUES('abc', 'cba', 55, 34.5); +} {} +do_execsql_test e_expr-23.1.2 { + SELECT CASE a WHEN 'xyz' THEN 'A' WHEN 'AbC' THEN 'B' END FROM t1 +} {B} +do_execsql_test e_expr-23.1.3 { + SELECT CASE 'AbC' WHEN 'abc' THEN 'A' WHEN a THEN 'B' END FROM t1 +} {B} +do_execsql_test e_expr-23.1.4 { + SELECT CASE a WHEN b THEN 'A' ELSE 'B' END FROM t1 +} {B} +do_execsql_test e_expr-23.1.5 { + SELECT CASE b WHEN a THEN 'A' ELSE 'B' END FROM t1 +} {A} +do_execsql_test e_expr-23.1.6 { + SELECT CASE 55 WHEN '55' THEN 'A' ELSE 'B' END +} {B} +do_execsql_test e_expr-23.1.7 { + SELECT CASE c WHEN '55' THEN 'A' ELSE 'B' END FROM t1 +} {A} +do_execsql_test e_expr-23.1.8 { + SELECT CASE '34.5' WHEN d THEN 'A' ELSE 'B' END FROM t1 +} {B} +do_execsql_test e_expr-23.1.9 { + SELECT CASE NULL WHEN NULL THEN 'A' ELSE 'B' END +} {B} + +# EVIDENCE-OF: R-37304-39405 If the base expression is NULL then the +# result of the CASE is always the result of evaluating the ELSE +# expression if it exists, or NULL if it does not. +# +do_execsql_test e_expr-24.1.1 { + SELECT CASE NULL WHEN 'abc' THEN 'A' WHEN 'def' THEN 'B' END; +} {{}} +do_execsql_test e_expr-24.1.2 { + SELECT CASE NULL WHEN 'abc' THEN 'A' WHEN 'def' THEN 'B' ELSE 'C' END; +} {C} + +# EVIDENCE-OF: R-56280-17369 Both forms of the CASE expression use lazy, +# or short-circuit, evaluation. +# +set varlist [list] +foreach {a b c} {0 1 0} break +do_execsql_test e_expr-25.1.1 { + SELECT CASE WHEN var('a') THEN 'A' + WHEN var('b') THEN 'B' + WHEN var('c') THEN 'C' + END +} {B} +do_test e_expr-25.1.2 { set ::varlist } {a b} +set varlist [list] +do_execsql_test e_expr-25.1.3 { + SELECT CASE '0' WHEN var('a') THEN 'A' + WHEN var('b') THEN 'B' + WHEN var('c') THEN 'C' + END +} {A} +do_test e_expr-25.1.4 { set ::varlist } {a} + +# EVIDENCE-OF: R-34773-62253 The only difference between the following +# two CASE expressions is that the x expression is evaluated exactly +# once in the first example but might be evaluated multiple times in the +# second: CASE x WHEN w1 THEN r1 WHEN w2 THEN r2 ELSE r3 END CASE WHEN +# x=w1 THEN r1 WHEN x=w2 THEN r2 ELSE r3 END +# +proc ceval {x} { + incr ::evalcount + return $x +} +db func ceval ceval +set ::evalcount 0 + +do_execsql_test e_expr-26.1.1 { + CREATE TABLE t2(x, w1, r1, w2, r2, r3); + INSERT INTO t2 VALUES(1, 1, 'R1', 2, 'R2', 'R3'); + INSERT INTO t2 VALUES(2, 1, 'R1', 2, 'R2', 'R3'); + INSERT INTO t2 VALUES(3, 1, 'R1', 2, 'R2', 'R3'); +} {} +do_execsql_test e_expr-26.1.2 { + SELECT CASE x WHEN w1 THEN r1 WHEN w2 THEN r2 ELSE r3 END FROM t2 +} {R1 R2 R3} +do_execsql_test e_expr-26.1.3 { + SELECT CASE WHEN x=w1 THEN r1 WHEN x=w2 THEN r2 ELSE r3 END FROM t2 +} {R1 R2 R3} + +do_execsql_test e_expr-26.1.4 { + SELECT CASE ceval(x) WHEN w1 THEN r1 WHEN w2 THEN r2 ELSE r3 END FROM t2 +} {R1 R2 R3} +do_test e_expr-26.1.5 { set ::evalcount } {3} +set ::evalcount 0 +do_execsql_test e_expr-26.1.6 { + SELECT CASE + WHEN ceval(x)=w1 THEN r1 + WHEN ceval(x)=w2 THEN r2 + ELSE r3 END + FROM t2 +} {R1 R2 R3} +do_test e_expr-26.1.6 { set ::evalcount } {5} + + +#------------------------------------------------------------------------- +# Test statements related to CAST expressions. +# +# EVIDENCE-OF: R-65079-31758 Application of a CAST expression is +# different to application of a column affinity, as with a CAST +# expression the storage class conversion is forced even if it is lossy +# and irrreversible. +# +do_execsql_test e_expr-27.1.1 { + CREATE TABLE t3(a TEXT, b REAL, c INTEGER); + INSERT INTO t3 VALUES(X'555655', '1.23abc', 4.5); + SELECT typeof(a), a, typeof(b), b, typeof(c), c FROM t3; +} {blob UVU text 1.23abc real 4.5} +do_execsql_test e_expr-27.1.2 { + SELECT + typeof(CAST(X'555655' as TEXT)), CAST(X'555655' as TEXT), + typeof(CAST('1.23abc' as REAL)), CAST('1.23abc' as REAL), + typeof(CAST(4.5 as INTEGER)), CAST(4.5 as INTEGER) +} {text UVU real 1.23 integer 4} + +# EVIDENCE-OF: R-27225-65050 If the value of is NULL, then +# the result of the CAST expression is also NULL. +# +do_expr_test e_expr-27.2.1 { CAST(NULL AS integer) } null {} +do_expr_test e_expr-27.2.2 { CAST(NULL AS text) } null {} +do_expr_test e_expr-27.2.3 { CAST(NULL AS blob) } null {} +do_expr_test e_expr-27.2.4 { CAST(NULL AS number) } null {} + +# EVIDENCE-OF: R-31076-23575 Casting a value to a with +# no affinity causes the value to be converted into a BLOB. +# +do_expr_test e_expr-27.3.1 { CAST('abc' AS blob) } blob abc +do_expr_test e_expr-27.3.2 { CAST('def' AS shobblob_x) } blob def +do_expr_test e_expr-27.3.3 { CAST('ghi' AS abbLOb10) } blob ghi + +# EVIDENCE-OF: R-22956-37754 Casting to a BLOB consists of first casting +# the value to TEXT in the encoding of the database connection, then +# interpreting the resulting byte sequence as a BLOB instead of as TEXT. +# +do_qexpr_test e_expr-27.4.1 { CAST('ghi' AS blob) } X'676869' +do_qexpr_test e_expr-27.4.2 { CAST(456 AS blob) } X'343536' +do_qexpr_test e_expr-27.4.3 { CAST(1.78 AS blob) } X'312E3738' +rename db db2 +sqlite3 db :memory: +db eval { PRAGMA encoding = 'utf-16le' } +do_qexpr_test e_expr-27.4.4 { CAST('ghi' AS blob) } X'670068006900' +do_qexpr_test e_expr-27.4.5 { CAST(456 AS blob) } X'340035003600' +do_qexpr_test e_expr-27.4.6 { CAST(1.78 AS blob) } X'31002E0037003800' +db close +sqlite3 db :memory: +db eval { PRAGMA encoding = 'utf-16be' } +do_qexpr_test e_expr-27.4.7 { CAST('ghi' AS blob) } X'006700680069' +do_qexpr_test e_expr-27.4.8 { CAST(456 AS blob) } X'003400350036' +do_qexpr_test e_expr-27.4.9 { CAST(1.78 AS blob) } X'0031002E00370038' +db close +rename db2 db + +# EVIDENCE-OF: R-04207-37981 To cast a BLOB value to TEXT, the sequence +# of bytes that make up the BLOB is interpreted as text encoded using +# the database encoding. +# +do_expr_test e_expr-28.1.1 { CAST (X'676869' AS text) } text ghi +do_expr_test e_expr-28.1.2 { CAST (X'670068006900' AS text) } text g +rename db db2 +sqlite3 db :memory: +db eval { PRAGMA encoding = 'utf-16le' } +do_expr_test e_expr-28.1.3 { CAST (X'676869' AS text) == 'ghi' } integer 0 +do_expr_test e_expr-28.1.4 { CAST (X'670068006900' AS text) } text ghi +db close +rename db2 db + +# EVIDENCE-OF: R-22235-47006 Casting an INTEGER or REAL value into TEXT +# renders the value as if via sqlite3_snprintf() except that the +# resulting TEXT uses the encoding of the database connection. +# +do_expr_test e_expr-28.2.1 { CAST (1 AS text) } text 1 +do_expr_test e_expr-28.2.2 { CAST (45 AS text) } text 45 +do_expr_test e_expr-28.2.3 { CAST (-45 AS text) } text -45 +do_expr_test e_expr-28.2.4 { CAST (8.8 AS text) } text 8.8 +do_expr_test e_expr-28.2.5 { CAST (2.3e+5 AS text) } text 230000.0 +do_expr_test e_expr-28.2.6 { CAST (-2.3e-5 AS text) } text -2.3e-05 +do_expr_test e_expr-28.2.7 { CAST (0.0 AS text) } text 0.0 +do_expr_test e_expr-28.2.7 { CAST (0 AS text) } text 0 + +# EVIDENCE-OF: R-26346-36443 When casting a BLOB value to a REAL, the +# value is first converted to TEXT. +# +do_expr_test e_expr-29.1.1 { CAST (X'312E3233' AS REAL) } real 1.23 +do_expr_test e_expr-29.1.2 { CAST (X'3233302E30' AS REAL) } real 230.0 +do_expr_test e_expr-29.1.3 { CAST (X'2D392E3837' AS REAL) } real -9.87 +do_expr_test e_expr-29.1.4 { CAST (X'302E30303031' AS REAL) } real 0.0001 +rename db db2 +sqlite3 db :memory: +db eval { PRAGMA encoding = 'utf-16le' } +do_expr_test e_expr-29.1.5 { + CAST (X'31002E0032003300' AS REAL) } real 1.23 +do_expr_test e_expr-29.1.6 { + CAST (X'3200330030002E003000' AS REAL) } real 230.0 +do_expr_test e_expr-29.1.7 { + CAST (X'2D0039002E0038003700' AS REAL) } real -9.87 +do_expr_test e_expr-29.1.8 { + CAST (X'30002E003000300030003100' AS REAL) } real 0.0001 +db close +rename db2 db + +# EVIDENCE-OF: R-54898-34554 When casting a TEXT value to REAL, the +# longest possible prefix of the value that can be interpreted as a real +# number is extracted from the TEXT value and the remainder ignored. +# +do_expr_test e_expr-29.2.1 { CAST('1.23abcd' AS REAL) } real 1.23 +do_expr_test e_expr-29.2.2 { CAST('1.45.23abcd' AS REAL) } real 1.45 +do_expr_test e_expr-29.2.3 { CAST('-2.12e-01ABC' AS REAL) } real -0.212 +do_expr_test e_expr-29.2.4 { CAST('1 2 3 4' AS REAL) } real 1.0 + +# EVIDENCE-OF: R-11321-47427 Any leading spaces in the TEXT value are +# ignored when converging from TEXT to REAL. +# +do_expr_test e_expr-29.3.1 { CAST(' 1.23abcd' AS REAL) } real 1.23 +do_expr_test e_expr-29.3.2 { CAST(' 1.45.23abcd' AS REAL) } real 1.45 +do_expr_test e_expr-29.3.3 { CAST(' -2.12e-01ABC' AS REAL) } real -0.212 +do_expr_test e_expr-29.3.4 { CAST(' 1 2 3 4' AS REAL) } real 1.0 + +# EVIDENCE-OF: R-22662-28218 If there is no prefix that can be +# interpreted as a real number, the result of the conversion is 0.0. +# +do_expr_test e_expr-29.4.1 { CAST('' AS REAL) } real 0.0 +do_expr_test e_expr-29.4.2 { CAST('not a number' AS REAL) } real 0.0 +do_expr_test e_expr-29.4.3 { CAST('XXI' AS REAL) } real 0.0 + +# EVIDENCE-OF: R-21829-14563 When casting a BLOB value to INTEGER, the +# value is first converted to TEXT. +# +do_expr_test e_expr-30.1.1 { CAST(X'313233' AS INTEGER) } integer 123 +do_expr_test e_expr-30.1.2 { CAST(X'2D363738' AS INTEGER) } integer -678 +do_expr_test e_expr-30.1.3 { + CAST(X'31303030303030' AS INTEGER) +} integer 1000000 +do_expr_test e_expr-30.1.4 { + CAST(X'2D31313235383939393036383432363234' AS INTEGER) +} integer -1125899906842624 + +rename db db2 +sqlite3 db :memory: +execsql { PRAGMA encoding = 'utf-16be' } +do_expr_test e_expr-30.1.5 { CAST(X'003100320033' AS INTEGER) } integer 123 +do_expr_test e_expr-30.1.6 { CAST(X'002D003600370038' AS INTEGER) } integer -678 +do_expr_test e_expr-30.1.7 { + CAST(X'0031003000300030003000300030' AS INTEGER) +} integer 1000000 +do_expr_test e_expr-30.1.8 { + CAST(X'002D0031003100320035003800390039003900300036003800340032003600320034' AS INTEGER) +} integer -1125899906842624 +db close +rename db2 db + +# EVIDENCE-OF: R-47612-45842 When casting a TEXT value to INTEGER, the +# longest possible prefix of the value that can be interpreted as an +# integer number is extracted from the TEXT value and the remainder +# ignored. +# +do_expr_test e_expr-30.2.1 { CAST('123abcd' AS INT) } integer 123 +do_expr_test e_expr-30.2.2 { CAST('14523abcd' AS INT) } integer 14523 +do_expr_test e_expr-30.2.3 { CAST('-2.12e-01ABC' AS INT) } integer -2 +do_expr_test e_expr-30.2.4 { CAST('1 2 3 4' AS INT) } integer 1 + +# EVIDENCE-OF: R-34400-33772 Any leading spaces in the TEXT value when +# converting from TEXT to INTEGER are ignored. +# +do_expr_test e_expr-30.3.1 { CAST(' 123abcd' AS INT) } integer 123 +do_expr_test e_expr-30.3.2 { CAST(' 14523abcd' AS INT) } integer 14523 +do_expr_test e_expr-30.3.3 { CAST(' -2.12e-01ABC' AS INT) } integer -2 +do_expr_test e_expr-30.3.4 { CAST(' 1 2 3 4' AS INT) } integer 1 + +# EVIDENCE-OF: R-43164-44276 If there is no prefix that can be +# interpreted as an integer number, the result of the conversion is 0. +# +do_expr_test e_expr-30.4.1 { CAST('' AS INTEGER) } integer 0 +do_expr_test e_expr-30.4.2 { CAST('not a number' AS INTEGER) } integer 0 +do_expr_test e_expr-30.4.3 { CAST('XXI' AS INTEGER) } integer 0 + +# EVIDENCE-OF: R-00741-38776 A cast of a REAL value into an INTEGER will +# truncate the fractional part of the REAL. +# +do_expr_test e_expr-31.1.1 { CAST(3.14159 AS INTEGER) } integer 3 +do_expr_test e_expr-31.1.2 { CAST(1.99999 AS INTEGER) } integer 1 +do_expr_test e_expr-31.1.3 { CAST(-1.99999 AS INTEGER) } integer -1 +do_expr_test e_expr-31.1.4 { CAST(-0.99999 AS INTEGER) } integer 0 + +# EVIDENCE-OF: R-06126-36021 If an REAL is too large to be represented +# as an INTEGER then the result of the cast is the largest negative +# integer: -9223372036854775808. +# +do_expr_test e_expr-31.2.1 { CAST(2e+50 AS INT) } integer -9223372036854775808 +do_expr_test e_expr-31.2.2 { CAST(-2e+50 AS INT) } integer -9223372036854775808 +do_expr_test e_expr-31.2.3 { + CAST(-9223372036854775809.0 AS INT) +} integer -9223372036854775808 +do_expr_test e_expr-31.2.4 { + CAST(9223372036854775809.0 AS INT) +} integer -9223372036854775808 + + +# EVIDENCE-OF: R-09295-61337 Casting a TEXT or BLOB value into NUMERIC +# first does a forced conversion into REAL but then further converts the +# result into INTEGER if and only if the conversion from REAL to INTEGER +# is lossless and reversible. +# +do_expr_test e_expr-32.1.1 { CAST('45' AS NUMERIC) } integer 45 +do_expr_test e_expr-32.1.2 { CAST('45.0' AS NUMERIC) } integer 45 +do_expr_test e_expr-32.1.3 { CAST('45.2' AS NUMERIC) } real 45.2 +do_expr_test e_expr-32.1.4 { CAST('11abc' AS NUMERIC) } integer 11 +do_expr_test e_expr-32.1.5 { CAST('11.1abc' AS NUMERIC) } real 11.1 + +# EVIDENCE-OF: R-30347-18702 Casting a REAL or INTEGER value to NUMERIC +# is a no-op, even if a real value could be losslessly converted to an +# integer. +# +do_expr_test e_expr-32.2.1 { CAST(13.0 AS NUMERIC) } real 13.0 +do_expr_test e_expr-32.2.2 { CAST(13.5 AS NUMERIC) } real 13.5 + +do_expr_test e_expr-32.2.3 { + CAST(-9223372036854775808 AS NUMERIC) +} integer -9223372036854775808 +do_expr_test e_expr-32.2.4 { + CAST(9223372036854775807 AS NUMERIC) +} integer 9223372036854775807 + +# EVIDENCE-OF: R-64550-29191 Note that the result from casting any +# non-BLOB value into a BLOB and the result from casting any BLOB value +# into a non-BLOB value may be different depending on whether the +# database encoding is UTF-8, UTF-16be, or UTF-16le. +# +sqlite3 db1 :memory: ; db1 eval { PRAGMA encoding = 'utf-8' } +sqlite3 db2 :memory: ; db2 eval { PRAGMA encoding = 'utf-16le' } +sqlite3 db3 :memory: ; db3 eval { PRAGMA encoding = 'utf-16be' } +foreach {tn castexpr differs} { + 1 { CAST(123 AS BLOB) } 1 + 2 { CAST('' AS BLOB) } 0 + 3 { CAST('abcd' AS BLOB) } 1 + + 4 { CAST(X'abcd' AS TEXT) } 1 + 5 { CAST(X'' AS TEXT) } 0 +} { + set r1 [db1 eval "SELECT typeof($castexpr), quote($castexpr)"] + set r2 [db2 eval "SELECT typeof($castexpr), quote($castexpr)"] + set r3 [db3 eval "SELECT typeof($castexpr), quote($castexpr)"] + + if {$differs} { + set res [expr {$r1!=$r2 && $r2!=$r3}] + } else { + set res [expr {$r1==$r2 && $r2==$r3}] + } + + do_test e_expr-33.1.$tn {set res} 1 +} +db1 close +db2 close +db3 close + +#------------------------------------------------------------------------- +# Test statements related to the EXISTS and NOT EXISTS operators. +# +catch { db close } +file delete -force test.db +sqlite3 db test.db + +do_execsql_test e_expr-34.1 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(NULL, 2); + INSERT INTO t1 VALUES(1, NULL); + INSERT INTO t1 VALUES(NULL, NULL); +} {} + +# EVIDENCE-OF: R-25588-27181 The EXISTS operator always evaluates to one +# of the integer values 0 and 1. +# +# This statement is not tested by itself. Instead, all e_expr-34.* tests +# following this point explicitly test that specific invocations of EXISTS +# return either integer 0 or integer 1. +# + +# EVIDENCE-OF: R-58553-63740 If executing the SELECT statement specified +# as the right-hand operand of the EXISTS operator would return one or +# more rows, then the EXISTS operator evaluates to 1. +# +foreach {tn expr} { + 1 { EXISTS ( SELECT a FROM t1 ) } + 2 { EXISTS ( SELECT b FROM t1 ) } + 3 { EXISTS ( SELECT 24 ) } + 4 { EXISTS ( SELECT NULL ) } + 5 { EXISTS ( SELECT a FROM t1 WHERE a IS NULL ) } +} { + do_expr_test e_expr-34.2.$tn $expr integer 1 +} + +# EVIDENCE-OF: R-19673-40972 If executing the SELECT would return no +# rows at all, then the EXISTS operator evaluates to 0. +# +foreach {tn expr} { + 1 { EXISTS ( SELECT a FROM t1 WHERE 0) } + 2 { EXISTS ( SELECT b FROM t1 WHERE a = 5) } + 3 { EXISTS ( SELECT 24 WHERE 0) } + 4 { EXISTS ( SELECT NULL WHERE 1=2) } +} { + do_expr_test e_expr-34.3.$tn $expr integer 0 +} + +# EVIDENCE-OF: R-35109-49139 The number of columns in each row returned +# by the SELECT statement (if any) and the specific values returned have +# no effect on the results of the EXISTS operator. +# +foreach {tn expr res} { + 1 { EXISTS ( SELECT * FROM t1 ) } 1 + 2 { EXISTS ( SELECT *, *, * FROM t1 ) } 1 + 3 { EXISTS ( SELECT 24, 25 ) } 1 + 4 { EXISTS ( SELECT NULL, NULL, NULL ) } 1 + 5 { EXISTS ( SELECT a,b,a||b FROM t1 WHERE a IS NULL ) } 1 + + 6 { EXISTS ( SELECT a, a FROM t1 WHERE 0) } 0 + 7 { EXISTS ( SELECT b, b, a FROM t1 WHERE a = 5) } 0 + 8 { EXISTS ( SELECT 24, 46, 89 WHERE 0) } 0 + 9 { EXISTS ( SELECT NULL, NULL WHERE 1=2) } 0 +} { + do_expr_test e_expr-34.4.$tn $expr integer $res +} + +# EVIDENCE-OF: R-10645-12439 In particular, rows containing NULL values +# are not handled any differently from rows without NULL values. +# +foreach {tn e1 e2} { + 1 { EXISTS (SELECT 'not null') } { EXISTS (SELECT NULL) } + 2 { EXISTS (SELECT NULL FROM t1) } { EXISTS (SELECT 'bread' FROM t1) } +} { + set res [db one "SELECT $e1"] + do_expr_test e_expr-34.5.${tn}a $e1 integer $res + do_expr_test e_expr-34.5.${tn}b $e2 integer $res +} + +#------------------------------------------------------------------------- +# Test statements related to scalar sub-queries. +# + +catch { db close } +file delete -force test.db +sqlite3 db test.db +do_test e_expr-35.0 { + execsql { + CREATE TABLE t2(a, b); + INSERT INTO t2 VALUES('one', 'two'); + INSERT INTO t2 VALUES('three', NULL); + INSERT INTO t2 VALUES(4, 5.0); + } +} {} + +# EVIDENCE-OF: R-00980-39256 A SELECT statement enclosed in parentheses +# may appear as a scalar quantity. +# +# EVIDENCE-OF: R-56294-03966 All types of SELECT statement, including +# aggregate and compound SELECT queries (queries with keywords like +# UNION or EXCEPT) are allowed as scalar subqueries. +# +do_expr_test e_expr-35.1.1 { (SELECT 35) } integer 35 +do_expr_test e_expr-35.1.2 { (SELECT NULL) } null {} + +do_expr_test e_expr-35.1.3 { (SELECT count(*) FROM t2) } integer 3 +do_expr_test e_expr-35.1.4 { (SELECT 4 FROM t2) } integer 4 + +do_expr_test e_expr-35.1.5 { + (SELECT b FROM t2 UNION SELECT a+1 FROM t2) +} null {} +do_expr_test e_expr-35.1.6 { + (SELECT a FROM t2 UNION SELECT COALESCE(b, 55) FROM t2 ORDER BY 1) +} integer 4 + +# EVIDENCE-OF: R-46899-53765 A SELECT used as a scalar quantity must +# return a result set with a single column. +# +# The following block tests that errors are returned in a bunch of cases +# where a subquery returns more than one column. +# +set M {only a single result allowed for a SELECT that is part of an expression} +foreach {tn sql} { + 1 { SELECT (SELECT * FROM t2 UNION SELECT a+1, b+1 FROM t2) } + 2 { SELECT (SELECT * FROM t2 UNION SELECT a+1, b+1 FROM t2 ORDER BY 1) } + 3 { SELECT (SELECT 1, 2) } + 4 { SELECT (SELECT NULL, NULL, NULL) } + 5 { SELECT (SELECT * FROM t2) } + 6 { SELECT (SELECT * FROM (SELECT 1, 2, 3)) } +} { + do_catchsql_test e_expr-35.2.$tn $sql [list 1 $M] +} + +# EVIDENCE-OF: R-35764-28041 The result of the expression is the value +# of the only column in the first row returned by the SELECT statement. +# +# EVIDENCE-OF: R-41898-06686 If the SELECT yields more than one result +# row, all rows after the first are ignored. +# +do_execsql_test e_expr-36.3.1 { + CREATE TABLE t4(x, y); + INSERT INTO t4 VALUES(1, 'one'); + INSERT INTO t4 VALUES(2, 'two'); + INSERT INTO t4 VALUES(3, 'three'); +} {} + +foreach {tn expr restype resval} { + 2 { ( SELECT x FROM t4 ORDER BY x ) } integer 1 + 3 { ( SELECT x FROM t4 ORDER BY y ) } integer 1 + 4 { ( SELECT x FROM t4 ORDER BY x DESC ) } integer 3 + 5 { ( SELECT x FROM t4 ORDER BY y DESC ) } integer 2 + 6 { ( SELECT y FROM t4 ORDER BY y DESC ) } text two + + 7 { ( SELECT sum(x) FROM t4 ) } integer 6 + 8 { ( SELECT group_concat(y,'') FROM t4 ) } text onetwothree + 9 { ( SELECT max(x) FROM t4 WHERE y LIKE '___') } integer 2 + +} { + do_expr_test e_expr-36.3.$tn $expr $restype $resval +} + +# EVIDENCE-OF: R-25492-41572 If the SELECT yields no rows, then the +# value of the expression is NULL. +# +foreach {tn expr} { + 1 { ( SELECT x FROM t4 WHERE x>3 ORDER BY x ) } + 2 { ( SELECT x FROM t4 WHERE y<'one' ORDER BY y ) } +} { + do_expr_test e_expr-36.4.$tn $expr null {} +} + finish_test + diff --git a/test/e_fkey.test b/test/e_fkey.test index cc87f359..ae789d50 100644 --- a/test/e_fkey.test +++ b/test/e_fkey.test @@ -968,19 +968,21 @@ do_test e_fkey-25.1 { ); } } {} -do_test e_fkey-25.2 { - execsql { - PRAGMA foreign_keys = OFF; - EXPLAIN QUERY PLAN DELETE FROM artist WHERE 1; - EXPLAIN QUERY PLAN SELECT rowid FROM track WHERE trackartist = ?; - } -} {0 0 {TABLE artist} 0 0 {TABLE track}} -do_test e_fkey-25.3 { - execsql { - PRAGMA foreign_keys = ON; - EXPLAIN QUERY PLAN DELETE FROM artist WHERE 1; - } -} {0 0 {TABLE artist} 0 0 {TABLE track}} +do_execsql_test e_fkey-25.2 { + PRAGMA foreign_keys = OFF; + EXPLAIN QUERY PLAN DELETE FROM artist WHERE 1; + EXPLAIN QUERY PLAN SELECT rowid FROM track WHERE trackartist = ?; +} { + 0 0 0 {SCAN TABLE artist (~1000000 rows)} + 0 0 0 {SCAN TABLE track (~100000 rows)} +} +do_execsql_test e_fkey-25.3 { + PRAGMA foreign_keys = ON; + EXPLAIN QUERY PLAN DELETE FROM artist WHERE 1; +} { + 0 0 0 {SCAN TABLE artist (~1000000 rows)} + 0 0 0 {SCAN TABLE track (~100000 rows)} +} do_test e_fkey-25.4 { execsql { INSERT INTO artist VALUES(5, 'artist 5'); @@ -1093,19 +1095,19 @@ do_test e_fkey-27.1 { do_test e_fkey-27.2 { eqp { INSERT INTO artist VALUES(?, ?) } } {} -do_test e_fkey-27.3 { - eqp { UPDATE artist SET artistid = ?, artistname = ? } -} [list \ - 0 0 {TABLE artist} \ - 0 0 {TABLE track WITH INDEX trackindex} \ - 0 0 {TABLE track WITH INDEX trackindex} -] -do_test e_fkey-27.4 { - eqp { DELETE FROM artist } -} [list \ - 0 0 {TABLE artist} \ - 0 0 {TABLE track WITH INDEX trackindex} -] +do_execsql_test e_fkey-27.3 { + EXPLAIN QUERY PLAN UPDATE artist SET artistid = ?, artistname = ? +} { + 0 0 0 {SCAN TABLE artist (~1000000 rows)} + 0 0 0 {SEARCH TABLE track USING COVERING INDEX trackindex (trackartist=?) (~10 rows)} + 0 0 0 {SEARCH TABLE track USING COVERING INDEX trackindex (trackartist=?) (~10 rows)} +} +do_execsql_test e_fkey-27.4 { + EXPLAIN QUERY PLAN DELETE FROM artist +} { + 0 0 0 {SCAN TABLE artist (~1000000 rows)} + 0 0 0 {SEARCH TABLE track USING COVERING INDEX trackindex (trackartist=?) (~10 rows)} +} ########################################################################### diff --git a/test/e_insert.test b/test/e_insert.test new file mode 100644 index 00000000..2192f2e9 --- /dev/null +++ b/test/e_insert.test @@ -0,0 +1,406 @@ +# 2010 September 18 +# +# 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. +# +#*********************************************************************** +# +# The majority of this file implements tests to verify that the "testable +# statements" in the lang_insert.html document are correct. +# +# Also, it contains tests to verify the statements in (the very short) +# lang_replace.html. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Organization of tests: +# +# e_insert-0.*: Test the syntax diagram. +# +# e_insert-1.*: Test statements of the form "INSERT ... VALUES(...)". +# +# e_insert-2.*: Test statements of the form "INSERT ... SELECT ...". +# +# e_insert-3.*: Test statements of the form "INSERT ... DEFAULT VALUES". +# +# e_insert-4.*: Test statements regarding the conflict clause. +# +# e_insert-5.*: Test that the qualified table name and "DEFAULT VALUES" +# syntaxes do not work in trigger bodies. +# + +do_execsql_test e_insert-0.0 { + CREATE TABLE a1(a, b); + CREATE TABLE a2(a, b, c DEFAULT 'xyz'); + CREATE TABLE a3(x DEFAULT 1.0, y DEFAULT 'string', z); + CREATE TABLE a4(c UNIQUE, d); +} {} + +proc do_insert_tests {args} { + uplevel do_select_tests $args +} + +# EVIDENCE-OF: R-41448-54465 -- syntax diagram insert-stmt +# +do_insert_tests e_insert-0 { + 1 "INSERT INTO a1 DEFAULT VALUES" {} + 2 "INSERT INTO main.a1 DEFAULT VALUES" {} + 3 "INSERT OR ROLLBACK INTO main.a1 DEFAULT VALUES" {} + 4 "INSERT OR ROLLBACK INTO a1 DEFAULT VALUES" {} + 5 "INSERT OR ABORT INTO main.a1 DEFAULT VALUES" {} + 6 "INSERT OR ABORT INTO a1 DEFAULT VALUES" {} + 7 "INSERT OR REPLACE INTO main.a1 DEFAULT VALUES" {} + 8 "INSERT OR REPLACE INTO a1 DEFAULT VALUES" {} + 9 "INSERT OR FAIL INTO main.a1 DEFAULT VALUES" {} + 10 "INSERT OR FAIL INTO a1 DEFAULT VALUES" {} + 11 "INSERT OR FAIL INTO main.a1 DEFAULT VALUES" {} + 12 "INSERT OR IGNORE INTO a1 DEFAULT VALUES" {} + 13 "REPLACE INTO a1 DEFAULT VALUES" {} + 14 "REPLACE INTO main.a1 DEFAULT VALUES" {} + 15 "INSERT INTO a1 VALUES(1, 2)" {} + 16 "INSERT INTO main.a1 VALUES(1, 2)" {} + 17 "INSERT OR ROLLBACK INTO main.a1 VALUES(1, 2)" {} + 18 "INSERT OR ROLLBACK INTO a1 VALUES(1, 2)" {} + 19 "INSERT OR ABORT INTO main.a1 VALUES(1, 2)" {} + 20 "INSERT OR ABORT INTO a1 VALUES(1, 2)" {} + 21 "INSERT OR REPLACE INTO main.a1 VALUES(1, 2)" {} + 22 "INSERT OR REPLACE INTO a1 VALUES(1, 2)" {} + 23 "INSERT OR FAIL INTO main.a1 VALUES(1, 2)" {} + 24 "INSERT OR FAIL INTO a1 VALUES(1, 2)" {} + 25 "INSERT OR FAIL INTO main.a1 VALUES(1, 2)" {} + 26 "INSERT OR IGNORE INTO a1 VALUES(1, 2)" {} + 27 "REPLACE INTO a1 VALUES(1, 2)" {} + 28 "REPLACE INTO main.a1 VALUES(1, 2)" {} + 29 "INSERT INTO a1 (b, a) VALUES(1, 2)" {} + 30 "INSERT INTO main.a1 (b, a) VALUES(1, 2)" {} + 31 "INSERT OR ROLLBACK INTO main.a1 (b, a) VALUES(1, 2)" {} + 32 "INSERT OR ROLLBACK INTO a1 (b, a) VALUES(1, 2)" {} + 33 "INSERT OR ABORT INTO main.a1 (b, a) VALUES(1, 2)" {} + 34 "INSERT OR ABORT INTO a1 (b, a) VALUES(1, 2)" {} + 35 "INSERT OR REPLACE INTO main.a1 (b, a) VALUES(1, 2)" {} + 36 "INSERT OR REPLACE INTO a1 (b, a) VALUES(1, 2)" {} + 37 "INSERT OR FAIL INTO main.a1 (b, a) VALUES(1, 2)" {} + 38 "INSERT OR FAIL INTO a1 (b, a) VALUES(1, 2)" {} + 39 "INSERT OR FAIL INTO main.a1 (b, a) VALUES(1, 2)" {} + 40 "INSERT OR IGNORE INTO a1 (b, a) VALUES(1, 2)" {} + 41 "REPLACE INTO a1 (b, a) VALUES(1, 2)" {} + 42 "REPLACE INTO main.a1 (b, a) VALUES(1, 2)" {} + 43 "INSERT INTO a1 SELECT c, b FROM a2" {} + 44 "INSERT INTO main.a1 SELECT c, b FROM a2" {} + 45 "INSERT OR ROLLBACK INTO main.a1 SELECT c, b FROM a2" {} + 46 "INSERT OR ROLLBACK INTO a1 SELECT c, b FROM a2" {} + 47 "INSERT OR ABORT INTO main.a1 SELECT c, b FROM a2" {} + 48 "INSERT OR ABORT INTO a1 SELECT c, b FROM a2" {} + 49 "INSERT OR REPLACE INTO main.a1 SELECT c, b FROM a2" {} + 50 "INSERT OR REPLACE INTO a1 SELECT c, b FROM a2" {} + 51 "INSERT OR FAIL INTO main.a1 SELECT c, b FROM a2" {} + 52 "INSERT OR FAIL INTO a1 SELECT c, b FROM a2" {} + 53 "INSERT OR FAIL INTO main.a1 SELECT c, b FROM a2" {} + 54 "INSERT OR IGNORE INTO a1 SELECT c, b FROM a2" {} + 55 "REPLACE INTO a1 SELECT c, b FROM a2" {} + 56 "REPLACE INTO main.a1 SELECT c, b FROM a2" {} + 57 "INSERT INTO a1 (b, a) SELECT c, b FROM a2" {} + 58 "INSERT INTO main.a1 (b, a) SELECT c, b FROM a2" {} + 59 "INSERT OR ROLLBACK INTO main.a1 (b, a) SELECT c, b FROM a2" {} + 60 "INSERT OR ROLLBACK INTO a1 (b, a) SELECT c, b FROM a2" {} + 61 "INSERT OR ABORT INTO main.a1 (b, a) SELECT c, b FROM a2" {} + 62 "INSERT OR ABORT INTO a1 (b, a) SELECT c, b FROM a2" {} + 63 "INSERT OR REPLACE INTO main.a1 (b, a) SELECT c, b FROM a2" {} + 64 "INSERT OR REPLACE INTO a1 (b, a) SELECT c, b FROM a2" {} + 65 "INSERT OR FAIL INTO main.a1 (b, a) SELECT c, b FROM a2" {} + 66 "INSERT OR FAIL INTO a1 (b, a) SELECT c, b FROM a2" {} + 67 "INSERT OR FAIL INTO main.a1 (b, a) SELECT c, b FROM a2" {} + 68 "INSERT OR IGNORE INTO a1 (b, a) SELECT c, b FROM a2" {} + 69 "REPLACE INTO a1 (b, a) SELECT c, b FROM a2" {} + 70 "REPLACE INTO main.a1 (b, a) SELECT c, b FROM a2" {} +} + +delete_all_data + +# EVIDENCE-OF: R-20288-20462 The first form (with the "VALUES" keyword) +# creates a single new row in an existing table. +# +do_insert_tests e_insert-1.1 { + 0 "SELECT count(*) FROM a2" {0} + + 1a "INSERT INTO a2 VALUES(1, 2, 3)" {} + 1b "SELECT count(*) FROM a2" {1} + + 2a "INSERT INTO a2(a, b) VALUES(1, 2)" {} + 2b "SELECT count(*) FROM a2" {2} +} + +# EVIDENCE-OF: R-36040-20870 If no column-list is specified then the +# number of values must be the same as the number of columns in the +# table. +# +# A test in the block above verifies that if the VALUES list has the +# correct number of columns (for table a2, 3 columns) works. So these +# tests just show that other values cause an error. +# +do_insert_tests e_insert-1.2 -error { + table %s has %d columns but %d values were supplied +} { + 1 "INSERT INTO a2 VALUES(1)" {a2 3 1} + 2 "INSERT INTO a2 VALUES(1,2)" {a2 3 2} + 3 "INSERT INTO a2 VALUES(1,2,3,4)" {a2 3 4} + 4 "INSERT INTO a2 VALUES(1,2,3,4,5)" {a2 3 5} +} + +# EVIDENCE-OF: R-52422-65517 In this case the result of evaluting the +# left-most expression in the VALUES list is inserted into the left-most +# column of the new row, and so on. +# +delete_all_data +do_insert_tests e_insert-1.3 { + 1a "INSERT INTO a2 VALUES(1, 2, 3)" {} + 1b "SELECT * FROM a2 WHERE oid=last_insert_rowid()" {1 2 3} + + 2a "INSERT INTO a2 VALUES('abc', NULL, 3*3+1)" {} + 2b "SELECT * FROM a2 WHERE oid=last_insert_rowid()" {abc {} 10} + + 3a "INSERT INTO a2 VALUES((SELECT count(*) FROM a2), 'x', 'y')" {} + 3b "SELECT * FROM a2 WHERE oid=last_insert_rowid()" {2 x y} +} + +# EVIDENCE-OF: R-62524-00361 If a column-list is specified, then the +# number of values must match the number of specified columns. +# +do_insert_tests e_insert-1.4 -error { + %d values for %d columns +} { + 1 "INSERT INTO a2(a, b, c) VALUES(1)" {1 3} + 2 "INSERT INTO a2(a, b, c) VALUES(1,2)" {2 3} + 3 "INSERT INTO a2(a, b, c) VALUES(1,2,3,4)" {4 3} + 4 "INSERT INTO a2(a, b, c) VALUES(1,2,3,4,5)" {5 3} + + 5 "INSERT INTO a2(c, a) VALUES(1)" {1 2} + 6 "INSERT INTO a2(c, a) VALUES(1,2,3)" {3 2} + 7 "INSERT INTO a2(c, a) VALUES(1,2,3,4)" {4 2} + 8 "INSERT INTO a2(c, a) VALUES(1,2,3,4,5)" {5 2} +} + +# EVIDENCE-OF: R-07016-26442 Each of the named columns of the new row is +# populated with the results of evaluating the corresponding VALUES +# expression. +# +# EVIDENCE-OF: R-12183-43719 Table columns that do not appear in the +# column list are populated with the default column value (specified as +# part of the CREATE TABLE statement), or with NULL if no default value +# is specified. +# +delete_all_data +do_insert_tests e_insert-1.5 { + 1a "INSERT INTO a2(b, c) VALUES('b', 'c')" {} + 1b "SELECT * FROM a2" {{} b c} + + 2a "INSERT INTO a2(a, b) VALUES('a', 'b')" {} + 2b "SELECT * FROM a2" {{} b c a b xyz} +} + +# EVIDENCE-OF: R-52173-30215 A new entry is inserted into the table for +# each row of data returned by executing the SELECT statement. +# +delete_all_data +do_insert_tests e_insert-2.1 { + 0 "SELECT count(*) FROM a1" {0} + + 1a "SELECT count(*) FROM (SELECT 1, 2)" {1} + 1b "INSERT INTO a1 SELECT 1, 2" {} + 1c "SELECT count(*) FROM a1" {1} + + 2a "SELECT count(*) FROM (SELECT b, a FROM a1)" {1} + 2b "INSERT INTO a1 SELECT b, a FROM a1" {} + 2c "SELECT count(*) FROM a1" {2} + + 3a "SELECT count(*) FROM (SELECT b, a FROM a1)" {2} + 3b "INSERT INTO a1 SELECT b, a FROM a1" {} + 3c "SELECT count(*) FROM a1" {4} + + 4a "SELECT count(*) FROM (SELECT b, a FROM a1)" {4} + 4b "INSERT INTO a1 SELECT b, a FROM a1" {} + 4c "SELECT count(*) FROM a1" {8} + + 4a "SELECT count(*) FROM (SELECT min(b), min(a) FROM a1)" {1} + 4b "INSERT INTO a1 SELECT min(b), min(a) FROM a1" {} + 4c "SELECT count(*) FROM a1" {9} +} + + +# EVIDENCE-OF: R-63614-47421 If a column-list is specified, the number +# of columns in the result of the SELECT must be the same as the number +# of items in the column-list. +# +do_insert_tests e_insert-2.2 -error { + %d values for %d columns +} { + 1 "INSERT INTO a3(x, y) SELECT a, b, c FROM a2" {3 2} + 2 "INSERT INTO a3(x, y) SELECT * FROM a2" {3 2} + 3 "INSERT INTO a3(x, y) SELECT * FROM a2 CROSS JOIN a1" {5 2} + 4 "INSERT INTO a3(x, y) SELECT * FROM a2 NATURAL JOIN a1" {3 2} + 5 "INSERT INTO a3(x, y) SELECT a2.a FROM a2,a1" {1 2} + + 6 "INSERT INTO a3(z) SELECT a, b, c FROM a2" {3 1} + 7 "INSERT INTO a3(z) SELECT * FROM a2" {3 1} + 8 "INSERT INTO a3(z) SELECT * FROM a2 CROSS JOIN a1" {5 1} + 9 "INSERT INTO a3(z) SELECT * FROM a2 NATURAL JOIN a1" {3 1} + 10 "INSERT INTO a3(z) SELECT a1.* FROM a2,a1" {2 1} +} + +# EVIDENCE-OF: R-58951-07798 Otherwise, if no column-list is specified, +# the number of columns in the result of the SELECT must be the same as +# the number of columns in the table. +# +do_insert_tests e_insert-2.3 -error { + table %s has %d columns but %d values were supplied +} { + 1 "INSERT INTO a1 SELECT a, b, c FROM a2" {a1 2 3} + 2 "INSERT INTO a1 SELECT * FROM a2" {a1 2 3} + 3 "INSERT INTO a1 SELECT * FROM a2 CROSS JOIN a1" {a1 2 5} + 4 "INSERT INTO a1 SELECT * FROM a2 NATURAL JOIN a1" {a1 2 3} + 5 "INSERT INTO a1 SELECT a2.a FROM a2,a1" {a1 2 1} +} + +# EVIDENCE-OF: R-31074-37730 Any SELECT statement, including compound +# SELECTs and SELECT statements with ORDER BY and/or LIMIT clauses, may +# be used in an INSERT statement of this form. +# +delete_all_data +do_execsql_test e_insert-2.3.0 { + INSERT INTO a1 VALUES('x', 'y'); +} {} +do_insert_tests e_insert-2.3 { + 1 "INSERT INTO a1 SELECT a,b FROM a1 UNION SELECT b,a FROM a1 ORDER BY 1" {} + 2 "INSERT INTO a1(b, a) SELECT * FROM a1 LIMIT 1" {} + 3 "INSERT INTO a1 SELECT 'a'||a, 'b'||b FROM a1 LIMIT 2 OFFSET 1" {} + 4 "INSERT INTO a1 SELECT * FROM a1 ORDER BY b, a" {} + S "SELECT * FROM a1" { + x y + x y y x + y x + ax by ay bx + ay bx ax by y x y x x y x y + } +} + +# EVIDENCE-OF: R-25149-22012 The INSERT ... DEFAULT VALUES statement +# inserts a single new row into the named table. +# +delete_all_data +do_insert_tests e_insert-3.1 { + 1 "SELECT count(*) FROM a3" {0} + 2a "INSERT INTO a3 DEFAULT VALUES" {} + 2b "SELECT count(*) FROM a3" {1} +} + +# EVIDENCE-OF: R-18927-01951 Each column of the new row is populated +# with its default value, or with a NULL if no default value is +# specified as part of the column definition in the CREATE TABLE +# statement. +# +delete_all_data +do_insert_tests e_insert-3.2 { + 1.1 "INSERT INTO a3 DEFAULT VALUES" {} + 1.2 "SELECT * FROM a3" {1.0 string {}} + + 2.1 "INSERT INTO a3 DEFAULT VALUES" {} + 2.2 "SELECT * FROM a3" {1.0 string {} 1.0 string {}} + + 3.1 "INSERT INTO a2 DEFAULT VALUES" {} + 3.2 "SELECT * FROM a2" {{} {} xyz} + + 4.1 "INSERT INTO a2 DEFAULT VALUES" {} + 4.2 "SELECT * FROM a2" {{} {} xyz {} {} xyz} + + 5.1 "INSERT INTO a1 DEFAULT VALUES" {} + 5.2 "SELECT * FROM a1" {{} {}} + + 6.1 "INSERT INTO a1 DEFAULT VALUES" {} + 6.2 "SELECT * FROM a1" {{} {} {} {}} +} + +# EVIDENCE-OF: R-46928-50290 The optional conflict-clause allows the +# specification of an alternative constraint conflict resolution +# algorithm to use during this one INSERT command. +# +# EVIDENCE-OF: R-23110-47146 the parser allows the use of the single +# keyword REPLACE as an alias for "INSERT OR REPLACE". +# +# The two requirements above are tested by e_select-4.1.* and +# e_select-4.2.*, respectively. +# +# EVIDENCE-OF: R-03421-22330 The REPLACE command is an alias for the +# "INSERT OR REPLACE" variant of the INSERT command. +# +# This is a dup of R-23110-47146. Therefore it is also verified +# by e_select-4.2.*. This requirement is the only one from +# lang_replace.html. +# +do_execsql_test e_insert-4.1.0 { + INSERT INTO a4 VALUES(1, 'a'); + INSERT INTO a4 VALUES(2, 'a'); + INSERT INTO a4 VALUES(3, 'a'); +} {} +foreach {tn sql error ac data } { + 1.1 "INSERT INTO a4 VALUES(2,'b')" {column c is not unique} 1 {1 a 2 a 3 a} + 1.2 "INSERT OR REPLACE INTO a4 VALUES(2, 'b')" {} 1 {1 a 3 a 2 b} + 1.3 "INSERT OR IGNORE INTO a4 VALUES(3, 'c')" {} 1 {1 a 3 a 2 b} + 1.4 "BEGIN" {} 0 {1 a 3 a 2 b} + 1.5 "INSERT INTO a4 VALUES(1, 'd')" {column c is not unique} 0 {1 a 3 a 2 b} + 1.6 "INSERT OR ABORT INTO a4 VALUES(1, 'd')" + {column c is not unique} 0 {1 a 3 a 2 b} + 1.7 "INSERT OR ROLLBACK INTO a4 VALUES(1, 'd')" + {column c is not unique} 1 {1 a 3 a 2 b} + 1.8 "INSERT INTO a4 SELECT 4, 'e' UNION ALL SELECT 3, 'e'" + {column c is not unique} 1 {1 a 3 a 2 b} + 1.9 "INSERT OR FAIL INTO a4 SELECT 4, 'e' UNION ALL SELECT 3, 'e'" + {column c is not unique} 1 {1 a 3 a 2 b 4 e} + + 2.1 "INSERT INTO a4 VALUES(2,'f')" + {column c is not unique} 1 {1 a 3 a 2 b 4 e} + 2.2 "REPLACE INTO a4 VALUES(2, 'f')" {} 1 {1 a 3 a 4 e 2 f} +} { + do_catchsql_test e_insert-4.1.$tn.1 $sql [list [expr {$error!=""}] $error] + do_execsql_test e_insert-4.1.$tn.2 {SELECT * FROM a4} [list {*}$data] + do_test e_insert-4.1.$tn.3 {sqlite3_get_autocommit db} $ac +} + +# EVIDENCE-OF: R-64196-02418 The optional "database-name." prefix on the +# table-name is support for top-level INSERT statements only. +# +# EVIDENCE-OF: R-05731-00924 The table name must be unqualified for +# INSERT statements that occur within CREATE TRIGGER statements. +# +set err {1 {qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers}} + +do_catchsql_test e_insert-5.1.1 { + CREATE TRIGGER AFTER UPDATE ON a1 BEGIN + INSERT INTO main.a4 VALUES(new.a, new.b); + END; +} $err +do_catchsql_test e_insert-5.1.2 { + CREATE TEMP TABLE IF NOT EXISTS tmptable(a, b); + CREATE TRIGGER AFTER DELETE ON a3 BEGIN + INSERT INTO temp.tmptable VALUES(1, 2); + END; +} $err + +# EVIDENCE-OF: R-15888-36326 Similarly, the "DEFAULT VALUES" form of the +# INSERT statement is supported for top-level INSERT statements only and +# not for INSERT statements within triggers. +# +do_catchsql_test e_insert-5.2.1 { + CREATE TRIGGER AFTER UPDATE ON a1 BEGIN + INSERT INTO a4 DEFAULT VALUES; + END; +} {1 {near "DEFAULT": syntax error}} + + +delete_all_data + +finish_test diff --git a/test/e_reindex.test b/test/e_reindex.test new file mode 100644 index 00000000..e9419dff --- /dev/null +++ b/test/e_reindex.test @@ -0,0 +1,294 @@ +# 2010 September 24 +# +# 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 implements tests to verify that the "testable statements" in +# the lang_reindex.html document are correct. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +proc do_reindex_tests {args} { + uplevel do_select_tests $args +} + +do_execsql_test e_reindex-0.0 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + CREATE INDEX i2 ON t1(b, a); +} {} + +# EVIDENCE-OF: R-57021-15304 -- syntax diagram reindex-stmt +# +do_reindex_tests e_reindex-0.1 { + 1 "REINDEX" {} + 2 "REINDEX nocase" {} + 3 "REINDEX binary" {} + 4 "REINDEX t1" {} + 5 "REINDEX main.t1" {} + 4 "REINDEX i1" {} + 5 "REINDEX main.i1" {} +} + +# EVIDENCE-OF: R-52173-44778 The REINDEX command is used to delete and +# recreate indices from scratch. +# +# Test this by corrupting some database indexes, running REINDEX, and +# observing that the corruption is gone. +# +do_execsql_test e_reindex-1.1 { + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t1 VALUES(5, 6); + PRAGMA writable_schema = 1; + UPDATE sqlite_master SET sql = '-- ' || sql WHERE type = 'index'; +} {} + +db close +sqlite3 db test.db +do_execsql_test e_reindex-1.2 { + DELETE FROM t1 WHERE a = 3; + INSERT INTO t1 VALUES(7, 8); + INSERT INTO t1 VALUES(9, 10); + PRAGMA writable_schema = 1; + UPDATE sqlite_master SET sql = substr(sql, 4) WHERE type = 'index'; +} {} + +db close +sqlite3 db test.db +do_execsql_test e_reindex-1.3 { + PRAGMA integrity_check; +} [list \ + {rowid 4 missing from index i2} \ + {rowid 4 missing from index i1} \ + {rowid 5 missing from index i2} \ + {rowid 5 missing from index i1} \ + {wrong # of entries in index i2} \ + {wrong # of entries in index i1} +] + +do_execsql_test e_reindex-1.4 { + REINDEX; + PRAGMA integrity_check; +} {ok} + +#------------------------------------------------------------------------- +# The remaining tests in this file focus on testing that the REINDEX +# command reindexes the correct subset of the indexes in the database. +# They all use the following dataset. +# +db close +forcedelete test.db2 +forcedelete test.db +sqlite3 db test.db + +proc sort_by_length {lhs rhs} { + set res [expr {[string length $lhs] - [string length $rhs]}] + if {$res!=0} {return $res} + return [string compare $lhs $rhs] +} +array set V {one 1 two 2 three 3 four 4 five 5 six 6 seven 7 eight 8} +proc sort_by_value {lhs rhs} { + global V + set res [expr {$V($lhs) - $V(rhs)}] + if {$res!=0} {return $res} + return [string compare $lhs $rhs] +} + +db collate collA sort_by_length +db collate collB sort_by_value + +set BY(length) {one six two five four eight seven three} +set BY(value) {one two three four five six seven eight} + +do_execsql_test e_reindex-2.0 { + ATTACH 'test.db2' AS aux; + + CREATE TABLE t1(x); + CREATE INDEX i1_a ON t1(x COLLATE collA); + CREATE INDEX i1_b ON t1(x COLLATE collB); + INSERT INTO t1 VALUES('one'); + INSERT INTO t1 VALUES('two'); + INSERT INTO t1 VALUES('three'); + INSERT INTO t1 VALUES('four'); + INSERT INTO t1 VALUES('five'); + INSERT INTO t1 VALUES('six'); + INSERT INTO t1 VALUES('seven'); + INSERT INTO t1 VALUES('eight'); + + CREATE TABLE t2(x); + CREATE INDEX i2_a ON t2(x COLLATE collA); + CREATE INDEX i2_b ON t2(x COLLATE collB); + INSERT INTO t2 SELECT x FROM t1; + + CREATE TABLE aux.t1(x); + CREATE INDEX aux.i1_a ON t1(x COLLATE collA); + CREATE INDEX aux.i1_b ON t1(x COLLATE collB); + INSERT INTO aux.t1 SELECT x FROM main.t1; + +} {} + +proc test_index {tn tbl collation expected} { + set sql "SELECT x FROM $tbl ORDER BY x COLLATE $collation" + uplevel do_execsql_test e_reindex-2.$tn [list $sql] [list $::BY($expected)] +} + +proc set_collations {a b} { + db collate collA "sort_by_$a" + db collate collB "sort_by_$b" +} + +test_index 1.1 t1 collA length +test_index 1.2 t1 collB value +test_index 1.3 t2 collA length +test_index 1.4 t2 collB value +test_index 1.5 aux.t1 collA length +test_index 1.6 aux.t1 collB value + + +# EVIDENCE-OF: R-47362-07898 If the REINDEX keyword is not followed by a +# collation-sequence or database object identifier, then all indices in +# all attached databases are rebuilt. +# +set_collations value length +do_execsql_test e_reindex-2.2.1 "REINDEX" {} +test_index 2.2 t1 collA value +test_index 2.3 t1 collB length +test_index 2.4 t2 collA value +test_index 2.5 t2 collB length +test_index 2.6 aux.t1 collA value +test_index 2.7 aux.t1 collB length + +# EVIDENCE-OF: R-45878-07697 If the REINDEX keyword is followed by a +# collation-sequence name, then all indices in all attached databases +# that use the named collation sequences are recreated. +# +set_collations length value +do_execsql_test e_reindex-2.3.1 "REINDEX collA" {} +test_index 3.2 t1 collA length +test_index 3.3 t1 collB length +test_index 3.4 t2 collA length +test_index 3.5 t2 collB length +test_index 3.6 aux.t1 collA length +test_index 3.7 aux.t1 collB length +do_execsql_test e_reindex-2.3.8 "REINDEX collB" {} +test_index 3.9 t1 collA length +test_index 3.10 t1 collB value +test_index 3.11 t2 collA length +test_index 3.12 t2 collB value +test_index 3.13 aux.t1 collA length +test_index 3.14 aux.t1 collB value + +# EVIDENCE-OF: R-49616-30196 Or, if the argument attached to the REINDEX +# identifies a specific database table, then all indices attached to the +# database table are rebuilt. +# +set_collations value length +do_execsql_test e_reindex-2.4.1 "REINDEX t1" {} +test_index 4.2 t1 collA value +test_index 4.3 t1 collB length +test_index 4.4 t2 collA length +test_index 4.5 t2 collB value +test_index 4.6 aux.t1 collA length +test_index 4.7 aux.t1 collB value +do_execsql_test e_reindex-2.4.8 "REINDEX aux.t1" {} +test_index 4.9 t1 collA value +test_index 4.10 t1 collB length +test_index 4.11 t2 collA length +test_index 4.12 t2 collB value +test_index 4.13 aux.t1 collA value +test_index 4.14 aux.t1 collB length +do_execsql_test e_reindex-2.4.15 "REINDEX t2" {} +test_index 4.16 t1 collA value +test_index 4.17 t1 collB length +test_index 4.18 t2 collA value +test_index 4.19 t2 collB length +test_index 4.20 aux.t1 collA value +test_index 4.21 aux.t1 collB length + +# EVIDENCE-OF: R-58823-28748 If it identifies a specific database index, +# then just that index is recreated. +# +set_collations length value +do_execsql_test e_reindex-2.5.1 "REINDEX i1_a" {} +test_index 5.2 t1 collA length +test_index 5.3 t1 collB length +test_index 5.4 t2 collA value +test_index 5.5 t2 collB length +test_index 5.6 aux.t1 collA value +test_index 5.7 aux.t1 collB length +do_execsql_test e_reindex-2.5.8 "REINDEX i2_b" {} +test_index 5.9 t1 collA length +test_index 5.10 t1 collB length +test_index 5.11 t2 collA value +test_index 5.12 t2 collB value +test_index 5.13 aux.t1 collA value +test_index 5.14 aux.t1 collB length +do_execsql_test e_reindex-2.5.15 "REINDEX aux.i1_b" {} +test_index 5.16 t1 collA length +test_index 5.17 t1 collB length +test_index 5.18 t2 collA value +test_index 5.19 t2 collB value +test_index 5.20 aux.t1 collA value +test_index 5.21 aux.t1 collB value +do_execsql_test e_reindex-2.5.22 "REINDEX i1_b" {} +test_index 5.23 t1 collA length +test_index 5.24 t1 collB value +test_index 5.25 t2 collA value +test_index 5.26 t2 collB value +test_index 5.27 aux.t1 collA value +test_index 5.28 aux.t1 collB value +do_execsql_test e_reindex-2.5.29 "REINDEX i2_a" {} +test_index 5.30 t1 collA length +test_index 5.31 t1 collB value +test_index 5.32 t2 collA length +test_index 5.33 t2 collB value +test_index 5.34 aux.t1 collA value +test_index 5.35 aux.t1 collB value +do_execsql_test e_reindex-2.5.36 "REINDEX aux.i1_a" {} +test_index 5.37 t1 collA length +test_index 5.38 t1 collB value +test_index 5.39 t2 collA length +test_index 5.40 t2 collB value +test_index 5.41 aux.t1 collA length +test_index 5.42 aux.t1 collB value + +# EVIDENCE-OF: R-15639-02023 If no database-name is specified and there +# exists both a table or index and a collation sequence of the specified +# name, SQLite interprets this as a request to rebuild the indices that +# use the named collation sequence. +# +set_collations value length +do_execsql_test e_reindex-2.6.0 { + CREATE TABLE collA(x); + CREATE INDEX icolla_a ON collA(x COLLATE collA); + CREATE INDEX icolla_b ON collA(x COLLATE collB); + + INSERT INTO collA SELECT x FROM t1; +} {} + +test_index 6.1 collA collA value +test_index 6.2 collA collB length + +set_collations length value +do_execsql_test e_reindex-2.6.3 "REINDEX collA" {} +test_index 6.4 collA collA length +test_index 6.5 collA collB length +do_execsql_test e_reindex-2.6.3 "REINDEX main.collA" {} +test_index 6.4 collA collA length +test_index 6.5 collA collB value + +set_collations value length +do_execsql_test e_reindex-2.6.6 "REINDEX main.collA" {} +test_index 6.7 collA collA value +test_index 6.8 collA collB length + +finish_test diff --git a/test/e_resolve.test b/test/e_resolve.test new file mode 100644 index 00000000..512fcf27 --- /dev/null +++ b/test/e_resolve.test @@ -0,0 +1,135 @@ +# 2010 November 30 +# +# 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 implements tests to verify that the "testable statements" in +# the lang_naming.html document are correct. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix e_resolve + +# An example database schema for testing name resolution: +# +set schema { + ATTACH 'test.db2' AS at1; + ATTACH 'test.db3' AS at2; + + CREATE TABLE temp.n1(x, y); INSERT INTO temp.n1 VALUES('temp', 'n1'); + CREATE TRIGGER temp.n3 AFTER INSERT ON n1 BEGIN SELECT 1; END; + CREATE INDEX temp.n4 ON n1(x, y); + + CREATE TABLE main.n1(x, y); INSERT INTO main.n1 VALUES('main', 'n1'); + CREATE TABLE main.n2(x, y); INSERT INTO main.n2 VALUES('main', 'n2'); + CREATE INDEX main.n3 ON n2(y, x); + CREATE TRIGGER main.n4 BEFORE INSERT ON n2 BEGIN SELECT 1; END; + + CREATE TABLE at1.n1(x, y); INSERT INTO at1.n1 VALUES('at1', 'n1'); + CREATE TABLE at1.n2(x, y); INSERT INTO at1.n2 VALUES('at1', 'n2'); + CREATE TABLE at1.n3(x, y); INSERT INTO at1.n3 VALUES('at1', 'n3'); + + CREATE TABLE at2.n1(x, y); INSERT INTO at2.n1 VALUES('at2', 'n1'); + CREATE TABLE at2.n2(x, y); INSERT INTO at2.n2 VALUES('at2', 'n2'); + CREATE TABLE at2.n3(x, y); INSERT INTO at2.n3 VALUES('at2', 'n3'); + CREATE TABLE at2.n4(x, y); INSERT INTO at2.n4 VALUES('at2', 'n4'); + CREATE TRIGGER at2.n4 BEFORE INSERT ON n4 BEGIN SELECT 1; END; +} + +proc resolve_reopen_db {} { + db close + forcedelete test.db test.db2 test.db3 + sqlite3 db test.db + db eval $::schema +} + + + +# EVIDENCE-OF: R-33528-20612 If no database is specified as part of the +# object reference, then SQLite searches the main, temp and all attached +# databases for an object with a matching name. The temp database is +# searched first, followed by the main database, followed all attached +# databases in the order that they were attached. The reference resolves +# to the first match found. +# +resolve_reopen_db +do_execsql_test 1.1 { SELECT * FROM n1 } {temp n1} +do_execsql_test 1.2 { SELECT * FROM n2 } {main n2} +do_execsql_test 1.3 { SELECT * FROM n3 } {at1 n3} +do_execsql_test 1.4 { SELECT * FROM n4 } {at2 n4} + +# EVIDENCE-OF: R-54577-28142 If a database name is specified as part of +# an object reference, it must be either "main", or "temp" or the name +# of an attached database. +# +# Or else it is a "no such table: xxx" error. +# +resolve_reopen_db +do_execsql_test 2.1.1 { SELECT * FROM main.n1 } {main n1} +do_execsql_test 2.1.2 { SELECT * FROM temp.n1 } {temp n1} +do_execsql_test 2.1.3 { SELECT * FROM at1.n1 } {at1 n1} +do_execsql_test 2.1.4 { SELECT * FROM at2.n1 } {at2 n1} + +do_catchsql_test 2.2 { SELECT * FROM xxx.n1 } {1 {no such table: xxx.n1}} + +# EVIDENCE-OF: R-26223-47623 Like other SQL identifiers, database names +# are case-insensitive. +# +resolve_reopen_db +do_execsql_test 3.1 { SELECT * FROM MAIN.n1 } {main n1} +do_execsql_test 3.2 { SELECT * FROM tEmP.n1 } {temp n1} +do_execsql_test 3.3 { SELECT * FROM aT1.n1 } {at1 n1} +do_execsql_test 3.4 { SELECT * FROM At2.n1 } {at2 n1} + +# EVIDENCE-OF: R-15639-28392 If a database name is specified, then only +# the named database is searched for the named object. +# +do_catchsql_test 4.1 { SELECT * FROM temp.n2 } {1 {no such table: temp.n2}} +do_catchsql_test 4.2 { SELECT * FROM main.n2 } {0 {main n2}} +do_catchsql_test 4.3 { SELECT * FROM at1.n2 } {0 {at1 n2}} +do_catchsql_test 4.4 { SELECT * FROM at2.n2 } {0 {at2 n2}} + +# EVIDENCE-OF: R-08951-19801 When searching database schemas for a named +# object, objects of types that cannot be used in the context of the +# reference are always ignored. +# +# In this case, "types that cannot be used" are triggers and indexes. +# The temp and main databases both contain triggers and indexes named +# "n3" and "n4". Database "at2" contains a trigger called "n4". And yet: +# +do_execsql_test 5.1 { SELECT * FROM n3 } {at1 n3} +do_execsql_test 5.2 { SELECT * FROM n4 } {at2 n4} + +#------------------------------------------------------------------------- +# EVIDENCE-OF: R-37286-42536 +# +db close +forcedelete test.db file.db +sqlite3 db test.db +do_execsql_test 6.1 { + ATTACH 'file.db' AS aux; + CREATE TABLE t1(x, y); + CREATE TEMP TABLE t1(x, y); + CREATE TABLE aux.t1(x, y); +} + +do_execsql_test 6.2.0 { DROP TABLE t1 } +do_catchsql_test 6.2.1 { SELECT * FROM temp.t1 } {1 {no such table: temp.t1}} +do_catchsql_test 6.2.2 { SELECT * FROM main.t1 } {0 {}} +do_catchsql_test 6.2.3 { SELECT * FROM aux.t1 } {0 {}} + +do_execsql_test 6.3.0 { DROP TABLE t1 } +do_catchsql_test 6.3.1 { SELECT * FROM main.t1 } {1 {no such table: main.t1}} +do_catchsql_test 6.3.3 { SELECT * FROM aux.t1 } {0 {}} + +do_execsql_test 6.4.0 { DROP TABLE t1 } +do_catchsql_test 6.4.1 { SELECT * FROM aux.t1 } {1 {no such table: aux.t1}} + +finish_test diff --git a/test/e_select.test b/test/e_select.test new file mode 100644 index 00000000..79e334ce --- /dev/null +++ b/test/e_select.test @@ -0,0 +1,2162 @@ +# 2010 July 16 +# +# 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 implements tests to verify that the "testable statements" in +# the lang_select.html document are correct. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_execsql_test e_select-1.0 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES('a', 'one'); + INSERT INTO t1 VALUES('b', 'two'); + INSERT INTO t1 VALUES('c', 'three'); + + CREATE TABLE t2(a, b); + INSERT INTO t2 VALUES('a', 'I'); + INSERT INTO t2 VALUES('b', 'II'); + INSERT INTO t2 VALUES('c', 'III'); + + CREATE TABLE t3(a, c); + INSERT INTO t3 VALUES('a', 1); + INSERT INTO t3 VALUES('b', 2); + + CREATE TABLE t4(a, c); + INSERT INTO t4 VALUES('a', NULL); + INSERT INTO t4 VALUES('b', 2); +} {} +set t1_cross_t2 [list \ + a one a I a one b II \ + a one c III b two a I \ + b two b II b two c III \ + c three a I c three b II \ + c three c III \ +] +set t1_cross_t1 [list \ + a one a one a one b two \ + a one c three b two a one \ + b two b two b two c three \ + c three a one c three b two \ + c three c three \ +] + + +# This proc is a specialized version of [do_execsql_test]. +# +# The second argument to this proc must be a SELECT statement that +# features a cross join of some time. Instead of the usual ",", +# "CROSS JOIN" or "INNER JOIN" join-op, the string %JOIN% must be +# substituted. +# +# This test runs the SELECT three times - once with: +# +# * s/%JOIN%/,/ +# * s/%JOIN%/JOIN/ +# * s/%JOIN%/INNER JOIN/ +# * s/%JOIN%/CROSS JOIN/ +# +# and checks that each time the results of the SELECT are $res. +# +proc do_join_test {tn select res} { + foreach {tn2 joinop} [list 1 , 2 "CROSS JOIN" 3 "INNER JOIN"] { + set S [string map [list %JOIN% $joinop] $select] + uplevel do_execsql_test $tn.$tn2 [list $S] [list $res] + } +} + +#------------------------------------------------------------------------- +# The following tests check that all paths on the syntax diagrams on +# the lang_select.html page may be taken. +# +# EVIDENCE-OF: R-18428-22111 -- syntax diagram join-constraint +# +do_join_test e_select-0.1.1 { + SELECT count(*) FROM t1 %JOIN% t2 ON (t1.a=t2.a) +} {3} +do_join_test e_select-0.1.2 { + SELECT count(*) FROM t1 %JOIN% t2 USING (a) +} {3} +do_join_test e_select-0.1.3 { + SELECT count(*) FROM t1 %JOIN% t2 +} {9} +do_catchsql_test e_select-0.1.4 { + SELECT count(*) FROM t1, t2 ON (t1.a=t2.a) USING (a) +} {1 {cannot have both ON and USING clauses in the same join}} +do_catchsql_test e_select-0.1.5 { + SELECT count(*) FROM t1, t2 USING (a) ON (t1.a=t2.a) +} {1 {near "ON": syntax error}} + +# EVIDENCE-OF: R-44854-11739 -- syntax diagram select-core +# +# 0: SELECT ... +# 1: SELECT DISTINCT ... +# 2: SELECT ALL ... +# +# 0: No FROM clause +# 1: Has FROM clause +# +# 0: No WHERE clause +# 1: Has WHERE clause +# +# 0: No GROUP BY clause +# 1: Has GROUP BY clause +# 2: Has GROUP BY and HAVING clauses +# +do_select_tests e_select-0.2 { + 0000.1 "SELECT 1, 2, 3 " {1 2 3} + 1000.1 "SELECT DISTINCT 1, 2, 3 " {1 2 3} + 2000.1 "SELECT ALL 1, 2, 3 " {1 2 3} + + 0100.1 "SELECT a, b, a||b FROM t1 " { + a one aone b two btwo c three cthree + } + 1100.1 "SELECT DISTINCT a, b, a||b FROM t1 " { + a one aone b two btwo c three cthree + } + 1200.1 "SELECT ALL a, b, a||b FROM t1 " { + a one aone b two btwo c three cthree + } + + 0010.1 "SELECT 1, 2, 3 WHERE 1 " {1 2 3} + 0010.2 "SELECT 1, 2, 3 WHERE 0 " {} + 0010.3 "SELECT 1, 2, 3 WHERE NULL " {} + + 1010.1 "SELECT DISTINCT 1, 2, 3 WHERE 1 " {1 2 3} + + 2010.1 "SELECT ALL 1, 2, 3 WHERE 1 " {1 2 3} + + 0110.1 "SELECT a, b, a||b FROM t1 WHERE a!='x' " { + a one aone b two btwo c three cthree + } + 0110.2 "SELECT a, b, a||b FROM t1 WHERE a=='x'" {} + + 1110.1 "SELECT DISTINCT a, b, a||b FROM t1 WHERE a!='x' " { + a one aone b two btwo c three cthree + } + + 2110.0 "SELECT ALL a, b, a||b FROM t1 WHERE a=='x'" {} + + 0001.1 "SELECT 1, 2, 3 GROUP BY 2" {1 2 3} + 0002.1 "SELECT 1, 2, 3 GROUP BY 2 HAVING count(*)=1" {1 2 3} + 0002.2 "SELECT 1, 2, 3 GROUP BY 2 HAVING count(*)>1" {} + + 1001.1 "SELECT DISTINCT 1, 2, 3 GROUP BY 2" {1 2 3} + 1002.1 "SELECT DISTINCT 1, 2, 3 GROUP BY 2 HAVING count(*)=1" {1 2 3} + 1002.2 "SELECT DISTINCT 1, 2, 3 GROUP BY 2 HAVING count(*)>1" {} + + 2001.1 "SELECT ALL 1, 2, 3 GROUP BY 2" {1 2 3} + 2002.1 "SELECT ALL 1, 2, 3 GROUP BY 2 HAVING count(*)=1" {1 2 3} + 2002.2 "SELECT ALL 1, 2, 3 GROUP BY 2 HAVING count(*)>1" {} + + 0101.1 "SELECT count(*), max(a) FROM t1 GROUP BY b" {1 a 1 c 1 b} + 0102.1 "SELECT count(*), max(a) FROM t1 GROUP BY b HAVING count(*)=1" { + 1 a 1 c 1 b + } + 0102.2 "SELECT count(*), max(a) FROM t1 GROUP BY b HAVING count(*)=2" { } + + 1101.1 "SELECT DISTINCT count(*), max(a) FROM t1 GROUP BY b" {1 a 1 c 1 b} + 1102.1 "SELECT DISTINCT count(*), max(a) FROM t1 + GROUP BY b HAVING count(*)=1" { + 1 a 1 c 1 b + } + 1102.2 "SELECT DISTINCT count(*), max(a) FROM t1 + GROUP BY b HAVING count(*)=2" { + } + + 2101.1 "SELECT ALL count(*), max(a) FROM t1 GROUP BY b" {1 a 1 c 1 b} + 2102.1 "SELECT ALL count(*), max(a) FROM t1 + GROUP BY b HAVING count(*)=1" { + 1 a 1 c 1 b + } + 2102.2 "SELECT ALL count(*), max(a) FROM t1 + GROUP BY b HAVING count(*)=2" { + } + + 0011.1 "SELECT 1, 2, 3 WHERE 1 GROUP BY 2" {1 2 3} + 0012.1 "SELECT 1, 2, 3 WHERE 0 GROUP BY 2 HAVING count(*)=1" {} + 0012.2 "SELECT 1, 2, 3 WHERE 0 GROUP BY 2 HAVING count(*)>1" {} + + 1011.1 "SELECT DISTINCT 1, 2, 3 WHERE 0 GROUP BY 2" {} + 1012.1 "SELECT DISTINCT 1, 2, 3 WHERE 1 GROUP BY 2 HAVING count(*)=1" + {1 2 3} + 1012.2 "SELECT DISTINCT 1, 2, 3 WHERE NULL GROUP BY 2 HAVING count(*)>1" {} + + 2011.1 "SELECT ALL 1, 2, 3 WHERE 1 GROUP BY 2" {1 2 3} + 2012.1 "SELECT ALL 1, 2, 3 WHERE 0 GROUP BY 2 HAVING count(*)=1" {} + 2012.2 "SELECT ALL 1, 2, 3 WHERE 'abc' GROUP BY 2 HAVING count(*)>1" {} + + 0111.1 "SELECT count(*), max(a) FROM t1 WHERE a='a' GROUP BY b" {1 a} + 0112.1 "SELECT count(*), max(a) FROM t1 + WHERE a='c' GROUP BY b HAVING count(*)=1" {1 c} + 0112.2 "SELECT count(*), max(a) FROM t1 + WHERE 0 GROUP BY b HAVING count(*)=2" { } + 1111.1 "SELECT DISTINCT count(*), max(a) FROM t1 WHERE a<'c' GROUP BY b" + {1 a 1 b} + 1112.1 "SELECT DISTINCT count(*), max(a) FROM t1 WHERE a>'a' + GROUP BY b HAVING count(*)=1" { + 1 c 1 b + } + 1112.2 "SELECT DISTINCT count(*), max(a) FROM t1 WHERE 0 + GROUP BY b HAVING count(*)=2" { + } + + 2111.1 "SELECT ALL count(*), max(a) FROM t1 WHERE b>'one' GROUP BY b" + {1 c 1 b} + 2112.1 "SELECT ALL count(*), max(a) FROM t1 WHERE a!='b' + GROUP BY b HAVING count(*)=1" { + 1 a 1 c + } + 2112.2 "SELECT ALL count(*), max(a) FROM t1 + WHERE 0 GROUP BY b HAVING count(*)=2" { } +} + + +# EVIDENCE-OF: R-23316-20169 -- syntax diagram result-column +# +do_select_tests e_select-0.3 { + 1 "SELECT * FROM t1" {a one b two c three} + 2 "SELECT t1.* FROM t1" {a one b two c three} + 3 "SELECT 'x'||a||'x' FROM t1" {xax xbx xcx} + 4 "SELECT 'x'||a||'x' alias FROM t1" {xax xbx xcx} + 5 "SELECT 'x'||a||'x' AS alias FROM t1" {xax xbx xcx} +} + +# EVIDENCE-OF: R-41233-21397 -- syntax diagram join-source +# +# EVIDENCE-OF: R-45040-11121 -- syntax diagram join-op +# +do_select_tests e_select-0.4 { + 1 "SELECT t1.rowid FROM t1" {1 2 3} + 2 "SELECT t1.rowid FROM t1,t2" {1 1 1 2 2 2 3 3 3} + 3 "SELECT t1.rowid FROM t1,t2,t3" {1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3} + + 4 "SELECT t1.rowid FROM t1" {1 2 3} + 5 "SELECT t1.rowid FROM t1 JOIN t2" {1 1 1 2 2 2 3 3 3} + 6 "SELECT t1.rowid FROM t1 JOIN t2 JOIN t3" + {1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3} + + 7 "SELECT t1.rowid FROM t1 NATURAL JOIN t3" {1 2} + 8 "SELECT t1.rowid FROM t1 NATURAL LEFT OUTER JOIN t3" {1 2 3} + 9 "SELECT t1.rowid FROM t1 NATURAL LEFT JOIN t3" {1 2 3} + 10 "SELECT t1.rowid FROM t1 NATURAL INNER JOIN t3" {1 2} + 11 "SELECT t1.rowid FROM t1 NATURAL CROSS JOIN t3" {1 2} + + 12 "SELECT t1.rowid FROM t1 JOIN t3" {1 1 2 2 3 3} + 13 "SELECT t1.rowid FROM t1 LEFT OUTER JOIN t3" {1 1 2 2 3 3} + 14 "SELECT t1.rowid FROM t1 LEFT JOIN t3" {1 1 2 2 3 3} + 15 "SELECT t1.rowid FROM t1 INNER JOIN t3" {1 1 2 2 3 3} + 16 "SELECT t1.rowid FROM t1 CROSS JOIN t3" {1 1 2 2 3 3} +} + +# EVIDENCE-OF: R-56911-63533 -- syntax diagram compound-operator +# +do_select_tests e_select-0.5 { + 1 "SELECT rowid FROM t1 UNION ALL SELECT rowid+2 FROM t4" {1 2 3 3 4} + 2 "SELECT rowid FROM t1 UNION SELECT rowid+2 FROM t4" {1 2 3 4} + 3 "SELECT rowid FROM t1 INTERSECT SELECT rowid+2 FROM t4" {3} + 4 "SELECT rowid FROM t1 EXCEPT SELECT rowid+2 FROM t4" {1 2} +} + +# EVIDENCE-OF: R-60388-27458 -- syntax diagram ordering-term +# +do_select_tests e_select-0.6 { + 1 "SELECT b||a FROM t1 ORDER BY b||a" {onea threec twob} + 2 "SELECT b||a FROM t1 ORDER BY (b||a) COLLATE nocase" {onea threec twob} + 3 "SELECT b||a FROM t1 ORDER BY (b||a) ASC" {onea threec twob} + 4 "SELECT b||a FROM t1 ORDER BY (b||a) DESC" {twob threec onea} +} + +# EVIDENCE-OF: R-36494-33519 -- syntax diagram select-stmt +# +do_select_tests e_select-0.7 { + 1 "SELECT * FROM t1" {a one b two c three} + 2 "SELECT * FROM t1 ORDER BY b" {a one c three b two} + 3 "SELECT * FROM t1 ORDER BY b, a" {a one c three b two} + + 4 "SELECT * FROM t1 LIMIT 10" {a one b two c three} + 5 "SELECT * FROM t1 LIMIT 10 OFFSET 5" {} + 6 "SELECT * FROM t1 LIMIT 10, 5" {} + + 7 "SELECT * FROM t1 ORDER BY a LIMIT 10" {a one b two c three} + 8 "SELECT * FROM t1 ORDER BY b LIMIT 10 OFFSET 5" {} + 9 "SELECT * FROM t1 ORDER BY a,b LIMIT 10, 5" {} + + 10 "SELECT * FROM t1 UNION SELECT b, a FROM t1" + {a one b two c three one a three c two b} + 11 "SELECT * FROM t1 UNION SELECT b, a FROM t1 ORDER BY b" + {one a two b three c a one c three b two} + 12 "SELECT * FROM t1 UNION SELECT b, a FROM t1 ORDER BY b, a" + {one a two b three c a one c three b two} + 13 "SELECT * FROM t1 UNION SELECT b, a FROM t1 LIMIT 10" + {a one b two c three one a three c two b} + 14 "SELECT * FROM t1 UNION SELECT b, a FROM t1 LIMIT 10 OFFSET 5" + {two b} + 15 "SELECT * FROM t1 UNION SELECT b, a FROM t1 LIMIT 10, 5" + {} + 16 "SELECT * FROM t1 UNION SELECT b, a FROM t1 ORDER BY a LIMIT 10" + {a one b two c three one a three c two b} + 17 "SELECT * FROM t1 UNION SELECT b, a FROM t1 ORDER BY b LIMIT 10 OFFSET 5" + {b two} + 18 "SELECT * FROM t1 UNION SELECT b, a FROM t1 ORDER BY a,b LIMIT 10, 5" + {} +} + +#------------------------------------------------------------------------- +# The following tests focus on FROM clause (join) processing. +# + +# EVIDENCE-OF: R-16074-54196 If the FROM clause is omitted from a simple +# SELECT statement, then the input data is implicitly a single row zero +# columns wide +# +do_select_tests e_select-1.1 { + 1 "SELECT 'abc'" {abc} + 2 "SELECT 'abc' WHERE NULL" {} + 3 "SELECT NULL" {{}} + 4 "SELECT count(*)" {1} + 5 "SELECT count(*) WHERE 0" {0} + 6 "SELECT count(*) WHERE 1" {1} +} + +# EVIDENCE-OF: R-48114-33255 If there is only a single table in the +# join-source following the FROM clause, then the input data used by the +# SELECT statement is the contents of the named table. +# +# The results of the SELECT queries suggest that they are operating on the +# contents of the table 'xx'. +# +do_execsql_test e_select-1.2.0 { + CREATE TABLE xx(x, y); + INSERT INTO xx VALUES('IiJlsIPepMuAhU', X'10B00B897A15BAA02E3F98DCE8F2'); + INSERT INTO xx VALUES(NULL, -16.87); + INSERT INTO xx VALUES(-17.89, 'linguistically'); +} {} +do_select_tests e_select-1.2 { + 1 "SELECT quote(x), quote(y) FROM xx" { + 'IiJlsIPepMuAhU' X'10B00B897A15BAA02E3F98DCE8F2' + NULL -16.87 + -17.89 'linguistically' + } + + 2 "SELECT count(*), count(x), count(y) FROM xx" {3 2 3} + 3 "SELECT sum(x), sum(y) FROM xx" {-17.89 -16.87} +} + +# EVIDENCE-OF: R-23593-12456 If there is more than one table specified +# as part of the join-source following the FROM keyword, then the +# contents of each named table are joined into a single dataset for the +# simple SELECT statement to operate on. +# +# There are more detailed tests for subsequent requirements that add +# more detail to this idea. We just add a single test that shows that +# data is coming from each of the three tables following the FROM clause +# here to show that the statement, vague as it is, is not incorrect. +# +do_select_tests e_select-1.3 { + 1 "SELECT * FROM t1, t2, t3" { + a one a I a 1 a one a I b 2 a one b II a 1 + a one b II b 2 a one c III a 1 a one c III b 2 + b two a I a 1 b two a I b 2 b two b II a 1 + b two b II b 2 b two c III a 1 b two c III b 2 + c three a I a 1 c three a I b 2 c three b II a 1 + c three b II b 2 c three c III a 1 c three c III b 2 + } +} + +# +# The following block of tests - e_select-1.4.* - test that the description +# of cartesian joins in the SELECT documentation is consistent with SQLite. +# In doing so, we test the following three requirements as a side-effect: +# +# EVIDENCE-OF: R-46122-14930 If the join-op is "CROSS JOIN", "INNER +# JOIN", "JOIN" or a comma (",") and there is no ON or USING clause, +# then the result of the join is simply the cartesian product of the +# left and right-hand datasets. +# +# The tests are built on this assertion. Really, they test that the output +# of a CROSS JOIN, JOIN, INNER JOIN or "," join matches the expected result +# of calculating the cartesian product of the left and right-hand datasets. +# +# EVIDENCE-OF: R-46256-57243 There is no difference between the "INNER +# JOIN", "JOIN" and "," join operators. +# +# EVIDENCE-OF: R-07544-24155 The "CROSS JOIN" join operator produces the +# same data as the "INNER JOIN", "JOIN" and "," operators +# +# All tests are run 4 times, with the only difference in each run being +# which of the 4 equivalent cartesian product join operators are used. +# Since the output data is the same in all cases, we consider that this +# qualifies as testing the two statements above. +# +do_execsql_test e_select-1.4.0 { + CREATE TABLE x1(a, b); + CREATE TABLE x2(c, d, e); + CREATE TABLE x3(f, g, h, i); + + -- x1: 3 rows, 2 columns + INSERT INTO x1 VALUES(24, 'converging'); + INSERT INTO x1 VALUES(NULL, X'CB71'); + INSERT INTO x1 VALUES('blonds', 'proprietary'); + + -- x2: 2 rows, 3 columns + INSERT INTO x2 VALUES(-60.06, NULL, NULL); + INSERT INTO x2 VALUES(-58, NULL, 1.21); + + -- x3: 5 rows, 4 columns + INSERT INTO x3 VALUES(-39.24, NULL, 'encompass', -1); + INSERT INTO x3 VALUES('presenting', 51, 'reformation', 'dignified'); + INSERT INTO x3 VALUES('conducting', -87.24, 37.56, NULL); + INSERT INTO x3 VALUES('coldest', -96, 'dramatists', 82.3); + INSERT INTO x3 VALUES('alerting', NULL, -93.79, NULL); +} {} + +# EVIDENCE-OF: R-59089-25828 The columns of the cartesian product +# dataset are, in order, all the columns of the left-hand dataset +# followed by all the columns of the right-hand dataset. +# +do_join_test e_select-1.4.1.1 { + SELECT * FROM x1 %JOIN% x2 LIMIT 1 +} [concat {24 converging} {-60.06 {} {}}] + +do_join_test e_select-1.4.1.2 { + SELECT * FROM x2 %JOIN% x1 LIMIT 1 +} [concat {-60.06 {} {}} {24 converging}] + +do_join_test e_select-1.4.1.3 { + SELECT * FROM x3 %JOIN% x2 LIMIT 1 +} [concat {-39.24 {} encompass -1} {-60.06 {} {}}] + +do_join_test e_select-1.4.1.4 { + SELECT * FROM x2 %JOIN% x3 LIMIT 1 +} [concat {-60.06 {} {}} {-39.24 {} encompass -1}] + +# EVIDENCE-OF: R-44414-54710 There is a row in the cartesian product +# dataset formed by combining each unique combination of a row from the +# left-hand and right-hand datasets. +# +do_join_test e_select-1.4.2.1 { + SELECT * FROM x2 %JOIN% x3 +} [list -60.06 {} {} -39.24 {} encompass -1 \ + -60.06 {} {} presenting 51 reformation dignified \ + -60.06 {} {} conducting -87.24 37.56 {} \ + -60.06 {} {} coldest -96 dramatists 82.3 \ + -60.06 {} {} alerting {} -93.79 {} \ + -58 {} 1.21 -39.24 {} encompass -1 \ + -58 {} 1.21 presenting 51 reformation dignified \ + -58 {} 1.21 conducting -87.24 37.56 {} \ + -58 {} 1.21 coldest -96 dramatists 82.3 \ + -58 {} 1.21 alerting {} -93.79 {} \ +] +# TODO: Come back and add a few more like the above. + +# EVIDENCE-OF: R-20659-43267 In other words, if the left-hand dataset +# consists of Nlhs rows of Mlhs columns, and the right-hand dataset of +# Nrhs rows of Mrhs columns, then the cartesian product is a dataset of +# Nlhs.Nrhs rows, each containing Mlhs+Mrhs columns. +# +# x1, x2 (Nlhs=3, Nrhs=2) (Mlhs=2, Mrhs=3) +do_join_test e_select-1.4.3.1 { + SELECT count(*) FROM x1 %JOIN% x2 +} [expr 3*2] +do_test e_select-1.4.3.2 { + expr {[llength [execsql {SELECT * FROM x1, x2}]] / 6} +} [expr 2+3] + +# x2, x3 (Nlhs=2, Nrhs=5) (Mlhs=3, Mrhs=4) +do_join_test e_select-1.4.3.3 { + SELECT count(*) FROM x2 %JOIN% x3 +} [expr 2*5] +do_test e_select-1.4.3.4 { + expr {[llength [execsql {SELECT * FROM x2 JOIN x3}]] / 10} +} [expr 3+4] + +# x3, x1 (Nlhs=5, Nrhs=3) (Mlhs=4, Mrhs=2) +do_join_test e_select-1.4.3.5 { + SELECT count(*) FROM x3 %JOIN% x1 +} [expr 5*3] +do_test e_select-1.4.3.6 { + expr {[llength [execsql {SELECT * FROM x3 CROSS JOIN x1}]] / 15} +} [expr 4+2] + +# x3, x3 (Nlhs=5, Nrhs=5) (Mlhs=4, Mrhs=4) +do_join_test e_select-1.4.3.7 { + SELECT count(*) FROM x3 %JOIN% x3 +} [expr 5*5] +do_test e_select-1.4.3.8 { + expr {[llength [execsql {SELECT * FROM x3 INNER JOIN x3 AS x4}]] / 25} +} [expr 4+4] + +# Some extra cartesian product tests using tables t1 and t2. +# +do_execsql_test e_select-1.4.4.1 { SELECT * FROM t1, t2 } $t1_cross_t2 +do_execsql_test e_select-1.4.4.2 { SELECT * FROM t1 AS x, t1 AS y} $t1_cross_t1 + +do_select_tests e_select-1.4.5 [list \ + 1 { SELECT * FROM t1 CROSS JOIN t2 } $t1_cross_t2 \ + 2 { SELECT * FROM t1 AS y CROSS JOIN t1 AS x } $t1_cross_t1 \ + 3 { SELECT * FROM t1 INNER JOIN t2 } $t1_cross_t2 \ + 4 { SELECT * FROM t1 AS y INNER JOIN t1 AS x } $t1_cross_t1 \ +] + + +# EVIDENCE-OF: R-22775-56496 If there is an ON clause specified, then +# the ON expression is evaluated for each row of the cartesian product +# as a boolean expression. All rows for which the expression evaluates +# to false are excluded from the dataset. +# +foreach {tn select res} [list \ + 1 { SELECT * FROM t1 %JOIN% t2 ON (1) } $t1_cross_t2 \ + 2 { SELECT * FROM t1 %JOIN% t2 ON (0) } [list] \ + 3 { SELECT * FROM t1 %JOIN% t2 ON (NULL) } [list] \ + 4 { SELECT * FROM t1 %JOIN% t2 ON ('abc') } [list] \ + 5 { SELECT * FROM t1 %JOIN% t2 ON ('1ab') } $t1_cross_t2 \ + 6 { SELECT * FROM t1 %JOIN% t2 ON (0.9) } $t1_cross_t2 \ + 7 { SELECT * FROM t1 %JOIN% t2 ON ('0.9') } $t1_cross_t2 \ + 8 { SELECT * FROM t1 %JOIN% t2 ON (0.0) } [list] \ + \ + 9 { SELECT t1.b, t2.b FROM t1 %JOIN% t2 ON (t1.a = t2.a) } \ + {one I two II three III} \ + 10 { SELECT t1.b, t2.b FROM t1 %JOIN% t2 ON (t1.a = 'a') } \ + {one I one II one III} \ + 11 { SELECT t1.b, t2.b + FROM t1 %JOIN% t2 ON (CASE WHEN t1.a = 'a' THEN NULL ELSE 1 END) } \ + {two I two II two III three I three II three III} \ +] { + do_join_test e_select-1.3.$tn $select $res +} + +# EVIDENCE-OF: R-63358-54862 If there is a USING clause specified as +# part of the join-constraint, then each of the column names specified +# must exist in the datasets to both the left and right of the join-op. +# +do_select_tests e_select-1.4 -error { + cannot join using column %s - column not present in both tables +} { + 1 { SELECT * FROM t1, t3 USING (b) } "b" + 2 { SELECT * FROM t3, t1 USING (c) } "c" + 3 { SELECT * FROM t3, (SELECT a AS b, b AS c FROM t1) USING (a) } "a" +} + +# EVIDENCE-OF: R-55987-04584 For each pair of namesake columns, the +# expression "lhs.X = rhs.X" is evaluated for each row of the cartesian +# product as a boolean expression. All rows for which one or more of the +# expressions evaluates to false are excluded from the result set. +# +do_select_tests e_select-1.5 { + 1 { SELECT * FROM t1, t3 USING (a) } {a one 1 b two 2} + 2 { SELECT * FROM t3, t4 USING (a,c) } {b 2} +} + +# EVIDENCE-OF: R-54046-48600 When comparing values as a result of a +# USING clause, the normal rules for handling affinities, collation +# sequences and NULL values in comparisons apply. +# +# EVIDENCE-OF: R-35466-18578 The column from the dataset on the +# left-hand side of the join operator is considered to be on the +# left-hand side of the comparison operator (=) for the purposes of +# collation sequence and affinity precedence. +# +do_execsql_test e_select-1.6.0 { + CREATE TABLE t5(a COLLATE nocase, b COLLATE binary); + INSERT INTO t5 VALUES('AA', 'cc'); + INSERT INTO t5 VALUES('BB', 'dd'); + INSERT INTO t5 VALUES(NULL, NULL); + CREATE TABLE t6(a COLLATE binary, b COLLATE nocase); + INSERT INTO t6 VALUES('aa', 'cc'); + INSERT INTO t6 VALUES('bb', 'DD'); + INSERT INTO t6 VALUES(NULL, NULL); +} {} +foreach {tn select res} { + 1 { SELECT * FROM t5 %JOIN% t6 USING (a) } {AA cc cc BB dd DD} + 2 { SELECT * FROM t6 %JOIN% t5 USING (a) } {} + 3 { SELECT * FROM (SELECT a COLLATE nocase, b FROM t6) %JOIN% t5 USING (a) } + {aa cc cc bb DD dd} + 4 { SELECT * FROM t5 %JOIN% t6 USING (a,b) } {AA cc} + 5 { SELECT * FROM t6 %JOIN% t5 USING (a,b) } {} +} { + do_join_test e_select-1.6.$tn $select $res +} + +# EVIDENCE-OF: R-57047-10461 For each pair of columns identified by a +# USING clause, the column from the right-hand dataset is omitted from +# the joined dataset. +# +# EVIDENCE-OF: R-56132-15700 This is the only difference between a USING +# clause and its equivalent ON constraint. +# +foreach {tn select res} { + 1a { SELECT * FROM t1 %JOIN% t2 USING (a) } + {a one I b two II c three III} + 1b { SELECT * FROM t1 %JOIN% t2 ON (t1.a=t2.a) } + {a one a I b two b II c three c III} + + 2a { SELECT * FROM t3 %JOIN% t4 USING (a) } + {a 1 {} b 2 2} + 2b { SELECT * FROM t3 %JOIN% t4 ON (t3.a=t4.a) } + {a 1 a {} b 2 b 2} + + 3a { SELECT * FROM t3 %JOIN% t4 USING (a,c) } {b 2} + 3b { SELECT * FROM t3 %JOIN% t4 ON (t3.a=t4.a AND t3.c=t4.c) } {b 2 b 2} + + 4a { SELECT * FROM (SELECT a COLLATE nocase, b FROM t6) AS x + %JOIN% t5 USING (a) } + {aa cc cc bb DD dd} + 4b { SELECT * FROM (SELECT a COLLATE nocase, b FROM t6) AS x + %JOIN% t5 ON (x.a=t5.a) } + {aa cc AA cc bb DD BB dd} +} { + do_join_test e_select-1.7.$tn $select $res +} + +# EVIDENCE-OF: R-41434-12448 If the join-op is a "LEFT JOIN" or "LEFT +# OUTER JOIN", then after the ON or USING filtering clauses have been +# applied, an extra row is added to the output for each row in the +# original left-hand input dataset that corresponds to no rows at all in +# the composite dataset (if any). +# +do_execsql_test e_select-1.8.0 { + CREATE TABLE t7(a, b, c); + CREATE TABLE t8(a, d, e); + + INSERT INTO t7 VALUES('x', 'ex', 24); + INSERT INTO t7 VALUES('y', 'why', 25); + + INSERT INTO t8 VALUES('x', 'abc', 24); + INSERT INTO t8 VALUES('z', 'ghi', 26); +} {} + +do_select_tests e_select-1.8 { + 1a "SELECT count(*) FROM t7 JOIN t8 ON (t7.a=t8.a)" {1} + 1b "SELECT count(*) FROM t7 LEFT JOIN t8 ON (t7.a=t8.a)" {2} + 2a "SELECT count(*) FROM t7 JOIN t8 USING (a)" {1} + 2b "SELECT count(*) FROM t7 LEFT JOIN t8 USING (a)" {2} +} + + +# EVIDENCE-OF: R-15607-52988 The added rows contain NULL values in the +# columns that would normally contain values copied from the right-hand +# input dataset. +# +do_select_tests e_select-1.9 { + 1a "SELECT * FROM t7 JOIN t8 ON (t7.a=t8.a)" {x ex 24 x abc 24} + 1b "SELECT * FROM t7 LEFT JOIN t8 ON (t7.a=t8.a)" + {x ex 24 x abc 24 y why 25 {} {} {}} + 2a "SELECT * FROM t7 JOIN t8 USING (a)" {x ex 24 abc 24} + 2b "SELECT * FROM t7 LEFT JOIN t8 USING (a)" {x ex 24 abc 24 y why 25 {} {}} +} + +# EVIDENCE-OF: R-01809-52134 If the NATURAL keyword is added to any of +# the join-ops, then an implicit USING clause is added to the +# join-constraints. The implicit USING clause contains each of the +# column names that appear in both the left and right-hand input +# datasets. +# +do_select_tests e_select-1-10 { + 1a "SELECT * FROM t7 JOIN t8 USING (a)" {x ex 24 abc 24} + 1b "SELECT * FROM t7 NATURAL JOIN t8" {x ex 24 abc 24} + + 2a "SELECT * FROM t8 JOIN t7 USING (a)" {x abc 24 ex 24} + 2b "SELECT * FROM t8 NATURAL JOIN t7" {x abc 24 ex 24} + + 3a "SELECT * FROM t7 LEFT JOIN t8 USING (a)" {x ex 24 abc 24 y why 25 {} {}} + 3b "SELECT * FROM t7 NATURAL LEFT JOIN t8" {x ex 24 abc 24 y why 25 {} {}} + + 4a "SELECT * FROM t8 LEFT JOIN t7 USING (a)" {x abc 24 ex 24 z ghi 26 {} {}} + 4b "SELECT * FROM t8 NATURAL LEFT JOIN t7" {x abc 24 ex 24 z ghi 26 {} {}} + + 5a "SELECT * FROM t3 JOIN t4 USING (a,c)" {b 2} + 5b "SELECT * FROM t3 NATURAL JOIN t4" {b 2} + + 6a "SELECT * FROM t3 LEFT JOIN t4 USING (a,c)" {a 1 b 2} + 6b "SELECT * FROM t3 NATURAL LEFT JOIN t4" {a 1 b 2} +} + +# EVIDENCE-OF: R-49566-01570 If the left and right-hand input datasets +# feature no common column names, then the NATURAL keyword has no effect +# on the results of the join. +# +do_execsql_test e_select-1.11.0 { + CREATE TABLE t10(x, y); + INSERT INTO t10 VALUES(1, 'true'); + INSERT INTO t10 VALUES(0, 'false'); +} {} +do_select_tests e_select-1-11 { + 1a "SELECT a, x FROM t1 CROSS JOIN t10" {a 1 a 0 b 1 b 0 c 1 c 0} + 1b "SELECT a, x FROM t1 NATURAL CROSS JOIN t10" {a 1 a 0 b 1 b 0 c 1 c 0} +} + +# EVIDENCE-OF: R-39625-59133 A USING or ON clause may not be added to a +# join that specifies the NATURAL keyword. +# +foreach {tn sql} { + 1 {SELECT * FROM t1 NATURAL LEFT JOIN t2 USING (a)} + 2 {SELECT * FROM t1 NATURAL LEFT JOIN t2 ON (t1.a=t2.a)} + 3 {SELECT * FROM t1 NATURAL LEFT JOIN t2 ON (45)} +} { + do_catchsql_test e_select-1.12.$tn " + $sql + " {1 {a NATURAL join may not have an ON or USING clause}} +} + +#------------------------------------------------------------------------- +# The next block of tests - e_select-3.* - concentrate on verifying +# statements made regarding WHERE clause processing. +# +drop_all_tables +do_execsql_test e_select-3.0 { + CREATE TABLE x1(k, x, y, z); + INSERT INTO x1 VALUES(1, 'relinquished', 'aphasia', 78.43); + INSERT INTO x1 VALUES(2, X'A8E8D66F', X'07CF', -81); + INSERT INTO x1 VALUES(3, -22, -27.57, NULL); + INSERT INTO x1 VALUES(4, NULL, 'bygone', 'picky'); + INSERT INTO x1 VALUES(5, NULL, 96.28, NULL); + INSERT INTO x1 VALUES(6, 0, 1, 2); + + CREATE TABLE x2(k, x, y2); + INSERT INTO x2 VALUES(1, 50, X'B82838'); + INSERT INTO x2 VALUES(5, 84.79, 65.88); + INSERT INTO x2 VALUES(3, -22, X'0E1BE452A393'); + INSERT INTO x2 VALUES(7, 'mistrusted', 'standardized'); +} {} + +# EVIDENCE-OF: R-06999-14330 If a WHERE clause is specified, the WHERE +# expression is evaluated for each row in the input data as a boolean +# expression. All rows for which the WHERE clause expression evaluates +# to false are excluded from the dataset before continuing. +# +do_execsql_test e_select-3.1.1 { SELECT k FROM x1 WHERE x } {3} +do_execsql_test e_select-3.1.2 { SELECT k FROM x1 WHERE y } {3 5 6} +do_execsql_test e_select-3.1.3 { SELECT k FROM x1 WHERE z } {1 2 6} +do_execsql_test e_select-3.1.4 { SELECT k FROM x1 WHERE '1'||z } {1 2 4 6} +do_execsql_test e_select-3.1.5 { SELECT k FROM x1 WHERE x IS NULL } {4 5} +do_execsql_test e_select-3.1.6 { SELECT k FROM x1 WHERE z - 78.43 } {2 4 6} + +do_execsql_test e_select-3.2.1a { + SELECT k FROM x1 LEFT JOIN x2 USING(k) +} {1 2 3 4 5 6} +do_execsql_test e_select-3.2.1b { + SELECT k FROM x1 LEFT JOIN x2 USING(k) WHERE x2.k +} {1 3 5} +do_execsql_test e_select-3.2.2 { + SELECT k FROM x1 LEFT JOIN x2 USING(k) WHERE x2.k IS NULL +} {2 4 6} + +do_execsql_test e_select-3.2.3 { + SELECT k FROM x1 NATURAL JOIN x2 WHERE x2.k +} {3} +do_execsql_test e_select-3.2.4 { + SELECT k FROM x1 NATURAL JOIN x2 WHERE x2.k-3 +} {} + +#------------------------------------------------------------------------- +# Tests below this point are focused on verifying the testable statements +# related to caculating the result rows of a simple SELECT statement. +# + +drop_all_tables +do_execsql_test e_select-4.0 { + CREATE TABLE z1(a, b, c); + CREATE TABLE z2(d, e); + CREATE TABLE z3(a, b); + + INSERT INTO z1 VALUES(51.65, -59.58, 'belfries'); + INSERT INTO z1 VALUES(-5, NULL, 75); + INSERT INTO z1 VALUES(-2.2, -23.18, 'suiters'); + INSERT INTO z1 VALUES(NULL, 67, 'quartets'); + INSERT INTO z1 VALUES(-1.04, -32.3, 'aspen'); + INSERT INTO z1 VALUES(63, 'born', -26); + + INSERT INTO z2 VALUES(NULL, 21); + INSERT INTO z2 VALUES(36, 6); + + INSERT INTO z3 VALUES('subsistence', 'gauze'); + INSERT INTO z3 VALUES(49.17, -67); +} {} + +# EVIDENCE-OF: R-36327-17224 If a result expression is the special +# expression "*" then all columns in the input data are substituted for +# that one expression. +# +# EVIDENCE-OF: R-43693-30522 If the expression is the alias of a table +# or subquery in the FROM clause followed by ".*" then all columns from +# the named table or subquery are substituted for the single expression. +# +do_select_tests e_select-4.1 { + 1 "SELECT * FROM z1 LIMIT 1" {51.65 -59.58 belfries} + 2 "SELECT * FROM z1,z2 LIMIT 1" {51.65 -59.58 belfries {} 21} + 3 "SELECT z1.* FROM z1,z2 LIMIT 1" {51.65 -59.58 belfries} + 4 "SELECT z2.* FROM z1,z2 LIMIT 1" {{} 21} + 5 "SELECT z2.*, z1.* FROM z1,z2 LIMIT 1" {{} 21 51.65 -59.58 belfries} + + 6 "SELECT count(*), * FROM z1" {6 63 born -26} + 7 "SELECT max(a), * FROM z1" {63 63 born -26} + 8 "SELECT *, min(a) FROM z1" {63 born -26 -5} + + 9 "SELECT *,* FROM z1,z2 LIMIT 1" { + 51.65 -59.58 belfries {} 21 51.65 -59.58 belfries {} 21 + } + 10 "SELECT z1.*,z1.* FROM z2,z1 LIMIT 1" { + 51.65 -59.58 belfries 51.65 -59.58 belfries + } +} + +# EVIDENCE-OF: R-61869-22578 It is an error to use a "*" or "alias.*" +# expression in any context other than than a result expression list. +# +# EVIDENCE-OF: R-44324-41166 It is also an error to use a "*" or +# "alias.*" expression in a simple SELECT query that does not have a +# FROM clause. +# +foreach {tn select err} { + 1.1 "SELECT a, b, c FROM z1 WHERE *" {near "*": syntax error} + 1.2 "SELECT a, b, c FROM z1 GROUP BY *" {near "*": syntax error} + 1.3 "SELECT 1 + * FROM z1" {near "*": syntax error} + 1.4 "SELECT * + 1 FROM z1" {near "+": syntax error} + + 2.1 "SELECT *" {no tables specified} + 2.2 "SELECT * WHERE 1" {no tables specified} + 2.3 "SELECT * WHERE 0" {no tables specified} + 2.4 "SELECT count(*), *" {no tables specified} +} { + do_catchsql_test e_select-4.2.$tn $select [list 1 $err] +} + +# EVIDENCE-OF: R-08669-22397 The number of columns in the rows returned +# by a simple SELECT statement is equal to the number of expressions in +# the result expression list after substitution of * and alias.* +# expressions. +# +foreach {tn select nCol} { + 1 "SELECT * FROM z1" 3 + 2 "SELECT * FROM z1 NATURAL JOIN z3" 3 + 3 "SELECT z1.* FROM z1 NATURAL JOIN z3" 3 + 4 "SELECT z3.* FROM z1 NATURAL JOIN z3" 2 + 5 "SELECT z1.*, z3.* FROM z1 NATURAL JOIN z3" 5 + 6 "SELECT 1, 2, z1.* FROM z1" 5 + 7 "SELECT a, *, b, c FROM z1" 6 +} { + set ::stmt [sqlite3_prepare_v2 db $select -1 DUMMY] + do_test e_select-4.3.$tn { sqlite3_column_count $::stmt } $nCol + sqlite3_finalize $::stmt +} + + + +# In lang_select.html, a non-aggregate query is defined as any simple SELECT +# that has no GROUP BY clause and no aggregate expressions in the result +# expression list. Other queries are aggregate queries. Test cases +# e_select-4.4.* through e_select-4.12.*, inclusive, which test the part of +# simple SELECT that is different for aggregate and non-aggregate queries +# verify (in a way) that these definitions are consistent: +# +# EVIDENCE-OF: R-20637-43463 A simple SELECT statement is an aggregate +# query if it contains either a GROUP BY clause or one or more aggregate +# functions in the result-set. +# +# EVIDENCE-OF: R-23155-55597 Otherwise, if a simple SELECT contains no +# aggregate functions or a GROUP BY clause, it is a non-aggregate query. +# + +# EVIDENCE-OF: R-44050-47362 If the SELECT statement is a non-aggregate +# query, then each expression in the result expression list is evaluated +# for each row in the dataset filtered by the WHERE clause. +# +do_select_tests e_select-4.4 { + 1 "SELECT a, b FROM z1" + {51.65 -59.58 -5 {} -2.2 -23.18 {} 67 -1.04 -32.3 63 born} + + 2 "SELECT a IS NULL, b+1, * FROM z1" { + 0 -58.58 51.65 -59.58 belfries + 0 {} -5 {} 75 + 0 -22.18 -2.2 -23.18 suiters + 1 68 {} 67 quartets + 0 -31.3 -1.04 -32.3 aspen + 0 1 63 born -26 + } + + 3 "SELECT 32*32, d||e FROM z2" {1024 {} 1024 366} +} + + +# Test cases e_select-4.5.* and e_select-4.6.* together show that: +# +# EVIDENCE-OF: R-51988-01124 The single row of result-set data created +# by evaluating the aggregate and non-aggregate expressions in the +# result-set forms the result of an aggregate query without a GROUP BY +# clause. +# + +# EVIDENCE-OF: R-57629-25253 If the SELECT statement is an aggregate +# query without a GROUP BY clause, then each aggregate expression in the +# result-set is evaluated once across the entire dataset. +# +do_select_tests e_select-4.5 { + 1 "SELECT count(a), max(a), count(b), max(b) FROM z1" {5 63 5 born} + 2 "SELECT count(*), max(1)" {1 1} + + 3 "SELECT sum(b+1) FROM z1 NATURAL LEFT JOIN z3" {-43.06} + 4 "SELECT sum(b+2) FROM z1 NATURAL LEFT JOIN z3" {-38.06} + 5 "SELECT sum(b IS NOT NULL) FROM z1 NATURAL LEFT JOIN z3" {5} +} + +# EVIDENCE-OF: R-26684-40576 Each non-aggregate expression in the +# result-set is evaluated once for an arbitrarily selected row of the +# dataset. +# +# EVIDENCE-OF: R-27994-60376 The same arbitrarily selected row is used +# for each non-aggregate expression. +# +# Note: The results of many of the queries in this block of tests are +# technically undefined, as the documentation does not specify which row +# SQLite will arbitrarily select to use for the evaluation of the +# non-aggregate expressions. +# +drop_all_tables +do_execsql_test e_select-4.6.0 { + CREATE TABLE a1(one PRIMARY KEY, two); + INSERT INTO a1 VALUES(1, 1); + INSERT INTO a1 VALUES(2, 3); + INSERT INTO a1 VALUES(3, 6); + INSERT INTO a1 VALUES(4, 10); + + CREATE TABLE a2(one PRIMARY KEY, three); + INSERT INTO a2 VALUES(1, 1); + INSERT INTO a2 VALUES(3, 2); + INSERT INTO a2 VALUES(6, 3); + INSERT INTO a2 VALUES(10, 4); +} {} +do_select_tests e_select-4.6 { + 1 "SELECT one, two, count(*) FROM a1" {4 10 4} + 2 "SELECT one, two, count(*) FROM a1 WHERE one<3" {2 3 2} + 3 "SELECT one, two, count(*) FROM a1 WHERE one>3" {4 10 1} + 4 "SELECT *, count(*) FROM a1 JOIN a2" {4 10 10 4 16} + 5 "SELECT *, sum(three) FROM a1 NATURAL JOIN a2" {3 6 2 3} + 6 "SELECT *, sum(three) FROM a1 NATURAL JOIN a2" {3 6 2 3} + 7 "SELECT group_concat(three, ''), a1.* FROM a1 NATURAL JOIN a2" {12 3 6} +} + +# EVIDENCE-OF: R-04486-07266 Or, if the dataset contains zero rows, then +# each non-aggregate expression is evaluated against a row consisting +# entirely of NULL values. +# +do_select_tests e_select-4.7 { + 1 "SELECT one, two, count(*) FROM a1 WHERE 0" {{} {} 0} + 2 "SELECT sum(two), * FROM a1, a2 WHERE three>5" {{} {} {} {} {}} + 3 "SELECT max(one) IS NULL, one IS NULL, two IS NULL FROM a1 WHERE two=7" { + 1 1 1 + } +} + +# EVIDENCE-OF: R-64138-28774 An aggregate query without a GROUP BY +# clause always returns exactly one row of data, even if there are zero +# rows of input data. +# +foreach {tn select} { + 8.1 "SELECT count(*) FROM a1" + 8.2 "SELECT count(*) FROM a1 WHERE 0" + 8.3 "SELECT count(*) FROM a1 WHERE 1" + 8.4 "SELECT max(a1.one)+min(two), a1.one, two, * FROM a1, a2 WHERE 1" + 8.5 "SELECT max(a1.one)+min(two), a1.one, two, * FROM a1, a2 WHERE 0" +} { + # Set $nRow to the number of rows returned by $select: + set ::stmt [sqlite3_prepare_v2 db $select -1 DUMMY] + set nRow 0 + while {"SQLITE_ROW" == [sqlite3_step $::stmt]} { incr nRow } + set rc [sqlite3_finalize $::stmt] + + # Test that $nRow==1 and that statement execution was successful + # (rc==SQLITE_OK). + do_test e_select-4.$tn [list list $rc $nRow] {SQLITE_OK 1} +} + +drop_all_tables +do_execsql_test e_select-4.9.0 { + CREATE TABLE b1(one PRIMARY KEY, two); + INSERT INTO b1 VALUES(1, 'o'); + INSERT INTO b1 VALUES(4, 'f'); + INSERT INTO b1 VALUES(3, 't'); + INSERT INTO b1 VALUES(2, 't'); + INSERT INTO b1 VALUES(5, 'f'); + INSERT INTO b1 VALUES(7, 's'); + INSERT INTO b1 VALUES(6, 's'); + + CREATE TABLE b2(x, y); + INSERT INTO b2 VALUES(NULL, 0); + INSERT INTO b2 VALUES(NULL, 1); + INSERT INTO b2 VALUES('xyz', 2); + INSERT INTO b2 VALUES('abc', 3); + INSERT INTO b2 VALUES('xyz', 4); + + CREATE TABLE b3(a COLLATE nocase, b COLLATE binary); + INSERT INTO b3 VALUES('abc', 'abc'); + INSERT INTO b3 VALUES('aBC', 'aBC'); + INSERT INTO b3 VALUES('Def', 'Def'); + INSERT INTO b3 VALUES('dEF', 'dEF'); +} {} + +# EVIDENCE-OF: R-57754-57109 If the SELECT statement is an aggregate +# query with a GROUP BY clause, then each of the expressions specified +# as part of the GROUP BY clause is evaluated for each row of the +# dataset. Each row is then assigned to a "group" based on the results; +# rows for which the results of evaluating the GROUP BY expressions are +# the same are assigned to the same group. +# +# These tests also show that the following is not untrue: +# +# EVIDENCE-OF: R-25883-55063 The expressions in the GROUP BY clause do +# not have to be expressions that appear in the result. +# +do_select_tests e_select-4.9 { + 1 "SELECT group_concat(one), two FROM b1 GROUP BY two" { + 4,5 f 1 o 7,6 s 3,2 t + } + 2 "SELECT group_concat(one), sum(one) FROM b1 GROUP BY (one>4)" { + 1,4,3,2 10 5,7,6 18 + } + 3 "SELECT group_concat(one) FROM b1 GROUP BY (two>'o'), one%2" { + 4 1,5 2,6 3,7 + } + 4 "SELECT group_concat(one) FROM b1 GROUP BY (one==2 OR two=='o')" { + 4,3,5,7,6 1,2 + } +} + +# EVIDENCE-OF: R-14926-50129 For the purposes of grouping rows, NULL +# values are considered equal. +# +do_select_tests e_select-4.10 { + 1 "SELECT group_concat(y) FROM b2 GROUP BY x" {0,1 3 2,4} + 2 "SELECT count(*) FROM b2 GROUP BY CASE WHEN y<4 THEN NULL ELSE 0 END" {4 1} +} + +# EVIDENCE-OF: R-10470-30318 The usual rules for selecting a collation +# sequence with which to compare text values apply when evaluating +# expressions in a GROUP BY clause. +# +do_select_tests e_select-4.11 { + 1 "SELECT count(*) FROM b3 GROUP BY b" {1 1 1 1} + 2 "SELECT count(*) FROM b3 GROUP BY a" {2 2} + 3 "SELECT count(*) FROM b3 GROUP BY +b" {1 1 1 1} + 4 "SELECT count(*) FROM b3 GROUP BY +a" {2 2} + 5 "SELECT count(*) FROM b3 GROUP BY b||''" {1 1 1 1} + 6 "SELECT count(*) FROM b3 GROUP BY a||''" {1 1 1 1} +} + +# EVIDENCE-OF: R-63573-50730 The expressions in a GROUP BY clause may +# not be aggregate expressions. +# +foreach {tn select} { + 12.1 "SELECT * FROM b3 GROUP BY count(*)" + 12.2 "SELECT max(a) FROM b3 GROUP BY max(b)" + 12.3 "SELECT group_concat(a) FROM b3 GROUP BY a, max(b)" +} { + set res {1 {aggregate functions are not allowed in the GROUP BY clause}} + do_catchsql_test e_select-4.$tn $select $res +} + +# EVIDENCE-OF: R-31537-00101 If a HAVING clause is specified, it is +# evaluated once for each group of rows as a boolean expression. If the +# result of evaluating the HAVING clause is false, the group is +# discarded. +# +# This requirement is tested by all e_select-4.13.* tests. +# +# EVIDENCE-OF: R-04132-09474 If the HAVING clause is an aggregate +# expression, it is evaluated across all rows in the group. +# +# Tested by e_select-4.13.1.* +# +# EVIDENCE-OF: R-28262-47447 If a HAVING clause is a non-aggregate +# expression, it is evaluated with respect to an arbitrarily selected +# row from the group. +# +# Tested by e_select-4.13.2.* +# +# Tests in this block also show that this is not untrue: +# +# EVIDENCE-OF: R-55403-13450 The HAVING expression may refer to values, +# even aggregate functions, that are not in the result. +# +do_execsql_test e_select-4.13.0 { + CREATE TABLE c1(up, down); + INSERT INTO c1 VALUES('x', 1); + INSERT INTO c1 VALUES('x', 2); + INSERT INTO c1 VALUES('x', 4); + INSERT INTO c1 VALUES('x', 8); + INSERT INTO c1 VALUES('y', 16); + INSERT INTO c1 VALUES('y', 32); + + CREATE TABLE c2(i, j); + INSERT INTO c2 VALUES(1, 0); + INSERT INTO c2 VALUES(2, 1); + INSERT INTO c2 VALUES(3, 3); + INSERT INTO c2 VALUES(4, 6); + INSERT INTO c2 VALUES(5, 10); + INSERT INTO c2 VALUES(6, 15); + INSERT INTO c2 VALUES(7, 21); + INSERT INTO c2 VALUES(8, 28); + INSERT INTO c2 VALUES(9, 36); + + CREATE TABLE c3(i PRIMARY KEY, k TEXT); + INSERT INTO c3 VALUES(1, 'hydrogen'); + INSERT INTO c3 VALUES(2, 'helium'); + INSERT INTO c3 VALUES(3, 'lithium'); + INSERT INTO c3 VALUES(4, 'beryllium'); + INSERT INTO c3 VALUES(5, 'boron'); + INSERT INTO c3 VALUES(94, 'plutonium'); +} {} + +do_select_tests e_select-4.13 { + 1.1 "SELECT up FROM c1 GROUP BY up HAVING count(*)>3" {x} + 1.2 "SELECT up FROM c1 GROUP BY up HAVING sum(down)>16" {y} + 1.3 "SELECT up FROM c1 GROUP BY up HAVING sum(down)<16" {x} + 1.4 "SELECT up||down FROM c1 GROUP BY (down<5) HAVING max(down)<10" {x4} + + 2.1 "SELECT up FROM c1 GROUP BY up HAVING down>10" {y} + 2.2 "SELECT up FROM c1 GROUP BY up HAVING up='y'" {y} + + 2.3 "SELECT i, j FROM c2 GROUP BY i>4 HAVING i>6" {9 36} +} + +# EVIDENCE-OF: R-23927-54081 Each expression in the result-set is then +# evaluated once for each group of rows. +# +# EVIDENCE-OF: R-53735-47017 If the expression is an aggregate +# expression, it is evaluated across all rows in the group. +# +do_select_tests e_select-4.15 { + 1 "SELECT sum(down) FROM c1 GROUP BY up" {15 48} + 2 "SELECT sum(j), max(j) FROM c2 GROUP BY (i%3)" {54 36 27 21 39 28} + 3 "SELECT sum(j), max(j) FROM c2 GROUP BY (j%2)" {80 36 40 21} + 4 "SELECT 1+sum(j), max(j)+1 FROM c2 GROUP BY (j%2)" {81 37 41 22} + 5 "SELECT count(*), round(avg(i),2) FROM c1, c2 ON (i=down) GROUP BY j%2" + {3 4.33 1 2.0} +} + +# EVIDENCE-OF: R-62913-19830 Otherwise, it is evaluated against a single +# arbitrarily chosen row from within the group. +# +# EVIDENCE-OF: R-53924-08809 If there is more than one non-aggregate +# expression in the result-set, then all such expressions are evaluated +# for the same row. +# +do_select_tests e_select-4.15 { + 1 "SELECT i, j FROM c2 GROUP BY i%2" {8 28 9 36} + 2 "SELECT i, j FROM c2 GROUP BY i%2 HAVING j<30" {8 28} + 3 "SELECT i, j FROM c2 GROUP BY i%2 HAVING j>30" {9 36} + 4 "SELECT i, j FROM c2 GROUP BY i%2 HAVING j>30" {9 36} + 5 "SELECT count(*), i, k FROM c2 NATURAL JOIN c3 GROUP BY substr(k, 1, 1)" + {2 5 boron 2 2 helium 1 3 lithium} +} + +# EVIDENCE-OF: R-19334-12811 Each group of input dataset rows +# contributes a single row to the set of result rows. +# +# EVIDENCE-OF: R-02223-49279 Subject to filtering associated with the +# DISTINCT keyword, the number of rows returned by an aggregate query +# with a GROUP BY clause is the same as the number of groups of rows +# produced by applying the GROUP BY and HAVING clauses to the filtered +# input dataset. +# +do_select_tests e_select.4.16 -count { + 1 "SELECT i, j FROM c2 GROUP BY i%2" 2 + 2 "SELECT i, j FROM c2 GROUP BY i" 9 + 3 "SELECT i, j FROM c2 GROUP BY i HAVING i<5" 4 +} + +#------------------------------------------------------------------------- +# The following tests attempt to verify statements made regarding the ALL +# and DISTINCT keywords. +# +drop_all_tables +do_execsql_test e_select-5.1.0 { + CREATE TABLE h1(a, b); + INSERT INTO h1 VALUES(1, 'one'); + INSERT INTO h1 VALUES(1, 'I'); + INSERT INTO h1 VALUES(1, 'i'); + INSERT INTO h1 VALUES(4, 'four'); + INSERT INTO h1 VALUES(4, 'IV'); + INSERT INTO h1 VALUES(4, 'iv'); + + CREATE TABLE h2(x COLLATE nocase); + INSERT INTO h2 VALUES('One'); + INSERT INTO h2 VALUES('Two'); + INSERT INTO h2 VALUES('Three'); + INSERT INTO h2 VALUES('Four'); + INSERT INTO h2 VALUES('one'); + INSERT INTO h2 VALUES('two'); + INSERT INTO h2 VALUES('three'); + INSERT INTO h2 VALUES('four'); + + CREATE TABLE h3(c, d); + INSERT INTO h3 VALUES(1, NULL); + INSERT INTO h3 VALUES(2, NULL); + INSERT INTO h3 VALUES(3, NULL); + INSERT INTO h3 VALUES(4, '2'); + INSERT INTO h3 VALUES(5, NULL); + INSERT INTO h3 VALUES(6, '2,3'); + INSERT INTO h3 VALUES(7, NULL); + INSERT INTO h3 VALUES(8, '2,4'); + INSERT INTO h3 VALUES(9, '3'); +} {} + +# EVIDENCE-OF: R-60770-10612 One of the ALL or DISTINCT keywords may +# follow the SELECT keyword in a simple SELECT statement. +# +do_select_tests e_select-5.1 { + 1 "SELECT ALL a FROM h1" {1 1 1 4 4 4} + 2 "SELECT DISTINCT a FROM h1" {1 4} +} + +# EVIDENCE-OF: R-08861-34280 If the simple SELECT is a SELECT ALL, then +# the entire set of result rows are returned by the SELECT. +# +# EVIDENCE-OF: R-47911-02086 If neither ALL or DISTINCT are present, +# then the behaviour is as if ALL were specified. +# +# EVIDENCE-OF: R-14442-41305 If the simple SELECT is a SELECT DISTINCT, +# then duplicate rows are removed from the set of result rows before it +# is returned. +# +# The three testable statements above are tested by e_select-5.2.*, +# 5.3.* and 5.4.* respectively. +# +do_select_tests e_select-5 { + 3.1 "SELECT ALL x FROM h2" {One Two Three Four one two three four} + 3.2 "SELECT ALL x FROM h1, h2 ON (x=b)" {One one Four four} + + 3.1 "SELECT x FROM h2" {One Two Three Four one two three four} + 3.2 "SELECT x FROM h1, h2 ON (x=b)" {One one Four four} + + 4.1 "SELECT DISTINCT x FROM h2" {four one three two} + 4.2 "SELECT DISTINCT x FROM h1, h2 ON (x=b)" {four one} +} + +# EVIDENCE-OF: R-02054-15343 For the purposes of detecting duplicate +# rows, two NULL values are considered to be equal. +# +do_select_tests e_select-5.5 { + 1 "SELECT DISTINCT d FROM h3" {{} 2 2,3 2,4 3} +} + +# EVIDENCE-OF: R-58359-52112 The normal rules for selecting a collation +# sequence to compare text values with apply. +# +do_select_tests e_select-5.6 { + 1 "SELECT DISTINCT b FROM h1" {I IV four i iv one} + 2 "SELECT DISTINCT b COLLATE nocase FROM h1" {four i iv one} + 3 "SELECT DISTINCT x FROM h2" {four one three two} + 4 "SELECT DISTINCT x COLLATE binary FROM h2" { + Four One Three Two four one three two + } +} + +#------------------------------------------------------------------------- +# The following tests - e_select-7.* - test that statements made to do +# with compound SELECT statements are correct. +# + +# EVIDENCE-OF: R-39368-64333 In a compound SELECT, all the constituent +# SELECTs must return the same number of result columns. +# +# All the other tests in this section use compound SELECTs created +# using component SELECTs that do return the same number of columns. +# So the tests here just show that it is an error to attempt otherwise. +# +drop_all_tables +do_execsql_test e_select-7.1.0 { + CREATE TABLE j1(a, b, c); + CREATE TABLE j2(e, f); + CREATE TABLE j3(g); +} {} +do_select_tests e_select-7.1 -error { + SELECTs to the left and right of %s do not have the same number of result columns +} { + 1 "SELECT a, b FROM j1 UNION ALL SELECT g FROM j3" {{UNION ALL}} + 2 "SELECT * FROM j1 UNION ALL SELECT * FROM j3" {{UNION ALL}} + 3 "SELECT a, b FROM j1 UNION ALL SELECT g FROM j3" {{UNION ALL}} + 4 "SELECT a, b FROM j1 UNION ALL SELECT * FROM j3,j2" {{UNION ALL}} + 5 "SELECT * FROM j3,j2 UNION ALL SELECT a, b FROM j1" {{UNION ALL}} + + 6 "SELECT a, b FROM j1 UNION SELECT g FROM j3" {UNION} + 7 "SELECT * FROM j1 UNION SELECT * FROM j3" {UNION} + 8 "SELECT a, b FROM j1 UNION SELECT g FROM j3" {UNION} + 9 "SELECT a, b FROM j1 UNION SELECT * FROM j3,j2" {UNION} + 10 "SELECT * FROM j3,j2 UNION SELECT a, b FROM j1" {UNION} + + 11 "SELECT a, b FROM j1 INTERSECT SELECT g FROM j3" {INTERSECT} + 12 "SELECT * FROM j1 INTERSECT SELECT * FROM j3" {INTERSECT} + 13 "SELECT a, b FROM j1 INTERSECT SELECT g FROM j3" {INTERSECT} + 14 "SELECT a, b FROM j1 INTERSECT SELECT * FROM j3,j2" {INTERSECT} + 15 "SELECT * FROM j3,j2 INTERSECT SELECT a, b FROM j1" {INTERSECT} + + 16 "SELECT a, b FROM j1 EXCEPT SELECT g FROM j3" {EXCEPT} + 17 "SELECT * FROM j1 EXCEPT SELECT * FROM j3" {EXCEPT} + 18 "SELECT a, b FROM j1 EXCEPT SELECT g FROM j3" {EXCEPT} + 19 "SELECT a, b FROM j1 EXCEPT SELECT * FROM j3,j2" {EXCEPT} + 20 "SELECT * FROM j3,j2 EXCEPT SELECT a, b FROM j1" {EXCEPT} +} + +# EVIDENCE-OF: R-01450-11152 As the components of a compound SELECT must +# be simple SELECT statements, they may not contain ORDER BY or LIMIT +# clauses. +# +foreach {tn select op1 op2} { + 1 "SELECT * FROM j1 ORDER BY a UNION ALL SELECT * FROM j2,j3" + {ORDER BY} {UNION ALL} + 2 "SELECT count(*) FROM j1 ORDER BY 1 UNION ALL SELECT max(e) FROM j2" + {ORDER BY} {UNION ALL} + 3 "SELECT count(*), * FROM j1 ORDER BY 1,2,3 UNION ALL SELECT *,* FROM j2" + {ORDER BY} {UNION ALL} + 4 "SELECT * FROM j1 LIMIT 10 UNION ALL SELECT * FROM j2,j3" + LIMIT {UNION ALL} + 5 "SELECT * FROM j1 LIMIT 10 OFFSET 5 UNION ALL SELECT * FROM j2,j3" + LIMIT {UNION ALL} + 6 "SELECT a FROM j1 LIMIT (SELECT e FROM j2) UNION ALL SELECT g FROM j2,j3" + LIMIT {UNION ALL} + + 7 "SELECT * FROM j1 ORDER BY a UNION SELECT * FROM j2,j3" + {ORDER BY} {UNION} + 8 "SELECT count(*) FROM j1 ORDER BY 1 UNION SELECT max(e) FROM j2" + {ORDER BY} {UNION} + 9 "SELECT count(*), * FROM j1 ORDER BY 1,2,3 UNION SELECT *,* FROM j2" + {ORDER BY} {UNION} + 10 "SELECT * FROM j1 LIMIT 10 UNION SELECT * FROM j2,j3" + LIMIT {UNION} + 11 "SELECT * FROM j1 LIMIT 10 OFFSET 5 UNION SELECT * FROM j2,j3" + LIMIT {UNION} + 12 "SELECT a FROM j1 LIMIT (SELECT e FROM j2) UNION SELECT g FROM j2,j3" + LIMIT {UNION} + + 13 "SELECT * FROM j1 ORDER BY a EXCEPT SELECT * FROM j2,j3" + {ORDER BY} {EXCEPT} + 14 "SELECT count(*) FROM j1 ORDER BY 1 EXCEPT SELECT max(e) FROM j2" + {ORDER BY} {EXCEPT} + 15 "SELECT count(*), * FROM j1 ORDER BY 1,2,3 EXCEPT SELECT *,* FROM j2" + {ORDER BY} {EXCEPT} + 16 "SELECT * FROM j1 LIMIT 10 EXCEPT SELECT * FROM j2,j3" + LIMIT {EXCEPT} + 17 "SELECT * FROM j1 LIMIT 10 OFFSET 5 EXCEPT SELECT * FROM j2,j3" + LIMIT {EXCEPT} + 18 "SELECT a FROM j1 LIMIT (SELECT e FROM j2) EXCEPT SELECT g FROM j2,j3" + LIMIT {EXCEPT} + + 19 "SELECT * FROM j1 ORDER BY a INTERSECT SELECT * FROM j2,j3" + {ORDER BY} {INTERSECT} + 20 "SELECT count(*) FROM j1 ORDER BY 1 INTERSECT SELECT max(e) FROM j2" + {ORDER BY} {INTERSECT} + 21 "SELECT count(*), * FROM j1 ORDER BY 1,2,3 INTERSECT SELECT *,* FROM j2" + {ORDER BY} {INTERSECT} + 22 "SELECT * FROM j1 LIMIT 10 INTERSECT SELECT * FROM j2,j3" + LIMIT {INTERSECT} + 23 "SELECT * FROM j1 LIMIT 10 OFFSET 5 INTERSECT SELECT * FROM j2,j3" + LIMIT {INTERSECT} + 24 "SELECT a FROM j1 LIMIT (SELECT e FROM j2) INTERSECT SELECT g FROM j2,j3" + LIMIT {INTERSECT} +} { + set err "$op1 clause should come after $op2 not before" + do_catchsql_test e_select-7.2.$tn $select [list 1 $err] +} + +# EVIDENCE-OF: R-22874-32655 ORDER BY and LIMIT clauses may only occur +# at the end of the entire compound SELECT. +# +foreach {tn select} { + 1 "SELECT * FROM j1 UNION ALL SELECT * FROM j2,j3 ORDER BY a" + 2 "SELECT count(*) FROM j1 UNION ALL SELECT max(e) FROM j2 ORDER BY 1" + 3 "SELECT count(*), * FROM j1 UNION ALL SELECT *,* FROM j2 ORDER BY 1,2,3" + 4 "SELECT * FROM j1 UNION ALL SELECT * FROM j2,j3 LIMIT 10" + 5 "SELECT * FROM j1 UNION ALL SELECT * FROM j2,j3 LIMIT 10 OFFSET 5" + 6 "SELECT a FROM j1 UNION ALL SELECT g FROM j2,j3 LIMIT (SELECT 10)" + + 7 "SELECT * FROM j1 UNION SELECT * FROM j2,j3 ORDER BY a" + 8 "SELECT count(*) FROM j1 UNION SELECT max(e) FROM j2 ORDER BY 1" + 9 "SELECT count(*), * FROM j1 UNION SELECT *,* FROM j2 ORDER BY 1,2,3" + 10 "SELECT * FROM j1 UNION SELECT * FROM j2,j3 LIMIT 10" + 11 "SELECT * FROM j1 UNION SELECT * FROM j2,j3 LIMIT 10 OFFSET 5" + 12 "SELECT a FROM j1 UNION SELECT g FROM j2,j3 LIMIT (SELECT 10)" + + 13 "SELECT * FROM j1 EXCEPT SELECT * FROM j2,j3 ORDER BY a" + 14 "SELECT count(*) FROM j1 EXCEPT SELECT max(e) FROM j2 ORDER BY 1" + 15 "SELECT count(*), * FROM j1 EXCEPT SELECT *,* FROM j2 ORDER BY 1,2,3" + 16 "SELECT * FROM j1 EXCEPT SELECT * FROM j2,j3 LIMIT 10" + 17 "SELECT * FROM j1 EXCEPT SELECT * FROM j2,j3 LIMIT 10 OFFSET 5" + 18 "SELECT a FROM j1 EXCEPT SELECT g FROM j2,j3 LIMIT (SELECT 10)" + + 19 "SELECT * FROM j1 INTERSECT SELECT * FROM j2,j3 ORDER BY a" + 20 "SELECT count(*) FROM j1 INTERSECT SELECT max(e) FROM j2 ORDER BY 1" + 21 "SELECT count(*), * FROM j1 INTERSECT SELECT *,* FROM j2 ORDER BY 1,2,3" + 22 "SELECT * FROM j1 INTERSECT SELECT * FROM j2,j3 LIMIT 10" + 23 "SELECT * FROM j1 INTERSECT SELECT * FROM j2,j3 LIMIT 10 OFFSET 5" + 24 "SELECT a FROM j1 INTERSECT SELECT g FROM j2,j3 LIMIT (SELECT 10)" +} { + do_test e_select-7.3.$tn { catch {execsql $select} msg } 0 +} + +# EVIDENCE-OF: R-08531-36543 A compound SELECT created using UNION ALL +# operator returns all the rows from the SELECT to the left of the UNION +# ALL operator, and all the rows from the SELECT to the right of it. +# +drop_all_tables +do_execsql_test e_select-7.4.0 { + CREATE TABLE q1(a TEXT, b INTEGER, c); + CREATE TABLE q2(d NUMBER, e BLOB); + CREATE TABLE q3(f REAL, g); + + INSERT INTO q1 VALUES(16, -87.66, NULL); + INSERT INTO q1 VALUES('legible', 94, -42.47); + INSERT INTO q1 VALUES('beauty', 36, NULL); + + INSERT INTO q2 VALUES('legible', 1); + INSERT INTO q2 VALUES('beauty', 2); + INSERT INTO q2 VALUES(-65.91, 4); + INSERT INTO q2 VALUES('emanating', -16.56); + + INSERT INTO q3 VALUES('beauty', 2); + INSERT INTO q3 VALUES('beauty', 2); +} {} +do_select_tests e_select-7.4 { + 1 {SELECT a FROM q1 UNION ALL SELECT d FROM q2} + {16 legible beauty legible beauty -65.91 emanating} + + 2 {SELECT * FROM q1 WHERE a=16 UNION ALL SELECT 'x', * FROM q2 WHERE oid=1} + {16 -87.66 {} x legible 1} + + 3 {SELECT count(*) FROM q1 UNION ALL SELECT min(e) FROM q2} + {3 -16.56} + + 4 {SELECT * FROM q2 UNION ALL SELECT * FROM q3} + {legible 1 beauty 2 -65.91 4 emanating -16.56 beauty 2 beauty 2} +} + +# EVIDENCE-OF: R-20560-39162 The UNION operator works the same way as +# UNION ALL, except that duplicate rows are removed from the final +# result set. +# +do_select_tests e_select-7.5 { + 1 {SELECT a FROM q1 UNION SELECT d FROM q2} + {-65.91 16 beauty emanating legible} + + 2 {SELECT * FROM q1 WHERE a=16 UNION SELECT 'x', * FROM q2 WHERE oid=1} + {16 -87.66 {} x legible 1} + + 3 {SELECT count(*) FROM q1 UNION SELECT min(e) FROM q2} + {-16.56 3} + + 4 {SELECT * FROM q2 UNION SELECT * FROM q3} + {-65.91 4 beauty 2 emanating -16.56 legible 1} +} + +# EVIDENCE-OF: R-45764-31737 The INTERSECT operator returns the +# intersection of the results of the left and right SELECTs. +# +do_select_tests e_select-7.6 { + 1 {SELECT a FROM q1 INTERSECT SELECT d FROM q2} {beauty legible} + 2 {SELECT * FROM q2 INTERSECT SELECT * FROM q3} {beauty 2} +} + +# EVIDENCE-OF: R-25787-28949 The EXCEPT operator returns the subset of +# rows returned by the left SELECT that are not also returned by the +# right-hand SELECT. +# +do_select_tests e_select-7.7 { + 1 {SELECT a FROM q1 EXCEPT SELECT d FROM q2} {16} + + 2 {SELECT * FROM q2 EXCEPT SELECT * FROM q3} + {-65.91 4 emanating -16.56 legible 1} +} + +# EVIDENCE-OF: R-40729-56447 Duplicate rows are removed from the results +# of INTERSECT and EXCEPT operators before the result set is returned. +# +do_select_tests e_select-7.8 { + 0 {SELECT * FROM q3} {beauty 2 beauty 2} + + 1 {SELECT * FROM q3 INTERSECT SELECT * FROM q3} {beauty 2} + 2 {SELECT * FROM q3 EXCEPT SELECT a,b FROM q1} {beauty 2} +} + +# EVIDENCE-OF: R-46765-43362 For the purposes of determining duplicate +# rows for the results of compound SELECT operators, NULL values are +# considered equal to other NULL values and distinct from all non-NULL +# values. +# +db nullvalue null +do_select_tests e_select-7.9 { + 1 {SELECT NULL UNION ALL SELECT NULL} {null null} + 2 {SELECT NULL UNION SELECT NULL} {null} + 3 {SELECT NULL INTERSECT SELECT NULL} {null} + 4 {SELECT NULL EXCEPT SELECT NULL} {} + + 5 {SELECT NULL UNION ALL SELECT 'ab'} {null ab} + 6 {SELECT NULL UNION SELECT 'ab'} {null ab} + 7 {SELECT NULL INTERSECT SELECT 'ab'} {} + 8 {SELECT NULL EXCEPT SELECT 'ab'} {null} + + 9 {SELECT NULL UNION ALL SELECT 0} {null 0} + 10 {SELECT NULL UNION SELECT 0} {null 0} + 11 {SELECT NULL INTERSECT SELECT 0} {} + 12 {SELECT NULL EXCEPT SELECT 0} {null} + + 13 {SELECT c FROM q1 UNION ALL SELECT g FROM q3} {null -42.47 null 2 2} + 14 {SELECT c FROM q1 UNION SELECT g FROM q3} {null -42.47 2} + 15 {SELECT c FROM q1 INTERSECT SELECT g FROM q3} {} + 16 {SELECT c FROM q1 EXCEPT SELECT g FROM q3} {null -42.47} +} +db nullvalue {} + +# EVIDENCE-OF: R-51232-50224 The collation sequence used to compare two +# text values is determined as if the columns of the left and right-hand +# SELECT statements were the left and right-hand operands of the equals +# (=) operator, except that greater precedence is not assigned to a +# collation sequence specified with the postfix COLLATE operator. +# +drop_all_tables +do_execsql_test e_select-7.10.0 { + CREATE TABLE y1(a COLLATE nocase, b COLLATE binary, c); + INSERT INTO y1 VALUES('Abc', 'abc', 'aBC'); +} {} +do_select_tests e_select-7.10 { + 1 {SELECT 'abc' UNION SELECT 'ABC'} {ABC abc} + 2 {SELECT 'abc' COLLATE nocase UNION SELECT 'ABC'} {ABC} + 3 {SELECT 'abc' UNION SELECT 'ABC' COLLATE nocase} {ABC} + 4 {SELECT 'abc' COLLATE binary UNION SELECT 'ABC' COLLATE nocase} {ABC abc} + 5 {SELECT 'abc' COLLATE nocase UNION SELECT 'ABC' COLLATE binary} {ABC} + + 6 {SELECT a FROM y1 UNION SELECT b FROM y1} {abc} + 7 {SELECT b FROM y1 UNION SELECT a FROM y1} {Abc abc} + 8 {SELECT a FROM y1 UNION SELECT c FROM y1} {aBC} + + 9 {SELECT a FROM y1 UNION SELECT c COLLATE binary FROM y1} {aBC} +} + +# EVIDENCE-OF: R-32706-07403 No affinity transformations are applied to +# any values when comparing rows as part of a compound SELECT. +# +drop_all_tables +do_execsql_test e_select-7.10.0 { + CREATE TABLE w1(a TEXT, b NUMBER); + CREATE TABLE w2(a, b TEXT); + + INSERT INTO w1 VALUES('1', 4.1); + INSERT INTO w2 VALUES(1, 4.1); +} {} + +do_select_tests e_select-7.11 { + 1 { SELECT a FROM w1 UNION SELECT a FROM w2 } {1 1} + 2 { SELECT a FROM w2 UNION SELECT a FROM w1 } {1 1} + 3 { SELECT b FROM w1 UNION SELECT b FROM w2 } {4.1 4.1} + 4 { SELECT b FROM w2 UNION SELECT b FROM w1 } {4.1 4.1} + + 5 { SELECT a FROM w1 INTERSECT SELECT a FROM w2 } {} + 6 { SELECT a FROM w2 INTERSECT SELECT a FROM w1 } {} + 7 { SELECT b FROM w1 INTERSECT SELECT b FROM w2 } {} + 8 { SELECT b FROM w2 INTERSECT SELECT b FROM w1 } {} + + 9 { SELECT a FROM w1 EXCEPT SELECT a FROM w2 } {1} + 10 { SELECT a FROM w2 EXCEPT SELECT a FROM w1 } {1} + 11 { SELECT b FROM w1 EXCEPT SELECT b FROM w2 } {4.1} + 12 { SELECT b FROM w2 EXCEPT SELECT b FROM w1 } {4.1} +} + + +# EVIDENCE-OF: R-32562-20566 When three or more simple SELECTs are +# connected into a compound SELECT, they group from left to right. In +# other words, if "A", "B" and "C" are all simple SELECT statements, (A +# op B op C) is processed as ((A op B) op C). +# +# e_select-7.12.1: Precedence of UNION vs. INTERSECT +# e_select-7.12.2: Precedence of UNION vs. UNION ALL +# e_select-7.12.3: Precedence of UNION vs. EXCEPT +# e_select-7.12.4: Precedence of INTERSECT vs. UNION ALL +# e_select-7.12.5: Precedence of INTERSECT vs. EXCEPT +# e_select-7.12.6: Precedence of UNION ALL vs. EXCEPT +# e_select-7.12.7: Check that "a EXCEPT b EXCEPT c" is processed as +# "(a EXCEPT b) EXCEPT c". +# +# The INTERSECT and EXCEPT operations are mutually commutative. So +# the e_select-7.12.5 test cases do not prove very much. +# +drop_all_tables +do_execsql_test e_select-7.12.0 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(1); + INSERT INTO t1 VALUES(2); + INSERT INTO t1 VALUES(3); +} {} +foreach {tn select res} { + 1a "(1,2) INTERSECT (1) UNION (3)" {1 3} + 1b "(3) UNION (1,2) INTERSECT (1)" {1} + + 2a "(1,2) UNION (3) UNION ALL (1)" {1 2 3 1} + 2b "(1) UNION ALL (3) UNION (1,2)" {1 2 3} + + 3a "(1,2) UNION (3) EXCEPT (1)" {2 3} + 3b "(1,2) EXCEPT (3) UNION (1)" {1 2} + + 4a "(1,2) INTERSECT (1) UNION ALL (3)" {1 3} + 4b "(3) UNION (1,2) INTERSECT (1)" {1} + + 5a "(1,2) INTERSECT (2) EXCEPT (2)" {} + 5b "(2,3) EXCEPT (2) INTERSECT (2)" {} + + 6a "(2) UNION ALL (2) EXCEPT (2)" {} + 6b "(2) EXCEPT (2) UNION ALL (2)" {2} + + 7 "(2,3) EXCEPT (2) EXCEPT (3)" {} +} { + set select [string map {( {SELECT x FROM t1 WHERE x IN (}} $select] + do_execsql_test e_select-7.12.$tn $select [list {*}$res] +} + + +#------------------------------------------------------------------------- +# ORDER BY clauses +# + +drop_all_tables +do_execsql_test e_select-8.1.0 { + CREATE TABLE d1(x, y, z); + + INSERT INTO d1 VALUES(1, 2, 3); + INSERT INTO d1 VALUES(2, 5, -1); + INSERT INTO d1 VALUES(1, 2, 8); + INSERT INTO d1 VALUES(1, 2, 7); + INSERT INTO d1 VALUES(2, 4, 93); + INSERT INTO d1 VALUES(1, 2, -20); + INSERT INTO d1 VALUES(1, 4, 93); + INSERT INTO d1 VALUES(1, 5, -1); + + CREATE TABLE d2(a, b); + INSERT INTO d2 VALUES('gently', 'failings'); + INSERT INTO d2 VALUES('commercials', 'bathrobe'); + INSERT INTO d2 VALUES('iterate', 'sexton'); + INSERT INTO d2 VALUES('babied', 'charitableness'); + INSERT INTO d2 VALUES('solemnness', 'annexed'); + INSERT INTO d2 VALUES('rejoicing', 'liabilities'); + INSERT INTO d2 VALUES('pragmatist', 'guarded'); + INSERT INTO d2 VALUES('barked', 'interrupted'); + INSERT INTO d2 VALUES('reemphasizes', 'reply'); + INSERT INTO d2 VALUES('lad', 'relenting'); +} {} + +# EVIDENCE-OF: R-44988-41064 Rows are first sorted based on the results +# of evaluating the left-most expression in the ORDER BY list, then ties +# are broken by evaluating the second left-most expression and so on. +# +do_select_tests e_select-8.1 { + 1 "SELECT * FROM d1 ORDER BY x, y, z" { + 1 2 -20 1 2 3 1 2 7 1 2 8 + 1 4 93 1 5 -1 2 4 93 2 5 -1 + } +} + +# EVIDENCE-OF: R-06617-54588 Each ORDER BY expression may be optionally +# followed by one of the keywords ASC (smaller values are returned +# first) or DESC (larger values are returned first). +# +# Test cases e_select-8.2.* test the above. +# +# EVIDENCE-OF: R-18705-33393 If neither ASC or DESC are specified, rows +# are sorted in ascending (smaller values first) order by default. +# +# Test cases e_select-8.3.* test the above. All 8.3 test cases are +# copies of 8.2 test cases with the explicit "ASC" removed. +# +do_select_tests e_select-8 { + 2.1 "SELECT * FROM d1 ORDER BY x ASC, y ASC, z ASC" { + 1 2 -20 1 2 3 1 2 7 1 2 8 + 1 4 93 1 5 -1 2 4 93 2 5 -1 + } + 2.2 "SELECT * FROM d1 ORDER BY x DESC, y DESC, z DESC" { + 2 5 -1 2 4 93 1 5 -1 1 4 93 + 1 2 8 1 2 7 1 2 3 1 2 -20 + } + 2.3 "SELECT * FROM d1 ORDER BY x DESC, y ASC, z DESC" { + 2 4 93 2 5 -1 1 2 8 1 2 7 + 1 2 3 1 2 -20 1 4 93 1 5 -1 + } + 2.4 "SELECT * FROM d1 ORDER BY x DESC, y ASC, z ASC" { + 2 4 93 2 5 -1 1 2 -20 1 2 3 + 1 2 7 1 2 8 1 4 93 1 5 -1 + } + + 3.1 "SELECT * FROM d1 ORDER BY x, y, z" { + 1 2 -20 1 2 3 1 2 7 1 2 8 + 1 4 93 1 5 -1 2 4 93 2 5 -1 + } + 3.3 "SELECT * FROM d1 ORDER BY x DESC, y, z DESC" { + 2 4 93 2 5 -1 1 2 8 1 2 7 + 1 2 3 1 2 -20 1 4 93 1 5 -1 + } + 3.4 "SELECT * FROM d1 ORDER BY x DESC, y, z" { + 2 4 93 2 5 -1 1 2 -20 1 2 3 + 1 2 7 1 2 8 1 4 93 1 5 -1 + } +} + +# EVIDENCE-OF: R-29779-04281 If the ORDER BY expression is a constant +# integer K then the expression is considered an alias for the K-th +# column of the result set (columns are numbered from left to right +# starting with 1). +# +do_select_tests e_select-8.4 { + 1 "SELECT * FROM d1 ORDER BY 1 ASC, 2 ASC, 3 ASC" { + 1 2 -20 1 2 3 1 2 7 1 2 8 + 1 4 93 1 5 -1 2 4 93 2 5 -1 + } + 2 "SELECT * FROM d1 ORDER BY 1 DESC, 2 DESC, 3 DESC" { + 2 5 -1 2 4 93 1 5 -1 1 4 93 + 1 2 8 1 2 7 1 2 3 1 2 -20 + } + 3 "SELECT * FROM d1 ORDER BY 1 DESC, 2 ASC, 3 DESC" { + 2 4 93 2 5 -1 1 2 8 1 2 7 + 1 2 3 1 2 -20 1 4 93 1 5 -1 + } + 4 "SELECT * FROM d1 ORDER BY 1 DESC, 2 ASC, 3 ASC" { + 2 4 93 2 5 -1 1 2 -20 1 2 3 + 1 2 7 1 2 8 1 4 93 1 5 -1 + } + 5 "SELECT * FROM d1 ORDER BY 1, 2, 3" { + 1 2 -20 1 2 3 1 2 7 1 2 8 + 1 4 93 1 5 -1 2 4 93 2 5 -1 + } + 6 "SELECT * FROM d1 ORDER BY 1 DESC, 2, 3 DESC" { + 2 4 93 2 5 -1 1 2 8 1 2 7 + 1 2 3 1 2 -20 1 4 93 1 5 -1 + } + 7 "SELECT * FROM d1 ORDER BY 1 DESC, 2, 3" { + 2 4 93 2 5 -1 1 2 -20 1 2 3 + 1 2 7 1 2 8 1 4 93 1 5 -1 + } + 8 "SELECT z, x FROM d1 ORDER BY 2" { + 3 1 8 1 7 1 -20 1 + 93 1 -1 1 -1 2 93 2 + } + 9 "SELECT z, x FROM d1 ORDER BY 1" { + -20 1 -1 2 -1 1 3 1 + 7 1 8 1 93 2 93 1 + } +} + +# EVIDENCE-OF: R-63286-51977 If the ORDER BY expression is an identifier +# that corresponds to the alias of one of the output columns, then the +# expression is considered an alias for that column. +# +do_select_tests e_select-8.5 { + 1 "SELECT z+1 AS abc FROM d1 ORDER BY abc" { + -19 0 0 4 8 9 94 94 + } + 2 "SELECT z+1 AS abc FROM d1 ORDER BY abc DESC" { + 94 94 9 8 4 0 0 -19 + } + 3 "SELECT z AS x, x AS z FROM d1 ORDER BY z" { + 3 1 8 1 7 1 -20 1 93 1 -1 1 -1 2 93 2 + } + 4 "SELECT z AS x, x AS z FROM d1 ORDER BY x" { + -20 1 -1 2 -1 1 3 1 7 1 8 1 93 2 93 1 + } +} + +# EVIDENCE-OF: R-27923-38747 Otherwise, if the ORDER BY expression is +# any other expression, it is evaluated and the the returned value used +# to order the output rows. +# +# EVIDENCE-OF: R-03421-57988 If the SELECT statement is a simple SELECT, +# then an ORDER BY may contain any arbitrary expressions. +# +do_select_tests e_select-8.6 { + 1 "SELECT * FROM d1 ORDER BY x+y+z" { + 1 2 -20 1 5 -1 1 2 3 2 5 -1 + 1 2 7 1 2 8 1 4 93 2 4 93 + } + 2 "SELECT * FROM d1 ORDER BY x*z" { + 1 2 -20 2 5 -1 1 5 -1 1 2 3 + 1 2 7 1 2 8 1 4 93 2 4 93 + } + 3 "SELECT * FROM d1 ORDER BY y*z" { + 1 2 -20 2 5 -1 1 5 -1 1 2 3 + 1 2 7 1 2 8 2 4 93 1 4 93 + } +} + +# EVIDENCE-OF: R-28853-08147 However, if the SELECT is a compound +# SELECT, then ORDER BY expressions that are not aliases to output +# columns must be exactly the same as an expression used as an output +# column. +# +do_select_tests e_select-8.7.1 -error { + %s ORDER BY term does not match any column in the result set +} { + 1 "SELECT x FROM d1 UNION ALL SELECT a FROM d2 ORDER BY x*z" 1st + 2 "SELECT x,z FROM d1 UNION ALL SELECT a,b FROM d2 ORDER BY x, x/z" 2nd +} + +do_select_tests e_select-8.7.2 { + 1 "SELECT x*z FROM d1 UNION ALL SELECT a FROM d2 ORDER BY x*z" { + -20 -2 -1 3 7 8 93 186 babied barked commercials gently + iterate lad pragmatist reemphasizes rejoicing solemnness + } + 2 "SELECT x, x/z FROM d1 UNION ALL SELECT a,b FROM d2 ORDER BY x, x/z" { + 1 -1 1 0 1 0 1 0 1 0 1 0 2 -2 2 0 + babied charitableness barked interrupted commercials bathrobe gently + failings iterate sexton lad relenting pragmatist guarded reemphasizes reply + rejoicing liabilities solemnness annexed + } +} + +do_execsql_test e_select-8.8.0 { + CREATE TABLE d3(a); + INSERT INTO d3 VALUES('text'); + INSERT INTO d3 VALUES(14.1); + INSERT INTO d3 VALUES(13); + INSERT INTO d3 VALUES(X'78787878'); + INSERT INTO d3 VALUES(15); + INSERT INTO d3 VALUES(12.9); + INSERT INTO d3 VALUES(null); + + CREATE TABLE d4(x COLLATE nocase); + INSERT INTO d4 VALUES('abc'); + INSERT INTO d4 VALUES('ghi'); + INSERT INTO d4 VALUES('DEF'); + INSERT INTO d4 VALUES('JKL'); +} {} + +# EVIDENCE-OF: R-10883-17697 For the purposes of sorting rows, values +# are compared in the same way as for comparison expressions. +# +# The following tests verify that values of different types are sorted +# correctly, and that mixed real and integer values are compared properly. +# +do_execsql_test e_select-8.8.1 { + SELECT a FROM d3 ORDER BY a +} {{} 12.9 13 14.1 15 text xxxx} +do_execsql_test e_select-8.8.2 { + SELECT a FROM d3 ORDER BY a DESC +} {xxxx text 15 14.1 13 12.9 {}} + + +# EVIDENCE-OF: R-64199-22471 If the ORDER BY expression is assigned a +# collation sequence using the postfix COLLATE operator, then the +# specified collation sequence is used. +# +do_execsql_test e_select-8.9.1 { + SELECT x FROM d4 ORDER BY 1 COLLATE binary +} {DEF JKL abc ghi} +do_execsql_test e_select-8.9.2 { + SELECT x COLLATE binary FROM d4 ORDER BY 1 COLLATE nocase +} {abc DEF ghi JKL} + +# EVIDENCE-OF: R-09398-26102 Otherwise, if the ORDER BY expression is +# an alias to an expression that has been assigned a collation sequence +# using the postfix COLLATE operator, then the collation sequence +# assigned to the aliased expression is used. +# +# In the test 8.10.2, the only result-column expression has no alias. So the +# ORDER BY expression is not a reference to it and therefore does not inherit +# the collation sequence. In test 8.10.3, "x" is the alias (as well as the +# column name), so the ORDER BY expression is interpreted as an alias and the +# collation sequence attached to the result column is used for sorting. +# +do_execsql_test e_select-8.10.1 { + SELECT x COLLATE binary FROM d4 ORDER BY 1 +} {DEF JKL abc ghi} +do_execsql_test e_select-8.10.2 { + SELECT x COLLATE binary FROM d4 ORDER BY x +} {abc DEF ghi JKL} +do_execsql_test e_select-8.10.3 { + SELECT x COLLATE binary AS x FROM d4 ORDER BY x +} {DEF JKL abc ghi} + +# EVIDENCE-OF: R-27301-09658 Otherwise, if the ORDER BY expression is a +# column or an alias of an expression that is a column, then the default +# collation sequence for the column is used. +# +do_execsql_test e_select-8.11.1 { + SELECT x AS y FROM d4 ORDER BY y +} {abc DEF ghi JKL} +do_execsql_test e_select-8.11.2 { + SELECT x||'' FROM d4 ORDER BY x +} {abc DEF ghi JKL} + +# EVIDENCE-OF: R-49925-55905 Otherwise, the BINARY collation sequence is +# used. +# +do_execsql_test e_select-8.12.1 { + SELECT x FROM d4 ORDER BY x||'' +} {DEF JKL abc ghi} + +# EVIDENCE-OF: R-44130-32593 If an ORDER BY expression is not an integer +# alias, then SQLite searches the left-most SELECT in the compound for a +# result column that matches either the second or third rules above. If +# a match is found, the search stops and the expression is handled as an +# alias for the result column that it has been matched against. +# Otherwise, the next SELECT to the right is tried, and so on. +# +do_execsql_test e_select-8.13.0 { + CREATE TABLE d5(a, b); + CREATE TABLE d6(c, d); + CREATE TABLE d7(e, f); + + INSERT INTO d5 VALUES(1, 'f'); + INSERT INTO d6 VALUES(2, 'e'); + INSERT INTO d7 VALUES(3, 'd'); + INSERT INTO d5 VALUES(4, 'c'); + INSERT INTO d6 VALUES(5, 'b'); + INSERT INTO d7 VALUES(6, 'a'); + + CREATE TABLE d8(x COLLATE nocase); + CREATE TABLE d9(y COLLATE nocase); + + INSERT INTO d8 VALUES('a'); + INSERT INTO d9 VALUES('B'); + INSERT INTO d8 VALUES('c'); + INSERT INTO d9 VALUES('D'); +} {} +do_select_tests e_select-8.13 { + 1 { SELECT a FROM d5 UNION ALL SELECT c FROM d6 UNION ALL SELECT e FROM d7 + ORDER BY a + } {1 2 3 4 5 6} + 2 { SELECT a FROM d5 UNION ALL SELECT c FROM d6 UNION ALL SELECT e FROM d7 + ORDER BY c + } {1 2 3 4 5 6} + 3 { SELECT a FROM d5 UNION ALL SELECT c FROM d6 UNION ALL SELECT e FROM d7 + ORDER BY e + } {1 2 3 4 5 6} + 4 { SELECT a FROM d5 UNION ALL SELECT c FROM d6 UNION ALL SELECT e FROM d7 + ORDER BY 1 + } {1 2 3 4 5 6} + + 5 { SELECT a, b FROM d5 UNION ALL SELECT b, a FROM d5 ORDER BY b } + {f 1 c 4 4 c 1 f} + 6 { SELECT a, b FROM d5 UNION ALL SELECT b, a FROM d5 ORDER BY 2 } + {f 1 c 4 4 c 1 f} + + 7 { SELECT a, b FROM d5 UNION ALL SELECT b, a FROM d5 ORDER BY a } + {1 f 4 c c 4 f 1} + 8 { SELECT a, b FROM d5 UNION ALL SELECT b, a FROM d5 ORDER BY 1 } + {1 f 4 c c 4 f 1} + + 9 { SELECT a, b FROM d5 UNION ALL SELECT b, a+1 FROM d5 ORDER BY a+1 } + {f 2 c 5 4 c 1 f} + 10 { SELECT a, b FROM d5 UNION ALL SELECT b, a+1 FROM d5 ORDER BY 2 } + {f 2 c 5 4 c 1 f} + + 11 { SELECT a+1, b FROM d5 UNION ALL SELECT b, a+1 FROM d5 ORDER BY a+1 } + {2 f 5 c c 5 f 2} + 12 { SELECT a+1, b FROM d5 UNION ALL SELECT b, a+1 FROM d5 ORDER BY 1 } + {2 f 5 c c 5 f 2} +} + +# EVIDENCE-OF: R-39265-04070 If no matching expression can be found in +# the result columns of any constituent SELECT, it is an error. +# +do_select_tests e_select-8.14 -error { + %s ORDER BY term does not match any column in the result set +} { + 1 { SELECT a FROM d5 UNION SELECT c FROM d6 ORDER BY a+1 } 1st + 2 { SELECT a FROM d5 UNION SELECT c FROM d6 ORDER BY a, a+1 } 2nd + 3 { SELECT * FROM d5 INTERSECT SELECT * FROM d6 ORDER BY 'hello' } 1st + 4 { SELECT * FROM d5 INTERSECT SELECT * FROM d6 ORDER BY blah } 1st + 5 { SELECT * FROM d5 INTERSECT SELECT * FROM d6 ORDER BY c,d,c+d } 3rd + 6 { SELECT * FROM d5 EXCEPT SELECT * FROM d7 ORDER BY 1,2,b,a/b } 4th +} + +# EVIDENCE-OF: R-03407-11483 Each term of the ORDER BY clause is +# processed separately and may be matched against result columns from +# different SELECT statements in the compound. +# +do_select_tests e_select-8.15 { + 1 { SELECT a, b FROM d5 UNION ALL SELECT c-1, d FROM d6 ORDER BY a, d } + {1 e 1 f 4 b 4 c} + 2 { SELECT a, b FROM d5 UNION ALL SELECT c-1, d FROM d6 ORDER BY c-1, b } + {1 e 1 f 4 b 4 c} + 3 { SELECT a, b FROM d5 UNION ALL SELECT c-1, d FROM d6 ORDER BY 1, 2 } + {1 e 1 f 4 b 4 c} +} + + +#------------------------------------------------------------------------- +# Tests related to statements made about the LIMIT/OFFSET clause. +# +do_execsql_test e_select-9.0 { + CREATE TABLE f1(a, b); + INSERT INTO f1 VALUES(26, 'z'); + INSERT INTO f1 VALUES(25, 'y'); + INSERT INTO f1 VALUES(24, 'x'); + INSERT INTO f1 VALUES(23, 'w'); + INSERT INTO f1 VALUES(22, 'v'); + INSERT INTO f1 VALUES(21, 'u'); + INSERT INTO f1 VALUES(20, 't'); + INSERT INTO f1 VALUES(19, 's'); + INSERT INTO f1 VALUES(18, 'r'); + INSERT INTO f1 VALUES(17, 'q'); + INSERT INTO f1 VALUES(16, 'p'); + INSERT INTO f1 VALUES(15, 'o'); + INSERT INTO f1 VALUES(14, 'n'); + INSERT INTO f1 VALUES(13, 'm'); + INSERT INTO f1 VALUES(12, 'l'); + INSERT INTO f1 VALUES(11, 'k'); + INSERT INTO f1 VALUES(10, 'j'); + INSERT INTO f1 VALUES(9, 'i'); + INSERT INTO f1 VALUES(8, 'h'); + INSERT INTO f1 VALUES(7, 'g'); + INSERT INTO f1 VALUES(6, 'f'); + INSERT INTO f1 VALUES(5, 'e'); + INSERT INTO f1 VALUES(4, 'd'); + INSERT INTO f1 VALUES(3, 'c'); + INSERT INTO f1 VALUES(2, 'b'); + INSERT INTO f1 VALUES(1, 'a'); +} {} + +# EVIDENCE-OF: R-30481-56627 Any scalar expression may be used in the +# LIMIT clause, so long as it evaluates to an integer or a value that +# can be losslessly converted to an integer. +# +do_select_tests e_select-9.1 { + 1 { SELECT b FROM f1 ORDER BY a LIMIT 5 } {a b c d e} + 2 { SELECT b FROM f1 ORDER BY a LIMIT 2+3 } {a b c d e} + 3 { SELECT b FROM f1 ORDER BY a LIMIT (SELECT a FROM f1 WHERE b = 'e') } + {a b c d e} + 4 { SELECT b FROM f1 ORDER BY a LIMIT 5.0 } {a b c d e} + 5 { SELECT b FROM f1 ORDER BY a LIMIT '5' } {a b c d e} +} + +# EVIDENCE-OF: R-46155-47219 If the expression evaluates to a NULL value +# or any other value that cannot be losslessly converted to an integer, +# an error is returned. +# + +do_select_tests e_select-9.2 -error "datatype mismatch" { + 1 { SELECT b FROM f1 ORDER BY a LIMIT 'hello' } {} + 2 { SELECT b FROM f1 ORDER BY a LIMIT NULL } {} + 3 { SELECT b FROM f1 ORDER BY a LIMIT X'ABCD' } {} + 4 { SELECT b FROM f1 ORDER BY a LIMIT 5.1 } {} + 5 { SELECT b FROM f1 ORDER BY a LIMIT (SELECT group_concat(b) FROM f1) } {} +} + +# EVIDENCE-OF: R-03014-26414 If the LIMIT expression evaluates to a +# negative value, then there is no upper bound on the number of rows +# returned. +# +do_select_tests e_select-9.4 { + 1 { SELECT b FROM f1 ORDER BY a LIMIT -1 } + {a b c d e f g h i j k l m n o p q r s t u v w x y z} + 2 { SELECT b FROM f1 ORDER BY a LIMIT length('abc')-100 } + {a b c d e f g h i j k l m n o p q r s t u v w x y z} + 3 { SELECT b FROM f1 ORDER BY a LIMIT (SELECT count(*) FROM f1)/2 - 14 } + {a b c d e f g h i j k l m n o p q r s t u v w x y z} +} + +# EVIDENCE-OF: R-33750-29536 Otherwise, the SELECT returns the first N +# rows of its result set only, where N is the value that the LIMIT +# expression evaluates to. +# +do_select_tests e_select-9.5 { + 1 { SELECT b FROM f1 ORDER BY a LIMIT 0 } {} + 2 { SELECT b FROM f1 ORDER BY a DESC LIMIT 4 } {z y x w} + 3 { SELECT b FROM f1 ORDER BY a DESC LIMIT 8 } {z y x w v u t s} + 4 { SELECT b FROM f1 ORDER BY a DESC LIMIT '12.0' } {z y x w v u t s r q p o} +} + +# EVIDENCE-OF: R-54935-19057 Or, if the SELECT statement would return +# less than N rows without a LIMIT clause, then the entire result set is +# returned. +# +do_select_tests e_select-9.6 { + 1 { SELECT b FROM f1 WHERE a>21 ORDER BY a LIMIT 10 } {v w x y z} + 2 { SELECT count(*) FROM f1 GROUP BY a/5 ORDER BY 1 LIMIT 10 } {2 4 5 5 5 5} +} + + +# EVIDENCE-OF: R-24188-24349 The expression attached to the optional +# OFFSET clause that may follow a LIMIT clause must also evaluate to an +# integer, or a value that can be losslessly converted to an integer. +# +foreach {tn select} { + 1 { SELECT b FROM f1 ORDER BY a LIMIT 2 OFFSET 'hello' } + 2 { SELECT b FROM f1 ORDER BY a LIMIT 2 OFFSET NULL } + 3 { SELECT b FROM f1 ORDER BY a LIMIT 2 OFFSET X'ABCD' } + 4 { SELECT b FROM f1 ORDER BY a LIMIT 2 OFFSET 5.1 } + 5 { SELECT b FROM f1 ORDER BY a + LIMIT 2 OFFSET (SELECT group_concat(b) FROM f1) + } +} { + do_catchsql_test e_select-9.7.$tn $select {1 {datatype mismatch}} +} + +# EVIDENCE-OF: R-20467-43422 If an expression has an OFFSET clause, then +# the first M rows are omitted from the result set returned by the +# SELECT statement and the next N rows are returned, where M and N are +# the values that the OFFSET and LIMIT clauses evaluate to, +# respectively. +# +do_select_tests e_select-9.8 { + 1 { SELECT b FROM f1 ORDER BY a LIMIT 10 OFFSET 5} {f g h i j k l m n o} + 2 { SELECT b FROM f1 ORDER BY a LIMIT 2+3 OFFSET 10} {k l m n o} + 3 { SELECT b FROM f1 ORDER BY a + LIMIT (SELECT a FROM f1 WHERE b='j') + OFFSET (SELECT a FROM f1 WHERE b='b') + } {c d e f g h i j k l} + 4 { SELECT b FROM f1 ORDER BY a LIMIT '5' OFFSET 3.0 } {d e f g h} + 5 { SELECT b FROM f1 ORDER BY a LIMIT '5' OFFSET 0 } {a b c d e} + 6 { SELECT b FROM f1 ORDER BY a LIMIT 0 OFFSET 10 } {} + 7 { SELECT b FROM f1 ORDER BY a LIMIT 3 OFFSET '1'||'5' } {p q r} +} + +# EVIDENCE-OF: R-34648-44875 Or, if the SELECT would return less than +# M+N rows if it did not have a LIMIT clause, then the first M rows are +# skipped and the remaining rows (if any) are returned. +# +do_select_tests e_select-9.9 { + 1 { SELECT b FROM f1 ORDER BY a LIMIT 10 OFFSET 20} {u v w x y z} + 2 { SELECT a FROM f1 ORDER BY a DESC LIMIT 100 OFFSET 18+4} {4 3 2 1} +} + + +# EVIDENCE-OF: R-23293-62447 If the OFFSET clause evaluates to a +# negative value, the results are the same as if it had evaluated to +# zero. +# +do_select_tests e_select-9.10 { + 1 { SELECT b FROM f1 ORDER BY a LIMIT 5 OFFSET -1 } {a b c d e} + 2 { SELECT b FROM f1 ORDER BY a LIMIT 5 OFFSET -500 } {a b c d e} + 3 { SELECT b FROM f1 ORDER BY a LIMIT 5 OFFSET 0 } {a b c d e} +} + +# EVIDENCE-OF: R-19509-40356 Instead of a separate OFFSET clause, the +# LIMIT clause may specify two scalar expressions separated by a comma. +# +# EVIDENCE-OF: R-33788-46243 In this case, the first expression is used +# as the OFFSET expression and the second as the LIMIT expression. +# +do_select_tests e_select-9.11 { + 1 { SELECT b FROM f1 ORDER BY a LIMIT 5, 10 } {f g h i j k l m n o} + 2 { SELECT b FROM f1 ORDER BY a LIMIT 10, 2+3 } {k l m n o} + 3 { SELECT b FROM f1 ORDER BY a + LIMIT (SELECT a FROM f1 WHERE b='b'), (SELECT a FROM f1 WHERE b='j') + } {c d e f g h i j k l} + 4 { SELECT b FROM f1 ORDER BY a LIMIT 3.0, '5' } {d e f g h} + 5 { SELECT b FROM f1 ORDER BY a LIMIT 0, '5' } {a b c d e} + 6 { SELECT b FROM f1 ORDER BY a LIMIT 10, 0 } {} + 7 { SELECT b FROM f1 ORDER BY a LIMIT '1'||'5', 3 } {p q r} + + 8 { SELECT b FROM f1 ORDER BY a LIMIT 20, 10 } {u v w x y z} + 9 { SELECT a FROM f1 ORDER BY a DESC LIMIT 18+4, 100 } {4 3 2 1} + + 10 { SELECT b FROM f1 ORDER BY a LIMIT -1, 5 } {a b c d e} + 11 { SELECT b FROM f1 ORDER BY a LIMIT -500, 5 } {a b c d e} + 12 { SELECT b FROM f1 ORDER BY a LIMIT 0, 5 } {a b c d e} +} + +finish_test diff --git a/test/e_select2.test b/test/e_select2.test new file mode 100644 index 00000000..b338d4f3 --- /dev/null +++ b/test/e_select2.test @@ -0,0 +1,580 @@ +# 2010 September 24 +# +# 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 implements tests to verify that the "testable statements" in +# the lang_select.html document are correct. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +#------------------------------------------------------------------------- +# te_* commands: +# +# +# te_read_sql DB SELECT-STATEMENT +# te_read_tbl DB TABLENAME +# +# These two commands are used to read a dataset from the database. A dataset +# consists of N rows of M named columns of values each, where each value has a +# type (null, integer, real, text or blob) and a value within the types domain. +# The tcl format for a "dataset" is a list of two elements: +# +# * A list of the column names. +# * A list of data rows. Each row is itself a list, where each element is +# the contents of a column of the row. Each of these is a list of two +# elements, the type name and the actual value. +# +# For example, the contents of table [t1] as a dataset is: +# +# CREATE TABLE t1(a, b); +# INSERT INTO t1 VALUES('abc', NULL); +# INSERT INTO t1 VALUES(43.1, 22); +# +# {a b} {{{TEXT abc} {NULL {}}} {{REAL 43.1} {INTEGER 22}}} +# +# The [te_read_tbl] command returns a dataset read from a table. The +# [te_read_sql] returns the dataset that results from executing a SELECT +# command. +# +# +# te_tbljoin ?SWITCHES? LHS-TABLE RHS-TABLE +# te_join ?SWITCHES? LHS-DATASET RHS-DATASET +# +# This command joins the two datasets and returns the resulting dataset. If +# there are no switches specified, then the results is the cartesian product +# of the two inputs. The [te_tbljoin] command reads the left and right-hand +# datasets from the specified tables. The [te_join] command is passed the +# datasets directly. +# +# Optional switches are as follows: +# +# -on SCRIPT +# -using COLUMN-LIST +# -left +# +# The -on option specifies a tcl script that is executed for each row in the +# cartesian product of the two datasets. The script has 4 arguments appended +# to it, in the following order: +# +# * The list of column-names from the left-hand dataset. +# * A single row from the left-hand dataset (one "data row" list as +# described above. +# * The list of column-names from the right-hand dataset. +# * A single row from the right-hand dataset. +# +# The script must return a boolean value - true if the combination of rows +# should be included in the output dataset, or false otherwise. +# +# The -using option specifies a list of the columns from the right-hand +# dataset that should be omitted from the output dataset. +# +# If the -left option is present, the join is done LEFT JOIN style. +# Specifically, an extra row is inserted if after the -on script is run there +# exist rows in the left-hand dataset that have no corresponding rows in +# the output. See the implementation for more specific comments. +# +# +# te_equals ?SWITCHES? COLNAME1 COLNAME2 <-on script args> +# +# The only supported switch is "-nocase". If it is present, then text values +# are compared in a case-independent fashion. Otherwise, they are compared +# as if using the SQLite BINARY collation sequence. +# +# +# te_and ONSCRIPT1 ONSCRIPT2... +# +# + + +# +# te_read_tbl DB TABLENAME +# te_read_sql DB SELECT-STATEMENT +# +# These two procs are used to extract datasets from the database, either +# by reading the contents of a named table (te_read_tbl), or by executing +# a SELECT statement (t3_read_sql). +# +# See the comment above, describing "te_* commands", for details of the +# return values. +# +proc te_read_tbl {db tbl} { + te_read_sql $db "SELECT * FROM '$tbl'" +} +proc te_read_sql {db sql} { + set S [sqlite3_prepare_v2 $db $sql -1 DUMMY] + + set cols [list] + for {set i 0} {$i < [sqlite3_column_count $S]} {incr i} { + lappend cols [sqlite3_column_name $S $i] + } + + set rows [list] + while {[sqlite3_step $S] == "SQLITE_ROW"} { + set r [list] + for {set i 0} {$i < [sqlite3_column_count $S]} {incr i} { + lappend r [list [sqlite3_column_type $S $i] [sqlite3_column_text $S $i]] + } + lappend rows $r + } + sqlite3_finalize $S + + return [list $cols $rows] +} + +#------- +# Usage: te_join ... +# +# Where a join-spec is an optional list of arguments as follows: +# +# ?-left? +# ?-using colname-list? +# ?-on on-expr-proc? +# +proc te_join {data1 data2 args} { + + set testproc "" + set usinglist [list] + set isleft 0 + for {set i 0} {$i < [llength $args]} {incr i} { + set a [lindex $args $i] + switch -- $a { + -on { set testproc [lindex $args [incr i]] } + -using { set usinglist [lindex $args [incr i]] } + -left { set isleft 1 } + default { + error "Unknown argument: $a" + } + } + } + + set c1 [lindex $data1 0] + set c2 [lindex $data2 0] + set omitlist [list] + set nullrowlist [list] + set cret $c1 + + set cidx 0 + foreach col $c2 { + set idx [lsearch $usinglist $col] + if {$idx>=0} {lappend omitlist $cidx} + if {$idx<0} { + lappend nullrowlist {NULL {}} + lappend cret $col + } + incr cidx + } + set omitlist [lsort -integer -decreasing $omitlist] + + + set rret [list] + foreach r1 [lindex $data1 1] { + set one 0 + foreach r2 [lindex $data2 1] { + set ok 1 + if {$testproc != ""} { + set ok [eval $testproc [list $c1 $r1 $c2 $r2]] + } + if {$ok} { + set one 1 + foreach idx $omitlist {set r2 [lreplace $r2 $idx $idx]} + lappend rret [concat $r1 $r2] + } + } + + if {$isleft && $one==0} { + lappend rret [concat $r1 $nullrowlist] + } + } + + list $cret $rret +} + +proc te_tbljoin {db t1 t2 args} { + te_join [te_read_tbl $db $t1] [te_read_tbl $db $t2] {*}$args +} + +proc te_apply_affinity {affinity typevar valvar} { + upvar $typevar type + upvar $valvar val + + switch -- $affinity { + integer { + if {[string is double $val]} { set type REAL } + if {[string is wideinteger $val]} { set type INTEGER } + if {$type == "REAL" && int($val)==$val} { + set type INTEGER + set val [expr {int($val)}] + } + } + text { + set type TEXT + } + none { } + + default { error "invalid affinity: $affinity" } + } +} + +#---------- +# te_equals ?SWITCHES? c1 c2 cols1 row1 cols2 row2 +# +proc te_equals {args} { + + if {[llength $args]<6} {error "invalid arguments to te_equals"} + foreach {c1 c2 cols1 row1 cols2 row2} [lrange $args end-5 end] break + + set nocase 0 + set affinity none + + for {set i 0} {$i < ([llength $args]-6)} {incr i} { + set a [lindex $args $i] + switch -- $a { + -nocase { + set nocase 1 + } + -affinity { + set affinity [string tolower [lindex $args [incr i]]] + } + default { + error "invalid arguments to te_equals" + } + } + } + + set idx2 [if {[string is integer $c2]} { set c2 } else { lsearch $cols2 $c2 }] + set idx1 [if {[string is integer $c1]} { set c1 } else { lsearch $cols1 $c1 }] + + set t1 [lindex $row1 $idx1 0] + set t2 [lindex $row2 $idx2 0] + set v1 [lindex $row1 $idx1 1] + set v2 [lindex $row2 $idx2 1] + + te_apply_affinity $affinity t1 v1 + te_apply_affinity $affinity t2 v2 + + if {$t1 == "NULL" || $t2 == "NULL"} { return 0 } + if {$nocase && $t1 == "TEXT"} { set v1 [string tolower $v1] } + if {$nocase && $t2 == "TEXT"} { set v2 [string tolower $v2] } + + + set res [expr {$t1 == $t2 && [string equal $v1 $v2]}] + return $res +} + +proc te_false {args} { return 0 } +proc te_true {args} { return 1 } + +proc te_and {args} { + foreach a [lrange $args 0 end-4] { + set res [eval $a [lrange $args end-3 end]] + if {$res == 0} {return 0} + } + return 1 +} + + +proc te_dataset_eq {testname got expected} { + uplevel #0 [list do_test $testname [list set {} $got] $expected] +} +proc te_dataset_eq_unordered {testname got expected} { + lset got 1 [lsort [lindex $got 1]] + lset expected 1 [lsort [lindex $expected 1]] + te_dataset_eq $testname $got $expected +} + +proc te_dataset_ne {testname got unexpected} { + uplevel #0 [list do_test $testname [list string equal $got $unexpected] 0] +} +proc te_dataset_ne_unordered {testname got unexpected} { + lset got 1 [lsort [lindex $got 1]] + lset unexpected 1 [lsort [lindex $unexpected 1]] + te_dataset_ne $testname $got $unexpected +} + + +#------------------------------------------------------------------------- +# +proc test_join {tn sqljoin tbljoinargs} { + set sql [te_read_sql db "SELECT * FROM $sqljoin"] + set te [te_tbljoin db {*}$tbljoinargs] + te_dataset_eq_unordered $tn $sql $te +} + +drop_all_tables +do_execsql_test e_select-2.0 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(a, b); + CREATE TABLE t3(b COLLATE nocase); + + INSERT INTO t1 VALUES(2, 'B'); + INSERT INTO t1 VALUES(1, 'A'); + INSERT INTO t1 VALUES(4, 'D'); + INSERT INTO t1 VALUES(NULL, NULL); + INSERT INTO t1 VALUES(3, NULL); + + INSERT INTO t2 VALUES(1, 'A'); + INSERT INTO t2 VALUES(2, NULL); + INSERT INTO t2 VALUES(5, 'E'); + INSERT INTO t2 VALUES(NULL, NULL); + INSERT INTO t2 VALUES(3, 'C'); + + INSERT INTO t3 VALUES('a'); + INSERT INTO t3 VALUES('c'); + INSERT INTO t3 VALUES('b'); +} {} + +foreach {tn indexes} { + e_select-2.1.1 { } + e_select-2.1.2 { CREATE INDEX i1 ON t1(a) } + e_select-2.1.3 { CREATE INDEX i1 ON t2(a) } + e_select-2.1.4 { CREATE INDEX i1 ON t3(b) } +} { + + catchsql { DROP INDEX i1 } + catchsql { DROP INDEX i2 } + catchsql { DROP INDEX i3 } + execsql $indexes + + # EVIDENCE-OF: R-46122-14930 If the join-op is "CROSS JOIN", "INNER + # JOIN", "JOIN" or a comma (",") and there is no ON or USING clause, + # then the result of the join is simply the cartesian product of the + # left and right-hand datasets. + # + # EVIDENCE-OF: R-46256-57243 There is no difference between the "INNER + # JOIN", "JOIN" and "," join operators. + # + # EVIDENCE-OF: R-07544-24155 The "CROSS JOIN" join operator produces the + # same data as the "INNER JOIN", "JOIN" and "," operators + # + test_join $tn.1.1 "t1, t2" {t1 t2} + test_join $tn.1.2 "t1 INNER JOIN t2" {t1 t2} + test_join $tn.1.3 "t1 CROSS JOIN t2" {t1 t2} + test_join $tn.1.4 "t1 JOIN t2" {t1 t2} + test_join $tn.1.5 "t2, t3" {t2 t3} + test_join $tn.1.6 "t2 INNER JOIN t3" {t2 t3} + test_join $tn.1.7 "t2 CROSS JOIN t3" {t2 t3} + test_join $tn.1.8 "t2 JOIN t3" {t2 t3} + test_join $tn.1.9 "t2, t2 AS x" {t2 t2} + test_join $tn.1.10 "t2 INNER JOIN t2 AS x" {t2 t2} + test_join $tn.1.11 "t2 CROSS JOIN t2 AS x" {t2 t2} + test_join $tn.1.12 "t2 JOIN t2 AS x" {t2 t2} + + # EVIDENCE-OF: R-22775-56496 If there is an ON clause specified, then + # the ON expression is evaluated for each row of the cartesian product + # as a boolean expression. All rows for which the expression evaluates + # to false are excluded from the dataset. + # + test_join $tn.2.1 "t1, t2 ON (t1.a=t2.a)" {t1 t2 -on {te_equals a a}} + test_join $tn.2.2 "t2, t1 ON (t1.a=t2.a)" {t2 t1 -on {te_equals a a}} + test_join $tn.2.3 "t2, t1 ON (1)" {t2 t1 -on te_true} + test_join $tn.2.4 "t2, t1 ON (NULL)" {t2 t1 -on te_false} + test_join $tn.2.5 "t2, t1 ON (1.1-1.1)" {t2 t1 -on te_false} + test_join $tn.2.6 "t1, t2 ON (1.1-1.0)" {t1 t2 -on te_true} + + + test_join $tn.3 "t1 LEFT JOIN t2 ON (t1.a=t2.a)" {t1 t2 -left -on {te_equals a a}} + test_join $tn.4 "t1 LEFT JOIN t2 USING (a)" { + t1 t2 -left -using a -on {te_equals a a} + } + test_join $tn.5 "t1 CROSS JOIN t2 USING(b, a)" { + t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.6 "t1 NATURAL JOIN t2" { + t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.7 "t1 NATURAL INNER JOIN t2" { + t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.8 "t1 NATURAL CROSS JOIN t2" { + t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.9 "t1 NATURAL INNER JOIN t2" { + t1 t2 -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.10 "t1 NATURAL LEFT JOIN t2" { + t1 t2 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.11 "t1 NATURAL LEFT OUTER JOIN t2" { + t1 t2 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.12 "t2 NATURAL JOIN t1" { + t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.13 "t2 NATURAL INNER JOIN t1" { + t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.14 "t2 NATURAL CROSS JOIN t1" { + t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.15 "t2 NATURAL INNER JOIN t1" { + t2 t1 -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.16 "t2 NATURAL LEFT JOIN t1" { + t2 t1 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.17 "t2 NATURAL LEFT OUTER JOIN t1" { + t2 t1 -left -using {a b} -on {te_and {te_equals a a} {te_equals b b}} + } + test_join $tn.18 "t1 LEFT JOIN t2 USING (b)" { + t1 t2 -left -using b -on {te_equals b b} + } + test_join $tn.19 "t1 JOIN t3 USING(b)" {t1 t3 -using b -on {te_equals b b}} + test_join $tn.20 "t3 JOIN t1 USING(b)" { + t3 t1 -using b -on {te_equals -nocase b b} + } + test_join $tn.21 "t1 NATURAL JOIN t3" { + t1 t3 -using b -on {te_equals b b} + } + test_join $tn.22 "t3 NATURAL JOIN t1" { + t3 t1 -using b -on {te_equals -nocase b b} + } + test_join $tn.23 "t1 NATURAL LEFT JOIN t3" { + t1 t3 -left -using b -on {te_equals b b} + } + test_join $tn.24 "t3 NATURAL LEFT JOIN t1" { + t3 t1 -left -using b -on {te_equals -nocase b b} + } + test_join $tn.25 "t1 LEFT JOIN t3 ON (t3.b=t1.b)" { + t1 t3 -left -on {te_equals -nocase b b} + } + test_join $tn.26 "t1 LEFT JOIN t3 ON (t1.b=t3.b)" { + t1 t3 -left -on {te_equals b b} + } + test_join $tn.27 "t1 JOIN t3 ON (t1.b=t3.b)" { t1 t3 -on {te_equals b b} } + + # EVIDENCE-OF: R-28760-53843 When more than two tables are joined + # together as part of a FROM clause, the join operations are processed + # in order from left to right. In other words, the FROM clause (A + # join-op-1 B join-op-2 C) is computed as ((A join-op-1 B) join-op-2 C). + # + # Tests 28a and 28b show that the statement above is true for this case. + # Test 28c shows that if the parenthesis force a different order of + # evaluation the result is different. Test 28d verifies that the result + # of the query with the parenthesis forcing a different order of evaluation + # is as calculated by the [te_*] procs. + # + set t3_natural_left_join_t2 [ + te_tbljoin db t3 t2 -left -using {b} -on {te_equals -nocase b b} + ] + set t1 [te_read_tbl db t1] + te_dataset_eq_unordered $tn.28a [ + te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN t2 NATURAL JOIN t1" + ] [te_join $t3_natural_left_join_t2 $t1 \ + -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}} \ + ] + + te_dataset_eq_unordered $tn.28b [ + te_read_sql db "SELECT * FROM (t3 NATURAL LEFT JOIN t2) NATURAL JOIN t1" + ] [te_join $t3_natural_left_join_t2 $t1 \ + -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}} \ + ] + + te_dataset_ne_unordered $tn.28c [ + te_read_sql db "SELECT * FROM (t3 NATURAL LEFT JOIN t2) NATURAL JOIN t1" + ] [ + te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN (t2 NATURAL JOIN t1)" + ] + + set t2_natural_join_t1 [te_tbljoin db t2 t1 -using {a b} \ + -using {a b} -on {te_and {te_equals a a} {te_equals -nocase b b}} \ + ] + set t3 [te_read_tbl db t3] + te_dataset_eq_unordered $tn.28d [ + te_read_sql db "SELECT * FROM t3 NATURAL LEFT JOIN (t2 NATURAL JOIN t1)" + ] [te_join $t3 $t2_natural_join_t1 \ + -left -using {b} -on {te_equals -nocase b b} \ + ] +} + +do_execsql_test e_select-2.2.0 { + CREATE TABLE t4(x TEXT COLLATE nocase); + CREATE TABLE t5(y INTEGER, z TEXT COLLATE binary); + + INSERT INTO t4 VALUES('2.0'); + INSERT INTO t4 VALUES('TWO'); + INSERT INTO t5 VALUES(2, 'two'); +} {} + +# EVIDENCE-OF: R-55824-40976 A sub-select specified in the join-source +# following the FROM clause in a simple SELECT statement is handled as +# if it was a table containing the data returned by executing the +# sub-select statement. +# +# EVIDENCE-OF: R-42612-06757 Each column of the sub-select dataset +# inherits the collation sequence and affinity of the corresponding +# expression in the sub-select statement. +# +foreach {tn subselect select spec} { + 1 "SELECT * FROM t2" "SELECT * FROM t1 JOIN %ss%" + {t1 %ss%} + + 2 "SELECT * FROM t2" "SELECT * FROM t1 JOIN %ss% AS x ON (t1.a=x.a)" + {t1 %ss% -on {te_equals 0 0}} + + 3 "SELECT * FROM t2" "SELECT * FROM %ss% AS x JOIN t1 ON (t1.a=x.a)" + {%ss% t1 -on {te_equals 0 0}} + + 4 "SELECT * FROM t1, t2" "SELECT * FROM %ss% AS x JOIN t3" + {%ss% t3} + + 5 "SELECT * FROM t1, t2" "SELECT * FROM %ss% NATURAL JOIN t3" + {%ss% t3 -using b -on {te_equals 1 0}} + + 6 "SELECT * FROM t1, t2" "SELECT * FROM t3 NATURAL JOIN %ss%" + {t3 %ss% -using b -on {te_equals -nocase 0 1}} + + 7 "SELECT * FROM t1, t2" "SELECT * FROM t3 NATURAL LEFT JOIN %ss%" + {t3 %ss% -left -using b -on {te_equals -nocase 0 1}} + + 8 "SELECT count(*) AS y FROM t4" "SELECT * FROM t5, %ss% USING (y)" + {t5 %ss% -using y -on {te_equals -affinity text 0 0}} + + 9 "SELECT count(*) AS y FROM t4" "SELECT * FROM %ss%, t5 USING (y)" + {%ss% t5 -using y -on {te_equals -affinity text 0 0}} + + 10 "SELECT x AS y FROM t4" "SELECT * FROM %ss% JOIN t5 USING (y)" + {%ss% t5 -using y -on {te_equals -nocase -affinity integer 0 0}} + + 11 "SELECT x AS y FROM t4" "SELECT * FROM t5 JOIN %ss% USING (y)" + {t5 %ss% -using y -on {te_equals -nocase -affinity integer 0 0}} + + 12 "SELECT y AS x FROM t5" "SELECT * FROM %ss% JOIN t4 USING (x)" + {%ss% t4 -using x -on {te_equals -nocase -affinity integer 0 0}} + + 13 "SELECT y AS x FROM t5" "SELECT * FROM t4 JOIN %ss% USING (x)" + {t4 %ss% -using x -on {te_equals -nocase -affinity integer 0 0}} + + 14 "SELECT +y AS x FROM t5" "SELECT * FROM %ss% JOIN t4 USING (x)" + {%ss% t4 -using x -on {te_equals -nocase -affinity text 0 0}} + + 15 "SELECT +y AS x FROM t5" "SELECT * FROM t4 JOIN %ss% USING (x)" + {t4 %ss% -using x -on {te_equals -nocase -affinity text 0 0}} +} { + + # Create a temporary table named %ss% containing the data returned by + # the sub-select. Then have the [te_tbljoin] proc use this table to + # compute the expected results of the $select query. Drop the temporary + # table before continuing. + # + execsql "CREATE TEMP TABLE '%ss%' AS $subselect" + set te [eval te_tbljoin db $spec] + execsql "DROP TABLE '%ss%'" + + # Check that the actual data returned by the $select query is the same + # as the expected data calculated using [te_tbljoin] above. + # + te_dataset_eq_unordered e_select-2.2.1.$tn [ + te_read_sql db [string map [list %ss% "($subselect)"] $select] + ] $te +} + +finish_test diff --git a/test/e_update.test b/test/e_update.test new file mode 100644 index 00000000..d8032ce0 --- /dev/null +++ b/test/e_update.test @@ -0,0 +1,608 @@ +# 2010 September 20 +# +# 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 implements tests to verify that the "testable statements" in +# the lang_update.html document are correct. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +#-------------------- +# Test organization: +# +# e_update-1.*: Test statements describing the workings of UPDATE statements. +# +# e_update-2.*: Test the restrictions on the UPDATE statement syntax that +# can be used within triggers. +# +# e_update-3.*: Test the special LIMIT/OFFSET and ORDER BY clauses that can +# be used with UPDATE when SQLite is compiled with +# SQLITE_ENABLE_UPDATE_DELETE_LIMIT. +# + +forcedelete test.db2 + +do_execsql_test e_update-0.0 { + ATTACH 'test.db2' AS aux; + CREATE TABLE t1(a, b); + CREATE TABLE t2(a, b, c); + CREATE TABLE t3(a, b UNIQUE); + CREATE TABLE t6(x, y); + CREATE INDEX i1 ON t1(a); + + CREATE TEMP TABLE t4(x, y); + CREATE TEMP TABLE t6(x, y); + + CREATE TABLE aux.t1(a, b); + CREATE TABLE aux.t5(a, b); +} {} + +proc do_update_tests {args} { + uplevel do_select_tests $args +} + +# EVIDENCE-OF: R-05685-44205 -- syntax diagram update-stmt +# +do_update_tests e_update-0 { + 1 "UPDATE t1 SET a=10" {} + 2 "UPDATE t1 SET a=10, b=5" {} + 3 "UPDATE t1 SET a=10 WHERE b=5" {} + 4 "UPDATE t1 SET b=5,a=10 WHERE 1" {} + 5 "UPDATE main.t1 SET a=10" {} + 6 "UPDATE main.t1 SET a=10, b=5" {} + 7 "UPDATE main.t1 SET a=10 WHERE b=5" {} + 9 "UPDATE OR ROLLBACK t1 SET a=10" {} + 10 "UPDATE OR ROLLBACK t1 SET a=10, b=5" {} + 11 "UPDATE OR ROLLBACK t1 SET a=10 WHERE b=5" {} + 12 "UPDATE OR ROLLBACK t1 SET b=5,a=10 WHERE 1" {} + 13 "UPDATE OR ROLLBACK main.t1 SET a=10" {} + 14 "UPDATE OR ROLLBACK main.t1 SET a=10, b=5" {} + 15 "UPDATE OR ROLLBACK main.t1 SET a=10 WHERE b=5" {} + 16 "UPDATE OR ROLLBACK main.t1 SET b=5,a=10 WHERE 1" {} + 17 "UPDATE OR ABORT t1 SET a=10" {} + 18 "UPDATE OR ABORT t1 SET a=10, b=5" {} + 19 "UPDATE OR ABORT t1 SET a=10 WHERE b=5" {} + 20 "UPDATE OR ABORT t1 SET b=5,a=10 WHERE 1" {} + 21 "UPDATE OR ABORT main.t1 SET a=10" {} + 22 "UPDATE OR ABORT main.t1 SET a=10, b=5" {} + 23 "UPDATE OR ABORT main.t1 SET a=10 WHERE b=5" {} + 24 "UPDATE OR ABORT main.t1 SET b=5,a=10 WHERE 1" {} + 25 "UPDATE OR REPLACE t1 SET a=10" {} + 26 "UPDATE OR REPLACE t1 SET a=10, b=5" {} + 27 "UPDATE OR REPLACE t1 SET a=10 WHERE b=5" {} + 28 "UPDATE OR REPLACE t1 SET b=5,a=10 WHERE 1" {} + 29 "UPDATE OR REPLACE main.t1 SET a=10" {} + 30 "UPDATE OR REPLACE main.t1 SET a=10, b=5" {} + 31 "UPDATE OR REPLACE main.t1 SET a=10 WHERE b=5" {} + 32 "UPDATE OR REPLACE main.t1 SET b=5,a=10 WHERE 1" {} + 33 "UPDATE OR FAIL t1 SET a=10" {} + 34 "UPDATE OR FAIL t1 SET a=10, b=5" {} + 35 "UPDATE OR FAIL t1 SET a=10 WHERE b=5" {} + 36 "UPDATE OR FAIL t1 SET b=5,a=10 WHERE 1" {} + 37 "UPDATE OR FAIL main.t1 SET a=10" {} + 38 "UPDATE OR FAIL main.t1 SET a=10, b=5" {} + 39 "UPDATE OR FAIL main.t1 SET a=10 WHERE b=5" {} + 40 "UPDATE OR FAIL main.t1 SET b=5,a=10 WHERE 1" {} + 41 "UPDATE OR IGNORE t1 SET a=10" {} + 42 "UPDATE OR IGNORE t1 SET a=10, b=5" {} + 43 "UPDATE OR IGNORE t1 SET a=10 WHERE b=5" {} + 44 "UPDATE OR IGNORE t1 SET b=5,a=10 WHERE 1" {} + 45 "UPDATE OR IGNORE main.t1 SET a=10" {} + 46 "UPDATE OR IGNORE main.t1 SET a=10, b=5" {} + 47 "UPDATE OR IGNORE main.t1 SET a=10 WHERE b=5" {} + 48 "UPDATE OR IGNORE main.t1 SET b=5,a=10 WHERE 1" {} +} + +# EVIDENCE-OF: R-38515-45264 An UPDATE statement is used to modify a +# subset of the values stored in zero or more rows of the database table +# identified by the qualified-table-name specified as part of the UPDATE +# statement. +# +# Test cases e_update-1.1.1.* test the "identified by the +# qualified-table-name" part of the statement above. Tests +# e_update-1.1.2.* show that the "zero or more rows" part is +# accurate. +# +do_execsql_test e_update-1.1.0 { + INSERT INTO main.t1 VALUES(1, 'i'); + INSERT INTO main.t1 VALUES(2, 'ii'); + INSERT INTO main.t1 VALUES(3, 'iii'); + + INSERT INTO aux.t1 VALUES(1, 'I'); + INSERT INTO aux.t1 VALUES(2, 'II'); + INSERT INTO aux.t1 VALUES(3, 'III'); +} {} +do_update_tests e_update-1.1 { + 1.1 "UPDATE t1 SET a = a+1; SELECT * FROM t1" {2 i 3 ii 4 iii} + 1.2 "UPDATE main.t1 SET a = a+1; SELECT * FROM main.t1" {3 i 4 ii 5 iii} + 1.3 "UPDATE aux.t1 SET a = a+1; SELECT * FROM aux.t1" {2 I 3 II 4 III} + + 2.1 "UPDATE t1 SET a = a+1 WHERE a = 1; SELECT * FROM t1" {3 i 4 ii 5 iii} + 2.2 "UPDATE t1 SET a = a+1 WHERE a = 4; SELECT * FROM t1" {3 i 5 ii 5 iii} +} + +# EVIDENCE-OF: R-55869-30521 If the UPDATE statement does not have a +# WHERE clause, all rows in the table are modified by the UPDATE. +# +do_execsql_test e_update-1.2.0 { + DELETE FROM main.t1; + INSERT INTO main.t1 VALUES(1, 'i'); + INSERT INTO main.t1 VALUES(2, 'ii'); + INSERT INTO main.t1 VALUES(3, 'iii'); +} {} +do_update_tests e_update-1.2 { + 1 "UPDATE t1 SET b = 'roman' ; SELECT * FROM t1" + {1 roman 2 roman 3 roman} + + 2 "UPDATE t1 SET a = 'greek' ; SELECT * FROM t1" + {greek roman greek roman greek roman} +} + +# EVIDENCE-OF: R-42117-40023 Otherwise, the UPDATE affects only those +# rows for which the result of evaluating the WHERE clause expression as +# a boolean expression is true. +# +do_execsql_test e_update-1.3.0 { + DELETE FROM main.t1; + INSERT INTO main.t1 VALUES(NULL, ''); + INSERT INTO main.t1 VALUES(1, 'i'); + INSERT INTO main.t1 VALUES(2, 'ii'); + INSERT INTO main.t1 VALUES(3, 'iii'); +} {} +do_update_tests e_update-1.3 { + 1 "UPDATE t1 SET b = 'roman' WHERE a<2 ; SELECT * FROM t1" + {{} {} 1 roman 2 ii 3 iii} + + 2 "UPDATE t1 SET b = 'egyptian' WHERE (a-3)/10.0 ; SELECT * FROM t1" + {{} {} 1 egyptian 2 egyptian 3 iii} + + 3 "UPDATE t1 SET b = 'macedonian' WHERE a; SELECT * FROM t1" + {{} {} 1 macedonian 2 macedonian 3 macedonian} + + 4 "UPDATE t1 SET b = 'lithuanian' WHERE a IS NULL; SELECT * FROM t1" + {{} lithuanian 1 macedonian 2 macedonian 3 macedonian} +} + +# EVIDENCE-OF: R-58129-20729 It is not an error if the WHERE clause does +# not evaluate to true for any row in the table - this just means that +# the UPDATE statement affects zero rows. +# +do_execsql_test e_update-1.4.0 { + DELETE FROM main.t1; + INSERT INTO main.t1 VALUES(NULL, ''); + INSERT INTO main.t1 VALUES(1, 'i'); + INSERT INTO main.t1 VALUES(2, 'ii'); + INSERT INTO main.t1 VALUES(3, 'iii'); +} {} +do_update_tests e_update-1.4 -query { + SELECT * FROM t1 +} { + 1 "UPDATE t1 SET b = 'burmese' WHERE a=5" {{} {} 1 i 2 ii 3 iii} + + 2 "UPDATE t1 SET b = 'burmese' WHERE length(b)<1 AND a IS NOT NULL" + {{} {} 1 i 2 ii 3 iii} + + 3 "UPDATE t1 SET b = 'burmese' WHERE 0" {{} {} 1 i 2 ii 3 iii} + + 4 "UPDATE t1 SET b = 'burmese' WHERE (SELECT a FROM t1 WHERE rowid=1)" + {{} {} 1 i 2 ii 3 iii} +} + +# EVIDENCE-OF: R-40598-36595 For each affected row, the named columns +# are set to the values found by evaluating the corresponding scalar +# expressions. +# +# EVIDENCE-OF: R-40472-60438 Columns that do not appear in the list of +# assignments are left unmodified. +# +do_execsql_test e_update-1.5.0 { + INSERT INTO t2(rowid, a, b, c) VALUES(1, 3, 1, 4); + INSERT INTO t2(rowid, a, b, c) VALUES(2, 1, 5, 9); + INSERT INTO t2(rowid, a, b, c) VALUES(3, 2, 6, 5); +} {} +do_update_tests e_update-1.5 -query { + SELECT * FROM t2 +} { + 1 "UPDATE t2 SET c = 1+1 WHERE a=2" + {3 1 4 1 5 9 2 6 2} + + 2 "UPDATE t2 SET b = 4/2, c=CAST((0.4*5) AS INTEGER) WHERE a<3" + {3 1 4 1 2 2 2 2 2} + + 3 "UPDATE t2 SET a = 1" + {1 1 4 1 2 2 1 2 2} + + 4 "UPDATE t2 SET b = (SELECT count(*)+2 FROM t2), c = 24/3+1 WHERE rowid=2" + {1 1 4 1 5 9 1 2 2} + + 5 "UPDATE t2 SET a = 3 WHERE c = 4" + {3 1 4 1 5 9 1 2 2} + + 6 "UPDATE t2 SET a = b WHERE rowid>2" + {3 1 4 1 5 9 2 2 2} + + 6 "UPDATE t2 SET b=6, c=5 WHERE a=b AND b=c" + {3 1 4 1 5 9 2 6 5} +} + +# EVIDENCE-OF: R-09060-20018 If a single column-name appears more than +# once in the list of assignment expressions, all but the rightmost +# occurence is ignored. +# +do_update_tests e_update-1.6 -query { + SELECT * FROM t2 +} { + 1 "UPDATE t2 SET c=5, c=6, c=7 WHERE rowid=1" {3 1 7 1 5 9 2 6 5} + 2 "UPDATE t2 SET c=7, c=6, c=5 WHERE rowid=1" {3 1 5 1 5 9 2 6 5} + 3 "UPDATE t2 SET c=5, b=6, c=7 WHERE rowid=1" {3 6 7 1 5 9 2 6 5} +} + +# EVIDENCE-OF: R-36239-04077 The scalar expressions may refer to columns +# of the row being updated. +# +# EVIDENCE-OF: R-04558-24451 In this case all scalar expressions are +# evaluated before any assignments are made. +# +do_execsql_test e_update-1.7.0 { + DELETE FROM t2; + INSERT INTO t2(rowid, a, b, c) VALUES(1, 3, 1, 4); + INSERT INTO t2(rowid, a, b, c) VALUES(2, 1, 5, 9); + INSERT INTO t2(rowid, a, b, c) VALUES(3, 2, 6, 5); +} {} +do_update_tests e_update-1.7 -query { + SELECT * FROM t2 +} { + 1 "UPDATE t2 SET a=b+c" {5 1 4 14 5 9 11 6 5} + 2 "UPDATE t2 SET a=b, b=a" {1 5 4 5 14 9 6 11 5} + 3 "UPDATE t2 SET a=c||c, c=NULL" {44 5 {} 99 14 {} 55 11 {}} +} + +# EVIDENCE-OF: R-12619-24112 The optional conflict-clause allows the +# user to nominate a specific constraint conflict resolution algorithm +# to use during this one UPDATE command. +# +do_execsql_test e_update-1.8.0 { + DELETE FROM t3; + INSERT INTO t3 VALUES(1, 'one'); + INSERT INTO t3 VALUES(2, 'two'); + INSERT INTO t3 VALUES(3, 'three'); + INSERT INTO t3 VALUES(4, 'four'); +} {} +foreach {tn sql error ac data } { + 1 "UPDATE t3 SET b='one' WHERE a=3" + {column b is not unique} 1 {1 one 2 two 3 three 4 four} + + 2 "UPDATE OR REPLACE t3 SET b='one' WHERE a=3" + {} 1 {2 two 3 one 4 four} + + 3 "UPDATE OR FAIL t3 SET b='three'" + {column b is not unique} 1 {2 three 3 one 4 four} + + 4 "UPDATE OR IGNORE t3 SET b='three' WHERE a=3" + {} 1 {2 three 3 one 4 four} + + 5 "UPDATE OR ABORT t3 SET b='three' WHERE a=3" + {column b is not unique} 1 {2 three 3 one 4 four} + + 6 "BEGIN" {} 0 {2 three 3 one 4 four} + + 7 "UPDATE t3 SET b='three' WHERE a=3" + {column b is not unique} 0 {2 three 3 one 4 four} + + 8 "UPDATE OR ABORT t3 SET b='three' WHERE a=3" + {column b is not unique} 0 {2 three 3 one 4 four} + + 9 "UPDATE OR FAIL t3 SET b='two'" + {column b is not unique} 0 {2 two 3 one 4 four} + + 10 "UPDATE OR IGNORE t3 SET b='four' WHERE a=3" + {} 0 {2 two 3 one 4 four} + + 11 "UPDATE OR REPLACE t3 SET b='four' WHERE a=3" + {} 0 {2 two 3 four} + + 12 "UPDATE OR ROLLBACK t3 SET b='four'" + {column b is not unique} 1 {2 three 3 one 4 four} +} { + do_catchsql_test e_update-1.8.$tn.1 $sql [list [expr {$error!=""}] $error] + do_execsql_test e_update-1.8.$tn.2 {SELECT * FROM t3} [list {*}$data] + do_test e_update-1.8.$tn.3 {sqlite3_get_autocommit db} $ac +} + + + +# EVIDENCE-OF: R-12123-54095 The table-name specified as part of an +# UPDATE statement within a trigger body must be unqualified. +# +# EVIDENCE-OF: R-09690-36749 In other words, the database-name. prefix +# on the table name of the UPDATE is not allowed within triggers. +# +do_update_tests e_update-2.1 -error { + qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers +} { + 1 { + CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN + UPDATE main.t2 SET a=1, b=2, c=3; + END; + } {} + + 2 { + CREATE TRIGGER tr1 BEFORE UPDATE ON t2 BEGIN + UPDATE aux.t1 SET a=1, b=2; + END; + } {} + + 3 { + CREATE TRIGGER tr1 AFTER DELETE ON t4 BEGIN + UPDATE main.t1 SET a=1, b=2; + END; + } {} +} + +# EVIDENCE-OF: R-06085-13761 Unless the table to which the trigger is +# attached is in the TEMP database, the table being updated by the +# trigger program must reside in the same database as it. +# +do_update_tests e_update-2.2 -error { + no such table: %s +} { + 1 { + CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN + UPDATE t4 SET x=x+1; + END; + INSERT INTO t1 VALUES(1, 2); + } "main.t4" + + 2 { + CREATE TRIGGER aux.tr1 AFTER INSERT ON t5 BEGIN + UPDATE t4 SET x=x+1; + END; + INSERT INTO t5 VALUES(1, 2); + } "aux.t4" +} +do_execsql_test e_update-2.2.X { + DROP TRIGGER tr1; + DROP TRIGGER aux.tr1; +} {} + +# EVIDENCE-OF: R-29512-54644 If the table to which the trigger is +# attached is in the TEMP database, then the unqualified name of the +# table being updated is resolved in the same way as it is for a +# top-level statement (by searching first the TEMP database, then the +# main database, then any other databases in the order they were +# attached). +# +do_execsql_test e_update-2.3.0 { + SELECT 'main', tbl_name FROM main.sqlite_master WHERE type = 'table' + UNION ALL + SELECT 'temp', tbl_name FROM sqlite_temp_master WHERE type = 'table' + UNION ALL + SELECT 'aux', tbl_name FROM aux.sqlite_master WHERE type = 'table' +} [list {*}{ + main t1 + main t2 + main t3 + main t6 + temp t4 + temp t6 + aux t1 + aux t5 +}] +do_execsql_test e_update-2.3.1 { + DELETE FROM main.t6; + DELETE FROM temp.t6; + INSERT INTO main.t6 VALUES(1, 2); + INSERT INTO temp.t6 VALUES(1, 2); + + CREATE TRIGGER temp.tr1 AFTER INSERT ON t4 BEGIN + UPDATE t6 SET x=x+1; + END; + + INSERT INTO t4 VALUES(1, 2); + SELECT * FROM main.t6; + SELECT * FROM temp.t6; +} {1 2 2 2} +do_execsql_test e_update-2.3.2 { + DELETE FROM main.t1; + DELETE FROM aux.t1; + INSERT INTO main.t1 VALUES(1, 2); + INSERT INTO aux.t1 VALUES(1, 2); + + CREATE TRIGGER temp.tr2 AFTER DELETE ON t4 BEGIN + UPDATE t1 SET a=a+1; + END; + + DELETE FROM t4; + SELECT * FROM main.t1; + SELECT * FROM aux.t1; +} {2 2 1 2} +do_execsql_test e_update-2.3.3 { + DELETE FROM aux.t5; + INSERT INTO aux.t5 VALUES(1, 2); + + INSERT INTO t4 VALUES('x', 'y'); + CREATE TRIGGER temp.tr3 AFTER UPDATE ON t4 BEGIN + UPDATE t5 SET a=a+1; + END; + + UPDATE t4 SET x=10; + SELECT * FROM aux.t5; +} {2 2} + +# EVIDENCE-OF: R-19619-42762 The INDEXED BY and NOT INDEXED clauses are +# not allowed on UPDATE statements within triggers. +# +do_update_tests e_update-2.4 -error { + the %s %s clause is not allowed on UPDATE or DELETE statements within triggers +} { + 1 { + CREATE TRIGGER tr1 AFTER INSERT ON t2 BEGIN + UPDATE t1 INDEXED BY i1 SET a=a+1; + END; + } {INDEXED BY} + + 2 { + CREATE TRIGGER tr1 AFTER INSERT ON t2 BEGIN + UPDATE t1 NOT INDEXED SET a=a+1; + END; + } {NOT INDEXED} +} + +ifcapable update_delete_limit { + +# EVIDENCE-OF: R-57359-59558 The LIMIT and ORDER BY clauses for UPDATE +# are unsupported within triggers, regardless of the compilation options +# used to build SQLite. +# +do_update_tests e_update-2.5 -error { + near "%s": syntax error +} { + 1 { + CREATE TRIGGER tr1 AFTER INSERT ON t2 BEGIN + UPDATE t1 SET a=a+1 LIMIT 10; + END; + } {LIMIT} + + 2 { + CREATE TRIGGER tr1 AFTER INSERT ON t2 BEGIN + UPDATE t1 SET a=a+1 ORDER BY a LIMIT 10; + END; + } {ORDER} + + 3 { + CREATE TRIGGER tr1 AFTER INSERT ON t2 BEGIN + UPDATE t1 SET a=a+1 ORDER BY a LIMIT 10 OFFSET 2; + END; + } {ORDER} + + 4 { + CREATE TRIGGER tr1 AFTER INSERT ON t2 BEGIN + UPDATE t1 SET a=a+1 LIMIT 10 OFFSET 2; + END; + } {LIMIT} +} + +# EVIDENCE-OF: R-59581-44104 If SQLite is built with the +# SQLITE_ENABLE_UPDATE_DELETE_LIMIT compile-time option then the syntax +# of the UPDATE statement is extended with optional ORDER BY and LIMIT +# clauses +# +# EVIDENCE-OF: R-08948-01887 -- syntax diagram update-stmt-limited +# +do_update_tests e_update-3.0 { + 1 "UPDATE t1 SET a=b LIMIT 5" {} + 2 "UPDATE t1 SET a=b LIMIT 5-1 OFFSET 2+2" {} + 3 "UPDATE t1 SET a=b LIMIT 2+2, 16/4" {} + 4 "UPDATE t1 SET a=b ORDER BY a LIMIT 5" {} + 5 "UPDATE t1 SET a=b ORDER BY a LIMIT 5-1 OFFSET 2+2" {} + 6 "UPDATE t1 SET a=b ORDER BY a LIMIT 2+2, 16/4" {} + 7 "UPDATE t1 SET a=b WHERE a>2 LIMIT 5" {} + 8 "UPDATE t1 SET a=b WHERE a>2 LIMIT 5-1 OFFSET 2+2" {} + 9 "UPDATE t1 SET a=b WHERE a>2 LIMIT 2+2, 16/4" {} + 10 "UPDATE t1 SET a=b WHERE a>2 ORDER BY a LIMIT 5" {} + 11 "UPDATE t1 SET a=b WHERE a>2 ORDER BY a LIMIT 5-1 OFFSET 2+2" {} + 12 "UPDATE t1 SET a=b WHERE a>2 ORDER BY a LIMIT 2+2, 16/4" {} +} + +do_execsql_test e_update-3.1.0 { + CREATE TABLE t7(q, r, s); + INSERT INTO t7 VALUES(1, 'one', 'X'); + INSERT INTO t7 VALUES(2, 'two', 'X'); + INSERT INTO t7 VALUES(3, 'three', 'X'); + INSERT INTO t7 VALUES(4, 'four', 'X'); + INSERT INTO t7 VALUES(5, 'five', 'X'); + INSERT INTO t7 VALUES(6, 'six', 'X'); + INSERT INTO t7 VALUES(7, 'seven', 'X'); + INSERT INTO t7 VALUES(8, 'eight', 'X'); + INSERT INTO t7 VALUES(9, 'nine', 'X'); + INSERT INTO t7 VALUES(10, 'ten', 'X'); +} {} + +# EVIDENCE-OF: R-58862-44169 If an UPDATE statement has a LIMIT clause, +# the maximum number of rows that will be updated is found by evaluating +# the accompanying expression and casting it to an integer value. +# +do_update_tests e_update-3.1 -query { SELECT s FROM t7 } { + 1 "UPDATE t7 SET s = q LIMIT 5" {1 2 3 4 5 X X X X X} + 2 "UPDATE t7 SET s = r WHERE q>2 LIMIT 4" {1 2 three four five six X X X X} + 3 "UPDATE t7 SET s = q LIMIT 0" {1 2 three four five six X X X X} +} + +# EVIDENCE-OF: R-63582-45120 A negative value is interpreted as "no limit". +# +do_update_tests e_update-3.2 -query { SELECT s FROM t7 } { + 1 "UPDATE t7 SET s = q LIMIT -1" {1 2 3 4 5 6 7 8 9 10} + 2 "UPDATE t7 SET s = r WHERE q>4 LIMIT -1" + {1 2 3 4 five six seven eight nine ten} + 3 "UPDATE t7 SET s = 'X' LIMIT -1" {X X X X X X X X X X} +} + +# EVIDENCE-OF: R-18628-11938 If the LIMIT expression evaluates to +# non-negative value N and the UPDATE statement has an ORDER BY clause, +# then all rows that would be updated in the absence of the LIMIT clause +# are sorted according to the ORDER BY and the first N updated. +# +do_update_tests e_update-3.3 -query { SELECT s FROM t7 } { + 1 "UPDATE t7 SET s = q ORDER BY r LIMIT 3" {X X X 4 5 X X 8 X X} + 2 "UPDATE t7 SET s = r ORDER BY r DESC LIMIT 2" {X two three 4 5 X X 8 X X} + 3 "UPDATE t7 SET s = q ORDER BY q DESC LIMIT 5" {X two three 4 5 6 7 8 9 10} + + X "UPDATE t7 SET s = 'X'" {X X X X X X X X X X} +} + +# EVIDENCE-OF: R-30955-38324 If the UPDATE statement also has an OFFSET +# clause, then it is similarly evaluated and cast to an integer value. +# If the OFFSET expression evaluates to a non-negative value M, then the +# first M rows are skipped and the following N rows updated instead. +# +do_update_tests e_update-3.3 -query { SELECT s FROM t7 } { + 1 "UPDATE t7 SET s = q ORDER BY q LIMIT 3 OFFSET 2" {X X 3 4 5 X X X X X} + 2 "UPDATE t7 SET s = q ORDER BY q DESC LIMIT 2, 3 " {X X 3 4 5 6 7 8 X X} + + X "UPDATE t7 SET s = 'X'" {X X X X X X X X X X} +} + +# EVIDENCE-OF: R-19486-35828 If the UPDATE statement has no ORDER BY +# clause, then all rows that would be updated in the absence of the +# LIMIT clause are assembled in an arbitrary order before applying the +# LIMIT and OFFSET clauses to determine which are actually updated. +# +# In practice, "arbitrary order" is rowid order. This is also tested +# by e_update-3.2.* above. +# +do_update_tests e_update-3.4 -query { SELECT s FROM t7 } { + 1 "UPDATE t7 SET s = q LIMIT 4, 2" {X X X X 5 6 X X X X} + 2 "UPDATE t7 SET s = q LIMIT 2 OFFSET 7" {X X X X 5 6 X 8 9 X} +} + +# EVIDENCE-OF: R-10927-26133 The ORDER BY clause on an UPDATE statement +# is used only to determine which rows fall within the LIMIT. The order +# in which rows are modified is arbitrary and is not influenced by the +# ORDER BY clause. +# +do_execsql_test e_update-3.5.0 { + CREATE TABLE t8(x); + CREATE TRIGGER tr7 BEFORE UPDATE ON t7 BEGIN + INSERT INTO t8 VALUES(old.q); + END; +} {} +do_update_tests e_update-3.5 -query { SELECT x FROM t8 ; DELETE FROM t8 } { + 1 "UPDATE t7 SET s = q ORDER BY r LIMIT -1" {1 2 3 4 5 6 7 8 9 10} + 2 "UPDATE t7 SET s = q ORDER BY r ASC LIMIT -1" {1 2 3 4 5 6 7 8 9 10} + 3 "UPDATE t7 SET s = q ORDER BY r DESC LIMIT -1" {1 2 3 4 5 6 7 8 9 10} + 4 "UPDATE t7 SET s = q ORDER BY q DESC LIMIT 5" {6 7 8 9 10} +} + + +} ;# ifcapable update_delete_limit + +finish_test + diff --git a/test/e_vacuum.test b/test/e_vacuum.test new file mode 100644 index 00000000..a7863c45 --- /dev/null +++ b/test/e_vacuum.test @@ -0,0 +1,301 @@ +# 2010 September 24 +# +# 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 implements tests to verify that the "testable statements" in +# the lang_vacuum.html document are correct. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +sqlite3_test_control_pending_byte 0x1000000 + +proc create_db {{sql ""}} { + catch { db close } + forcedelete test.db + sqlite3 db test.db + + db transaction { + execsql { PRAGMA page_size = 1024; } + execsql $sql + execsql { + CREATE TABLE t1(a PRIMARY KEY, b UNIQUE); + INSERT INTO t1 VALUES(1, randomblob(400)); + INSERT INTO t1 SELECT a+1, randomblob(400) FROM t1; + INSERT INTO t1 SELECT a+2, randomblob(400) FROM t1; + INSERT INTO t1 SELECT a+4, randomblob(400) FROM t1; + INSERT INTO t1 SELECT a+8, randomblob(400) FROM t1; + INSERT INTO t1 SELECT a+16, randomblob(400) FROM t1; + INSERT INTO t1 SELECT a+32, randomblob(400) FROM t1; + INSERT INTO t1 SELECT a+64, randomblob(400) FROM t1; + + CREATE TABLE t2(a PRIMARY KEY, b UNIQUE); + INSERT INTO t2 SELECT * FROM t1; + } + } + + return [expr {[file size test.db] / 1024}] +} + +# This proc returns the number of contiguous blocks of pages that make up +# the table or index named by the only argument. For example, if the table +# occupies database pages 3, 4, 8 and 9, then this command returns 2 (there +# are 2 fragments - one consisting of pages 3 and 4, the other of fragments +# 8 and 9). +# +proc fragment_count {name} { + execsql { CREATE VIRTUAL TABLE temp.stat USING dbstat } + set nFrag 1 + db eval {SELECT pageno FROM stat WHERE name = 't1' ORDER BY pageno} { + if {[info exists prevpageno] && $prevpageno != $pageno-1} { + incr nFrag + } + set prevpageno $pageno + } + execsql { DROP TABLE temp.stat } + set nFrag +} + + +# EVIDENCE-OF: R-63707-33375 -- syntax diagram vacuum-stmt +# +do_execsql_test e_vacuum-0.1 { VACUUM } {} + +# EVIDENCE-OF: R-51469-36013 Unless SQLite is running in +# "auto_vacuum=FULL" mode, when a large amount of data is deleted from +# the database file it leaves behind empty space, or "free" database +# pages. +# +# EVIDENCE-OF: R-60541-63059 Running VACUUM to rebuild the database +# reclaims this space and reduces the size of the database file. +# +foreach {tn avmode sz} { + 1 none 7 + 2 full 8 + 3 incremental 8 +} { + set nPage [create_db "PRAGMA auto_vacuum = $avmode"] + + do_execsql_test e_vacuum-1.1.$tn.1 { + DELETE FROM t1; + DELETE FROM t2; + } {} + + if {$avmode == "full"} { + # This branch tests the "unless ... auto_vacuum=FULL" in the requirement + # above. If auto_vacuum is set to FULL, then no empty space is left in + # the database file. + do_execsql_test e_vacuum-1.1.$tn.2 {PRAGMA freelist_count} 0 + } else { + set freelist [expr {$nPage - $sz}] + if {$avmode == "incremental"} { + # The page size is 1024 bytes. Therefore, assuming the database contains + # somewhere between 207 and 411 pages (it does), there are 2 pointer-map + # pages. + incr freelist -2 + } + do_execsql_test e_vacuum-1.1.$tn.3 {PRAGMA freelist_count} $freelist + do_execsql_test e_vacuum-1.1.$tn.4 {VACUUM} {} + } + + do_test e_vacuum-1.1.$tn.5 { expr {[file size test.db] / 1024} } $sz +} + +# EVIDENCE-OF: R-50943-18433 Frequent inserts, updates, and deletes can +# cause the database file to become fragmented - where data for a single +# table or index is scattered around the database file. +# +# EVIDENCE-OF: R-05791-54928 Running VACUUM ensures that each table and +# index is largely stored contiguously within the database file. +# +# e_vacuum-1.2.1 - Perform many INSERT, UPDATE and DELETE ops on table t1. +# e_vacuum-1.2.2 - Verify that t1 and its indexes are now quite fragmented. +# e_vacuum-1.2.3 - Run VACUUM. +# e_vacuum-1.2.4 - Verify that t1 and its indexes are now much +# less fragmented. +# +ifcapable vtab { + create_db + register_dbstat_vtab db + do_execsql_test e_vacuum-1.2.1 { + DELETE FROM t1 WHERE a%2; + INSERT INTO t1 SELECT b, a FROM t2 WHERE a%2; + UPDATE t1 SET b=randomblob(600) WHERE (a%2)==0; + } {} + + do_test e_vacuum-1.2.2.1 { expr [fragment_count t1]>100 } 1 + do_test e_vacuum-1.2.2.2 { expr [fragment_count sqlite_autoindex_t1_1]>100 } 1 + do_test e_vacuum-1.2.2.3 { expr [fragment_count sqlite_autoindex_t1_2]>100 } 1 + + do_execsql_test e_vacuum-1.2.3 { VACUUM } {} + + # In practice, the tables and indexes each end up stored as two fragments - + # one containing the root page and another containing all other pages. + # + do_test e_vacuum-1.2.4.1 { fragment_count t1 } 2 + do_test e_vacuum-1.2.4.2 { fragment_count sqlite_autoindex_t1_1 } 2 + do_test e_vacuum-1.2.4.3 { fragment_count sqlite_autoindex_t1_2 } 2 +} + +# EVIDENCE-OF: R-20474-44465 Normally, the database page_size and +# whether or not the database supports auto_vacuum must be configured +# before the database file is actually created. +# +do_test e_vacuum-1.3.1.1 { + create_db "PRAGMA page_size = 1024 ; PRAGMA auto_vacuum = FULL" + execsql { PRAGMA page_size ; PRAGMA auto_vacuum } +} {1024 1} +do_test e_vacuum-1.3.1.2 { + execsql { PRAGMA page_size = 2048 } + execsql { PRAGMA auto_vacuum = NONE } + execsql { PRAGMA page_size ; PRAGMA auto_vacuum } +} {1024 1} + +# EVIDENCE-OF: R-08570-19916 However, when not in write-ahead log mode, +# the page_size and/or auto_vacuum properties of an existing database +# may be changed by using the page_size and/or pragma auto_vacuum +# pragmas and then immediately VACUUMing the database. +# +do_test e_vacuum-1.3.2.1 { + execsql { PRAGMA journal_mode = delete } + execsql { PRAGMA page_size = 2048 } + execsql { PRAGMA auto_vacuum = NONE } + execsql VACUUM + execsql { PRAGMA page_size ; PRAGMA auto_vacuum } +} {2048 0} + +# EVIDENCE-OF: R-48521-51450 When in write-ahead log mode, only the +# auto_vacuum support property can be changed using VACUUM. +# +do_test e_vacuum-1.3.3.1 { + execsql { PRAGMA journal_mode = wal } + execsql { PRAGMA page_size ; PRAGMA auto_vacuum } +} {2048 0} +do_test e_vacuum-1.3.3.2 { + execsql { PRAGMA page_size = 1024 } + execsql { PRAGMA auto_vacuum = FULL } + execsql VACUUM + execsql { PRAGMA page_size ; PRAGMA auto_vacuum } +} {2048 1} + +# EVIDENCE-OF: R-38001-03952 VACUUM only works on the main database. It +# is not possible to VACUUM an attached database file. +forcedelete test.db2 +create_db { PRAGMA auto_vacuum = NONE } +do_execsql_test e_vacuum-2.1.1 { + ATTACH 'test.db2' AS aux; + PRAGMA aux.page_size = 1024; + CREATE TABLE aux.t3 AS SELECT * FROM t1; + DELETE FROM t3; +} {} +set original_size [file size test.db2] + +# Try everything we can think of to get the aux database vacuumed: +do_execsql_test e_vacuum-2.1.3 { VACUUM } {} +do_execsql_test e_vacuum-2.1.4 { VACUUM aux } {} +do_execsql_test e_vacuum-2.1.5 { VACUUM 'test.db2' } {} + +# Despite our efforts, space in the aux database has not been reclaimed: +do_test e_vacuum-2.1.6 { expr {[file size test.db2]==$::original_size} } 1 + +# EVIDENCE-OF: R-17495-17419 The VACUUM command may change the ROWIDs of +# entries in any tables that do not have an explicit INTEGER PRIMARY +# KEY. +# +# Tests e_vacuum-3.1.1 - 3.1.2 demonstrate that rowids can change when +# a database is VACUUMed. Tests e_vacuum-3.1.3 - 3.1.4 show that adding +# an INTEGER PRIMARY KEY column to a table stops this from happening. +# +do_execsql_test e_vacuum-3.1.1 { + CREATE TABLE t4(x); + INSERT INTO t4(x) VALUES('x'); + INSERT INTO t4(x) VALUES('y'); + INSERT INTO t4(x) VALUES('z'); + DELETE FROM t4 WHERE x = 'y'; + SELECT rowid, x FROM t4; +} {1 x 3 z} +do_execsql_test e_vacuum-3.1.2 { + VACUUM; + SELECT rowid, x FROM t4; +} {1 x 2 z} + +do_execsql_test e_vacuum-3.1.3 { + CREATE TABLE t5(x, y INTEGER PRIMARY KEY); + INSERT INTO t5(x) VALUES('x'); + INSERT INTO t5(x) VALUES('y'); + INSERT INTO t5(x) VALUES('z'); + DELETE FROM t5 WHERE x = 'y'; + SELECT rowid, x FROM t5; +} {1 x 3 z} +do_execsql_test e_vacuum-3.1.4 { + VACUUM; + SELECT rowid, x FROM t5; +} {1 x 3 z} + +# EVIDENCE-OF: R-49563-33883 A VACUUM will fail if there is an open +# transaction, or if there are one or more active SQL statements when it +# is run. +# +do_execsql_test e_vacuum-3.2.1.1 { BEGIN } {} +do_catchsql_test e_vacuum-3.2.1.2 { + VACUUM +} {1 {cannot VACUUM from within a transaction}} +do_execsql_test e_vacuum-3.2.1.3 { COMMIT } {} +do_execsql_test e_vacuum-3.2.1.4 { VACUUM } {} +do_execsql_test e_vacuum-3.2.1.5 { SAVEPOINT x } {} +do_catchsql_test e_vacuum-3.2.1.6 { + VACUUM +} {1 {cannot VACUUM from within a transaction}} +do_execsql_test e_vacuum-3.2.1.7 { COMMIT } {} +do_execsql_test e_vacuum-3.2.1.8 { VACUUM } {} + +create_db +do_test e_vacuum-3.2.2.1 { + set res "" + db eval { SELECT a FROM t1 } { + if {$a == 10} { set res [catchsql VACUUM] } + } + set res +} {1 {cannot VACUUM - SQL statements in progress}} + + +# EVIDENCE-OF: R-38735-12540 As of SQLite version 3.1, an alternative to +# using the VACUUM command to reclaim space after data has been deleted +# is auto-vacuum mode, enabled using the auto_vacuum pragma. +# +do_test e_vacuum-3.3.1 { + create_db { PRAGMA auto_vacuum = FULL } + execsql { PRAGMA auto_vacuum } +} {1} + +# EVIDENCE-OF: R-64844-34873 When auto_vacuum is enabled for a database +# free pages may be reclaimed after deleting data, causing the file to +# shrink, without rebuilding the entire database using VACUUM. +# +do_test e_vacuum-3.3.2.1 { + create_db { PRAGMA auto_vacuum = FULL } + execsql { + DELETE FROM t1; + DELETE FROM t2; + } + expr {[file size test.db] / 1024} +} {8} +do_test e_vacuum-3.3.2.2 { + create_db { PRAGMA auto_vacuum = INCREMENTAL } + execsql { + DELETE FROM t1; + DELETE FROM t2; + PRAGMA incremental_vacuum; + } + expr {[file size test.db] / 1024} +} {8} + +finish_test diff --git a/test/enc4.test b/test/enc4.test new file mode 100644 index 00000000..de2a1b88 --- /dev/null +++ b/test/enc4.test @@ -0,0 +1,139 @@ +# 2010 Sept 29 +# +# 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 implements regression tests for SQLite library. The focus of +# this file is testing the SQLite routines used for converting between the +# various suported unicode encodings (UTF-8, UTF-16, UTF-16le and +# UTF-16be). +# +# $Id: enc4.test,v 1.0 2010/09/29 08:29:32 shaneh Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# If UTF16 support is disabled, ignore the tests in this file +# +ifcapable {!utf16} { + finish_test + return +} + +db close + +# The three unicode encodings understood by SQLite. +set encodings [list UTF-8 UTF-16le UTF-16be] + +# initial value to use in SELECT +set inits [list 1 1.0 1. 1e0] + +# vals +set vals [list\ +"922337203685477580792233720368547758079223372036854775807"\ +"100000000000000000000000000000000000000000000000000000000"\ +"1.0000000000000000000000000000000000000000000000000000000"\ +] + +set i 1 +foreach enc $encodings { + + file delete -force test.db + sqlite3 db test.db + db eval "PRAGMA encoding = \"$enc\"" + + do_test enc4-$i.1 { + db eval {PRAGMA encoding} + } $enc + + set j 1 + foreach init $inits { + + do_test enc4-$i.$j.2 { + set S [sqlite3_prepare_v2 db "SELECT $init+?" -1 dummy] + sqlite3_expired $S + } {0} + + set k 1 + foreach val $vals { + for {set x 1} {$x<18} {incr x} { + set part [expr $init + [string range $val 0 [expr $x-1]]] + regsub {e\+0} $part {e+} part + regsub {^1e} $part {1.0e} part + + do_test enc4-$i.$j.$k.3.$x { + sqlite3_reset $S + sqlite3_bind_text $S 1 $val $x + sqlite3_step $S + sqlite3_column_text $S 0 + } [list $part] + + do_test enc4-$i.$j.$k.4.$x { + sqlite3_reset $S + sqlite3_bind_text16 $S 1 [encoding convertto unicode $val] [expr $x*2] + sqlite3_step $S + sqlite3_column_text $S 0 + } [list $part] + } + + incr k + } + + do_test enc4-$i.$j.5 { + sqlite3_finalize $S + } {SQLITE_OK} + + incr j + } + + db close + incr i +} + +file delete -force test.db +sqlite3 db test.db + +do_test enc4-4.1 { + db eval "select 1+1." +} {2.0} + +do_test enc4-4.2.1 { + set S [sqlite3_prepare_v2 db "SELECT 1+1." -1 dummy] + sqlite3_step $S + sqlite3_column_text $S 0 +} {2.0} + +do_test enc4-4.2.2 { + sqlite3_finalize $S +} {SQLITE_OK} + +do_test enc4-4.3.1 { + set S [sqlite3_prepare_v2 db "SELECT 1+?" -1 dummy] + sqlite3_bind_text $S 1 "1." 2 + sqlite3_step $S + sqlite3_column_text $S 0 +} {2.0} + +do_test enc4-4.3.2 { + sqlite3_finalize $S +} {SQLITE_OK} + +do_test enc4-4.4.1 { + set S [sqlite3_prepare_v2 db "SELECT 1+?" -1 dummy] + sqlite3_bind_text $S 1 "1.0" 2 + sqlite3_step $S + sqlite3_column_text $S 0 +} {2.0} + +do_test enc4-4.4.2 { + sqlite3_finalize $S +} {SQLITE_OK} + +db close + +finish_test diff --git a/test/eqp.test b/test/eqp.test new file mode 100644 index 00000000..cd08b8ff --- /dev/null +++ b/test/eqp.test @@ -0,0 +1,535 @@ +# 2010 November 6 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +set testprefix eqp + +#------------------------------------------------------------------------- +# +# eqp-1.*: Assorted tests. +# eqp-2.*: Tests for single select statements. +# eqp-3.*: Select statements that execute sub-selects. +# eqp-4.*: Compound select statements. +# + +proc det {args} { uplevel do_eqp_test $args } + +do_execsql_test 1.1 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a); + CREATE INDEX i2 ON t1(b); + CREATE TABLE t2(a, b); + CREATE TABLE t3(a, b); +} + +do_eqp_test 1.2 { + SELECT * FROM t2, t1 WHERE t1.a=1 OR t1.b=2; +} { + 0 0 1 {SEARCH TABLE t1 USING INDEX i1 (a=?) (~10 rows)} + 0 0 1 {SEARCH TABLE t1 USING INDEX i2 (b=?) (~10 rows)} + 0 1 0 {SCAN TABLE t2 (~1000000 rows)} +} +do_eqp_test 1.3 { + SELECT * FROM t2 CROSS JOIN t1 WHERE t1.a=1 OR t1.b=2; +} { + 0 0 0 {SCAN TABLE t2 (~1000000 rows)} + 0 1 1 {SEARCH TABLE t1 USING INDEX i1 (a=?) (~10 rows)} + 0 1 1 {SEARCH TABLE t1 USING INDEX i2 (b=?) (~10 rows)} +} +do_eqp_test 1.3 { + SELECT a FROM t1 ORDER BY a +} { + 0 0 0 {SCAN TABLE t1 USING COVERING INDEX i1 (~1000000 rows)} +} +do_eqp_test 1.4 { + SELECT a FROM t1 ORDER BY +a +} { + 0 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +do_eqp_test 1.5 { + SELECT a FROM t1 WHERE a=4 +} { + 0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?) (~10 rows)} +} +do_eqp_test 1.6 { + SELECT DISTINCT count(*) FROM t3 GROUP BY a; +} { + 0 0 0 {SCAN TABLE t3 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR GROUP BY} + 0 0 0 {USE TEMP B-TREE FOR DISTINCT} +} + +do_eqp_test 1.7 { + SELECT * FROM t3 JOIN (SELECT 1) +} { + 0 0 1 {SCAN SUBQUERY 1 (~1 rows)} + 0 1 0 {SCAN TABLE t3 (~1000000 rows)} +} +do_eqp_test 1.8 { + SELECT * FROM t3 JOIN (SELECT 1 UNION SELECT 2) +} { + 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)} + 0 0 1 {SCAN SUBQUERY 1 (~2 rows)} + 0 1 0 {SCAN TABLE t3 (~1000000 rows)} +} +do_eqp_test 1.9 { + SELECT * FROM t3 JOIN (SELECT 1 EXCEPT SELECT a FROM t3 LIMIT 17) +} { + 3 0 0 {SCAN TABLE t3 (~1000000 rows)} + 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (EXCEPT)} + 0 0 1 {SCAN SUBQUERY 1 (~17 rows)} + 0 1 0 {SCAN TABLE t3 (~1000000 rows)} +} +do_eqp_test 1.10 { + SELECT * FROM t3 JOIN (SELECT 1 INTERSECT SELECT a FROM t3 LIMIT 17) +} { + 3 0 0 {SCAN TABLE t3 (~1000000 rows)} + 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (INTERSECT)} + 0 0 1 {SCAN SUBQUERY 1 (~1 rows)} + 0 1 0 {SCAN TABLE t3 (~1000000 rows)} +} + +do_eqp_test 1.11 { + SELECT * FROM t3 JOIN (SELECT 1 UNION ALL SELECT a FROM t3 LIMIT 17) +} { + 3 0 0 {SCAN TABLE t3 (~1000000 rows)} + 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 (UNION ALL)} + 0 0 1 {SCAN SUBQUERY 1 (~17 rows)} + 0 1 0 {SCAN TABLE t3 (~1000000 rows)} +} + +#------------------------------------------------------------------------- +# Test cases eqp-2.* - tests for single select statements. +# +drop_all_tables +do_execsql_test 2.1 { + CREATE TABLE t1(x, y); + + CREATE TABLE t2(x, y); + CREATE INDEX t2i1 ON t2(x); +} + +det 2.2.1 "SELECT DISTINCT min(x), max(x) FROM t1 GROUP BY x ORDER BY 1" { + 0 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR GROUP BY} + 0 0 0 {USE TEMP B-TREE FOR DISTINCT} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +det 2.2.2 "SELECT DISTINCT min(x), max(x) FROM t2 GROUP BY x ORDER BY 1" { + 0 0 0 {SCAN TABLE t2 USING COVERING INDEX t2i1 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR DISTINCT} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +det 2.2.3 "SELECT DISTINCT * FROM t1" { + 0 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR DISTINCT} +} +det 2.2.4 "SELECT DISTINCT * FROM t1, t2" { + 0 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 1 1 {SCAN TABLE t2 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR DISTINCT} +} +det 2.2.5 "SELECT DISTINCT * FROM t1, t2 ORDER BY t1.x" { + 0 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 1 1 {SCAN TABLE t2 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR DISTINCT} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +det 2.2.6 "SELECT DISTINCT t2.x FROM t1, t2 ORDER BY t2.x" { + 0 0 1 {SCAN TABLE t2 USING COVERING INDEX t2i1 (~1000000 rows)} + 0 1 0 {SCAN TABLE t1 (~1000000 rows)} +} + +det 2.3.1 "SELECT max(x) FROM t2" { + 0 0 0 {SEARCH TABLE t2 USING COVERING INDEX t2i1 (~1 rows)} +} +det 2.3.2 "SELECT min(x) FROM t2" { + 0 0 0 {SEARCH TABLE t2 USING COVERING INDEX t2i1 (~1 rows)} +} +det 2.3.3 "SELECT min(x), max(x) FROM t2" { + 0 0 0 {SCAN TABLE t2 (~1000000 rows)} +} + +det 2.4.1 "SELECT * FROM t1 WHERE rowid=?" { + 0 0 0 {SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} +} + + + +#------------------------------------------------------------------------- +# Test cases eqp-3.* - tests for select statements that use sub-selects. +# +do_eqp_test 3.1.1 { + SELECT (SELECT x FROM t1 AS sub) FROM t1; +} { + 0 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 0 0 {EXECUTE SCALAR SUBQUERY 1} + 1 0 0 {SCAN TABLE t1 AS sub (~1000000 rows)} +} +do_eqp_test 3.1.2 { + SELECT * FROM t1 WHERE (SELECT x FROM t1 AS sub); +} { + 0 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 0 0 {EXECUTE SCALAR SUBQUERY 1} + 1 0 0 {SCAN TABLE t1 AS sub (~1000000 rows)} +} +do_eqp_test 3.1.3 { + SELECT * FROM t1 WHERE (SELECT x FROM t1 AS sub ORDER BY y); +} { + 0 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 0 0 {EXECUTE SCALAR SUBQUERY 1} + 1 0 0 {SCAN TABLE t1 AS sub (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +do_eqp_test 3.1.4 { + SELECT * FROM t1 WHERE (SELECT x FROM t2 ORDER BY x); +} { + 0 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 0 0 {EXECUTE SCALAR SUBQUERY 1} + 1 0 0 {SCAN TABLE t2 USING COVERING INDEX t2i1 (~1000000 rows)} +} + +det 3.2.1 { + SELECT * FROM (SELECT * FROM t1 ORDER BY x LIMIT 10) ORDER BY y LIMIT 5 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {SCAN SUBQUERY 1 (~10 rows)} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +det 3.2.2 { + SELECT * FROM + (SELECT * FROM t1 ORDER BY x LIMIT 10) AS x1, + (SELECT * FROM t2 ORDER BY x LIMIT 10) AS x2 + ORDER BY x2.y LIMIT 5 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {SCAN TABLE t2 USING INDEX t2i1 (~1000000 rows)} + 0 0 0 {SCAN SUBQUERY 1 AS x1 (~10 rows)} + 0 1 1 {SCAN SUBQUERY 2 AS x2 (~10 rows)} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} + +det 3.3.1 { + SELECT * FROM t1 WHERE y IN (SELECT y FROM t2) +} { + 0 0 0 {SCAN TABLE t1 (~100000 rows)} + 0 0 0 {EXECUTE LIST SUBQUERY 1} + 1 0 0 {SCAN TABLE t2 (~1000000 rows)} +} +det 3.3.2 { + SELECT * FROM t1 WHERE y IN (SELECT y FROM t2 WHERE t1.x!=t2.x) +} { + 0 0 0 {SCAN TABLE t1 (~500000 rows)} + 0 0 0 {EXECUTE CORRELATED LIST SUBQUERY 1} + 1 0 0 {SCAN TABLE t2 (~500000 rows)} +} +det 3.3.3 { + SELECT * FROM t1 WHERE EXISTS (SELECT y FROM t2 WHERE t1.x!=t2.x) +} { + 0 0 0 {SCAN TABLE t1 (~500000 rows)} + 0 0 0 {EXECUTE CORRELATED SCALAR SUBQUERY 1} + 1 0 0 {SCAN TABLE t2 (~500000 rows)} +} + +#------------------------------------------------------------------------- +# Test cases eqp-4.* - tests for composite select statements. +# +do_eqp_test 4.1.1 { + SELECT * FROM t1 UNION ALL SELECT * FROM t2 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (UNION ALL)} +} +do_eqp_test 4.1.2 { + SELECT * FROM t1 UNION ALL SELECT * FROM t2 ORDER BY 2 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (UNION ALL)} +} +do_eqp_test 4.1.3 { + SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY 2 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (UNION)} +} +do_eqp_test 4.1.4 { + SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 2 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (INTERSECT)} +} +do_eqp_test 4.1.5 { + SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 2 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (EXCEPT)} +} + +do_eqp_test 4.2.2 { + SELECT * FROM t1 UNION ALL SELECT * FROM t2 ORDER BY 1 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {SCAN TABLE t2 USING INDEX t2i1 (~1000000 rows)} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (UNION ALL)} +} +do_eqp_test 4.2.3 { + SELECT * FROM t1 UNION SELECT * FROM t2 ORDER BY 1 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (UNION)} +} +do_eqp_test 4.2.4 { + SELECT * FROM t1 INTERSECT SELECT * FROM t2 ORDER BY 1 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (INTERSECT)} +} +do_eqp_test 4.2.5 { + SELECT * FROM t1 EXCEPT SELECT * FROM t2 ORDER BY 1 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 1 0 0 {USE TEMP B-TREE FOR ORDER BY} + 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (EXCEPT)} +} + +do_eqp_test 4.3.1 { + SELECT x FROM t1 UNION SELECT x FROM t2 +} { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 USING TEMP B-TREE (UNION)} +} + +do_eqp_test 4.3.2 { + SELECT x FROM t1 UNION SELECT x FROM t2 UNION SELECT x FROM t1 +} { + 2 0 0 {SCAN TABLE t1 (~1000000 rows)} + 3 0 0 {SCAN TABLE t2 (~1000000 rows)} + 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)} + 4 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 4 USING TEMP B-TREE (UNION)} +} +do_eqp_test 4.3.3 { + SELECT x FROM t1 UNION SELECT x FROM t2 UNION SELECT x FROM t1 ORDER BY 1 +} { + 2 0 0 {SCAN TABLE t1 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 3 0 0 {SCAN TABLE t2 USING COVERING INDEX t2i1 (~1000000 rows)} + 1 0 0 {COMPOUND SUBQUERIES 2 AND 3 (UNION)} + 4 0 0 {SCAN TABLE t1 (~1000000 rows)} + 4 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 4 (UNION)} +} + +#------------------------------------------------------------------------- +# This next block of tests verifies that the examples on the +# lang_explain.html page are correct. +# +drop_all_tables + +# EVIDENCE-OF: R-64208-08323 sqlite> EXPLAIN QUERY PLAN SELECT a, b +# FROM t1 WHERE a=1; 0|0|0|SCAN TABLE t1 (~100000 rows) +do_execsql_test 5.1.0 { CREATE TABLE t1(a, b) } +det 5.1.1 "SELECT a, b FROM t1 WHERE a=1" { + 0 0 0 {SCAN TABLE t1 (~100000 rows)} +} + +# EVIDENCE-OF: R-09022-44606 sqlite> CREATE INDEX i1 ON t1(a); +# sqlite> EXPLAIN QUERY PLAN SELECT a, b FROM t1 WHERE a=1; +# 0|0|0|SEARCH TABLE t1 USING INDEX i1 (a=?) (~10 rows) +do_execsql_test 5.2.0 { CREATE INDEX i1 ON t1(a) } +det 5.2.1 "SELECT a, b FROM t1 WHERE a=1" { + 0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?) (~10 rows)} +} + +# EVIDENCE-OF: R-62228-34103 sqlite> CREATE INDEX i2 ON t1(a, b); +# sqlite> EXPLAIN QUERY PLAN SELECT a, b FROM t1 WHERE a=1; +# 0|0|0|SEARCH TABLE t1 USING COVERING INDEX i2 (a=?) (~10 rows) +do_execsql_test 5.3.0 { CREATE INDEX i2 ON t1(a, b) } +det 5.3.1 "SELECT a, b FROM t1 WHERE a=1" { + 0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=?) (~10 rows)} +} + +# EVIDENCE-OF: R-22253-05302 sqlite> EXPLAIN QUERY PLAN SELECT t1.*, +# t2.* FROM t1, t2 WHERE t1.a=1 AND t1.b>2; 0|0|0|SEARCH TABLE t1 +# USING COVERING INDEX i2 (a=? AND b>?) (~3 rows) 0|1|1|SCAN TABLE t2 +# (~1000000 rows) +do_execsql_test 5.4.0 {CREATE TABLE t2(c, d)} +det 5.4.1 "SELECT t1.*, t2.* FROM t1, t2 WHERE t1.a=1 AND t1.b>2" { + 0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=? AND b>?) (~3 rows)} + 0 1 1 {SCAN TABLE t2 (~1000000 rows)} +} + +# EVIDENCE-OF: R-21040-07025 sqlite> EXPLAIN QUERY PLAN SELECT t1.*, +# t2.* FROM t2, t1 WHERE t1.a=1 AND t1.b>2; 0|0|1|SEARCH TABLE t1 +# USING COVERING INDEX i2 (a=? AND b>?) (~3 rows) 0|1|0|SCAN TABLE t2 +# (~1000000 rows) +det 5.5 "SELECT t1.*, t2.* FROM t2, t1 WHERE t1.a=1 AND t1.b>2" { + 0 0 1 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=? AND b>?) (~3 rows)} + 0 1 0 {SCAN TABLE t2 (~1000000 rows)} +} + +# EVIDENCE-OF: R-39007-61103 sqlite> CREATE INDEX i3 ON t1(b); +# sqlite> EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=1 OR b=2; +# 0|0|0|SEARCH TABLE t1 USING COVERING INDEX i2 (a=?) (~10 rows) +# 0|0|0|SEARCH TABLE t1 USING INDEX i3 (b=?) (~10 rows) +do_execsql_test 5.5.0 {CREATE INDEX i3 ON t1(b)} +det 5.6.1 "SELECT * FROM t1 WHERE a=1 OR b=2" { + 0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=?) (~10 rows)} + 0 0 0 {SEARCH TABLE t1 USING INDEX i3 (b=?) (~10 rows)} +} + +# EVIDENCE-OF: R-33025-54904 sqlite> EXPLAIN QUERY PLAN SELECT c, d +# FROM t2 ORDER BY c; 0|0|0|SCAN TABLE t2 (~1000000 rows) 0|0|0|USE TEMP +# B-TREE FOR ORDER BY +det 5.7 "SELECT c, d FROM t2 ORDER BY c" { + 0 0 0 {SCAN TABLE t2 (~1000000 rows)} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} + +# EVIDENCE-OF: R-38854-22809 sqlite> CREATE INDEX i4 ON t2(c); +# sqlite> EXPLAIN QUERY PLAN SELECT c, d FROM t2 ORDER BY c; +# 0|0|0|SCAN TABLE t2 USING INDEX i4 (~1000000 rows) +do_execsql_test 5.8.0 {CREATE INDEX i4 ON t2(c)} +det 5.8.1 "SELECT c, d FROM t2 ORDER BY c" { + 0 0 0 {SCAN TABLE t2 USING INDEX i4 (~1000000 rows)} +} + +# EVIDENCE-OF: R-29884-43993 sqlite> EXPLAIN QUERY PLAN SELECT +# (SELECT b FROM t1 WHERE a=0), (SELECT a FROM t1 WHERE b=t2.c) FROM t2; +# 0|0|0|SCAN TABLE t2 (~1000000 rows) 0|0|0|EXECUTE SCALAR SUBQUERY 1 +# 1|0|0|SEARCH TABLE t1 USING COVERING INDEX i2 (a=?) (~10 rows) +# 0|0|0|EXECUTE CORRELATED SCALAR SUBQUERY 2 2|0|0|SEARCH TABLE t1 USING +# INDEX i3 (b=?) (~10 rows) +det 5.9 { + SELECT (SELECT b FROM t1 WHERE a=0), (SELECT a FROM t1 WHERE b=t2.c) FROM t2 +} { + 0 0 0 {SCAN TABLE t2 (~1000000 rows)} + 0 0 0 {EXECUTE SCALAR SUBQUERY 1} + 1 0 0 {SEARCH TABLE t1 USING COVERING INDEX i2 (a=?) (~10 rows)} + 0 0 0 {EXECUTE CORRELATED SCALAR SUBQUERY 2} + 2 0 0 {SEARCH TABLE t1 USING INDEX i3 (b=?) (~10 rows)} +} + +# EVIDENCE-OF: R-17911-16445 sqlite> EXPLAIN QUERY PLAN SELECT +# count(*) FROM (SELECT max(b) AS x FROM t1 GROUP BY a) GROUP BY x; +# 1|0|0|SCAN TABLE t1 USING COVERING INDEX i2 (~1000000 rows) 0|0|0|SCAN +# SUBQUERY 1 (~1000000 rows) 0|0|0|USE TEMP B-TREE FOR GROUP BY +det 5.10 { + SELECT count(*) FROM (SELECT max(b) AS x FROM t1 GROUP BY a) GROUP BY x +} { + 1 0 0 {SCAN TABLE t1 USING COVERING INDEX i2 (~1000000 rows)} + 0 0 0 {SCAN SUBQUERY 1 (~100 rows)} + 0 0 0 {USE TEMP B-TREE FOR GROUP BY} +} + +# EVIDENCE-OF: R-18544-33103 sqlite> EXPLAIN QUERY PLAN SELECT * FROM +# (SELECT * FROM t2 WHERE c=1), t1; 0|0|0|SEARCH TABLE t2 USING INDEX i4 +# (c=?) (~10 rows) 0|1|1|SCAN TABLE t1 (~1000000 rows) +det 5.11 "SELECT * FROM (SELECT * FROM t2 WHERE c=1), t1" { + 0 0 0 {SEARCH TABLE t2 USING INDEX i4 (c=?) (~10 rows)} + 0 1 1 {SCAN TABLE t1 (~1000000 rows)} +} + +# EVIDENCE-OF: R-40701-42164 sqlite> EXPLAIN QUERY PLAN SELECT a FROM +# t1 UNION SELECT c FROM t2; 1|0|0|SCAN TABLE t1 (~1000000 rows) +# 2|0|0|SCAN TABLE t2 (~1000000 rows) 0|0|0|COMPOUND SUBQUERIES 1 AND 2 +# USING TEMP B-TREE (UNION) +det 5.12 "SELECT a FROM t1 UNION SELECT c FROM t2" { + 1 0 0 {SCAN TABLE t1 (~1000000 rows)} + 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 USING TEMP B-TREE (UNION)} +} + +# EVIDENCE-OF: R-61538-24748 sqlite> EXPLAIN QUERY PLAN SELECT a FROM +# t1 EXCEPT SELECT d FROM t2 ORDER BY 1; 1|0|0|SCAN TABLE t1 USING +# COVERING INDEX i2 (~1000000 rows) 2|0|0|SCAN TABLE t2 (~1000000 rows) +# 2|0|0|USE TEMP B-TREE FOR ORDER BY 0|0|0|COMPOUND SUBQUERIES 1 AND 2 +# (EXCEPT) +det 5.13 "SELECT a FROM t1 EXCEPT SELECT d FROM t2 ORDER BY 1" { + 1 0 0 {SCAN TABLE t1 USING COVERING INDEX i2 (~1000000 rows)} + 2 0 0 {SCAN TABLE t2 (~1000000 rows)} + 2 0 0 {USE TEMP B-TREE FOR ORDER BY} + 0 0 0 {COMPOUND SUBQUERIES 1 AND 2 (EXCEPT)} +} + + +#------------------------------------------------------------------------- +# The following tests - eqp-6.* - test that the example C code on +# documentation page eqp.html works. The C code is duplicated in test1.c +# and wrapped in Tcl command [print_explain_query_plan] +# +set boilerplate { + proc explain_query_plan {db sql} { + set stmt [sqlite3_prepare_v2 db $sql -1 DUMMY] + print_explain_query_plan $stmt + sqlite3_finalize $stmt + } + sqlite3 db test.db + explain_query_plan db {%SQL%} + db close + exit +} + +proc do_peqp_test {tn sql res} { + set fd [open script.tcl w] + puts $fd [string map [list %SQL% $sql] $::boilerplate] + close $fd + + uplevel do_test $tn [list { + set fd [open "|[info nameofexec] script.tcl"] + set data [read $fd] + close $fd + set data + }] [list $res] +} + +do_peqp_test 6.1 { + SELECT a FROM t1 EXCEPT SELECT d FROM t2 ORDER BY 1 +} [string trimleft { +1 0 0 SCAN TABLE t1 USING COVERING INDEX i2 (~1000000 rows) +2 0 0 SCAN TABLE t2 (~1000000 rows) +2 0 0 USE TEMP B-TREE FOR ORDER BY +0 0 0 COMPOUND SUBQUERIES 1 AND 2 (EXCEPT) +}] + + + + +finish_test diff --git a/test/exclusive.test b/test/exclusive.test index a4ac9eac..db79d303 100644 --- a/test/exclusive.test +++ b/test/exclusive.test @@ -396,7 +396,7 @@ do_test exclusive-4.5 { # longer active. # db close -sqlite db test.db +sqlite3 db test.db # if we're using proxy locks, we use 3 filedescriptors for a db # that is open but NOT writing changes, normally @@ -469,4 +469,41 @@ do_test exclusive-5.7 { expr $sqlite_open_file_count-$extrafds } {1} +#------------------------------------------------------------------------- + +do_execsql_test exclusive-6.1 { + CREATE TABLE t4(a, b); + INSERT INTO t4 VALUES('Eden', 1955); + BEGIN; + INSERT INTO t4 VALUES('Macmillan', 1957); + INSERT INTO t4 VALUES('Douglas-Home', 1963); + INSERT INTO t4 VALUES('Wilson', 1964); +} +do_test exclusive-6.2 { + forcedelete test2.db test2.db-journal + file copy test.db test2.db + file copy test.db-journal test2.db-journal + sqlite3 db test2.db +} {} + +do_execsql_test exclusive-6.3 { + PRAGMA locking_mode = EXCLUSIVE; + SELECT * FROM t4; +} {exclusive Eden 1955} + +do_test exclusive-6.4 { + db close + forcedelete test.db test.db-journal + set fd [open test.db-journal w] + puts $fd x + close $fd + sqlite3 db test.db +} {} + +do_execsql_test exclusive-6.5 { + PRAGMA locking_mode = EXCLUSIVE; + SELECT * FROM sqlite_master; +} {exclusive} + finish_test + diff --git a/test/exclusive2.test b/test/exclusive2.test index 00e2c168..b2b98ff5 100644 --- a/test/exclusive2.test +++ b/test/exclusive2.test @@ -83,7 +83,7 @@ do_test exclusive2-1.0 { # The following tests - exclusive2-1.X - check that: # # 1-3: Build a database with connection 1, calculate a signature. -# 4-9: Modify the database using a second connection in a way that +# 4-7: Modify the database using a second connection in a way that # does not modify the freelist, then reset the pager change-counter # to the value it had before the modifications. # 8: Check that using the first connection, the database signature @@ -99,6 +99,11 @@ do_test exclusive2-1.0 { # the cache size must be at least 17. Otherwise, some pages will be # loaded from the database file in step 8. # +# For similar reasons, this test does not work with the memsubsys1 permutation. +# Permutation memsubsys1 configures the pcache subsystem to use a static +# allocation of 24 pages (shared between all pagers). This is not enough for +# this test. +# do_test exclusive2-1.1 { execsql { BEGIN; @@ -146,16 +151,19 @@ do_test exclusive2-1.6 { do_test exclusive2-1.7 { pagerChangeCounter test.db 1 } {1} -do_test exclusive2-1.9 { - t1sig - expr {[t1sig] eq $::sig} -} {1} +if {[permutation] != "memsubsys1"} { + do_test exclusive2-1.9 { + t1sig + expr {[t1sig] eq $::sig} + } {1} +} do_test exclusive2-1.10 { pagerChangeCounter test.db 2 } {2} do_test exclusive2-1.11 { expr {[t1sig] eq $::sig} } {0} +db2 close #-------------------------------------------------------------------- # These tests - exclusive2-2.X - are similar to exclusive2-1.X, @@ -245,7 +253,6 @@ do_test exclusive2-2.8 { # db close -db2 close catch {close $::fd} file delete -force test.db file delete -force test.db-journal @@ -255,47 +262,48 @@ do_test exclusive2-3.0 { execsql { BEGIN; CREATE TABLE t1(a UNIQUE); - INSERT INTO t1 VALUES(randstr(10, 400)); - INSERT INTO t1 VALUES(randstr(10, 400)); + INSERT INTO t1 VALUES(randstr(200, 200)); + INSERT INTO t1 VALUES(randstr(200, 200)); COMMIT; } readPagerChangeCounter test.db } {1} do_test exclusive2-3.1 { execsql { - INSERT INTO t1 VALUES(randstr(10, 400)); + INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {2} do_test exclusive2-3.2 { execsql { - INSERT INTO t1 VALUES(randstr(10, 400)); + INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {3} do_test exclusive2-3.3 { execsql { PRAGMA locking_mode = exclusive; - INSERT INTO t1 VALUES(randstr(10, 400)); + INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {4} do_test exclusive2-3.4 { +breakpoint execsql { - INSERT INTO t1 VALUES(randstr(10, 400)); + INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {4} do_test exclusive2-3.5 { execsql { PRAGMA locking_mode = normal; - INSERT INTO t1 VALUES(randstr(10, 400)); + INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {4} do_test exclusive2-3.6 { execsql { - INSERT INTO t1 VALUES(randstr(10, 400)); + INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {5} diff --git a/test/expr.test b/test/expr.test index 87ee8ed3..140ab8d8 100644 --- a/test/expr.test +++ b/test/expr.test @@ -290,7 +290,7 @@ ifcapable floatingpoint { test_expr expr-4.14 {r1='abc', r2='Bbc'} {r1>r2} 1 test_expr expr-4.15 {r1='0', r2='0.0'} {r1==r2} 1 test_expr expr-4.16 {r1='0.000', r2='0.0'} {r1==r2} 1 - test_expr expr-4.17 {r1=' 0.000', r2=' 0.0'} {r1==r2} 0 + test_expr expr-4.17 {r1=' 0.000', r2=' 0.0'} {r1==r2} 1 test_expr expr-4.18 {r1='0.0', r2='abc'} {r1r2} 0 diff --git a/test/fkey2.test b/test/fkey2.test index 07c47e4b..f0cc4d24 100644 --- a/test/fkey2.test +++ b/test/fkey2.test @@ -1414,9 +1414,15 @@ do_test fkey2-17.1.2 { set STMT [sqlite3_prepare_v2 db "INSERT INTO two VALUES(4, 5, 6)" -1 dummy] sqlite3_step $STMT } {SQLITE_CONSTRAINT} -do_test fkey2-17.1.3 { - sqlite3_step $STMT -} {SQLITE_CONSTRAINT} +ifcapable autoreset { + do_test fkey2-17.1.3 { + sqlite3_step $STMT + } {SQLITE_CONSTRAINT} +} else { + do_test fkey2-17.1.3 { + sqlite3_step $STMT + } {SQLITE_MISUSE} +} do_test fkey2-17.1.4 { sqlite3_finalize $STMT } {SQLITE_CONSTRAINT} diff --git a/test/fts3aa.test b/test/fts3aa.test index cc2dabaa..5d79e931 100644 --- a/test/fts3aa.test +++ b/test/fts3aa.test @@ -196,9 +196,32 @@ do_test fts3aa-6.2 { do_test fts3aa-6.3 { execsql {SELECT content FROM t1 WHERE rowid = -1} } {{three four}} -breakpoint do_test fts3aa-6.4 { execsql {SELECT rowid FROM t1 WHERE t1 MATCH 'four'} } {-1 0 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31} +# Test creation of FTS3 and FTS4 tables with columns that contain +# an "=" character. +# +do_execsql_test fts3aa-7.1 { + CREATE VIRTUAL TABLE t2 USING fts3(xyz=abc); + SELECT xyz FROM t2; +} {} +do_catchsql_test fts3aa-7.2 { + CREATE VIRTUAL TABLE t3 USING fts4(xyz=abc); +} {1 {unrecognized parameter: xyz=abc}} +do_catchsql_test fts3aa-7.3 { + CREATE VIRTUAL TABLE t3 USING fts4(xyz = abc); +} {1 {unrecognized parameter: xyz = abc}} + +do_execsql_test fts3aa-7.4 { + CREATE VIRTUAL TABLE t3 USING fts3(tokenize=simple, tokenize=simple); + SELECT tokenize FROM t3; +} {} +do_catchsql_test fts3aa-7.5 { + CREATE VIRTUAL TABLE t4 USING fts4(tokenize=simple, tokenize=simple); +} {1 {unrecognized parameter: tokenize=simple}} + + finish_test + diff --git a/test/fts3ah.test b/test/fts3ah.test index 1a58e49f..3810ec37 100644 --- a/test/fts3ah.test +++ b/test/fts3ah.test @@ -1,37 +1,32 @@ -# 2006 October 31 (scaaarey) +# 2006 October 31 # -# The author disclaims copyright to this source code. +# 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 implements regression tests for SQLite library. The focus -# here is testing correct handling of excessively long terms. -# -# $Id: fts3ah.test,v 1.1 2007/08/20 17:38:42 shess Exp $ +# here is testing correct handling of very long terms. # set testdir [file dirname $argv0] source $testdir/tester.tcl -# If SQLITE_ENABLE_FTS3 is defined, omit this file. +# If SQLITE_ENABLE_FTS3 is not defined, omit this file. ifcapable !fts3 { finish_test return } -# Generate a term of len copies of char. -proc bigterm {char len} { - for {set term ""} {$len>0} {incr len -1} { - append term $char - } - return $term -} - # Generate a document of bigterms based on characters from the list # chars. proc bigtermdoc {chars len} { set doc "" foreach char $chars { - append doc " " [bigterm $char $len] + append doc " " [string repeat $char $len] } return $doc } @@ -41,9 +36,9 @@ set doc1 [bigtermdoc {a b c d} $len] set doc2 [bigtermdoc {b d e f} $len] set doc3 [bigtermdoc {a c e} $len] -set aterm [bigterm a $len] -set bterm [bigterm b $len] -set xterm [bigterm x $len] +set aterm [string repeat a $len] +set bterm [string repeat b $len] +set xterm [string repeat x $len] db eval { CREATE VIRTUAL TABLE t1 USING fts3(content); @@ -61,15 +56,15 @@ do_test fts3ah-1.2 { execsql {SELECT rowid FROM t1 WHERE t1 MATCH $aterm} } {1 3} -do_test fts3ah-1.2 { +do_test fts3ah-1.3 { execsql {SELECT rowid FROM t1 WHERE t1 MATCH $xterm} } {} -do_test fts3ah-1.3 { +do_test fts3ah-1.4 { execsql "SELECT rowid FROM t1 WHERE t1 MATCH '$aterm -$xterm'" } {1 3} -do_test fts3ah-1.4 { +do_test fts3ah-1.5 { execsql "SELECT rowid FROM t1 WHERE t1 MATCH '\"$aterm $bterm\"'" } {1} diff --git a/test/fts3ao.test b/test/fts3ao.test index c3d356e8..cd9df01e 100644 --- a/test/fts3ao.test +++ b/test/fts3ao.test @@ -23,6 +23,8 @@ ifcapable !fts3 { return } +set ::testprefix fts3ao + #--------------------------------------------------------------------- # These tests, fts3ao-1.*, test that ticket #2429 is fixed. # @@ -166,4 +168,53 @@ do_test fts3ao-3.3 { execsql { SELECT a, b, c FROM t1 WHERE c MATCH 'two'; } } {{one three four} {one four} {one two}} +#--------------------------------------------------------------------- +# Test that it is possible to rename an fts3 table within a +# transaction. +# +do_test fts3ao-4.1 { + execsql { + CREATE VIRTUAL TABLE t4 USING fts3; + INSERT INTO t4 VALUES('the quick brown fox'); + } +} {} +do_test fts3ao-4.2 { + execsql { + BEGIN; + INSERT INTO t4 VALUES('jumped over the'); + } +} {} +do_test fts3ao-4.3 { execsql { ALTER TABLE t4 RENAME TO t5; } } {} +do_test fts3ao-4.4 { execsql { INSERT INTO t5 VALUES('lazy dog'); } } {} +do_test fts3ao-4.5 { execsql COMMIT } {} +do_test fts3ao-4.6 { + execsql { SELECT * FROM t5 } +} {{the quick brown fox} {jumped over the} {lazy dog}} +do_test fts3ao-4.7 { + execsql { + BEGIN; + INSERT INTO t5 VALUES('Down came a jumbuck to drink at that billabong'); + ALTER TABLE t5 RENAME TO t6; + INSERT INTO t6 VALUES('Down came the troopers, one, two, three'); + ROLLBACK; + SELECT * FROM t5; + } +} {{the quick brown fox} {jumped over the} {lazy dog}} + +# Test that it is possible to rename an FTS4 table. Renaming an FTS4 table +# involves renaming the extra %_docsize and %_stat tables. +# +do_execsql_test 5.1 { + CREATE VIRTUAL TABLE t7 USING FTS4; + INSERT INTO t7 VALUES('coined by a German clinician'); + SELECT count(*) FROM sqlite_master WHERE name LIKE 't7%'; + SELECT count(*) FROM sqlite_master WHERE name LIKE 't8%'; +} {6 0} +do_execsql_test 5.2 { + ALTER TABLE t7 RENAME TO t8; + SELECT count(*) FROM sqlite_master WHERE name LIKE 't7%'; + SELECT count(*) FROM sqlite_master WHERE name LIKE 't8%'; +} {0 6} + finish_test + diff --git a/test/fts3corrupt.test b/test/fts3corrupt.test new file mode 100644 index 00000000..c2889b87 --- /dev/null +++ b/test/fts3corrupt.test @@ -0,0 +1,135 @@ +# 2010 October 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. +# +#*********************************************************************** +# Test that the FTS3 extension does not crash when it encounters a +# corrupt data structure on disk. +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# If SQLITE_ENABLE_FTS3 is not defined, omit this file. +ifcapable !fts3 { finish_test ; return } + +set ::testprefix fts3corrupt + + +# Test that a doclist with a length field that indicates that the doclist +# extends past the end of the node on which it resides is correctly identified +# as database corruption. +# +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts3; + INSERT INTO t1 VALUES('hello'); +} {} +do_test fts3corrupt-1.1 { + set blob [db one {SELECT root from t1_segdir}] + set blob [binary format a7ca* $blob 24 [string range $blob 8 end]] + execsql { UPDATE t1_segdir SET root = $blob } +} {} +do_test fts3corrupt-1.2 { + foreach w {a b c d e f g h i j k l m n o} { + execsql { INSERT INTO t1 VALUES($w) } + } +} {} +do_catchsql_test 1.3 { + INSERT INTO t1 VALUES('world'); +} {1 {database disk image is malformed}} +do_execsql_test 1.4 { + DROP TABLE t1; +} + +# This block of tests checks that corruption is correctly detected if the +# length field of a term on a leaf node indicates that the term extends past +# the end of the node on which it resides. There are two cases: +# +# 1. The first term on the node. +# 2. The second or subsequent term on the node (prefix compressed term). +# +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts3; + BEGIN; + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('hello'); + COMMIT; +} {} +do_test fts3corrupt-2.1 { + set blob [db one {SELECT root from t1_segdir}] + set blob [binary format a*a* "\x00\x7F" [string range $blob 2 end]] + execsql { UPDATE t1_segdir SET root = $blob } +} {} +do_catchsql_test 2.2 { + SELECT rowid FROM t1 WHERE t1 MATCH 'hello' +} {1 {database disk image is malformed}} + +do_execsql_test 3.0 { + DROP TABLE t1; + CREATE VIRTUAL TABLE t1 USING fts3; + BEGIN; + INSERT INTO t1 VALUES('hello'); + INSERT INTO t1 VALUES('world'); + COMMIT; +} {} +do_test fts3corrupt-3.1 { + set blob [db one {SELECT quote(root) from t1_segdir}] + set blob [binary format a11a*a* $blob "\x7F" [string range $blob 12 end]] + execsql { UPDATE t1_segdir SET root = $blob } +} {} +do_catchsql_test 3.2 { + SELECT rowid FROM t1 WHERE t1 MATCH 'world' +} {1 {database disk image is malformed}} + + +do_execsql_test 4.0 { + DROP TABLE t1; + CREATE VIRTUAL TABLE t1 USING fts3; + INSERT INTO t1(t1) VALUES('nodesize=24'); +} +do_test fts3corrupt-4.1 { + execsql BEGIN + foreach s { + "amxtvoo adqwroyhz auq aithtir avniqnuynvf axp ahibayfynig agbicpm" + "ajdtebs anteaxr aieynenwmd awpl alo akxcrwow aoxftge aoqvgul" + "amcfvdr auz apu aebelm ahuxyz aqc asyafdb agulvhvqu" + "apepwfyz azkhdvkw aenyelxzbk aslnitbyet aycdsdcpgr aqzzdbc agfi axnypydou" + "aaqrzzcm apcxdxo atumltzj aevvivo aodknoft aqoyytoz alobx apldt" + } { + execsql { INSERT INTO t1 VALUES($s) } + } + execsql COMMIT +} {} + +do_catchsql_test 4.2 { + UPDATE t1_segdir SET root = X'FFFFFFFFFFFFFFFF'; + SELECT rowid FROM t1 WHERE t1 MATCH 'world'; +} {1 {database disk image is malformed}} + +set blob [binary format cca*cca*cca*cca*cca*cca*cca*cca*cca*cca*a* \ + 22 120 [string repeat a 120] \ + 22 120 [string repeat b 120] \ + 22 120 [string repeat c 120] \ + 22 120 [string repeat d 120] \ + 22 120 [string repeat e 120] \ + 22 120 [string repeat f 120] \ + 22 120 [string repeat g 120] \ + 22 120 [string repeat h 120] \ + 22 120 [string repeat i 120] \ + 22 120 [string repeat j 120] \ + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" +] + +do_catchsql_test 4.3 { + UPDATE t1_segdir SET root = $blob; + SELECT rowid FROM t1 WHERE t1 MATCH 'world'; +} {1 {database disk image is malformed}} + +finish_test + diff --git a/test/fts3corrupt2.test b/test/fts3corrupt2.test new file mode 100644 index 00000000..78c76778 --- /dev/null +++ b/test/fts3corrupt2.test @@ -0,0 +1,110 @@ +# 2010 October 30 +# +# 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. +# +#*********************************************************************** +# Test that the FTS3 extension does not crash when it encounters a +# corrupt data structure on disk. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# If SQLITE_ENABLE_FTS3 is not defined, omit this file. +ifcapable !fts3 { finish_test ; return } + +set ::testprefix fts3corrupt2 + +set data [list] +lappend data {*}{ + "amxtvoo adqwroyhz auq aithtir avniqnuynvf axp ahibayfynig agbicpm" + "ajdtebs anteaxr aieynenwmd awpl alo akxcrwow aoxftge aoqvgul" + "amcfvdr auz apu aebelm ahuxyz aqc asyafdb agulvhvqu" + "apepwfyz azkhdvkw aenyelxzbk aslnitbyet aycdsdcpgr aqzzdbc agfi axnypydou" + "aaqrzzcm apcxdxo atumltzj aevvivo aodknoft aqoyytoz alobx apldt" + "adjllxlhnmj aiuhvuj adwppceuht atvj azrsam ahkjqdhny audlqxr aotgcd" + "aira azflsceos awj auzbobfkc awmezplr aeh awec ahndxlmv" + "aydwnied alk auoap agihyqeix aymqxzajnl aydwnied aojkarx agbo" + "ahajsmcl anvx amdhjm aoptsj agugzjjm apkevm acnj acjg" + "amwtkw aogttbykvt aubwrfqnbjf ajow agsj aerkqzjdqst anenlvbalkn arfajzzgckx" + "adqqqofkmz amjpavjuhw aqgehgnb awvvxlbtqzn agstqko akmkzehyh atagzey agwja" + "amag ahe autkllywhr avnk atmt akn anvdh aixfrv" + "aqdyerbws avefykly awl azaduojgzo anxfsmw axpt abgbvk ati" + "attyqkwz aiweypiczul afy asitaqbczhh aitxisizpv auhviq aibql ajfqc" + "aylzprtmta aiuemihqrpi awluvgsw ampbuy axlifpzfqr aems aoaxwads apianfn" + "aodrkijelq acdb aaserrdxm aqyasgofqu aevvivo afi apmwu aeoqysl" + "amqnk ankaotm ayfy ajcupeeoc advcbukan aucahlwnyk adbfyo azqjpeant" + "afczpp asqrs ahslvda akhlf aiqgdp atyd aznuglxqbrg awirndrh" + "aqhiajp amxeazb asxuehg akod axvolvsp agcz asmovmohy acmqa" + "avvomv aafms ashuaec arevx audtq alrwqhjvao avgsgpg ajbrctpsel" + "atxoirr ayopboobqdu ajunntua arh aernimxid aipljda aglo aefk" + "aonxf acmnnkna abgviaswe aulvcbv axp apemgakpzo aibql acioaid" + "axo alrwqhjvao ayqounftdzl azmoakdyh apajze ajk artvy apxiamy" + "ayjafsraz addjj agsj asejtziqws acatvhegu aoxdjqblsvv aekdmmbs aaobe" + "abjjvzubkwt alczv ati awz auyxgcxeb aymjoym anqoukprtyt atwfhpmbooh" + "ajfqz aethlgir aclcx aowlyvetby aproqm afjlqtkv anebfy akzrcpfrrvw" + "aoledfotm aiwlfm aeejlaej anz abgbvk aktfn aayoh anpywgdvgz" + "acvmldguld asdvz aqb aeomsyzyu aggylhprbdz asrfkwz auipybpsn agsnszzfb" +} + +do_test fts3corrupt2-1.0 { + execsql BEGIN + execsql { CREATE VIRTUAL TABLE t2 USING FTS3(a, b); } + execsql { INSERT INTO t2(t2) VALUES('nodesize=32') } + foreach d $data { + execsql { INSERT INTO t2 VALUES($d, $d) } + } + execsql COMMIT + execsql { SELECT count(*) FROM t2_segments } +} {163} + +proc set_byte {blob byte val} { + binary format a*ca* \ + [string range $blob 0 [expr $byte-1]] \ + $val \ + [string range $blob [expr $byte+1] end] \ +} + +set tn 0 +set c 256 +foreach {rowid sz blob} [ + db eval {SELECT rowid, length(block), block FROM t2_segments} +] { + incr tn + set c [expr (($c+255)%256)] + for {set i 0} {$i < $sz} {incr i} { + set b2 [set_byte $blob $i $c] + execsql { UPDATE t2_segments SET block = $b2 WHERE rowid = $rowid } + do_test fts3corrupt2-1.$tn.$i { + catchsql { SELECT * FROM t2 WHERE t2 MATCH 'a*' } + set {} {} + } {} + } + execsql { UPDATE t2_segments SET block = $blob WHERE rowid = $rowid } +} + +foreach c {50 100 150 200 250} { + foreach {rowid sz blob} [ + db eval {SELECT rowid, length(root), root FROM t2_segdir} + ] { + incr tn + for {set i 0} {$i < $sz} {incr i} { + set b2 [set_byte $blob $i $c] + execsql { UPDATE t2_segdir SET root = $b2 WHERE rowid = $rowid } + do_test fts3corrupt2-2.$c.$tn.$i { + catchsql { SELECT * FROM t2 WHERE t2 MATCH 'a*' } + set {} {} + } {} + } + execsql { UPDATE t2_segdir SET root = $blob WHERE rowid = $rowid } + } +} + + + + + + +finish_test diff --git a/test/fts3cov.test b/test/fts3cov.test index d3fe4fa8..c43999d2 100644 --- a/test/fts3cov.test +++ b/test/fts3cov.test @@ -6,9 +6,7 @@ # #*********************************************************************** # -# The tests in this file are structural coverage tests. They are designed -# to complement the tests in fts3rnd.test and fts3doc.test. Between them, -# the three files should provide full coverage of the fts3 extension code. +# The tests in this file are structural coverage tests for FTS3. # set testdir [file dirname $argv0] @@ -21,6 +19,7 @@ source $testdir/fts3_common.tcl source $testdir/malloc_common.tcl set DO_MALLOC_TEST 0 +set testprefix fts3cov #-------------------------------------------------------------------------- # When it first needs to read a block from the %_segments table, the FTS3 @@ -82,29 +81,30 @@ do_test fts3cov-2.1 { INSERT INTO t1 VALUES('And she in the midnight wood will pray'); INSERT INTO t1 VALUES('For the weal of her lover that''s far away.'); COMMIT; - + } + execsql { INSERT INTO t1(t1) VALUES('optimize'); SELECT substr(hex(root), 1, 2) FROM t1_segdir; } } {03} # Test the "missing entry" case: -do_test fts3cov-2.1 { +do_test fts3cov-2.2 { set root [db one {SELECT root FROM t1_segdir}] read_fts3varint [string range $root 1 end] left_child execsql { DELETE FROM t1_segments WHERE blockid = $left_child } } {} -do_error_test fts3cov-2.2 { +do_error_test fts3cov-2.3 { SELECT * FROM t1 WHERE t1 MATCH 'c*' -} {database disk image is malformed} +} {SQL logic error or missing database} # Test the "replaced with NULL" case: -do_test fts3cov-2.3 { +do_test fts3cov-2.4 { execsql { INSERT INTO t1_segments VALUES($left_child, NULL) } } {} -do_error_test fts3cov-2.4 { +do_error_test fts3cov-2.5 { SELECT * FROM t1 WHERE t1 MATCH 'cloud' -} {database disk image is malformed} +} {SQL logic error or missing database} #-------------------------------------------------------------------------- # The following tests are to test the effects of OOM errors while storing @@ -370,4 +370,63 @@ do_malloc_test fts3cov-13 -sqlprep { SELECT snippet(t13, '%%') FROM t13 WHERE t13 MATCH 'two'; } +do_execsql_test 14.0 { + CREATE VIRTUAL TABLE t14 USING fts4(a, b); + INSERT INTO t14 VALUES('one two three', 'one three four'); + INSERT INTO t14 VALUES('a b c', 'd e a'); +} +do_execsql_test 14.1 { + SELECT rowid FROM t14 WHERE t14 MATCH '"one two three"' +} {1} +do_execsql_test 14.2 { + SELECT rowid FROM t14 WHERE t14 MATCH '"one four"' +} {} +do_execsql_test 14.3 { + SELECT rowid FROM t14 WHERE t14 MATCH '"e a"' +} {2} +do_execsql_test 14.5 { + SELECT rowid FROM t14 WHERE t14 MATCH '"e b"' +} {} +do_catchsql_test 14.6 { + SELECT rowid FROM t14 WHERE rowid MATCH 'one' +} {1 {unable to use function MATCH in the requested context}} +do_catchsql_test 14.7 { + SELECT rowid FROM t14 WHERE docid MATCH 'one' +} {1 {unable to use function MATCH in the requested context}} + +do_execsql_test 15.0 { + CREATE VIRTUAL TABLE t15 USING fts4(a, b, c); + INSERT INTO t15 VALUES('abc def ghi', 'abc2 def2 ghi2', 'abc3 def3 ghi3'); + INSERT INTO t15 VALUES('abc2 def2 ghi2', 'abc2 def2 ghi2', 'abc def3 ghi3'); +} +do_execsql_test 15.1 { + SELECT rowid FROM t15 WHERE t15 MATCH '"abc* def2"' +} {1 2} + +# Test a corruption case. +# +do_execsql_test 16.1 { + CREATE VIRTUAL TABLE t16 USING fts4; + INSERT INTO t16 VALUES('theoretical work to examine the relationship'); + INSERT INTO t16 VALUES('solution of our problems on the invisible'); + DELETE FROM t16_content WHERE rowid = 2; +} +do_catchsql_test 16.2 { + SELECT * FROM t16 WHERE t16 MATCH 'invisible' +} {1 {database disk image is malformed}} + +# And another corruption test case. +# +do_execsql_test 17.1 { + CREATE VIRTUAL TABLE t17 USING fts4; + INSERT INTO t17(content) VALUES('one one one'); + UPDATE t17_segdir SET root = X'00036F6E65FFFFFFFFFFFFFFFFFFFFFF02030300' +} {} +do_catchsql_test 17.2 { + SELECT * FROM t17 WHERE t17 MATCH 'one' +} {1 {database disk image is malformed}} + + + + finish_test diff --git a/test/fts3defer.test b/test/fts3defer.test new file mode 100644 index 00000000..1c9056fd --- /dev/null +++ b/test/fts3defer.test @@ -0,0 +1,446 @@ +# 2010 October 15 +# +# 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. +# +#*********************************************************************** + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl + +ifcapable !fts3 { + finish_test + return +} + +set sqlite_fts3_enable_parentheses 1 + +set ::testprefix fts3defer + +#-------------------------------------------------------------------------- +# Test cases fts3defer-1.* are the "warm body" cases. The database contains +# one row with 15000 instances of the token "a". This makes the doclist for +# "a" so large that FTS3 will avoid loading it in most cases. +# +# To show this, test cases fts3defer-1.2.* execute a bunch of FTS3 queries +# involving token "a". Then, fts3defer-1.3.* replaces the doclist for token +# "a" with all zeroes and fts3defer-1.4.* repeats the tests from 1.2. If +# the tests still work, we can conclude that the doclist for "a" was not +# used. +# + +set aaa [string repeat "a " 15000] + +do_execsql_test 1.1 { + CREATE VIRTUAL TABLE t1 USING fts4; + BEGIN; + INSERT INTO t1 VALUES('this is a dog'); + INSERT INTO t1 VALUES('an instance of a phrase'); + INSERT INTO t1 VALUES('an instance of a longer phrase'); + INSERT INTO t1 VALUES($aaa); + COMMIT; +} {} + +set tests { + 1 {SELECT rowid FROM t1 WHERE t1 MATCH '"a dog"'} {1} + 2 {SELECT rowid FROM t1 WHERE t1 MATCH '"is a dog"'} {1} + 3 {SELECT rowid FROM t1 WHERE t1 MATCH '"a longer phrase"'} {3} + 4 {SELECT snippet(t1) FROM t1 WHERE t1 MATCH '"a longer phrase"'} + {"an instance of a longer phrase"} + 5 {SELECT rowid FROM t1 WHERE t1 MATCH 'a dog'} {1} +} + +do_select_tests 1.2 $tests + +do_execsql_test 1.3 { + SELECT count(*) FROM t1_segments WHERE length(block)>10000; + UPDATE t1_segments + SET block = zeroblob(length(block)) + WHERE length(block)>10000; +} {1} + +do_select_tests 1.4 $tests + +# Drop the table. It is corrupt now anyhow, so not useful for subsequent tests. +# +do_execsql_test 1.5 { DROP TABLE t1 } + +#-------------------------------------------------------------------------- +# These tests - fts3defer-2.* - are more rigorous. They test that for a +# variety of queries, FTS3 and FTS4 return the same results. And that +# zeroing the very large doclists that FTS4 does not load does not change +# the results. +# +# They use the following pseudo-randomly generated document data. The +# tokens "zm" and "jk" are especially common in this dataset. Additionally, +# two documents are added to the pseudo-random data before it is loaded +# into FTS4 containing 100,000 instances of the "zm" and "jk" tokens. This +# makes the doclists for those tokens so large that FTS4 avoids loading them +# into memory if possible. +# +set data [list] +lappend data [string repeat "zm " 100000] +lappend data [string repeat "jk " 100000] +lappend data {*}{ + "zm zm agmckuiu uhzq nsab jk rrkx duszemmzl hyq jk" + "jk uhzq zm zm rgpzmlnmd zm zk jk jk zm" + "duszemmzl zm jk xldlpy zm jk sbptoa xh jk xldlpy" + "zm xh zm xqf azavwm jk jk trqd rgpzmlnmd jk" + "zm vwq urvysbnykk ubwrfqnbjf zk lsz jk doiwavhwwo jk jk" + "jk xduvfhk orpfawpx zkhdvkw jk mjpavjuhw zm jk duszemmzl zm" + "jk igju jk jk zm hmjf xh zm gwdfhwurx zk" + "vgsld jk jk zm hrlipdm jn zm zsmhnf vgsld duszemmzl" + "gtuiexzsu aayxpmve zm zm zm drir scpgna xh azavwm uhzq" + "farlehdhq hkfoudzftq igju duszemmzl xnxhf ewle zm hrlipdm urvysbnykk kn" + "xnxhf jk jk agmckuiu duszemmzl jk zm zm jk vgsld" + "zm zm zm jk jk urvysbnykk ogttbykvt zm zm jk" + "iasrqgqv zm azavwm zidhxhbtv jk jk mjpavjuhw zm zm ajmvcydy" + "rgpzmlnmd tmt mjpavjuhw xh igju jk azavwm fibokdry vgsld ofm" + "zm jk vgsld jk xh jk csjqxhgj drir jk pmrb" + "xh jk jk zm rrkx duszemmzl mjpavjuhw xldlpy igju zm" + "jk hkfoudzftq zf rrkx wdmy jupk jk zm urvysbnykk npywgdvgz" + "zm jk zm zm zhbrzadb uenvbm aayxpmve urvysbnykk duszemmzl jk" + "uenvbm jk zm fxw xh bdilwmjw mjpavjuhw uv jk zm" + "nk jk bnhc pahlds jk igju dzadnqzprr jk jk jk" + "uhzq uv zm duszemmzl tlqix jk jk xh jk zm" + "jk zm agmckuiu urvysbnykk jk jk zm zm jk jk" + "azavwm mjpavjuhw lsgshn trqd xldlpy ogyavjvv agmckuiu ryvwwhlbc jk jk" + "tmt jk zk zm azavwm ofm acpgim bvgimjik iasrqgqv wuvajhwqz" + "igju ogyavjvv xrbdak rrkx fibokdry zf ujfhmrllq jk zm hxgwvib" + "zm pahlds jk uenvbm aayxpmve iaf hmjf xph vnlyvtkgx zm" + "jk xnxhf igju jk xh jk nvfasfh zm js jk" + "zm zm rwaj igju xr rrkx xnxhf nvfasfh skxbsqzvmt xatbxeqq" + "vgsld zm ujfhmrllq uhzq ogyavjvv nsab azavwm zm vgsld jmfiqhwnjg" + "ymjoym duszemmzl urvysbnykk azavwm jk jmfiqhwnjg bu qcdziqomqk vnlyvtkgx" + "zm nbilqcnz dzadnqzprr xh bkfgzsxn urvysbnykk xrujfzxqf zm zf agmckuiu" + "jk urvysbnykk nvfasfh zf xh zm zm qcdziqomqk qvxtclg wdmy" + "fibokdry jk urvysbnykk jk xr osff zm cvnnsl zm vgsld" + "jk mjpavjuhw hkfoudzftq jk zm xh xqf urvysbnykk jk iasrqgqv" + "jk csjqxhgj duszemmzl iasrqgqv aayxpmve zm brsuoqww jk qpmhtvl wluvgsw" + "jk mj azavwm jk zm jn dzadnqzprr zm jk uhzq" + "zk xqf jupk fxw nbilqcnz zm jk jcpiwj tznlvbfcv nvfasfh" + "jk jcpiwj zm xnxhf zm mjpavjuhw mj drir pa pvjrjlas" + "duszemmzl dzadnqzprr jk swc duszemmzl tmt jk jk pahlds jk" + "zk zm jk zm zm eczkjblu zm hi pmrb jk" + "azavwm zm iz agmckuiu jk sntk jk duszemmzl duszemmzl zm" + "jk zm jk eczkjblu urvysbnykk sk gnl jk ttvgf hmjf" + "jk bnhc jjrxpjkb mjpavjuhw fibokdry igju jk zm zm xh" + "wxe ogttbykvt uhzq xr iaf zf urvysbnykk aayxpmve oacaxgjoo mjpavjuhw" + "gazrt jk ephknonq myjp uenvbm wuvajhwqz jk zm xnxhf nvfasfh" + "zm aayxpmve csjqxhgj xnxhf xr jk aayxpmve xnxhf zm zm" + "sokcyf zm ogyavjvv jk zm fibokdry zm jk igju igju" + "vgsld bvgimjik xuprtlyle jk akmikrqyt jk aayxpmve hkfoudzftq ddjj ithtir" + "zm uhzq ovkyevlgv zk uenvbm csjqxhgj jk vgsld pgybs jk" + "zm agmckuiu zexh fibokdry jk uhzq bu tugflixoex xnxhf sk" + "zm zf uenvbm jk azavwm zm zm agmckuiu zm jk" + "rrkx jk zf jt zm oacaxgjoo fibokdry wdmy igju csjqxhgj" + "hi igju zm jk zidhxhbtv dzadnqzprr jk jk trqd duszemmzl" + "zm zm mjpavjuhw xrbdak qrvbjruc jk qzzqdxq guwq cvnnsl zm" + "ithtir jk jk qcdziqomqk zm farlehdhq zm zm xrbdak jk" + "ixfipk csjqxhgj azavwm sokcyf ttvgf vgsld jk sk xh zk" + "nvfasfh azavwm zm zm zm fxw nvfasfh zk gnl trqd" + "zm fibokdry csjqxhgj ofm dzadnqzprr jk akmikrqyt orpfawpx duszemmzl vwq" + "csjqxhgj jk jk vgsld urvysbnykk jk nxum jk jk nxum" + "zm hkfoudzftq jk ryvwwhlbc mjpavjuhw ephknonq jk zm ogyavjvv zm" + "lwa hi xnxhf qdyerbws zk njtc jk uhzq zm jk" + "trqd zm dzadnqzprr zm urvysbnykk jk lsz jk mjpavjuhw cmnnkna" + "duszemmzl zk jk jk fibokdry jseuhjnzo zm aayxpmve zk jk" + "fibokdry jk sviq qvxtclg wdmy jk doiwavhwwo zexh jk zm" + "jupk zm xh jk mjpavjuhw zm jk nsab npywgdvgz duszemmzl" + "zm igju zm zm nvfasfh eh hkfoudzftq fibokdry fxw xkblf" + "jk zm jk jk zm xh zk abthnzcv zf csjqxhgj" + "zm zm jk nkaotm urvysbnykk sbptoa bq jk ktxdty ubwrfqnbjf" + "nvfasfh aayxpmve xdcuz zm tugflixoex jcpiwj zm mjpavjuhw fibokdry doiwavhwwo" + "iaf jk mjpavjuhw zm duszemmzl jk jk uhzq pahlds fibokdry" + "ddjj zk azavwm jk swc zm gjtexkv jk xh jk" + "igju jk csjqxhgj zm jk dzadnqzprr duszemmzl ulvcbv jk jk" + "jk fibokdry zm csjqxhgj jn zm zm zm zf uhzq" + "duszemmzl jk xkblf zk hrlipdm aayxpmve uenvbm uhzq jk zf" + "dzadnqzprr jk zm zdu nvfasfh zm jk urvysbnykk hmjf jk" + "jk aayxpmve aserrdxm acpgim fibokdry jk drir wxe brsuoqww rrkx" + "uhzq csjqxhgj nvfasfh jk rrkx qbamok trqd uenvbm sntk zm" + "ps azavwm zkhdvkw jk zm jk jk zm csjqxhgj xedlrcfo" + "jk jk ogyavjvv jk zm farlehdhq duszemmzl jk agitgxamxe jk" + "qzzqdxq rwaj jk jk zm xqf jk uenvbm jk zk" + "zm hxgwvib akmikrqyt zf agmckuiu uenvbm bq npywgdvgz azavwm jk" + "zf jmfiqhwnjg js igju zm aayxpmve zm mbxnljomiv csjqxhgj nvfasfh" + "zm jk jk gazrt jk jk lkc jk nvfasfh jk" + "xldlpy orpfawpx zkhdvkw jk zm igju zm urvysbnykk dzadnqzprr mbxnljomiv" + "urvysbnykk jk zk igju zm uenvbm jk zm ithtir jk" + "zm zk zm zf ofm zm xdcuz dzadnqzprr zm vgsld" + "sbptoa jk tugflixoex jk zm zm vgsld zm xh zm" + "uhzq jk zk evvivo vgsld vniqnuynvf agmckuiu jk zm zm" + "zm nvfasfh zm zm zm abthnzcv uenvbm jk zk dzadnqzprr" + "zm azavwm igju qzzqdxq jk xnxhf abthnzcv jk nvfasfh zm" + "qbamok fxw vgsld igju cmnnkna xnxhf vniqnuynvf zk xh zm" + "nvfasfh zk zm mjpavjuhw dzadnqzprr jk jk duszemmzl xldlpy nvfasfh" + "xnxhf sviq nsab npywgdvgz osff vgsld farlehdhq fibokdry wjbkhzsa hhac" + "zm azavwm scpgna jk jk bq jk duszemmzl fibokdry ovkyevlgv" + "csjqxhgj zm jk jk duszemmzl zk xh zm jk zf" + "urvysbnykk dzadnqzprr csjqxhgj mjpavjuhw ubwrfqnbjf nkaotm jk jk zm drir" + "nvfasfh xh igju zm wluvgsw jk zm srwwnezqk ewle ovnq" + "jk nvfasfh eh ktxdty urvysbnykk vgsld zm jk eh uenvbm" + "orpfawpx pahlds jk uhzq hi zm zm zf jk dzadnqzprr" + "srwwnezqk csjqxhgj rbwzuf nvfasfh jcpiwj xldlpy nvfasfh jk vgsld wjybxmieki" +} + +proc add_empty_records {n} { + execsql BEGIN + for {set i 0} {$i < $n} {incr i} { + execsql { INSERT INTO t1 VALUES('') } + } + execsql COMMIT +} + + +#set e [list] +#foreach d $data {set e [concat $e $d]} +#puts [lsort -unique $e] +#exit + +set zero_long_doclists { + UPDATE t1_segments SET block=zeroblob(length(block)) WHERE length(block)>10000 +} + +foreach {tn setup} { + 1 { + set dmt_modes 0 + execsql { CREATE VIRTUAL TABLE t1 USING FTS3 } + foreach doc $data { execsql { INSERT INTO t1 VALUES($doc) } } + } + 2 { + set dmt_modes 0 + execsql { CREATE VIRTUAL TABLE t1 USING FTS4 } + foreach doc $data { execsql { INSERT INTO t1 VALUES($doc) } } + } + 3 { + set dmt_modes {0 1 2} + execsql { CREATE VIRTUAL TABLE t1 USING FTS4 } + foreach doc $data { execsql { INSERT INTO t1 VALUES($doc) } } + add_empty_records 1000 + execsql $zero_long_doclists + } + 4 { + set dmt_modes 0 + execsql { CREATE VIRTUAL TABLE t1 USING FTS4 } + foreach doc $data { execsql { INSERT INTO t1 VALUES($doc) } } + add_empty_records 1000 + execsql "INSERT INTO t1(t1) VALUES('optimize')" + execsql $zero_long_doclists + } + 5 { + set dmt_modes 0 + execsql { CREATE VIRTUAL TABLE t1 USING FTS4(matchinfo=fts3) } + foreach doc $data { execsql { INSERT INTO t1 VALUES($doc) } } + add_empty_records 1000 + execsql $zero_long_doclists + } +} { + + execsql { DROP TABLE IF EXISTS t1 } + eval $setup + set ::testprefix fts3defer-2.$tn + set DO_MALLOC_TEST 0 + + do_execsql_test 0 { + SELECT count(*) FROM t1_segments WHERE length(block)>10000 + } {2} + + do_select_test 1.1 { + SELECT rowid FROM t1 WHERE t1 MATCH 'jk xnxhf' + } {13 29 40 47 48 52 63 92} + do_select_test 1.2 { + SELECT rowid FROM t1 WHERE t1 MATCH 'jk eh' + } {100} +if {$tn==3} breakpoint + do_select_test 1.3 { + SELECT rowid FROM t1 WHERE t1 MATCH 'jk ubwrfqnbjf' + } {7 70 98} + do_select_test 1.4 { + SELECT rowid FROM t1 WHERE t1 MATCH 'duszemmzl jk' + } {3 5 8 10 13 18 20 23 32 37 41 43 55 60 65 67 72 74 76 81 94 96 97} + do_select_test 1.5 { + SELECT rowid FROM t1 WHERE t1 MATCH 'ubwrfqnbjf jk' + } {7 70 98} + do_select_test 1.6 { + SELECT rowid FROM t1 WHERE t1 MATCH 'jk ubwrfqnbjf jk jk jk jk' + } {7 70 98} + do_select_test 1.7 { + SELECT rowid FROM t1 WHERE t1 MATCH 'zm xnxhf' + } {12 13 29 30 40 47 48 52 63 92 93} + do_select_test 1.8 { + SELECT rowid FROM t1 WHERE t1 MATCH 'zm eh' + } {68 100} + do_select_test 1.9 { + SELECT rowid FROM t1 WHERE t1 MATCH 'zm ubwrfqnbjf' + } {7 70 98} + do_select_test 1.10 { + SELECT rowid FROM t1 WHERE t1 MATCH 'z* vgsld' + } {10 13 17 31 35 51 58 88 89 90 93 100} + do_select_test 1.11 { + SELECT rowid FROM t1 + WHERE t1 MATCH '( + zdu OR zexh OR zf OR zhbrzadb OR zidhxhbtv OR + zk OR zkhdvkw OR zm OR zsmhnf + ) vgsld' + } {10 13 17 31 35 51 58 88 89 90 93 100} + + do_select_test 2.1 { + SELECT rowid FROM t1 WHERE t1 MATCH '"zm agmckuiu"' + } {3 24 52 53} + do_select_test 2.2 { + SELECT rowid FROM t1 WHERE t1 MATCH '"zm zf"' + } {33 53 75 88 101} + do_select_test 2.3 { + SELECT rowid FROM t1 WHERE t1 MATCH '"zm aayxpmve"' + } {48 65 84} + do_select_test 2.4 { + SELECT rowid FROM t1 WHERE t1 MATCH '"aayxpmve zm"' + } {11 37 84} + do_select_test 2.5 { + SELECT rowid FROM t1 WHERE t1 MATCH '"jk azavwm"' + } {16 53} + do_select_test 2.6 { + SELECT rowid FROM t1 WHERE t1 MATCH '"xh jk jk"' + } {18} + do_select_test 2.7 { + SELECT rowid FROM t1 WHERE t1 MATCH '"zm jk vgsld"' + } {13 17} + do_select_test 2.8 { + SELECT rowid FROM t1 WHERE t1 MATCH '"zm jk vgsld lkjlkjlkj"' + } {} + + do_select_test 3.1 { + SELECT snippet(t1, '[', ']') FROM t1 WHERE t1 MATCH '"zm agmckuiu"' + } { + {zm [zm] [agmckuiu] uhzq nsab jk rrkx duszemmzl hyq jk} + {jk [zm] [agmckuiu] urvysbnykk jk jk zm zm jk jk} + {[zm] [agmckuiu] zexh fibokdry jk uhzq bu tugflixoex xnxhf sk} + {zm zf uenvbm jk azavwm zm [zm] [agmckuiu] zm jk} + } + + do_select_test 3.2 { + SELECT snippet(t1, '[', ']') FROM t1 WHERE t1 MATCH 'xnxhf jk' + } { + {[xnxhf] [jk] [jk] agmckuiu duszemmzl [jk] zm zm [jk] vgsld} + {[jk] [xnxhf] igju [jk] xh [jk] nvfasfh zm js [jk]} + {[jk] jcpiwj zm [xnxhf] zm mjpavjuhw mj drir pa pvjrjlas} + {gazrt [jk] ephknonq myjp uenvbm wuvajhwqz [jk] zm [xnxhf] nvfasfh} + {zm aayxpmve csjqxhgj [xnxhf] xr [jk] aayxpmve [xnxhf] zm zm} + {zm agmckuiu zexh fibokdry [jk] uhzq bu tugflixoex [xnxhf] sk} + {lwa hi [xnxhf] qdyerbws zk njtc [jk] uhzq zm [jk]} + {zm azavwm igju qzzqdxq [jk] [xnxhf] abthnzcv [jk] nvfasfh zm} + } + + do_select_test 4.1 { + SELECT offsets(t1) FROM t1 WHERE t1 MATCH '"jk uenvbm"' + } { + {0 0 10 2 0 1 13 6} {0 0 26 2 0 1 29 6} + } + + do_select_test 4.2 { + SELECT offsets(t1) FROM t1 WHERE t1 MATCH 'duszemmzl jk fibokdry' + } { + {0 2 3 8 0 1 36 2 0 0 58 9} + {0 0 0 9 0 1 13 2 0 1 16 2 0 2 19 8 0 1 53 2} + {0 1 4 2 0 0 20 9 0 1 30 2 0 1 33 2 0 2 48 8} + {0 1 17 2 0 1 20 2 0 1 26 2 0 0 29 9 0 2 39 8} + } + + do_select_test 4.3 { + SELECT offsets(t1) FROM t1 + WHERE t1 MATCH 'vgsld (hrlipdm OR (aapmve NEAR duszemmzl))' + } {{0 0 0 5 0 1 15 7 0 0 36 5}} + + # The following block of tests runs normally with FTS3 or FTS4 without the + # long doclists zeroed. And with OOM-injection for FTS4 with long doclists + # zeroed. Change this by messing with the [set dmt_modes] commands above. + # + foreach DO_MALLOC_TEST $dmt_modes { + + # Phrase search. + do_select_test 5.$DO_MALLOC_TEST.1 { + SELECT rowid FROM t1 WHERE t1 MATCH '"jk mjpavjuhw"' + } {8 15 36 64 67 72} + + # Multiple tokens search. + do_select_test 5.$DO_MALLOC_TEST.2 { + SELECT rowid FROM t1 WHERE t1 MATCH 'duszemmzl zm' + } {3 5 8 10 12 13 18 20 23 37 43 55 60 65 67 72 74 81 94 96 97} + + # snippet() function with phrase. + do_select_test 5.$DO_MALLOC_TEST.3 { + SELECT snippet(t1, '[', ']') FROM t1 WHERE t1 MATCH '"zm aayxpmve"' + } { + {[zm] [aayxpmve] csjqxhgj xnxhf xr jk aayxpmve xnxhf zm zm} + {duszemmzl zk jk jk fibokdry jseuhjnzo [zm] [aayxpmve] zk jk} + {zf jmfiqhwnjg js igju [zm] [aayxpmve] zm mbxnljomiv csjqxhgj nvfasfh} + } + + # snippet() function with multiple tokens. + do_select_test 5.$DO_MALLOC_TEST.4 { + SELECT snippet(t1, '[', ']') FROM t1 WHERE t1 MATCH 'zm zhbrzadb' + } { + {[zm] jk [zm] [zm] [zhbrzadb] uenvbm aayxpmve urvysbnykk duszemmzl jk} + } + + # snippet() function with phrase. + do_select_test 5.$DO_MALLOC_TEST.5 { + SELECT offsets(t1) FROM t1 WHERE t1 MATCH '"zm aayxpmve"' + } { + {0 0 0 2 0 1 3 8} {0 0 38 2 0 1 41 8} {0 0 22 2 0 1 25 8} + } + + # snippet() function with multiple tokens. + do_select_test 5.$DO_MALLOC_TEST.6 { + SELECT offsets(t1) FROM t1 WHERE t1 MATCH 'zm zhbrzadb' + } { + {0 0 0 2 0 0 6 2 0 0 9 2 0 1 12 8} + } + + set DO_MALLOC_TEST 0 + } + + do_select_test 6.1 { + SELECT rowid FROM t1 + WHERE t1 MATCH 'vgsld (hrlipdm OR (aayxpmve duszemmzl))' + } {10} + do_select_test 6.2.1 { + SELECT rowid FROM t1 WHERE t1 MATCH '"jk xduvfhk"' + } {8} + do_select_test 6.2.2 { + SELECT rowid FROM t1 WHERE t1 MATCH '"zm azavwm"' + } {15 26 92 96} + do_select_test 6.2.3 { + SELECT rowid FROM t1 WHERE t1 MATCH '"jk xduvfhk" OR "zm azavwm"' + } {8 15 26 92 96} +} + +set testprefix fts3defer + +do_execsql_test 3.1 { + CREATE VIRTUAL TABLE x1 USING fts4(a, b); + INSERT INTO x1 VALUES('a b c', 'd e f'); + INSERT INTO x1 SELECT * FROM x1; + INSERT INTO x1 SELECT * FROM x1; + INSERT INTO x1 SELECT * FROM x1; + INSERT INTO x1 SELECT * FROM x1; +} +do_execsql_test 3.2 " + INSERT INTO x1 VALUES( + '[string repeat {d } 3000]', '[string repeat {f } 30000]' + ); + INSERT INTO x1(x1) VALUES('optimize'); +" + +do_execsql_test 3.3 { + SELECT count(*) FROM x1 WHERE x1 MATCH '"d e f"' +} {16} + + +finish_test diff --git a/test/fts3defer2.test b/test/fts3defer2.test new file mode 100644 index 00000000..142c92a9 --- /dev/null +++ b/test/fts3defer2.test @@ -0,0 +1,134 @@ +# 2010 October 23 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +ifcapable !fts3 { finish_test ; return } + +set testprefix fts3defer2 + +proc mit {blob} { + set scan(littleEndian) i* + set scan(bigEndian) I* + binary scan $blob $scan($::tcl_platform(byteOrder)) r + return $r +} +db func mit mit + +#----------------------------------------------------------------------------- +# This block of tests - fts3defer2-1.* - test the interaction of deferred NEAR +# expressions and the snippet, offsets and matchinfo functions. +# +do_execsql_test 1.1.1 { + CREATE VIRTUAL TABLE t1 USING fts4; +} +do_execsql_test 1.1.2 "INSERT INTO t1 VALUES('[string repeat {a } 20000]')" +do_execsql_test 1.1.3 "INSERT INTO t1 VALUES('[string repeat {z } 20000]')" +do_execsql_test 1.1.4 { + INSERT INTO t1 VALUES('a b c d e f a x y'); + INSERT INTO t1 VALUES(''); + INSERT INTO t1 VALUES(''); + INSERT INTO t1 VALUES(''); + INSERT INTO t1 VALUES(''); + INSERT INTO t1 VALUES(''); + INSERT INTO t1(t1) VALUES('optimize'); +} +do_execsql_test 1.1.4 { + SELECT count(*) FROM t1_segments WHERE length(block)>10000; + UPDATE t1_segments SET block = zeroblob(length(block)) WHERE length(block)>10000; +} {2} + +do_execsql_test 1.2.1 { + SELECT content FROM t1 WHERE t1 MATCH 'f (e NEAR/2 a)'; +} {{a b c d e f a x y}} + +do_execsql_test 1.2.2 { + SELECT snippet(t1, '[', ']'), offsets(t1), mit(matchinfo(t1, 'pcxnal')) + FROM t1 WHERE t1 MATCH 'f (e NEAR/2 a)'; +} [list \ + {a b c d [e] [f] [a] x y} \ + {0 1 8 1 0 0 10 1 0 2 12 1} \ + [list 3 1 1 1 1 1 8 8 1 8 8 8 5001 9] +] + +do_execsql_test 1.2.3 { + SELECT snippet(t1, '[', ']'), offsets(t1), mit(matchinfo(t1, 'pcxnal')) + FROM t1 WHERE t1 MATCH 'f (e NEAR/3 a)'; +} [list \ + {[a] b c d [e] [f] [a] x y} \ + {0 2 0 1 0 1 8 1 0 0 10 1 0 2 12 1} \ + [list 3 1 1 1 1 1 8 8 2 8 8 8 5001 9] +] + +do_execsql_test 1.3.1 { DROP TABLE t1 } + +#----------------------------------------------------------------------------- +# Test cases fts3defer2-2.* focus specifically on the matchinfo function. +# +do_execsql_test 2.1.1 "CREATE VIRTUAL TABLE t2 USING fts4" +do_execsql_test 2.1.2 "INSERT INTO t2 VALUES('[string repeat {a } 10000]')" +do_execsql_test 2.1.3 "INSERT INTO t2 VALUES('b [string repeat {z } 10000]')" +do_execsql_test 2.1.4 [string repeat "INSERT INTO t2 VALUES('x');" 50] +do_execsql_test 2.1.5 { + INSERT INTO t2 VALUES('a b c d e f g'); + INSERT INTO t2 VALUES('a b c d e f g'); +} +foreach {tn sql} { + 1 {} + 2 { INSERT INTO t2(t2) VALUES('optimize') } + 3 { UPDATE t2_segments SET block = zeroblob(length(block)) + WHERE length(block)>10000; + } +} { + execsql $sql + + do_execsql_test 2.2.$tn { + SELECT mit(matchinfo(t2, 'pcxnal')) FROM t2 WHERE t2 MATCH 'a b'; + } [list \ + [list 2 1 1 54 54 1 3 3 54 372 7] \ + [list 2 1 1 54 54 1 3 3 54 372 7] \ + ] +} + +do_execsql_test 2.3.1 { + CREATE VIRTUAL TABLE t3 USING fts4; + INSERT INTO t3 VALUES('a b c d e f'); + INSERT INTO t3 VALUES('x b c d e f'); + INSERT INTO t3 VALUES('d e f a b c'); + INSERT INTO t3 VALUES('b c d e f'); + INSERT INTO t3 VALUES(''); + INSERT INTO t3 VALUES(''); + INSERT INTO t3 VALUES(''); + INSERT INTO t3 VALUES(''); + INSERT INTO t3 VALUES(''); + INSERT INTO t3 VALUES(''); +} +do_execsql_test 2.3.2 " + INSERT INTO t3 VALUES('f e d c b [string repeat {a } 10000]') +" +foreach {tn sql} { + 1 {} + 2 { INSERT INTO t3(t3) VALUES('optimize') } + 3 { UPDATE t3_segments SET block = zeroblob(length(block)) + WHERE length(block)>10000; + } +} { + execsql $sql + do_execsql_test 2.4.$tn { + SELECT docid, mit(matchinfo(t3, 'pcxnal')) FROM t3 WHERE t3 MATCH '"a b c"'; + } {1 {1 1 1 4 4 11 912 6} 3 {1 1 1 4 4 11 912 6}} +} + + +finish_test + diff --git a/test/fts3fault.test b/test/fts3fault.test new file mode 100644 index 00000000..2dd7bbbc --- /dev/null +++ b/test/fts3fault.test @@ -0,0 +1,233 @@ +# 2010 June 15 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +set ::testprefix fts3fault + +# If SQLITE_ENABLE_FTS3 is not defined, omit this file. +ifcapable !fts3 { finish_test ; return } + +if 1 { + +# Test error handling in the sqlite3Fts3Init() function. This is the +# function that registers the FTS3 module and various support functions +# with SQLite. +# +do_faultsim_test 1 -body { + sqlite3 db test.db + expr 0 +} -test { + catch { db close } +} + +# Test error handling in an "ALTER TABLE ... RENAME TO" statement on an +# FTS3 table. Specifically, test renaming the table within a transaction +# after it has been written to. +# +faultsim_delete_and_reopen +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t1 USING fts3; + INSERT INTO t1 VALUES('test renaming the table'); + INSERT INTO t1 VALUES(' after it has been written'); +} +do_faultsim_test 2 -prep { + sqlite3 db test.db + execsql { + BEGIN; + INSERT INTO t1 VALUES('registers the FTS3 module'); + INSERT INTO t1 VALUES('various support functions'); + } +} -body { + execsql { ALTER TABLE t1 RENAME TO t2 } +} -test { + faultsim_test_result {0 {}} +} + +# Test error handling in the special case where a single prefix query +# matches terms that reside on a large range of leaf nodes. +# +do_test fts3fault-3.0 { + sqlite3 db test.db + execsql { CREATE VIRTUAL TABLE t3 USING fts4; } + execsql { INSERT INTO t3(t3) VALUES('nodesize=50') } + execsql { BEGIN } + for {set i 0} {$i < 1000} {incr i} { + execsql { INSERT INTO t3 VALUES('aaa' || $i) } + } + execsql { COMMIT } +} {} + +do_faultsim_test 3 -faults oom-transient -prep { + sqlite3 db test.db + execsql { SELECT * FROM t3 WHERE t3 MATCH 'x' } +} -body { + execsql { SELECT count(rowid) FROM t3 WHERE t3 MATCH 'aa*' } +} -test { + faultsim_test_result {0 1000} +} + +do_test fts3fault-4.0 { + faultsim_delete_and_reopen + execsql { + CREATE VIRTUAL TABLE t4 USING fts4; + INSERT INTO t4 VALUES('The British Government called on'); + INSERT INTO t4 VALUES('as pesetas then became much'); + } +} {} +faultsim_save_and_close +do_faultsim_test 4 -prep { + faultsim_restore_and_reopen + execsql { SELECT content FROM t4 } +} -body { + execsql { SELECT optimize(t4) FROM t4 LIMIT 1 } +} -test { + faultsim_test_result {0 {{Index optimized}}} +} + +do_test fts3fault-5.0 { + faultsim_delete_and_reopen + execsql { + CREATE VIRTUAL TABLE t5 USING fts4; + INSERT INTO t5 VALUES('The British Government called on'); + INSERT INTO t5 VALUES('as pesetas then became much'); + } +} {} +faultsim_save_and_close +do_faultsim_test 5 -prep { + faultsim_restore_and_reopen + execsql { + BEGIN; + INSERT INTO t5 VALUES('influential in shaping his future outlook'); + INSERT INTO t5 VALUES('might be acceptable to the British electorate'); + } +} -body { + execsql { SELECT rowid FROM t5 WHERE t5 MATCH 'british' } +} -test { + faultsim_test_result {0 {1 4}} +} + +do_test fts3fault-6.0 { + faultsim_delete_and_reopen + execsql { CREATE VIRTUAL TABLE t6 USING fts4 } +} {} +faultsim_save_and_close +do_faultsim_test 6 -prep { + faultsim_restore_and_reopen + execsql { SELECT rowid FROM t6 } +} -body { + execsql { DROP TABLE t6 } +} -test { + faultsim_test_result {0 {}} +} + +# Test various malloc failures while processing FTS4 parameters. +# +do_faultsim_test 7.1 -prep { + faultsim_delete_and_reopen +} -body { + execsql { CREATE VIRTUAL TABLE t1 USING fts4(a, b, matchinfo=fts3) } +} -test { + faultsim_test_result {0 {}} +} +do_faultsim_test 7.2 -prep { + faultsim_delete_and_reopen +} -body { + execsql { CREATE VIRTUAL TABLE t1 USING fts4(a, b, matchinfo=fs3) } +} -test { + faultsim_test_result {1 {unrecognized matchinfo: fs3}} \ + {1 {vtable constructor failed: t1}} +} +do_faultsim_test 7.3 -prep { + faultsim_delete_and_reopen +} -body { + execsql { CREATE VIRTUAL TABLE t1 USING fts4(a, b, matchnfo=fts3) } +} -test { + faultsim_test_result {1 {unrecognized parameter: matchnfo=fts3}} \ + {1 {vtable constructor failed: t1}} +} + +} + +proc mit {blob} { + set scan(littleEndian) i* + set scan(bigEndian) I* + binary scan $blob $scan($::tcl_platform(byteOrder)) r + return $r +} + +do_test 8.0 { + faultsim_delete_and_reopen + execsql { CREATE VIRTUAL TABLE t8 USING fts4 } + execsql "INSERT INTO t8 VALUES('a b c')" + execsql "INSERT INTO t8 VALUES('b b b')" + execsql "INSERT INTO t8 VALUES('[string repeat {c } 50000]')" + execsql "INSERT INTO t8 VALUES('d d d')" + execsql "INSERT INTO t8 VALUES('e e e')" + execsql "INSERT INTO t8(t8) VALUES('optimize')" + faultsim_save_and_close +} {} + +do_faultsim_test 8.1 -prep { + faultsim_restore_and_reopen + db func mit mit +} -body { + execsql { SELECT mit(matchinfo(t8, 'x')) FROM t8 WHERE t8 MATCH 'a b c' } +} -test { + faultsim_test_result {0 {{1 1 1 1 4 2 1 5 5}}} +} +do_faultsim_test 8.2 -faults oom-t* -prep { + faultsim_restore_and_reopen + db func mit mit +} -body { + execsql { SELECT mit(matchinfo(t8, 's')) FROM t8 WHERE t8 MATCH 'a b c' } +} -test { + faultsim_test_result {0 3} +} +do_faultsim_test 8.3 -prep { + faultsim_restore_and_reopen + db func mit mit +} -body { + execsql { SELECT mit(matchinfo(t8, 'a')) FROM t8 WHERE t8 MATCH 'a b c' } +} -test { + faultsim_test_result {0 10002} +} +do_faultsim_test 8.4 -prep { + faultsim_restore_and_reopen + db func mit mit +} -body { + execsql { SELECT mit(matchinfo(t8, 'l')) FROM t8 WHERE t8 MATCH 'a b c' } +} -test { + faultsim_test_result {0 3} +} + +do_test 9.0 { + faultsim_delete_and_reopen + execsql { + CREATE VIRTUAL TABLE t9 USING fts4(tokenize=porter); + INSERT INTO t9 VALUES( + 'this record is used toooooooooooooooooooooooooooooooooooooo try to' + ); + SELECT offsets(t9) FROM t9 WHERE t9 MATCH 'to*'; + } + faultsim_save_and_close +} {} +do_faultsim_test 9.1 -prep { + faultsim_restore_and_reopen +} -body { + execsql { SELECT offsets(t9) FROM t9 WHERE t9 MATCH 'to*' } +} -test { + faultsim_test_result {0 {{0 0 20 39 0 0 64 2}}} +} + +finish_test diff --git a/test/fts3malloc.test b/test/fts3malloc.test index 5e793fbd..932ea098 100644 --- a/test/fts3malloc.test +++ b/test/fts3malloc.test @@ -44,7 +44,6 @@ proc normal_list {l} { set ret } - do_write_test fts3_malloc-1.1 sqlite_master { CREATE VIRTUAL TABLE ft1 USING fts3(a, b) } diff --git a/test/fts3matchinfo.test b/test/fts3matchinfo.test new file mode 100644 index 00000000..8f194e72 --- /dev/null +++ b/test/fts3matchinfo.test @@ -0,0 +1,384 @@ +# 2010 November 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 implements regression tests for the FTS3 module. The focus +# of this file is tables created with the "matchinfo=fts3" option. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# If SQLITE_ENABLE_FTS3 is not defined, omit this file. +ifcapable !fts3 { finish_test ; return } + +set testprefix fts3matchinfo + +proc mit {blob} { + set scan(littleEndian) i* + set scan(bigEndian) I* + binary scan $blob $scan($::tcl_platform(byteOrder)) r + return $r +} +db func mit mit + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts4(matchinfo=fts3); + SELECT name FROM sqlite_master WHERE type = 'table'; +} {t1 t1_content t1_segments t1_segdir t1_stat} + +do_execsql_test 1.1 { + INSERT INTO t1(content) VALUES('I wandered lonely as a cloud'); + INSERT INTO t1(content) VALUES('That floats on high o''er vales and hills,'); + INSERT INTO t1(content) VALUES('When all at once I saw a crowd,'); + INSERT INTO t1(content) VALUES('A host, of golden daffodils,'); + SELECT mit(matchinfo(t1)) FROM t1 WHERE t1 MATCH 'I'; +} {{1 1 1 2 2} {1 1 1 2 2}} + +# Now create an FTS4 table that does not specify matchinfo=fts3. +# +do_execsql_test 1.2 { + CREATE VIRTUAL TABLE t2 USING fts4; + INSERT INTO t2 SELECT * FROM t1; + SELECT mit(matchinfo(t2)) FROM t2 WHERE t2 MATCH 'I'; +} {{1 1 1 2 2} {1 1 1 2 2}} + +# Test some syntax-error handling. +# +do_catchsql_test 2.0 { + CREATE VIRTUAL TABLE x1 USING fts4(matchinfo=fs3); +} {1 {unrecognized matchinfo: fs3}} +do_catchsql_test 2.1 { + CREATE VIRTUAL TABLE x2 USING fts4(mtchinfo=fts3); +} {1 {unrecognized parameter: mtchinfo=fts3}} + +# Check that with fts3, the "=" character is permitted in column definitions. +# +do_execsql_test 3.1 { + CREATE VIRTUAL TABLE t3 USING fts3(mtchinfo=fts3); + INSERT INTO t3(mtchinfo) VALUES('Beside the lake, beneath the trees'); + SELECT mtchinfo FROM t3; +} {{Beside the lake, beneath the trees}} + +do_execsql_test 3.2 { + CREATE VIRTUAL TABLE xx USING FTS4; + SELECT * FROM xx WHERE xx MATCH 'abc'; + SELECT * FROM xx WHERE xx MATCH 'a b c'; +} + + +#-------------------------------------------------------------------------- +# Proc [do_matchinfo_test] is used to test the FTSX matchinfo() function. +# +# The first argument - $tn - is a test identifier. This may be either a +# full identifier (i.e. "fts3matchinfo-1.1") or, if global var $testprefix +# is set, just the numeric component (i.e. "1.1"). +# +# The second argument is the name of an FTSX table. The third is the +# full text of a WHERE/MATCH expression to query the table for +# (i.e. "t1 MATCH 'abc'"). The final argument - $results - should be a +# key-value list (serialized array) with matchinfo() format specifiers +# as keys, and the results of executing the statement: +# +# SELECT matchinfo($tbl, '$key') FROM $tbl WHERE $expr +# +# For example: +# +# CREATE VIRTUAL TABLE t1 USING fts4; +# INSERT INTO t1 VALUES('abc'); +# INSERT INTO t1 VALUES('def'); +# INSERT INTO t1 VALUES('abc abc'); +# +# do_matchinfo_test 1.1 t1 "t1 MATCH 'abc'" { +# n {3 3} +# p {1 1} +# c {1 1} +# x {{1 3 2} {2 3 2}} +# } +# +# If the $results list contains keys mapped to "-" instead of a matchinfo() +# result, then this command computes the expected results based on other +# mappings to test the matchinfo() function. For example, the command above +# could be changed to: +# +# do_matchinfo_test 1.1 t1 "t1 MATCH 'abc'" { +# n {3 3} p {1 1} c {1 1} x {{1 3 2} {2 3 2}} +# pcx - +# } +# +# And this command would compute the expected results for matchinfo(t1, 'pcx') +# based on the results of matchinfo(t1, 'p'), matchinfo(t1, 'c') and +# matchinfo(t1, 'x') in order to test 'pcx'. +# +proc do_matchinfo_test {tn tbl expr results} { + + foreach {fmt res} $results { + if {$res == "-"} continue + set resarray($fmt) $res + } + + set nRow 0 + foreach {fmt res} [array get resarray] { + if {[llength $res]>$nRow} { set nRow [llength $res] } + } + + # Construct expected results for any formats for which the caller + # supplied result is "-". + # + foreach {fmt res} $results { + if {$res == "-"} { + set res [list] + for {set iRow 0} {$iRow<$nRow} {incr iRow} { + set rowres [list] + foreach c [split $fmt ""] { + set rowres [concat $rowres [lindex $resarray($c) $iRow]] + } + lappend res $rowres + } + set resarray($fmt) $res + } + } + + # Test each matchinfo() request individually. + # + foreach {fmt res} [array get resarray] { + set sql "SELECT mit(matchinfo($tbl, '$fmt')) FROM $tbl WHERE $expr" + do_execsql_test $tn.$fmt $sql [normalize2 $res] + } + + # Test them all executed together (multiple invocations of matchinfo()). + # + set exprlist [list] + foreach {format res} [array get resarray] { + lappend exprlist "mit(matchinfo($tbl, '$format'))" + } + set allres [list] + for {set iRow 0} {$iRow<$nRow} {incr iRow} { + foreach {format res} [array get resarray] { + lappend allres [lindex $res $iRow] + } + } + set sql "SELECT [join $exprlist ,] FROM $tbl WHERE $expr" + do_execsql_test $tn.multi $sql [normalize2 $allres] +} +proc normalize2 {list_of_lists} { + set res [list] + foreach elem $list_of_lists { + lappend res [list {*}$elem] + } + return $res +} + + +do_execsql_test 4.1.0 { + CREATE VIRTUAL TABLE t4 USING fts4(x, y); + INSERT INTO t4 VALUES('a b c d e', 'f g h i j'); + INSERT INTO t4 VALUES('f g h i j', 'a b c d e'); +} + +do_matchinfo_test 4.1.1 t4 {t4 MATCH 'a b c'} { + p {3 3} + c {2 2} + x { + {1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 0 1 1} + {0 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1} + } + n {2 2} + l {{5 5} {5 5}} + a {{5 5} {5 5}} + + s {{3 0} {0 3}} + + xxxxxxxxxxxxxxxxxx - pcx - xpc - ccc - pppxpcpcx - laxnpc - + xpxsscplax - +} + +do_matchinfo_test 4.1.2 t4 {t4 MATCH '"g h i"'} { + p {1 1} + c {2 2} + x { + {0 1 1 1 1 1} + {1 1 1 0 1 1} + } + n {2 2} + l {{5 5} {5 5}} + a {{5 5} {5 5}} + + s {{0 1} {1 0}} + + xxxxxxxxxxxxxxxxxx - pcx - xpc - ccc - pppxpcpcx - laxnpc - + sxsxs - +} + +do_matchinfo_test 4.1.3 t4 {t4 MATCH 'a b'} { s {{2 0} {0 2}} } +do_matchinfo_test 4.1.4 t4 {t4 MATCH '"a b" c'} { s {{2 0} {0 2}} } +do_matchinfo_test 4.1.5 t4 {t4 MATCH 'a "b c"'} { s {{2 0} {0 2}} } +do_matchinfo_test 4.1.6 t4 {t4 MATCH 'd d'} { s {{1 0} {0 1}} } + +do_execsql_test 4.2.0 { + CREATE VIRTUAL TABLE t5 USING fts4; + INSERT INTO t5 VALUES('a a a a a'); + INSERT INTO t5 VALUES('a b a b a'); + INSERT INTO t5 VALUES('c b c b c'); + INSERT INTO t5 VALUES('x x x x x'); +} +do_matchinfo_test 4.2.1 t5 {t5 MATCH 'a a'} { + x {{5 8 2 5 8 2} {3 8 2 3 8 2}} + s {2 1} +} +do_matchinfo_test 4.2.2 t5 {t5 MATCH 'a b'} { s {2} } +do_matchinfo_test 4.2.3 t5 {t5 MATCH 'a b a'} { s {3} } +do_matchinfo_test 4.2.4 t5 {t5 MATCH 'a a a'} { s {3 1} } +do_matchinfo_test 4.2.5 t5 {t5 MATCH '"a b" "a b"'} { s {2} } +do_matchinfo_test 4.2.6 t5 {t5 MATCH 'a OR b'} { s {1 2 1} } + +do_execsql_test 4.3.0 "INSERT INTO t5 VALUES('x y [string repeat {b } 50000]')"; + +do_matchinfo_test 4.3.1 t5 {t5 MATCH 'a a'} { + x {{5 8 2 5 5 5} {3 8 2 3 5 5}} + s {2 1} +} + +do_matchinfo_test 4.3.2 t5 {t5 MATCH 'a b'} { s {2} } +do_matchinfo_test 4.3.3 t5 {t5 MATCH 'a b a'} { s {3} } +do_matchinfo_test 4.3.4 t5 {t5 MATCH 'a a a'} { s {3 1} } +do_matchinfo_test 4.3.5 t5 {t5 MATCH '"a b" "a b"'} { s {2} } +do_matchinfo_test 4.3.6 t5 {t5 MATCH 'a OR b'} { s {1 2 1 1} } + +do_execsql_test 4.4.0 { + INSERT INTO t5(t5) VALUES('optimize'); + UPDATE t5_segments + SET block = zeroblob(length(block)) + WHERE length(block)>10000; +} + +do_matchinfo_test 4.4.2 t5 {t5 MATCH 'a b'} { s {2} } +do_matchinfo_test 4.4.1 t5 {t5 MATCH 'a a'} { s {2 1} } +do_matchinfo_test 4.4.2 t5 {t5 MATCH 'a b'} { s {2} } +do_matchinfo_test 4.4.3 t5 {t5 MATCH 'a b a'} { s {3} } +do_matchinfo_test 4.4.4 t5 {t5 MATCH 'a a a'} { s {3 1} } +do_matchinfo_test 4.4.5 t5 {t5 MATCH '"a b" "a b"'} { s {2} } + +do_execsql_test 4.5.0 { + CREATE VIRTUAL TABLE t6 USING fts4(a, b, c); + INSERT INTO t6 VALUES('a', 'b', 'c'); +} +do_matchinfo_test 4.5.1 t6 {t6 MATCH 'a b c'} { s {{1 1 1}} } + + +#------------------------------------------------------------------------- +# Check the following restrictions: +# +# + Matchinfo flags 'a', 'l' and 'n' can only be used with fts4, not fts3. +# + Matchinfo flag 'l' cannot be used with matchinfo=fts3. +# +do_execsql_test 5.1 { + CREATE VIRTUAL TABLE t7 USING fts3(a, b); + INSERT INTO t7 VALUES('u v w', 'x y z'); + + CREATE VIRTUAL TABLE t8 USING fts4(a, b, matchinfo=fts3); + INSERT INTO t8 VALUES('u v w', 'x y z'); +} + +do_catchsql_test 5.2.1 { + SELECT matchinfo(t7, 'a') FROM t7 WHERE t7 MATCH 'x y' +} {1 {unrecognized matchinfo request: a}} +do_catchsql_test 5.2.2 { + SELECT matchinfo(t7, 'l') FROM t7 WHERE t7 MATCH 'x y' +} {1 {unrecognized matchinfo request: l}} +do_catchsql_test 5.2.3 { + SELECT matchinfo(t7, 'n') FROM t7 WHERE t7 MATCH 'x y' +} {1 {unrecognized matchinfo request: n}} + +do_catchsql_test 5.3.1 { + SELECT matchinfo(t8, 'l') FROM t8 WHERE t8 MATCH 'x y' +} {1 {unrecognized matchinfo request: l}} + +#------------------------------------------------------------------------- +# Test that the offsets() function handles corruption in the %_content +# table correctly. +# +do_execsql_test 6.1 { + CREATE VIRTUAL TABLE t9 USING fts4; + INSERT INTO t9 VALUES( + 'this record is used to try to dectect corruption' + ); + SELECT offsets(t9) FROM t9 WHERE t9 MATCH 'to'; +} {{0 0 20 2 0 0 27 2}} + +do_catchsql_test 6.2 { + UPDATE t9_content SET c0content = 'this record is used to'; + SELECT offsets(t9) FROM t9 WHERE t9 MATCH 'to'; +} {1 {database disk image is malformed}} + +#------------------------------------------------------------------------- +# Test the outcome of matchinfo() when used within a query that does not +# use the full-text index (i.e. lookup by rowid or full-table scan). +# +do_execsql_test 7.1 { + CREATE VIRTUAL TABLE t10 USING fts4; + INSERT INTO t10 VALUES('first record'); + INSERT INTO t10 VALUES('second record'); +} +do_execsql_test 7.2 { + SELECT typeof(matchinfo(t10)), length(matchinfo(t10)) FROM t10; +} {blob 0 blob 0} +do_execsql_test 7.3 { + SELECT typeof(matchinfo(t10)), length(matchinfo(t10)) FROM t10 WHERE docid=1; +} {blob 0} +do_execsql_test 7.4 { + SELECT typeof(matchinfo(t10)), length(matchinfo(t10)) + FROM t10 WHERE t10 MATCH 'record' +} {blob 20 blob 20} + +#------------------------------------------------------------------------- +# Test a special case - matchinfo('nxa') with many zero length documents. +# Special because "x" internally uses a statement used by both "n" and "a". +# This was causing a problem at one point in the obscure case where the +# total number of bytes of data stored in an fts3 table was greater than +# the number of rows. i.e. when the following query returns true: +# +# SELECT sum(length(content)) < count(*) FROM fts4table; +# +do_execsql_test 8.1 { + CREATE VIRTUAL TABLE t11 USING fts4; + INSERT INTO t11(t11) VALUES('nodesize=24'); + INSERT INTO t11 VALUES('quitealongstringoftext'); + INSERT INTO t11 VALUES('anotherquitealongstringoftext'); + INSERT INTO t11 VALUES('athirdlongstringoftext'); + INSERT INTO t11 VALUES('andonemoreforgoodluck'); +} +do_test 8.2 { + for {set i 0} {$i < 200} {incr i} { + execsql { INSERT INTO t11 VALUES('') } + } + execsql { INSERT INTO t11(t11) VALUES('optimize') } +} {} +do_execsql_test 8.3 { + SELECT mit(matchinfo(t11, 'nxa')) FROM t11 WHERE t11 MATCH 'a*' +} {{204 1 3 3 0} {204 1 3 3 0} {204 1 3 3 0}} + +# Corruption related tests. +do_execsql_test 8.4.1.1 { UPDATE t11_stat SET value = X'0000'; } +do_catchsql_test 8.5.1.2 { + SELECT mit(matchinfo(t11, 'nxa')) FROM t11 WHERE t11 MATCH 'a*' +} {1 {database disk image is malformed}} + +do_execsql_test 8.4.2.1 { UPDATE t11_stat SET value = X'00'; } +do_catchsql_test 8.5.2.2 { + SELECT mit(matchinfo(t11, 'nxa')) FROM t11 WHERE t11 MATCH 'a*' +} {1 {database disk image is malformed}} + +do_execsql_test 8.4.3.1 { UPDATE t11_stat SET value = NULL; } +do_catchsql_test 8.5.3.2 { + SELECT mit(matchinfo(t11, 'nxa')) FROM t11 WHERE t11 MATCH 'a*' +} {1 {database disk image is malformed}} + +finish_test + diff --git a/test/fts3query.test b/test/fts3query.test index 68467df9..c8f8686b 100644 --- a/test/fts3query.test +++ b/test/fts3query.test @@ -24,6 +24,8 @@ source $testdir/malloc_common.tcl source $testdir/fts3_common.tcl set DO_MALLOC_TEST 0 +set testprefix fts3query + do_test fts3query-1.1 { execsql { CREATE VIRTUAL TABLE t1 USING fts3(x); @@ -104,9 +106,6 @@ do_test fts3query-3.3 { # The following tests check that ticket 775b39dd3c has been fixed. # -proc eqp {sql} { - uplevel [list execsql "EXPLAIN QUERY PLAN $sql"] -} do_test fts3query-4.1 { execsql { DROP TABLE IF EXISTS t1; @@ -116,18 +115,98 @@ do_test fts3query-4.1 { CREATE TABLE bt(title); } } {} -do_test fts3query-4.2 { - eqp "SELECT t1.number FROM t1, ft WHERE t1.number=ft.rowid ORDER BY t1.date" -} {0 0 {TABLE t1 WITH INDEX i1 ORDER BY} 1 1 {TABLE ft VIRTUAL TABLE INDEX 1:}} -do_test fts3query-4.3 { - eqp "SELECT t1.number FROM ft, t1 WHERE t1.number=ft.rowid ORDER BY t1.date" -} {0 1 {TABLE t1 WITH INDEX i1 ORDER BY} 1 0 {TABLE ft VIRTUAL TABLE INDEX 1:}} -do_test fts3query-4.4 { - eqp "SELECT t1.number FROM t1, bt WHERE t1.number=bt.rowid ORDER BY t1.date" -} {0 0 {TABLE t1 WITH INDEX i1 ORDER BY} 1 1 {TABLE bt USING PRIMARY KEY}} -do_test fts3query-4.5 { - eqp "SELECT t1.number FROM bt, t1 WHERE t1.number=bt.rowid ORDER BY t1.date" -} {0 1 {TABLE t1 WITH INDEX i1 ORDER BY} 1 0 {TABLE bt USING PRIMARY KEY}} +do_eqp_test fts3query-4.2 { + SELECT t1.number FROM t1, ft WHERE t1.number=ft.rowid ORDER BY t1.date +} { + 0 0 0 {SCAN TABLE t1 USING COVERING INDEX i1 (~1000000 rows)} + 0 1 1 {SCAN TABLE ft VIRTUAL TABLE INDEX 1: (~0 rows)} +} +do_eqp_test fts3query-4.3 { + SELECT t1.number FROM ft, t1 WHERE t1.number=ft.rowid ORDER BY t1.date +} { + 0 0 1 {SCAN TABLE t1 USING COVERING INDEX i1 (~1000000 rows)} + 0 1 0 {SCAN TABLE ft VIRTUAL TABLE INDEX 1: (~0 rows)} +} +do_eqp_test fts3query-4.4 { + SELECT t1.number FROM t1, bt WHERE t1.number=bt.rowid ORDER BY t1.date +} { + 0 0 0 {SCAN TABLE t1 USING COVERING INDEX i1 (~1000000 rows)} + 0 1 1 {SEARCH TABLE bt USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} +} +do_eqp_test fts3query-4.5 { + SELECT t1.number FROM bt, t1 WHERE t1.number=bt.rowid ORDER BY t1.date +} { + 0 0 1 {SCAN TABLE t1 USING COVERING INDEX i1 (~1000000 rows)} + 0 1 0 {SEARCH TABLE bt USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} +} + + +# Test that calling matchinfo() with the wrong number of arguments, or with +# an invalid argument returns an error. +# +do_execsql_test 5.1 { + CREATE VIRTUAL TABLE t2 USING FTS4; + INSERT INTO t2 VALUES('it was the first time in history'); +} +do_select_tests 5.2 -errorformat { + wrong number of arguments to function %s() +} { + 1 "SELECT matchinfo() FROM t2 WHERE t2 MATCH 'history'" matchinfo + 3 "SELECT snippet(t2, 1, 2, 3, 4, 5, 6) FROM t2 WHERE t2 MATCH 'history'" + snippet +} +do_select_tests 5.3 -errorformat { + illegal first argument to %s +} { + 1 "SELECT matchinfo(content) FROM t2 WHERE t2 MATCH 'history'" matchinfo + 2 "SELECT offsets(content) FROM t2 WHERE t2 MATCH 'history'" offsets + 3 "SELECT snippet(content) FROM t2 WHERE t2 MATCH 'history'" snippet + 4 "SELECT optimize(content) FROM t2 WHERE t2 MATCH 'history'" optimize +} +do_execsql_test 5.4.0 { UPDATE t2_content SET c0content = X'1234' } +do_select_tests 5.4 -errorformat { + illegal first argument to %s +} { + 1 "SELECT matchinfo(content) FROM t2 WHERE t2 MATCH 'history'" matchinfo + 2 "SELECT offsets(content) FROM t2 WHERE t2 MATCH 'history'" offsets + 3 "SELECT snippet(content) FROM t2 WHERE t2 MATCH 'history'" snippet + 4 "SELECT optimize(content) FROM t2 WHERE t2 MATCH 'history'" optimize +} +do_catchsql_test 5.5.1 { + SELECT matchinfo(t2, 'abc') FROM t2 WHERE t2 MATCH 'history' +} {1 {unrecognized matchinfo request: b}} + +do_execsql_test 5.5 { DROP TABLE t2 } + + +# Test the snippet() function with 1 to 6 arguments. +# +do_execsql_test 6.1 { + CREATE VIRTUAL TABLE t3 USING FTS4(a, b); + INSERT INTO t3 VALUES('no gestures', 'another intriguing discovery by observing the hand gestures (called beats) people make while speaking. Research has shown that such gestures do more than add visual emphasis to our words (many people gesture while they''re on the telephone, for example); it seems they actually help our brains find words'); +} +do_select_tests 6.2 { + 1 "SELECT snippet(t3) FROM t3 WHERE t3 MATCH 'gestures'" + {{...hand gestures (called beats) people make while speaking. Research has shown that such gestures do...}} + + 2 "SELECT snippet(t3, 'XXX') FROM t3 WHERE t3 MATCH 'gestures'" + {{...hand XXXgestures (called beats) people make while speaking. Research has shown that such XXXgestures do...}} + + 3 "SELECT snippet(t3, 'XXX', 'YYY') FROM t3 WHERE t3 MATCH 'gestures'" + {{...hand XXXgesturesYYY (called beats) people make while speaking. Research has shown that such XXXgesturesYYY do...}} + + 4 "SELECT snippet(t3, 'XXX', 'YYY', 'ZZZ') FROM t3 WHERE t3 MATCH 'gestures'" + {{ZZZhand XXXgesturesYYY (called beats) people make while speaking. Research has shown that such XXXgesturesYYY doZZZ}} + + 5 "SELECT snippet(t3, 'XXX', 'YYY', 'ZZZ', 1) FROM t3 WHERE t3 MATCH 'gestures'" + {{ZZZhand XXXgesturesYYY (called beats) people make while speaking. Research has shown that such XXXgesturesYYY doZZZ}} + + 6 "SELECT snippet(t3, 'XXX', 'YYY', 'ZZZ', 0) FROM t3 WHERE t3 MATCH 'gestures'" + {{no XXXgesturesYYY}} + + 7 "SELECT snippet(t3, 'XXX', 'YYY', 'ZZZ', 1, 5) FROM t3 WHERE t3 MATCH 'gestures'" + {{ZZZthe hand XXXgesturesYYY (called beatsZZZ}} +} finish_test diff --git a/test/fts3rnd.test b/test/fts3rnd.test index fe1ea5e7..0909cee6 100644 --- a/test/fts3rnd.test +++ b/test/fts3rnd.test @@ -306,6 +306,10 @@ foreach nodesize {50 500 1000 2000} { delete_row $iDelete if {0==($iTest%2)} { execsql COMMIT } + if {0==($iTest%2)} { + do_test fts3rnd-1.$nodesize.$iTest.0 { fts3_integrity_check t1 } ok + } + # Pick 10 terms from the vocabulary. Check that the results of querying # the database for the set of documents containing each of these terms # is the same as the result obtained by scanning the contents of the Tcl @@ -379,7 +383,6 @@ foreach nodesize {50 500 1000 2000} { set terms [list [random_term] [random_term] [random_term]] set nNear 11 set match [join $terms " NEAR/$nNear "] - set fts3 [execsql { SELECT docid FROM t1 WHERE t1 MATCH $match }] do_select_test fts3rnd-1.$nodesize.$iTest.7.$i { SELECT docid FROM t1 WHERE t1 MATCH $match } [simple_near $terms $nNear] diff --git a/test/fts3shared.test b/test/fts3shared.test new file mode 100644 index 00000000..5f75bd5f --- /dev/null +++ b/test/fts3shared.test @@ -0,0 +1,72 @@ +# 2010 September 17 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +ifcapable !fts3||!shared_cache { + finish_test + return +} + +db close +set ::enable_shared_cache [sqlite3_enable_shared_cache 1] + +# Open two connections to the database in shared-cache mode. +# +sqlite3 db test.db +sqlite3 db2 test.db + +# Create a virtual FTS3 table. Populate it with some initial data. +# +do_execsql_test fts3shared-1.1 { + CREATE VIRTUAL TABLE t1 USING fts3(x); + BEGIN; + INSERT INTO t1 VALUES('We listened and looked sideways up!'); + INSERT INTO t1 VALUES('Fear at my heart, as at a cup,'); + INSERT INTO t1 VALUES('My life-blood seemed to sip!'); + INSERT INTO t1 VALUES('The stars were dim, and thick the night'); + COMMIT; +} {} + +# Open a write transaction and insert rows into the FTS3 table. This takes +# a write-lock on the underlying t1_content table. +# +do_execsql_test fts3shared-1.2 { + BEGIN; + INSERT INTO t1 VALUES('The steersman''s face by his lamp gleamed white;'); +} {} + +# Now try a SELECT on the full-text table. This particular SELECT does not +# read data from the %_content table. But it still attempts to obtain a lock +# on that table and so the SELECT fails. +# +do_test fts3shared-1.3 { + catchsql { + BEGIN; + SELECT rowid FROM t1 WHERE t1 MATCH 'stars' + } db2 +} {1 {database table is locked}} + +# Verify that the first connection can commit its transaction. +# +do_test fts3shared-1.4 { sqlite3_get_autocommit db } 0 +do_execsql_test fts3shared-1.5 { COMMIT } {} +do_test fts3shared-1.6 { sqlite3_get_autocommit db } 1 + +# Verify that the second connection still has an open transaction. +# +do_test fts3shared-1.6 { sqlite3_get_autocommit db2 } 0 + +db close +db2 close + +sqlite3_enable_shared_cache $::enable_shared_cache +finish_test + diff --git a/test/fts3snippet.test b/test/fts3snippet.test index e7adaee0..359a87c6 100644 --- a/test/fts3snippet.test +++ b/test/fts3snippet.test @@ -20,7 +20,6 @@ source $testdir/tester.tcl # If SQLITE_ENABLE_FTS3 is not defined, omit this file. ifcapable !fts3 { finish_test ; return } source $testdir/fts3_common.tcl -source $testdir/malloc_common.tcl set sqlite_fts3_enable_parentheses 1 set DO_MALLOC_TEST 0 @@ -433,10 +432,10 @@ foreach {DO_MALLOC_TEST enc} { {2 2 1 3 3 3 6 3 0 0 0 2 3 2} }] - # EVIDENCE-OF: R-56101-59725 If used within a SELECT that uses the - # "query by rowid" or "linear scan" strategies, then the snippet and - # offsets both return an an empty string, and the matchinfo function - # returns a blob value zero bytes in size. + # EVIDENCE-OF: R-40630-02268 If used within a SELECT that uses the + # "query by rowid" or "linear scan" strategies, then the snippet and + # offsets both return an empty string, and the matchinfo function + # returns a blob value zero bytes in size. # set r 1000000 ;# A rowid that exists in table ft do_select_test $T.10.0 { SELECT rowid FROM ft WHERE rowid = $r } $r @@ -460,6 +459,5 @@ foreach {DO_MALLOC_TEST enc} { } {0 blob} } - set sqlite_fts3_enable_parentheses 0 finish_test diff --git a/test/func3.test b/test/func3.test new file mode 100644 index 00000000..e58d730f --- /dev/null +++ b/test/func3.test @@ -0,0 +1,71 @@ +# 2010 August 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 implements regression tests for SQLite library. The +# focus of this file is testing that destructor functions associated +# with functions created using sqlite3_create_function_v2() is +# correctly invoked. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl + + +do_test func3-1.1 { + set destroyed 0 + proc destroy {} { set ::destroyed 1 } + sqlite3_create_function_v2 db f2 -1 any -func f2 -destroy destroy + set destroyed +} 0 +do_test func3-1.2 { + sqlite3_create_function_v2 db f2 -1 utf8 -func f2 + set destroyed +} 0 +do_test func3-1.3 { + sqlite3_create_function_v2 db f2 -1 utf16le -func f2 + set destroyed +} 0 +do_test func3-1.4 { + sqlite3_create_function_v2 db f2 -1 utf16be -func f2 + set destroyed +} 1 + +do_test func3-2.1 { + set destroyed 0 + proc destroy {} { set ::destroyed 1 } + sqlite3_create_function_v2 db f3 -1 utf8 -func f3 -destroy destroy + set destroyed +} 0 +do_test func3-2.2 { + sqlite3_create_function_v2 db f3 -1 utf8 -func f3 + set destroyed +} 1 + +do_test func3-3.1 { + set destroyed 0 + proc destroy {} { set ::destroyed 1 } + sqlite3_create_function_v2 db f3 -1 any -func f3 -destroy destroy + set destroyed +} 0 +do_test func3-3.2 { + db close + set destroyed +} 1 + +sqlite3 db test.db +do_test func3-4.1 { + set destroyed 0 + set rc [catch { + sqlite3_create_function_v2 db f3 -1 any -func f3 -step f3 -destroy destroy + } msg] + list $rc $msg +} {1 SQLITE_MISUSE} +do_test func3-4.2 { set destroyed } 1 + +finish_test diff --git a/test/in.test b/test/in.test index 8fbfbed8..2c38a0f4 100644 --- a/test/in.test +++ b/test/in.test @@ -404,33 +404,62 @@ do_test in-12.5 { do_test in-12.6 { catchsql { SELECT * FROM t2 WHERE a IN ( - SELECT a FROM t3 UNION ALL SELECT a, b FROM t2 + SELECT a, b FROM t3 UNION ALL SELECT a FROM t2 ); } } {1 {SELECTs to the left and right of UNION ALL do not have the same number of result columns}} do_test in-12.7 { catchsql { SELECT * FROM t2 WHERE a IN ( - SELECT a FROM t3 UNION SELECT a, b FROM t2 + SELECT a, b FROM t3 UNION SELECT a FROM t2 ); } } {1 {SELECTs to the left and right of UNION do not have the same number of result columns}} do_test in-12.8 { catchsql { SELECT * FROM t2 WHERE a IN ( - SELECT a FROM t3 EXCEPT SELECT a, b FROM t2 + SELECT a, b FROM t3 EXCEPT SELECT a FROM t2 ); } } {1 {SELECTs to the left and right of EXCEPT do not have the same number of result columns}} do_test in-12.9 { catchsql { SELECT * FROM t2 WHERE a IN ( - SELECT a FROM t3 INTERSECT SELECT a, b FROM t2 + SELECT a, b FROM t3 INTERSECT SELECT a FROM t2 ); } } {1 {SELECTs to the left and right of INTERSECT do not have the same number of result columns}} } +do_test in-12.10 { + catchsql { + SELECT * FROM t2 WHERE a IN ( + SELECT a FROM t3 UNION ALL SELECT a, b FROM t2 + ); + } +} {1 {only a single result allowed for a SELECT that is part of an expression}} +do_test in-12.11 { + catchsql { + SELECT * FROM t2 WHERE a IN ( + SELECT a FROM t3 UNION SELECT a, b FROM t2 + ); + } +} {1 {only a single result allowed for a SELECT that is part of an expression}} +do_test in-12.12 { + catchsql { + SELECT * FROM t2 WHERE a IN ( + SELECT a FROM t3 EXCEPT SELECT a, b FROM t2 + ); + } +} {1 {only a single result allowed for a SELECT that is part of an expression}} +do_test in-12.13 { + catchsql { + SELECT * FROM t2 WHERE a IN ( + SELECT a FROM t3 INTERSECT SELECT a, b FROM t2 + ); + } +} {1 {only a single result allowed for a SELECT that is part of an expression}} + #------------------------------------------------------------------------ # The following tests check that NULL is handled correctly when it diff --git a/test/incrblob.test b/test/incrblob.test index 08659129..9c9389eb 100644 --- a/test/incrblob.test +++ b/test/incrblob.test @@ -420,86 +420,94 @@ unset -nocomplain ::text # after the transaction is commited and the blob channel # closed. # -sqlite3_soft_heap_limit 0 -do_test incrblob-6.1 { - sqlite3 db2 test.db - execsql { - BEGIN; - INSERT INTO blobs(k, v, i) VALUES('a', 'different', 'connection'); - } db2 -} {} -do_test incrblob-6.2 { - execsql { - SELECT rowid FROM blobs - } -} {1 2 3} -do_test incrblob-6.3 { - set rc [catch { - db incrblob blobs v 1 - } msg] - list $rc $msg -} {1 {database is locked}} -do_test incrblob-6.4 { - set rc [catch { - db incrblob blobs v 3 - } msg] - list $rc $msg -} {1 {database is locked}} -do_test incrblob-6.5 { - set ::blob [db incrblob -readonly blobs v 3] - read $::blob -} {hello} -do_test incrblob-6.6 { - close $::blob -} {} - -do_test incrblob-6.7 { - set ::blob [db2 incrblob blobs i 4] - gets $::blob -} {connection} -do_test incrblob-6.8 { - tell $::blob -} {10} -do_test incrblob-6.9 { - seek $::blob 0 - puts -nonewline $::blob "invocation" - flush $::blob -} {} - -# At this point rollback should be illegal (because -# there is an open blob channel). But commit is also illegal because -# the open blob is read-write. +# This test does not work with the "memsubsys1" configuration. +# Permutation memsubsys1 configures a very small static allocation +# for use as page-cache memory. This causes SQLite to upgrade +# to an exclusive lock when writing earlier than usual, which +# makes some of these tests fail. # -do_test incrblob-6.10 { - catchsql { - ROLLBACK; - } db2 -} {1 {cannot rollback transaction - SQL statements in progress}} -do_test incrblob-6.11 { - catchsql { - COMMIT; - } db2 -} {1 {cannot commit transaction - SQL statements in progress}} - -do_test incrblob-6.12 { - execsql { - SELECT * FROM blobs WHERE rowid = 4; - } -} {} -do_test incrblob-6.13 { - close $::blob -} {} -do_test incrblob-6.14 { - catchsql { - COMMIT; - } db2 -} {0 {}} -do_test incrblob-6.15 { - execsql { - SELECT * FROM blobs WHERE rowid = 4; - } -} {a different invocation} -db2 close +sqlite3_soft_heap_limit 0 +if {[permutation] != "memsubsys1"} { + do_test incrblob-6.1 { + sqlite3 db2 test.db + execsql { + BEGIN; + INSERT INTO blobs(k, v, i) VALUES('a', 'different', 'connection'); + } db2 + } {} + do_test incrblob-6.2 { + execsql { + SELECT rowid FROM blobs + } + } {1 2 3} + do_test incrblob-6.3 { + set rc [catch { + db incrblob blobs v 1 + } msg] + list $rc $msg + } {1 {database is locked}} + do_test incrblob-6.4 { + set rc [catch { + db incrblob blobs v 3 + } msg] + list $rc $msg + } {1 {database is locked}} + do_test incrblob-6.5 { + set ::blob [db incrblob -readonly blobs v 3] + read $::blob + } {hello} + do_test incrblob-6.6 { + close $::blob + } {} + + do_test incrblob-6.7 { + set ::blob [db2 incrblob blobs i 4] + gets $::blob + } {connection} + do_test incrblob-6.8 { + tell $::blob + } {10} + do_test incrblob-6.9 { + seek $::blob 0 + puts -nonewline $::blob "invocation" + flush $::blob + } {} + + # At this point rollback should be illegal (because + # there is an open blob channel). But commit is also illegal because + # the open blob is read-write. + # + do_test incrblob-6.10 { + catchsql { + ROLLBACK; + } db2 + } {1 {cannot rollback transaction - SQL statements in progress}} + do_test incrblob-6.11 { + catchsql { + COMMIT; + } db2 + } {1 {cannot commit transaction - SQL statements in progress}} + + do_test incrblob-6.12 { + execsql { + SELECT * FROM blobs WHERE rowid = 4; + } + } {} + do_test incrblob-6.13 { + close $::blob + } {} + do_test incrblob-6.14 { + catchsql { + COMMIT; + } db2 + } {0 {}} + do_test incrblob-6.15 { + execsql { + SELECT * FROM blobs WHERE rowid = 4; + } + } {a different invocation} + db2 close +} sqlite3_soft_heap_limit $cmdlinearg(soft-heap-limit) #----------------------------------------------------------------------- @@ -669,5 +677,14 @@ do_test incrblob-8.7 { execsql {SELECT b FROM t1 WHERE a = 314159} } {etilqs} +# The following test case exposes an instance in the blob code where +# an error message was set using a call similar to sqlite3_mprintf(zErr), +# where zErr is an arbitrary string. This is no good if the string contains +# characters that can be mistaken for printf() formatting directives. +# +do_test incrblob-9.1 { + list [catch { db incrblob t1 "A tricky column name %s%s" 1 } msg] $msg +} {1 {no such column: "A tricky column name %s%s"}} + finish_test diff --git a/test/incrblob3.test b/test/incrblob3.test new file mode 100644 index 00000000..4c49f156 --- /dev/null +++ b/test/incrblob3.test @@ -0,0 +1,272 @@ +# 2010 October 20 +# +# 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. +# +#*********************************************************************** +# +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +sqlite3 db test.db +sqlite3_db_config_lookaside db 0 0 0 + +do_execsql_test incrblob3-1.1 { + CREATE TABLE blobs(k INTEGER PRIMARY KEY, v BLOB); + INSERT INTO blobs VALUES(1, zeroblob(100)); + INSERT INTO blobs VALUES(2, zeroblob(100)); +} {} + +# Test the sqlite3_blob_reopen()/read()/write() functions. +# +do_test incrblob3-1.2 { + set ::blob [db incrblob blobs v 1] + puts $::blob "hello world" +} {} + +do_test incrblob3-1.3 { + sqlite3_blob_reopen $::blob 2 + puts $::blob "world hello" +} {} + +do_test incrblob3-1.4 { + sqlite3_blob_reopen $::blob 1 + gets $::blob +} {hello world} + +do_test incrblob3-1.5 { + sqlite3_blob_reopen $::blob 2 + gets $::blob +} {world hello} + +do_test incrblob3-1.6 { close $::blob } {} + +# Test some error conditions. +# +# incrblob3-2.1: Attempting to reopen a row that does not exist. +# incrblob3-2.2: Attempting to reopen a row that does not contain a blob +# or text value. +# +do_test incrblob3-2.1.1 { + set ::blob [db incrblob blobs v 1] + list [catch {sqlite3_blob_reopen $::blob 3} msg] $msg +} {1 SQLITE_ERROR} +do_test incrblob3-2.1.2 { + list [sqlite3_errcode db] [sqlite3_errmsg db] +} {SQLITE_ERROR {no such rowid: 3}} +do_test incrblob3-2.1.3 { + list [catch {sqlite3_blob_reopen $::blob 1} msg] $msg +} {1 SQLITE_ABORT} +do_test incrblob3-2.1.4 { close $::blob } {} + +do_execsql_test incrblob3-2.2.1 { + INSERT INTO blobs VALUES(3, 42); + INSERT INTO blobs VALUES(4, 54.4); + INSERT INTO blobs VALUES(5, NULL); +} +foreach {tn rowid type} { + 1 3 integer + 2 4 real + 3 5 null +} { + do_test incrblob3-2.2.$tn.1 { + set ::blob [db incrblob blobs v 1] + list [catch {sqlite3_blob_reopen $::blob $rowid} msg] $msg + } {1 SQLITE_ERROR} + do_test incrblob3-2.2.$tn.2 { + list [sqlite3_errcode db] [sqlite3_errmsg db] + } "SQLITE_ERROR {cannot open value of type $type}" + + do_test incrblob3-2.2.$tn.3 { + list [catch {sqlite3_blob_reopen $::blob 1} msg] $msg + } {1 SQLITE_ABORT} + do_test incrblob3-2.2.$tn.4 { + list [catch {sqlite3_blob_read $::blob 0 10} msg] $msg + } {1 SQLITE_ABORT} + do_test incrblob3-2.2.$tn.5 { + list [catch {sqlite3_blob_write $::blob 0 "abcd"} msg] $msg + } {1 SQLITE_ABORT} + do_test incrblob3-2.2.$tn.6 { + sqlite3_blob_bytes $::blob + } {0} + + do_test incrblob3-2.2.$tn.7 { close $::blob } {} +} + +# Test that passing NULL to sqlite3_blob_XXX() APIs returns SQLITE_MISUSE. +# +# incrblob3-3.1: sqlite3_blob_reopen() +# incrblob3-3.2: sqlite3_blob_read() +# incrblob3-3.3: sqlite3_blob_write() +# incrblob3-3.4: sqlite3_blob_bytes() +# +do_test incrblob3-3.1 { + list [catch {sqlite3_blob_reopen {} 3} msg] $msg +} {1 SQLITE_MISUSE} + +do_test incrblob3-3.2 { + list [catch {sqlite3_blob_read {} 0 10} msg] $msg +} {1 SQLITE_MISUSE} + +do_test incrblob3-3.3 { + list [catch {sqlite3_blob_write {} 0 "abcd"} msg] $msg +} {1 SQLITE_MISUSE} + +do_test incrblob3-3.4 { sqlite3_blob_bytes {} } {0} + +do_test incrblob3-3.5 { sqlite3_blob_close {} } {} + +# Test out-of-range reading and writing +# +do_test incrblob3-4.1 { + set ::blob [db incrblob blobs v 1] + sqlite3_blob_bytes $::blob +} {100} +do_test incrblob3-4.2 { + list [catch { sqlite3_blob_read $::blob -1 10 } msg] $msg +} {1 SQLITE_ERROR} +do_test incrblob3-4.3 { + list [catch { sqlite3_blob_read $::blob 0 -10 } msg] $msg +} {1 SQLITE_ERROR} +do_test incrblob3-4.4 { + list [catch { sqlite3_blob_read $::blob 95 10 } msg] $msg +} {1 SQLITE_ERROR} +do_test incrblob3-4.5 { + list [catch { sqlite3_blob_write $::blob -1 "abcdefghij" 10 } msg] $msg +} {1 SQLITE_ERROR} +do_test incrblob3-4.6 { + list [catch { sqlite3_blob_write $::blob 0 "abcdefghij" -10 } msg] $msg +} {1 SQLITE_ERROR} +do_test incrblob3-4.7 { + list [catch { sqlite3_blob_write $::blob 95 "abcdefghij" } msg] $msg +} {1 SQLITE_ERROR} + +do_test incrblob3-4.8 { close $::blob } {} + +# Test that modifying the row a blob handle points to aborts the blob. +# +do_test incrblob3-5.1 { + set ::blob [db incrblob blobs v 1] + sqlite3_blob_bytes $::blob +} {100} +do_test incrblob3-5.2 { + execsql { UPDATE blobs SET v = '123456789012345678901234567890' WHERE k = 1 } + list [catch { sqlite3_blob_read $::blob 0 10 } msg] $msg +} {1 SQLITE_ABORT} + +# Test various errors that can occur in sqlite3_blob_open(): +# +# 1. Trying to open a virtual table column. +# 2. Trying to open a view column. +# 3. Trying to open a column that does not exist. +# 4. Trying to open a read/write handle on an indexed column. +# 5. Trying to open a read/write handle on the child key of an FK constraint. +# +ifcapable fts3 { + do_test incrblob3-6.1 { + execsql { + CREATE VIRTUAL TABLE ft USING fts3; + INSERT INTO ft VALUES('rules to open a column to which'); + } + + list [catch { db incrblob ft content 1 } msg] $msg + } {1 {cannot open virtual table: ft}} +} +ifcapable view { + do_test incrblob3-6.2 { + execsql { CREATE VIEW v1 AS SELECT * FROM blobs } + list [catch { db incrblob v1 content 1 } msg] $msg + } {1 {cannot open view: v1}} +} + +do_test incrblob3-6.3 { + list [catch { db incrblob blobs content 1 } msg] $msg +} {1 {no such column: "content"}} + +do_test incrblob3-6.4.1 { + execsql { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(b); + INSERT INTO t1 VALUES(zeroblob(100), zeroblob(100)); + } + list [catch { db incrblob t1 b 1 } msg] $msg +} {1 {cannot open indexed column for writing}} +do_test incrblob3-6.4.2 { + set ::blob [db incrblob t1 a 1] + close $::blob +} {} +do_test incrblob3-6.4.3 { + set ::blob [db incrblob -readonly t1 b 1] + close $::blob +} {} + +do_test incrblob3-6.5.1 { + execsql { + CREATE TABLE p1(a PRIMARY KEY); + CREATE TABLE c1(a, b REFERENCES p1); + PRAGMA foreign_keys = 1; + INSERT INTO p1 VALUES(zeroblob(100)); + INSERT INTO c1 VALUES(zeroblob(100), zeroblob(100)); + } + list [catch { db incrblob c1 b 1 } msg] $msg +} {1 {cannot open foreign key column for writing}} + +do_test incrblob3-6.5.2 { + set ::blob [db incrblob c1 a 1] + close $::blob +} {} +do_test incrblob3-6.5.3 { + set ::blob [db incrblob -readonly c1 b 1] + close $::blob +} {} +do_test incrblob3-6.5.4 { + execsql { PRAGMA foreign_keys = 0 } + set ::blob [db incrblob c1 b 1] + close $::blob +} {} + + +# Test that sqlite3_blob_open() handles transient and persistent schema +# errors correctly. +# +do_test incrblob3-7.1 { + sqlite3 db2 test.db + sqlite3_db_config_lookaside db2 0 0 0 + execsql { CREATE TABLE t2(x) } db2 + set ::blob [db incrblob blobs v 1] + close $::blob +} {} +db2 close + +testvfs tvfs -default 1 +tvfs filter xAccess +tvfs script access_method + +proc access_method {args} { + set schemacookie [hexio_get_int [hexio_read test.db 40 4]] + incr schemacookie + hexio_write test.db 40 [hexio_render_int32 $schemacookie] + + set dbversion [hexio_get_int [hexio_read test.db 24 4]] + incr dbversion + hexio_write test.db 24 [hexio_render_int32 $dbversion] + + return "" +} + +do_test incrblob3-7.2 { + sqlite3 db test.db + sqlite3_db_config_lookaside db 0 0 0 + list [catch {db incrblob blobs v 1} msg] $msg +} {1 {database schema has changed}} +db close +tvfs delete + +finish_test + diff --git a/test/incrblobfault.test b/test/incrblobfault.test new file mode 100644 index 00000000..d125471c --- /dev/null +++ b/test/incrblobfault.test @@ -0,0 +1,70 @@ +# 2010 October 26 +# +# 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. +# +#*********************************************************************** +# +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +set testprefix incrblobfault + +do_execsql_test 1.0 { + CREATE TABLE blob(x INTEGER PRIMARY KEY, v BLOB); + INSERT INTO blob VALUES(1, 'hello world'); + INSERT INTO blob VALUES(2, 'world hello'); + INSERT INTO blob SELECT NULL, v FROM blob; + INSERT INTO blob SELECT NULL, v FROM blob; + INSERT INTO blob SELECT NULL, v FROM blob; + INSERT INTO blob SELECT NULL, v FROM blob; + INSERT INTO blob SELECT NULL, v FROM blob; + INSERT INTO blob SELECT NULL, v FROM blob; + INSERT INTO blob SELECT NULL, v FROM blob; + INSERT INTO blob SELECT NULL, v FROM blob; + INSERT INTO blob SELECT NULL, v FROM blob; + INSERT INTO blob SELECT NULL, v FROM blob; +} + +do_faultsim_test 1 -prep { + sqlite3 db test.db + set ::blob [db incrblob blob v 1] +} -body { + if {[catch {sqlite3_blob_reopen $::blob 1000}]} { + error [sqlite3_errmsg db] + } +} -test { + faultsim_test_result {0 {}} + close $::blob +} + +do_faultsim_test 2 -prep { + sqlite3 db test.db + set ::blob [db incrblob blob v 1] +} -body { + if {[catch {sqlite3_blob_reopen $::blob -1}]} { + error [sqlite3_errmsg db] + } +} -test { + faultsim_test_result {1 {no such rowid: -1}} + close $::blob +} + +do_faultsim_test 3 -prep { + sqlite3 db test.db +} -body { + set ::blob [db incrblob blob v 1] + gets $::blob +} -test { + faultsim_test_result {0 {hello world}} + catch { close $::blob } +} + +finish_test + diff --git a/test/index.test b/test/index.test index a278ac88..0a41a6de 100644 --- a/test/index.test +++ b/test/index.test @@ -532,10 +532,19 @@ do_test index-15.2 { INSERT INTO t1 VALUES('+',9); INSERT INTO t1 VALUES('+12347.E+02',10); INSERT INTO t1 VALUES('+12347E+02',11); - SELECT b FROM t1 ORDER BY a; + INSERT INTO t1 VALUES('+.125E+04',12); + INSERT INTO t1 VALUES('-.125E+04',13); + INSERT INTO t1 VALUES('.125E+0',14); + INSERT INTO t1 VALUES('.125',15); + SELECT b FROM t1 ORDER BY a, b; } -} {8 5 2 1 3 6 11 9 10 4 7} -integrity_check index-15.1 +} {13 14 15 12 8 5 2 1 3 6 10 11 9 4 7} +do_test index-15.3 { + execsql { + SELECT b FROM t1 WHERE typeof(a) IN ('integer','real') ORDER BY b; + } +} {1 2 3 5 6 8 10 11 12 13 14 15} +integrity_check index-15.4 # The following tests - index-16.* - test that when a table definition # includes qualifications that specify the same constraint twice only a diff --git a/test/indexedby.test b/test/indexedby.test index 70ab9f9b..b8b5be63 100644 --- a/test/indexedby.test +++ b/test/indexedby.test @@ -40,15 +40,18 @@ proc EQP {sql} { # These tests are to check that "EXPLAIN QUERY PLAN" is working as expected. # -do_test indexedby-1.2 { - EQP { select * from t1 WHERE a = 10; } -} {0 0 {TABLE t1 WITH INDEX i1}} -do_test indexedby-1.3 { - EQP { select * from t1 ; } -} {0 0 {TABLE t1}} -do_test indexedby-1.4 { - EQP { select * from t1, t2 WHERE c = 10; } -} {0 1 {TABLE t2 WITH INDEX i3} 1 0 {TABLE t1}} +do_execsql_test indexedby-1.2 { + EXPLAIN QUERY PLAN select * from t1 WHERE a = 10; +} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?) (~10 rows)}} +do_execsql_test indexedby-1.3 { + EXPLAIN QUERY PLAN select * from t1 ; +} {0 0 0 {SCAN TABLE t1 (~1000000 rows)}} +do_execsql_test indexedby-1.4 { + EXPLAIN QUERY PLAN select * from t1, t2 WHERE c = 10; +} { + 0 0 1 {SEARCH TABLE t2 USING INDEX i3 (c=?) (~10 rows)} + 0 1 0 {SCAN TABLE t1 (~1000000 rows)} +} # Parser tests. Test that an INDEXED BY or NOT INDEX clause can be # attached to a table in the FROM clause, but not to a sub-select or @@ -80,15 +83,17 @@ do_test indexedby-2.7 { # Tests for single table cases. # -do_test indexedby-3.1 { - EQP { SELECT * FROM t1 NOT INDEXED WHERE a = 'one' AND b = 'two'} -} {0 0 {TABLE t1}} -do_test indexedby-3.2 { - EQP { SELECT * FROM t1 INDEXED BY i1 WHERE a = 'one' AND b = 'two'} -} {0 0 {TABLE t1 WITH INDEX i1}} -do_test indexedby-3.3 { - EQP { SELECT * FROM t1 INDEXED BY i2 WHERE a = 'one' AND b = 'two'} -} {0 0 {TABLE t1 WITH INDEX i2}} +do_execsql_test indexedby-3.1 { + EXPLAIN QUERY PLAN SELECT * FROM t1 NOT INDEXED WHERE a = 'one' AND b = 'two' +} {0 0 0 {SCAN TABLE t1 (~10000 rows)}} +do_execsql_test indexedby-3.2 { + EXPLAIN QUERY PLAN + SELECT * FROM t1 INDEXED BY i1 WHERE a = 'one' AND b = 'two' +} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?) (~2 rows)}} +do_execsql_test indexedby-3.3 { + EXPLAIN QUERY PLAN + SELECT * FROM t1 INDEXED BY i2 WHERE a = 'one' AND b = 'two' +} {0 0 0 {SEARCH TABLE t1 USING INDEX i2 (b=?) (~2 rows)}} do_test indexedby-3.4 { catchsql { SELECT * FROM t1 INDEXED BY i2 WHERE a = 'one' } } {1 {cannot use index: i2}} @@ -102,12 +107,14 @@ do_test indexedby-3.7 { catchsql { SELECT * FROM t1 INDEXED BY i1 ORDER BY a } } {0 {}} -do_test indexedby-3.8 { - EQP { SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_1 ORDER BY e } -} {0 0 {TABLE t3 WITH INDEX sqlite_autoindex_t3_1 ORDER BY}} -do_test indexedby-3.9 { - EQP { SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_1 WHERE e = 10 } -} {0 0 {TABLE t3 WITH INDEX sqlite_autoindex_t3_1}} +do_execsql_test indexedby-3.8 { + EXPLAIN QUERY PLAN + SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_1 ORDER BY e +} {0 0 0 {SCAN TABLE t3 USING INDEX sqlite_autoindex_t3_1 (~1000000 rows)}} +do_execsql_test indexedby-3.9 { + EXPLAIN QUERY PLAN + SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_1 WHERE e = 10 +} {0 0 0 {SEARCH TABLE t3 USING INDEX sqlite_autoindex_t3_1 (e=?) (~1 rows)}} do_test indexedby-3.10 { catchsql { SELECT * FROM t3 INDEXED BY sqlite_autoindex_t3_1 WHERE f = 10 } } {1 {cannot use index: sqlite_autoindex_t3_1}} @@ -117,12 +124,18 @@ do_test indexedby-3.11 { # Tests for multiple table cases. # -do_test indexedby-4.1 { - EQP { SELECT * FROM t1, t2 WHERE a = c } -} {0 0 {TABLE t1} 1 1 {TABLE t2 WITH INDEX i3}} -do_test indexedby-4.2 { - EQP { SELECT * FROM t1 INDEXED BY i1, t2 WHERE a = c } -} {0 1 {TABLE t2} 1 0 {TABLE t1 WITH INDEX i1}} +do_execsql_test indexedby-4.1 { + EXPLAIN QUERY PLAN SELECT * FROM t1, t2 WHERE a = c +} { + 0 0 0 {SCAN TABLE t1 (~1000000 rows)} + 0 1 1 {SEARCH TABLE t2 USING INDEX i3 (c=?) (~10 rows)} +} +do_execsql_test indexedby-4.2 { + EXPLAIN QUERY PLAN SELECT * FROM t1 INDEXED BY i1, t2 WHERE a = c +} { + 0 0 1 {SCAN TABLE t2 (~1000000 rows)} + 0 1 0 {SEARCH TABLE t1 USING INDEX i1 (a=?) (~10 rows)} +} do_test indexedby-4.3 { catchsql { SELECT * FROM t1 INDEXED BY i1, t2 INDEXED BY i3 WHERE a=c @@ -138,15 +151,13 @@ do_test indexedby-4.4 { # also tests that nothing bad happens if an index refered to by # a CREATE VIEW statement is dropped and recreated. # -do_test indexedby-5.1 { - execsql { - CREATE VIEW v2 AS SELECT * FROM t1 INDEXED BY i1 WHERE a > 5; - } - EQP { SELECT * FROM v2 } -} {0 0 {TABLE t1 WITH INDEX i1}} -do_test indexedby-5.2 { - EQP { SELECT * FROM v2 WHERE b = 10 } -} {0 0 {TABLE t1 WITH INDEX i1}} +do_execsql_test indexedby-5.1 { + CREATE VIEW v2 AS SELECT * FROM t1 INDEXED BY i1 WHERE a > 5; + EXPLAIN QUERY PLAN SELECT * FROM v2 +} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a>?) (~330000 rows)}} +do_execsql_test indexedby-5.2 { + EXPLAIN QUERY PLAN SELECT * FROM v2 WHERE b = 10 +} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a>?) (~33000 rows)}} do_test indexedby-5.3 { execsql { DROP INDEX i1 } catchsql { SELECT * FROM v2 } @@ -165,51 +176,53 @@ do_test indexedby-5.5 { # Test that "NOT INDEXED" may use the rowid index, but not others. # -do_test indexedby-6.1 { - EQP { SELECT * FROM t1 WHERE b = 10 ORDER BY rowid } -} {0 0 {TABLE t1 WITH INDEX i2 ORDER BY}} -do_test indexedby-6.2 { - EQP { SELECT * FROM t1 NOT INDEXED WHERE b = 10 ORDER BY rowid } -} {0 0 {TABLE t1 USING PRIMARY KEY ORDER BY}} +do_execsql_test indexedby-6.1 { + EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE b = 10 ORDER BY rowid +} {0 0 0 {SEARCH TABLE t1 USING INDEX i2 (b=?) (~10 rows)}} +do_execsql_test indexedby-6.2 { + EXPLAIN QUERY PLAN SELECT * FROM t1 NOT INDEXED WHERE b = 10 ORDER BY rowid +} {0 0 0 {SCAN TABLE t1 USING INTEGER PRIMARY KEY (~100000 rows)}} # Test that "INDEXED BY" can be used in a DELETE statement. # -do_test indexedby-7.1 { - EQP { DELETE FROM t1 WHERE a = 5 } -} {0 0 {TABLE t1 WITH INDEX i1}} -do_test indexedby-7.2 { - EQP { DELETE FROM t1 NOT INDEXED WHERE a = 5 } -} {0 0 {TABLE t1}} -do_test indexedby-7.3 { - EQP { DELETE FROM t1 INDEXED BY i1 WHERE a = 5 } -} {0 0 {TABLE t1 WITH INDEX i1}} -do_test indexedby-7.4 { - EQP { DELETE FROM t1 INDEXED BY i1 WHERE a = 5 AND b = 10} -} {0 0 {TABLE t1 WITH INDEX i1}} -do_test indexedby-7.5 { - EQP { DELETE FROM t1 INDEXED BY i2 WHERE a = 5 AND b = 10} -} {0 0 {TABLE t1 WITH INDEX i2}} +do_execsql_test indexedby-7.1 { + EXPLAIN QUERY PLAN DELETE FROM t1 WHERE a = 5 +} {0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?) (~10 rows)}} +do_execsql_test indexedby-7.2 { + EXPLAIN QUERY PLAN DELETE FROM t1 NOT INDEXED WHERE a = 5 +} {0 0 0 {SCAN TABLE t1 (~100000 rows)}} +do_execsql_test indexedby-7.3 { + EXPLAIN QUERY PLAN DELETE FROM t1 INDEXED BY i1 WHERE a = 5 +} {0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?) (~10 rows)}} +do_execsql_test indexedby-7.4 { + EXPLAIN QUERY PLAN DELETE FROM t1 INDEXED BY i1 WHERE a = 5 AND b = 10 +} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?) (~2 rows)}} +do_execsql_test indexedby-7.5 { + EXPLAIN QUERY PLAN DELETE FROM t1 INDEXED BY i2 WHERE a = 5 AND b = 10 +} {0 0 0 {SEARCH TABLE t1 USING INDEX i2 (b=?) (~2 rows)}} do_test indexedby-7.6 { catchsql { DELETE FROM t1 INDEXED BY i2 WHERE a = 5} } {1 {cannot use index: i2}} # Test that "INDEXED BY" can be used in an UPDATE statement. # -do_test indexedby-8.1 { - EQP { UPDATE t1 SET rowid=rowid+1 WHERE a = 5 } -} {0 0 {TABLE t1 WITH INDEX i1}} -do_test indexedby-8.2 { - EQP { UPDATE t1 NOT INDEXED SET rowid=rowid+1 WHERE a = 5 } -} {0 0 {TABLE t1}} -do_test indexedby-8.3 { - EQP { UPDATE t1 INDEXED BY i1 SET rowid=rowid+1 WHERE a = 5 } -} {0 0 {TABLE t1 WITH INDEX i1}} -do_test indexedby-8.4 { - EQP { UPDATE t1 INDEXED BY i1 SET rowid=rowid+1 WHERE a = 5 AND b = 10} -} {0 0 {TABLE t1 WITH INDEX i1}} -do_test indexedby-8.5 { - EQP { UPDATE t1 INDEXED BY i2 SET rowid=rowid+1 WHERE a = 5 AND b = 10} -} {0 0 {TABLE t1 WITH INDEX i2}} +do_execsql_test indexedby-8.1 { + EXPLAIN QUERY PLAN UPDATE t1 SET rowid=rowid+1 WHERE a = 5 +} {0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?) (~10 rows)}} +do_execsql_test indexedby-8.2 { + EXPLAIN QUERY PLAN UPDATE t1 NOT INDEXED SET rowid=rowid+1 WHERE a = 5 +} {0 0 0 {SCAN TABLE t1 (~100000 rows)}} +do_execsql_test indexedby-8.3 { + EXPLAIN QUERY PLAN UPDATE t1 INDEXED BY i1 SET rowid=rowid+1 WHERE a = 5 +} {0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?) (~10 rows)}} +do_execsql_test indexedby-8.4 { + EXPLAIN QUERY PLAN + UPDATE t1 INDEXED BY i1 SET rowid=rowid+1 WHERE a = 5 AND b = 10 +} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?) (~2 rows)}} +do_execsql_test indexedby-8.5 { + EXPLAIN QUERY PLAN + UPDATE t1 INDEXED BY i2 SET rowid=rowid+1 WHERE a = 5 AND b = 10 +} {0 0 0 {SEARCH TABLE t1 USING INDEX i2 (b=?) (~2 rows)}} do_test indexedby-8.6 { catchsql { UPDATE t1 INDEXED BY i2 SET rowid=rowid+1 WHERE a = 5} } {1 {cannot use index: i2}} diff --git a/test/ioerr.test b/test/ioerr.test index 21052e41..f9b95555 100644 --- a/test/ioerr.test +++ b/test/ioerr.test @@ -135,8 +135,13 @@ ifcapable attach { # Test IO errors when replaying two hot journals from a 2-file # transaction. This test only runs on UNIX. +# +# It cannot be run under the "exclusive" permutation. In that case, the +# locks held by the connection in the local (this) process prevent a +# second connection from attempting the multi-file transaction. +# ifcapable crashtest&&attach { - if {![catch {sqlite3 -has_codec} r] && !$r} { + if {![catch {sqlite3 -has-codec} r] && !$r && [permutation]!="exclusive"} { do_ioerr_test ioerr-6 -ckrefcount true -tclprep { execsql { ATTACH 'test2.db' as aux; diff --git a/test/jrnlmode3.test b/test/jrnlmode3.test index 7a6f4c3a..6ae1346e 100644 --- a/test/jrnlmode3.test +++ b/test/jrnlmode3.test @@ -45,7 +45,7 @@ do_test jrnlmode3-1.2 { ROLLBACK; SELECT * FROM t1; } -} {1 2} +} {1} db close file delete -force test.db test.db-journal @@ -67,7 +67,7 @@ do_test jrnlmode3-2.2 { ROLLBACK; SELECT * FROM t1; } -} {1 2} +} {1} # Test cases to verify that we can move from any journal_mode # to any other, as long as we are not in a transaction. Verify @@ -112,15 +112,14 @@ foreach fromjmode $all_journal_modes { db eval "PRAGMA journal_mode=$tojmode" } $fromjmode - # Rollback the transaction. Verify that the rollback occurred - # if journal_mode!=OFF. + # Rollback the transaction. # do_test jrnlmode3-3.$cnt.4 { db eval { ROLLBACK; SELECT * FROM t1; } - } [expr {$fromjmode=="off"?$cnt:""}] + } {} # Now change the journal mode again. This time the new mode # should take. @@ -143,7 +142,7 @@ foreach fromjmode $all_journal_modes { db eval { SELECT * FROM t1; } - } [expr {$tojmode=="off"?"1":""}] + } {} } } diff --git a/test/like.test b/test/like.test index 229f3868..e60d89bd 100644 --- a/test/like.test +++ b/test/like.test @@ -115,7 +115,7 @@ do_test like-2.1 { proc test_regexp {a b} { return [regexp $a $b] } - db function regexp test_regexp + db function regexp -argcount 2 test_regexp execsql { SELECT x FROM t1 WHERE x REGEXP 'abc' ORDER BY 1; } @@ -608,7 +608,7 @@ do_test like-8.4 { } {1 abcdef 1 ghijkl 1 mnopqr 2 abcdef 2 ghijkl 2 mnopqr} -ifcapable like_opt { +ifcapable like_opt&&!icu { # Evaluate SQL. Return the result set followed by the # and the number of full-scan steps. # @@ -673,110 +673,110 @@ ifcapable like_opt { regexp {INDEX i2} $res } {1} } -} -# Do an SQL statement. Append the search count to the end of the result. -# -proc count sql { - set ::sqlite_search_count 0 - set ::sqlite_like_count 0 - return [concat [execsql $sql] scan $::sqlite_search_count \ - like $::sqlite_like_count] -} + # Do an SQL statement. Append the search count to the end of the result. + # + proc count sql { + set ::sqlite_search_count 0 + set ::sqlite_like_count 0 + return [concat [execsql $sql] scan $::sqlite_search_count \ + like $::sqlite_like_count] + } -# The LIKE and GLOB optimizations do not work on columns with -# affinity other than TEXT. -# Ticket #3901 -# -do_test like-10.1 { - db close - sqlite3 db test.db - execsql { - CREATE TABLE t10( - a INTEGER PRIMARY KEY, - b INTEGER COLLATE nocase UNIQUE, - c NUMBER COLLATE nocase UNIQUE, - d BLOB COLLATE nocase UNIQUE, - e COLLATE nocase UNIQUE, - f TEXT COLLATE nocase UNIQUE - ); - INSERT INTO t10 VALUES(1,1,1,1,1,1); - INSERT INTO t10 VALUES(12,12,12,12,12,12); - INSERT INTO t10 VALUES(123,123,123,123,123,123); - INSERT INTO t10 VALUES(234,234,234,234,234,234); - INSERT INTO t10 VALUES(345,345,345,345,345,345); - INSERT INTO t10 VALUES(45,45,45,45,45,45); - } - count { - SELECT a FROM t10 WHERE b LIKE '12%' ORDER BY a; - } -} {12 123 scan 5 like 6} -do_test like-10.2 { - count { - SELECT a FROM t10 WHERE c LIKE '12%' ORDER BY a; - } -} {12 123 scan 5 like 6} -do_test like-10.3 { - count { - SELECT a FROM t10 WHERE d LIKE '12%' ORDER BY a; - } -} {12 123 scan 5 like 6} -do_test like-10.4 { - count { - SELECT a FROM t10 WHERE e LIKE '12%' ORDER BY a; - } -} {12 123 scan 5 like 6} -do_test like-10.5 { - count { - SELECT a FROM t10 WHERE f LIKE '12%' ORDER BY a; - } -} {12 123 scan 3 like 0} -do_test like-10.6 { - count { - SELECT a FROM t10 WHERE a LIKE '12%' ORDER BY a; - } -} {12 123 scan 5 like 6} -do_test like-10.10 { - execsql { - CREATE TABLE t10b( - a INTEGER PRIMARY KEY, - b INTEGER UNIQUE, - c NUMBER UNIQUE, - d BLOB UNIQUE, - e UNIQUE, - f TEXT UNIQUE - ); - INSERT INTO t10b SELECT * FROM t10; - } - count { - SELECT a FROM t10b WHERE b GLOB '12*' ORDER BY a; - } -} {12 123 scan 5 like 6} -do_test like-10.11 { - count { - SELECT a FROM t10b WHERE c GLOB '12*' ORDER BY a; - } -} {12 123 scan 5 like 6} -do_test like-10.12 { - count { - SELECT a FROM t10b WHERE d GLOB '12*' ORDER BY a; - } -} {12 123 scan 5 like 6} -do_test like-10.13 { - count { - SELECT a FROM t10b WHERE e GLOB '12*' ORDER BY a; - } -} {12 123 scan 5 like 6} -do_test like-10.14 { - count { - SELECT a FROM t10b WHERE f GLOB '12*' ORDER BY a; - } -} {12 123 scan 3 like 0} -do_test like-10.15 { - count { - SELECT a FROM t10b WHERE a GLOB '12*' ORDER BY a; - } -} {12 123 scan 5 like 6} + # The LIKE and GLOB optimizations do not work on columns with + # affinity other than TEXT. + # Ticket #3901 + # + do_test like-10.1 { + db close + sqlite3 db test.db + execsql { + CREATE TABLE t10( + a INTEGER PRIMARY KEY, + b INTEGER COLLATE nocase UNIQUE, + c NUMBER COLLATE nocase UNIQUE, + d BLOB COLLATE nocase UNIQUE, + e COLLATE nocase UNIQUE, + f TEXT COLLATE nocase UNIQUE + ); + INSERT INTO t10 VALUES(1,1,1,1,1,1); + INSERT INTO t10 VALUES(12,12,12,12,12,12); + INSERT INTO t10 VALUES(123,123,123,123,123,123); + INSERT INTO t10 VALUES(234,234,234,234,234,234); + INSERT INTO t10 VALUES(345,345,345,345,345,345); + INSERT INTO t10 VALUES(45,45,45,45,45,45); + } + count { + SELECT a FROM t10 WHERE b LIKE '12%' ORDER BY a; + } + } {12 123 scan 5 like 6} + do_test like-10.2 { + count { + SELECT a FROM t10 WHERE c LIKE '12%' ORDER BY a; + } + } {12 123 scan 5 like 6} + do_test like-10.3 { + count { + SELECT a FROM t10 WHERE d LIKE '12%' ORDER BY a; + } + } {12 123 scan 5 like 6} + do_test like-10.4 { + count { + SELECT a FROM t10 WHERE e LIKE '12%' ORDER BY a; + } + } {12 123 scan 5 like 6} + do_test like-10.5 { + count { + SELECT a FROM t10 WHERE f LIKE '12%' ORDER BY a; + } + } {12 123 scan 3 like 0} + do_test like-10.6 { + count { + SELECT a FROM t10 WHERE a LIKE '12%' ORDER BY a; + } + } {12 123 scan 5 like 6} + do_test like-10.10 { + execsql { + CREATE TABLE t10b( + a INTEGER PRIMARY KEY, + b INTEGER UNIQUE, + c NUMBER UNIQUE, + d BLOB UNIQUE, + e UNIQUE, + f TEXT UNIQUE + ); + INSERT INTO t10b SELECT * FROM t10; + } + count { + SELECT a FROM t10b WHERE b GLOB '12*' ORDER BY a; + } + } {12 123 scan 5 like 6} + do_test like-10.11 { + count { + SELECT a FROM t10b WHERE c GLOB '12*' ORDER BY a; + } + } {12 123 scan 5 like 6} + do_test like-10.12 { + count { + SELECT a FROM t10b WHERE d GLOB '12*' ORDER BY a; + } + } {12 123 scan 5 like 6} + do_test like-10.13 { + count { + SELECT a FROM t10b WHERE e GLOB '12*' ORDER BY a; + } + } {12 123 scan 5 like 6} + do_test like-10.14 { + count { + SELECT a FROM t10b WHERE f GLOB '12*' ORDER BY a; + } + } {12 123 scan 3 like 0} + do_test like-10.15 { + count { + SELECT a FROM t10b WHERE a GLOB '12*' ORDER BY a; + } + } {12 123 scan 5 like 6} +} # LIKE and GLOB where the default collating sequence is not appropriate # but an index with the appropriate collating sequence exists. diff --git a/test/lock.test b/test/lock.test index 82da4b0d..22f359c1 100644 --- a/test/lock.test +++ b/test/lock.test @@ -398,7 +398,7 @@ do_test lock-6.5 { # * a transaction that modified zero database pages is committed. # set temp_status unlocked -if {$TEMP_STORE==3} {set temp_status unknown} +if {$TEMP_STORE>=2} {set temp_status unknown} do_test lock-7.1 { set STMT [sqlite3_prepare $DB "SELECT * FROM sqlite_master" -1 TAIL] sqlite3_step $STMT diff --git a/test/lock6.test b/test/lock6.test index 84f88881..b47d18f7 100644 --- a/test/lock6.test +++ b/test/lock6.test @@ -112,10 +112,10 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { sqlite3_soft_heap_limit 0 do_test lock6-1.3 { - sqlite3 db test.db - catchsql { - select * from sqlite_master; - } + list [catch { + sqlite3 db test.db + execsql { select * from sqlite_master } + } msg] $msg } {1 {database is locked}} do_test lock6-1.4 { diff --git a/test/lock_common.tcl b/test/lock_common.tcl index 0b7c5f92..21f58426 100644 --- a/test/lock_common.tcl +++ b/test/lock_common.tcl @@ -16,6 +16,7 @@ proc do_multiclient_test {varname script} { foreach code [list { + if {[info exists ::G(valgrind)]} { db close ; continue } set ::code2_chan [launch_testfixture] set ::code3_chan [launch_testfixture] proc code2 {tcl} { testfixture $::code2_chan $tcl } @@ -58,6 +59,7 @@ proc do_multiclient_test {varname script} { code3 { db3 close } catch { close $::code2_chan } catch { close $::code3_chan } + catch { db close } } } diff --git a/test/lookaside.test b/test/lookaside.test index bf554941..0b239a04 100644 --- a/test/lookaside.test +++ b/test/lookaside.test @@ -46,36 +46,48 @@ do_test lookaside-1.1 { do_test lookaside-1.2 { sqlite3_db_config_lookaside db 1 18 18 } {0} -do_test lookaside-1.3 { - sqlite3_db_status db SQLITE_DBSTATUS_LOOKASIDE_USED 0 +do_test lookaside-1.3.1 { + sqlite3_db_status db DBSTATUS_LOOKASIDE_USED 0 +} {0 0 0} +do_test lookaside-1.3.2 { + sqlite3_db_status db DBSTATUS_LOOKASIDE_HIT 0 +} {0 0 0} +do_test lookaside-1.3.3 { + sqlite3_db_status db DBSTATUS_LOOKASIDE_MISS_SIZE 0 +} {0 0 0} +do_test lookaside-1.3.4 { + sqlite3_db_status db DBSTATUS_LOOKASIDE_MISS_FULL 0 } {0 0 0} do_test lookaside-1.4 { db eval {CREATE TABLE t1(w,x,y,z);} - foreach {x y z} [sqlite3_db_status db SQLITE_DBSTATUS_LOOKASIDE_USED 0] break - expr {$x==0 && $y<$z && $z==18} + foreach {x y z} [sqlite3_db_status db DBSTATUS_LOOKASIDE_USED 0] break + set p [lindex [sqlite3_db_status db DBSTATUS_LOOKASIDE_HIT 0] 2] + set q [lindex [sqlite3_db_status db DBSTATUS_LOOKASIDE_MISS_SIZE 0] 2] + set r [lindex [sqlite3_db_status db DBSTATUS_LOOKASIDE_MISS_FULL 0] 2] + expr {$x==0 && $y<$z && $z==18 && $p>0 && $q>0 && $r>0} } {0} do_test lookaside-1.5 { - foreach {x y z} [sqlite3_db_status db SQLITE_DBSTATUS_LOOKASIDE_USED 1] break + foreach {x y z} [sqlite3_db_status db DBSTATUS_LOOKASIDE_USED 1] break expr {$x==0 && $y<$z && $z==18} } {0} do_test lookaside-1.6 { - foreach {x y z} [sqlite3_db_status db SQLITE_DBSTATUS_LOOKASIDE_USED 0] break + foreach {x y z} [sqlite3_db_status db DBSTATUS_LOOKASIDE_USED 0] break expr {$x==0 && $y==$z && $y<18} } {1} do_test lookaside-1.7 { db cache flush - foreach {x y z} [sqlite3_db_status db SQLITE_DBSTATUS_LOOKASIDE_USED 0] break + foreach {x y z} [sqlite3_db_status db DBSTATUS_LOOKASIDE_USED 0] break expr {$x==0 && $y==0 && $z<18} } {1} do_test lookaside-1.8 { db cache flush - foreach {x y z} [sqlite3_db_status db SQLITE_DBSTATUS_LOOKASIDE_USED 1] break + foreach {x y z} [sqlite3_db_status db DBSTATUS_LOOKASIDE_USED 1] break expr {$x==0 && $y==0 && $z<18} } {1} do_test lookaside-1.9 { db cache flush - sqlite3_db_status db SQLITE_DBSTATUS_LOOKASIDE_USED 0 + sqlite3_db_status db DBSTATUS_LOOKASIDE_USED 0 } {0 0 0} do_test lookaside-2.1 { @@ -83,7 +95,7 @@ do_test lookaside-2.1 { } {0} do_test lookaside-2.2 { db eval {CREATE TABLE t2(x);} - foreach {x y z} [sqlite3_db_status db SQLITE_DBSTATUS_LOOKASIDE_USED 0] break + foreach {x y z} [sqlite3_db_status db DBSTATUS_LOOKASIDE_USED 0] break expr {$x==0 && $y<$z && $z>10 && $z<100} } {1} do_test lookaside-2.3 { diff --git a/test/malloc3.test b/test/malloc3.test index 0046798e..73d2d396 100644 --- a/test/malloc3.test +++ b/test/malloc3.test @@ -655,7 +655,7 @@ sqlite3 db test.db sqlite3_extended_result_codes db 1 set ::DB [sqlite3_connection_pointer db] -# Turn of the Tcl interface's prepared statement caching facility in +# Turn off the Tcl interface's prepared statement caching facility in # the new connnection. Then run the tests with "transient" malloc failures. db cache size 0 run_test $::run_test_script 0 diff --git a/test/malloc_common.tcl b/test/malloc_common.tcl index ef5deac6..379b9707 100644 --- a/test/malloc_common.tcl +++ b/test/malloc_common.tcl @@ -106,6 +106,10 @@ set FAULTSIM(cantopen-persistent) [list \ # # -test Script to execute after -body. # +# -install Script to execute after faultsim -injectinstall +# +# -uninstall Script to execute after faultsim -uninjectinstall +# proc do_faultsim_test {name args} { global FAULTSIM @@ -113,6 +117,10 @@ proc do_faultsim_test {name args} { set DEFAULT(-prep) "" set DEFAULT(-body) "" set DEFAULT(-test) "" + set DEFAULT(-install) "" + set DEFAULT(-uninstall) "" + + fix_testname name array set O [array get DEFAULT] array set O $args @@ -127,12 +135,15 @@ proc do_faultsim_test {name args} { set faultlist [concat $faultlist $flist] } - set testspec [list -prep $O(-prep) -body $O(-body) -test $O(-test)] + set testspec [list -prep $O(-prep) -body $O(-body) \ + -test $O(-test) -install $O(-install) -uninstall $O(-uninstall) + ] foreach f [lsort -unique $faultlist] { eval do_one_faultsim_test "$name-$f" $FAULTSIM($f) $testspec } } + #------------------------------------------------------------------------- # Procedures to save and restore the current file-system state: # @@ -142,29 +153,16 @@ proc do_faultsim_test {name args} { # faultsim_restore_and_reopen # faultsim_delete_and_reopen # -proc faultsim_save {} { - foreach f [glob -nocomplain sv_test.db*] { file delete -force $f } - foreach f [glob -nocomplain test.db*] { - set f2 "sv_$f" - file copy -force $f $f2 - } +proc faultsim_save {args} { uplevel db_save $args } +proc faultsim_save_and_close {args} { uplevel db_save_and_close $args } +proc faultsim_restore {args} { uplevel db_restore $args } +proc faultsim_restore_and_reopen {args} { + uplevel db_restore_and_reopen $args + sqlite3_extended_result_codes db 1 + sqlite3_db_config_lookaside db 0 0 0 } -proc faultsim_save_and_close {} { - faultsim_save - catch { db close } - return "" -} -proc faultsim_restore {} { - foreach f [glob -nocomplain test.db*] { file delete -force $f } - foreach f2 [glob -nocomplain sv_test.db*] { - set f [string range $f2 3 end] - file copy -force $f2 $f - } -} -proc faultsim_restore_and_reopen {{dbfile test.db}} { - catch { db close } - faultsim_restore - sqlite3 db $dbfile +proc faultsim_delete_and_reopen {args} { + uplevel db_delete_and_reopen $args sqlite3_extended_result_codes db 1 sqlite3_db_config_lookaside db 0 0 0 } @@ -174,18 +172,12 @@ proc faultsim_integrity_check {{db db}} { if {$ic != "ok"} { error "Integrity check: $ic" } } -proc faultsim_delete_and_reopen {{file test.db}} { - catch { db close } - foreach f [glob -nocomplain test.db*] { file delete -force $f } - sqlite3 db $file -} - # The following procs are used as [do_one_faultsim_test] callbacks when # injecting OOM faults into test cases. # proc oom_injectstart {nRepeat iFail} { - sqlite3_memdebug_fail $iFail -repeat $nRepeat + sqlite3_memdebug_fail [expr $iFail-1] -repeat $nRepeat } proc oom_injectstop {} { sqlite3_memdebug_fail -1 @@ -310,6 +302,8 @@ proc do_one_faultsim_test {testname args} { set DEFAULT(-prep) "" set DEFAULT(-body) "" set DEFAULT(-test) "" + set DEFAULT(-install) "" + set DEFAULT(-uninstall) "" array set O [array get DEFAULT] array set O $args @@ -323,6 +317,7 @@ proc do_one_faultsim_test {testname args} { " eval $O(-injectinstall) + eval $O(-install) set stop 0 for {set iFail 1} {!$stop} {incr iFail} { @@ -354,6 +349,7 @@ proc do_one_faultsim_test {testname args} { if {$nfail==0} { set stop 1 } } + eval $O(-uninstall) eval $O(-injectuninstall) } @@ -422,12 +418,12 @@ proc do_malloc_test {tn args} { # catch {db close} catch {db2 close} - catch {file delete -force test.db} - catch {file delete -force test.db-journal} - catch {file delete -force test.db-wal} - catch {file delete -force test2.db} - catch {file delete -force test2.db-journal} - catch {file delete -force test2.db-wal} + forcedelete test.db + forcedelete test.db-journal + forcedelete test.db-wal + forcedelete test2.db + forcedelete test2.db-journal + forcedelete test2.db-wal if {[info exists ::mallocopts(-testdb)]} { file copy $::mallocopts(-testdb) test.db } @@ -526,7 +522,7 @@ proc do_malloc_test {tn args} { # match the expected results passed via parameter $result. # proc do_select_test {name sql result} { - uplevel [list doPassiveTest 0 $name $sql [list 0 $result]] + uplevel [list doPassiveTest 0 $name $sql [list 0 [list {*}$result]]] } proc do_restart_select_test {name sql result} { @@ -540,6 +536,12 @@ proc do_error_test {name sql error} { proc doPassiveTest {isRestart name sql catchres} { if {![info exists ::DO_MALLOC_TEST]} { set ::DO_MALLOC_TEST 1 } + if {[info exists ::testprefix] + && [string is integer [string range $name 0 0]] + } { + set name $::testprefix.$name + } + switch $::DO_MALLOC_TEST { 0 { # No malloc failures. do_test $name [list set {} [uplevel [list catchsql $sql]]] $catchres diff --git a/test/memsubsys1.test b/test/memsubsys1.test index 918b2864..3bb2cc71 100644 --- a/test/memsubsys1.test +++ b/test/memsubsys1.test @@ -11,7 +11,6 @@ # # This file contains tests of the memory allocation subsystem # -# $Id: memsubsys1.test,v 1.17 2009/07/18 14:36:24 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -97,17 +96,13 @@ sqlite3_initialize reset_highwater_marks build_test_db memsubsys1-2 {PRAGMA page_size=1024} #show_memstats +set MEMORY_MANAGEMENT $sqlite_options(memorymanage) do_test memsubsys1-2.3 { set pg_ovfl [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_OVERFLOW 0] 2] - set pg_used [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_USED 0] 2] - expr { - ($pg_used*1024 + $pg_ovfl) < $max_pagecache && - ($pg_used*(1024+$xtra_size) + $pg_ovfl) >= $max_pagecache - } -} 1 +} [expr ($TEMP_STORE>1 || $MEMORY_MANAGEMENT==0)*1024] do_test memsubsys1-2.4 { set pg_used [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_USED 0] 2] -} 19 +} 20 do_test memsubsys1-2.5 { set s_used [lindex [sqlite3_status SQLITE_STATUS_SCRATCH_USED 0] 2] } 0 @@ -143,7 +138,7 @@ do_test memsubsys1-3.2.3 { } 2048 do_test memsubsys1-3.2.4 { set pg_used [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_USED 0] 2] -} 19 +} 20 do_test memsubsys1-3.2.5 { set s_used [lindex [sqlite3_status SQLITE_STATUS_SCRATCH_USED 0] 2] } 0 @@ -160,15 +155,11 @@ build_test_db memsubsys1-4 {PRAGMA page_size=1024} #show_memstats do_test memsubsys1-4.3 { set pg_used [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_USED 0] 2] -} 49 + expr {$pg_used>=45 && $pg_used<=50} +} 1 do_test memsubsys1-4.4 { set pg_ovfl [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_OVERFLOW 0] 2] - set pg_used [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_USED 0] 2] - expr { - ($pg_used*1024 + $pg_ovfl) < $max_pagecache && - ($pg_used*(1024+$xtra_size) + $pg_ovfl) >= $max_pagecache - } -} 1 +} 0 do_test memsubsys1-4.5 { set maxreq [lindex [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] 2] expr {$maxreq<7000} @@ -190,7 +181,7 @@ build_test_db memsubsys1-5 {PRAGMA page_size=4096} #show_memstats do_test memsubsys1-5.3 { set pg_used [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_USED 0] 2] -} 23 +} 24 do_test memsubsys1-5.4 { set maxreq [lindex [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] 2] expr {$maxreq>4096} @@ -216,11 +207,11 @@ build_test_db memsubsys1-6 {PRAGMA page_size=4096} #show_memstats do_test memsubsys1-6.3 { set pg_used [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_USED 0] 2] -} 23 -do_test memsubsys1-6.4 { - set maxreq [lindex [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] 2] - expr {$maxreq>4096 && $maxreq<=(4096+$xtra_size)} -} 1 +} 24 +#do_test memsubsys1-6.4 { +# set maxreq [lindex [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] 2] +# expr {$maxreq>4096 && $maxreq<=(4096+$xtra_size)} +#} 1 do_test memsubsys1-6.5 { set s_used [lindex [sqlite3_status SQLITE_STATUS_SCRATCH_USED 0] 2] } 1 diff --git a/test/misc4.test b/test/misc4.test index 026dd032..59c1d118 100644 --- a/test/misc4.test +++ b/test/misc4.test @@ -151,7 +151,7 @@ ifcapable subquery { select a.*, x.* from a, (select key,sum(period) from b group by key) as x - where a.key=x.key; + where a.key=x.key order by 1 desc; } } {01 data01 01 3 +1 data+1 +1 7} diff --git a/test/misc7.test b/test/misc7.test index c08ceefa..7d2ba6e8 100644 --- a/test/misc7.test +++ b/test/misc7.test @@ -259,24 +259,22 @@ file delete -force test.db-journal sqlite3 db test.db ifcapable explain { - do_test misc7-14.1 { - execsql { - CREATE TABLE abc(a PRIMARY KEY, b, c); - } - execsql { - EXPLAIN QUERY PLAN SELECT * FROM abc AS t2 WHERE rowid = 1; - } - } {0 0 {TABLE abc AS t2 USING PRIMARY KEY}} - do_test misc7-14.2 { - execsql { - EXPLAIN QUERY PLAN SELECT * FROM abc AS t2 WHERE a = 1; - } - } {0 0 {TABLE abc AS t2 WITH INDEX sqlite_autoindex_abc_1}} - do_test misc7-14.3 { - execsql { - EXPLAIN QUERY PLAN SELECT * FROM abc AS t2 ORDER BY a; - } - } {0 0 {TABLE abc AS t2 WITH INDEX sqlite_autoindex_abc_1 ORDER BY}} + do_execsql_test misc7-14.1 { + CREATE TABLE abc(a PRIMARY KEY, b, c); + EXPLAIN QUERY PLAN SELECT * FROM abc AS t2 WHERE rowid = 1; + } { + 0 0 0 {SEARCH TABLE abc AS t2 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} + } + do_execsql_test misc7-14.2 { + EXPLAIN QUERY PLAN SELECT * FROM abc AS t2 WHERE a = 1; + } {0 0 0 + {SEARCH TABLE abc AS t2 USING INDEX sqlite_autoindex_abc_1 (a=?) (~1 rows)} + } + do_execsql_test misc7-14.3 { + EXPLAIN QUERY PLAN SELECT * FROM abc AS t2 ORDER BY a; + } {0 0 0 + {SCAN TABLE abc AS t2 USING INDEX sqlite_autoindex_abc_1 (~1000000 rows)} + } } db close diff --git a/test/multiplex.test b/test/multiplex.test new file mode 100644 index 00000000..742ca507 --- /dev/null +++ b/test/multiplex.test @@ -0,0 +1,499 @@ +# 2010 October 29 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl + +set g_chunk_size 2147483648 +set g_max_chunks 32 + +# This handles appending the chunk number +# to the end of the filename. if +# SQLITE_MULTIPLEX_EXT_OVWR is defined, then +# it overwrites the last 2 bytes of the +# file name with the chunk number. +proc multiplex_name {name chunk} { + if {$chunk==0} { return $name } + set num [format "%02d" $chunk] + ifcapable {multiplex_ext_overwrite} { + set name [string range $name 0 [expr [string length $name]-2-1]] + } + return $name$num +} + +# This saves off the parameters and calls the +# underlying sqlite3_multiplex_set() API. +proc multiplex_set {chunk_size max_chunks} { + global g_chunk_size + global g_max_chunks + set g_chunk_size $chunk_size + set g_max_chunks $max_chunks + sqlite3_multiplex_set $chunk_size $max_chunks +} + +# This attempts to delete the base file and +# and files with the chunk extension. +proc multiplex_delete {name} { + global g_max_chunks + for {set i 0} {$i<$g_max_chunks} {incr i} { + forcedelete [multiplex_name $name $i] + forcedelete [multiplex_name $name-journal $i] + forcedelete [multiplex_name $name-wal $i] + } +} + +db close + +multiplex_delete test.db +multiplex_delete test2.db + +#------------------------------------------------------------------------- +# multiplex-1.1.*: Test initialize and shutdown. + +do_test multiplex-1.1 { sqlite3_multiplex_initialize nosuchvfs 1 } {SQLITE_ERROR} +do_test multiplex-1.2 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} +do_test multiplex-1.3 { sqlite3_multiplex_initialize "" 1 } {SQLITE_MISUSE} +do_test multiplex-1.4 { sqlite3_multiplex_shutdown } {SQLITE_OK} + +do_test multiplex-1.5 { sqlite3_multiplex_initialize "" 0 } {SQLITE_OK} +do_test multiplex-1.6 { sqlite3_multiplex_shutdown } {SQLITE_OK} +do_test multiplex-1.7 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} +do_test multiplex-1.8 { sqlite3_multiplex_shutdown } {SQLITE_OK} + +do_test multiplex-1.9 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} +do_test multiplex-1.10.1 { multiplex_set 32768 16 } {SQLITE_OK} +do_test multiplex-1.10.2 { multiplex_set 32768 -1 } {SQLITE_MISUSE} +do_test multiplex-1.10.3 { multiplex_set -1 16 } {SQLITE_MISUSE} +do_test multiplex-1.10.4 { multiplex_set 31 16 } {SQLITE_MISUSE} +do_test multiplex-1.10.5 { multiplex_set 32768 100 } {SQLITE_MISUSE} +do_test multiplex-1.11 { sqlite3_multiplex_shutdown } {SQLITE_OK} + + +#------------------------------------------------------------------------- +# Some simple warm-body tests with a single database file in rollback +# mode: +# +# multiplex-2.1.*: Test simple writing to a multiplex file. +# +# multiplex-2.2.*: More writing. +# +# multiplex-2.3.*: Open and close a second db. +# +# multiplex-2.4.*: Try to shutdown the multiplex system before closing the db +# file. Check that this fails and the multiplex system still works +# afterwards. Then close the database and successfully shut +# down the multiplex system. +# +# multiplex-2.5.*: More reading/writing. +# +# multiplex-2.6.*: More reading/writing with varying small chunk sizes, as +# well as varying journal mode. + +sqlite3_multiplex_initialize "" 1 +multiplex_set 32768 16 + +do_test multiplex-2.1.2 { + sqlite3 db test.db + execsql { + PRAGMA page_size=1024; + PRAGMA auto_vacuum=OFF; + PRAGMA journal_mode=DELETE; + } + execsql { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, randomblob(1100)); + INSERT INTO t1 VALUES(2, randomblob(1100)); + } +} {} +do_test multiplex-2.1.3 { file size [multiplex_name test.db 0] } {4096} +do_test multiplex-2.1.4 { + execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } +} {} + +do_test multiplex-2.2.1 { + execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } +} {} +do_test multiplex-2.2.3 { file size [multiplex_name test.db 0] } {6144} + +do_test multiplex-2.3.1 { + sqlite3 db2 test2.db + db2 close +} {} + +do_test multiplex-2.4.1 { + sqlite3_multiplex_shutdown +} {SQLITE_MISUSE} +do_test multiplex-2.4.2 { + execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } +} {} +do_test multiplex-2.4.4 { file size [multiplex_name test.db 0] } {7168} +do_test multiplex-2.4.99 { + db close + sqlite3_multiplex_shutdown +} {SQLITE_OK} + + +do_test multiplex-2.5.1 { + multiplex_delete test.db + sqlite3_multiplex_initialize "" 1 + multiplex_set 4096 16 +} {SQLITE_OK} + +do_test multiplex-2.5.2 { + sqlite3 db test.db + execsql { + PRAGMA page_size = 1024; + PRAGMA journal_mode = delete; + PRAGMA auto_vacuum = off; + CREATE TABLE t1(a PRIMARY KEY, b); + } +} {delete} + +do_test multiplex-2.5.3 { + execsql { + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, randomblob(4000)); + INSERT INTO t1 VALUES(3, 'three'); + INSERT INTO t1 VALUES(4, randomblob(4000)); + INSERT INTO t1 VALUES(5, 'five') + } +} {} + +do_test multiplex-2.5.4 { + db eval {SELECT * FROM t1 WHERE a=1} +} {1 one} + +do_test multiplex-2.5.5 { + db eval {SELECT * FROM t1 WHERE a=3} +} {3 three} + +do_test multiplex-2.5.6 { + db eval {SELECT * FROM t1 WHERE a=5} +} {5 five} + +do_test multiplex-2.5.7 { + db eval {SELECT a,length(b) FROM t1 WHERE a=2} +} {2 4000} + +do_test multiplex-2.5.8 { + db eval {SELECT a,length(b) FROM t1 WHERE a=4} +} {4 4000} + +do_test multiplex-2.5.9 { file size [multiplex_name test.db 0] } [list $g_chunk_size] +do_test multiplex-2.5.10 { file size [multiplex_name test.db 1] } [list $g_chunk_size] + +do_test multiplex-2.5.99 { + db close + sqlite3_multiplex_shutdown +} {SQLITE_OK} + + +set all_journal_modes {delete persist truncate memory off} +foreach jmode $all_journal_modes { + for {set sz 151} {$sz<8000} {set sz [expr $sz+419]} { + + do_test multiplex-2.6.1.$sz.$jmode { + multiplex_delete test.db + sqlite3_multiplex_initialize "" 1 + multiplex_set $sz 32 + } {SQLITE_OK} + + do_test multiplex-2.6.2.$sz.$jmode { + sqlite3 db test.db + db eval { + PRAGMA page_size = 1024; + PRAGMA auto_vacuum = off; + } + db eval "PRAGMA journal_mode = $jmode;" + } $jmode + + do_test multiplex-2.6.3.$sz.$jmode { + execsql { + CREATE TABLE t1(a PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, randomblob($g_chunk_size)); + } + } {} + + do_test multiplex-2.6.4.$sz.$jmode { + db eval {SELECT b FROM t1 WHERE a=1} + } {one} + + do_test multiplex-2.6.5.$sz.$jmode { + db eval {SELECT length(b) FROM t1 WHERE a=2} + } [list $g_chunk_size] + + do_test multiplex-2.6.6.$sz.$jmode { file size [multiplex_name test.db 0] } [list $g_chunk_size] + + do_test multiplex-2.6.99.$sz.$jmode { + db close + sqlite3_multiplex_shutdown + } {SQLITE_OK} + + } +} + +#------------------------------------------------------------------------- +# Try some tests with more than one connection to a database file. Still +# in rollback mode. +# +# multiplex-3.1.*: Two connections to a single database file. +# +# multiplex-3.2.*: Two connections to each of several database files (that +# are in the same multiplex group). +# +do_test multiplex-3.1.1 { + multiplex_delete test.db + sqlite3_multiplex_initialize "" 1 + multiplex_set 32768 16 +} {SQLITE_OK} +do_test multiplex-3.1.2 { + sqlite3 db test.db + execsql { + PRAGMA page_size = 1024; + PRAGMA journal_mode = delete; + PRAGMA auto_vacuum = off; + CREATE TABLE t1(a PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, 'one'); + } + file size [multiplex_name test.db 0] +} {3072} +do_test multiplex-3.1.3 { + sqlite3 db2 test.db + execsql { CREATE TABLE t2(a, b) } db2 +} {} +do_test multiplex-3.1.4 { + execsql { CREATE TABLE t3(a, b) } +} {} +do_test multiplex-3.1.5 { + catchsql { CREATE TABLE t3(a, b) } +} {1 {table t3 already exists}} +do_test multiplex-3.1.6 { + db close + db2 close +} {} + +do_test multiplex-3.2.1a { + + multiplex_delete test.db + multiplex_delete test2.db + + sqlite3 db1a test.db + sqlite3 db2a test2.db + + foreach db {db1a db2a} { + execsql { + PRAGMA page_size = 1024; + PRAGMA journal_mode = delete; + PRAGMA auto_vacuum = off; + CREATE TABLE t1(a, b); + } $db + } + + list [file size [multiplex_name test.db 0]] [file size [multiplex_name test2.db 0]] +} {2048 2048} + +do_test multiplex-3.2.1b { + sqlite3 db1b test.db + sqlite3 db2b test2.db +} {} + +do_test multiplex-3.2.2 { execsql { INSERT INTO t1 VALUES('x', 'y') } db1a } {} +do_test multiplex-3.2.3 { execsql { INSERT INTO t1 VALUES('v', 'w') } db1b } {} +do_test multiplex-3.2.4 { execsql { INSERT INTO t1 VALUES('t', 'u') } db2a } {} +do_test multiplex-3.2.5 { execsql { INSERT INTO t1 VALUES('r', 's') } db2b } {} + +do_test multiplex-3.2.6 { + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1a +} {} +do_test multiplex-3.2.7 { + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1b +} {} +do_test multiplex-3.2.8 { + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2a +} {} +do_test multiplex-3.2.9 { + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2b +} {} + +do_test multiplex-3.3.1 { + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1a + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1b + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2a + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2b +} {} + +do_test multiplex-3.2.X { + foreach db {db1a db2a db2b db1b} { catch { $db close } } +} {} + +#------------------------------------------------------------------------- +# + +sqlite3_multiplex_initialize "" 1 +multiplex_set 32768 16 + +# Return a list of all currently defined multiplexs. +proc multiplex_list {} { + set allq {} + foreach q [sqlite3_multiplex_dump] { + lappend allq [lindex $q 0] + } + return [lsort $allq] +} + +do_test multiplex-4.1.6 { + multiplex_delete test2.db + sqlite3 db test2.db + db eval {CREATE TABLE t2(x); INSERT INTO t2 VALUES('tab-t2');} + set res [multiplex_list] + list [regexp {test2.db} $res] +} {1} +do_test multiplex-4.1.6a { + sqlite3 db2 test2.db + db2 eval {SELECT * FROM t2} +} {tab-t2} +do_test multiplex-4.1.7 { + execsql {INSERT INTO t2 VALUES(zeroblob(200000))} +} {} +do_test multiplex-4.1.8 { + sqlite3 db2 test2.db + db2 eval {SELECT count(*) FROM t2} +} {2} +do_test multiplex-4.1.8a { + db2 eval { DELETE FROM t2 WHERE x = 'tab-t2' } +} {} +do_test multiplex-4.1.8b { + sqlite3 db2 test2.db + db2 eval {SELECT count(*) FROM t2} +} {1} + + +do_test multiplex-4.1.9 { + execsql {INSERT INTO t2 VALUES(zeroblob(200000))} +} {} +do_test multiplex-4.1.10 { + set res [multiplex_list] + list [regexp {test2.db} $res] +} {1} +do_test multiplex-4.1.11 { + db2 close + set res [multiplex_list] + list [regexp {test2.db} $res] +} {1} +do_test multiplex-4.1.12 { + db close + multiplex_list +} {} + + +#------------------------------------------------------------------------- +# The following tests test that the multiplex VFS handles malloc and IO +# errors. +# + +sqlite3_multiplex_initialize "" 1 +multiplex_set 32768 16 + +do_faultsim_test multiplex-5.1 -prep { + catch {db close} +} -body { + sqlite3 db test2.db +} +do_faultsim_test multiplex-5.2 -prep { + catch {db close} +} -body { + sqlite3 db test.db +} + +catch { db close } +multiplex_delete test.db +multiplex_delete test2.db + +do_test multiplex-5.3.prep { + sqlite3 db test.db + execsql { + PRAGMA auto_vacuum = 1; + PRAGMA page_size = 1024; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(10, zeroblob(1200)); + } + faultsim_save_and_close +} {} +do_faultsim_test multiplex-5.3 -prep { + faultsim_restore_and_reopen +} -body { + execsql { DELETE FROM t1 } +} + +do_test multiplex-5.4.1 { + catch { db close } + multiplex_delete test.db + file mkdir test.db + list [catch { sqlite3 db test.db } msg] $msg +} {1 {unable to open database file}} +catch { file delete test.db } + +do_faultsim_test multiplex-5.5 -prep { + catch { sqlite3_multiplex_shutdown } +} -body { + sqlite3_multiplex_initialize "" 1 + multiplex_set 32768 16 +} + +# test that mismatch filesize is detected +# +# Do not run this test if $::G(perm:presql) is set. If it is set, then the +# expected IO error will occur within the Tcl [sqlite3] wrapper, not within +# the first SQL statement executed below. This breaks the test case. +# +if {0==[info exists ::G(perm:presql)] || $::G(perm:presql) == ""} { + set all_journal_modes {delete persist truncate memory off} + foreach jmode $all_journal_modes { + do_test multiplex-5.6.1.$jmode { + sqlite3_multiplex_shutdown + multiplex_delete test.db + sqlite3 db test.db + db eval { + PRAGMA page_size = 1024; + PRAGMA auto_vacuum = off; + } + db eval "PRAGMA journal_mode = $jmode;" + } $jmode + do_test multiplex-5.6.2.$jmode { + execsql { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, randomblob(1100)); + INSERT INTO t1 VALUES(2, randomblob(1100)); + INSERT INTO t1 VALUES(3, randomblob(1100)); + INSERT INTO t1 VALUES(4, randomblob(1100)); + INSERT INTO t1 VALUES(5, randomblob(1100)); + } + db close + sqlite3_multiplex_initialize "" 1 + multiplex_set 4096 16 + sqlite3 db test.db + } {} + do_test multiplex-5.6.3.$jmode { + catchsql { + INSERT INTO t1 VALUES(6, randomblob(1100)); + } + } {1 {disk I/O error}} + do_test multiplex-5.6.4.$jmode { + db close + } {} + } +} + +catch { sqlite3_multiplex_shutdown } +finish_test diff --git a/test/mutex1.test b/test/mutex1.test index ad6bd147..4bdf769a 100644 --- a/test/mutex1.test +++ b/test/mutex1.test @@ -101,8 +101,14 @@ ifcapable threadsafe&&shared_cache { set enable_shared_cache [sqlite3_enable_shared_cache 1] foreach {mode mutexes} { singlethread {} - multithread {fast static_lru static_master static_mem static_open static_prng } - serialized {fast recursive static_lru static_master static_mem static_open static_prng} + multithread { + fast static_lru static_master static_mem static_open static_prng + static_pmem + } + serialized { + fast recursive static_lru static_master static_mem static_open + static_prng static_pmem + } } { do_test mutex1.2.$mode.1 { @@ -120,7 +126,9 @@ ifcapable threadsafe&&shared_cache { INSERT INTO abc VALUES(1, 2, 3); } } {} - + ifcapable !memorymanage { + regsub { static_lru} $mutexes {} mutexes + } do_test mutex1.2.$mode.3 { mutex_counters counters diff --git a/test/pager1.test b/test/pager1.test index 4eab17cf..481ef4a0 100644 --- a/test/pager1.test +++ b/test/pager1.test @@ -1062,7 +1062,7 @@ do_execsql_test pager1-6.8 { } {11} do_execsql_test pager1-6.9 { COMMIT } {} -do_execsql_test pager1-6.10 { PRAGMA max_page_count = 10 } {10} +do_execsql_test pager1-6.10 { PRAGMA max_page_count = 10 } {11} do_execsql_test pager1-6.11 { SELECT * FROM t11 } {1 2 3 4} do_execsql_test pager1-6.12 { PRAGMA max_page_count } {11} @@ -1107,10 +1107,10 @@ ifcapable wal { COMMIT; } {1 2} 0 -1 - 4 { PRAGMA journal_mode = WAL } wal -1 -1 - 5 { INSERT INTO t1 VALUES(3, 4) } {} -1 [wal_file_size 1 1024] - 6 { PRAGMA locking_mode = NORMAL } normal -1 [wal_file_size 1 1024] - 7 { INSERT INTO t1 VALUES(5, 6); } {} -1 [wal_file_size 2 1024] + 4 { PRAGMA journal_mode = WAL } wal -1 -1 + 5 { INSERT INTO t1 VALUES(3, 4) } {} -1 [wal_file_size 1 1024] + 6 { PRAGMA locking_mode = NORMAL } exclusive -1 [wal_file_size 1 1024] + 7 { INSERT INTO t1 VALUES(5, 6); } {} -1 [wal_file_size 2 1024] 8 { PRAGMA journal_mode = TRUNCATE } truncate 0 -1 9 { INSERT INTO t1 VALUES(7, 8) } {} 0 -1 @@ -1614,7 +1614,7 @@ do_catchsql_test pager1-14.1.2 { } {0 {}} do_execsql_test pager1-14.1.3 { SELECT * FROM t1; -} {1 2 3 4} +} {1 2} do_catchsql_test pager1-14.1.4 { BEGIN; INSERT INTO t1(rowid, a, b) SELECT a+3, b, b FROM t1; @@ -1623,7 +1623,7 @@ do_catchsql_test pager1-14.1.4 { do_execsql_test pager1-14.1.5 { COMMIT; SELECT * FROM t1; -} {1 2 3 4 2 2 4 4} +} {1 2 2 2} #------------------------------------------------------------------------- # Test opening and closing the pager sub-system with different values @@ -1919,32 +1919,34 @@ do_test pager1-20.2.2 { } } {} -do_test pager1-20.3.1 { - faultsim_delete_and_reopen - db func a_string a_string - execsql { - PRAGMA cache_size = 10; - PRAGMA journal_mode = wal; - BEGIN; - CREATE TABLE t1(x); - CREATE TABLE t2(y); - INSERT INTO t1 VALUES(a_string(800)); - INSERT INTO t1 SELECT a_string(800) FROM t1; /* 2 */ - INSERT INTO t1 SELECT a_string(800) FROM t1; /* 4 */ - INSERT INTO t1 SELECT a_string(800) FROM t1; /* 8 */ - INSERT INTO t1 SELECT a_string(800) FROM t1; /* 16 */ - INSERT INTO t1 SELECT a_string(800) FROM t1; /* 32 */ - COMMIT; - } -} {wal} -do_test pager1-20.3.2 { - execsql { - BEGIN; - INSERT INTO t2 VALUES('xxxx'); - } - recursive_select 32 t1 - execsql COMMIT -} {} +ifcapable wal { + do_test pager1-20.3.1 { + faultsim_delete_and_reopen + db func a_string a_string + execsql { + PRAGMA cache_size = 10; + PRAGMA journal_mode = wal; + BEGIN; + CREATE TABLE t1(x); + CREATE TABLE t2(y); + INSERT INTO t1 VALUES(a_string(800)); + INSERT INTO t1 SELECT a_string(800) FROM t1; /* 2 */ + INSERT INTO t1 SELECT a_string(800) FROM t1; /* 4 */ + INSERT INTO t1 SELECT a_string(800) FROM t1; /* 8 */ + INSERT INTO t1 SELECT a_string(800) FROM t1; /* 16 */ + INSERT INTO t1 SELECT a_string(800) FROM t1; /* 32 */ + COMMIT; + } + } {wal} + do_test pager1-20.3.2 { + execsql { + BEGIN; + INSERT INTO t2 VALUES('xxxx'); + } + recursive_select 32 t1 + execsql COMMIT + } {} +} #------------------------------------------------------------------------- # Test that a WAL database may not be opened if: @@ -1952,28 +1954,30 @@ do_test pager1-20.3.2 { # pager1-21.1.*: The VFS has an iVersion less than 2, or # pager1-21.2.*: The VFS does not provide xShmXXX() methods. # -do_test pager1-21.0 { - faultsim_delete_and_reopen - execsql { - PRAGMA journal_mode = WAL; - CREATE TABLE ko(c DEFAULT 'abc', b DEFAULT 'def'); - INSERT INTO ko DEFAULT VALUES; - } -} {wal} -do_test pager1-21.1 { - testvfs tv -noshm 1 - sqlite3 db2 test.db -vfs tv - catchsql { SELECT * FROM ko } db2 -} {1 {unable to open database file}} -db2 close -tv delete -do_test pager1-21.2 { - testvfs tv -iversion 1 - sqlite3 db2 test.db -vfs tv - catchsql { SELECT * FROM ko } db2 -} {1 {unable to open database file}} -db2 close -tv delete +ifcapable wal { + do_test pager1-21.0 { + faultsim_delete_and_reopen + execsql { + PRAGMA journal_mode = WAL; + CREATE TABLE ko(c DEFAULT 'abc', b DEFAULT 'def'); + INSERT INTO ko DEFAULT VALUES; + } + } {wal} + do_test pager1-21.1 { + testvfs tv -noshm 1 + sqlite3 db2 test.db -vfs tv + catchsql { SELECT * FROM ko } db2 + } {1 {unable to open database file}} + db2 close + tv delete + do_test pager1-21.2 { + testvfs tv -iversion 1 + sqlite3 db2 test.db -vfs tv + catchsql { SELECT * FROM ko } db2 + } {1 {unable to open database file}} + db2 close + tv delete +} #------------------------------------------------------------------------- # Test that a "PRAGMA wal_checkpoint": @@ -2254,24 +2258,26 @@ do_test pager1.27.1 { # the same database. # catch { db close } -do_multiclient_test tn { - do_test pager1-28.$tn.1 { - sql1 { - PRAGMA journal_mode = WAL; - CREATE TABLE t1(a, b); - INSERT INTO t1 VALUES('a', 'b'); - } - } {wal} - do_test pager1-28.$tn.2 { sql2 { SELECT * FROM t1 } } {a b} +ifcapable wal { + do_multiclient_test tn { + do_test pager1-28.$tn.1 { + sql1 { + PRAGMA journal_mode = WAL; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES('a', 'b'); + } + } {wal} + do_test pager1-28.$tn.2 { sql2 { SELECT * FROM t1 } } {a b} - do_test pager1-28.$tn.3 { sql1 { PRAGMA locking_mode=exclusive } } {exclusive} - do_test pager1-28.$tn.4 { - csql1 { BEGIN; INSERT INTO t1 VALUES('c', 'd'); } - } {1 {database is locked}} - code2 { db2 close ; sqlite3 db2 test.db } - do_test pager1-28.$tn.4 { - sql1 { INSERT INTO t1 VALUES('c', 'd'); COMMIT } - } {} + do_test pager1-28.$tn.3 { sql1 { PRAGMA locking_mode=exclusive } } {exclusive} + do_test pager1-28.$tn.4 { + csql1 { BEGIN; INSERT INTO t1 VALUES('c', 'd'); } + } {1 {database is locked}} + code2 { db2 close ; sqlite3 db2 test.db } + do_test pager1-28.$tn.4 { + sql1 { INSERT INTO t1 VALUES('c', 'd'); COMMIT } + } {} + } } #------------------------------------------------------------------------- @@ -2347,5 +2353,62 @@ do_test pager1-29.2 { file size test.db } [expr 4096*3] +#------------------------------------------------------------------------- +# Test that if an empty database file (size 0 bytes) is opened in +# exclusive-locking mode, any journal file is deleted from the file-system +# without being rolled back. And that the RESERVED lock obtained while +# doing this is not released. +# +do_test pager1-30.1 { + db close + file delete test.db + file delete test.db-journal + set fd [open test.db-journal w] + seek $fd [expr 512+1032*2] + puts -nonewline $fd x + close $fd + + sqlite3 db test.db + execsql { + PRAGMA locking_mode=EXCLUSIVE; + SELECT count(*) FROM sqlite_master; + PRAGMA lock_status; + } +} {exclusive 0 main reserved temp closed} + +#------------------------------------------------------------------------- +# Test that if the "page-size" field in a journal-header is 0, the journal +# file can still be rolled back. This is required for backward compatibility - +# versions of SQLite prior to 3.5.8 always set this field to zero. +# +do_test pager1-31.1 { + faultsim_delete_and_reopen + execsql { + PRAGMA cache_size = 10; + PRAGMA page_size = 1024; + CREATE TABLE t1(x, y, UNIQUE(x, y)); + INSERT INTO t1 VALUES(randomblob(1500), randomblob(1500)); + INSERT INTO t1 SELECT randomblob(1500), randomblob(1500) FROM t1; + INSERT INTO t1 SELECT randomblob(1500), randomblob(1500) FROM t1; + INSERT INTO t1 SELECT randomblob(1500), randomblob(1500) FROM t1; + INSERT INTO t1 SELECT randomblob(1500), randomblob(1500) FROM t1; + INSERT INTO t1 SELECT randomblob(1500), randomblob(1500) FROM t1; + INSERT INTO t1 SELECT randomblob(1500), randomblob(1500) FROM t1; + INSERT INTO t1 SELECT randomblob(1500), randomblob(1500) FROM t1; + INSERT INTO t1 SELECT randomblob(1500), randomblob(1500) FROM t1; + INSERT INTO t1 SELECT randomblob(1500), randomblob(1500) FROM t1; + INSERT INTO t1 SELECT randomblob(1500), randomblob(1500) FROM t1; + BEGIN; + UPDATE t1 SET y = randomblob(1499); + } + file copy test.db test.db2 + file copy test.db-journal test.db2-journal + + hexio_write test.db2-journal 24 00000000 + sqlite3 db2 test.db2 + execsql { PRAGMA integrity_check } db2 +} {ok} + + finish_test diff --git a/test/pager2.test b/test/pager2.test index 06389f5d..fa5f7b96 100644 --- a/test/pager2.test +++ b/test/pager2.test @@ -133,7 +133,7 @@ do_test pager2-2.1 { ROLLBACK; SELECT * FROM t1; } -} {off 1 2} +} {off} do_test pager2-2.2 { faultsim_delete_and_reopen execsql { diff --git a/test/pagerfault.test b/test/pagerfault.test index c73bc422..c5c7d3b3 100644 --- a/test/pagerfault.test +++ b/test/pagerfault.test @@ -673,21 +673,30 @@ do_faultsim_test pagerfault-14a -prep { } -test { faultsim_test_result {0 {}} {1 {}} {1 {SQL logic error or missing database}} } -do_faultsim_test pagerfault-14b -prep { - catch { db2 close } - faultsim_restore_and_reopen - sqlite3 db2 "" - db2 eval { PRAGMA page_size = 4096; CREATE TABLE xx(a) } -} -body { - sqlite3_backup B db2 main db main - B step 200 - set rc [B finish] - if {[string match SQLITE_IOERR_* $rc]} {set rc SQLITE_IOERR} - if {$rc != "SQLITE_OK"} { error [sqlite3_test_errstr $rc] } - set {} {} -} -test { - faultsim_test_result {0 {}} + +# If TEMP_STORE is 2 or greater, then the database [db2] will be created +# as an in-memory database. This test will not work in that case, as it +# is not possible to change the page-size of an in-memory database. Even +# using the backup API. +# +if {$TEMP_STORE<2} { + do_faultsim_test pagerfault-14b -prep { + catch { db2 close } + faultsim_restore_and_reopen + sqlite3 db2 "" + db2 eval { PRAGMA page_size = 4096; CREATE TABLE xx(a) } + } -body { + sqlite3_backup B db2 main db main + B step 200 + set rc [B finish] + if {[string match SQLITE_IOERR_* $rc]} {set rc SQLITE_IOERR} + if {$rc != "SQLITE_OK"} { error [sqlite3_test_errstr $rc] } + set {} {} + } -test { + faultsim_test_result {0 {}} {1 {sqlite3_backup_init() failed}} + } } + do_faultsim_test pagerfault-14c -prep { catch { db2 close } faultsim_restore_and_reopen @@ -705,7 +714,7 @@ do_faultsim_test pagerfault-14c -prep { if {$rc != "SQLITE_OK"} { error [sqlite3_test_errstr $rc] } set {} {} } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} {1 {sqlite3_backup_init() failed}} } do_test pagerfault-15-pre1 { @@ -1091,8 +1100,8 @@ do_faultsim_test pagerfault-22 -prep { # PagerRollback() # do_faultsim_test pagerfault-23 -prep { - foreach f [glob -nocomplain test.db*] { file delete -force $f } sqlite3 db :memory: + foreach f [glob -nocomplain test.db*] { file delete -force $f } db eval { ATTACH 'test.db2' AS aux; CREATE TABLE t1(a, b); diff --git a/test/pagerfault3.test b/test/pagerfault3.test new file mode 100644 index 00000000..6f63b83f --- /dev/null +++ b/test/pagerfault3.test @@ -0,0 +1,63 @@ +# 2011 January 28 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl + +if {[permutation] == "inmemory_journal"} { + finish_test + return +} + +# Create a database with page-size 2048 bytes that uses 2 pages. Populate +# it so that if the page-size is changed to 1024 bytes and the db vacuumed, +# the new db size is 3 pages. +# +do_test pagerfault3-pre1 { + execsql { + PRAGMA page_size = 2048; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(randomblob(1200)); + PRAGMA page_count; + } +} {2} +do_test pagerfault3-pre2 { + faultsim_save_and_close + faultsim_restore_and_reopen + execsql { + PRAGMA page_size = 1024; + VACUUM; + PRAGMA page_count; + } +} {3} + +# Now do the page-size change and VACUUM with IO error injection. When +# an IO error is injected into the final xSync() of the commit, the pager +# will have to extend the db file from 3072 to 4096 byts when rolling +# back the hot-journal file. This is a special case in pager_truncate(). +# +do_faultsim_test pagerfault3-1 -faults ioerr-transient -prep { + faultsim_restore_and_reopen +} -body { + execsql { + PRAGMA page_size = 1024; + VACUUM; + } +} -test { + faultsim_test_result {0 {}} + faultsim_integrity_check +} + +finish_test + diff --git a/test/pcache.test b/test/pcache.test index e2bcd513..5dc3059c 100644 --- a/test/pcache.test +++ b/test/pcache.test @@ -21,6 +21,14 @@ source $testdir/tester.tcl # do_not_use_codec +# Only works with a mode-2 pcache where all pcaches share a single set +# of pages. +# +ifcapable {!memorymanage && threadsafe} { + finish_test + return +} + # The pcache module limits the number of pages available to purgeable # caches to the sum of the 'cache_size' values for the set of open # caches. This block of tests, pcache-1.*, test that the library behaves diff --git a/test/permutations.test b/test/permutations.test index 086897c4..d336a3a1 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -25,6 +25,7 @@ db close # -presql SQL (default "") # -files LIST-OF-FILES (default $::ALLTESTS) # -prefix NAME (default "$::NAME.") +# -dbconfig SCRIPT (default "") # proc test_suite {name args} { @@ -34,6 +35,7 @@ proc test_suite {name args} { set default(-description) "no description supplied (fixme)" set default(-files) "" set default(-prefix) "${name}." + set default(-dbconfig) "" array set options [array get default] if {[llength $args]%2} { @@ -48,7 +50,6 @@ proc test_suite {name args} { set ::testspec($name) [array get options] lappend ::testsuitelist $name - } #------------------------------------------------------------------------- @@ -69,6 +70,7 @@ proc test_set {args} { foreach f $a { set t($f) 1 } } else { foreach f $a { array unset t $f } + foreach f $a { array unset t */$f } } } @@ -84,13 +86,17 @@ proc test_set {args} { # set alltests [list] foreach f [glob $testdir/*.test] { lappend alltests [file tail $f] } +foreach f [glob -nocomplain $testdir/../ext/rtree/*.test] { + lappend alltests $f +} + if {$::tcl_platform(platform)!="unix"} { set alltests [test_set $alltests -exclude crash.test crash2.test] } set alltests [test_set $alltests -exclude { all.test async.test quick.test veryquick.test memleak.test permutations.test soak.test fts3.test - mallocAll.test + mallocAll.test rtree.test }] set allquicktests [test_set $alltests -exclude { @@ -105,7 +111,7 @@ set allquicktests [test_set $alltests -exclude { thread003.test thread004.test thread005.test trans2.test vacuum3.test incrvacuum_ioerr.test autovacuum_crash.test btree8.test shared_err.test vtab_err.test walslow.test walcrash.test - walthread.test + walthread.test rtree3.test }] if {[info exists ::env(QUICKTEST_INCLUDE)]} { set allquicktests [concat $allquicktests $::env(QUICKTEST_INCLUDE)] @@ -132,6 +138,17 @@ test_suite "veryquick" -prefix "" -description { test_set $allquicktests -exclude *malloc* *ioerr* *fault* ] +test_suite "valgrind" -prefix "" -description { + Run the "veryquick" test suite with a couple of multi-process tests (that + fail under valgrind) omitted. +} -files [ + test_set $allquicktests -exclude *malloc* *ioerr* *fault* +] -initialize { + set ::G(valgrind) 1 +} -shutdown { + unset -nocomplain ::G(valgrind) +} + test_suite "quick" -prefix "" -description { Quick test suite. Runs in around 10 minutes on a workstation. } -files [ @@ -154,14 +171,16 @@ test_suite "threads" -prefix "" -description { } test_suite "fts3" -prefix "" -description { - All FTS3 tests except fts3malloc.test and fts3rnd.test. + All FTS3 tests except fts3rnd.test. } -files { fts3aa.test fts3ab.test fts3ac.test fts3ad.test fts3ae.test fts3af.test fts3ag.test fts3ah.test fts3ai.test fts3aj.test fts3ak.test fts3al.test fts3am.test fts3an.test fts3ao.test fts3atoken.test fts3b.test fts3c.test fts3cov.test fts3d.test - fts3e.test fts3expr.test fts3expr2.test fts3near.test - fts3query.test fts3snippet.test + fts3defer.test fts3defer2.test fts3e.test fts3expr.test fts3expr2.test + fts3near.test fts3query.test fts3shared.test fts3snippet.test + + fts3fault.test fts3malloc.test fts3matchinfo.test } @@ -176,7 +195,7 @@ test_suite "coverage-wal" -description { } -files { wal.test wal2.test wal3.test walmode.test walbak.test walhook.test walcrash2.test walcksum.test - walfault.test walbig.test + walfault.test walbig.test walnoshm.test } test_suite "coverage-pager" -description { @@ -582,6 +601,7 @@ ifcapable mem5 { } -initialize { catch {db close} sqlite3_shutdown + sqlite3_config_memstatus 0 sqlite3_config_heap 40000000 16 sqlite3_config_lookaside 0 0 install_malloc_faultsim 1 @@ -734,6 +754,24 @@ test_suite "wal" -description { fts3am.test fts3an.test fts3ao.test fts3b.test fts3c.test fts3d.test fts3e.test fts3query.test } + +test_suite "rtree" -description { + All R-tree related tests. Provides coverage of source file rtree.c. +} -files [glob -nocomplain $::testdir/../ext/rtree/*.test] + +test_suite "no_optimization" -description { + Run test scripts with optimizations disabled using the + sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS) interface. +} -files { + where.test where2.test where3.test where4.test where5.test + where6.test where7.test where8.test where9.test + whereA.test whereB.test wherelimit.test + select1.test select2.test select3.test select4.test select5.test + select7.test select8.test selectA.test selectC.test +} -dbconfig { + optimization_control $::dbhandle all 0 +} + # End of tests ############################################################################# @@ -755,11 +793,13 @@ proc run_tests {name args} { set ::G(perm:prefix) $options(-prefix) set ::G(perm:presql) $options(-presql) set ::G(isquick) 1 + set ::G(perm:dbconfig) $options(-dbconfig) uplevel $options(-initialize) foreach file [lsort $options(-files)] { - slave_test_file $::testdir/$file + if {[file tail $file] == $file} { set file [file join $::testdir $file] } + slave_test_file $file } uplevel $options(-shutdown) @@ -767,6 +807,7 @@ proc run_tests {name args} { unset ::G(perm:name) unset ::G(perm:prefix) unset ::G(perm:presql) + unset ::G(perm:dbconfig) } proc run_test_suite {name} { diff --git a/test/pragma.test b/test/pragma.test index 3e926ebd..4b441829 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -1379,21 +1379,21 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { set sqlite_hostid_num 2 do_test pragma-16.7 { - sqlite3 db test2.db - execsql { - PRAGMA lock_proxy_file=":auto:"; - } - catchsql { - select * from sqlite_master; - } + list [catch { + sqlite3 db test2.db + execsql { + PRAGMA lock_proxy_file=":auto:"; + select * from sqlite_master; + } + } msg] $msg } {1 {database is locked}} db close do_test pragma-16.8 { - sqlite3 db test2.db - catchsql { - select * from sqlite_master; - } + list [catch { + sqlite3 db test2.db + execsql { select * from sqlite_master } + } msg] $msg } {1 {database is locked}} db2 close diff --git a/test/quota.test b/test/quota.test new file mode 100644 index 00000000..2cf76280 --- /dev/null +++ b/test/quota.test @@ -0,0 +1,396 @@ +# 2010 September 1 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl + +db close + +do_test quota-1.1 { sqlite3_quota_initialize nosuchvfs 1 } {SQLITE_ERROR} +do_test quota-1.2 { sqlite3_quota_initialize "" 1 } {SQLITE_OK} +do_test quota-1.3 { sqlite3_quota_initialize "" 1 } {SQLITE_MISUSE} +do_test quota-1.4 { sqlite3_quota_shutdown } {SQLITE_OK} + +do_test quota-1.5 { sqlite3_quota_initialize "" 0 } {SQLITE_OK} +do_test quota-1.6 { sqlite3_quota_shutdown } {SQLITE_OK} +do_test quota-1.7 { sqlite3_quota_initialize "" 1 } {SQLITE_OK} +do_test quota-1.8 { sqlite3_quota_shutdown } {SQLITE_OK} + + +#------------------------------------------------------------------------- +# Some simple warm-body tests with a single database file in rollback +# mode: +# +# quota-2.1.*: Test that SQLITE_FULL is returned if the database would +# exceed the configured quota. +# +# quota-2.2.*: Test that SQLITE_FULL is not returned and the database +# grows if the callback extends the quota when the database +# attempts to grow beyond the configured quota. +# +# quota-2.3.*: Open and close a db that is not part of any quota group. At +# one point this was causing mutex refs to be leaked. +# +# quota-2.4.*: Try to shutdown the quota system before closing the db +# file. Check that this fails and the quota system still works +# afterwards. Then close the database and successfully shut +# down the quota system. +# +sqlite3_quota_initialize "" 1 + +proc quota_check {filename limitvar size} { + upvar $limitvar limit + + lappend ::quota [set limit] $size + if {[info exists ::quota_request_ok]} { set limit $size } +} + +do_test quota-2.1.1 { + sqlite3_quota_set *test.db 4096 quota_check +} {SQLITE_OK} +do_test quota-2.1.2 { + sqlite3 db test.db + execsql { + PRAGMA page_size=1024; + PRAGMA auto_vacuum=OFF; + PRAGMA journal_mode=DELETE; + } + set ::quota [list] + execsql { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, randomblob(1100)); + INSERT INTO t1 VALUES(2, randomblob(1100)); + } + set ::quota +} {} +do_test quota-2.1.3 { file size test.db } {4096} +do_test quota-2.1.4 { + catchsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } +} {1 {database or disk is full}} +do_test quota-2.1.5 { set ::quota } {4096 5120} + +set ::quota_request_ok 1 +set ::quota [list] +do_test quota-2.2.1 { + execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } +} {} +do_test quota-2.2.2 { set ::quota } {4096 5120} +do_test quota-2.2.3 { file size test.db } {5120} +unset ::quota_request_ok + +do_test quota-2.3.1 { + sqlite3 db2 bak.db + db2 close +} {} + +do_test quota-2.4.1 { + sqlite3_quota_shutdown +} {SQLITE_MISUSE} +set ::quota [list] +do_test quota-2.4.2 { + catchsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } +} {1 {database or disk is full}} +do_test quota-2.4.3 { set ::quota } {5120 6144} +do_test quota-2.4.4 { file size test.db } {5120} +do_test quota-2.4.99 { + db close + sqlite3_quota_shutdown +} {SQLITE_OK} + +#------------------------------------------------------------------------- +# Try some tests with more than one connection to a database file. Still +# in rollback mode. +# +# quota-3.1.*: Two connections to a single database file. +# +# quota-3.2.*: Two connections to each of several database files (that +# are in the same quota group). +# +proc quota_check {filename limitvar size} { + upvar $limitvar limit + lappend ::quota [set limit] $size + if {[info exists ::quota_request_ok]} { set limit $size } +} + +do_test quota-3.1.1 { + file delete -force test.db + sqlite3_quota_initialize "" 1 + sqlite3_quota_set *test.db 4096 quota_check +} {SQLITE_OK} +do_test quota-3.1.2 { + sqlite3 db test.db + execsql { + PRAGMA page_size = 1024; + PRAGMA journal_mode = delete; + PRAGMA auto_vacuum = off; + CREATE TABLE t1(a PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, 'one'); + } + file size test.db +} {3072} +do_test quota-3.1.3 { + sqlite3 db2 test.db + set ::quota [list] + execsql { CREATE TABLE t2(a, b) } db2 + set ::quota +} {} +do_test quota-3.1.4 { + catchsql { CREATE TABLE t3(a, b) } +} {1 {database or disk is full}} +do_test quota-3.1.5 { + set ::quota_request_ok 1 + execsql { CREATE TABLE t3(a, b) } +} {} +do_test quota-3.1.6 { + db close + db2 close + sqlite3_quota_set *test.db 0 {} +} {SQLITE_OK} + +do_test quota-3.2.1 { + file delete force test.db test2.db + + sqlite3_quota_set * 4096 {} + sqlite3 db1a test.db + sqlite3 db2a test2.db + + foreach db {db1a db2a} { + execsql { + PRAGMA page_size = 1024; + PRAGMA journal_mode = delete; + PRAGMA auto_vacuum = off; + CREATE TABLE t1(a, b); + } $db + } + + sqlite3 db1b test.db + sqlite3 db2b test2.db + + list [file size test.db] [file size test2.db] +} {2048 2048} + +catch { unset ::quota_request_ok } + +do_test quota-3.2.2 { execsql { INSERT INTO t1 VALUES('x', 'y') } db1a } {} +do_test quota-3.2.3 { execsql { INSERT INTO t1 VALUES('v', 'w') } db1b } {} +do_test quota-3.2.4 { execsql { INSERT INTO t1 VALUES('t', 'u') } db2a } {} +do_test quota-3.2.5 { execsql { INSERT INTO t1 VALUES('r', 's') } db2b } {} + +do_test quota-3.2.6 { + catchsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1a +} {1 {database or disk is full}} +do_test quota-3.2.7 { + catchsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1b +} {1 {database or disk is full}} +do_test quota-3.2.8 { + catchsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2a +} {1 {database or disk is full}} +do_test quota-3.2.9 { + catchsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2b +} {1 {database or disk is full}} + +set ::quota [list] +proc quota_callback {file limitvar size} { + upvar $limitvar limit + lappend ::quota $file $size + set limit 0 +} +sqlite3_quota_set * 4096 quota_callback +do_test quota-3.3.1 { + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1a + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1b + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2a + execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2b + set ::quota +} [list [file join [pwd] test.db] 5120] + +do_test quota-3.2.X { + foreach db {db1a db2a db2b db1b} { catch { $db close } } + sqlite3_quota_set * 0 {} +} {SQLITE_OK} + +#------------------------------------------------------------------------- +# Quotas are deleted when unused and when there limit is set to zero +# + +# Return a list of all currently defined quotas. Each quota is identified +# by its pattern. +proc quota_list {} { + set allq {} + foreach q [sqlite3_quota_dump] { + lappend allq [lindex $q 0] + } + return [lsort $allq] +} + +do_test quota-4.1.1 { + sqlite3_quota_set *test.db 0 {} + quota_list +} {} +do_test quota-4.1.2 { + sqlite3_quota_set *test.db 4096 {} + quota_list +} {*test.db} +do_test quota-4.1.3 { + sqlite3_quota_set *test2.db 0 {} + quota_list +} {*test.db} +do_test quota-4.1.4 { + sqlite3_quota_set *test2.db 100000 {} + quota_list +} {*test.db *test2.db} +do_test quota-4.1.5 { + sqlite3_quota_set *test.db 0 {} + quota_list +} {*test2.db} +do_test quota-4.1.6 { + file delete -force test2.db test2.db-journal test2.db-wal + sqlite3 db test2.db + db eval {CREATE TABLE t2(x); INSERT INTO t2 VALUES('tab-t2');} + quota_list +} {*test2.db} +do_test quota-4.1.7 { + catchsql {INSERT INTO t2 VALUES(zeroblob(200000))} +} {1 {database or disk is full}} +do_test quota-4.1.8 { + sqlite3 db2 test2.db + db2 eval {SELECT * FROM t2} +} {tab-t2} +do_test quota-4.1.9 { + sqlite3_quota_set *test2.db 0 {} + catchsql {INSERT INTO t2 VALUES(zeroblob(200000))} +} {0 {}} +do_test quota-4.1.10 { + quota_list +} {*test2.db} +do_test quota-4.1.11 { + db2 close + quota_list +} {*test2.db} +do_test quota-4.1.12 { + db close + quota_list +} {} + +do_test quota-4.2.1 { + sqlite3_quota_set A 1000 {} + sqlite3_quota_set B 1000 {} + sqlite3_quota_set C 1000 {} + sqlite3_quota_set D 1000 {} + quota_list +} {A B C D} +do_test quota-4.2.2 { + sqlite3_quota_set C 0 {} + sqlite3_quota_set B 0 {} + quota_list +} {A D} +do_test quota-4.2.3 { + sqlite3_quota_set A 0 {} + sqlite3_quota_set D 0 {} + quota_list +} {} +do_test quota-4.2.4 { + sqlite3_quota_set A 1000 {} + sqlite3_quota_set B 1000 {} + sqlite3_quota_set C 1000 {} + sqlite3_quota_set A 0 {} + sqlite3_quota_set B 0 {} + sqlite3_quota_set C 0 {} + quota_list +} {} +do_test quota-4.2.5 { + sqlite3_quota_set A 1000 {} + sqlite3_quota_set B 1000 {} + sqlite3_quota_set C 1000 {} + sqlite3_quota_set C 0 {} + sqlite3_quota_set B 0 {} + sqlite3_quota_set A 0 {} + quota_list +} {} + +do_test quota-4.3.1 { + sqlite3_quota_set A 1000 quota_callback + sqlite3 db A + sqlite3_quota_set A 0 quota_callback + db close + quota_list +} {} + +do_test quota-4.4.1 { + sqlite3_quota_set A 1000 quota_callback + sqlite3_quota_shutdown +} {SQLITE_OK} +do_test quota-4.4.2 { + quota_list +} {} + +#------------------------------------------------------------------------- +# The following tests test that the quota VFS handles malloc and IO +# errors. +# + +sqlite3_quota_initialize "" 1 +sqlite3_quota_set *test.db 4096 {} + +do_faultsim_test quota-5.1 -prep { + catch {db close} +} -body { + sqlite3 db test2.db +} +do_faultsim_test quota-5.2 -prep { + catch {db close} +} -body { + sqlite3 db test.db +} + +catch { db close } +file delete -force test.db + +do_test quota-5.3.prep { + sqlite3 db test.db + execsql { + PRAGMA auto_vacuum = 1; + PRAGMA page_size = 1024; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(10, zeroblob(1200)); + } + faultsim_save_and_close +} {} +do_faultsim_test quota-5.3 -prep { + faultsim_restore_and_reopen +} -body { + execsql { DELETE FROM t1 } +} + +do_test quota-5.4.1 { + catch { db close } + file delete -force test.db + file mkdir test.db + list [catch { sqlite3 db test.db } msg] $msg +} {1 {unable to open database file}} + +do_faultsim_test quota-5.5 -prep { + catch { sqlite3_quota_shutdown } +} -body { + sqlite3_quota_initialize "" 1 +} + +do_faultsim_test quota-5.6 -prep { + catch { sqlite3_quota_shutdown } + sqlite3_quota_initialize "" 1 +} -body { + sqlite3_quota_set * 4096 {} +} + +catch { sqlite3_quota_shutdown } +finish_test diff --git a/test/releasetest.mk b/test/releasetest.mk new file mode 100644 index 00000000..5d217c64 --- /dev/null +++ b/test/releasetest.mk @@ -0,0 +1,14 @@ +######################################################## +TOP=/home/drh/sqlite/sqlite + +TCL_FLAGS=-I/home/drh/tcltk/86linux +LIBTCL=/home/drh/tcltk/86linux/libtcl8.6.a -lm -ldl -lpthread + +BCC = gcc +TCC = gcc -ansi -g $(CFLAGS) +NAWK = awk +AR = ar cr +RANLIB = ranlib +THREADLIB = -lpthread -ldl -lz +include $(TOP)/main.mk +######################################################## diff --git a/test/releasetest.tcl b/test/releasetest.tcl new file mode 100644 index 00000000..71ad4311 --- /dev/null +++ b/test/releasetest.tcl @@ -0,0 +1,332 @@ + +set rcsid {$Id: $} + +# Documentation for this script. This may be output to stderr +# if the script is invoked incorrectly. See the [process_options] +# proc below. +# +set ::USAGE_MESSAGE { +This Tcl script is used to test the various configurations required +before releasing a new version. Supported command line options (all +optional) are: + + -makefile PATH-TO-MAKEFILE (default "releasetest.mk") + -platform PLATFORM (see below) + -quick BOOLEAN (default "0") + +The default value for -makefile is "./releasetest.mk". + +The script determines the default value for -platform using the +$tcl_platform(os) and $tcl_platform(machine) variables. Supported +platforms are "Linux-x86", "Linux-x86_64" and "Darwin-i386". + +If the -quick option is set to true, then the "veryquick.test" script +is run for all compilation configurations. Otherwise, sometimes "all.test" +is run, sometimes "veryquick.test". + +Almost any SQLite makefile (except those generated by configure - see below) +should work. The following properties are required: + + * The makefile should support the "fulltest" target. + * The makefile should support the variable "OPTS" as a way to pass + options from the make command line to lemon and the C compiler. + +More precisely, the following invocation must be supported: + + make -f $::MAKEFILE fulltest OPTS="-DSQLITE_SECURE_DELETE=1 -DSQLITE_DEBUG=1" + +Makefiles generated by the sqlite configure program cannot be used as +they do not respect the OPTS variable. + +Example Makefile contents: + + ######################################################## + TOP=/home/dan/work/sqlite/sqlite + + TCL_FLAGS=-I/home/dan/tcl/include + LIBTCL=-L/home/dan/tcl/lib -ltcl + + BCC = gcc + TCC = gcc -ansi -g $(CFLAGS) + NAWK = awk + AR = ar cr + RANLIB = ranlib + THREADLIB = -lpthread -ldl + include $(TOP)/main.mk + ######################################################## +} + +array set ::Configs { + "Default" { + -O2 + } + "Unlock-Notify" { + -O2 + -DSQLITE_ENABLE_UNLOCK_NOTIFY + -DSQLITE_THREADSAFE + -DOS_UNIX + -DSQLITE_TCL_DEFAULT_FULLMUTEX=1 + } + "Secure-Delete" { + -O2 + -DSQLITE_SECURE_DELETE=1 + -DSQLITE_SOUNDEX=1 + } + "Update-Delete-Limit" { + -O2 + -DSQLITE_DEFAULT_FILE_FORMAT=4 + -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 + } + "Debug-One" { + -O2 + -DSQLITE_DEBUG=1 + -DSQLITE_MEMDEBUG=1 + -DSQLITE_MUTEX_NOOP=1 + -DSQLITE_TCL_DEFAULT_FULLMUTEX=1 + -DSQLITE_ENABLE_FTS3=1 + -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_ENABLE_MEMSYS5=1 + -DSQLITE_ENABLE_MEMSYS3=1 + -DSQLITE_ENABLE_COLUMN_METADATA=1 + } + "Device-One" { + -O2 + -DSQLITE_DEBUG=1 + -DSQLITE_DEFAULT_AUTOVACUUM=1 + -DSQLITE_DEFAULT_CACHE_SIZE=64 + -DSQLITE_DEFAULT_PAGE_SIZE=1024 + -DSQLITE_DEFAULT_TEMP_CACHE_SIZE=32 + -DSQLITE_DISABLE_LFS=1 + -DSQLITE_ENABLE_ATOMIC_WRITE=1 + -DSQLITE_ENABLE_IOTRACE=1 + -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 + -DSQLITE_MAX_PAGE_SIZE=4096 + -DSQLITE_OMIT_LOAD_EXTENSION=1 + -DSQLITE_OMIT_PROGRESS_CALLBACK=1 + -DSQLITE_OMIT_VIRTUALTABLE=1 + -DSQLITE_TEMP_STORE=3 + } + "Device-Two" { + -DSQLITE_4_BYTE_ALIGNED_MALLOC=1 + -DSQLITE_DEFAULT_AUTOVACUUM=1 + -DSQLITE_DEFAULT_CACHE_SIZE=1000 + -DSQLITE_DEFAULT_LOCKING_MODE=0 + -DSQLITE_DEFAULT_PAGE_SIZE=1024 + -DSQLITE_DEFAULT_TEMP_CACHE_SIZE=1000 + -DSQLITE_DISABLE_LFS=1 + -DSQLITE_ENABLE_FTS3=1 + -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 + -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_MAX_COMPOUND_SELECT=50 + -DSQLITE_MAX_PAGE_SIZE=32768 + -DSQLITE_OMIT_TRACE=1 + -DSQLITE_TEMP_STORE=3 + -DSQLITE_THREADSAFE=2 + } + "Locking-Style" { + -O2 + -DSQLITE_ENABLE_LOCKING_STYLE=1 + } + "OS-X" { + -DSQLITE_OMIT_LOAD_EXTENSION=1 + -DSQLITE_DEFAULT_MEMSTATUS=0 + -DSQLITE_THREADSAFE=2 + -DSQLITE_OS_UNIX=1 + -DSQLITE_ENABLE_LOCKING_STYLE=1 + -DUSE_PREAD=1 + -DSQLITE_ENABLE_RTREE=1 + -DSQLITE_ENABLE_FTS3=1 + -DSQLITE_ENABLE_FTS3_PARENTHESIS=1 + -DSQLITE_DEFAULT_CACHE_SIZE=1000 + -DSQLITE_MAX_LENGTH=2147483645 + -DSQLITE_MAX_VARIABLE_NUMBER=500000 + -DSQLITE_DEBUG=1 + -DSQLITE_PREFER_PROXY_LOCKING=1 + } + "Extra-Robustness" { + -DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1 + } +} + +array set ::Platforms { + Linux-x86_64 { + "Secure-Delete" test + "Unlock-Notify" "QUICKTEST_INCLUDE=notify2.test test" + "Update-Delete-Limit" test + "Debug-One" test + "Extra-Robustness" test + "Device-Two" test + "Default" "threadtest test" + "Device-One" fulltest + } + Linux-i686 { + "Unlock-Notify" "QUICKTEST_INCLUDE=notify2.test test" + "Device-One" test + "Device-Two" test + "Default" "threadtest fulltest" + } + Darwin-i386 { + "Locking-Style" test + "OS-X" "threadtest fulltest" + } +} + +# End of configuration section. +######################################################################### +######################################################################### + +foreach {key value} [array get ::Platforms] { + foreach {v t} $value { + if {0==[info exists ::Configs($v)]} { + puts stderr "No such configuration: \"$v\"" + exit -1 + } + } +} + +proc run_test_suite {name testtarget config} { + + # Tcl variable $opts is used to build up the value used to set the + # OPTS Makefile variable. Variable $cflags holds the value for + # CFLAGS. The makefile will pass OPTS to both gcc and lemon, but + # CFLAGS is only passed to gcc. + # + set cflags "" + set opts "" + foreach arg $config { + if {[string match -D* $arg]} { + lappend opts $arg + } else { + lappend cflags $arg + } + } + + set cflags [join $cflags " "] + set opts [join $opts " "] + append opts " -DSQLITE_NO_SYNC=1 -DHAVE_USLEEP" + + # Set the sub-directory to use. + # + set dir [string tolower [string map {- _ " " _} $name]] + + if {$::tcl_platform(platform)=="windows"} { + append opts " -DSQLITE_OS_WIN=1" + } elseif {$::tcl_platform(platform)=="os2"} { + append opts " -DSQLITE_OS_OS2=1" + } else { + append opts " -DSQLITE_OS_UNIX=1" + } + + # Run the test. + # + set makefile [file normalize $::MAKEFILE] + file mkdir $dir + puts -nonewline "Testing configuration \"$name\" (logfile=$dir/test.log)..." + flush stdout + + set makecmd [concat \ + [list exec make -C $dir -f $makefile clean] \ + $testtarget \ + [list CFLAGS=$cflags OPTS=$opts >& $dir/test.log] \ + ] + + set tm1 [clock seconds] + set rc [catch $makecmd] + set tm2 [clock seconds] + + set minutes [expr {($tm2-$tm1)/60}] + set seconds [expr {($tm2-$tm1)%60}] + puts -nonewline [format " (%d:%.2d) " $minutes $seconds] + if {$rc} { + puts "FAILED." + } else { + puts "Ok." + } +} + + +# This proc processes the command line options passed to this script. +# Currently the only option supported is "-makefile", default +# "releasetest.mk". Set the ::MAKEFILE variable to the value of this +# option. +# +proc process_options {argv} { + set ::MAKEFILE releasetest.mk ;# Default value + set ::QUICK 0 ;# Default value + set platform $::tcl_platform(os)-$::tcl_platform(machine) + + for {set i 0} {$i < [llength $argv]} {incr i} { + switch -- [lindex $argv $i] { + -makefile { + incr i + set ::MAKEFILE [lindex $argv $i] + } + + -platform { + incr i + set platform [lindex $argv $i] + } + + -quick { + incr i + set ::QUICK [lindex $argv $i] + } + + default { + puts stderr "" + puts stderr [string trim $::USAGE_MESSAGE] + exit -1 + } + } + } + + set ::MAKEFILE [file normalize $::MAKEFILE] + + if {0==[info exists ::Platforms($platform)]} { + puts "Unknown platform: $platform" + puts -nonewline "Set the -platform option to " + set print [list] + foreach p [array names ::Platforms] { + lappend print "\"$p\"" + } + lset print end "or [lindex $print end]" + puts "[join $print {, }]." + exit + } + + set ::CONFIGLIST $::Platforms($platform) + puts "Running the following configurations for $platform:" + puts " [string trim $::CONFIGLIST]" +} + +# Main routine. +# +proc main {argv} { + + # Process any command line options. + process_options $argv + + foreach {zConfig target} $::CONFIGLIST { + if {$::QUICK} {set target test} + set config_options $::Configs($zConfig) + + run_test_suite $zConfig $target $config_options + + # If the configuration included the SQLITE_DEBUG option, then remove + # it and run veryquick.test. If it did not include the SQLITE_DEBUG option + # add it and run veryquick.test. + set debug_idx [lsearch -glob $config_options -DSQLITE_DEBUG*] + if {$debug_idx < 0} { + run_test_suite "${zConfig}_debug" test [ + concat $config_options -DSQLITE_DEBUG=1 + ] + } else { + run_test_suite "${zConfig}_ndebug" test [ + lreplace $config_options $debug_idx $debug_idx + ] + } + + } +} + +main $argv diff --git a/test/rtree.test b/test/rtree.test index 5603b05c..b5045746 100644 --- a/test/rtree.test +++ b/test/rtree.test @@ -1,3 +1,4 @@ +# 2008 June 23 # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. @@ -6,34 +7,12 @@ #*********************************************************************** # This file runs all rtree related tests. # -# $Id: rtree.test,v 1.3 2009/05/25 14:17:35 drh Exp $ set testdir [file dirname $argv0] -source $testdir/tester.tcl +source $testdir/permutations.test -rename finish_test rtree_finish_test -proc finish_test {} {} - -set RTREE_EXCLUDE { } -if {[info exists G(isquick)] && $G(isquick)} { - set RTREE_EXCLUDE rtree3.test +ifcapable rtree { + run_test_suite rtree } -set rtreedir [file join $testdir .. ext rtree] - -foreach testfile [lsort -dictionary [glob -nocomplain $rtreedir/*.test]] { - set tail [file tail $testfile] - if {[lsearch -exact $RTREE_EXCLUDE $tail]>=0} continue - source $testfile - catch {db close} - if {$sqlite_open_file_count>0} { - puts "$tail did not close all files: $sqlite_open_file_count" - fail_test $tail - set sqlite_open_file_count 0 - } -} - -set sqlite_open_file_count 0 -rtree_finish_test -rename finish_test {} -rename rtree_finish_test finish_test +finish_test diff --git a/test/savepoint.test b/test/savepoint.test index 29f64f66..042c5558 100644 --- a/test/savepoint.test +++ b/test/savepoint.test @@ -905,7 +905,7 @@ if {[wal_is_wal_mode]==0} { ROLLBACK; SELECT * FROM t1; } - } {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16} + } {1 2 3 4 5 6 7 8 9 10 11 12} } db close @@ -1019,4 +1019,27 @@ do_multiclient_test tn { } {1 2 3 4} } +#------------------------------------------------------------------------- +# This next block of tests verifies that a problem reported on the mailing +# list has been resolved. At one point the second "CREATE TABLE t6" would +# fail as table t6 still existed in the internal cache of the db schema +# (even though it had been removed from the database by the ROLLBACK +# command). +# +sqlite3 db test.db +do_execsql_test savepoint-17.1 { + BEGIN; + CREATE TABLE t6(a, b); + INSERT INTO t6 VALUES(1, 2); + SAVEPOINT one; + INSERT INTO t6 VALUES(3, 4); + ROLLBACK TO one; + SELECT * FROM t6; + ROLLBACK; +} {1 2} + +do_execsql_test savepoint-17.2 { + CREATE TABLE t6(a, b); +} {} + finish_test diff --git a/test/schema4.test b/test/schema4.test new file mode 100644 index 00000000..6618d753 --- /dev/null +++ b/test/schema4.test @@ -0,0 +1,175 @@ +# 2010 September 28 +# +# 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 implements regression tests for SQLite library. The +# focus of this file is testing that a trigger may have the same +# name as an index, view or table in the same database. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +#-------------------------------------------------------------------------- +# Test organization: +# +# schema4-1.*: Dropping and creating triggers and other objects where +# triggers and at least on other object share a name. +# +# schema4-2.*: Renaming tables where there is a trigger that shares the +# name of the table or one of its indices. +# + +do_execsql_test schema4-1.1 { + CREATE TABLE log(x, a, b); + CREATE TABLE tbl(a, b); + + CREATE TABLE t1(a, b); + CREATE VIEW v1 AS SELECT * FROM tbl; + CREATE INDEX i1 ON tbl(a); +} {} + +do_execsql_test schema4-1.2 { + CREATE TRIGGER t1 AFTER INSERT ON tbl BEGIN + INSERT INTO log VALUES('after insert', new.a, new.b); + END; + CREATE TRIGGER v1 AFTER UPDATE ON tbl BEGIN + INSERT INTO log VALUES('after update', new.a, new.b); + END; + CREATE TRIGGER i1 AFTER DELETE ON tbl BEGIN + INSERT INTO log VALUES('after delete', old.a, old.b); + END; +} {} + +do_execsql_test schema4-1.3 { + INSERT INTO tbl VALUES(1, 2); + UPDATE tbl SET b=a+b, a=a+1; + DELETE FROM tbl; + + SELECT x, a, b FROM log; +} {{after insert} 1 2 {after update} 2 3 {after delete} 2 3} + +do_execsql_test schema4-1.4 { + DELETE FROM log; + + DROP INDEX i1; + DROP TABLE t1; + DROP VIEW v1; + + INSERT INTO tbl VALUES(1, 2); + UPDATE tbl SET b=a+b, a=a+1; + DELETE FROM tbl; + + SELECT x, a, b FROM log; +} {{after insert} 1 2 {after update} 2 3 {after delete} 2 3} + +db close +sqlite3 db test.db + +do_execsql_test schema4-1.5 { + DELETE FROM log; + INSERT INTO tbl VALUES(1, 2); + UPDATE tbl SET b=a+b, a=a+1; + DELETE FROM tbl; + SELECT x, a, b FROM log; +} {{after insert} 1 2 {after update} 2 3 {after delete} 2 3} + +do_execsql_test schema4-1.6 { + CREATE TABLE t1(a, b); + CREATE VIEW v1 AS SELECT * FROM tbl; + CREATE INDEX i1 ON tbl(a); +} {} + +ifcapable fts3 { + do_execsql_test schema4-1.7 { + DROP TABLE t1; + CREATE VIRTUAL TABLE t1 USING fts3; + } {} + + do_execsql_test schema4-1.8 { + DELETE FROM log; + DROP TABLE t1; + INSERT INTO tbl VALUES(1, 2); + UPDATE tbl SET b=a+b, a=a+1; + DELETE FROM tbl; + SELECT x, a, b FROM log; + } {{after insert} 1 2 {after update} 2 3 {after delete} 2 3} +} + +ifcapable altertable { + drop_all_tables + do_execsql_test schema4-2.1 { + CREATE TABLE log(x, a, b); + CREATE TABLE tbl(a, b); + + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + } {} + + do_execsql_test schema4-2.2 { + CREATE TRIGGER t1 AFTER INSERT ON tbl BEGIN + INSERT INTO log VALUES('after insert', new.a, new.b); + END; + CREATE TRIGGER i1 AFTER DELETE ON tbl BEGIN + INSERT INTO log VALUES('after delete', old.a, old.b); + END; + } {} + + do_execsql_test schema4-2.3 { ALTER TABLE t1 RENAME TO t2 } {} + + do_execsql_test schema4-2.4 { + INSERT INTO tbl VALUES('a', 'b'); + DELETE FROM tbl; + SELECT * FROM log; + } {{after insert} a b {after delete} a b} + + db close + sqlite3 db test.db + + do_execsql_test schema4-2.5 { + DELETE FROM log; + INSERT INTO tbl VALUES('c', 'd'); + DELETE FROM tbl; + SELECT * FROM log; + } {{after insert} c d {after delete} c d} + + do_execsql_test schema4-2.6 { + CREATE TEMP TRIGGER x1 AFTER UPDATE ON tbl BEGIN + INSERT INTO log VALUES('after update', new.a, new.b); + END; + + CREATE TEMP TABLE x1(x); + INSERT INTO x1 VALUES(123); + } {} + + do_execsql_test schema4-2.8 { + select sql from sqlite_temp_master WHERE type='table'; + } {{CREATE TABLE x1(x)}} + + do_execsql_test schema4-2.7 { ALTER TABLE tbl RENAME TO tbl2 } {} + + do_execsql_test schema4-2.9 { + select sql from sqlite_temp_master WHERE type='table'; + } {{CREATE TABLE x1(x)}} + + do_execsql_test schema4-2.10 { + DELETE FROM log; + INSERT INTO tbl2 VALUES('e', 'f'); + UPDATE tbl2 SET a='g', b='h'; + DELETE FROM tbl2; + SELECT * FROM log; + } {{after insert} e f {after update} g h {after delete} g h} + + do_execsql_test schema4-2.11 { + INSERT INTO x1 VALUES(456); + SELECT * FROM x1 + } {123 456} +} + +finish_test diff --git a/test/select6.test b/test/select6.test index ef0bfd56..e0ff165c 100644 --- a/test/select6.test +++ b/test/select6.test @@ -458,6 +458,7 @@ do_test select6-8.6 { do_test select6-9.1 { execsql { SELECT a.x, b.x FROM t1 AS a, (SELECT x FROM t1 LIMIT 2) AS b + ORDER BY 1, 2 } } {1 1 1 2 2 1 2 2 3 1 3 2 4 1 4 2} do_test select6-9.2 { diff --git a/test/shared2.test b/test/shared2.test index 37f9516f..d2a0d1b0 100644 --- a/test/shared2.test +++ b/test/shared2.test @@ -13,6 +13,8 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl db close ifcapable !shared_cache { @@ -166,5 +168,14 @@ do_test shared2-5.1 { db close db2 close +# The following test verifies that shared-cache mode does not automatically +# turn on exclusive-locking mode for some reason. +do_multiclient_test {tn} { + sql1 { CREATE TABLE t1(a, b) } + sql2 { CREATE TABLE t2(a, b) } + do_test shared2-6.$tn.1 { sql1 { SELECT * FROM t2 } } {} + do_test shared2-6.$tn.2 { sql2 { SELECT * FROM t1 } } {} +} + sqlite3_enable_shared_cache $::enable_shared_cache finish_test diff --git a/test/stat.test b/test/stat.test index 177da0a4..e1aeaa9e 100644 --- a/test/stat.test +++ b/test/stat.test @@ -27,14 +27,18 @@ db func a_string a_string register_dbstat_vtab db do_execsql_test stat-0.0 { + PRAGMA auto_vacuum = OFF; CREATE VIRTUAL TABLE temp.stat USING dbstat; SELECT * FROM stat; } {} -do_execsql_test stat-0.1 { - PRAGMA journal_mode = WAL; - PRAGMA journal_mode = delete; - SELECT * FROM stat; -} {wal delete sqlite_master / 1 leaf 0 0 916 0} + +ifcapable wal { + do_execsql_test stat-0.1 { + PRAGMA journal_mode = WAL; + PRAGMA journal_mode = delete; + SELECT * FROM stat; + } {wal delete sqlite_master / 1 leaf 0 0 916 0} +} do_test stat-1.0 { execsql { @@ -140,6 +144,7 @@ sqlite3 db test.db register_dbstat_vtab db breakpoint do_execsql_test stat-5.1 { + PRAGMA auto_vacuum = OFF; CREATE VIRTUAL TABLE temp.stat USING dbstat; CREATE TABLE t1(x); INSERT INTO t1 VALUES(zeroblob(1513)); diff --git a/test/stmt.test b/test/stmt.test index 482a7d4a..49a41414 100644 --- a/test/stmt.test +++ b/test/stmt.test @@ -35,6 +35,7 @@ do_test stmt-1.2 { } {1} do_test stmt-1.3 { execsql { + PRAGMA temp_store = file; BEGIN; INSERT INTO t1 VALUES(1, 1); } diff --git a/test/superlock.test b/test/superlock.test new file mode 100644 index 00000000..41a55144 --- /dev/null +++ b/test/superlock.test @@ -0,0 +1,249 @@ +# 2010 November 19 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl + +set testprefix superlock + +# Test organization: +# +# 1.*: Test superlock on a rollback database. Test that once the db is +# superlocked, it is not possible for a second client to read from +# it. +# +# 2.*: Test superlock on a WAL database with zero frames in the WAL file. +# Test that once the db is superlocked, it is not possible to read, +# write or checkpoint the db. +# +# 3.*: As 2.*, for WAL databases with one or more frames in the WAL. +# +# 4.*: As 2.*, for WAL databases with one or more checkpointed frames +# in the WAL. +# +# 5.*: Test that a call to sqlite3demo_superlock() uses the busy handler +# correctly to wait for existing clients to clear on a WAL database. +# And returns SQLITE_BUSY if no busy handler is defined or the busy +# handler returns 0 before said clients relinquish their locks. +# +# 6.*: Test that if a superlocked WAL database is overwritten, existing +# clients run the recovery to build the new wal-index after the +# superlock is released. +# +# + +do_execsql_test 1.1 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + PRAGMA journal_mode = DELETE; +} {delete} + +do_test 1.2 { sqlite3demo_superlock unlock test.db } {unlock} +do_catchsql_test 1.3 { SELECT * FROM t1 } {1 {database is locked}} +do_test 1.4 { unlock } {} + +do_execsql_test 2.1 { + INSERT INTO t1 VALUES(3, 4); + PRAGMA journal_mode = WAL; +} {wal} + +do_test 2.2 { sqlite3demo_superlock unlock test.db } {unlock} +do_catchsql_test 2.3 { SELECT * FROM t1 } {1 {database is locked}} +do_catchsql_test 2.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}} +do_catchsql_test 2.5 { PRAGMA wal_checkpoint } {1 {database is locked}} +do_test 2.6 { unlock } {} + +do_execsql_test 3.1 { INSERT INTO t1 VALUES(3, 4) } + +do_test 3.2 { sqlite3demo_superlock unlock test.db } {unlock} +do_catchsql_test 3.3 { SELECT * FROM t1 } {1 {database is locked}} +do_catchsql_test 3.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}} +do_catchsql_test 3.5 { PRAGMA wal_checkpoint } {1 {database is locked}} +do_test 3.6 { unlock } {} + +do_execsql_test 4.1 { PRAGMA wal_checkpoint } {} + +do_test 4.2 { sqlite3demo_superlock unlock test.db } {unlock} +do_catchsql_test 4.3 { SELECT * FROM t1 } {1 {database is locked}} +do_catchsql_test 4.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}} +do_catchsql_test 4.5 { PRAGMA wal_checkpoint } {1 {database is locked}} +do_test 4.6 { unlock } {} + +do_multiclient_test tn { + + proc busyhandler {x} { + switch -- $x { + 1 { sql1 "COMMIT" } + 2 { sql2 "COMMIT" } + 3 { sql3 "COMMIT" } + } + lappend ::busylist $x + return 1 + } + set ::busylist [list] + + do_test 5.$tn.1 { + sql1 { + CREATE TABLE t1(a, b); + PRAGMA journal_mode = WAL; + INSERT INTO t1 VALUES(1, 2); + } + } {wal} + + do_test 5.$tn.2 { + sql1 { BEGIN ; SELECT * FROM t1 } + sql2 { BEGIN ; INSERT INTO t1 VALUES(3, 4) } + sql3 { BEGIN ; SELECT * FROM t1 } + } {1 2} + + do_test 5.$tn.3 { + set ::busylist [list] + sqlite3demo_superlock unlock test.db "" busyhandler + set ::busylist + } {0 1 2 3} + + do_test 5.$tn.4 { csql2 { SELECT * FROM t1 } } {1 {database is locked}} + do_test 5.$tn.5 { + csql3 { INSERT INTO t1 VALUES(5, 6) } + } {1 {database is locked}} + do_test 5.$tn.6 { csql1 "PRAGMA wal_checkpoint" } {1 {database is locked}} + + do_test 5.$tn.7 { unlock } {} + + + do_test 5.$tn.8 { + sql1 { BEGIN ; SELECT * FROM t1 } + sql2 { BEGIN ; INSERT INTO t1 VALUES(5, 6) } + sql3 { BEGIN ; SELECT * FROM t1 } + } {1 2 3 4} + + do_test 5.$tn.9 { + list [catch {sqlite3demo_superlock unlock test.db} msg] $msg + } {1 {database is locked}} + do_test 5.$tn.10 { + sql1 COMMIT + list [catch {sqlite3demo_superlock unlock test.db} msg] $msg + } {1 {database is locked}} + do_test 5.$tn.11 { + sql2 COMMIT + list [catch {sqlite3demo_superlock unlock test.db} msg] $msg + } {1 {database is locked}} + do_test 5.$tn.12 { + sql3 COMMIT + list [catch {sqlite3demo_superlock unlock test.db} msg] $msg + } {0 unlock} + unlock + + + do_test 5.$tn.13 { sql1 { SELECT * FROM t1 } } {1 2 3 4 5 6} + do_test 5.$tn.14 { sql2 { SELECT * FROM t1 } } {1 2 3 4 5 6} + do_test 5.$tn.15 { sqlite3demo_superlock unlock test.db } {unlock} + do_test 5.$tn.16 { unlock } {} + do_test 5.$tn.17 { sql2 { SELECT * FROM t1 } } {1 2 3 4 5 6} + do_test 5.$tn.18 { sql1 { SELECT * FROM t1 } } {1 2 3 4 5 6} + do_test 5.$tn.19 { sql2 { SELECT * FROM t1 } } {1 2 3 4 5 6} +} + +proc read_content {file} { + if {[file exists $file]==0} {return ""} + set fd [open $file] + fconfigure $fd -encoding binary -translation binary + set content [read $fd] + close $fd + return $content +} + +proc write_content {file content} { + set fd [open $file w+] + fconfigure $fd -encoding binary -translation binary + puts -nonewline $fd $content + close $fd +} + +# Both $file1 and $file2 are database files. This function takes a +# superlock on each, then exchanges the content of the two files (i.e. +# overwrites $file1 with the initial contents of $file2, and overwrites +# $file2 with the initial contents of $file1). The contents of any WAL +# file is also exchanged. +# +proc db_swap {file1 file2} { + sqlite3demo_superlock unlock1 $file1 + sqlite3demo_superlock unlock2 $file2 + + set db1 [read_content $file1] + set db2 [read_content $file2] + write_content $file1 $db2 + write_content $file2 $db1 + + set wal1 [read_content ${file1}-wal] + set wal2 [read_content ${file2}-wal] + write_content ${file1}-wal $wal2 + write_content ${file2}-wal $wal1 + + unlock1 + unlock2 +} + +forcedelete test.db +sqlite3 db test.db +do_execsql_test 6.1 { + ATTACH 'test.db2' AS aux; + PRAGMA aux.journal_mode = wal; + CREATE TABLE aux.t2(x, y); + INSERT INTO aux.t2 VALUES('a', 'b'); + PRAGMA schema_version = 450; + DETACH aux; + + PRAGMA main.journal_mode = wal; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + SELECT * FROM t1; +} {wal wal 1 2 3 4} + + +db_swap test.db2 test.db +do_catchsql_test 6.2 { SELECT * FROM t1 } {1 {no such table: t1}} +do_catchsql_test 6.3 { SELECT * FROM t2 } {0 {a b}} + +db_swap test.db2 test.db +do_catchsql_test 6.4 { SELECT * FROM t1 } {0 {1 2 3 4}} +do_catchsql_test 6.5 { SELECT * FROM t2 } {1 {no such table: t2}} + +do_execsql_test 6.6 { PRAGMA wal_checkpoint } + +db_swap test.db2 test.db +do_catchsql_test 6.7 { SELECT * FROM t1 } {1 {no such table: t1}} +do_catchsql_test 6.8 { SELECT * FROM t2 } {0 {a b}} + +db_swap test.db2 test.db +do_catchsql_test 6.9 { SELECT * FROM t1 } {0 {1 2 3 4}} +do_catchsql_test 6.10 { SELECT * FROM t2 } {1 {no such table: t2}} + +do_execsql_test 6.11 { + PRAGMA journal_mode = delete; + PRAGMA page_size = 512; + VACUUM; + PRAGMA journal_mode = wal; + INSERT INTO t1 VALUES(5, 6); +} {delete wal} + +db_swap test.db2 test.db +do_catchsql_test 6.12 { SELECT * FROM t1 } {1 {no such table: t1}} +do_catchsql_test 6.13 { SELECT * FROM t2 } {0 {a b}} + +db_swap test.db2 test.db +do_catchsql_test 6.14 { SELECT * FROM t1 } {0 {1 2 3 4 5 6}} +do_catchsql_test 6.15 { SELECT * FROM t2 } {1 {no such table: t2}} + +finish_test diff --git a/test/tempdb.test b/test/tempdb.test index ef0c906b..ecd23d4e 100644 --- a/test/tempdb.test +++ b/test/tempdb.test @@ -59,7 +59,7 @@ do_test tempdb-2.1 { # number of open files in the test cases below. # set jrnl_in_memory [expr {[permutation] eq "inmemory_journal"}] - set subj_in_memory [expr {$jrnl_in_memory || $TEMP_STORE == 3}] + set subj_in_memory [expr {$jrnl_in_memory || $TEMP_STORE>=2}] db close sqlite3 db test.db diff --git a/test/tester.tcl b/test/tester.tcl index fb26561b..4c7c10ed 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -109,6 +109,10 @@ if {[info command sqlite_orig]==""} { if {[info exists ::G(perm:presql)]} { [lindex $args 0] eval $::G(perm:presql) } + if {[info exists ::G(perm:dbconfig)]} { + set ::dbhandle [lindex $args 0] + uplevel #0 $::G(perm:dbconfig) + } set res } else { # This command is not opening a new database connection. Pass the @@ -245,6 +249,10 @@ reset_db # if {[info exists TC(count)]} return +# Make sure memory statistics are enabled. +# +sqlite3_config_memstatus 1 + # Initialize the test counters and set up commands to access them. # Or, if this is a slave interpreter, set up aliases to write the # counters in the parent interpreter. @@ -299,6 +307,8 @@ proc do_test {name cmd expected} { global argv cmdlinearg + fix_testname name + sqlite3_memdebug_settitle $name # if {[llength $argv]==0} { @@ -331,14 +341,100 @@ proc do_test {name cmd expected} { } flush stdout } + +proc fix_testname {varname} { + upvar $varname testname + if {[info exists ::testprefix] + && [string is digit [string range $testname 0 0]] + } { + set testname "${::testprefix}-$testname" + } +} -proc do_execsql_test {testname sql result} { - uplevel do_test $testname [list "execsql {$sql}"] [list $result] +proc do_execsql_test {testname sql {result {}}} { + fix_testname testname + uplevel do_test $testname [list "execsql {$sql}"] [list [list {*}$result]] } proc do_catchsql_test {testname sql result} { + fix_testname testname uplevel do_test $testname [list "catchsql {$sql}"] [list $result] } +proc do_eqp_test {name sql res} { + uplevel do_execsql_test $name [list "EXPLAIN QUERY PLAN $sql"] [list $res] +} +#------------------------------------------------------------------------- +# Usage: do_select_tests PREFIX ?SWITCHES? TESTLIST +# +# Where switches are: +# +# -errorformat FMTSTRING +# -count +# -query SQL +# -tclquery TCL +# -repair TCL +# +proc do_select_tests {prefix args} { + + set testlist [lindex $args end] + set switches [lrange $args 0 end-1] + + set errfmt "" + set countonly 0 + set tclquery "" + set repair "" + + for {set i 0} {$i < [llength $switches]} {incr i} { + set s [lindex $switches $i] + set n [string length $s] + if {$n>=2 && [string equal -length $n $s "-query"]} { + set tclquery [list execsql [lindex $switches [incr i]]] + } elseif {$n>=2 && [string equal -length $n $s "-tclquery"]} { + set tclquery [lindex $switches [incr i]] + } elseif {$n>=2 && [string equal -length $n $s "-errorformat"]} { + set errfmt [lindex $switches [incr i]] + } elseif {$n>=2 && [string equal -length $n $s "-repair"]} { + set repair [lindex $switches [incr i]] + } elseif {$n>=2 && [string equal -length $n $s "-count"]} { + set countonly 1 + } else { + error "unknown switch: $s" + } + } + + if {$countonly && $errfmt!=""} { + error "Cannot use -count and -errorformat together" + } + set nTestlist [llength $testlist] + if {$nTestlist%3 || $nTestlist==0 } { + error "SELECT test list contains [llength $testlist] elements" + } + + eval $repair + foreach {tn sql res} $testlist { + if {$tclquery != ""} { + execsql $sql + uplevel do_test ${prefix}.$tn [list $tclquery] [list [list {*}$res]] + } elseif {$countonly} { + set nRow 0 + db eval $sql {incr nRow} + uplevel do_test ${prefix}.$tn [list [list set {} $nRow]] [list $res] + } elseif {$errfmt==""} { + uplevel do_execsql_test ${prefix}.${tn} [list $sql] [list [list {*}$res]] + } else { + set res [list 1 [string trim [format $errfmt {*}$res]]] + uplevel do_catchsql_test ${prefix}.${tn} [list $sql] [list $res] + } + eval $repair + } + +} + +proc delete_all_data {} { + db eval {SELECT tbl_name AS t FROM sqlite_master WHERE type = 'table'} { + db eval "DELETE FROM '[string map {' ''} $t]'" + } +} # Run an SQL script. # Return the number of microseconds per statement. @@ -357,6 +453,7 @@ proc speed_trial {name numstmt units sql} { puts [format {%12d uS %s %s} $tm $rate $u2] global total_time set total_time [expr {$total_time+$tm}] + lappend ::speed_trial_times $name $tm } proc speed_trial_tcl {name numstmt units script} { puts -nonewline [format {%-21.21s } $name...] @@ -372,10 +469,12 @@ proc speed_trial_tcl {name numstmt units script} { puts [format {%12d uS %s %s} $tm $rate $u2] global total_time set total_time [expr {$total_time+$tm}] + lappend ::speed_trial_times $name $tm } proc speed_trial_init {name} { global total_time set total_time 0 + set ::speed_trial_times [list] sqlite3 versdb :memory: set vers [versdb one {SELECT sqlite_source_id()}] versdb close @@ -384,6 +483,16 @@ proc speed_trial_init {name} { proc speed_trial_summary {name} { global total_time puts [format {%-21.21s %12d uS TOTAL} $name $total_time] + + if { 0 } { + sqlite3 versdb :memory: + set vers [lindex [versdb one {SELECT sqlite_source_id()}] 0] + versdb close + puts "CREATE TABLE IF NOT EXISTS time(version, script, test, us);" + foreach {test us} $::speed_trial_times { + puts "INSERT INTO time VALUES('$vers', '$name', '$test', $us);" + } + } } # Run this routine last @@ -596,9 +705,25 @@ proc stepsql {dbptr sql} { # Delete a file or directory # -proc forcedelete {filename} { - if {[catch {file delete -force $filename}]} { - exec rm -rf $filename +proc forcedelete {args} { + foreach filename $args { + # On windows, sometimes even a [file delete -force] can fail just after + # a file is closed. The cause is usually "tag-alongs" - programs like + # anti-virus software, automatic backup tools and various explorer + # extensions that keep a file open a little longer than we expect, causing + # the delete to fail. + # + # The solution is to wait a short amount of time before retrying the + # delete. + # + set nRetry 50 ;# Maximum number of retries. + set nDelay 100 ;# Delay in ms before retrying. + for {set i 0} {$i<$nRetry} {incr i} { + set rc [catch {file delete -force $filename} msg] + if {$rc==0} break + after $nDelay + } + if {$rc} { error $msg } } } @@ -1100,9 +1225,9 @@ proc drop_all_tables {{db db}} { } foreach {t type} [$db eval " SELECT name, type FROM $master - WHERE type IN('table', 'view') AND name NOT like 'sqlite_%' + WHERE type IN('table', 'view') AND name NOT LIKE 'sqliteX_%' ESCAPE 'X' "] { - $db eval "DROP $type $t" + $db eval "DROP $type \"$t\"" } } ifcapable trigger&&foreignkey { @@ -1254,8 +1379,39 @@ proc sql36231 {sql} { return "" } +proc db_save {} { + foreach f [glob -nocomplain sv_test.db*] { forcedelete $f } + foreach f [glob -nocomplain test.db*] { + set f2 "sv_$f" + file copy -force $f $f2 + } +} +proc db_save_and_close {} { + db_save + catch { db close } + return "" +} +proc db_restore {} { + foreach f [glob -nocomplain test.db*] { forcedelete $f } + foreach f2 [glob -nocomplain sv_test.db*] { + set f [string range $f2 3 end] + file copy -force $f2 $f + } +} +proc db_restore_and_reopen {{dbfile test.db}} { + catch { db close } + db_restore + sqlite3 db $dbfile +} +proc db_delete_and_reopen {{file test.db}} { + catch { db close } + foreach f [glob -nocomplain test.db*] { file delete -force $f } + sqlite3 db $file +} + # If the library is compiled with the SQLITE_DEFAULT_AUTOVACUUM macro set # to non-zero, then set the global variable $AUTOVACUUM to 1. set AUTOVACUUM $sqlite_options(default_autovacuum) source $testdir/thread_common.tcl +source $testdir/malloc_common.tcl diff --git a/test/threadtest3.c b/test/threadtest3.c index 6373926c..82b4708d 100644 --- a/test/threadtest3.c +++ b/test/threadtest3.c @@ -1272,6 +1272,129 @@ static void cgt_pager_1(int nMs){ print_and_free_err(&err); } +/*------------------------------------------------------------------------ +** Test case "dynamic_triggers" +** +** Two threads executing statements that cause deeply nested triggers +** to fire. And one thread busily creating and deleting triggers. This +** is an attempt to find a bug reported to us. +*/ + +static char *dynamic_triggers_1(int iTid, int iArg){ + Error err = {0}; /* Error code and message */ + Sqlite db = {0}; /* SQLite database connection */ + int nDrop = 0; + int nCreate = 0; + + opendb(&err, &db, "test.db", 0); + while( !timetostop(&err) ){ + int i; + + for(i=1; i<9; i++){ + char *zSql = sqlite3_mprintf( + "CREATE TRIGGER itr%d BEFORE INSERT ON t%d BEGIN " + "INSERT INTO t%d VALUES(new.x, new.y);" + "END;", i, i, i+1 + ); + execsql(&err, &db, zSql); + sqlite3_free(zSql); + nCreate++; + } + + for(i=1; i<9; i++){ + char *zSql = sqlite3_mprintf( + "CREATE TRIGGER dtr%d BEFORE DELETE ON t%d BEGIN " + "DELETE FROM t%d WHERE x = old.x; " + "END;", i, i, i+1 + ); + execsql(&err, &db, zSql); + sqlite3_free(zSql); + nCreate++; + } + + for(i=1; i<9; i++){ + char *zSql = sqlite3_mprintf("DROP TRIGGER itr%d", i); + execsql(&err, &db, zSql); + sqlite3_free(zSql); + nDrop++; + } + + for(i=1; i<9; i++){ + char *zSql = sqlite3_mprintf("DROP TRIGGER dtr%d", i); + execsql(&err, &db, zSql); + sqlite3_free(zSql); + nDrop++; + } + } + + print_and_free_err(&err); + return sqlite3_mprintf("%d created, %d dropped", nCreate, nDrop); +} + +static char *dynamic_triggers_2(int iTid, int iArg){ + Error err = {0}; /* Error code and message */ + Sqlite db = {0}; /* SQLite database connection */ + i64 iVal = 0; + int nInsert = 0; + int nDelete = 0; + + opendb(&err, &db, "test.db", 0); + while( !timetostop(&err) ){ + do { + iVal = (iVal+1)%100; + execsql(&err, &db, "INSERT INTO t1 VALUES(:iX, :iY+1)", &iVal, &iVal); + nInsert++; + } while( iVal ); + + do { + iVal = (iVal+1)%100; + execsql(&err, &db, "DELETE FROM t1 WHERE x = :iX", &iVal); + nDelete++; + } while( iVal ); + } + + print_and_free_err(&err); + return sqlite3_mprintf("%d inserts, %d deletes", nInsert, nDelete); +} + +static void dynamic_triggers(int nMs){ + Error err = {0}; + Sqlite db = {0}; + Threadset threads = {0}; + + opendb(&err, &db, "test.db", 1); + sql_script(&err, &db, + "PRAGMA page_size = 1024;" + "PRAGMA journal_mode = WAL;" + "CREATE TABLE t1(x, y);" + "CREATE TABLE t2(x, y);" + "CREATE TABLE t3(x, y);" + "CREATE TABLE t4(x, y);" + "CREATE TABLE t5(x, y);" + "CREATE TABLE t6(x, y);" + "CREATE TABLE t7(x, y);" + "CREATE TABLE t8(x, y);" + "CREATE TABLE t9(x, y);" + ); + + setstoptime(&err, nMs); + + sqlite3_enable_shared_cache(1); + launch_thread(&err, &threads, dynamic_triggers_2, 0); + launch_thread(&err, &threads, dynamic_triggers_2, 0); + sqlite3_enable_shared_cache(0); + + sleep(2); + + launch_thread(&err, &threads, dynamic_triggers_2, 0); + launch_thread(&err, &threads, dynamic_triggers_1, 0); + + join_all_threads(&err, &threads); + + print_and_free_err(&err); +} + + int main(int argc, char **argv){ struct ThreadTest { void (*xTest)(int); @@ -1286,6 +1409,7 @@ int main(int argc, char **argv){ { walthread5, "walthread5", 1000 }, { cgt_pager_1, "cgt_pager_1", 0 }, + { dynamic_triggers, "dynamic_triggers", 20000 }, }; int i; diff --git a/test/tkt-313723c356.test b/test/tkt-313723c356.test new file mode 100644 index 00000000..8c08c349 --- /dev/null +++ b/test/tkt-313723c356.test @@ -0,0 +1,57 @@ +# 2010 September 20 +# +# 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 implements regression tests for SQLite library. +# +# This file implements tests to verify that ticket [313723c356] has been +# fixed. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl + +ifcapable !wal { finish_test ; return } + +do_execsql_test tkt-313723c356.1 { + PRAGMA page_size = 1024; + PRAGMA journal_mode = WAL; + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + INSERT INTO t1 VALUES(randomblob(400), randomblob(400)); + INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM t1; + INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM t1; + INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM t1; + INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM t1; +} {wal} +faultsim_save_and_close + +do_faultsim_test tkt-313723c356.2 -faults shmerr* -prep { + faultsim_restore_and_reopen + sqlite3 db2 test.db + db eval { SELECT * FROM t1 } + db2 eval { UPDATE t1 SET a = randomblob(399) } + db2 close +} -body { + # At this point, the cache contains all of table t1 and none of index i1. The + # cache is out of date. When the bug existed and the right xShmLock() fails + # in the following statement, the internal cache of the WAL header was + # being updated, but the contents of the page-cache not flushed. This causes + # the integrity-check in the "-test" code to fail, as it is comparing the + # cached (out-of-date) version of table t1 with the on disk (up-to-date) + # version of index i1. + # + execsql { SELECT min(rowid) FROM t1 } +} -test { + faultsim_test_result {0 1} + faultsim_integrity_check +} + +finish_test diff --git a/test/tkt-38cb5df375.test b/test/tkt-38cb5df375.test new file mode 100644 index 00000000..47b0b551 --- /dev/null +++ b/test/tkt-38cb5df375.test @@ -0,0 +1,321 @@ +# 2010 October 6 +# +# 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 implements regression tests for SQLite library. Specifically, +# it tests that ticket [38cb5df375078d3f9711482d2a1615d09f6b3f33] has +# been resolved. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_test tkt-38cb5df375.0 { + execsql { + CREATE TABLE t1(a); + INSERT INTO t1 VALUES(1); + INSERT INTO t1 VALUES(2); + INSERT INTO t1 SELECT a+2 FROM t1; + INSERT INTO t1 SELECT a+4 FROM t1; + } +} {} + +foreach ii {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16} { + do_test tkt-38cb5df375.1.$ii { + execsql { + SELECT * FROM (SELECT * FROM t1 ORDER BY a) + UNION ALL SELECT 9 FROM (SELECT a FROM t1) + LIMIT $::ii; + } + } [lrange {1 2 3 4 5 6 7 8 9 9 9 9 9 9 9 9} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16} { + do_test tkt-38cb5df375.2.$ii { + execsql { + SELECT 9 FROM (SELECT * FROM t1) + UNION ALL SELECT a FROM (SELECT a FROM t1 ORDER BY a) + LIMIT $::ii; + } + } [lrange {9 9 9 9 9 9 9 9 1 2 3 4 5 6 7 8} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16} { + do_test tkt-38cb5df375.3.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a) + UNION ALL SELECT a FROM (SELECT a FROM t1 ORDER BY a) + LIMIT $::ii; + } + } [lrange {1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16} { + do_test tkt-38cb5df375.4.$ii { + execsql { + SELECT 0 FROM (SELECT * FROM t1) + UNION ALL SELECT 9 FROM (SELECT a FROM t1) + LIMIT $::ii; + } + } [lrange {0 0 0 0 0 0 0 0 9 9 9 9 9 9 9 9} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4} { + do_test tkt-38cb5df375.5.$ii { + execsql { + SELECT 0 FROM (SELECT * FROM t1) + UNION SELECT 9 FROM (SELECT a FROM t1) + LIMIT $::ii; + } + } [lrange {0 9} 0 [expr {$ii-1}]] +} + +foreach ii {1 2 3 4 5 6 7 8 9 10 11} { + do_test tkt-38cb5df375.11.$ii { + execsql { + SELECT * FROM (SELECT * FROM t1 ORDER BY a LIMIT 3) + UNION ALL SELECT 9 FROM (SELECT a FROM t1) + LIMIT $::ii; + } + } [lrange {1 2 3 9 9 9 9 9 9 9 9} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7 8 9 10 11} { + do_test tkt-38cb5df375.12.$ii { + execsql { + SELECT 9 FROM (SELECT * FROM t1) + UNION ALL SELECT a FROM (SELECT a FROM t1 ORDER BY a LIMIT 3) + LIMIT $::ii; + } + } [lrange {9 9 9 9 9 9 9 9 1 2 3} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6} { + do_test tkt-38cb5df375.13.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a LIMIT 3) + UNION ALL SELECT a FROM (SELECT a FROM t1 ORDER BY a LIMIT 3) + LIMIT $::ii; + } + } [lrange {1 2 3 1 2 3} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6} { + do_test tkt-38cb5df375.14.$ii { + execsql { + SELECT 0 FROM (SELECT * FROM t1 LIMIT 3) + UNION ALL SELECT 9 FROM (SELECT a FROM t1 LIMIT 3) + LIMIT $::ii; + } + } [lrange {0 0 0 9 9 9} 0 [expr {$ii-1}]] +} + +foreach ii {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16} { + do_test tkt-38cb5df375.21.$ii { + execsql { + SELECT * FROM (SELECT * FROM t1 ORDER BY a) + UNION ALL SELECT 9 FROM (SELECT a FROM t1) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 2 3 4 5 6 7 8 9 9 9 9 9 9 9 9} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16} { + do_test tkt-38cb5df375.22.$ii { + execsql { + SELECT 9 FROM (SELECT * FROM t1) + UNION ALL SELECT a FROM (SELECT a FROM t1 ORDER BY a) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 2 3 4 5 6 7 8 9 9 9 9 9 9 9 9} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16} { + do_test tkt-38cb5df375.23.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a) + UNION ALL SELECT a FROM (SELECT a FROM t1 ORDER BY a) + ORDER BY 1 DESC + LIMIT $::ii; + } + } [lrange {8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16} { + do_test tkt-38cb5df375.24.$ii { + execsql { + SELECT 0 FROM (SELECT * FROM t1) + UNION ALL SELECT 9 FROM (SELECT a FROM t1) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {0 0 0 0 0 0 0 0 9 9 9 9 9 9 9 9} 0 [expr {$ii-1}]] +} + +foreach ii {1 2 3 4 5 6 7 8 9 10 11} { + do_test tkt-38cb5df375.31.$ii { + execsql { + SELECT * FROM (SELECT * FROM t1 ORDER BY a LIMIT 3) + UNION ALL SELECT 9 FROM (SELECT a FROM t1) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 2 3 9 9 9 9 9 9 9 9} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7 8 9 10 11} { + do_test tkt-38cb5df375.32.$ii { + execsql { + SELECT 9 FROM (SELECT * FROM t1) + UNION ALL SELECT a FROM (SELECT a FROM t1 ORDER BY a LIMIT 3) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 2 3 9 9 9 9 9 9 9 9} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7} { + do_test tkt-38cb5df375.33.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a LIMIT 4) + UNION ALL SELECT 90+a FROM (SELECT a FROM t1 ORDER BY a LIMIT 3) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 2 3 4 91 92 93} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7} { + do_test tkt-38cb5df375.34.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a LIMIT 2) + UNION ALL SELECT a FROM (SELECT a FROM t1 ORDER BY a LIMIT 5) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 1 2 2 3 4 5} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7} { + do_test tkt-38cb5df375.35.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a LIMIT 5) + UNION ALL SELECT a FROM (SELECT a FROM t1 ORDER BY a LIMIT 2) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 1 2 2 3 4 5} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7} { + do_test tkt-38cb5df375.35b.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a LIMIT 5) + UNION ALL SELECT a+10 FROM (SELECT a FROM t1 ORDER BY a LIMIT 2) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 2 3 4 5 11 12} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7} { + do_test tkt-38cb5df375.35c.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a LIMIT 5) + UNION SELECT a+10 FROM (SELECT a FROM t1 ORDER BY a LIMIT 2) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 2 3 4 5 11 12} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7} { + do_test tkt-38cb5df375.35d.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a LIMIT 5) + INTERSECT SELECT a FROM (SELECT a FROM t1 ORDER BY a LIMIT 2) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 2} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7} { + do_test tkt-38cb5df375.35e.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a LIMIT 5) + EXCEPT SELECT a FROM (SELECT a FROM t1 ORDER BY a LIMIT 2) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {3 4 5} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7} { + do_test tkt-38cb5df375.36.$ii { + execsql { + SELECT 0 FROM (SELECT * FROM t1 LIMIT 3) + UNION ALL SELECT 9 FROM (SELECT a FROM t1 LIMIT 4) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {0 0 0 9 9 9 9} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7} { + do_test tkt-38cb5df375.37.$ii { + execsql { + SELECT 0 FROM (SELECT * FROM t1 LIMIT 3) + UNION SELECT 9 FROM (SELECT a FROM t1 LIMIT 4) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {0 9} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7} { + do_test tkt-38cb5df375.38.$ii { + execsql { + SELECT 0 FROM (SELECT * FROM t1 LIMIT 3) + EXCEPT SELECT 9 FROM (SELECT a FROM t1 LIMIT 4) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {0} 0 [expr {$ii-1}]] +} + +foreach ii {1 2 3 4 5 6 7 8 9} { + do_test tkt-38cb5df375.41.$ii { + execsql { + SELECT 0 FROM (SELECT * FROM t1 LIMIT 3) + UNION ALL SELECT 9 FROM (SELECT a FROM t1 LIMIT 4) + UNION ALL SELECT 88 FROM (SELECT a FROM t1 LIMIT 2) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {0 0 0 9 9 9 9 88 88} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7 8 9} { + do_test tkt-38cb5df375.42.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a LIMIT 3) + UNION ALL SELECT a+10 FROM (SELECT a FROM t1 ORDER BY a LIMIT 4) + UNION ALL SELECT a+20 FROM (SELECT a FROM t1 ORDER BY a LIMIT 2) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 2 3 11 12 13 14 21 22} 0 [expr {$ii-1}]] +} +foreach ii {1 2 3 4 5 6 7 8 9} { + do_test tkt-38cb5df375.43.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a LIMIT 3) + UNION SELECT a+10 FROM (SELECT a FROM t1 ORDER BY a LIMIT 4) + UNION SELECT a+20 FROM (SELECT a FROM t1 ORDER BY a LIMIT 2) + ORDER BY 1 + LIMIT $::ii; + } + } [lrange {1 2 3 11 12 13 14 21 22} 0 [expr {$ii-1}]] +} + +foreach ii {1 2 3 4 5 6 7} { + set jj [expr {7-$ii}] + do_test tkt-38cb5df375.51.$ii { + execsql { + SELECT a FROM (SELECT * FROM t1 ORDER BY a) + EXCEPT SELECT a FROM (SELECT a FROM t1 ORDER BY a LIMIT $::ii) + ORDER BY a DESC + LIMIT $::jj; + } + } [lrange {8 7 6 5 4 3 2 1} 0 [expr {$jj-1}]] +} + + +finish_test diff --git a/test/tkt-3998683a16.test b/test/tkt-3998683a16.test new file mode 100644 index 00000000..919dc233 --- /dev/null +++ b/test/tkt-3998683a16.test @@ -0,0 +1,46 @@ +# 2010 September 30 +# +# 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 implements regression tests for SQLite library. Specifically, +# it tests that ticket [3998683a16a7076e08f5585c1f4816414c8c653a] where in +# floating point values with a decimal point at the beginning or end +# of the mantissa are used. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_test tkt-3998683a16.1 { + db eval { + CREATE TABLE t1(x, y REAL); + INSERT INTO t1 VALUES(1, '1.0'); + INSERT INTO t1 VALUES(2, '.125'); + INSERT INTO t1 VALUES(3, '123.'); + INSERT INTO t1 VALUES(4, '123.e+2'); + INSERT INTO t1 VALUES(5, '.125e+3'); + INSERT INTO t1 VALUES(6, '123e4'); + INSERT INTO t1 VALUES(11, ' 1.0'); + INSERT INTO t1 VALUES(12, ' .125'); + INSERT INTO t1 VALUES(13, ' 123.'); + INSERT INTO t1 VALUES(14, ' 123.e+2'); + INSERT INTO t1 VALUES(15, ' .125e+3'); + INSERT INTO t1 VALUES(16, ' 123e4'); + INSERT INTO t1 VALUES(21, '1.0 '); + INSERT INTO t1 VALUES(22, '.125 '); + INSERT INTO t1 VALUES(23, '123. '); + INSERT INTO t1 VALUES(24, '123.e+2 '); + INSERT INTO t1 VALUES(25, '.125e+3 '); + INSERT INTO t1 VALUES(26, '123e4 '); + SELECT x FROM t1 WHERE typeof(y)=='real' ORDER BY x; + } +} {1 2 3 4 5 6 11 12 13 14 15 16 21 22 23 24 25 26} + +finish_test diff --git a/test/tkt-5d863f876e.test b/test/tkt-5d863f876e.test new file mode 100644 index 00000000..0a9017de --- /dev/null +++ b/test/tkt-5d863f876e.test @@ -0,0 +1,53 @@ +# 2011 January 15 +# +# 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 implements regression tests for SQLite library. +# +# This file implements tests to verify that ticket [5d863f876e] has been +# fixed. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl + +do_multiclient_test tn { + do_test $tn.1 { + sql1 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + PRAGMA journal_mode = WAL; + VACUUM; + PRAGMA journal_mode = DELETE; + } + } {wal delete} + + do_test $tn.2 { + sql2 { SELECT * FROM t1 } + } {1 2 3 4} + + do_test $tn.3 { + sql1 { + INSERT INTO t1 VALUES(5, 6); + PRAGMA journal_mode = WAL; + VACUUM; + PRAGMA journal_mode = DELETE; + } + } {wal delete} + + do_test $tn.2 { + sql2 { PRAGMA integrity_check } + } {ok} +} + + +finish_test diff --git a/test/tkt-78e04e52ea.test b/test/tkt-78e04e52ea.test index 9cd25f5d..9524d845 100644 --- a/test/tkt-78e04e52ea.test +++ b/test/tkt-78e04e52ea.test @@ -44,7 +44,7 @@ do_test tkt-78e04-1.4 { execsql { EXPLAIN QUERY PLAN SELECT * FROM "" WHERE "" LIKE 'abc%'; } -} {0 0 {TABLE }} +} {0 0 0 {SCAN TABLE (~500000 rows)}} do_test tkt-78e04-1.5 { execsql { DROP TABLE ""; @@ -57,12 +57,12 @@ do_test tkt-78e04-2.1 { CREATE INDEX "" ON t2(x); EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE x=5; } -} {0 0 {TABLE t2 WITH INDEX }} +} {0 0 0 {SEARCH TABLE t2 USING COVERING INDEX (x=?) (~10 rows)}} do_test tkt-78e04-2.2 { execsql { DROP INDEX ""; EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE x=2; } -} {0 0 {TABLE t2}} +} {0 0 0 {SCAN TABLE t2 (~100000 rows)}} finish_test diff --git a/test/tkt-80ba201079.test b/test/tkt-80ba201079.test new file mode 100644 index 00000000..95e99b5a --- /dev/null +++ b/test/tkt-80ba201079.test @@ -0,0 +1,191 @@ +# 2010 December 6 +# +# 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 implements regression tests for SQLite library. Specifically, +# it tests that ticket [80ba201079ea608071d22a57856b940ea3ac53ce] is +# resolved. That ticket is about an incorrect result that appears when +# an index is added. The root cause is that a constant is being used +# without initialization when the OR optimization applies in the WHERE clause. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix tkt-80ba2 + +do_test tkt-80ba2-100 { + db eval { + CREATE TABLE t1(a); + INSERT INTO t1 VALUES('A'); + CREATE TABLE t2(b); + INSERT INTO t2 VALUES('B'); + CREATE TABLE t3(c); + INSERT INTO t3 VALUES('C'); + SELECT * FROM t1, t2 + WHERE (a='A' AND b='X') + OR (a='A' AND EXISTS (SELECT * FROM t3 WHERE c='C')); + } +} {A B} +do_test tkt-80ba2-101 { + db eval { + CREATE INDEX i1 ON t1(a); + SELECT * FROM t1, t2 + WHERE (a='A' AND b='X') + OR (a='A' AND EXISTS (SELECT * FROM t3 WHERE c='C')); + } +} {A B} +do_test tkt-80ba2-102 { + optimization_control db factor-constants 0 + db cache flush + db eval { + SELECT * FROM t1, t2 + WHERE (a='A' AND b='X') + OR (a='A' AND EXISTS (SELECT * FROM t3 WHERE c='C')); + } +} {A B} +optimization_control db all 1 + +# Verify that the optimization_control command is actually working +# +do_test tkt-80ba2-150 { + optimization_control db factor-constants 1 + db cache flush + set x1 [db eval {EXPLAIN + SELECT * FROM t1, t2 + WHERE (a='A' AND b='X') + OR (a='A' AND EXISTS (SELECT * FROM t3 WHERE c='C'));}] + optimization_control db factor-constants 0 + db cache flush + set x2 [db eval {EXPLAIN + SELECT * FROM t1, t2 + WHERE (a='A' AND b='X') + OR (a='A' AND EXISTS (SELECT * FROM t3 WHERE c='C'));}] + + expr {$x1==$x2} +} {0} + +do_test tkt-80ba2-200 { + db eval { + CREATE TABLE entry_types ( + id integer primary key, + name text + ); + INSERT INTO "entry_types" VALUES(100,'cli_command'); + INSERT INTO "entry_types" VALUES(300,'object_change'); + CREATE TABLE object_changes ( + change_id integer primary key, + system_id int, + obj_id int, + obj_context text, + change_type int, + command_id int + ); + INSERT INTO "object_changes" VALUES(1551,1,114608,'exported_pools',1,2114); + INSERT INTO "object_changes" VALUES(2048,1,114608,'exported_pools',2,2319); + CREATE TABLE timeline ( + rowid integer primary key, + timestamp text, + system_id int, + entry_type int, + entry_id int + ); + INSERT INTO "timeline" VALUES(6735,'2010-11-21 17:08:27.000',1,300,2048); + INSERT INTO "timeline" VALUES(6825,'2010-11-21 17:09:21.000',1,300,2114); + SELECT entry_type, + entry_types.name, + entry_id + FROM timeline JOIN entry_types ON entry_type = entry_types.id + WHERE (entry_types.name = 'cli_command' AND entry_id=2114) + OR (entry_types.name = 'object_change' + AND entry_id IN (SELECT change_id + FROM object_changes + WHERE obj_context = 'exported_pools')); + } +} {300 object_change 2048} +do_test tkt-80ba2-201 { + db eval { + CREATE INDEX timeline_entry_id_idx on timeline(entry_id); + SELECT entry_type, + entry_types.name, + entry_id + FROM timeline JOIN entry_types ON entry_type = entry_types.id + WHERE (entry_types.name = 'cli_command' AND entry_id=2114) + OR (entry_types.name = 'object_change' + AND entry_id IN (SELECT change_id + FROM object_changes + WHERE obj_context = 'exported_pools')); + } +} {300 object_change 2048} +do_test tkt-80ba2-202 { + optimization_control db factor-constants 0 + db cache flush + db eval { + SELECT entry_type, + entry_types.name, + entry_id + FROM timeline JOIN entry_types ON entry_type = entry_types.id + WHERE (entry_types.name = 'cli_command' AND entry_id=2114) + OR (entry_types.name = 'object_change' + AND entry_id IN (SELECT change_id + FROM object_changes + WHERE obj_context = 'exported_pools')); + } +} {300 object_change 2048} + +#------------------------------------------------------------------------- +# + +drop_all_tables +do_execsql_test 301 { + CREATE TABLE t1(a, b, c); + CREATE INDEX i1 ON t1(a); + CREATE INDEX i2 ON t1(b); + CREATE TABLE t2(d, e); + + INSERT INTO t1 VALUES('A', 'B', 'C'); + INSERT INTO t2 VALUES('D', 'E'); +} + +do_execsql_test 302 { + SELECT * FROM t1, t2 WHERE + (a='A' AND d='E') OR + (b='B' AND c IN ('C', 'D', 'E')) +} {A B C D E} + +do_execsql_test 303 { + SELECT * FROM t1, t2 WHERE + (a='A' AND d='E') OR + (b='B' AND c IN (SELECT c FROM t1)) +} {A B C D E} + +do_execsql_test 304 { + SELECT * FROM t1, t2 WHERE + (a='A' AND d='E') OR + (b='B' AND c IN (SELECT 'B' UNION SELECT 'C' UNION SELECT 'D')) +} {A B C D E} + +do_execsql_test 305 { + SELECT * FROM t1, t2 WHERE + (b='B' AND c IN ('C', 'D', 'E')) OR + (a='A' AND d='E') +} {A B C D E} + +do_execsql_test 306 { + SELECT * FROM t1, t2 WHERE + (b='B' AND c IN (SELECT c FROM t1)) OR + (a='A' AND d='E') +} {A B C D E} + +do_execsql_test 307 { + SELECT * FROM t1, t2 WHERE + (b='B' AND c IN (SELECT 'B' UNION SELECT 'C' UNION SELECT 'D')) OR + (a='A' AND d='E') +} {A B C D E} + +finish_test diff --git a/test/tkt-8454a207b9.test b/test/tkt-8454a207b9.test new file mode 100644 index 00000000..88a8614f --- /dev/null +++ b/test/tkt-8454a207b9.test @@ -0,0 +1,67 @@ +# 2010 September 30 +# +# 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 implements regression tests for SQLite library. Specifically, +# it tests that ticket [8454a207b9fd2243c4c6b7a73f67ea0315717c1a]. Verify +# that a negative default value on an added text column actually comes +# out negative. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_test tkt-8454a207b9.1 { + db eval { + CREATE TABLE t1(a); + INSERT INTO t1 VALUES(1); + ALTER TABLE t1 ADD COLUMN b TEXT DEFAULT -123.0; + SELECT b, typeof(b) FROM t1; + } +} {-123.0 text} +do_test tkt-8454a207b9.2 { + db eval { + ALTER TABLE t1 ADD COLUMN c TEXT DEFAULT -123.5; + SELECT c, typeof(c) FROM t1; + } +} {-123.5 text} +do_test tkt-8454a207b9.3 { + db eval { + ALTER TABLE t1 ADD COLUMN d TEXT DEFAULT -'hello'; + SELECT d, typeof(d) FROM t1; + } +} {0 text} +do_test tkt-8454a207b9.4 { + db eval { + ALTER TABLE t1 ADD COLUMN e DEFAULT -123.0; + SELECT e, typeof(e) FROM t1; + } +} {-123 integer} +do_test tkt-8454a207b9.5 { + db eval { + ALTER TABLE t1 ADD COLUMN f DEFAULT -123.5; + SELECT f, typeof(f) FROM t1; + } +} {-123.5 real} +do_test tkt-8454a207b9.6 { + db eval { + ALTER TABLE t1 ADD COLUMN g DEFAULT -9223372036854775808; + SELECT g, typeof(g) FROM t1; + } +} {-9223372036854775808 integer} +do_test tkt-8454a207b9.7 { + db eval { + ALTER TABLE t1 ADD COLUMN h DEFAULT 9223372036854775807; + SELECT h, typeof(h) FROM t1; + } +} {9223372036854775807 integer} + + +finish_test diff --git a/test/tkt-b351d95f9.test b/test/tkt-b351d95f9.test new file mode 100644 index 00000000..5bd5ee48 --- /dev/null +++ b/test/tkt-b351d95f9.test @@ -0,0 +1,47 @@ +# 2010 September 28 +# +# 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 implements regression tests for SQLite library. Specifically, +# it tests that ticket [b351d95f9cd5ef17e9d9dbae18f5ca8611190001] has been +# resolved. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl + +do_test tkt-b351d95.1 { + execsql { + CREATE table t1(a,b); + INSERT INTO t1 VALUES('name1','This is a test'); + INSERT INTO t1 VALUES('name2','xyz'); + CREATE TABLE t2(x,y); + INSERT INTO t2 SELECT a, CASE b WHEN 'xyz' THEN null ELSE b END FROM t1; + SELECT x, y FROM t2 ORDER BY x; + } +} {name1 {This is a test} name2 {}} + +do_test tkt-b351d95.2 { + execsql { + DELETE FROM t2; + INSERT INTO t2 SELECT a, coalesce(b,a) FROM t1; + SELECT x, y FROM t2 ORDER BY x; + } +} {name1 {This is a test} name2 xyz} +do_test tkt-b351d95.3 { + execsql { + DELETE FROM t2; + INSERT INTO t2 SELECT a, coalesce(b,a) FROM t1; + SELECT x, y BETWEEN 'xy' AND 'xz' FROM t2 ORDER BY x; + } +} {name1 0 name2 1} + +finish_test diff --git a/test/tkt-f3e5abed55.test b/test/tkt-f3e5abed55.test index d574480b..241c1612 100644 --- a/test/tkt-f3e5abed55.test +++ b/test/tkt-f3e5abed55.test @@ -61,53 +61,57 @@ db2 close # file "test.db-journal", a snapshot of the current file-system contents # is taken. # -testvfs tvfs -default 1 -tvfs script xDelete -tvfs filter xDelete -proc xDelete {method file args} { - if {[file tail $file] == "test.db-journal"} { - faultsim_save - tvfs filter {} +# This test will not work with an in-memory journal. +# +if {[permutation]!="inmemory_journal"} { + testvfs tvfs -default 1 + tvfs script xDelete + tvfs filter xDelete + proc xDelete {method file args} { + if {[file tail $file] == "test.db-journal"} { + faultsim_save + tvfs filter {} + } + return "SQLITE_OK" } - return "SQLITE_OK" + + sqlite3 db test.db + sqlite3 db2 test.db + do_test tkt-f3e5abed55-2.1 { + execsql { + ATTACH 'test.db2' AS aux; + BEGIN; + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t2 VALUES(3, 4); + } + } {} + do_test tkt-f3e5abed55-2.2 { + execsql { BEGIN; SELECT * FROM t1 } db2 + } {1 2} + do_test tkt-f3e5abed55-2.3 { + catchsql COMMIT + } {1 {database is locked}} + + do_test tkt-f3e5abed55-2.4 { + execsql COMMIT db2 + execsql { + COMMIT; + SELECT * FROM t1; + SELECT * FROM t2; + } + } {1 2 3 4 1 2 3 4} + do_test tkt-f3e5abed55-2.5 { + db close + db2 close + faultsim_restore_and_reopen + execsql { + ATTACH 'test.db2' AS aux; + SELECT * FROM t1; + SELECT * FROM t2; + } + } {1 2 3 4 1 2 3 4} } -sqlite3 db test.db -sqlite3 db2 test.db -do_test tkt-f3e5abed55-2.1 { - execsql { - ATTACH 'test.db2' AS aux; - BEGIN; - INSERT INTO t1 VALUES(3, 4); - INSERT INTO t2 VALUES(3, 4); - } -} {} -do_test tkt-f3e5abed55-2.2 { - execsql { BEGIN; SELECT * FROM t1 } db2 -} {1 2} -do_test tkt-f3e5abed55-2.3 { - catchsql COMMIT -} {1 {database is locked}} - -do_test tkt-f3e5abed55-2.4 { - execsql COMMIT db2 - execsql { - COMMIT; - SELECT * FROM t1; - SELECT * FROM t2; - } -} {1 2 3 4 1 2 3 4} -do_test tkt-f3e5abed55-2.5 { - db close - db2 close - faultsim_restore_and_reopen - execsql { - ATTACH 'test.db2' AS aux; - SELECT * FROM t1; - SELECT * FROM t2; - } -} {1 2 3 4 1 2 3 4} - finish_test diff --git a/test/tkt1667.test b/test/tkt1667.test index bccb7c69..9883a208 100644 --- a/test/tkt1667.test +++ b/test/tkt1667.test @@ -34,7 +34,7 @@ file delete -force test.db test.db-journal set first_ptrmap_page [expr 1024/5 + 3] sqlite3_test_control_pending_byte [expr 1024 * ($first_ptrmap_page-1)] -sqlite db test.db +sqlite3 db test.db do_test tkt1667-1 { execsql { diff --git a/test/tkt3442.test b/test/tkt3442.test index 86bd695c..ae03949d 100644 --- a/test/tkt3442.test +++ b/test/tkt3442.test @@ -49,10 +49,10 @@ proc EQP {sql} { ifcapable explain { do_test tkt3442-1.2 { EQP { SELECT node FROM listhash WHERE id='5000' LIMIT 1; } - } {0 0 {TABLE listhash WITH INDEX ididx}} + } {0 0 0 {SEARCH TABLE listhash USING INDEX ididx (id=?) (~1 rows)}} do_test tkt3442-1.3 { EQP { SELECT node FROM listhash WHERE id="5000" LIMIT 1; } - } {0 0 {TABLE listhash WITH INDEX ididx}} + } {0 0 0 {SEARCH TABLE listhash USING INDEX ididx (id=?) (~1 rows)}} } @@ -61,7 +61,7 @@ ifcapable explain { ifcapable explain { do_test tkt3442-1.4 { EQP { SELECT node FROM listhash WHERE id=5000 LIMIT 1; } - } {0 0 {TABLE listhash WITH INDEX ididx}} + } {0 0 0 {SEARCH TABLE listhash USING INDEX ididx (id=?) (~1 rows)}} } do_test tkt3442-1.5 { catchsql { diff --git a/test/tkt3757.test b/test/tkt3757.test index 14bfb23b..011beb5c 100644 --- a/test/tkt3757.test +++ b/test/tkt3757.test @@ -35,9 +35,9 @@ do_test tkt3757-1.1 { CREATE TABLE t2(a INTEGER, b TEXT); INSERT INTO t2 VALUES(2, 'two'); ANALYZE; - SELECT * FROM sqlite_stat1; + SELECT * FROM sqlite_stat1 ORDER BY 1, 2; } -} {t1 t1i1 {1 1 1}} +} {t1 t1i1 {1 1 1} t2 {} 1} # Modify statistics in order to make the optimizer then that: # diff --git a/test/trace2.test b/test/trace2.test new file mode 100644 index 00000000..2cf50d08 --- /dev/null +++ b/test/trace2.test @@ -0,0 +1,152 @@ +# 2011 Jan 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 implements regression tests for SQLite library. +# +# This file implements tests for the "sqlite3_trace()" API. Specifically, +# it tests the special handling of nested SQL statements (those executed +# by virtual table or user function callbacks). These statements are treated +# differently in two respects: +# +# 1. Each line of the statement is prefixed with "-- " to turn it into +# an SQL comment. +# +# 2. Parameter expansion is not performed. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +ifcapable !trace { finish_test ; return } +set ::testprefix trace2 + +proc sql {zSql} { db one $zSql } +proc trace {zSql} { lappend ::trace $zSql } + +db func sql sql +db trace trace + +proc do_trace_test {tn sql expected} { + # Test that the list of string passed to the trace callback when $sql + # is executed is equivalent to the list of strings in $expected. + # + set ::trace [list] + execsql $sql + uplevel do_test $tn [list {set ::trace}] [list [list {*}$expected]] +} + +proc do_trace_select_test {tn sql expected} { + + uplevel [list do_trace_test ${tn}.a $sql $expected] + + # Now execute each SQL statement passed to the trace callback in the + # block above. Check that this causes the same set of strings to be + # passed to the trace callback again. i.e. that executing the output + # of the trace callback is equivalent to the SQL script in $sql. + # + set sqllist $::trace + set ::trace [list] + foreach item $sqllist { execsql $item } + uplevel do_test $tn.b [list {set ::trace}] [list $sqllist] +} + +do_trace_select_test 1.1 { + SELECT 1, 2, 3; +} { + "SELECT 1, 2, 3;" +} + +do_trace_select_test 1.2 { + SELECT sql('SELECT 1, 2, 3'); +} { + "SELECT sql('SELECT 1, 2, 3');" + "-- SELECT 1, 2, 3" +} + +do_trace_select_test 1.3 { + SELECT sql('SELECT 1, + 2, + 3' + ); +} { + "SELECT sql('SELECT 1, + 2, + 3' + );" + "-- SELECT 1, +-- 2, +-- 3" +} + +do_trace_select_test 1.4 { + SELECT sql('SELECT 1, + + + 3' + ); +} { + "SELECT sql('SELECT 1, + + + 3' + );" + "-- SELECT 1, +-- +-- +-- 3" +} + +do_trace_select_test 1.5 { + SELECT $var, sql('SELECT 1, + $var, + 3' + ); +} { + "SELECT NULL, sql('SELECT 1, + $var, + 3' + );" + "-- SELECT 1, +-- $var, +-- 3" +} + +ifcapable fts3 { + do_execsql_test 2.1 { + CREATE VIRTUAL TABLE x1 USING fts4; + INSERT INTO x1 VALUES('Cloudy, with a high near 16'); + INSERT INTO x1 VALUES('Wind chill values as low as -13'); + } + + do_trace_test 2.2 { + INSERT INTO x1 VALUES('North northwest wind between 8 and 14 mph'); + } { + "INSERT INTO x1 VALUES('North northwest wind between 8 and 14 mph');" + "-- INSERT INTO 'main'.'x1_content' VALUES(?,?)" + "-- REPLACE INTO 'main'.'x1_docsize' VALUES(?,?)" + "-- SELECT value FROM 'main'.'x1_stat' WHERE id=0" + "-- REPLACE INTO 'main'.'x1_stat' VALUES(0,?)" + "-- SELECT (SELECT max(idx) FROM 'main'.'x1_segdir' WHERE level = ?) + 1" + "-- SELECT coalesce((SELECT max(blockid) FROM 'main'.'x1_segments') + 1, 1)" + "-- INSERT INTO 'main'.'x1_segdir' VALUES(?,?,?,?,?,?)" + } + + do_trace_test 2.3 { + INSERT INTO x1(x1) VALUES('optimize'); + } { + "INSERT INTO x1(x1) VALUES('optimize');" + "-- SELECT count(*), max(level) FROM 'main'.'x1_segdir'" + "-- SELECT idx, start_block, leaves_end_block, end_block, root FROM 'main'.'x1_segdir' ORDER BY level DESC, idx ASC" + "-- SELECT coalesce((SELECT max(blockid) FROM 'main'.'x1_segments') + 1, 1)" + "-- DELETE FROM 'main'.'x1_segdir'" + "-- INSERT INTO 'main'.'x1_segdir' VALUES(?,?,?,?,?,?)" + } +} + +finish_test diff --git a/test/triggerC.test b/test/triggerC.test index d3300b42..694d069d 100644 --- a/test/triggerC.test +++ b/test/triggerC.test @@ -937,6 +937,18 @@ do_test triggerC-12.2 { execsql { SELECT count(*) FROM sqlite_master } } {1} +do_execsql_test triggerC-13.1 { + PRAGMA recursive_triggers = ON; + CREATE TABLE t12(a, b); + INSERT INTO t12 VALUES(1, 2); + CREATE TRIGGER tr12 AFTER UPDATE ON t12 BEGIN + UPDATE t12 SET a=new.a+1, b=new.b+1; + END; +} {} +do_catchsql_test triggerC-13.2 { + UPDATE t12 SET a=a+1, b=b+1; +} {1 {too many levels of trigger recursion}} + finish_test diff --git a/test/types.test b/test/types.test index a2dd6322..62a8efca 100644 --- a/test/types.test +++ b/test/types.test @@ -135,7 +135,7 @@ execsql { # level. Return a list that is the length of each record # in the table, in the tables default scanning order. proc record_sizes {rootpage} { - set bt [btree_open test.db 10 0] + set bt [btree_open test.db 10] btree_begin_transaction $bt set c [btree_cursor $bt $rootpage 0] btree_first $c diff --git a/test/vacuum.test b/test/vacuum.test index 256730b3..34be57ce 100644 --- a/test/vacuum.test +++ b/test/vacuum.test @@ -300,14 +300,38 @@ do_test vacuum-7.0 { CREATE TABLE t1(t); VACUUM; } db2 +} {} +do_test vacuum-7.1 { execsql { CREATE TABLE t2(t); CREATE TABLE t3(t); DROP TABLE t2; + PRAGMA freelist_count; + } +} {1} +do_test vacuum-7.2 { + execsql { VACUUM; pragma integrity_check; } db2 } {ok} +do_test vacuum-7.3 { + execsql { PRAGMA freelist_count; } db2 +} {0} +ifcapable autovacuum { + do_test vacuum-7.4 { + execsql { PRAGMA auto_vacuum } db2 + } {0} + do_test vacuum-7.5 { + execsql { PRAGMA auto_vacuum = 1} db2 + execsql { PRAGMA auto_vacuum } db2 + } {0} + do_test vacuum-7.6 { + execsql { PRAGMA auto_vacuum = 1} db2 + execsql { VACUUM } db2 + execsql { PRAGMA auto_vacuum } db2 + } {1} +} db2 close # Ticket #873. VACUUM a database that has ' in its name. diff --git a/test/vacuum2.test b/test/vacuum2.test index 3cd61653..1a054cc6 100644 --- a/test/vacuum2.test +++ b/test/vacuum2.test @@ -182,4 +182,45 @@ ifcapable autovacuum { } {2} } + +#------------------------------------------------------------------------- +# The following block of tests verify the behaviour of the library when +# a database is VACUUMed when there are one or more unfinalized SQL +# statements reading the same database using the same db handle. +# +db close +forcedelete test.db +sqlite3 db test.db +do_execsql_test vacuum2-5.1 { + CREATE TABLE t1(a PRIMARY KEY, b UNIQUE); + INSERT INTO t1 VALUES(1, randomblob(500)); + INSERT INTO t1 SELECT a+1, randomblob(500) FROM t1; -- 2 + INSERT INTO t1 SELECT a+2, randomblob(500) FROM t1; -- 4 + INSERT INTO t1 SELECT a+4, randomblob(500) FROM t1; -- 8 + INSERT INTO t1 SELECT a+8, randomblob(500) FROM t1; -- 16 +} {} + +do_test vacuum2-5.2 { + list [catch { + db eval {SELECT a, b FROM t1} { if {$a == 8} { execsql VACUUM } } + } msg] $msg +} {1 {cannot VACUUM - SQL statements in progress}} + +do_test vacuum2-5.3 { + list [catch { + db eval {SELECT 1, 2, 3} { execsql VACUUM } + } msg] $msg +} {1 {cannot VACUUM - SQL statements in progress}} + +do_test vacuum2-5.4 { + set res "" + set res2 "" + db eval {SELECT a, b FROM t1 WHERE a<=10} { + if {$a==6} { set res [catchsql VACUUM] } + lappend res2 $a + } + lappend res2 $res +} {1 2 3 4 5 6 7 8 9 10 {1 {cannot VACUUM - SQL statements in progress}}} + + finish_test diff --git a/test/vtab1.test b/test/vtab1.test index bafd29ae..a9aca503 100644 --- a/test/vtab1.test +++ b/test/vtab1.test @@ -1163,5 +1163,20 @@ ifcapable altertable { incr tn } +# The following test case exposes an instance in sqlite3_declare_vtab() +# an error message was set using a call similar to sqlite3_mprintf(zErr), +# where zErr is an arbitrary string. This is no good if the string contains +# characters that can be mistaken for printf() formatting directives. +# +do_test vtab1-17.1 { + execsql { + PRAGMA writable_schema = 1; + INSERT INTO sqlite_master VALUES( + 'table', 't3', 't3', 0, 'INSERT INTO "%s%s" VALUES(1)' + ); + } + catchsql { CREATE VIRTUAL TABLE t4 USING echo(t3); } +} {1 {vtable constructor failed: t4}} + unset -nocomplain echo_module_begin_fail finish_test diff --git a/test/wal2.test b/test/wal2.test index 03c0018f..e31fe794 100644 --- a/test/wal2.test +++ b/test/wal2.test @@ -19,6 +19,8 @@ source $testdir/lock_common.tcl source $testdir/malloc_common.tcl source $testdir/wal_common.tcl +set testprefix wal2 + ifcapable !wal {finish_test ; return } proc set_tvfs_hdr {file args} { @@ -430,13 +432,16 @@ do_test wal2-6.1.1 { sqlite3 db test.db execsql { Pragma Journal_Mode = Wal; - Pragma Locking_Mode = Exclusive; } -} {wal exclusive} +} {wal} do_test wal2-6.1.2 { execsql { PRAGMA lock_status } } {main unlocked temp closed} do_test wal2-6.1.3 { + execsql { + SELECT * FROM sqlite_master; + Pragma Locking_Mode = Exclusive; + } execsql { BEGIN; CREATE TABLE t1(a, b); @@ -486,6 +491,7 @@ do_test wal2-6.2.2 { do_test wal2-6.2.3 { db close sqlite3 db test.db + execsql { SELECT * FROM sqlite_master } execsql { PRAGMA LOCKING_MODE = EXCLUSIVE } } {exclusive} do_test wal2-6.2.4 { @@ -732,6 +738,7 @@ do_test wal2-6.6.1 { T script lock_control T filter {} sqlite3 db test.db -vfs T + execsql { SELECT * FROM sqlite_master } execsql { PRAGMA locking_mode = exclusive } execsql { INSERT INTO t2 VALUES('V', 'VI') } } {} @@ -755,7 +762,7 @@ do_test wal2-6.6.2 { T filter {} execsql { INSERT INTO t2 VALUES('IX', 'X') } } {} -do_test wal2-6.6.3 { +do_test wal2-6.6.4 { # This time, we have successfully exited exclusive mode. So the second # connection can read the database. sqlite3 db2 test.db -vfs T @@ -1147,6 +1154,127 @@ if {$::tcl_platform(platform) == "unix"} { } catch { db close } } +} + +#------------------------------------------------------------------------- +# Test that "PRAGMA checkpoint_fullsync" appears to be working. +# +foreach {tn sql reslist} { + 1 { } {8 0 3 0 5 0} + 2 { PRAGMA checkpoint_fullfsync = 1 } {8 4 3 2 5 2} + 3 { PRAGMA checkpoint_fullfsync = 0 } {8 0 3 0 5 0} +} { + faultsim_delete_and_reopen + + execsql $sql + do_execsql_test wal2-14.$tn.1 { PRAGMA journal_mode = WAL } {wal} + + set sqlite_sync_count 0 + set sqlite_fullsync_count 0 + + do_execsql_test wal2-14.$tn.2 { + PRAGMA wal_autocheckpoint = 10; + CREATE TABLE t1(a, b); -- 2 wal syncs + INSERT INTO t1 VALUES(1, 2); -- 1 wal sync + PRAGMA wal_checkpoint; -- 1 wal sync, 1 db sync + BEGIN; + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t1 VALUES(5, 6); + COMMIT; -- 1 wal sync + PRAGMA wal_checkpoint; -- 1 wal sync, 1 db sync + } {10} + + do_test wal2-14.$tn.3 { + list $sqlite_sync_count $sqlite_fullsync_count + } [lrange $reslist 0 1] + + set sqlite_sync_count 0 + set sqlite_fullsync_count 0 + + do_test wal2-14.$tn.4 { + execsql { INSERT INTO t1 VALUES(7, zeroblob(12*4096)) } + list $sqlite_sync_count $sqlite_fullsync_count + } [lrange $reslist 2 3] + + set sqlite_sync_count 0 + set sqlite_fullsync_count 0 + + do_test wal2-14.$tn.5 { + execsql { PRAGMA wal_autocheckpoint = 1000 } + execsql { INSERT INTO t1 VALUES(9, 10) } + execsql { INSERT INTO t1 VALUES(11, 12) } + execsql { INSERT INTO t1 VALUES(13, 14) } + db close + list $sqlite_sync_count $sqlite_fullsync_count + } [lrange $reslist 4 5] } +catch { db close } + +# PRAGMA checkpoint_fullsync +# PRAGMA fullfsync +# PRAGMA synchronous +# +foreach {tn settings commit_sync ckpt_sync} { + 1 {0 0 off} {0 0} {0 0} + 2 {0 0 normal} {0 0} {2 0} + 3 {0 0 full} {1 0} {2 0} + + 4 {0 1 off} {0 0} {0 0} + 5 {0 1 normal} {0 0} {0 2} + 6 {0 1 full} {0 1} {0 2} + + 7 {1 0 off} {0 0} {0 0} + 8 {1 0 normal} {0 0} {0 2} + 9 {1 0 full} {1 0} {0 2} + + 10 {1 1 off} {0 0} {0 0} + 11 {1 1 normal} {0 0} {0 2} + 12 {1 1 full} {0 1} {0 2} +} { + forcedelete test.db + + testvfs tvfs -default 1 + tvfs filter xSync + tvfs script xSyncCb + proc xSyncCb {method file fileid flags} { + incr ::sync($flags) + } + + sqlite3 db test.db + do_execsql_test 15.$tn.1 " + CREATE TABLE t1(x); + PRAGMA journal_mode = WAL; + PRAGMA checkpoint_fullfsync = [lindex $settings 0]; + PRAGMA fullfsync = [lindex $settings 1]; + PRAGMA synchronous = [lindex $settings 2]; + " {wal} + + do_test 15.$tn.2 { + set sync(normal) 0 + set sync(full) 0 + execsql { INSERT INTO t1 VALUES('abc') } + list $::sync(normal) $::sync(full) + } $commit_sync + + do_test 15.$tn.3 { + set sync(normal) 0 + set sync(full) 0 + execsql { INSERT INTO t1 VALUES('def') } + list $::sync(normal) $::sync(full) + } $commit_sync + + do_test 15.$tn.4 { + set sync(normal) 0 + set sync(full) 0 + execsql { PRAGMA wal_checkpoint } + list $::sync(normal) $::sync(full) + } $ckpt_sync + + db close + tvfs delete +} + + + finish_test diff --git a/test/wal3.test b/test/wal3.test index b53b8440..2ed39b8f 100644 --- a/test/wal3.test +++ b/test/wal3.test @@ -703,6 +703,8 @@ T delete # the client takes a shared-lock on a slot without modifying the value # and continues. # +set nConn 50 +if { [string match *BSD $tcl_platform(os)] } { set nConn 35 } do_test wal3-9.0 { file delete -force test.db test.db-journal test.db wal sqlite3 db test.db @@ -713,7 +715,7 @@ do_test wal3-9.0 { INSERT INTO whoami VALUES('nobody'); } } {wal} -for {set i 0} {$i < 50} {incr i} { +for {set i 0} {$i < $nConn} {incr i} { set c db$i do_test wal3-9.1.$i { sqlite3 $c test.db @@ -724,7 +726,7 @@ for {set i 0} {$i < 50} {incr i} { } $c } $c } -for {set i 0} {$i < 50} {incr i} { +for {set i 0} {$i < $nConn} {incr i} { set c db$i do_test wal3-9.2.$i { execsql { SELECT * FROM whoami } $c @@ -733,18 +735,50 @@ for {set i 0} {$i < 50} {incr i} { set sz [expr 1024 * (2+$AUTOVACUUM)] do_test wal3-9.3 { - for {set i 0} {$i < 49} {incr i} { db$i close } + for {set i 0} {$i < ($nConn-1)} {incr i} { db$i close } execsql { PRAGMA wal_checkpoint } byte_is_zero test.db [expr $sz-1024] } {1} do_test wal3-9.4 { - db49 close + db[expr $nConn-1] close execsql { PRAGMA wal_checkpoint } set sz2 [file size test.db] byte_is_zero test.db [expr $sz-1024] } {0} -db close +do_multiclient_test tn { + do_test wal3-10.$tn.1 { + sql1 { + PRAGMA page_size = 1024; + CREATE TABLE t1(x); + PRAGMA journal_mode = WAL; + PRAGMA wal_autocheckpoint = 100000; + BEGIN; + INSERT INTO t1 VALUES(randomblob(800)); + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 2 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 4 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 8 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 16 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 32 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 64 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 128 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 256 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 512 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 1024 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 2048 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 4096 + INSERT INTO t1 SELECT randomblob(800) FROM t1; -- 8192 + COMMIT; + CREATE INDEX i1 ON t1(x); + } + + expr {[file size test.db-wal] > [expr 1032*9000]} + } 1 + + do_test wal3-10.$tn.2 { + sql2 {PRAGMA integrity_check} + } {ok} +} finish_test diff --git a/test/wal6.test b/test/wal6.test new file mode 100644 index 00000000..6fae48e9 --- /dev/null +++ b/test/wal6.test @@ -0,0 +1,90 @@ +# 2010 December 1 +# +# 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 implements regression tests for SQLite library. The +# focus of this file is testing the operation of the library in +# "PRAGMA journal_mode=WAL" mode. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/wal_common.tcl +source $testdir/malloc_common.tcl +ifcapable !wal {finish_test ; return } + +#------------------------------------------------------------------------- +# Changing to WAL mode in one connection forces the change in others. +# +db close +forcedelete test.db + +set all_journal_modes {delete persist truncate memory off} +foreach jmode $all_journal_modes { + + do_test wal6-1.0.$jmode { + sqlite3 db test.db + execsql "PRAGMA journal_mode = $jmode;" + } $jmode + + do_test wal6-1.1.$jmode { + execsql { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1 VALUES(1,2); + SELECT * FROM t1; + } + } {1 2} + +# Under Windows, you'll get an error trying to delete +# a file this is already opened. For now, make sure +# we get that error, then close the first connection +# so the other tests work. +if {$tcl_platform(platform)=="windows"} { + if {$jmode=="persist" || $jmode=="truncate"} { + do_test wal6-1.2.$jmode.win { + sqlite3 db2 test.db + catchsql { + PRAGMA journal_mode=WAL; + } db2 + } {1 {disk I/O error}} + db2 close + db close + } +} + + do_test wal6-1.2.$jmode { + sqlite3 db2 test.db + execsql { + PRAGMA journal_mode=WAL; + INSERT INTO t1 VALUES(3,4); + SELECT * FROM t1 ORDER BY a; + } db2 + } {wal 1 2 3 4} + +if {$tcl_platform(platform)=="windows"} { + if {$jmode=="persist" || $jmode=="truncate"} { + sqlite3 db test.db + } +} + + do_test wal6-1.3.$jmode { + execsql { + SELECT * FROM t1 ORDER BY a; + } + } {1 2 3 4} + + db close + db2 close + forcedelete test.db + +} + +finish_test + diff --git a/test/walfault.test b/test/walfault.test index f22a40ec..d0c907ea 100644 --- a/test/walfault.test +++ b/test/walfault.test @@ -446,5 +446,102 @@ do_faultsim_test walfault-12 -prep { faultsim_test_result {0 {}} } +#------------------------------------------------------------------------- +# Test simple recovery, reading and writing a database file using a +# heap-memory wal-index. +# +do_test walfault-13-pre-1 { + faultsim_delete_and_reopen + execsql { + PRAGMA journal_mode = WAL; + PRAGMA wal_autocheckpoint = 0; + BEGIN; + CREATE TABLE abc(a PRIMARY KEY); + INSERT INTO abc VALUES(randomblob(1500)); + INSERT INTO abc VALUES(randomblob(1500)); + COMMIT; + } + faultsim_save_and_close + file delete sv_test.db-shm +} {} + +do_faultsim_test walfault-13.1 -prep { + faultsim_restore_and_reopen +} -body { + db eval { PRAGMA locking_mode = exclusive } + db eval { SELECT count(*) FROM abc } +} -test { + faultsim_test_result {0 2} + if {[file exists test.db-shm]} { error "Not using heap-memory mode" } + faultsim_integrity_check +} + +do_faultsim_test walfault-13.2 -prep { + faultsim_restore_and_reopen + db eval { PRAGMA locking_mode = exclusive } +} -body { + db eval { PRAGMA journal_mode = delete } +} -test { + faultsim_test_result {0 delete} + if {[file exists test.db-shm]} { error "Not using heap-memory mode" } + faultsim_integrity_check +} + +do_test walfault-13-pre-2 { + faultsim_delete_and_reopen + execsql { + BEGIN; + CREATE TABLE abc(a PRIMARY KEY); + INSERT INTO abc VALUES(randomblob(1500)); + INSERT INTO abc VALUES(randomblob(1500)); + COMMIT; + } + faultsim_save_and_close +} {} + +do_faultsim_test walfault-13.3 -prep { + faultsim_restore_and_reopen +} -body { + db eval { + PRAGMA locking_mode = exclusive; + PRAGMA journal_mode = WAL; + INSERT INTO abc VALUES(randomblob(1500)); + } +} -test { + faultsim_test_result {0 {exclusive wal}} + if {[file exists test.db-shm]} { error "Not using heap-memory mode" } + faultsim_integrity_check + set nRow [db eval {SELECT count(*) FROM abc}] + if {!(($nRow==2 && $testrc) || $nRow==3)} { error "Bad db content" } +} + +#------------------------------------------------------------------------- +# Test fault-handling when wrapping around to the start of a WAL file. +# +do_test walfault-14-pre { + faultsim_delete_and_reopen + execsql { + PRAGMA journal_mode = WAL; + BEGIN; + CREATE TABLE abc(a PRIMARY KEY); + INSERT INTO abc VALUES(randomblob(1500)); + INSERT INTO abc VALUES(randomblob(1500)); + COMMIT; + } + faultsim_save_and_close +} {} +do_faultsim_test walfault-14 -prep { + faultsim_restore_and_reopen +} -body { + db eval { + PRAGMA wal_checkpoint; + INSERT INTO abc VALUES(randomblob(1500)); + } +} -test { + faultsim_test_result {0 {}} + faultsim_integrity_check + set nRow [db eval {SELECT count(*) FROM abc}] + if {!(($nRow==2 && $testrc) || $nRow==3)} { error "Bad db content" } +} finish_test diff --git a/test/walmode.test b/test/walmode.test index 2506c90e..11113c6b 100644 --- a/test/walmode.test +++ b/test/walmode.test @@ -207,13 +207,18 @@ do_test walmode-5.1.5 { } } {1 2 3 4 memory} +if {$TEMP_STORE>=2} { + set tempJrnlMode memory +} else { + set tempJrnlMode delete +} do_test walmode-5.2.1 { sqlite3 db "" execsql { PRAGMA main.journal_mode } -} {delete} +} $tempJrnlMode do_test walmode-5.2.2 { execsql { PRAGMA main.journal_mode = wal } -} {delete} +} $tempJrnlMode do_test walmode-5.2.3 { execsql { BEGIN; @@ -223,23 +228,18 @@ do_test walmode-5.2.3 { SELECT * FROM t1; PRAGMA main.journal_mode; } -} {1 2 delete} +} [list 1 2 $tempJrnlMode] do_test walmode-5.2.4 { execsql { PRAGMA main.journal_mode = wal } -} {delete} +} $tempJrnlMode do_test walmode-5.2.5 { execsql { INSERT INTO t1 VALUES(3, 4); SELECT * FROM t1; PRAGMA main.journal_mode; } -} {1 2 3 4 delete} +} [list 1 2 3 4 $tempJrnlMode] -if {$TEMP_STORE>=2} { - set tempJrnlMode memory -} else { - set tempJrnlMode delete -} do_test walmode-5.3.1 { sqlite3 db test.db execsql { PRAGMA temp.journal_mode } diff --git a/test/walnoshm.test b/test/walnoshm.test new file mode 100644 index 00000000..de059dd5 --- /dev/null +++ b/test/walnoshm.test @@ -0,0 +1,184 @@ +# 2010 November 1 +# +# 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 implements regression tests for SQLite library. The +# focus of this file is testing that WAL databases may be accessed without +# using the xShm primitives if the connection is in exclusive-mode. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix walnoshm +ifcapable !wal {finish_test ; return } + +db close +testvfs tvfsshm +testvfs tvfs -default 1 -iversion 1 +sqlite3 db test.db + +#-------------------------------------------------------------------------- +# Test that when using a version 1 VFS, a database can only be converted +# to WAL mode after setting locking_mode=EXCLUSIVE. Also, test that if a +# WAL database is opened using heap-memory for the WAL index, the connection +# cannot change back to locking_mode=NORMAL while the database is still in +# WAL mode. +# +do_execsql_test 1.1 { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); +} + +do_execsql_test 1.2 { + PRAGMA journal_mode = WAL; + SELECT * FROM t1; +} {delete 1 2} +do_test 1.3 { file exists test.db-wal } {0} + +do_execsql_test 1.4 { + PRAGMA locking_mode = exclusive; + PRAGMA journal_mode = WAL; + SELECT * FROM t1; +} {exclusive wal 1 2} +do_test 1.5 { file exists test.db-wal } {1} + +do_execsql_test 1.6 { INSERT INTO t1 VALUES(3, 4) } + +do_execsql_test 1.7 { + PRAGMA locking_mode = normal; +} {exclusive} +do_execsql_test 1.8 { + PRAGMA journal_mode = delete; + PRAGMA main.locking_mode; +} {delete exclusive} +do_execsql_test 1.9 { + PRAGMA locking_mode = normal; +} {normal} +do_execsql_test 1.10 { + SELECT * FROM t1; +} {1 2 3 4} +do_test 1.11 { file exists test.db-wal } {0} + +#------------------------------------------------------------------------- +# +# 2.1.*: Test that a connection using a version 1 VFS can open a WAL database +# and convert it to rollback mode if it is set to use +# locking_mode=exclusive. +# +# 2.2.*: Test that if the exclusive lock cannot be obtained while attempting +# the above, the operation fails and the WAL file is not opened. +# +do_execsql_test 2.1.1 { + CREATE TABLE t2(x, y); + INSERT INTO t2 VALUES('a', 'b'); + INSERT INTO t2 VALUES('c', 'd'); +} +do_execsql_test 2.1.2 { + PRAGMA locking_mode = exclusive; + PRAGMA journal_mode = WAL; + INSERT INTO t2 VALUES('e', 'f'); + INSERT INTO t2 VALUES('g', 'h'); +} {exclusive wal} + +do_test 2.1.3 { + file copy -force test.db test2.db + file copy -force test.db-wal test2.db-wal + sqlite3 db2 test2.db + catchsql { SELECT * FROM t2 } db2 +} {1 {unable to open database file}} +do_test 2.1.4 { + catchsql { PRAGMA journal_mode = delete } db2 +} {1 {unable to open database file}} +do_test 2.1.5 { + execsql { + PRAGMA locking_mode = exclusive; + PRAGMA journal_mode = delete; + SELECT * FROM t2; + } db2 +} {exclusive delete a b c d e f g h} + +do_test 2.2.1 { + file copy -force test.db test2.db + file copy -force test.db-wal test2.db-wal + sqlite3 db3 test2.db -vfs tvfsshm + sqlite3 db2 test2.db + execsql { SELECT * FROM t2 } db3 +} {a b c d e f g h} + +do_test 2.2.2 { + execsql { PRAGMA locking_mode = exclusive } db2 + catchsql { PRAGMA journal_mode = delete } db2 +} {1 {database is locked}} + +do_test 2.2.3 { + # This is to test that [db2] is not holding a PENDING lock (which can + # happen when an attempt to obtain an EXCLUSIVE lock fails). + sqlite3 db4 test2.db -vfs tvfsshm + execsql { SELECT * FROM t2 } db4 +} {a b c d e f g h} + +do_test 2.2.4 { + catchsql { SELECT * FROM t2 } db2 +} {1 {database is locked}} + +do_test 2.2.5 { + db4 close + sqlite3 db4 test2.db -vfs tvfsshm + execsql { SELECT * FROM t2 } db4 +} {a b c d e f g h} + +do_test 2.2.6 { + db3 close + db4 close + execsql { SELECT * FROM t2 } db2 +} {a b c d e f g h} + +db2 close +db close + +#------------------------------------------------------------------------- +# +# 3.1: Test that if locking_mode=EXCLUSIVE is set after the wal file is +# opened, it is possible to drop back to locking_mode=NORMAL. +# +# 3.2: Test that if locking_mode=EXCLUSIVE is set before the wal file is +# opened, it is not. +# +do_test 3.1 { + sqlite3 db test.db -vfs tvfsshm + execsql { + SELECT * FROM t1; + PRAGMA locking_mode = EXCLUSIVE; + INSERT INTO t1 VALUES(5, 6); + PRAGMA locking_mode = NORMAL; + INSERT INTO t1 VALUES(7, 8); + } + sqlite3 db2 test.db -vfs tvfsshm + execsql { SELECT * FROM t1 } db2 +} {1 2 3 4 5 6 7 8} +db close +db2 close +do_test 3.2 { + sqlite3 db test.db -vfs tvfsshm + execsql { + PRAGMA locking_mode = EXCLUSIVE; + INSERT INTO t1 VALUES(9, 10); + PRAGMA locking_mode = NORMAL; + INSERT INTO t1 VALUES(11, 12); + } + sqlite3 db2 test.db -vfs tvfsshm + catchsql { SELECT * FROM t1 } db2 +} {1 {database is locked}} +db close +db2 close + +tvfs delete +tvfsshm delete + +finish_test diff --git a/test/walshared.test b/test/walshared.test index 658a25f9..73a3fb8b 100644 --- a/test/walshared.test +++ b/test/walshared.test @@ -15,6 +15,9 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl + +ifcapable !wal {finish_test ; return } + db close set ::enable_shared_cache [sqlite3_enable_shared_cache 1] diff --git a/test/where3.test b/test/where3.test index 9fa4cf99..c032f1f0 100644 --- a/test/where3.test +++ b/test/where3.test @@ -217,48 +217,129 @@ do_test where3-2.7 { # the planner into use a table for the outer loop that might be indexable # if held until an inner loop. # -do_test where3-3.0 { - execsql { - CREATE TABLE t301(a INTEGER PRIMARY KEY,b,c); - CREATE INDEX t301c ON t301(c); - INSERT INTO t301 VALUES(1,2,3); - CREATE TABLE t302(x, y); - ANALYZE; - explain query plan - SELECT * FROM t302, t301 WHERE t302.x=5 AND t301.a=t302.y; - } -} {0 0 {TABLE t302} 1 1 {TABLE t301 USING PRIMARY KEY}} -do_test where3-3.1 { - execsql { - explain query plan - SELECT * FROM t301, t302 WHERE t302.x=5 AND t301.a=t302.y; - } -} {0 1 {TABLE t302} 1 0 {TABLE t301 USING PRIMARY KEY}} +do_execsql_test where3-3.0 { + CREATE TABLE t301(a INTEGER PRIMARY KEY,b,c); + CREATE INDEX t301c ON t301(c); + INSERT INTO t301 VALUES(1,2,3); + CREATE TABLE t302(x, y); + ANALYZE; + explain query plan SELECT * FROM t302, t301 WHERE t302.x=5 AND t301.a=t302.y; +} { + 0 0 0 {SCAN TABLE t302 (~0 rows)} + 0 1 1 {SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} +} +do_execsql_test where3-3.1 { + explain query plan + SELECT * FROM t301, t302 WHERE t302.x=5 AND t301.a=t302.y; +} { + 0 0 1 {SCAN TABLE t302 (~0 rows)} + 0 1 0 {SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} +} # Verify that when there are multiple tables in a join which must be # full table scans that the query planner attempts put the table with # the fewest number of output rows as the outer loop. # -do_test where3-4.0 { - execsql { - CREATE TABLE t400(a INTEGER PRIMARY KEY, b, c); - CREATE TABLE t401(p INTEGER PRIMARY KEY, q, r); - CREATE TABLE t402(x INTEGER PRIMARY KEY, y, z); - EXPLAIN QUERY PLAN - SELECT * FROM t400, t401, t402 WHERE t402.z GLOB 'abc*'; - } -} {0 2 {TABLE t402} 1 0 {TABLE t400} 2 1 {TABLE t401}} -do_test where3-4.1 { - execsql { - EXPLAIN QUERY PLAN - SELECT * FROM t400, t401, t402 WHERE t401.r GLOB 'abc*'; - } -} {0 1 {TABLE t401} 1 0 {TABLE t400} 2 2 {TABLE t402}} -do_test where3-4.2 { - execsql { - EXPLAIN QUERY PLAN - SELECT * FROM t400, t401, t402 WHERE t400.c GLOB 'abc*'; - } -} {0 0 {TABLE t400} 1 1 {TABLE t401} 2 2 {TABLE t402}} +do_execsql_test where3-4.0 { + CREATE TABLE t400(a INTEGER PRIMARY KEY, b, c); + CREATE TABLE t401(p INTEGER PRIMARY KEY, q, r); + CREATE TABLE t402(x INTEGER PRIMARY KEY, y, z); + EXPLAIN QUERY PLAN + SELECT * FROM t400, t401, t402 WHERE t402.z GLOB 'abc*'; +} { + 0 0 2 {SCAN TABLE t402 (~500000 rows)} + 0 1 0 {SCAN TABLE t400 (~1000000 rows)} + 0 2 1 {SCAN TABLE t401 (~1000000 rows)} +} +do_execsql_test where3-4.1 { + EXPLAIN QUERY PLAN + SELECT * FROM t400, t401, t402 WHERE t401.r GLOB 'abc*'; +} { + 0 0 1 {SCAN TABLE t401 (~500000 rows)} + 0 1 0 {SCAN TABLE t400 (~1000000 rows)} + 0 2 2 {SCAN TABLE t402 (~1000000 rows)} +} +do_execsql_test where3-4.2 { + EXPLAIN QUERY PLAN + SELECT * FROM t400, t401, t402 WHERE t400.c GLOB 'abc*'; +} { + 0 0 0 {SCAN TABLE t400 (~500000 rows)} + 0 1 1 {SCAN TABLE t401 (~1000000 rows)} + 0 2 2 {SCAN TABLE t402 (~1000000 rows)} +} + +# Verify that a performance regression encountered by firefox +# has been fixed. +# +do_execsql_test where3-5.0 { + CREATE TABLE aaa (id INTEGER PRIMARY KEY, type INTEGER, + fk INTEGER DEFAULT NULL, parent INTEGER, + position INTEGER, title LONGVARCHAR, + keyword_id INTEGER, folder_type TEXT, + dateAdded INTEGER, lastModified INTEGER); + CREATE INDEX aaa_111 ON aaa (fk, type); + CREATE INDEX aaa_222 ON aaa (parent, position); + CREATE INDEX aaa_333 ON aaa (fk, lastModified); + CREATE TABLE bbb (id INTEGER PRIMARY KEY, type INTEGER, + fk INTEGER DEFAULT NULL, parent INTEGER, + position INTEGER, title LONGVARCHAR, + keyword_id INTEGER, folder_type TEXT, + dateAdded INTEGER, lastModified INTEGER); + CREATE INDEX bbb_111 ON bbb (fk, type); + CREATE INDEX bbb_222 ON bbb (parent, position); + CREATE INDEX bbb_333 ON bbb (fk, lastModified); + + EXPLAIN QUERY PLAN + SELECT bbb.title AS tag_title + FROM aaa JOIN bbb ON bbb.id = aaa.parent + WHERE aaa.fk = 'constant' + AND LENGTH(bbb.title) > 0 + AND bbb.parent = 4 + ORDER BY bbb.title COLLATE NOCASE ASC; +} { + 0 0 0 {SEARCH TABLE aaa USING INDEX aaa_333 (fk=?) (~10 rows)} + 0 1 1 {SEARCH TABLE bbb USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +do_execsql_test where3-5.1 { + EXPLAIN QUERY PLAN + SELECT bbb.title AS tag_title + FROM aaa JOIN aaa AS bbb ON bbb.id = aaa.parent + WHERE aaa.fk = 'constant' + AND LENGTH(bbb.title) > 0 + AND bbb.parent = 4 + ORDER BY bbb.title COLLATE NOCASE ASC; +} { + 0 0 0 {SEARCH TABLE aaa USING INDEX aaa_333 (fk=?) (~10 rows)} + 0 1 1 {SEARCH TABLE aaa AS bbb USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +do_execsql_test where3-5.2 { + EXPLAIN QUERY PLAN + SELECT bbb.title AS tag_title + FROM bbb JOIN aaa ON bbb.id = aaa.parent + WHERE aaa.fk = 'constant' + AND LENGTH(bbb.title) > 0 + AND bbb.parent = 4 + ORDER BY bbb.title COLLATE NOCASE ASC; +} { + 0 0 1 {SEARCH TABLE aaa USING INDEX aaa_333 (fk=?) (~10 rows)} + 0 1 0 {SEARCH TABLE bbb USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} +do_execsql_test where3-5.3 { + EXPLAIN QUERY PLAN + SELECT bbb.title AS tag_title + FROM aaa AS bbb JOIN aaa ON bbb.id = aaa.parent + WHERE aaa.fk = 'constant' + AND LENGTH(bbb.title) > 0 + AND bbb.parent = 4 + ORDER BY bbb.title COLLATE NOCASE ASC; +} { + 0 0 1 {SEARCH TABLE aaa USING INDEX aaa_333 (fk=?) (~10 rows)} + 0 1 0 {SEARCH TABLE aaa AS bbb USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} + finish_test diff --git a/test/where7.test b/test/where7.test index dbb9bc6b..e34778c7 100644 --- a/test/where7.test +++ b/test/where7.test @@ -23299,6 +23299,52 @@ do_test where7-2.1001.2 { OR a=91 } } {2 22 23 28 54 80 91 scan 0 sort 0} -finish_test + +# test case for the performance regression fixed by +# check-in 28ba6255282b on 2010-10-21 02:05:06 +# +# The test case that follows is code from an actual +# application with identifiers change and unused columns +# remove. +# +do_execsql_test where7-3.1 { + CREATE TABLE t301 ( + c8 INTEGER PRIMARY KEY, + c6 INTEGER, + c4 INTEGER, + c7 INTEGER, + FOREIGN KEY (c4) REFERENCES series(c4) + ); + CREATE INDEX t301_c6 on t301(c6); + CREATE INDEX t301_c4 on t301(c4); + CREATE INDEX t301_c7 on t301(c7); + + CREATE TABLE t302 ( + c1 INTEGER PRIMARY KEY, + c8 INTEGER, + c5 INTEGER, + c3 INTEGER, + c2 INTEGER, + c4 INTEGER, + FOREIGN KEY (c8) REFERENCES t301(c8) + ); + CREATE INDEX t302_c3 on t302(c3); + CREATE INDEX t302_c8_c3 on t302(c8, c3); + CREATE INDEX t302_c5 on t302(c5); + + EXPLAIN QUERY PLAN + SELECT t302.c1 + FROM t302 JOIN t301 ON t302.c8 = t301.c8 + WHERE t302.c2 = 19571 + AND t302.c3 > 1287603136 + AND (t301.c4 = 1407449685622784 + OR t301.c8 = 1407424651264000) + ORDER BY t302.c5 LIMIT 200; +} { + 0 0 1 {SEARCH TABLE t301 USING COVERING INDEX t301_c4 (c4=?) (~10 rows)} + 0 0 1 {SEARCH TABLE t301 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} + 0 1 0 {SEARCH TABLE t302 USING INDEX t302_c8_c3 (c8=? AND c3>?) (~2 rows)} + 0 0 0 {USE TEMP B-TREE FOR ORDER BY} +} finish_test diff --git a/test/where9.test b/test/where9.test index bf72a716..1ffc8ef9 100644 --- a/test/where9.test +++ b/test/where9.test @@ -358,32 +358,25 @@ do_test where9-2.8 { ifcapable explain { - do_test where9-3.1 { - set r [db eval { - EXPLAIN QUERY PLAN - SELECT t2.a FROM t1, t2 - WHERE t1.a=80 - AND ((t1.c=t2.c AND t1.d=t2.d) OR t1.f=t2.f) - }] - set a [expr {[lsearch $r {TABLE t2 VIA MULTI-INDEX UNION}]>=0}] - set b [expr {[lsearch $r {TABLE t2 WITH INDEX t2f}]>=0}] - set c [expr {([lsearch $r {TABLE t2 WITH INDEX t2c}]>=0)+ - [lsearch $r {TABLE t2 WITH INDEX t2d}]>=0}] - concat $a $b $c - } {1 1 1} - do_test where9-3.2 { - set r [db eval { - EXPLAIN QUERY PLAN - SELECT coalesce(t2.a,9999) - FROM t1 LEFT JOIN t2 ON (t1.c+1=t2.c AND t1.d=t2.d) OR (t1.f||'x')=t2.f - WHERE t1.a=80 - }] - set a [expr {[lsearch $r {TABLE t2 VIA MULTI-INDEX UNION}]>=0}] - set b [expr {[lsearch $r {TABLE t2 WITH INDEX t2f}]>=0}] - set c [expr {([lsearch $r {TABLE t2 WITH INDEX t2c}]>=0)+ - [lsearch $r {TABLE t2 WITH INDEX t2d}]>=0}] - concat $a $b $c - } {1 1 1} + do_execsql_test where9-3.1 { + EXPLAIN QUERY PLAN + SELECT t2.a FROM t1, t2 + WHERE t1.a=80 AND ((t1.c=t2.c AND t1.d=t2.d) OR t1.f=t2.f) + } { + 0 0 0 {SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} + 0 1 1 {SEARCH TABLE t2 USING INDEX t2d (d=?) (~2 rows)} + 0 1 1 {SEARCH TABLE t2 USING COVERING INDEX t2f (f=?) (~10 rows)} + } + do_execsql_test where9-3.2 { + EXPLAIN QUERY PLAN + SELECT coalesce(t2.a,9999) + FROM t1 LEFT JOIN t2 ON (t1.c+1=t2.c AND t1.d=t2.d) OR (t1.f||'x')=t2.f + WHERE t1.a=80 + } { + 0 0 0 {SEARCH TABLE t1 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} + 0 1 1 {SEARCH TABLE t2 USING INDEX t2d (d=?) (~2 rows)} + 0 1 1 {SEARCH TABLE t2 USING COVERING INDEX t2f (f=?) (~10 rows)} + } } # Make sure that INDEXED BY and multi-index OR clauses play well with @@ -458,46 +451,29 @@ ifcapable explain { # The (c=31031 OR d IS NULL) clause is preferred over b>1000 because # the former is an equality test which is expected to return fewer rows. # - do_test where9-5.1 { - set r [db eval { - EXPLAIN QUERY PLAN - SELECT a FROM t1 - WHERE b>1000 - AND (c=31031 OR d IS NULL) - }] - set a [expr {[lsearch $r {TABLE t1 VIA MULTI-INDEX UNION}]>=0}] - set b [expr {[lsearch $r {TABLE t1 WITH INDEX t1b}]>=0}] - concat $a $b - } {1 0} + do_execsql_test where9-5.1 { + EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE b>1000 AND (c=31031 OR d IS NULL) + } { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1c (c=?) (~10 rows)} + 0 0 0 {SEARCH TABLE t1 USING INDEX t1d (d=?) (~10 rows)} + } # In contrast, b=1000 is preferred over any OR-clause. # - do_test where9-5.2 { - set r [db eval { - EXPLAIN QUERY PLAN - SELECT a FROM t1 - WHERE b=1000 - AND (c=31031 OR d IS NULL) - }] - set a [expr {[lsearch $r {TABLE t1 VIA MULTI-INDEX UNION}]>=0}] - set b [expr {[lsearch $r {TABLE t1 WITH INDEX t1b}]>=0}] - concat $a $b - } {0 1} + do_execsql_test where9-5.2 { + EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE b=1000 AND (c=31031 OR d IS NULL) + } { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?) (~5 rows)} + } # Likewise, inequalities in an AND are preferred over inequalities in # an OR. # - do_test where9-5.3 { - set r [db eval { - EXPLAIN QUERY PLAN - SELECT a FROM t1 - WHERE b>1000 - AND (c>=31031 OR d IS NULL) - }] - set a [expr {[lsearch $r {TABLE t1 VIA MULTI-INDEX UNION}]>=0}] - set b [expr {[lsearch $r {TABLE t1 WITH INDEX t1b}]>=0}] - concat $a $b - } {0 1} + do_execsql_test where9-5.3 { + EXPLAIN QUERY PLAN SELECT a FROM t1 WHERE b>1000 AND (c>=31031 OR d IS NULL) + } { + 0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b>?) (~165000 rows)} + } } ############################################################################ diff --git a/tool/lemon.c b/tool/lemon.c index 70d7c1cb..898022e2 100644 --- a/tool/lemon.c +++ b/tool/lemon.c @@ -20,7 +20,13 @@ #endif #ifdef __WIN32__ -extern int access(); +#ifdef __cplusplus +extern "C" { +#endif +extern int access(const char *path, int mode); +#ifdef __cplusplus +} +#endif #else #include #endif @@ -3263,7 +3269,7 @@ PRIVATE char *append_str(const char *zText, int n, int p1, int p2){ } n = lemonStrlen(zText); } - if( n+sizeof(zInt)*2+used >= alloced ){ + if( (int) (n+sizeof(zInt)*2+used) >= alloced ){ alloced = n + sizeof(zInt)*2 + used + 200; z = (char *) realloc(z, alloced); } diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl index da313069..c3216f6e 100644 --- a/tool/mksqlite3c.tcl +++ b/tool/mksqlite3c.tcl @@ -54,7 +54,7 @@ puts $out [subst \ ** single large file, the entire code can be compiled as a one translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements -** of 5% are more are commonly seen when SQLite is compiled as a single +** of 5% or more are commonly seen when SQLite is compiled as a single ** translation unit. ** ** This file is all you need to compile SQLite. To use SQLite in other diff --git a/tool/mksqlite3h.tcl b/tool/mksqlite3h.tcl index 58fe8a89..554069c3 100644 --- a/tool/mksqlite3h.tcl +++ b/tool/mksqlite3h.tcl @@ -53,7 +53,7 @@ set in [open $TOP/manifest] set zDate {} while {![eof $in]} { set line [gets $in] - if {[regexp {^D (2.*[0-9])} $line all date]} { + if {[regexp {^D (2[-0-9T:]+)} $line all date]} { set zDate [string map {T { }} $date] break } @@ -65,32 +65,39 @@ close $in set varpattern {^[a-zA-Z][a-zA-Z_0-9 *]+sqlite3_[_a-zA-Z0-9]+(\[|;| =)} set declpattern {^ *[a-zA-Z][a-zA-Z_0-9 ]+ \**sqlite3_[_a-zA-Z0-9]+\(} -# Process the src/sqlite.h.in file. +# Process the src/sqlite.h.in ext/rtree/sqlite3rtree.h files. # -set in [open $TOP/src/sqlite.h.in] -while {![eof $in]} { +foreach file [list $TOP/src/sqlite.h.in $TOP/ext/rtree/sqlite3rtree.h] { + set in [open $file] + while {![eof $in]} { + + set line [gets $in] - set line [gets $in] - - regsub -- --VERS-- $line $zVersion line - regsub -- --VERSION-NUMBER-- $line $nVersion line - regsub -- --SOURCE-ID-- $line "$zDate $zUuid" line - - if {[regexp {define SQLITE_EXTERN extern} $line]} { + # File sqlite3rtree.h contains a line "#include ". Omit this + # line when copying sqlite3rtree.h into sqlite3.h. + # + if {[string match {*#include**} $line]} continue + + regsub -- --VERS-- $line $zVersion line + regsub -- --VERSION-NUMBER-- $line $nVersion line + regsub -- --SOURCE-ID-- $line "$zDate $zUuid" line + + if {[regexp {define SQLITE_EXTERN extern} $line]} { + puts $line + puts [gets $in] + puts "" + puts "#ifndef SQLITE_API" + puts "# define SQLITE_API" + puts "#endif" + set line "" + } + + if {([regexp $varpattern $line] && ![regexp {^ *typedef} $line]) + || ([regexp $declpattern $line]) + } { + set line "SQLITE_API $line" + } puts $line - puts [gets $in] - puts "" - puts "#ifndef SQLITE_API" - puts "# define SQLITE_API" - puts "#endif" - set line "" } - - if {([regexp $varpattern $line] && ![regexp {^ *typedef} $line]) - || ([regexp $declpattern $line]) - } { - set line "SQLITE_API $line" - } - puts $line + close $in } -close $in diff --git a/tool/shell1.test b/tool/shell1.test index f21601a4..e33013d6 100644 --- a/tool/shell1.test +++ b/tool/shell1.test @@ -200,7 +200,7 @@ do_test shell1-1.15.3 { # -version show SQLite version do_test shell1-1.16.1 { catchcmd "-version test.db" "" -} {0 3.7.1} +} {0 3.7.5} #---------------------------------------------------------------------------- # Test cases shell1-2.*: Basic "dot" command token parsing. diff --git a/tool/showdb.c b/tool/showdb.c index f313b1af..c954153c 100644 --- a/tool/showdb.c +++ b/tool/showdb.c @@ -57,8 +57,9 @@ static void out_of_memory(void){ */ static unsigned char *getContent(int ofst, int nByte){ unsigned char *aData; - aData = malloc(nByte); + aData = malloc(nByte+32); if( aData==0 ) out_of_memory(); + memset(aData, 0, nByte+32); lseek(db, ofst, SEEK_SET); read(db, aData, nByte); return aData; @@ -181,56 +182,189 @@ static void print_db_header(void){ } /* -** Create a description for a single cell. +** Describe cell content. */ -static int describeCell(unsigned char cType, unsigned char *a, char **pzDesc){ +static int describeContent( + unsigned char *a, /* Cell content */ + int nLocal, /* Bytes in a[] */ + char *zDesc /* Write description here */ +){ + int nDesc = 0; + int n, i, j; + i64 x, v; + const unsigned char *pData; + const unsigned char *pLimit; + char sep = ' '; + + pLimit = &a[nLocal]; + n = decodeVarint(a, &x); + pData = &a[x]; + a += n; + i = x - n; + while( i>0 && pData<=pLimit ){ + n = decodeVarint(a, &x); + a += n; + i -= n; + nLocal -= n; + zDesc[0] = sep; + sep = ','; + nDesc++; + zDesc++; + if( x==0 ){ + sprintf(zDesc, "*"); /* NULL is a "*" */ + }else if( x>=1 && x<=6 ){ + v = (signed char)pData[0]; + pData++; + switch( x ){ + case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; + case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; + case 4: v = (v<<8) + pData[0]; pData++; + case 3: v = (v<<8) + pData[0]; pData++; + case 2: v = (v<<8) + pData[0]; pData++; + } + sprintf(zDesc, "%lld", v); + }else if( x==7 ){ + sprintf(zDesc, "real"); + pData += 8; + }else if( x==8 ){ + sprintf(zDesc, "0"); + }else if( x==9 ){ + sprintf(zDesc, "1"); + }else if( x>=12 ){ + int size = (x-12)/2; + if( (x&1)==0 ){ + sprintf(zDesc, "blob(%d)", size); + }else{ + sprintf(zDesc, "txt(%d)", size); + } + pData += size; + } + j = strlen(zDesc); + zDesc += j; + nDesc += j; + } + return nDesc; +} + +/* +** Compute the local payload size given the total payload size and +** the page size. +*/ +static int localPayload(i64 nPayload, char cType){ + int maxLocal; + int minLocal; + int surplus; + int nLocal; + if( cType==13 ){ + /* Table leaf */ + maxLocal = pagesize-35; + minLocal = (pagesize-12)*32/255-23; + }else{ + maxLocal = (pagesize-12)*64/255-23; + minLocal = (pagesize-12)*32/255-23; + } + if( nPayload>maxLocal ){ + surplus = minLocal + (nPayload-minLocal)%(pagesize-4); + if( surplus<=maxLocal ){ + nLocal = surplus; + }else{ + nLocal = minLocal; + } + }else{ + nLocal = nPayload; + } + return nLocal; +} + + +/* +** Create a description for a single cell. +** +** The return value is the local cell size. +*/ +static int describeCell( + unsigned char cType, /* Page type */ + unsigned char *a, /* Cell content */ + int showCellContent, /* Show cell content if true */ + char **pzDesc /* Store description here */ +){ int i; int nDesc = 0; int n = 0; int leftChild; i64 nPayload; i64 rowid; - static char zDesc[100]; + int nLocal; + static char zDesc[1000]; i = 0; if( cType<=5 ){ leftChild = ((a[0]*256 + a[1])*256 + a[2])*256 + a[3]; a += 4; n += 4; - sprintf(zDesc, "left-child: %d ", leftChild); + sprintf(zDesc, "lx: %d ", leftChild); nDesc = strlen(zDesc); } if( cType!=5 ){ i = decodeVarint(a, &nPayload); a += i; n += i; - sprintf(&zDesc[nDesc], "sz: %lld ", nPayload); + sprintf(&zDesc[nDesc], "n: %lld ", nPayload); nDesc += strlen(&zDesc[nDesc]); + nLocal = localPayload(nPayload, cType); + }else{ + nPayload = nLocal = 0; } if( cType==5 || cType==13 ){ i = decodeVarint(a, &rowid); a += i; n += i; - sprintf(&zDesc[nDesc], "rowid: %lld ", rowid); + sprintf(&zDesc[nDesc], "r: %lld ", rowid); nDesc += strlen(&zDesc[nDesc]); } + if( nLocal0 ){ + printf(" key: lx=left-child n=payload-size r=rowid\n"); + } + if( showMap ){ + zMap = malloc(pagesize); + memset(zMap, '.', pagesize); + memset(zMap, '1', hdrSize); + memset(&zMap[hdrSize], 'H', iCellPtr); + memset(&zMap[hdrSize+iCellPtr], 'P', 2*nCell); + } for(i=0; i #include -#include -#include -#include -#include #include +#include +/* +** state information +*/ +static int pageSize = 1024; +static int sectorSize = 512; +static FILE *db = 0; +static int showPageContent = 0; +static int fileSize = 0; +static unsigned cksumNonce = 0; -static int pagesize = 1024; -static int db = -1; -static int mxPage = 0; - +/* Report a memory allocation error */ static void out_of_memory(void){ fprintf(stderr,"Out of memory...\n"); exit(1); } -static print_page(int iPg){ - unsigned char *aData; - int i, j; - aData = malloc(pagesize); - if( aData==0 ) out_of_memory(); - read(db, aData, pagesize); - fprintf(stdout, "Page %d:\n", iPg); - for(i=0; i=nByte ){ + sprintf(&zBuf[i], " "); + }else{ + sprintf(&zBuf[i], " %02x", aData[ofst+j]); + val = val*256 + aData[ofst+j]; + } + i += strlen(&zBuf[i]); + } + sprintf(&zBuf[i], " %10u", val); + printf("%s %s\n", zBuf, zMsg); + return val; +} + +/* +** Read and print a journal header. Store key information (page size, etc) +** in global variables. +*/ +static unsigned decode_journal_header(int iOfst){ + char *pHdr = read_content(64, iOfst); + unsigned nPage; + printf("Header at offset %d:\n", iOfst); + print_decode_line(pHdr, 0, 4, "Header part 1 (3654616569)"); + print_decode_line(pHdr, 4, 4, "Header part 2 (547447767)"); + nPage = + print_decode_line(pHdr, 8, 4, "page count"); + cksumNonce = + print_decode_line(pHdr, 12, 4, "chksum nonce"); + print_decode_line(pHdr, 16, 4, "initial database size in pages"); + sectorSize = + print_decode_line(pHdr, 20, 4, "sector size"); + pageSize = + print_decode_line(pHdr, 24, 4, "page size"); + print_decode_line(pHdr, 28, 4, "zero"); + print_decode_line(pHdr, 32, 4, "zero"); + print_decode_line(pHdr, 36, 4, "zero"); + print_decode_line(pHdr, 40, 4, "zero"); + free(pHdr); + return nPage; +} + +static void print_page(int iOfst){ + unsigned char *aData; + char zTitle[50]; + aData = read_content(pageSize+8, iOfst); + sprintf(zTitle, "page number for page at offset %d", iOfst); + print_decode_line(aData, 0, 4, zTitle); free(aData); } int main(int argc, char **argv){ - struct stat sbuf; - unsigned int u; int rc; - unsigned char zBuf[10]; - unsigned char zBuf2[sizeof(u)]; + int nPage, cnt; + int iOfst; if( argc!=2 ){ fprintf(stderr,"Usage: %s FILENAME\n", argv[0]); exit(1); } - db = open(argv[1], O_RDONLY); - if( db<0 ){ + db = fopen(argv[1], "rb"); + if( db==0 ){ fprintf(stderr,"%s: can't open %s\n", argv[0], argv[1]); exit(1); } - read(db, zBuf, 8); - if( zBuf[7]==0xd6 ){ - read(db, &u, sizeof(u)); - printf("Records in Journal: %u\n", u); - read(db, &u, sizeof(u)); - printf("Magic Number: 0x%08x\n", u); - } - read(db, zBuf2, sizeof(zBuf2)); - u = zBuf2[0]<<24 | zBuf2[1]<<16 | zBuf2[2]<<8 | zBuf2[3]; - printf("Database Size: %u\n", u); - while( read(db, zBuf2, sizeof(zBuf2))==sizeof(zBuf2) ){ - u = zBuf2[0]<<24 | zBuf2[1]<<16 | zBuf2[2]<<8 | zBuf2[3]; - print_page(u); - if( zBuf[7]==0xd6 ){ - read(db, &u, sizeof(u)); - printf("Checksum: 0x%08x\n", u); + fseek(db, 0, SEEK_END); + fileSize = ftell(db); + printf("journal file size: %d bytes\n", fileSize); + fseek(db, 0, SEEK_SET); + iOfst = 0; + while( iOfst